光伏无法自动起来的问题

This commit is contained in:
2026-05-07 15:40:49 +08:00
parent 0d2f47fe0e
commit 42731a837d
2 changed files with 236 additions and 16 deletions

View File

@@ -0,0 +1,22 @@
---
trigger: always_on
---
# 这个是一个Ems 软件程序控制储能柜内BMS,PCS,消防,液冷的协调和控制,现场有两个储能柜,都是用本程序进行控制,只不过配置信息不一样,其中一个叫做主柜,另一个叫做从柜。
# 主控柜连接的设备包括储能柜柜内BMS,PCS,消防,液冷;各个电表()
# 从控柜连接的设备包括储能柜柜内BMS,PCS,消防,液冷
# 从控柜的充放电是受到主控柜控制的主控柜的EMS是汇聚所有设备的信息并控制所有的设备
# 这个项目有两个用电节点,一个是管理大楼,一个是税务大楼,项目中包括光伏发电,储能,负载这些,本项目除了控制两个储能的充放电外,还要确保光伏发电不能馈电到电网中,在确保满足负载使用的情况下不让光伏发电溢出到电网中,自发自用
# 本次光储充每天是一充一放,根据配置的时间进行削峰填谷套利,当然也要确保光伏的自发自用,不能馈电到电网中。
# 本项目有4个电操控制不同的电操让两个储能柜协调给管理大楼和税务大楼侧光伏
1号 4号电操闭合2号 3号电操断开主控储能柜给管理大楼充放电从储能柜根据税务大楼的负载情况和光伏发电情况是否需要消纳光伏防止光伏馈电掉电网
2号 3号电操闭合1号 4号电操断开从控储能柜给管理大楼充放电主控储能柜在税务大楼侧根据税务大楼的负载情况和光伏发电情况是否需要消纳光伏防止光伏馈电掉电网
两个储能夜里充电时只能从管理大楼侧进行充电根据线路结构的要求夜间充电时同一时刻只有一个储能进行充电充到要求的SOC时再切换电操到让另一个储能对接管理大楼进行充电不能从税务大楼侧进行充电任何一个储能线路对接到税务大楼时都不能放电到税务大楼只是根据当前的税务大楼的负载和光伏情况决定是否需要消纳光伏发电防止光伏馈电掉电网
# 当前的项目程序已经能正常运行了运行了半年只不过有点小问题需要更改和完善或者重构程序。当前项目程序跟设备层面的连接是经过测试和验证的有modbusTCP,RS485,CAN通信
# 整个项目程序协调这些进行控制,在确保不馈电到电网的前提下,实现储能的充放电控制和光伏的消纳控制,实现削峰填谷的利益最大化。
# 里面的模式切换时需要考虑电操切换的安全性因为电操切换时会导致两个储能柜和电网断开连接柜内的PCS光伏液冷等都要关闭切换完成后再启动切换断电时储能柜内有个小电池可以维持柜内设备的正常运行确保EMS运行半个小时左右。
# 因为周六周日园区不上班,周五晚上和周六晚上就不需要充电了,周六周日白天时负载很小,可以让储能柜当时对接税务大楼的储能接受光伏的充电,这样能最大化收益。其他时间的晚上就正常充电,白天放电,削峰填谷套利。
# 当前程序使用了状态机来管理程序的运行状态,请在这些状态机上进行完善编程
# 更改程序时要谨慎,要充分考虑各种情况,确保程序的稳定性和可靠性,要详细的注释代码
# 主要核心就是协调两个储能柜管理大楼负责税务大楼负载光伏使他们协调工作达到稳态不馈电到电网中当然两个储能柜内的各个系统PCS,BMS电池消防液冷也要协调工作防止他们报错影响整个系统的运行。
# 夜间充电时段是当天的23:00 到第二天的凌晨5:00这个时间段认为是默认夜间的充电时间这个时间也没有光伏发电的干扰。的其他的都认为是根据负载正常放电时间当然白天有光伏可能充电。主要是靠削峰填谷和光伏发电产生收益。

View File

@@ -269,12 +269,156 @@ namespace OrpaonEMS.App.Services
/// <exception cref="NotImplementedException"></exception> /// <exception cref="NotImplementedException"></exception>
private void SolarCurBackFlow_TrigTimeOutHandler(object? sender, EventArgs e) private void SolarCurBackFlow_TrigTimeOutHandler(object? sender, EventArgs e)
{ {
//关闭光伏 var now = DateTime.Now;
CloseSolar(); _solarBackflowClosePending = true;
Console.WriteLine($"时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}-【月浦】- 软件检测到光伏逆流,关闭光伏逆变器 "); _lastSolarBackflowCloseTime = now;
_solarRecoverImportTrigActive = false;
_solarRecoverImportTrigStartTime = DateTime.MinValue;
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-软件检测到光伏逆流,关闭光伏逆变器"); var closeOk = TryCloseSolarThrottled($"逆流触发-并网点功率:{SolarEleMeter5.RtPw}");
Console.WriteLine($"时间:{now.ToString("yyyy-MM-dd HH:mm:ss")}-【月浦】- 软件检测到光伏逆流,关闭光伏逆变器 ");
LogService.Info($"时间:{now}-【动作】-软件检测到光伏逆流,关闭光伏逆变器,关闭结果:{closeOk},状态:{CurESChargInfo},并网点功率:{SolarEleMeter5.RtPw}");
}
private const int SolarRecoverCooldownSeconds = 60;
private const int SolarCmdMinIntervalSeconds = 3;
private DateTime _lastSolarBackflowCloseTime = DateTime.MinValue;
private bool _solarBackflowClosePending = false;
private DateTime _lastSolarOpenCmdTime = DateTime.MinValue;
private DateTime _lastSolarCloseCmdTime = DateTime.MinValue;
private bool _solarRecoverImportTrigActive = false;
private DateTime _solarRecoverImportTrigStartTime = DateTime.MinValue;
/// <summary>
/// 判断逆流触发关闭后,是否已经过了冷却时间,允许再次尝试启动光伏。
/// </summary>
/// <returns>True 表示允许尝试启动光伏</returns>
private bool IsSolarOpenAllowedAfterBackflowCooldown()
{
if (!_solarBackflowClosePending)
{
return true;
}
return (DateTime.Now - _lastSolarBackflowCloseTime).TotalSeconds >= SolarRecoverCooldownSeconds;
}
/// <summary>
/// 下发启动光伏命令(带节流与异常捕获)。
/// </summary>
/// <param name="reason">触发原因,用于日志定位</param>
/// <returns>命令写入成功返回 True否则返回 False</returns>
private bool TryOpenSolarThrottled(string reason)
{
var now = DateTime.Now;
if ((now - _lastSolarOpenCmdTime).TotalSeconds < SolarCmdMinIntervalSeconds)
{
return false;
}
_lastSolarOpenCmdTime = now;
try
{
var ok = OpenSolar();
if (ok)
{
LogService.Info($"时间:{now}-【光伏启动】-已下发启动命令,原因:{reason},状态:{CurESChargInfo},并网点功率:{SolarEleMeter5.RtPw},通信:{SolarLinkState}");
}
if (!ok)
{
LogService.Info($"时间:{now}-【光伏启动】-失败,原因:{reason},状态:{CurESChargInfo},并网点功率:{SolarEleMeter5.RtPw},通信:{SolarLinkState}");
}
return ok;
}
catch (Exception ex)
{
LogService.Info($"时间:{now}-【光伏启动】-异常,原因:{reason},状态:{CurESChargInfo}{ex.Message}");
return false;
}
}
private bool TryCloseSolarThrottled(string reason)
{
var now = DateTime.Now;
if ((now - _lastSolarCloseCmdTime).TotalSeconds < SolarCmdMinIntervalSeconds)
{
return false;
}
_lastSolarCloseCmdTime = now;
try
{
var ok = CloseSolar();
if (!ok)
{
LogService.Info($"时间:{now}-【光伏关闭】-失败,原因:{reason},状态:{CurESChargInfo},并网点功率:{SolarEleMeter5.RtPw},通信:{SolarLinkState}");
}
return ok;
}
catch (Exception ex)
{
LogService.Info($"时间:{now}-【光伏关闭】-异常,原因:{reason},状态:{CurESChargInfo}{ex.Message}");
return false;
}
}
/// <summary>
/// 逆流触发关闭光伏后的自动恢复逻辑。
/// 目标:避免光伏长期处于关闭状态造成发电损失。
/// </summary>
private void TryAutoRecoverSolarAfterBackflow()
{
try
{
if (!_solarBackflowClosePending)
{
return;
}
if (!YuPuAutoHand)
{
return;
}
if (CurESChargInfo != ESChargInfo.Master && CurESChargInfo != ESChargInfo.Slave)
{
return;
}
var now = DateTime.Now;
if (!IsSolarOpenAllowedAfterBackflowCooldown())
{
return;
}
if (CheckSolarState())
{
_solarBackflowClosePending = false;
LogService.Info($"时间:{now}-【光伏自动恢复】-已检测到光伏处于运行状态,清除待恢复标志,状态:{CurESChargInfo},并网点功率:{SolarEleMeter5.RtPw}");
return;
}
var openOk = TryOpenSolarThrottled($"逆流后自动恢复-并网点功率:{SolarEleMeter5.RtPw}");
LogService.Info($"时间:{now}-【光伏自动恢复】-尝试启动光伏,结果:{openOk},状态:{CurESChargInfo},并网点功率:{SolarEleMeter5.RtPw}");
if (openOk)
{
_solarBackflowClosePending = false;
_solarRecoverImportTrigActive = false;
_solarRecoverImportTrigStartTime = DateTime.MinValue;
}
if (CheckSolarState())
{
_solarBackflowClosePending = false;
LogService.Info($"时间:{now}-【光伏自动恢复】-光伏已恢复运行,状态:{CurESChargInfo},并网点功率:{SolarEleMeter5.RtPw}");
}
}
catch (Exception ex)
{
LogService.Info($"时间:{DateTime.Now}-【光伏自动恢复】-异常,状态:{CurESChargInfo}{ex.Message}");
}
} }
@@ -286,6 +430,8 @@ namespace OrpaonEMS.App.Services
/// </summary> /// </summary>
public StateMachine<ESChargInfo, ESChargInfoTrig> SysRunStateMachine { get; set; } public StateMachine<ESChargInfo, ESChargInfoTrig> SysRunStateMachine { get; set; }
private DateTime _lastAutoAlarmResetTime = DateTime.MinValue;
///// <summary> ///// <summary>
///// 夜间充电模型信息 ///// 夜间充电模型信息
///// 标记白天放电信息 ///// 标记白天放电信息
@@ -320,7 +466,7 @@ namespace OrpaonEMS.App.Services
.Permit(ESChargInfoTrig.WaitTrig, ESChargInfo.Wait) .Permit(ESChargInfoTrig.WaitTrig, ESChargInfo.Wait)
.Permit(ESChargInfoTrig.NoSolarTrig, ESChargInfo.NoSolar) .Permit(ESChargInfoTrig.NoSolarTrig, ESChargInfo.NoSolar)
.Ignore(ESChargInfoTrig.MasterTrig) .Ignore(ESChargInfoTrig.MasterTrig)
.OnEntry(() => EntryMaster()) .OnEntry(() => EntryWithAutoAlarmReset(ESChargInfo.Master, EntryMaster))
.OnExit(() => ExitMaster()); .OnExit(() => ExitMaster());
//Slave 状态 //Slave 状态
@@ -331,7 +477,7 @@ namespace OrpaonEMS.App.Services
.Permit(ESChargInfoTrig.WaitTrig, ESChargInfo.Wait) .Permit(ESChargInfoTrig.WaitTrig, ESChargInfo.Wait)
.Permit(ESChargInfoTrig.NoSolarTrig, ESChargInfo.NoSolar) .Permit(ESChargInfoTrig.NoSolarTrig, ESChargInfo.NoSolar)
.Ignore(ESChargInfoTrig.SlaveTrig) .Ignore(ESChargInfoTrig.SlaveTrig)
.OnEntry(() => EntrySlave()) .OnEntry(() => EntryWithAutoAlarmReset(ESChargInfo.Slave, EntrySlave))
.OnExit(() => ExitSlave()); .OnExit(() => ExitSlave());
//Night_Master 状态 //Night_Master 状态
@@ -342,7 +488,7 @@ namespace OrpaonEMS.App.Services
.Permit(ESChargInfoTrig.WaitTrig, ESChargInfo.Wait) .Permit(ESChargInfoTrig.WaitTrig, ESChargInfo.Wait)
.Permit(ESChargInfoTrig.NoSolarTrig, ESChargInfo.NoSolar) .Permit(ESChargInfoTrig.NoSolarTrig, ESChargInfo.NoSolar)
.Ignore(ESChargInfoTrig.Night_MasterTrig) .Ignore(ESChargInfoTrig.Night_MasterTrig)
.OnEntry(() => EntryNight_Master()) .OnEntry(() => EntryWithAutoAlarmReset(ESChargInfo.Night_Master, EntryNight_Master))
.OnExit(() => ExitNight_Master()); .OnExit(() => ExitNight_Master());
//Night_Slave 状态 //Night_Slave 状态
@@ -353,7 +499,7 @@ namespace OrpaonEMS.App.Services
.Permit(ESChargInfoTrig.NoSolarTrig, ESChargInfo.NoSolar) .Permit(ESChargInfoTrig.NoSolarTrig, ESChargInfo.NoSolar)
.Permit(ESChargInfoTrig.Night_MasterTrig, ESChargInfo.Night_Master) .Permit(ESChargInfoTrig.Night_MasterTrig, ESChargInfo.Night_Master)
.Ignore(ESChargInfoTrig.Night_SlaveTrig) .Ignore(ESChargInfoTrig.Night_SlaveTrig)
.OnEntry(() => EntryNight_Slave()) .OnEntry(() => EntryWithAutoAlarmReset(ESChargInfo.Night_Slave, EntryNight_Slave))
.OnExit(() => ExitNight_Slave()); .OnExit(() => ExitNight_Slave());
//Wait 状态 //Wait 状态
@@ -364,7 +510,7 @@ namespace OrpaonEMS.App.Services
.Permit(ESChargInfoTrig.Night_SlaveTrig, ESChargInfo.Night_Slave) .Permit(ESChargInfoTrig.Night_SlaveTrig, ESChargInfo.Night_Slave)
.Permit(ESChargInfoTrig.NoSolarTrig, ESChargInfo.NoSolar) .Permit(ESChargInfoTrig.NoSolarTrig, ESChargInfo.NoSolar)
.Ignore(ESChargInfoTrig.WaitTrig) .Ignore(ESChargInfoTrig.WaitTrig)
.OnEntry(() => EntryWait()) .OnEntry(() => EntryWithAutoAlarmReset(ESChargInfo.Wait, EntryWait))
.OnExit(() => ExitWait()); .OnExit(() => ExitWait());
//NoSolar 状态 //NoSolar 状态
@@ -375,13 +521,63 @@ namespace OrpaonEMS.App.Services
.Permit(ESChargInfoTrig.Night_SlaveTrig, ESChargInfo.Night_Slave) .Permit(ESChargInfoTrig.Night_SlaveTrig, ESChargInfo.Night_Slave)
.Permit(ESChargInfoTrig.WaitTrig, ESChargInfo.Wait) .Permit(ESChargInfoTrig.WaitTrig, ESChargInfo.Wait)
.Ignore(ESChargInfoTrig.NoSolarTrig) .Ignore(ESChargInfoTrig.NoSolarTrig)
.OnEntry(() => EntryNoSolar()) .OnEntry(() => EntryWithAutoAlarmReset(ESChargInfo.NoSolar, EntryNoSolar))
.OnExit(() => ExitNoSolar()); .OnExit(() => ExitNoSolar());
PreESChargInfo = ESChargInfo.Master; PreESChargInfo = ESChargInfo.Master;
SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig); SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig);
} }
private void EntryWithAutoAlarmReset(ESChargInfo enteringState, Action entryAction)
{
TryAutoResetBmsAndPcsAlarms($"进入状态:{enteringState}", enteringState);
entryAction();
}
private void TryAutoResetBmsAndPcsAlarms(string reason, ESChargInfo enteringState)
{
try
{
if (!YuPuAutoHand)
{
return;
}
var now = DateTime.Now;
if ((now - _lastAutoAlarmResetTime).TotalSeconds < 30)
{
return;
}
_lastAutoAlarmResetTime = now;
bool pcsResetOk = false;
try
{
pcsResetOk = InPowerPCSDataService.PCSFaultReset();
}
catch (Exception ex)
{
LogService.Info($"时间:{DateTime.Now}-【报警复位】-PCS复位异常原因:{reason},状态:{enteringState}{ex.Message}");
}
try
{
BmsDataService.BmsAlarmReset();
}
catch (Exception ex)
{
LogService.Info($"时间:{DateTime.Now}-【报警复位】-BMS复位异常原因:{reason},状态:{enteringState}{ex.Message}");
}
LogService.Info($"时间:{DateTime.Now}-【报警复位】-已触发复位,原因:{reason},状态:{enteringState}PCS复位结果:{pcsResetOk}");
}
catch (Exception ex)
{
LogService.Info($"时间:{DateTime.Now}-【报警复位】-复位流程异常,原因:{reason},状态:{enteringState}{ex.Message}");
}
}
/// <summary> /// <summary>
/// 进入 NoSolar /// 进入 NoSolar
/// </summary> /// </summary>
@@ -420,7 +616,7 @@ namespace OrpaonEMS.App.Services
//关闭光伏 //关闭光伏
if (CheckSolarState() == true) if (CheckSolarState() == true)
{ {
CloseSolar(); TryCloseSolarThrottled("EntryNoSolar");
} }
TaxRealPw = SolarEleMeter5.AvePw - SolarEleMeter3.AvePw;//光伏和市电供应给税务大楼的负载 TaxRealPw = SolarEleMeter5.AvePw - SolarEleMeter3.AvePw;//光伏和市电供应给税务大楼的负载
@@ -615,7 +811,7 @@ namespace OrpaonEMS.App.Services
if (CheckSolarState() == true) if (CheckSolarState() == true)
{ {
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-【Wait】状态-光伏不满足条件,关闭光伏"); LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-【Wait】状态-光伏不满足条件,关闭光伏");
CloseSolar(); TryCloseSolarThrottled("EntryWait");
} }
} }
@@ -1031,9 +1227,9 @@ namespace OrpaonEMS.App.Services
} }
//打开光伏 //打开光伏
if (CheckSolarState() == false) if (CheckSolarState() == false && IsSolarOpenAllowedAfterBackflowCooldown())
{ {
OpenSolar(); TryOpenSolarThrottled("EntrySlave");
} }
//循环执行方法 //循环执行方法
@@ -1239,9 +1435,9 @@ namespace OrpaonEMS.App.Services
} }
//打开光伏 //打开光伏
if (CheckSolarState() == false) if (CheckSolarState() == false && IsSolarOpenAllowedAfterBackflowCooldown())
{ {
OpenSolar(); TryOpenSolarThrottled("EntryMaster");
} }
//循环执行方法 //循环执行方法
@@ -2971,6 +3167,8 @@ namespace OrpaonEMS.App.Services
continue; continue;
} }
TryAutoRecoverSolarAfterBackflow();
//实时给当前的SOC数据给夜电白放的模型 //实时给当前的SOC数据给夜电白放的模型
//CurNightChargEleModel.MasterSoc = MasterClient.ClientInfo!.SOC; //CurNightChargEleModel.MasterSoc = MasterClient.ClientInfo!.SOC;
//CurNightChargEleModel.SlaveSoc = SlaveClient.ClientInfo!.SOC; //CurNightChargEleModel.SlaveSoc = SlaveClient.ClientInfo!.SOC;