Files
YuPu-OrpaonEMS/OrpaonEMS.App/Services/YuePuRunModelService.cs

4013 lines
186 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using CsvHelper.Configuration;
using HslCommunication;
using HslCommunication.ModBus;
using OrpaonEMS.App.Com;
using OrpaonEMS.App.Models;
using OrpaonEMS.App.PrismEvent;
using OrpaonEMS.Core.Enums;
using OrpaonEMS.Model.Enums;
using OrpaonEMS.Model.MasterSlave;
using Prism.Events;
using Prism.Mvvm;
using Stateless;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace OrpaonEMS.App.Services
{
/// <summary>
/// 月浦储能柜运行模型
/// </summary>
public class YuePuRunModelService : BindableBase
{
///////
/////怎么认定为晚上,光伏发电功率低于一个阈值时可认为是晚上
public YuePuRunModelService(ILogService logService, ConfigDataService configDataService,BmsDataService bmsDataService,FFService fFService, InPowerPCSDataService inPowerPCSDataService,
IEventAggregator eventAggregator)
{
ConfigDataService = configDataService;
BmsDataService = bmsDataService;
FFService = fFService;
InPowerPCSDataService = inPowerPCSDataService;
LogService = logService;
//CurNightChargEleModel = new NightChargEleModel(ConfigDataService);
if (ConfigDataService.IsMaster)
{
_eventAggregator = eventAggregator;
_eventAggregator.GetEvent<PeakValleyTimeEvent>().Subscribe(OnPeakValleyTimeEventMsgReceived);
MeterMdDrive?.Close();
MeterMdDrive = new ModbusRtu();
MeterMdDrive.AddressStartWithZero = true;//华为光伏逆变器从0开始的
MeterMdDrive.IsStringReverse = false;
MeterMdDrive.DataFormat = HslCommunication.Core.DataFormat.ABCD;
MeterMdDrive.ReceiveTimeOut = 500;
try
{
MeterMdDrive.SerialPortInni(sp =>
{
sp.PortName = "COM2";
//sp.PortName = "COM10";
sp.BaudRate = 9600;
sp.DataBits = 8;
sp.StopBits = System.IO.Ports.StopBits.One;
sp.Parity = System.IO.Ports.Parity.None;
});
var result = MeterMdDrive.Open();
if (!result.IsSuccess)
{
MessageBox.Show("Meter仪表连接失败");
}
MeterScanDeviceStart();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
//SolarMdDrive
SolarMdDrive?.Close();
SolarMdDrive = new ModbusRtu();
SolarMdDrive.AddressStartWithZero = false;//跟ModbusPull一样的地址
SolarMdDrive.IsStringReverse = false;
SolarMdDrive.DataFormat = HslCommunication.Core.DataFormat.ABCD;
SolarMdDrive.ReceiveTimeOut = 500;
try
{
SolarMdDrive.SerialPortInni(sp =>
{
sp.PortName = "COM8";
//sp.PortName = "COM10";
sp.BaudRate = 9600;
sp.DataBits = 8;
sp.StopBits = System.IO.Ports.StopBits.One;
sp.Parity = System.IO.Ports.Parity.None;
});
var result = SolarMdDrive.Open();
if (!result.IsSuccess)
{
MessageBox.Show("光伏连接失败");
}
SolarLinkTrigTimeModel = new TrigTimeModel(1, 10);//trigThValue 参数无所谓,因为不用这个参数 bool类型
SolarLinkTrigTimeModel.TrigTimeOutHandler += SolarLinkTrigTimeModel_TrigTimeOutHandler;
SolarScanDeviceStart();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
ModbusTcpNetDrive = new ModbusTcpNet("192.0.3.200", 502);
//ModbusTcpNetDrive = new ModbusTcpNet("127.0.0.1", 502);
//为了方便和ModbusPull及手册的地址能对应的话设置从1开始
ModbusTcpNetDrive.AddressStartWithZero = false;
ModbusTcpNetDrive.SetPersistentConnection();
var DATA = ModbusTcpNetDrive.ConnectServer();
if (!DATA.IsSuccess)
{
MessageBox.Show("远程IO 连接失败");
}
QFSwitch1 = new SwitchModel("17", "18", ModbusTcpNetDrive);
QFSwitch2 = new SwitchModel("19", "20", ModbusTcpNetDrive);
QFSwitch3 = new SwitchModel("21", "22", ModbusTcpNetDrive);
QFSwitch4 = new SwitchModel("23", "24", ModbusTcpNetDrive);
ReIOScanDeviceStart();
//初始的设置防止上一次的残留导致状态和开关的IO的不同步
//CurESChargInfo = ESChargInfo.Master;
//Thread.Sleep(1000);
//QFSwitch3.SetRtSwitch(SwitchEm.Off);
//QFSwitch4.SetRtSwitch(SwitchEm.On);
//QFSwitch2.SetRtSwitch(SwitchEm.Off);
//QFSwitch1.SetRtSwitch(SwitchEm.On);
// 放电时间模型初始化:基于配置的 DisChargeModel 列表与 SelectedDisChargeModel
try
{
var list = ConfigDataService.GetDisChargeModel();
int selectedId = 1;
try
{
selectedId = ConfigDataService.ControlConfigValue?.SelectedDisChargeModel ?? 1;
}
catch { selectedId = 1; }
DisChargeModelItem? selected = null;
if (list != null && list.Count > 0)
{
foreach (var it in list)
{
if (it != null && it.Model == selectedId)
{
selected = it;
break;
}
}
if (selected == null)
{
selected = list[0];
}
}
// 映射到旧的枚举(如可用),否则回退到 DischargeA
var enumValue = DisChargeType.DischargeA;
if (selected != null && System.Enum.IsDefined(typeof(DisChargeType), selected.Model))
{
enumValue = (DisChargeType)selected.Model;
}
CurDischargeTimeModel = new DischargeTimeModel(enumValue);
// 设置具体时间(优先使用配置时间),格式期望 HH:mm
if (selected != null && !string.IsNullOrWhiteSpace(selected.DisChargeTime) && TimeSpan.TryParse(selected.DisChargeTime, out var ts))
{
CurDischargeTimeModel.CurDischargeTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, ts.Hours, ts.Minutes, 0);
}
else
{
// 解析失败使用默认 08:00
CurDischargeTimeModel.CurDischargeTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 8, 0, 0);
LogService.Warn("DisChargeModel 配置时间缺失或格式非法,已使用默认 08:00");
}
}
catch (System.Exception ex)
{
// 失败时初始化为默认模式与时间
CurDischargeTimeModel = new DischargeTimeModel(DisChargeType.DischargeA)
{
CurDischargeTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 8, 0, 0)
};
LogService.Error($"初始化放电时间模型失败: {ex.Message}");
}
StateMachineInitial();
LogicScan();
SolarCurBackFlow = new TrigTimeModel(-5, 10);
SolarCurBackFlow.TrigTimeOutHandler += SolarCurBackFlow_TrigTimeOutHandler;
}
}
/// <summary>
/// 削峰填谷的时间信息
/// </summary>
/// <param name="enum"></param>
/// <exception cref="NotImplementedException"></exception>
private void OnPeakValleyTimeEventMsgReceived(ElePVEnum data)
{
PeakValleySglModel = data;
}
private ElePVEnum _PeakValleySglModel = ElePVEnum.Peak;
/// <summary>
/// 削峰填谷的信号模型
/// 由其他的线程轮训赋值监控
/// </summary>
public ElePVEnum PeakValleySglModel
{
get { return _PeakValleySglModel; }
set
{
//if (_PeakValleySglModel != value)//监听线程不停的赋值,变化时触发执行
//{
_PeakValleySglModel = value;//削峰填谷不使用状态机
//}
}
}
/// <summary>
/// 当前的放电时间模型
/// </summary>
public DischargeTimeModel CurDischargeTimeModel { get; set; }
/// <summary>
/// 税务大楼光伏逆流触发模型
/// </summary>
public TrigTimeModel SolarCurBackFlow { get; set; }
/// <summary>
/// 事件聚合器
/// </summary>
private readonly IEventAggregator _eventAggregator;
/// <summary>
/// 光伏通信失败触发
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <exception cref="NotImplementedException"></exception>
private void SolarLinkTrigTimeModel_TrigTimeOutHandler(object? sender, EventArgs e)
{
Console.WriteLine($"时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}-【月浦】- 光伏通信失败 ");
}
/// <summary>
/// 税务大楼光伏通信失败触发模型
/// </summary>
public TrigTimeModel SolarLinkTrigTimeModel { get; set; }
/// <summary>
/// 税务大楼的光伏逆流触发事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <exception cref="NotImplementedException"></exception>
private void SolarCurBackFlow_TrigTimeOutHandler(object? sender, EventArgs e)
{
//关闭光伏
CloseSolar();
Console.WriteLine($"时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}-【月浦】- 软件检测到光伏逆流,关闭光伏逆变器 ");
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-软件检测到光伏逆流,关闭光伏逆变器");
}
#region
/// <summary>
/// 系统运行的状态机
/// </summary>
public StateMachine<ESChargInfo, ESChargInfoTrig> SysRunStateMachine { get; set; }
///// <summary>
///// 夜间充电模型信息
///// 标记白天放电信息
///// </summary>
//public NightChargEleModel CurNightChargEleModel; /*= new NightChargEleModel();*/
/// <summary>
/// 系统运行的状态机
/// </summary>
private void StateMachineInitial()
{
//初始状态是这个状态,不然同一个状态会重复进入导致状态机报错
CurESChargInfo = ESChargInfo.Inital;
SysRunStateMachine = new StateMachine<ESChargInfo, ESChargInfoTrig>(ESChargInfo.Inital);
//初始状态
SysRunStateMachine.Configure(ESChargInfo.Inital)
.Permit(ESChargInfoTrig.SlaveTrig, ESChargInfo.Slave)
.Permit(ESChargInfoTrig.Night_MasterTrig, ESChargInfo.Night_Master)
.Permit(ESChargInfoTrig.Night_SlaveTrig, ESChargInfo.Night_Slave)
.Permit(ESChargInfoTrig.MasterTrig, ESChargInfo.Master)
.Permit(ESChargInfoTrig.NoSolarTrig, ESChargInfo.NoSolar)
.Permit(ESChargInfoTrig.WaitTrig, ESChargInfo.Wait)
.Ignore(ESChargInfoTrig.InitalTrig);
//Master 状态
SysRunStateMachine.Configure(ESChargInfo.Master)
.Permit(ESChargInfoTrig.SlaveTrig, ESChargInfo.Slave)
.Permit(ESChargInfoTrig.Night_MasterTrig, ESChargInfo.Night_Master)
.Permit(ESChargInfoTrig.Night_SlaveTrig, ESChargInfo.Night_Slave)
.Permit(ESChargInfoTrig.WaitTrig, ESChargInfo.Wait)
.Permit(ESChargInfoTrig.NoSolarTrig, ESChargInfo.NoSolar)
.Ignore(ESChargInfoTrig.MasterTrig)
.OnEntry(() => EntryMaster())
.OnExit(() => ExitMaster());
//Slave 状态
SysRunStateMachine.Configure(ESChargInfo.Slave)
.Permit(ESChargInfoTrig.MasterTrig, ESChargInfo.Master)
.Permit(ESChargInfoTrig.Night_MasterTrig, ESChargInfo.Night_Master)
.Permit(ESChargInfoTrig.Night_SlaveTrig, ESChargInfo.Night_Slave)
.Permit(ESChargInfoTrig.WaitTrig, ESChargInfo.Wait)
.Permit(ESChargInfoTrig.NoSolarTrig, ESChargInfo.NoSolar)
.Ignore(ESChargInfoTrig.SlaveTrig)
.OnEntry(() => EntrySlave())
.OnExit(() => ExitSlave());
//Night_Master 状态
SysRunStateMachine.Configure(ESChargInfo.Night_Master)
.Permit(ESChargInfoTrig.MasterTrig, ESChargInfo.Master)
.Permit(ESChargInfoTrig.Night_SlaveTrig, ESChargInfo.Night_Slave)
.Permit(ESChargInfoTrig.SlaveTrig, ESChargInfo.Slave)
.Permit(ESChargInfoTrig.WaitTrig, ESChargInfo.Wait)
.Permit(ESChargInfoTrig.NoSolarTrig, ESChargInfo.NoSolar)
.Ignore(ESChargInfoTrig.Night_MasterTrig)
.OnEntry(() => EntryNight_Master())
.OnExit(() => ExitNight_Master());
//Night_Slave 状态
SysRunStateMachine.Configure(ESChargInfo.Night_Slave)
.Permit(ESChargInfoTrig.MasterTrig, ESChargInfo.Master)
.Permit(ESChargInfoTrig.SlaveTrig, ESChargInfo.Slave)
.Permit(ESChargInfoTrig.WaitTrig, ESChargInfo.Wait)
.Permit(ESChargInfoTrig.NoSolarTrig, ESChargInfo.NoSolar)
.Permit(ESChargInfoTrig.Night_MasterTrig, ESChargInfo.Night_Master)
.Ignore(ESChargInfoTrig.Night_SlaveTrig)
.OnEntry(() => EntryNight_Slave())
.OnExit(() => ExitNight_Slave());
//Wait 状态
SysRunStateMachine.Configure(ESChargInfo.Wait)
.Permit(ESChargInfoTrig.MasterTrig, ESChargInfo.Master)
.Permit(ESChargInfoTrig.SlaveTrig, ESChargInfo.Slave)
.Permit(ESChargInfoTrig.Night_MasterTrig, ESChargInfo.Night_Master)
.Permit(ESChargInfoTrig.Night_SlaveTrig, ESChargInfo.Night_Slave)
.Permit(ESChargInfoTrig.NoSolarTrig, ESChargInfo.NoSolar)
.Ignore(ESChargInfoTrig.WaitTrig)
.OnEntry(() => EntryWait())
.OnExit(() => ExitWait());
//NoSolar 状态
SysRunStateMachine.Configure(ESChargInfo.NoSolar)
.Permit(ESChargInfoTrig.MasterTrig, ESChargInfo.Master)
.Permit(ESChargInfoTrig.SlaveTrig, ESChargInfo.Slave)
.Permit(ESChargInfoTrig.Night_MasterTrig, ESChargInfo.Night_Master)
.Permit(ESChargInfoTrig.Night_SlaveTrig, ESChargInfo.Night_Slave)
.Permit(ESChargInfoTrig.WaitTrig, ESChargInfo.Wait)
.Ignore(ESChargInfoTrig.NoSolarTrig)
.OnEntry(() => EntryNoSolar())
.OnExit(() => ExitNoSolar());
PreESChargInfo = ESChargInfo.Master;
SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig);
}
/// <summary>
/// 进入 NoSolar
/// </summary>
/// <exception cref="NotImplementedException"></exception>
private void EntryNoSolar()
{
CurESChargInfo = ESChargInfo.NoSolar;
Console.WriteLine($"时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}-【月浦】- 运行进入【NoSolar】模式 ");
NoSolarTokenSource = new CancellationTokenSource();
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-进入【EntryNoSolar】模式");
Task.Run(() =>
{
//判断状态是否正确
while (CurESChargInfo == ESChargInfo.NoSolar)
{
try
{
Thread.Sleep(1000);
if (NoSolarTokenSource.IsCancellationRequested)
{
break;
}
if (MasterClient == null)//防止未加载导致错误
{
continue;
}
if (!YuPuAutoHand)
{
continue;
}
//关闭光伏
if (CheckSolarState() == true)
{
CloseSolar();
}
TaxRealPw = SolarEleMeter5.AvePw - SolarEleMeter3.AvePw;//光伏和市电供应给税务大楼的负载
//循环执行方法
//让储能给管理大楼放电,腾出空间,让光伏参与进来
//优先让主储能给管理大楼供电,因为涉及到电操,让主储能柜体放电 1 4 ON 2 3OFF
if (MasterClient.IsCanDisCharg())
{
///////////////////Master主控制柜给管理大楼供电
//注意方向
//主储能箱体放电的控制
ManageRealPw = EsEleMeter4.AvePw - EsEleMeter1.AvePw;
if (ManageRealPw > ManageTargetPw)//真实的负载大于一个值的时候才介入,或者控制关口电表的数据维持在一个水平,两个不同的控制方式
{
//Master 主储能箱根据负载情况给管理大楼放电
//可以放电的话,则
if (MasterClient.IsCanDisCharg())
{
MasterClient.ServerCmd.CmdType = "DisCharg";
ManageDifLoad = ManageRealPw - ManageTargetPw;
if (ManageDifLoad < MasterClient.ClientInfo.MaxDisChargPw)
{
//储能发送功率 补全总功率数据
//DisChargPwCmd(AcrelMeters.Psum - EleMeterActionLimitValue.MidValue);
MasterClient.ServerCmd.CmdPw = ManageDifLoad;
MasterControlMsg = "【主储能】放电【管理大楼】:【主储能】放电补全目标差值,【主储能】正常放电";
}
else //储能不能满足总的差值
{
//满功率放电
//储能全功率放电,不足的部分电网自动补全
MasterClient.ServerCmd.CmdPw = (MasterClient.ClientInfo.MaxDisChargPw * ConfigDataService.energyStorageRunConfig.MaxBatDisChargRatio);
MasterControlMsg = "【主储能】放电【管理大楼】:【主储能】全功率放电无法补全差值,【主储能】全功率放电";
}
}
else
{
//储能处于待机状态,防止其他的状态残留
MasterClient.ServerCmd.CmdType = "DisCharg";//功率为0充放电的指令类型无所谓
MasterClient.ServerCmd.CmdPw = 0;
MasterControlMsg = "【主储能】放电【管理大楼】:【主储能】无法放电,【主储能】不动作";
}
}
else
{
//真实的负载功率比较小,防止储能放电导致逆流,就不需要输出动作了,等真实的负载大于一个值后才开始介入
MasterClient.ServerCmd.CmdType = "DisCharg"; //功率为0充放电的指令类型无所谓
MasterClient.ServerCmd.CmdPw = 0;
MasterControlMsg = "【主储能】放电【管理大楼】:【管理大楼】负载较小,为防止放电导致逆流,【主储能】不动作";
}
//只能一个放电,另一个停止放电
SlaveClient.ServerCmd.CmdPw = 0;
}
else
{
ManageRealPw = EsEleMeter4.AvePw - EsEleMeter1.AvePw;
/////////////////////Slave从控制柜给管理大楼供电
////注意方向
////从储能箱体放电的控制
//ManageRealPw = EsEleMeter4.AvePw - EsEleMeter1.AvePw;
//if (ManageRealPw > ManageTargetPw)//真实的负载大于一个值的时候才介入,或者控制关口电表的数据维持在一个水平,两个不同的控制方式
//{
// //Slave 从储能箱根据负载情况给管理大楼放电
// //可以放电的话,则
// if (SlaveClient.IsCanDisCharg())
// {
// SlaveClient.ServerCmd.CmdType = "DisCharg";
// ManageDifLoad = ManageRealPw - ManageTargetPw;
// if (ManageDifLoad < SlaveClient.ClientInfo!.MaxDisChargPw)
// {
// //储能发送功率 补全总功率数据
// //DisChargPwCmd(AcrelMeters.Psum - EleMeterActionLimitValue.MidValue);
// SlaveClient.ServerCmd.CmdPw = ManageDifLoad;
// SlaveControlMsg = "【从储能】放电【管理大楼】:【从储能】放电补全目标差值,【从储能】正常放电";
// }
// else //储能不能满足总的差值
// {
// //满功率放电
// //储能全功率放电,不足的部分电网自动补全
// SlaveClient.ServerCmd.CmdPw = (SlaveClient.ClientInfo.MaxDisChargPw * ConfigDataService.energyStorageRunConfig.MaxBatDisChargRatio);
// SlaveControlMsg = "【从储能】放电【管理大楼】:【从储能】全功率放电无法补全差值,【从储能】全功率放电";
// }
// }
// else
// {
// //储能处于待机状态,防止其他的状态残留
// SlaveClient.ServerCmd.CmdType = "DisCharg";//功率为0充放电的指令类型无所谓
// SlaveClient.ServerCmd.CmdPw = 0;
// SlaveControlMsg = "【从储能】放电【管理大楼】:【从储能】无法放电,【从储能】不动作";
// }
//}
//else
//{
// //真实的负载功率比较小,防止储能放电导致逆流,就不需要输出动作了,等真实的负载大于一个值后才开始介入
// SlaveClient.ServerCmd.CmdType = "DisCharg"; //功率为0充放电的指令类型无所谓
// SlaveClient.ServerCmd.CmdPw = 0;
// SlaveControlMsg = "【从储能】放电【管理大楼】:【管理大楼】负载较小,为防止放电导致逆流,【从储能】不动作";
//}
////只能一个放电,另一个停止放电
//MasterClient.ServerCmd.CmdPw = 0;
}
}
catch (Exception ex)
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【EntryNoSolar】-{ex.Message}");
}
}
}, NoSolarTokenSource.Token);
}
/// <summary>
/// 推出 NoSolar
/// </summary>
/// <exception cref="NotImplementedException"></exception>
private void ExitNoSolar()
{
NoSolarTokenSource.Cancel();
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-离开【EntryNoSolar】模式");
}
/// <summary>
/// 进入 Wait 等待状态
/// </summary>
/// <exception cref="NotImplementedException"></exception>
private void EntryWait()
{
CurESChargInfo = ESChargInfo.Wait;
Console.WriteLine($"时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}-【月浦】- 运行进入【Wait】模式 ");
WaitTokenSource = new CancellationTokenSource();
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-进入【EntryWait】模式");
Task.Run(() =>
{
//判断状态是否正确
while (CurESChargInfo == ESChargInfo.Wait)
{
try
{
if (WaitTokenSource.IsCancellationRequested)
{
break;
}
if (MasterClient == null)//防止未加载导致错误
{
continue;
}
//等待期间 关闭两个储能柜的输出
SlaveClient.ServerCmd.CmdPw = 0;
MasterClient.ServerCmd.CmdPw = 0;
//等待期间 两个储能柜关闭液冷
SlaveClient.ServerCmd.CoolOnOffCmd = false;
MasterClient.ServerCmd.CoolOnOffCmd = false;
Thread.Sleep(30000);
//WaitEnvActionResult
//循环执行方法
// 1 关闭光伏输出,为了确保安全,防止光伏这个时候比较还输出导致逆流产生
//确认光伏是否立刻断开
//当通信失败时,则认为光伏关闭,不需要通信确认光伏关闭
if (CheckSolarState() == false && GetTotalRtSolarPw() < 5)
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-【Wait】状态-确认光伏是否立刻断开确认等待光伏结果值是OK的");
WaitSolarActionResult = true;
}
else
{
WaitSolarActionResult = false;
//不满足条件的话可能还在动作中功率还没有降低到快为0,需要判断是否关闭,如果关闭了就需要等待,不需要再关闭了
if (CheckSolarState() == true)
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-【Wait】状态-光伏不满足条件,关闭光伏");
CloseSolar();
}
}
// 2 监控开关状态和合闸 确保电闸符合要求
//先检查状态
if (CheckSwitchState(PreESChargInfo))
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-【Wait】状态-检查电闸状态,状态符合要求");
WaitEleSwitchActionResult = true;
}
else//不满足状态,则先关闭电操,然后开启对应的电操
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-【Wait】状态-检查电闸状态,不状态符合要求,关闭所有电操,重新合闸需要的电操");
//关电操
QFSwitch1.SetRtSwitch(SwitchEm.Off);
QFSwitch2.SetRtSwitch(SwitchEm.Off);
QFSwitch3.SetRtSwitch(SwitchEm.Off);
QFSwitch4.SetRtSwitch(SwitchEm.Off);
//等待2秒
Thread.Sleep(3000);
SetControlModelNoBeforClose(PreESChargInfo);
}
//准备满足条件,可以切换状态
if (WaitSolarActionResult && WaitEleSwitchActionResult)
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-【Wait】状态-所有的状态都满足,开始进行预设的模式切换");
switch (PreESChargInfo)
{
case ESChargInfo.Inital:
break;
case ESChargInfo.Master:
SysRunStateMachine.Fire(ESChargInfoTrig.MasterTrig);
break;
case ESChargInfo.Slave:
SysRunStateMachine.Fire(ESChargInfoTrig.SlaveTrig);
break;
case ESChargInfo.Night_Master:
SysRunStateMachine.Fire(ESChargInfoTrig.Night_MasterTrig);
break;
case ESChargInfo.Night_Slave:
SysRunStateMachine.Fire(ESChargInfoTrig.Night_SlaveTrig);
break;
case ESChargInfo.Wait:
SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig);
break;
case ESChargInfo.NoSolar:
SysRunStateMachine.Fire(ESChargInfoTrig.NoSolarTrig);
break;
default:
break;
}
}
}
catch (Exception ex)
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【EntryWait】-{ex.Message}");
}
}
}, WaitTokenSource.Token);
}
/// <summary>
/// 等待的光伏动作结果值
/// </summary>
private bool WaitSolarActionResult { get; set; } = false;
/// <summary>
/// 等待环境切换结果值 PCS 液冷
/// 先关掉PCS和液冷
/// 直接切换电操长时间会导致PCS和液冷报错。
/// </summary>
private bool WaitEnvActionResult { get; set; } = false;
/// <summary>
/// 等待的电操动作结果值
/// </summary>
private bool WaitEleSwitchActionResult { get; set; } = false;
/// <summary>
/// 退出 Wait 等待状态
/// </summary>
/// <exception cref="NotImplementedException"></exception>
private void ExitWait()
{
WaitTokenSource.Cancel();
//开启液冷
SlaveClient.ServerCmd.CoolOnOffCmd = true;
MasterClient.ServerCmd.CoolOnOffCmd = true;
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-离开【EntryWait】模式");
}
/// <summary>
/// 进入 Night_Slave
/// </summary>
/// <exception cref="NotImplementedException"></exception>
private void EntryNight_Slave()
{
CurESChargInfo = ESChargInfo.Night_Slave;
Console.WriteLine($"时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}-【月浦】- 运行进入【Night_Slave】模式 ");
Night_SlaveTokenSource = new CancellationTokenSource();
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-进入【EntryNight_Slave】模式");
Task.Run(() =>
{
double? lastSlaveNightTargetSoc = null;
bool? lastSlaveShouldCharge = null;
bool? lastSlaveInWeekendTime = null;
//判断状态是否正确
while (CurESChargInfo == ESChargInfo.Night_Slave)
{
try
{
Thread.Sleep(1000);
if (Night_SlaveTokenSource.IsCancellationRequested)
{
break;
}
if (MasterClient == null)//防止未加载导致错误
{
continue;
}
if (!YuPuAutoHand)
{
continue;
}
////打开光伏
//if (CheckSolarState() == false)
//{
// OpenSolar();
//}
bool inWeekendTime = IsInWeekendTime();
var slaveNightTargetSoc = Math.Min(NightSlave_ToSlaveFullSoc, ConfigDataService.energyStorageRunConfig.BMSSocUpSignLimitValue);
bool shouldCharge = !inWeekendTime && SlaveClient.ClientInfo!.SOC < slaveNightTargetSoc;
if (lastSlaveNightTargetSoc == null
|| Math.Abs(lastSlaveNightTargetSoc.Value - slaveNightTargetSoc) > 0.0001
|| lastSlaveInWeekendTime == null
|| lastSlaveInWeekendTime.Value != inWeekendTime)
{
LogService.Info($"时间:{DateTime.Now}-【夜间充电目标】-模式:Night_Slave从储能SOC:{SlaveClient.ClientInfo!.SOC:0.##}配置目标SOC:{NightSlave_ToSlaveFullSoc:0.##}BMS上限:{ConfigDataService.energyStorageRunConfig.BMSSocUpSignLimitValue:0.##}采用目标SOC:{slaveNightTargetSoc:0.##},是否周末禁充:{inWeekendTime}");
lastSlaveNightTargetSoc = slaveNightTargetSoc;
lastSlaveInWeekendTime = inWeekendTime;
}
if (lastSlaveShouldCharge == null || lastSlaveShouldCharge.Value != shouldCharge)
{
if (shouldCharge)
{
LogService.Info($"时间:{DateTime.Now}-【夜间充电开始】-模式:Night_Slave从储能SOC:{SlaveClient.ClientInfo!.SOC:0.##}目标SOC:{slaveNightTargetSoc:0.##}");
}
else
{
LogService.Info($"时间:{DateTime.Now}-【夜间充电停止】-模式:Night_Slave从储能SOC:{SlaveClient.ClientInfo!.SOC:0.##}目标SOC:{slaveNightTargetSoc:0.##},是否周末禁充:{inWeekendTime}");
}
lastSlaveShouldCharge = shouldCharge;
}
if (!inWeekendTime)
{
//展示用
ManageRealPw = EsEleMeter4.AvePw - EsEleMeter1.AvePw;
TaxRealPw = SolarEleMeter5.AvePw - SolarEleMeter3.AvePw;//光伏和市电供应给税务大楼的负载
//循环执行方法
//从储能箱充满为止
if (SlaveClient.ClientInfo!.SOC < slaveNightTargetSoc)
{
//主储能箱充电
SlaveClient.ServerCmd.CmdType = "Charg";
SlaveClient.ServerCmd.CmdPw = SlaveClient.ClientInfo!.MaxDisChargPw * ConfigDataService.energyStorageRunConfig.MaxBatDisChargRatio;
}
else
{
//从储能箱充电
SlaveClient.ServerCmd.CmdType = "Charg";
SlaveClient.ServerCmd.CmdPw = 0;
}
//主储能箱不运行
MasterClient.ServerCmd.CmdType = "Charg";
MasterClient.ServerCmd.CmdPw = 0;
}
else
{
//周五或者周六
//主从都不充电
MasterClient.ServerCmd.CmdType = "Charg";
MasterClient.ServerCmd.CmdPw = 0;
//MasterControlMsg = "【主储能】周五周六晚上不充电,【主储能】不动作";
SlaveClient.ServerCmd.CmdType = "Charg";
SlaveClient.ServerCmd.CmdPw = 0;
//SlaveControlMsg = "【从储能】周五周六晚上不充电,【从储能】不动作";
}
}
catch (Exception ex)
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【EntryAlarm】-{ex.Message}");
}
}
}, Night_SlaveTokenSource.Token);
}
/// <summary>
/// 退出 Night_Slave
/// </summary>
/// <exception cref="NotImplementedException"></exception>
private void ExitNight_Slave()
{
Night_SlaveTokenSource.Cancel();
//退出Night_Slave状态关闭输出
SlaveClient.ServerCmd.CmdPw = 0;
MasterClient.ServerCmd.CmdPw = 0;
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-离开【EntryNight_Slave】模式");
}
/// <summary>
/// 进入 Night_Master状态
/// </summary>
/// <exception cref="NotImplementedException"></exception>
private void EntryNight_Master()
{
CurESChargInfo = ESChargInfo.Night_Master;
Console.WriteLine($"时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}-【月浦】- 运行进入【Night_Master】模式 ");
Night_MasterTokenSource = new CancellationTokenSource();
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-进入【EntryNight_Master】模式");
Task.Run(() =>
{
double? lastMasterNightTargetSoc = null;
bool? lastMasterShouldCharge = null;
bool? lastMasterInWeekendTime = null;
//判断状态是否正确
while (CurESChargInfo == ESChargInfo.Night_Master)
{
try
{
Thread.Sleep(1000);
if (Night_MasterTokenSource.IsCancellationRequested)
{
break;
}
if (MasterClient == null)//防止未加载导致错误
{
continue;
}
if (!YuPuAutoHand)
{
continue;
}
////打开光伏
//if (CheckSolarState() == false)
//{
// OpenSolar();
//}
bool inWeekendTime = IsInWeekendTime();
var masterNightTargetSoc = Math.Min(NightMaster_ToMasterFullSoc, ConfigDataService.energyStorageRunConfig.BMSSocUpSignLimitValue);
bool shouldCharge = !inWeekendTime && MasterClient.ClientInfo!.SOC < masterNightTargetSoc;
if (lastMasterNightTargetSoc == null
|| Math.Abs(lastMasterNightTargetSoc.Value - masterNightTargetSoc) > 0.0001
|| lastMasterInWeekendTime == null
|| lastMasterInWeekendTime.Value != inWeekendTime)
{
LogService.Info($"时间:{DateTime.Now}-【夜间充电目标】-模式:Night_Master主储能SOC:{MasterClient.ClientInfo!.SOC:0.##}配置目标SOC:{NightMaster_ToMasterFullSoc:0.##}BMS上限:{ConfigDataService.energyStorageRunConfig.BMSSocUpSignLimitValue:0.##}采用目标SOC:{masterNightTargetSoc:0.##},是否周末禁充:{inWeekendTime}");
lastMasterNightTargetSoc = masterNightTargetSoc;
lastMasterInWeekendTime = inWeekendTime;
}
if (lastMasterShouldCharge == null || lastMasterShouldCharge.Value != shouldCharge)
{
if (shouldCharge)
{
LogService.Info($"时间:{DateTime.Now}-【夜间充电开始】-模式:Night_Master主储能SOC:{MasterClient.ClientInfo!.SOC:0.##}目标SOC:{masterNightTargetSoc:0.##}");
}
else
{
LogService.Info($"时间:{DateTime.Now}-【夜间充电停止】-模式:Night_Master主储能SOC:{MasterClient.ClientInfo!.SOC:0.##}目标SOC:{masterNightTargetSoc:0.##},是否周末禁充:{inWeekendTime}");
}
lastMasterShouldCharge = shouldCharge;
}
if (!inWeekendTime)
{
//展示用
ManageRealPw = EsEleMeter4.AvePw - EsEleMeter1.AvePw;
TaxRealPw = SolarEleMeter5.AvePw - SolarEleMeter3.AvePw;//光伏和市电供应给税务大楼的负载
//循环执行方法
//主储能箱充满为止
if (MasterClient.ClientInfo!.SOC < masterNightTargetSoc)
{
//主储能箱充电
MasterClient.ServerCmd.CmdType = "Charg";
MasterClient.ServerCmd.CmdPw = MasterClient.ClientInfo!.MaxDisChargPw * ConfigDataService.energyStorageRunConfig.MaxBatDisChargRatio;
}
else
{
//主储能箱充电
MasterClient.ServerCmd.CmdType = "Charg";
MasterClient.ServerCmd.CmdPw = 0;
}
//从储能箱不运行
SlaveClient.ServerCmd.CmdType = "Charg";
SlaveClient.ServerCmd.CmdPw = 0;
}
else
{
//周五或者周六
//主从都不充电
MasterClient.ServerCmd.CmdType = "Charg";
MasterClient.ServerCmd.CmdPw = 0;
//MasterControlMsg = "【主储能】周五周六晚上不充电,【主储能】不动作";
SlaveClient.ServerCmd.CmdType = "Charg";
SlaveClient.ServerCmd.CmdPw = 0;
//SlaveControlMsg = "【从储能】周五周六晚上不充电,【从储能】不动作";
}
}
catch (Exception ex)
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【EntryAlarm】-{ex.Message}");
}
}
}, Night_MasterTokenSource.Token);
}
/// <summary>
/// 推出 Night_Master状态
/// </summary>
/// <exception cref="NotImplementedException"></exception>
private void ExitNight_Master()
{
Night_MasterTokenSource.Cancel();
//退出Night_Master状态关闭输出
SlaveClient.ServerCmd.CmdPw = 0;
MasterClient.ServerCmd.CmdPw = 0;
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-离开【EntryNight_Master】模式");
}
/// <summary>
/// 进入 Slave状态
/// </summary>
/// <exception cref="NotImplementedException"></exception>
private void EntrySlave()
{
CurESChargInfo = ESChargInfo.Slave;
Console.WriteLine($"时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}-【月浦】- 运行进入【Slave】模式 ");
SlaveTokenSource = new CancellationTokenSource();
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-进入【EntrySlave】模式");
Task.Run(() =>
{
//判断状态是否正确
while (CurESChargInfo == ESChargInfo.Slave)
{
try
{
Thread.Sleep(1000);
if (SlaveTokenSource.IsCancellationRequested)
{
break;
}
if (MasterClient == null)//防止未加载导致错误
{
continue;
}
if (!YuPuAutoHand)
{
continue;
}
//打开光伏
if (CheckSolarState() == false)
{
OpenSolar();
}
//循环执行方法
//在峰的时候放电
//if (PeakValleySglModel == ElePVEnum.TopPeak || PeakValleySglModel == ElePVEnum.Peak)
if (ConfigDataService.IsTimeCanDischarge())
{
///////////////////Slave从控制柜给管理大楼供电
//注意方向
//从储能箱体放电的控制
ManageRealPw = EsEleMeter4.AvePw - EsEleMeter1.AvePw;
if (ManageRealPw > ManageTargetPw)//真实的负载大于一个值的时候才介入,或者控制关口电表的数据维持在一个水平,两个不同的控制方式
{
//Slave 从储能箱根据负载情况给管理大楼放电
//可以放电的话,则
if (SlaveClient.IsCanDisCharg())
{
SlaveClient.ServerCmd.CmdType = "DisCharg";
ManageDifLoad = ManageRealPw - ManageTargetPw;
if (ManageDifLoad < SlaveClient.ClientInfo!.MaxDisChargPw)
{
//储能发送功率 补全总功率数据
//DisChargPwCmd(AcrelMeters.Psum - EleMeterActionLimitValue.MidValue);
SlaveClient.ServerCmd.CmdPw = ManageDifLoad;
SlaveControlMsg = "【从储能】放电【管理大楼】:【从储能】放电补全目标差值,【从储能】正常放电";
}
else //储能不能满足总的差值
{
//满功率放电
//储能全功率放电,不足的部分电网自动补全
SlaveClient.ServerCmd.CmdPw = (SlaveClient.ClientInfo.MaxDisChargPw * ConfigDataService.energyStorageRunConfig.MaxBatDisChargRatio);
SlaveControlMsg = "【从储能】放电【管理大楼】:【从储能】全功率放电无法补全差值,【从储能】全功率放电";
}
}
else
{
//储能处于待机状态,防止其他的状态残留
SlaveClient.ServerCmd.CmdType = "DisCharg";//功率为0充放电的指令类型无所谓
SlaveClient.ServerCmd.CmdPw = 0;
SlaveControlMsg = "【从储能】放电【管理大楼】:【从储能】无法放电,【从储能】不动作";
}
}
else
{
//真实的负载功率比较小,防止储能放电导致逆流,就不需要输出动作了,等真实的负载大于一个值后才开始介入
SlaveClient.ServerCmd.CmdType = "DisCharg"; //功率为0充放电的指令类型无所谓
SlaveClient.ServerCmd.CmdPw = 0;
SlaveControlMsg = "【从储能】放电【管理大楼】:【管理大楼】负载较小,为防止放电导致逆流,【从储能】不动作";
}
}
else
{
//展示用
ManageRealPw = EsEleMeter4.AvePw - EsEleMeter1.AvePw;
SlaveClient.ServerCmd.CmdType = "DisCharg"; //功率为0充放电的指令类型无所谓
SlaveClient.ServerCmd.CmdPw = 0;
SlaveControlMsg = "【从储能】放电【管理大楼】:当前未处于峰和尖峰时刻,【从储能】不动作";
}
///////////////主储能箱体充电 吸收光伏能量的控制
//注意方向
TaxRealPw = SolarEleMeter5.AvePw - SolarEleMeter3.AvePw;//光伏和市电供应给税务大楼的负载
TotalSolarPw = Math.Abs(SolarEleMeter2.AvePw + SolarEleMeter3.AvePw);
//调节储能吸收光伏能量来调节光伏到税务大楼的能量,根据真实负载的值来判断是否调控介入
if (TaxRealPw > TaxTargetPwBigSmallUp)
{
////////////////////调节使光伏尽可能满足税务大楼////////////////////
if (MasterClient.IsCanCharg())
{
//差值 这个差值是3号电表已经补充进去之后差值
TaxDifLoad = TaxRealPw - TaxTargetPw;
//*************** 储能和光伏综合调节达到这个差值 TaxDifLoad ***************
if (TaxDifLoad < TotalSolarPw)//光伏的现在能力能达到这个差值,就是可以调节
{
//差值
var difValue1 = TotalSolarPw - TaxDifLoad;
// 储能吸收这个差值剩余的TaxDifLoad 光伏值给税务大楼,以达到要求
MasterClient.ServerCmd.CmdPw = difValue1;
MasterClient.ServerCmd.CmdType = "Charg";
MasterControlMsg = "【主储能】充电吸收【光伏】电能:【主储能】充电满足差值,【主储能】正常充电";
}
else
{
//当前光伏的所有能量无法满足需要这个差值,那么全部光伏给税务大楼,储能不进行充电
MasterClient.ServerCmd.CmdPw = 0;
MasterClient.ServerCmd.CmdType = "Charg";
MasterControlMsg = "【主储能】充电吸收【光伏】电能:【主储能】不需要充电,把光伏都给税务大楼,【主储能】不动作";
}
}
else
{
MasterControlMsg = "【主储能】充电吸收【光伏】电能:【主储能】无法充电,【主储能】停止工作";
}
}
else
{
/////////////真实的负载小于目标值
//让主储能尽力充电吸走光伏的能力,为了防止逆流,
if (MasterClient.IsCanCharg())
{
if (TotalSolarPw < MasterClient.ClientInfo!.MaxChargPw)
{
//光伏发电能力小于储能充电的能力,储能把光伏吸收完全
MasterClient.ServerCmd.CmdType = "Charg";
MasterClient.ServerCmd.CmdPw = TotalSolarPw;
MasterControlMsg = "【主储能】充电吸收【光伏】电能:【主储能】完全吸收光伏电能,【主储能】正常充电";
}
else
{
//光伏发电能力大于储能充电能力,则储能尽可能充电吧,存在逆流可能了,储能尽力了 【存在逆流的可能性】
MasterClient.ServerCmd.CmdType = "Charg";
MasterClient.ServerCmd.CmdPw = (MasterClient.ClientInfo!.MaxChargPw * ConfigDataService.energyStorageRunConfig.MaxBatChargRatio);
MasterControlMsg = "【主储能】充电吸收【光伏】电能:税务大楼负载小,【主储能】最大功率充电吸收光伏电能,存在【逆流】可能性!!!!!!!!!!!!!!!!!!";
}
}
else
{
MasterControlMsg = "【主储能】充电吸收【光伏】电能:【主储能】无法充电,【主储能】停止工作";
}
}
}
catch (Exception ex)
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【EntryAlarm】-{ex.Message}");
}
}
}, SlaveTokenSource.Token);
}
/// <summary>
/// 退出 Slave状态
/// </summary>
/// <exception cref="NotImplementedException"></exception>
private void ExitSlave()
{
SlaveTokenSource.Cancel();
//退出Slave状态关闭输出
SlaveClient.ServerCmd.CmdPw = 0;
MasterClient.ServerCmd.CmdPw = 0;
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-离开【EntrySlave】模式");
}
/// <summary>
/// 进入 Master状态
/// </summary>
/// <exception cref="NotImplementedException"></exception>
private void EntryMaster()
{
CurESChargInfo = ESChargInfo.Master;
Console.WriteLine($"时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}-【月浦】- 运行进入【Master】模式 ");
MasterTokenSource = new CancellationTokenSource();
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-进入【EntryMaster】模式");
Task.Run(() =>
{
//判断状态是否正确
while (CurESChargInfo == ESChargInfo.Master)
{
try
{
Thread.Sleep(1000);
if (MasterTokenSource.IsCancellationRequested)
{
break;
}
if (MasterClient == null)//防止未加载导致错误
{
continue;
}
if (!YuPuAutoHand)
{
continue;
}
//打开光伏
if (CheckSolarState() == false)
{
OpenSolar();
}
//循环执行方法
//在峰的时候放电
//if (PeakValleySglModel == ElePVEnum.TopPeak || PeakValleySglModel == ElePVEnum.Peak)
if (ConfigDataService.IsTimeCanDischarge())
{
///////////////////Master主控制柜给管理大楼供电
//注意方向
//主储能箱体放电的控制
ManageRealPw = EsEleMeter4.AvePw - EsEleMeter1.AvePw;
if (ManageRealPw > ManageTargetPw)//真实的负载大于一个值的时候才介入,或者控制关口电表的数据维持在一个水平,两个不同的控制方式
{
//Master 主储能箱根据负载情况给管理大楼放电
//可以放电的话,则
if (MasterClient.IsCanDisCharg())
{
MasterClient.ServerCmd.CmdType = "DisCharg";
ManageDifLoad = ManageRealPw - ManageTargetPw;
if (ManageDifLoad < MasterClient.ClientInfo.MaxDisChargPw)
{
//储能发送功率 补全总功率数据
//DisChargPwCmd(AcrelMeters.Psum - EleMeterActionLimitValue.MidValue);
MasterClient.ServerCmd.CmdPw = ManageDifLoad;
MasterControlMsg = "【主储能】放电【管理大楼】:【主储能】放电补全目标差值,【主储能】正常放电";
}
else //储能不能满足总的差值
{
//满功率放电
//储能全功率放电,不足的部分电网自动补全
MasterClient.ServerCmd.CmdPw = (MasterClient.ClientInfo.MaxDisChargPw * ConfigDataService.energyStorageRunConfig.MaxBatDisChargRatio);
MasterControlMsg = "【主储能】放电【管理大楼】:【主储能】全功率放电无法补全差值,【主储能】全功率放电";
}
}
else
{
//储能处于待机状态,防止其他的状态残留
MasterClient.ServerCmd.CmdType = "DisCharg";//功率为0充放电的指令类型无所谓
MasterClient.ServerCmd.CmdPw = 0;
MasterControlMsg = "【主储能】放电【管理大楼】:【主储能】无法放电,【主储能】不动作";
}
}
else
{
//真实的负载功率比较小,防止储能放电导致逆流,就不需要输出动作了,等真实的负载大于一个值后才开始介入
MasterClient.ServerCmd.CmdType = "DisCharg"; //功率为0充放电的指令类型无所谓
MasterClient.ServerCmd.CmdPw = 0;
MasterControlMsg = "【主储能】放电【管理大楼】:【管理大楼】负载较小,为防止放电导致逆流,【主储能】不动作";
}
}
else
{
ManageRealPw = EsEleMeter4.AvePw - EsEleMeter1.AvePw;
MasterClient.ServerCmd.CmdType = "DisCharg"; //功率为0充放电的指令类型无所谓
MasterClient.ServerCmd.CmdPw = 0;
MasterControlMsg = "【主储能】放电【管理大楼】:当前未处于峰和尖峰时刻,【主储能】不放电";
}
///////////////从储能箱体充电 吸收光伏能量的控制
//注意方向
TaxRealPw = SolarEleMeter5.AvePw - SolarEleMeter3.AvePw;//光伏和市电供应给税务大楼的负载
TotalSolarPw = Math.Abs(SolarEleMeter2.AvePw + SolarEleMeter3.AvePw);
//调节储能吸收光伏能量来调节光伏到税务大楼的能量,根据真实负载的值来判断是否调控介入
//税务真实的负载比较大的时候,储能参与调控吸收能量,真实的负载比较小的时候,则调动储能吸收全部光伏能量。
//这个点的判断跟其他的标准的不一样
if (TaxRealPw >= TaxTargetPwBigSmallUp)//TaxTargetPwBigSmall则是认为税务负载大和小的值
{
//有负载,并且认为负载较大,则参与调控
////////////////////调节使光伏尽可能满足税务大楼////////////////////
if (SlaveClient.IsCanCharg())
{
//差值 这个差值是3号电表已经补充进去之后差值
TaxDifLoad = TaxRealPw - TaxTargetPw;
//*************** 储能和光伏综合调节使3号电表给税务大楼供电达到这个差值 TaxDifLoad ***************
if (TaxDifLoad < TotalSolarPw)//光伏的现在能力能达到这个差值,就是可以调节
{
//差值
var difValue1 = TotalSolarPw - TaxDifLoad;
// 储能吸收这个差值剩余的TaxDifLoad 光伏值给税务大楼,以达到要求
SlaveClient.ServerCmd.CmdPw = difValue1;
SlaveClient.ServerCmd.CmdType = "Charg";
SlaveControlMsg = "【从储能】充电吸收【光伏】电能【从储能】充电满足差值1【从储能】正常充电";
}
else
{
//当前光伏的所有能量无法满足需要这个差值,那么全部光伏给税务大楼,储能不进行充电
SlaveClient.ServerCmd.CmdPw = 0;
SlaveClient.ServerCmd.CmdType = "Charg";
SlaveControlMsg = "【从储能】充电吸收【光伏】电能【从储能】不需要充电把光伏都给税务大楼2【从储能】不动作";
}
}
else
{
//从储能无法充电
SlaveClient.ServerCmd.CmdType = "Charg";
SlaveClient.ServerCmd.CmdPw = 0;
SlaveControlMsg = "【从储能】充电吸收【光伏】电能【从储能】无法充电3【从储能】停止工作";
}
}
else//真实的负载比较小的时候,认为负载很小,则考虑调动储能吸收光伏能量。
{
//观测3号电表的情况维持3号电表的数据就相当于控制光伏输出到税务大楼的能量
//这样控制会比较直接,否则波动有点大
if (SlaveClient.IsCanCharg())
{
if (TotalSolarPw < SlaveClient.ClientInfo!.MaxChargPw)
{
//光伏发电能力小于储能充电的能力,储能把光伏吸收完全
SlaveClient.ServerCmd.CmdType = "Charg";
SlaveClient.ServerCmd.CmdPw = TotalSolarPw;
SlaveControlMsg = "【从储能】充电吸收【光伏】电能【从储能】完全吸收光伏电能4【从储能】正常充电";
}
else
{
//光伏发电能力大于储能充电能力,则储能尽可能充电吧,存在逆流可能了,储能尽力了 【存在逆流的可能性】
SlaveClient.ServerCmd.CmdType = "Charg";
SlaveClient.ServerCmd.CmdPw = (SlaveClient.ClientInfo!.MaxChargPw * ConfigDataService.energyStorageRunConfig.MaxBatChargRatio);
SlaveControlMsg = "【从储能】充电吸收【光伏】电能税务大楼负载小5【从储能】最大功率充电吸收光伏电能存在【逆流】可能性";
}
}
else
{
//从储能无法充电
SlaveClient.ServerCmd.CmdType = "Charg";
SlaveClient.ServerCmd.CmdPw = 0;
SlaveControlMsg = "【从储能】充电吸收【光伏】电能:【从储能】无法充电,【从储能】停止工作";
}
}
}
catch (Exception ex)
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【EntryAlarm】-{ex.Message}");
}
}
}, MasterTokenSource.Token);
}
/// <summary>
/// 退出 Master状态
/// </summary>
/// <exception cref="NotImplementedException"></exception>
private void ExitMaster()
{
MasterTokenSource.Cancel();
//退出Master状态关闭输出
SlaveClient.ServerCmd.CmdPw = 0;
MasterClient.ServerCmd.CmdPw = 0;
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-推出【EntryMaster】模式");
}
/// <summary>
/// 获取运行状态
/// </summary>
private void GetRunState()
{
///////////////////确认控制模式////////////////////////////////////////
//在白天的情况下,主储能箱和从储能储能箱切换的标准:以不浪费光伏为主
//依据当前的状态判断是否需要切换到其他状态
switch (CurESChargInfo)
{
case ESChargInfo.Master:
//判断从储能箱是否被光伏充满,以不浪费光伏为主
if (SlaveClient.ClientInfo!.SOC >= 85)
{
//从储能箱充满,那么具备切换的条件,还需要判断一下,主储能箱体是否被消耗掉
if (MasterClient.ClientInfo!.SOC <= 80)
{
if (PreCheckIsESChargInfoChange(ESChargInfo.Slave))
{
if (!IsESChargInfoChange)
{
//模式改变的话,先关闭输出,继续下一个循环,给开关动作预留时间
SlaveClient.ServerCmd.CmdPw = 0;
MasterClient.ServerCmd.CmdPw = 0;
IsESChargInfoChange = true;
return;
}
}
//从储能箱充满,主储能箱未满,则可以切换
CurESChargInfo = ESChargInfo.Slave;
IsESChargInfoChange = false;
//SetControlModel(CurESChargInfo);
}
else
{
//从储能箱充满,主储能箱并没有完全释放完毕,即是两个储能箱体都满了,
//从储能箱停止光伏充电,直到主储能箱释放能量后再切换
//那么此时如果光伏发电很大税务大楼负载很小触发光伏并网柜的逆流保护切换光伏并网柜的市电光伏停止输出从储能箱PCS无市电会报错交流欠压
//还能切换回来吗?如何恢复?
//当主储能继续放电,那么后面可以切换,
// 1从储能箱体切到管理大楼从储能箱从刚才的报错中恢复正常主储能箱切换到税务大楼并网柜已经停止市电连接了也会PCS无市电主储能箱报错交流欠压需要人过来切换3号电表附近的那个开关
// 2) 切到晚上了,主储能没有影响,从储能本身就满了,然后继续无市电,报错
SlaveClient.ServerCmd.CmdType = "Charg";
SlaveClient.ServerCmd.CmdPw = 0;
}
}
else
{
//以不浪费光伏为主
//从储能箱光伏没有充满维持当前的状态可能主储能箱在放电中或者放电到SOC低后停止放电
}
break;
case ESChargInfo.Slave:
//判断主储能箱是否被光伏充满,以不浪费光伏为主
if (MasterClient.ClientInfo!.SOC >= 85)
{
//主储能箱充满,那么具备切换的条件,还需要判断一下,从储能箱体是否被消耗掉
if (SlaveClient.ClientInfo!.SOC <= 80)
{
if (PreCheckIsESChargInfoChange(ESChargInfo.Master))
{
if (!IsESChargInfoChange)
{
//模式改变的话,先关闭输出,继续下一个循环,给开关动作预留时间
SlaveClient.ServerCmd.CmdPw = 0;
MasterClient.ServerCmd.CmdPw = 0;
IsESChargInfoChange = true;
return;
}
}
//主储能箱充满,从储能箱未满,则可以切换
CurESChargInfo = ESChargInfo.Master;
IsESChargInfoChange = false;
//SetControlModel(CurESChargInfo);
}
else
{
//主储能箱充满,从储能箱并没有完全释放完毕,即是两个储能箱体都满了,
//主储能箱停止光伏充电,直到从储能箱释放能量后再切换
// 那么此时如果光伏发电很大税务大楼负载很小触发光伏并网柜的逆流保护切换光伏并网柜的市电光伏停止输出主储能箱PCS无市电会报错交流欠压
//还能切换回来吗?如何恢复?
//当从储能继续放电,那么后面可以切换,
// 1主储能箱体切到管理大楼主储能箱从刚才的报错中恢复正常从储能箱切换到税务大楼并网柜已经停止市电连接了也会PCS无市电从储能箱报错需要人过来切换3号电表附近的那个开关
// 2) 切到晚上了主储能切换到管理大楼无市电导致PCS报错消失正常运行但是已经满了晚上也不会充电了从储能开始无市电PCS报错交流欠压
SlaveClient.ServerCmd.CmdType = "Charg";
SlaveClient.ServerCmd.CmdPw = 0;
}
}
else
{
//主储能箱光伏没有充满,维持当前的状态
}
break;
//为什么白天有Night_Master的判断因为从晚上切换到白天时需要由晚上作为旧的基础模型然后切换到Master或者Slaver
//先判断白天晚上然后再具体操作模式从晚上到白天后具体模式还是晚上需要从晚上的模式到白天的Master或者Slaver模式
case ESChargInfo.Night_Master:
//从晚上切换到白天切换默认晚上给主储能箱充电满了直接到Master模式
//判断主储能箱是否被光伏充满,以不浪费光伏为主
//还需要判断吗直接到Master不就行了嘛
if (MasterClient.ClientInfo!.SOC >= 70)
{
//主储能箱充满,那么具备切换的条件,还需要判断一下,从储能箱体是否被消耗掉
if (SlaveClient.ClientInfo!.SOC <= 60)
{
if (PreCheckIsESChargInfoChange(ESChargInfo.Master))
{
if (!IsESChargInfoChange)
{
//模式改变的话,先关闭输出,继续下一个循环,给开关动作预留时间
SlaveClient.ServerCmd.CmdPw = 0;
MasterClient.ServerCmd.CmdPw = 0;
IsESChargInfoChange = true;
return;
}
}
//主储能箱充满,从储能箱未满,则可以切换
CurESChargInfo = ESChargInfo.Master;
IsESChargInfoChange = false;
//SetControlModel(CurESChargInfo);
}
else
{
//主储能箱充满,从储能箱并没有完全释放完毕,即是两个储能箱体都满了,
//主储能箱停止光伏充电,直到从储能箱释放能量后再切换
// 那么此时如果光伏发电很大税务大楼负载很小触发光伏并网柜的逆流保护切换光伏并网柜的市电光伏停止输出主储能箱PCS无市电会报错交流欠压
//还能切换回来吗?如何恢复?
//当从储能继续放电,那么后面可以切换,
// 1主储能箱体切到管理大楼主储能箱从刚才的报错中恢复正常从储能箱切换到税务大楼并网柜已经停止市电连接了也会PCS无市电从储能箱报错需要人过来切换3号电表附近的那个开关
// 2) 切到晚上了主储能切换到管理大楼无市电导致PCS报错消失正常运行但是已经满了晚上也不会充电了从储能开始无市电PCS报错交流欠压
SlaveClient.ServerCmd.CmdType = "Charg";
SlaveClient.ServerCmd.CmdPw = 0;
}
}
else
{
//主储能箱光伏没有充满,维持当前的状态
}
break;
default:
break;
}
}
/// <summary>
/// 手动运行模式切换
/// </summary>
public void HandRunModel(ESChargInfo Value)
{
switch (Value)
{
case ESChargInfo.Inital:
break;
case ESChargInfo.Master:
PreESChargInfo = ESChargInfo.Master;
SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig);
break;
case ESChargInfo.Slave:
PreESChargInfo = ESChargInfo.Slave;
SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig);
break;
case ESChargInfo.Night_Master:
PreESChargInfo = ESChargInfo.Night_Master;
SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig);
break;
case ESChargInfo.Wait:
break;
default:
break;
}
}
/// <summary>
/// Master的线程取消操作
/// </summary>
private CancellationTokenSource MasterTokenSource { get; set; }
/// <summary>
/// Slave的线程取消操作
/// </summary>
private CancellationTokenSource SlaveTokenSource { get; set; }
/// <summary>
/// Night_Master的线程取消操作
/// </summary>
private CancellationTokenSource Night_MasterTokenSource { get; set; }
/// <summary>
/// Night_Slave的线程取消操作
/// </summary>
private CancellationTokenSource Night_SlaveTokenSource { get; set; }
/// <summary>
/// Wait的线程取消操作
/// </summary>
private CancellationTokenSource WaitTokenSource { get; set; }
/// <summary>
/// NoSolar的线程取消操作
/// </summary>
private CancellationTokenSource NoSolarTokenSource { get; set; }
/// <summary>
/// 关闭系统:停止线程循环并断开所有通信连接
/// </summary>
public void CloseSystem()
{
// 先关闭扫描线程使能,令 while(ThreadEnable) 循环尽快退出
try { ThreadEnable = false; } catch { }
try { MasterTokenSource?.Cancel(); } catch { }
try { SlaveTokenSource?.Cancel(); } catch { }
try { Night_MasterTokenSource?.Cancel(); } catch { }
try { Night_SlaveTokenSource?.Cancel(); } catch { }
try { WaitTokenSource?.Cancel(); } catch { }
try { NoSolarTokenSource?.Cancel(); } catch { }
// 停止输出,置零功率
try { if (SlaveClient != null) { SlaveClient.ServerCmd.CmdPw = 0; } } catch { }
try { if (MasterClient != null) { MasterClient.ServerCmd.CmdPw = 0; } } catch { }
// 断开设备通信
try { SolarMdDrive?.Close(); } catch { }
try { MeterMdDrive?.Close(); } catch { }
try { ModbusTcpNetDrive?.ConnectClose(); } catch { }
try { BmsDataService.CloseDrive(); } catch { }
try { FFService.CloseDrive(); } catch { }
try { InPowerPCSDataService.CloseModbusRtu(); } catch { }
}
/// <summary>
/// 预先状态
/// 就是确定目标状态,先进入准备状态时这个预先的状态信息,等条件具备后再进行操作
/// </summary>
private ESChargInfo PreESChargInfo { get; set; }
#endregion
private bool _YuPuAutoHand = true;
/// <summary>
/// 手自动信息
/// 0未手动 false
/// 1为自动 true
/// </summary>
public bool YuPuAutoHand
{
get { return _YuPuAutoHand; }
set { _YuPuAutoHand = value; RaisePropertyChanged(); }
}
/// <summary>
/// 客户端集合
/// </summary>
public List<DistClient> ListDistClients { get; set; }
/// <summary>
/// 主客户端
/// </summary>
public DistClient MasterClient { get; set; }
/// <summary>
/// Slave客户端
/// </summary>
public DistClient SlaveClient { get; set; }
/// <summary>
/// 扫描线程
/// </summary>
private void MeterScanDeviceStart()
{
ScanDeviceTask = Task.Run(async () =>
{
while (ThreadEnable)
{
await Task.Delay(20);
//DiagnosticsTime.Reset();
//DiagnosticsTime.Start();
try
{
//1号电表 站号1
OperateResultBytes1 = MeterMdDrive.Read($"s=1;0", 35);
if (OperateResultBytes1.IsSuccess)
{
//有符号功率 二次有功功率数据,单位 W转换成一次功率需乘上电流、电压变比 地址 07H
//转换成Kw /1000 CT=80
EsEleMeter1.RtPw = MeterMdDrive.ByteTransform.TransInt16(OperateResultBytes1.Content, 14) * 80.0 / 1000.0;//
EsEleMeter1.EleQ_P = MeterMdDrive.ByteTransform.TransUInt32(OperateResultBytes1.Content, 58) * 80.0 / 100.0;//
EsEleMeter1.EleQ_N = MeterMdDrive.ByteTransform.TransUInt32(OperateResultBytes1.Content, 62) * 80.0 / 100.0;//
//通信正常
EsEleMeter1.MeterLinkState = true;
}
else
{
//通信正常
EsEleMeter1.MeterLinkState = false;
}
//2号电表 站号2
OperateResultBytes2 = MeterMdDrive.Read($"s=2;0", 35);
if (OperateResultBytes2.IsSuccess)
{
//有符号功率 二次有功功率数据,单位 W转换成一次功率需乘上电流、电压变比 地址 07H
//转换成Kw /1000 CT=20
SolarEleMeter2.RtPw = MeterMdDrive.ByteTransform.TransInt16(OperateResultBytes2.Content, 14) * 20.0 / 1000.0;//
SolarEleMeter2.EleQ_P = MeterMdDrive.ByteTransform.TransUInt32(OperateResultBytes2.Content, 58) * 20.0 / 100.0;//
SolarEleMeter2.EleQ_N = MeterMdDrive.ByteTransform.TransUInt32(OperateResultBytes2.Content, 62) * 20.0 / 100.0;//
//通信正常
SolarEleMeter2.MeterLinkState = true;
}
else
{
//通信正常
SolarEleMeter2.MeterLinkState = false;
}
//3号电表 站号3
OperateResultBytes3 = MeterMdDrive.Read($"s=3;0", 35);
if (OperateResultBytes3.IsSuccess)
{
//有符号功率 二次有功功率数据,单位 W转换成一次功率需乘上电流、电压变比 地址 07H
//转换成Kw /1000 CT=80
SolarEleMeter3.RtPw = MeterMdDrive.ByteTransform.TransInt16(OperateResultBytes3.Content, 14) * 80.0 / 1000.0;//
SolarEleMeter3.EleQ_P = MeterMdDrive.ByteTransform.TransUInt32(OperateResultBytes3.Content, 58) * 80.0 / 100.0;//
SolarEleMeter3.EleQ_N = MeterMdDrive.ByteTransform.TransUInt32(OperateResultBytes3.Content, 62) * 80.0 / 100.0;//
//通信正常
SolarEleMeter3.MeterLinkState = true;
}
else
{
//通信正常
SolarEleMeter3.MeterLinkState = false;
}
//4号电表 站号4
OperateResultBytes4 = MeterMdDrive.Read($"s=4;0", 35);
if (OperateResultBytes4.IsSuccess)
{
//有符号功率 二次有功功率数据,单位 W转换成一次功率需乘上电流、电压变比 地址 07H
//转换成Kw /1000 CT=80
EsEleMeter4.RtPw = MeterMdDrive.ByteTransform.TransInt16(OperateResultBytes4.Content, 14) * 80.0 / 1000.0;//
EsEleMeter4.EleQ_P = MeterMdDrive.ByteTransform.TransUInt32(OperateResultBytes4.Content, 58) * 80.0 / 100.0;//
EsEleMeter4.EleQ_N = MeterMdDrive.ByteTransform.TransUInt32(OperateResultBytes4.Content, 62) * 80.0 / 100.0;//
//通信正常
EsEleMeter4.MeterLinkState = true;
}
else
{
//通信正常
EsEleMeter4.MeterLinkState = false;
}
//5号电表 站号5
OperateResultBytes5 = MeterMdDrive.Read($"s=5;x=3;2048", 28);
if (OperateResultBytes5.IsSuccess)
{
//081AH 总有功功率 4 R 081CH A 相无功功
//本身单位Kw
SolarEleMeter5.RtPw = MeterMdDrive.ByteTransform.TransSingle(OperateResultBytes5.Content, 52);//
//Psum = MeterMdDrive.ByteTransform.TransSingle(OperateResultBytes1.Content, 52);//BaseIndex1 + 0x81A
//Qsum = MeterMdDrive.ByteTransform.TransSingle(OperateResultBytes1.Content, 68);//BaseIndex1 + 0x822
//Ua = MeterMdDrive.ByteTransform.TransSingle(OperateResultBytes1.Content, 0);//BaseIndex1 + 0x800
//Ub = MeterMdDrive.ByteTransform.TransSingle(OperateResultBytes1.Content, 4);//BaseIndex1 + 0x802
//Uc = MeterMdDrive.ByteTransform.TransSingle(OperateResultBytes1.Content, 8);//BaseIndex1 + 0x804
//Ia = MeterMdDrive.ByteTransform.TransSingle(OperateResultBytes1.Content, 24);//BaseIndex1 + 0x80C
//Ib = MeterMdDrive.ByteTransform.TransSingle(OperateResultBytes1.Content, 28);//BaseIndex1 + 0x80E
//Ic = MeterMdDrive.ByteTransform.TransSingle(OperateResultBytes1.Content, 32);//BaseIndex1 + 0x810
//cosQ = MeterMdDrive.ByteTransform.TransSingle(OperateResultBytes1.Content, 100);//BaseIndex1 + 0x832
//通信正常
SolarEleMeter5.MeterLinkState = true;
}
else
{
//通信正常
SolarEleMeter5.MeterLinkState = false;
}
//两个合并的电能减去晚上储能空调的电能
SolarTotalPw = SolarEleMeter3.EleQ_N + SolarEleMeter2.EleQ_N - SolarEleMeter3.EleQ_P;
}
catch (Exception ex)
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【Meter】-{ex.Message}");
}
//DiagnosticsTime.Stop();
//ScanRtTimeinfo = $"电表:{DiagnosticsTime.Elapsed.TotalMilliseconds.ToString()}";
}
});
}
/// <summary>
/// 远程IO扫描线程
/// </summary>
private void ReIOScanDeviceStart()
{
ReIOScanDeviceTask = Task.Run(async () =>
{
while (ThreadEnable)
{
await Task.Delay(100);
//DiagnosticsTime.Reset();
//DiagnosticsTime.Start();
try
{
ReIOOperateResultBytes1 = ModbusTcpNetDrive.ReadBool($"x=1;1", 8);
if (ReIOOperateResultBytes1.IsSuccess)
{
QFSwitch1.CurSwtichState = GetSwitch(ReIOOperateResultBytes1.Content[0]);//DI1
if (QFSwitch1.CurSwtichState == SwitchEm.On)
{
QFSwitch1.CurSwitchStateInfo = SwitchStateInfo.On;
}
else
{
QFSwitch1.CurSwitchStateInfo = SwitchStateInfo.Off;
}
QFSwitch2.CurSwtichState = GetSwitch(ReIOOperateResultBytes1.Content[2]);//DI3
if (QFSwitch2.CurSwtichState == SwitchEm.On)
{
QFSwitch2.CurSwitchStateInfo = SwitchStateInfo.On;
}
else
{
QFSwitch2.CurSwitchStateInfo = SwitchStateInfo.Off;
}
QFSwitch3.CurSwtichState = GetSwitch(ReIOOperateResultBytes1.Content[4]);//DI5
if (QFSwitch3.CurSwtichState == SwitchEm.On)
{
QFSwitch3.CurSwitchStateInfo = SwitchStateInfo.On;
}
else
{
QFSwitch3.CurSwitchStateInfo = SwitchStateInfo.Off;
}
QFSwitch4.CurSwtichState = GetSwitch(ReIOOperateResultBytes1.Content[6]);//DI7
if (QFSwitch4.CurSwtichState == SwitchEm.On)
{
QFSwitch4.CurSwitchStateInfo = SwitchStateInfo.On;
}
else
{
QFSwitch4.CurSwitchStateInfo = SwitchStateInfo.Off;
}
QFSwitch1.CurSwtichErrInfo = GetSwitchErr(ReIOOperateResultBytes1.Content[1]);//DI2
if (QFSwitch1.CurSwtichErrInfo == SwitchErr.NG)
{
QFSwitch1.CurSwitchStateInfo = SwitchStateInfo.Err;
}
QFSwitch2.CurSwtichErrInfo = GetSwitchErr(ReIOOperateResultBytes1.Content[3]);//DI4
if (QFSwitch2.CurSwtichErrInfo == SwitchErr.NG)
{
QFSwitch2.CurSwitchStateInfo = SwitchStateInfo.Err;
}
QFSwitch3.CurSwtichErrInfo = GetSwitchErr(ReIOOperateResultBytes1.Content[5]);//DI6
if (QFSwitch3.CurSwtichErrInfo == SwitchErr.NG)
{
QFSwitch3.CurSwitchStateInfo = SwitchStateInfo.Err;
}
QFSwitch4.CurSwtichErrInfo = GetSwitchErr(ReIOOperateResultBytes1.Content[7]);//DI8
if (QFSwitch4.CurSwtichErrInfo == SwitchErr.NG)
{
QFSwitch4.CurSwitchStateInfo = SwitchStateInfo.Err;
}
//通信正常
ReIOLinkState = true;
}
else
{
//通信失败
ReIOLinkState = false;
}
}
catch (Exception ex)
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【Meter】-{ex.Message}");
}
//DiagnosticsTime.Stop();
//ScanRtTimeinfo = $"电表:{DiagnosticsTime.Elapsed.TotalMilliseconds.ToString()}";
}
});
}
/// <summary>
/// 光伏设备的扫描 YuPuSolarTask
/// </summary>
private void SolarScanDeviceStart()
{
YuPuSolarTask = Task.Run(async () =>
{
while (ThreadEnable)
{
await Task.Delay(400);
//DiagnosticsTime.Reset();
//DiagnosticsTime.Start();
try
{
SolarOperateResultBytes1 = SolarMdDrive.Read($"s=2;x=4;5000", 35);
if (SolarOperateResultBytes1.IsSuccess)
{
SolarRtPw1 = SolarMdDrive.ByteTransform.TransUInt16(SolarOperateResultBytes1.Content, 62) * 1.0 / 1000;//
//通信正常
SolarLinkState = true;
SolarLinkTrigTimeModel.UpdateByBool(true);
}
else
{
//通信失败
SolarLinkState = false;
SolarLinkTrigTimeModel.UpdateByBool(false);
}
SolarOperateResultBytes2 = SolarMdDrive.Read($"s=3;x=4;5000", 35);
if (SolarOperateResultBytes2.IsSuccess)
{
//跟手册不太一样 TransUInt32
SolarRtPw2 = SolarMdDrive.ByteTransform.TransUInt16(SolarOperateResultBytes2.Content, 62) * 1.0 / 1000;//
//通信正常
SolarLinkState = true;
SolarLinkTrigTimeModel.UpdateByBool(true);
SolarTotalRt = SolarRtPw1 + SolarRtPw2;
}
else
{
//通信失败
SolarLinkState = false;
SolarLinkTrigTimeModel.UpdateByBool(false);
//同时
SolarTotalRt = 0;
}
SolarOperateResultBytes3 = SolarMdDrive.Read($"s=2;5000", 10);
if (SolarOperateResultBytes3.IsSuccess)
{
if (SolarMdDrive.ByteTransform.TransUInt16(SolarOperateResultBytes3.Content, 12) == 0xCE)
{
SolarRtState1 = false;
}
else if (SolarMdDrive.ByteTransform.TransUInt16(SolarOperateResultBytes3.Content, 12) == 0xCF)
{
SolarRtState1 = true;
}
//通信正常
SolarLinkState = true;
SolarLinkTrigTimeModel.UpdateByBool(true);
}
else
{
//通信失败
SolarLinkState = false;
SolarLinkTrigTimeModel.UpdateByBool(false);
}
SolarOperateResultBytes4 = SolarMdDrive.Read($"s=3;5000", 10);
if (SolarOperateResultBytes4.IsSuccess)
{
if (SolarMdDrive.ByteTransform.TransUInt16(SolarOperateResultBytes4.Content, 12) == 0xCE)
{
SolarRtState2 = false;
}
else if (SolarMdDrive.ByteTransform.TransUInt16(SolarOperateResultBytes4.Content, 12) == 0xCF)
{
SolarRtState2 = true;
}
//通信正常
SolarLinkState = true;
SolarLinkTrigTimeModel.UpdateByBool(true);
}
else
{
//通信失败
SolarLinkState = false;
SolarLinkTrigTimeModel.UpdateByBool(false);
}
}
catch (Exception ex)
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【Meter】-{ex.Message}");
}
//DiagnosticsTime.Stop();
//ScanRtTimeinfo = $"电表:{DiagnosticsTime.Elapsed.TotalMilliseconds.ToString()}";
}
});
}
private double _EsSoc1;
/// <summary>
/// 储能1的SOC
/// </summary>
public double EsSoc1
{
get { return _EsSoc1; }
set { _EsSoc1 = value; RaisePropertyChanged(); }
}
private double _EsSoc2;
/// <summary>
/// 储能2的SOC
/// </summary>
public double EsSoc2
{
get { return _EsSoc2; }
set { _EsSoc2 = value; RaisePropertyChanged(); }
}
private double _EsSoe2;
/// <summary>
/// 储能2的SOE
/// </summary>
public double EsSoe2
{
get { return _EsSoe2; }
set { _EsSoe2 = value; RaisePropertyChanged(); }
}
private double _EsSoe1;
/// <summary>
/// 储能1的SOE
/// </summary>
public double EsSoe1
{
get { return _EsSoe1; }
set { _EsSoe1 = value; RaisePropertyChanged(); }
}
private double _EsSoh1;
/// <summary>
/// 储能1的SOH
/// </summary>
public double EsSoh1
{
get { return _EsSoh1; }
set { _EsSoh1 = value; RaisePropertyChanged(); }
}
private double _EsSoh2;
/// <summary>
/// 储能2的SOH
/// </summary>
public double EsSoh2
{
get { return _EsSoh2; }
set { _EsSoh2 = value; RaisePropertyChanged(); }
}
private double _SolarRtPw1;
/// <summary>
/// 光伏1 实时功率
/// 来自于光伏
/// </summary>
public double SolarRtPw1
{
get { return _SolarRtPw1; }
set { _SolarRtPw1 = value; RaisePropertyChanged(); }
}
private double _SolarRtPw2;
/// <summary>
/// 光伏1 实时功率
/// 来自于光伏
/// </summary>
public double SolarRtPw2
{
get { return _SolarRtPw2; }
set { _SolarRtPw2 = value; RaisePropertyChanged(); }
}
private bool _SolarRtState1;
/// <summary>
/// 光伏1的状态
/// 开关的状态
/// True 开 False 关
/// </summary>
public bool SolarRtState1
{
get { return _SolarRtState1; }
set { _SolarRtState1 = value; RaisePropertyChanged(); }
}
private bool _SolarRtState2;
/// <summary>
/// 光伏2的状态
/// 开关的状态
/// True 开 False 关
/// </summary>
public bool SolarRtState2
{
get { return _SolarRtState2; }
set { _SolarRtState2 = value; RaisePropertyChanged(); }
}
private double _SolarTotalPw;
/// <summary>
///光伏总电能
///两个仪表合并得到
/// </summary>
public double SolarTotalPw
{
get { return _SolarTotalPw; }
set { _SolarTotalPw = value; RaisePropertyChanged(); }
}
private double _SolarTotalRt;
/// <summary>
///光伏的实时功率
///两个仪表合并得到
/// </summary>
public double SolarTotalRt
{
get { return _SolarTotalRt; }
set { _SolarTotalRt = value; RaisePropertyChanged(); }
}
/// <summary>
/// 关闭光伏
/// 两个光伏
/// </summary>
/// <returns></returns>
public bool CloseSolar()
{
var result1 = SolarMdDrive.Write("s=2;5006", (ushort)0xCE);
var result2 = SolarMdDrive.Write("s=3;5006", (ushort)0xCE);
if (result1.IsSuccess && result2.IsSuccess)
{
return true;
}
return false;
}
/// <summary>
/// 开启光伏
/// 两个光伏
/// </summary>
/// <returns></returns>
public bool OpenSolar()
{
var result1 = SolarMdDrive.Write("s=2;5006", (ushort)0xCF);
var result2 = SolarMdDrive.Write("s=3;5006", (ushort)0xCF);
if (result1.IsSuccess && result2.IsSuccess)
{
return true;
}
return false;
}
/// <summary>
/// 设置光伏输出百分比
/// 两个光伏
/// ratio是比值1.1 0.5 0.1等
/// </summary>
/// <returns></returns>
public bool SetSolarRatioValue(double ratio)
{
if (ratio > 1.1)
{
ratio = 1.1;
}
else if (ratio < 0)
{
ratio = 0;
}
//好像两个光伏板的功率不一样
short Value = (short)(ratio * 1000);
var result1 = SolarMdDrive.Write("s=2;5008", Value);
var result2 = SolarMdDrive.Write("s=3;5008", Value);
if (result1.IsSuccess && result2.IsSuccess)
{
return true;
}
return false;
}
/// <summary>
/// 获取光伏的运行状态
/// True 是开
/// False 是关
/// </summary>
/// <returns></returns>
private bool CheckSolarState()
{
if (!SolarLinkState)//通信失败,则认为光伏关闭了
{
return false;
}
if (SolarRtState1 && SolarRtState2)
{
return true;
}
return false;
}
/// <summary>
/// 获取光伏的实时功率
/// 来自于光伏
/// </summary>
/// <returns></returns>
public double GetTotalRtSolarPw()
{
if (SolarLinkState)//如果通信正常,则可以使用光伏的数据
{
return SolarRtPw1 + SolarRtPw2;
}
//否则使用电表的数据
return Math.Abs(SolarEleMeter3.RtPw);
}
/// <summary>
/// 获取状态值
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
private SwitchEm GetSwitch(bool value)
{
if (value)
{
return SwitchEm.On;
}
return SwitchEm.Off;
}
/// <summary>
/// 获取Err信息
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
private SwitchErr GetSwitchErr(bool value)
{
if (value)
{
return SwitchErr.NG;
}
return SwitchErr.OK;
}
private double _ManageRealPw;
/// <summary>
/// 管理大楼真实的负载
/// </summary>
public double ManageRealPw
{
get { return _ManageRealPw; }
set { _ManageRealPw = value; RaisePropertyChanged(); }
}
private double _TaxRealPw;
/// <summary>
/// 税务大楼的真实的负载
/// </summary>
public double TaxRealPw
{
get { return _TaxRealPw; }
set { _TaxRealPw = value; RaisePropertyChanged(); }
}
/// <summary>
/// 管理大楼 实际负载和目标值的差值
/// </summary>
public double ManageDifLoad { get; set; }
private string _CurESChargMsg = "Master";
/// <summary>
/// 消息内容
/// </summary>
public string CurESChargMsg
{
get { return _CurESChargMsg; }
set { _CurESChargMsg = value; RaisePropertyChanged(); }
}
private ESChargInfo _CurESChargInfo;
/// <summary>
///两个储能箱体选择切换的功能状态
///两个储能箱在管理大楼和税务大楼切换
/// </summary>
public ESChargInfo CurESChargInfo
{
get { return _CurESChargInfo; }
set
{
if (_CurESChargInfo != value)//状态改变,进行切换
{
//改变后进行开关控制操作
//SetControlModel(value);
_CurESChargInfo = value;
CurESChargMsg = value.ToString();
}
_CurESChargInfo = value;
}
}
/// <summary>
/// 预先检查状态是否会改变
/// </summary>
/// <returns></returns>
private bool PreCheckIsESChargInfoChange(ESChargInfo value)
{
if (value != _CurESChargInfo)
{
return true;
}
return false;
}
/// <summary>
/// 是否改变模式
/// </summary>
public bool IsESChargInfoChange { get; set; } = false;
private double _TotalSolarPw;
/// <summary>
/// 光伏的总的输出功率
/// </summary>
public double TotalSolarPw
{
get { return _TotalSolarPw; }
set { _TotalSolarPw = value; RaisePropertyChanged(); }
}
/// <summary>
/// 税务大楼根据真实负载介入的阈值 上限值
/// </summary>
public double TaxTargetPwUp { get; set; } = 10;
/// <summary>
/// 税务大楼根据真实负载介入的阈值 中间值
/// </summary>
public double TaxTargetPwMid { get; set; } = 10;
/// <summary>
/// 税务大楼根据真实负载介入的阈值 中间值
/// </summary>
public double TaxTargetPw { get; set; } = 6;
/// <summary>
/// 税务大楼根据真实负载介入的阈值 下限值
/// </summary>
public double TaxTargetPwDown { get; set; } = 18;
/// <summary>
/// 税务大楼根据真实负载是大还是小的判断值
/// </summary>
public double TaxTargetPwBigSmallUp { get; set; } = 8;
/// <summary>
/// 税务大楼根据真实负载是大还是小的判断值
/// </summary>
public double TaxTargetPwBigSmallDown { get; set; } = 15;
/// <summary>
/// 管理大楼根据真实负载介入的阈值
/// </summary>
public double ManageTargetPw { get; set; } = 8;
/// <summary>
/// 逆流的阀值
/// </summary>
public double TaxCurBackFlow { get; set; }
/// <summary>
/// 税务大楼 负载和目标值的差值
/// </summary>
public double TaxDifLoad { get; set; }
private string _MasterControlMsg;
/// <summary>
/// Master控制逻辑消息文本
/// </summary>
public string MasterControlMsg
{
get { return _MasterControlMsg; }
set { _MasterControlMsg = value; RaisePropertyChanged(); }
}
private string _SlaveControlMsg;
/// <summary>
/// Slave控制逻辑消息文本
/// </summary>
public string SlaveControlMsg
{
get { return _SlaveControlMsg; }
set { _SlaveControlMsg = value; RaisePropertyChanged(); }
}
private double _SolarToEsAsFullSoc = 97;
/// <summary>
/// 光伏给储能充电 作为满的比值
/// 90-97
/// </summary>
public double SolarToEsAsFullSoc
{
get { return _SolarToEsAsFullSoc; }
set { _SolarToEsAsFullSoc = value; RaisePropertyChanged(); }
}
private double _Master_ToSlaveByMasterSoc = 5;
/// <summary>
/// Master模式切换到Slave模式时MasterSOC的阀值
/// 两个同时考虑
/// </summary>
public double Master_ToSlaveByMasterSoc
{
get { return _Master_ToSlaveByMasterSoc; }
set { _Master_ToSlaveByMasterSoc = value; RaisePropertyChanged(); }
}
private double _Master_ToSlaveBySlaveSoc = 15;
/// <summary>
/// Master模式切换到Slave模式时Slave SOC的阀值
/// 两个同时考虑
/// </summary>
public double Master_ToSlaveBySlaveSoc
{
get { return _Master_ToSlaveBySlaveSoc; }
set { _Master_ToSlaveBySlaveSoc = value; RaisePropertyChanged(); }
}
private double _Master_SolarToSlaveEsFullByMasterSoc = 95;
/// <summary>
/// Master模式光伏给从储能充满了是否切换到Slave模式但是此时需要判断主储能MasterSOC是否满了主储能也满的话也无法接收光伏的电否则不切换
/// 主要考虑尽可能的不浪费光伏的电,主从储能只要有余量就要接受光伏的电,也要防止频繁的切换
/// < SolarToEsAsFullSoc
/// </summary>
public double Master_SolarToSlaveEsFullByMasterSoc
{
get { return _Master_SolarToSlaveEsFullByMasterSoc; }
set { _Master_SolarToSlaveEsFullByMasterSoc = value; RaisePropertyChanged(); }
}
private double _Slave_ToMasterBySlaveSoc = 5;
/// <summary>
/// Slave模式切换到Master模式时SOC的阀值
/// 两个同时考虑
/// </summary>
public double Slave_ToMasterBySlaveSoc
{
get { return _Slave_ToMasterBySlaveSoc; }
set { _Slave_ToMasterBySlaveSoc = value; RaisePropertyChanged(); }
}
private double _Slave_ToMasterByMasterSoc = 15;
/// <summary>
/// Slave模式切换到Master模式时SOC的阀值
/// 两个同时考虑
/// </summary>
public double Slave_ToMasterByMasterSoc
{
get { return _Slave_ToMasterByMasterSoc; }
set { _Slave_ToMasterByMasterSoc = value; RaisePropertyChanged(); }
}
private double _Slave_SolarToMasterEsFullBySlaverSoc = 95;
/// <summary>
/// Slave模式光伏给主储能充满了是否切换到Master模式但是此时需要判断从储能SOC是否满了从储能也满的话也无法接收光伏的电否则不切换
/// 主要考虑尽可能的不浪费光伏的电,主从储能只要有余量就要接受光伏的电,也要防止频繁的切换
/// < SolarToEsAsFullSoc
/// </summary>
public double Slave_SolarToMasterEsFullBySlaverSoc
{
get { return _Slave_SolarToMasterEsFullBySlaverSoc; }
set { _Slave_SolarToMasterEsFullBySlaverSoc = value; RaisePropertyChanged(); }
}
private double _NightMaster_ToMasterFullSoc = 98;
/// <summary>
/// 晚上,主储能充满的标志,也是切换到从储能的控制标志
/// </summary>
public double NightMaster_ToMasterFullSoc
{
get { return _NightMaster_ToMasterFullSoc; }
set { _NightMaster_ToMasterFullSoc = value; RaisePropertyChanged(); }
}
private double _NightSlave_ToSlaveFullSoc = 98;
/// <summary>
/// 晚上,从储能充满的标志,也是切换到主储能的控制标志
/// </summary>
public double NightSlave_ToSlaveFullSoc
{
get { return _NightSlave_ToSlaveFullSoc; }
set { _NightSlave_ToSlaveFullSoc = value; RaisePropertyChanged(); }
}
private string _LogicMsg;
/// <summary>
/// 逻辑消息
/// </summary>
public string LogicMsg
{
get { return _LogicMsg; }
set
{
if (value != _LogicMsg)
{
RaisePropertyChanged();
_LogicMsg = value;
}
}
}
/// <summary>
/// 逻辑扫描1
/// </summary>
private void LogicScanOld()
{
YuPuLogicScanTask = Task.Run(async () =>
{
await Task.Delay(5000);
//依据光伏发电为主要
while (ThreadEnable)
{
await Task.Delay(2000);
try
{
if (MasterClient == null)//防止未加载导致错误
{
continue;
}
///////////////////////////获取当前的是白天还是夜晚,依据时间判断,留有余量,有可能实际已经晚上了,但是负载全部没有了
if (IsBetweenTime(DateTime.Now.ToString(), SunriseTimeStr, SunsetTimeStr))
{
//根据日落和日出时间判断当前是白天
DayNight = DayNightEnum.Day;
}
else
{
DayNight = DayNightEnum.Night;
//var SolarCur = SolarEleMeter3.RtPw + SolarEleMeter2.RtPw;
//根据日落和日出时间判断当前是晚上
}
if (!YuPuAutoHand)
{
continue;
}
switch (DayNight)
{
//白天
case DayNightEnum.Day:
///////////////////////////主从储能箱如何分配 依据光伏发电为主要,不浪费光伏发电为主线
//白天光伏发电QF4断路器合闸(QF3分闸)主储能箱给管理大楼供电。QF1断路器合闸光伏给从储能箱充电QF2分闸
//若从储能箱满电后QF2、QF3断路器合闸QF1、QF4分闸光伏给主储能箱充电从储能箱给管理大楼供电。
///////////////////////根据控制模式进行储能输出信息/////////////////////////
if (CurESChargInfo == ESChargInfo.Master)//当前是主连接管理大楼作为放电使用,从作为充电吸收光伏
{
///////////////////Master主控制柜给管理大楼供电
//注意方向
//主储能箱体放电的控制
ManageRealPw = EsEleMeter4.AvePw - EsEleMeter1.AvePw;
if (ManageRealPw > ManageTargetPw)//真实的负载大于一个值的时候才介入,或者控制关口电表的数据维持在一个水平,两个不同的控制方式
{
//Master 主储能箱根据负载情况给管理大楼放电
//可以放电的话,则
if (MasterClient.IsCanDisCharg())
{
MasterClient.ServerCmd.CmdType = "DisCharg";
ManageDifLoad = ManageRealPw - ManageTargetPw;
if (ManageDifLoad < MasterClient.ClientInfo.MaxDisChargPw)
{
//储能发送功率 补全总功率数据
//DisChargPwCmd(AcrelMeters.Psum - EleMeterActionLimitValue.MidValue);
MasterClient.ServerCmd.CmdPw = ManageDifLoad;
MasterControlMsg = "【主储能】放电【管理大楼】:【主储能】放电补全目标差值,【主储能】正常放电";
}
else //储能不能满足总的差值
{
//满功率放电
//储能全功率放电,不足的部分电网自动补全
MasterClient.ServerCmd.CmdPw = (MasterClient.ClientInfo.MaxDisChargPw * ConfigDataService.energyStorageRunConfig.MaxBatDisChargRatio);
MasterControlMsg = "【主储能】放电【管理大楼】:【主储能】全功率放电无法补全差值,【主储能】全功率放电";
}
}
else
{
//储能处于待机状态,防止其他的状态残留
MasterClient.ServerCmd.CmdType = "DisCharg";//功率为0充放电的指令类型无所谓
MasterClient.ServerCmd.CmdPw = 0;
MasterControlMsg = "【主储能】放电【管理大楼】:【主储能】无法放电,【主储能】不动作";
}
}
else
{
//真实的负载功率比较小,防止储能放电导致逆流,就不需要输出动作了,等真实的负载大于一个值后才开始介入
MasterClient.ServerCmd.CmdType = "DisCharg"; //功率为0充放电的指令类型无所谓
MasterClient.ServerCmd.CmdPw = 0;
MasterControlMsg = "【主储能】放电【管理大楼】:【管理大楼】负载较小,为防止放电导致逆流,【主储能】不动作";
}
///////////////从储能箱体充电 吸收光伏能量的控制
//注意方向
TaxRealPw = SolarEleMeter5.AvePw - SolarEleMeter3.AvePw;//光伏和市电供应给税务大楼的负载
TotalSolarPw = Math.Abs(SolarEleMeter2.AvePw + SolarEleMeter3.AvePw);
//调节储能吸收光伏能量来调节光伏到税务大楼的能量,根据真实负载的值来判断是否调控介入
//税务真实的负载比较大的时候,储能参与调控吸收能量,真实的负载比较小的时候,则调动储能吸收全部光伏能量。
//这个点的判断跟其他的标准的不一样
if (TaxRealPw >= TaxTargetPwBigSmallUp)//TaxTargetPwBigSmall则是认为税务负载大和小的值
{
//有负载,并且认为负载较大,则参与调控
////////////////////调节使光伏尽可能满足税务大楼////////////////////
if (SlaveClient.IsCanCharg())
{
//差值 这个差值是3号电表已经补充进去之后差值
TaxDifLoad = TaxRealPw - TaxTargetPw;
//*************** 储能和光伏综合调节使3号电表给税务大楼供电达到这个差值 TaxDifLoad ***************
if (TaxDifLoad < TotalSolarPw)//光伏的现在能力能达到这个差值,就是可以调节
{
//差值
var difValue1 = TotalSolarPw - TaxDifLoad;
// 储能吸收这个差值剩余的TaxDifLoad 光伏值给税务大楼,以达到要求
SlaveClient.ServerCmd.CmdPw = difValue1;
SlaveClient.ServerCmd.CmdType = "Charg";
SlaveControlMsg = "【从储能】充电吸收【光伏】电能【从储能】充电满足差值1【从储能】正常充电";
}
else
{
//当前光伏的所有能量无法满足需要这个差值,那么全部光伏给税务大楼,储能不进行充电
SlaveClient.ServerCmd.CmdPw = 0;
SlaveClient.ServerCmd.CmdType = "Charg";
SlaveControlMsg = "【从储能】充电吸收【光伏】电能【从储能】不需要充电把光伏都给税务大楼2【从储能】不动作";
}
}
else
{
//从储能无法充电
SlaveClient.ServerCmd.CmdType = "Charg";
SlaveClient.ServerCmd.CmdPw = 0;
SlaveControlMsg = "【从储能】充电吸收【光伏】电能【从储能】无法充电3【从储能】停止工作";
}
}
else//真实的负载比较小的时候,认为负载很小,则考虑调动储能吸收光伏能量。
{
//观测3号电表的情况维持3号电表的数据就相当于控制光伏输出到税务大楼的能量
//这样控制会比较直接,否则波动有点大
if (SlaveClient.IsCanCharg())
{
if (TotalSolarPw < SlaveClient.ClientInfo!.MaxChargPw)
{
//光伏发电能力小于储能充电的能力,储能把光伏吸收完全
SlaveClient.ServerCmd.CmdType = "Charg";
SlaveClient.ServerCmd.CmdPw = TotalSolarPw;
SlaveControlMsg = "【从储能】充电吸收【光伏】电能【从储能】完全吸收光伏电能4【从储能】正常充电";
}
else
{
//光伏发电能力大于储能充电能力,则储能尽可能充电吧,存在逆流可能了,储能尽力了 【存在逆流的可能性】
SlaveClient.ServerCmd.CmdType = "Charg";
SlaveClient.ServerCmd.CmdPw = (SlaveClient.ClientInfo!.MaxChargPw * ConfigDataService.energyStorageRunConfig.MaxBatChargRatio);
SlaveControlMsg = "【从储能】充电吸收【光伏】电能税务大楼负载小5【从储能】最大功率充电吸收光伏电能存在【逆流】可能性";
}
}
else
{
//从储能无法充电
SlaveClient.ServerCmd.CmdType = "Charg";
SlaveClient.ServerCmd.CmdPw = 0;
SlaveControlMsg = "【从储能】充电吸收【光伏】电能:【从储能】无法充电,【从储能】停止工作";
}
}
}
else//当前是从储能箱作为放电,主储能箱作为充电吸收光伏
{
///////////////////Slave从控制柜给管理大楼供电
//注意方向
//从储能箱体放电的控制
ManageRealPw = EsEleMeter4.AvePw - EsEleMeter1.AvePw;
if (ManageRealPw > ManageTargetPw)//真实的负载大于一个值的时候才介入,或者控制关口电表的数据维持在一个水平,两个不同的控制方式
{
//Slave 从储能箱根据负载情况给管理大楼放电
//可以放电的话,则
if (SlaveClient.IsCanDisCharg())
{
SlaveClient.ServerCmd.CmdType = "DisCharg";
ManageDifLoad = ManageRealPw - ManageTargetPw;
if (ManageDifLoad < SlaveClient.ClientInfo!.MaxDisChargPw)
{
//储能发送功率 补全总功率数据
//DisChargPwCmd(AcrelMeters.Psum - EleMeterActionLimitValue.MidValue);
SlaveClient.ServerCmd.CmdPw = ManageDifLoad;
SlaveControlMsg = "【从储能】放电【管理大楼】:【从储能】放电补全目标差值,【从储能】正常放电";
}
else //储能不能满足总的差值
{
//满功率放电
//储能全功率放电,不足的部分电网自动补全
SlaveClient.ServerCmd.CmdPw = (SlaveClient.ClientInfo.MaxDisChargPw * ConfigDataService.energyStorageRunConfig.MaxBatDisChargRatio);
SlaveControlMsg = "【从储能】放电【管理大楼】:【从储能】全功率放电无法补全差值,【从储能】全功率放电";
}
}
else
{
//储能处于待机状态,防止其他的状态残留
SlaveClient.ServerCmd.CmdType = "DisCharg";//功率为0充放电的指令类型无所谓
SlaveClient.ServerCmd.CmdPw = 0;
SlaveControlMsg = "【从储能】放电【管理大楼】:【从储能】无法放电,【从储能】不动作";
}
}
else
{
//真实的负载功率比较小,防止储能放电导致逆流,就不需要输出动作了,等真实的负载大于一个值后才开始介入
SlaveClient.ServerCmd.CmdType = "DisCharg"; //功率为0充放电的指令类型无所谓
SlaveClient.ServerCmd.CmdPw = 0;
SlaveControlMsg = "【从储能】放电【管理大楼】:【管理大楼】负载较小,为防止放电导致逆流,【从储能】不动作";
}
///////////////主储能箱体充电 吸收光伏能量的控制
//注意方向
TaxRealPw = SolarEleMeter5.AvePw - SolarEleMeter3.AvePw;//光伏和市电供应给税务大楼的负载
TotalSolarPw = Math.Abs(SolarEleMeter2.AvePw + SolarEleMeter3.AvePw);
//调节储能吸收光伏能量来调节光伏到税务大楼的能量,根据真实负载的值来判断是否调控介入
if (TaxRealPw > TaxTargetPwBigSmallUp)
{
////////////////////调节使光伏尽可能满足税务大楼////////////////////
if (MasterClient.IsCanCharg())
{
//差值 这个差值是3号电表已经补充进去之后差值
TaxDifLoad = TaxRealPw - TaxTargetPw;
//*************** 储能和光伏综合调节达到这个差值 TaxDifLoad ***************
if (TaxDifLoad < TotalSolarPw)//光伏的现在能力能达到这个差值,就是可以调节
{
//差值
var difValue1 = TotalSolarPw - TaxDifLoad;
// 储能吸收这个差值剩余的TaxDifLoad 光伏值给税务大楼,以达到要求
MasterClient.ServerCmd.CmdPw = difValue1;
MasterClient.ServerCmd.CmdType = "Charg";
MasterControlMsg = "【主储能】充电吸收【光伏】电能:【主储能】充电满足差值,【主储能】正常充电";
}
else
{
//当前光伏的所有能量无法满足需要这个差值,那么全部光伏给税务大楼,储能不进行充电
MasterClient.ServerCmd.CmdPw = 0;
MasterClient.ServerCmd.CmdType = "Charg";
MasterControlMsg = "【主储能】充电吸收【光伏】电能:【主储能】不需要充电,把光伏都给税务大楼,【主储能】不动作";
}
}
else
{
MasterControlMsg = "【主储能】充电吸收【光伏】电能:【主储能】无法充电,【主储能】停止工作";
}
}
else
{
/////////////真实的负载小于目标值
//让主储能尽力充电吸走光伏的能力,为了防止逆流,
if (MasterClient.IsCanCharg())
{
if (TotalSolarPw < MasterClient.ClientInfo!.MaxChargPw)
{
//光伏发电能力小于储能充电的能力,储能把光伏吸收完全
MasterClient.ServerCmd.CmdType = "Charg";
MasterClient.ServerCmd.CmdPw = TotalSolarPw;
MasterControlMsg = "【主储能】充电吸收【光伏】电能:【主储能】完全吸收光伏电能,【主储能】正常充电";
}
else
{
//光伏发电能力大于储能充电能力,则储能尽可能充电吧,存在逆流可能了,储能尽力了 【存在逆流的可能性】
MasterClient.ServerCmd.CmdType = "Charg";
MasterClient.ServerCmd.CmdPw = (MasterClient.ClientInfo!.MaxChargPw * ConfigDataService.energyStorageRunConfig.MaxBatChargRatio);
MasterControlMsg = "【主储能】充电吸收【光伏】电能:税务大楼负载小,【主储能】最大功率充电吸收光伏电能,存在【逆流】可能性!!!!!!!!!!!!!!!!!!";
}
}
else
{
MasterControlMsg = "【主储能】充电吸收【光伏】电能:【主储能】无法充电,【主储能】停止工作";
}
}
}
//从储能箱接受电量来自于光伏 从储能箱的光伏充电功率,采用逐步的方法,主要是吸收光伏的电量,不可以吸收税务大楼的电量过来,怎么分配
break;
//晚上
case DayNightEnum.Night:
//进来这个模式时有用走出Night_Master模式时靠白天的那里的逻辑判断切换到白天时这里就不走到这里了
CurESChargInfo = ESChargInfo.Night_Master;
//SetControlModel(ESChargInfo.Night_Master);
//主储能箱充满为止
var masterNightTargetSoc = Math.Min(NightMaster_ToMasterFullSoc, ConfigDataService.energyStorageRunConfig.BMSSocUpSignLimitValue);
if (MasterClient.ClientInfo!.SOC < masterNightTargetSoc)
{
//主储能箱充电
MasterClient.ServerCmd.CmdType = "Charg";
MasterClient.ServerCmd.CmdPw = MasterClient.ClientInfo!.MaxDisChargPw * ConfigDataService.energyStorageRunConfig.MaxBatDisChargRatio;
}
else
{
//主储能箱充电
MasterClient.ServerCmd.CmdType = "Charg";
MasterClient.ServerCmd.CmdPw = 0;
}
//从储能箱不运行
SlaveClient.ServerCmd.CmdType = "Charg";
SlaveClient.ServerCmd.CmdPw = 0;
break;
default:
break;
}
}
catch (Exception ex)
{
LogService.Error(String.Format("ErrSource : {0} ErrMsg : {1}", ex.StackTrace.ToString(), ex.Message.ToString()));
}
}
});
}
/// <summary>
/// 逻辑扫描
/// 模式切换的模式更改的逻辑扫描
/// </summary>
private void LogicScan()
{
YuPuLogicScanTask = Task.Run(async () =>
{
await Task.Delay(5000);
//依据光伏发电为主要
while (ThreadEnable)
{
await Task.Delay(2000);
try
{
//实时把税务大楼的并网点的仪表数据给
SolarCurBackFlow.UpdateByCompareLess(SolarEleMeter5.RtPw);
if (MasterClient == null)//防止未加载导致错误
{
continue;
}
EsSoc1 = MasterClient.ClientInfo.SOC;
EsSoe1 = MasterClient.ClientInfo.SOE;
EsSoh1 = MasterClient.ClientInfo.SOH;
EsSoc2 = SlaveClient.ClientInfo.SOC;
EsSoe2 = SlaveClient.ClientInfo.SOE;
EsSoh2 = SlaveClient.ClientInfo.SOH;
if (!YuPuAutoHand)
{
continue;
}
//实时给当前的SOC数据给夜电白放的模型
//CurNightChargEleModel.MasterSoc = MasterClient.ClientInfo!.SOC;
//CurNightChargEleModel.SlaveSoc = SlaveClient.ClientInfo!.SOC;
///////////////////////////主从储能箱如何分配 依据光伏发电为主要,不浪费光伏发电为主线
///////////////////确认控制模式////////////////////////////////////////
//在白天的情况下,主储能箱和从储能储能箱切换的标准:以不浪费光伏为主
//依据当前的状态判断是否需要切换到其他状态
switch (CurESChargInfo)
{
case ESChargInfo.Master://当前是白天Master状态
///////////////////////////获取当前的是白天还是夜晚,依据时间判断,留有余量,有可能实际已经晚上了,但是负载全部没有了
if (IsBetweenTime(DateTime.Now.ToString(), SunriseTimeStr, SunsetTimeStr))
{
//根据日落和日出时间判断当前是白天
DayNight = DayNightEnum.Day;
}
else
{
//CurNightChargEleModel.SetDayEndState();
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-当前是【Master】-Master->Night_Master 白天Master 到晚上Night_Master的切换");
DayNight = DayNightEnum.Night;
PreESChargInfo = ESChargInfo.Night_Master;
SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig);
//var SolarCur = SolarEleMeter3.RtPw + SolarEleMeter2.RtPw;
//根据日落和日出时间判断当前是晚上
continue;
}
//?????? 如果昨晚充满了,今天的话,早上就要进入到无光伏的模式了,因为两个都满了 ,设置的数据不要太冲突
//从储能箱晚上充电的SOC NightSlave_ToSlaveFullSoc 90 > SolarToEsAsFullSoc 97
//
//能到这里说明是白天,也要判断光伏把储能是否充满,充满的话进入无光伏阶段 新策略 是否可以切换
if (SlaveClient.ClientInfo!.SOC >= SolarToEsAsFullSoc)
{
//光伏把从储能充满,此时需要判断主储能是否满,满的话就是都满了进入无光伏阶段
if (MasterClient.ClientInfo!.SOC >= Master_SolarToSlaveEsFullByMasterSoc)
{
//两个储能都满了,进行切换
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-当前是【Master】-光伏把【从储能】充满SOC:{SlaveClient.ClientInfo!.SOC},主储能也满了,充满的话进入无光伏阶段 新策略");
PreESChargInfo = ESChargInfo.NoSolar;
SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig);
continue;
}
else
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-当前是【Master】-从储能箱体被光伏充满SOC:{SlaveClient.ClientInfo!.SOC},主储能箱体还有余量:{MasterClient.ClientInfo!.SOC}切换到Slave让主储能箱接受光伏电量");
//主储能箱体放完,从储能箱体还有电,则可以切换
//先进入等待状态,为切换准备条件
PreESChargInfo = ESChargInfo.Slave;
SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig);
continue;
}
}
//主储能箱体放完为止策略 新策略
if (MasterClient.ClientInfo!.SOC <= Master_ToSlaveByMasterSoc)
{
//此时切换到从储能箱体需要查看从储能箱体的SOC
if (SlaveClient.ClientInfo!.SOC >= Master_ToSlaveBySlaveSoc)
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-当前是【Master】-主储能箱体放完SOC:{MasterClient.ClientInfo!.SOC}从储能箱体还有电SOC:{SlaveClient.ClientInfo!.SOC}切换到Slave");
//主储能箱体放完,从储能箱体还有电,则可以切换
//先进入等待状态,为切换准备条件
PreESChargInfo = ESChargInfo.Slave;
SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig);
continue;
}
else
{
LogicMsg = "主储能箱电放完,从储能箱还有电,暂不切换,维持当前状态";
//主储能箱体放完,从储能箱体也没有电,维持当前状态
}
}
else
{
//主储能箱维持当前的状态,
LogicMsg = "主储能箱电没有放完,维持当前状态";
}
////原有的Master逻辑判断
////判断从储能箱是否被光伏充满,以不浪费光伏为主
//if (SlaveClient.ClientInfo!.SOC >= 95)
//{
// //从储能箱充满,那么具备切换的条件,还需要判断一下,主储能箱体是否被消耗掉
// if (MasterClient.ClientInfo!.SOC <= 80)
// {
// //从储能箱充满,主储能箱未满,则可以切换
// //先进入等待状态,为切换准备条件
// PreESChargInfo = ESChargInfo.Slave;
// SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig);
// }
// else
// {
// //从储能箱充满,主储能箱并没有完全释放完毕,即是两个储能箱体都满了,
// //从储能箱停止光伏充电,直到主储能箱释放能量后再切换
// //两个储能箱都充满了,无论出于什么原因,都不应该让光伏发电了
// //1可能储能报错无法放电2可能节假日负载很小
// PreESChargInfo = ESChargInfo.NoSolar;
// SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig);
// }
//}
//else
//{
// //以不浪费光伏为主
// //从储能箱光伏没有充满维持当前的状态可能主储能箱在放电中或者放电到SOC低后停止放电
//}
break;
case ESChargInfo.Slave:
///////////////////////////获取当前的是白天还是夜晚,依据时间判断,留有余量,有可能实际已经晚上了,但是负载全部没有了
if (IsBetweenTime(DateTime.Now.ToString(), SunriseTimeStr, SunsetTimeStr))
{
//根据日落和日出时间判断当前是白天
DayNight = DayNightEnum.Day;
}
else
{
//CurNightChargEleModel.SetDayEndState();
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-当前是【Slave】-Slave->Night_Master 白天Slave 到晚上Night_Master的切换");
DayNight = DayNightEnum.Night;
PreESChargInfo = ESChargInfo.Night_Master;
SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig);
//var SolarCur = SolarEleMeter3.RtPw + SolarEleMeter2.RtPw;
//根据日落和日出时间判断当前是晚上
continue;
}
//判断光伏把主储能是否充满,充满的话进入无光伏阶段 新策略
if (MasterClient.ClientInfo!.SOC >= SolarToEsAsFullSoc)
{
//判断从储能箱放电程度
if (SlaveClient.ClientInfo!.SOC >= Slave_SolarToMasterEsFullBySlaverSoc)
{
//主从储能箱都充满了,无论出于什么原因,都不应该让光伏发电了,进入无光伏阶段
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-当前是【Slave】-光伏把主储能充满SOC:{MasterClient.ClientInfo!.SOC},充满的话进入无光伏阶段 新策略");
PreESChargInfo = ESChargInfo.NoSolar;
SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig);
continue;
}
else
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-当前是【Slave】-主储能箱体被光伏充满SOC:{MasterClient.ClientInfo!.SOC},从储能箱体还有余量:{SlaveClient.ClientInfo!.SOC}切换到Master让从储能箱接受光伏电量");
//主储能箱体放完,从储能箱体还有电,则可以切换
//先进入等待状态,为切换准备条件
PreESChargInfo = ESChargInfo.Master;
SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig);
continue;
}
}
//从储能箱体放完为止策略 新策略
if (SlaveClient.ClientInfo!.SOC <= Slave_ToMasterBySlaveSoc)
{
//此时切换到从储能箱体需要查看从储能箱体的SOC
if (MasterClient.ClientInfo!.SOC >= Slave_ToMasterByMasterSoc)
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-当前是【Slave】-从储能箱体放完SOC:{SlaveClient.ClientInfo!.SOC}主储能箱体还有电SOC:{MasterClient.ClientInfo!.SOC}切换到Master");
//主储能箱体放完,从储能箱体还有电,则可以切换
//先进入等待状态,为切换准备条件
PreESChargInfo = ESChargInfo.Master;
SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig);
continue;
}
else
{
//主储能箱体放完,从储能箱体也没有电,维持当前状态
LogicMsg = "从储能箱电放完,主储能箱还有电,暂不切换,维持当前状态";
}
}
else
{
//主储能箱维持当前的状态
LogicMsg = "从储能箱电没有放完,维持当前状态";
}
////原有的Slave逻辑判断
////判断主储能箱是否被光伏充满,以不浪费光伏为主
//if (MasterClient.ClientInfo!.SOC >= 95)
//{
// //主储能箱充满,那么具备切换的条件,还需要判断一下,从储能箱体是否被消耗掉
// if (SlaveClient.ClientInfo!.SOC <= 80)
// {
// //主储能箱充满,从储能箱未满,则可以切换
// PreESChargInfo = ESChargInfo.Master;
// SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig);
// }
// else
// {
// //主储能箱充满,从储能箱并没有完全释放完毕,即是两个储能箱体都满了,
// //主储能箱停止光伏充电,直到从储能箱释放能量后再切换
// // 那么此时如果光伏发电很大税务大楼负载很小触发光伏并网柜的逆流保护切换光伏并网柜的市电光伏停止输出主储能箱PCS无市电会报错交流欠压
// //两个储能箱都充满了,无论出于什么原因,都不应该让光伏发电了
// //1可能储能报错无法放电2可能节假日负载很小
// PreESChargInfo = ESChargInfo.NoSolar;
// SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig);
// }
//}
//else
//{
// //主储能箱光伏没有充满,维持当前的状态
//}
break;
//为什么白天有Night_Master的判断因为从晚上切换到白天时需要由晚上作为旧的基础模型然后切换到Master或者Slaver
//先判断白天晚上然后再具体操作模式从晚上到白天后具体模式还是晚上需要从晚上的模式到白天的Master或者Slaver模式
case ESChargInfo.Night_Master:
///////////////////////////获取当前的是白天还是夜晚,依据时间判断,留有余量,有可能实际已经晚上了,但是负载全部没有了
if (IsBetweenTime(DateTime.Now.ToString(), SunriseTimeStr, SunsetTimeStr))
{
//根据日落和日出时间判断当前是白天
DayNight = DayNightEnum.Day;
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-当前是【Night_Master】-Night_Master->Master 晚上Night_Master切换到白天的Master");
//晚上转到白天,标记每天初始没有完成
//CurNightChargEleModel.MasterCheck = DischargInfo.NoComplete;
//CurNightChargEleModel.SlaveCheck = DischargInfo.NoComplete;
//CurNightChargEleModel.SetDayInitState(MasterClient.ClientInfo!.SOC, SlaveClient.ClientInfo!.SOC);
//晚上切换到白天的话直接到Master模式
PreESChargInfo = ESChargInfo.Master;
SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig);
continue;
}
else
{
//可能转到Night_Slave模式本身充满Night没有充满
if (MasterClient.ClientInfo!.SOC >= NightMaster_ToMasterFullSoc && SlaveClient.ClientInfo!.SOC <= (NightSlave_ToSlaveFullSoc - 10))
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-当前是【Night_Master】-主储能箱体充满SOC:{MasterClient.ClientInfo!.SOC}从储能箱体SOC:{SlaveClient.ClientInfo!.SOC}切换到Night_Slave");
//切换到Night_Slave模式
PreESChargInfo = ESChargInfo.Night_Slave;
SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig);
continue;
}
else
{
//继续维持当前的状态
MasterControlMsg = "【主储能】夜晚充电";
SlaveControlMsg = "【从储能】夜晚待机";
LogicMsg = "【主储能】夜晚充电,【从储能】夜晚待机";
}
}
break;
case ESChargInfo.Night_Slave:
///////////////////////////获取当前的是白天还是夜晚,依据时间判断,留有余量,有可能实际已经晚上了,但是负载全部没有了
if (IsBetweenTime(DateTime.Now.ToString(), SunriseTimeStr, SunsetTimeStr))
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-当前是【Night_Slave】-Night_Slave->Master 晚上Night_Slave切换到白天的Master");
//根据日落和日出时间判断当前是白天
DayNight = DayNightEnum.Day;
//晚上转到白天,标记每天初始没有完成
//CurNightChargEleModel.MasterCheck = DischargInfo.NoComplete;
//CurNightChargEleModel.SlaveCheck = DischargInfo.NoComplete;
//CurNightChargEleModel.SetDayInitState(MasterClient.ClientInfo!.SOC, SlaveClient.ClientInfo!.SOC);
//晚上切换到白天的话直接到Master模式
PreESChargInfo = ESChargInfo.Master;
SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig);
}
else
{
//可能转到Night_Master模式本身充到SOC=60算充满
if (SlaveClient.ClientInfo!.SOC >= NightSlave_ToSlaveFullSoc && MasterClient.ClientInfo!.SOC <= (NightMaster_ToMasterFullSoc - 10))
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-当前是【Night_Slave】-从储能箱体充满SOC:{SlaveClient.ClientInfo!.SOC}主储能箱体SOC:{MasterClient.ClientInfo!.SOC}切换到Night_Master");
//切换到Night_Slave模式
PreESChargInfo = ESChargInfo.Night_Master;
SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig);
}
else
{
//继续维持当前的状态
MasterControlMsg = "【主储能】夜晚待机";
SlaveControlMsg = "【从储能】夜晚充电";
LogicMsg = "【主储能】夜晚待机,【从储能】夜晚充电";
}
}
break;
case ESChargInfo.Wait:
LogicMsg = "模式切换中";
//切换中
break;
case ESChargInfo.NoSolar:
//无光伏参与
//其中一个储能柜没有满就可以投入到Master/Slave中间
if (IsBetweenTime(DateTime.Now.ToString(), SunriseTimeStr, SunsetTimeStr))
{
//根据日落和日出时间判断当前是白天
DayNight = DayNightEnum.Day;
}
else
{
//CurNightChargEleModel.SetDayEndState();
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-当前是【NoSolar】-NoSolar->Night_Master NoSolar切换晚上Night_Master");
DayNight = DayNightEnum.Night;
PreESChargInfo = ESChargInfo.Night_Master;
SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig);
//var SolarCur = SolarEleMeter3.RtPw + SolarEleMeter2.RtPw;
//根据日落和日出时间判断当前是晚上
continue;
}
//用Soc小的吸收光伏能量
if (SlaveClient.ClientInfo!.SOC <= (SolarToEsAsFullSoc - 10))
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-当前是【NoSolar】-从储能箱体SOC:{SlaveClient.ClientInfo!.SOC}主储能箱体SOC:{MasterClient.ClientInfo!.SOC}切换到Master");
//主给管理大楼放电,从吸收光伏
PreESChargInfo = ESChargInfo.Master;
SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig);
continue;
}
if (MasterClient.ClientInfo!.SOC <= (SolarToEsAsFullSoc - 10))
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-当前是【NoSolar】-从储能箱体SOC:{SlaveClient.ClientInfo!.SOC}主储能箱体SOC:{MasterClient.ClientInfo!.SOC}切换到Slave");
//从给管理大楼放电,主吸收光伏
PreESChargInfo = ESChargInfo.Slave;
SysRunStateMachine.Fire(ESChargInfoTrig.WaitTrig);
continue;
}
break;
default:
break;
}
}
catch (Exception ex)
{
LogService.Error(String.Format("ErrSource : {0} ErrMsg : {1}", ex.StackTrace.ToString(), ex.Message.ToString()));
}
}
});
}
/// <summary>
/// 设置控制模式
/// 电操操作之后 要进行储能的状态检查,因为极短时间内储能是断电的
/// 12 34不能同时合闸互锁的功能切换开关时先断然后合闸中间有时间差
///因为有互锁的功能,在一组里面所以要先动作要关闭的电操,然后再操作打开的电操
/// </summary>
public void SetControlModel(ESChargInfo value)
{
switch (value)
{
case ESChargInfo.Master:
SetSwitchClose();
//主储能给管理大楼供电
QFSwitch3.SetRtSwitch(SwitchEm.Off);
QFSwitch4.SetRtSwitch(SwitchEm.On);
QFSwitch2.SetRtSwitch(SwitchEm.Off);
QFSwitch1.SetRtSwitch(SwitchEm.On);
break;
case ESChargInfo.Slave:
SetSwitchClose();
//从储能给管理大楼供电
QFSwitch4.SetRtSwitch(SwitchEm.Off);
QFSwitch3.SetRtSwitch(SwitchEm.On);
QFSwitch1.SetRtSwitch(SwitchEm.Off);
QFSwitch2.SetRtSwitch(SwitchEm.On);
break;
case ESChargInfo.Night_Master:
SetSwitchClose();
//晚上,给主储能箱供电
QFSwitch3.SetRtSwitch(SwitchEm.Off);
QFSwitch2.SetRtSwitch(SwitchEm.Off);
QFSwitch1.SetRtSwitch(SwitchEm.On);
QFSwitch4.SetRtSwitch(SwitchEm.On);
break;
default:
break;
}
}
/// <summary>
/// 设置模式
/// 无先关后设置的命令
/// </summary>
/// <param name="value"></param>
public void SetControlModelNoBeforClose(ESChargInfo value)
{
switch (value)
{
case ESChargInfo.Master:
//主储能给管理大楼供电
QFSwitch3.SetRtSwitch(SwitchEm.Off);
QFSwitch4.SetRtSwitch(SwitchEm.On);
QFSwitch2.SetRtSwitch(SwitchEm.Off);
QFSwitch1.SetRtSwitch(SwitchEm.On);
break;
case ESChargInfo.Slave:
//从储能给管理大楼供电
QFSwitch4.SetRtSwitch(SwitchEm.Off);
QFSwitch3.SetRtSwitch(SwitchEm.On);
QFSwitch1.SetRtSwitch(SwitchEm.Off);
QFSwitch2.SetRtSwitch(SwitchEm.On);
break;
case ESChargInfo.Night_Master:
//晚上,给主储能箱供电
QFSwitch3.SetRtSwitch(SwitchEm.Off);
QFSwitch2.SetRtSwitch(SwitchEm.Off);
QFSwitch1.SetRtSwitch(SwitchEm.On);
QFSwitch4.SetRtSwitch(SwitchEm.On);
break;
case ESChargInfo.Night_Slave:
//晚上,给从储能箱供电
QFSwitch4.SetRtSwitch(SwitchEm.Off);
QFSwitch3.SetRtSwitch(SwitchEm.On);
QFSwitch1.SetRtSwitch(SwitchEm.Off);
QFSwitch2.SetRtSwitch(SwitchEm.On);
break;
case ESChargInfo.NoSolar:
//主储能给管理大楼供电
QFSwitch3.SetRtSwitch(SwitchEm.Off);
QFSwitch4.SetRtSwitch(SwitchEm.On);
QFSwitch2.SetRtSwitch(SwitchEm.Off);
QFSwitch1.SetRtSwitch(SwitchEm.On);
break;
default:
break;
}
}
/// <summary>
/// 关闭所有的开关
/// </summary>
/// <returns></returns>
public bool SetSwitchClose()
{
QFSwitch1.SetRtSwitch(SwitchEm.Off);
QFSwitch2.SetRtSwitch(SwitchEm.Off);
QFSwitch3.SetRtSwitch(SwitchEm.Off);
QFSwitch4.SetRtSwitch(SwitchEm.Off);
Thread.Sleep(2000);
if (QFSwitch1.CurSwtichState == SwitchEm.Off &&
QFSwitch2.CurSwtichState == SwitchEm.Off &&
QFSwitch3.CurSwtichState == SwitchEm.Off &&
QFSwitch4.CurSwtichState == SwitchEm.Off
)
{
return true;
}
else
{
return false;
}
}
/// <summary>
/// 只关闭所有的电操
/// </summary>
public void OnlyCloseSwitch()
{
QFSwitch1.SetRtSwitch(SwitchEm.Off);
QFSwitch2.SetRtSwitch(SwitchEm.Off);
QFSwitch3.SetRtSwitch(SwitchEm.Off);
QFSwitch4.SetRtSwitch(SwitchEm.Off);
}
/// <summary>
/// 检查电操的状态
/// </summary>
/// <returns></returns>
public bool CheckSwitchState(ESChargInfo ESChargInfoData)
{
switch (ESChargInfoData)
{
case ESChargInfo.Inital:
return true;
case ESChargInfo.Master:
// 1 4ON / 2 3OFF
if (QFSwitch1.CurSwtichState == SwitchEm.On &&
QFSwitch2.CurSwtichState == SwitchEm.Off &&
QFSwitch3.CurSwtichState == SwitchEm.Off &&
QFSwitch4.CurSwtichState == SwitchEm.On
)
{
return true;
}
else
{
return false;
}
case ESChargInfo.Slave:
// 1 4Off / 2 3ON
if (QFSwitch1.CurSwtichState == SwitchEm.Off &&
QFSwitch2.CurSwtichState == SwitchEm.On &&
QFSwitch3.CurSwtichState == SwitchEm.On &&
QFSwitch4.CurSwtichState == SwitchEm.Off
)
{
return true;
}
else
{
return false;
}
case ESChargInfo.Night_Master:
// 1 4ON / 2 3OFF
if (QFSwitch1.CurSwtichState == SwitchEm.On &&
QFSwitch2.CurSwtichState == SwitchEm.Off &&
QFSwitch3.CurSwtichState == SwitchEm.Off &&
QFSwitch4.CurSwtichState == SwitchEm.On
)
{
return true;
}
else
{
return false;
}
case ESChargInfo.Night_Slave:
// 1 4Off / 2 3ON
if (QFSwitch1.CurSwtichState == SwitchEm.Off &&
QFSwitch2.CurSwtichState == SwitchEm.On &&
QFSwitch3.CurSwtichState == SwitchEm.On &&
QFSwitch4.CurSwtichState == SwitchEm.Off
)
{
return true;
}
else
{
return false;
}
case ESChargInfo.Wait:
return true;
case ESChargInfo.NoSolar:
// 1 4ON / 2 3OFF
if (QFSwitch1.CurSwtichState == SwitchEm.On &&
QFSwitch2.CurSwtichState == SwitchEm.Off &&
QFSwitch3.CurSwtichState == SwitchEm.Off &&
QFSwitch4.CurSwtichState == SwitchEm.On
)
{
return true;
}
else
{
return false;
}
default:
return true;
}
}
/// <summary>
/// 检查PCS的状态
/// </summary>
/// <returns></returns>
public bool CheckPCSState()
{
return true;
}
/// <summary>
/// 是周五还是周六
/// 在晚上判断是周五晚上还是周六晚上,腾出储能给光伏充电用
/// </summary>
/// <returns></returns>
private bool IsSpecialWeekDay()
{
return (DateTime.Now.DayOfWeek == DayOfWeek.Friday && IsPm()) || (DateTime.Now.DayOfWeek == DayOfWeek.Saturday && IsPm());
}
/// <summary>
/// 是周五还是周六
/// 在晚上判断是周五晚上还是周六晚上,腾出储能给光伏充电用
/// </summary>
/// <returns></returns>
public bool IsInWeekendTime()
{
DateTime now = DateTime.Now;
DayOfWeek currentDay = now.DayOfWeek;
TimeSpan currentTime = now.TimeOfDay;
// 周五情况:时间 >= 23:00
if (currentDay == DayOfWeek.Friday && currentTime.Hours >= 23)
{
return true;
}
// 周六情况:全天有效
else if (currentDay == DayOfWeek.Saturday)
{
return true;
}
// 周日情况:时间 < 07:00
else if (currentDay == DayOfWeek.Sunday && currentTime.Hours < 7)
{
return true;
}
return false;
}
/// <summary>
/// 是否时下午
/// </summary>
/// <returns></returns>
private bool IsPm()
{
return DateTime.Now.Hour >= 23;
}
/// <summary>
/// 读取的字节数据集合
/// 1号电表
/// </summary>
public OperateResult<byte[]> OperateResultBytes1 { get; set; }
/// <summary>
/// 读取的字节数据集合
/// 2号电表
/// </summary>
public OperateResult<byte[]> OperateResultBytes2 { get; set; }
/// <summary>
/// 读取的字节数据集合
/// 3号电表
/// </summary>
public OperateResult<byte[]> OperateResultBytes3 { get; set; }
/// <summary>
/// 读取的字节数据集合
/// 4号电表
/// </summary>
public OperateResult<byte[]> OperateResultBytes4 { get; set; }
/// <summary>
/// 读取的字节数据集合
/// 5号电表
/// </summary>
public OperateResult<byte[]> OperateResultBytes5 { get; set; }
/// <summary>
/// 读取的字节数据集合
/// </summary>
public OperateResult<bool[]> ReIOOperateResultBytes1 { get; set; }
/// <summary>
/// 读取的字节数据集合
/// </summary>
public OperateResult<byte[]> SolarOperateResultBytes1 { get; set; }
/// <summary>
/// 读取的字节数据集合
/// </summary>
public OperateResult<byte[]> SolarOperateResultBytes2 { get; set; }
/// <summary>
/// 读取的字节数据集合
/// </summary>
public OperateResult<byte[]> SolarOperateResultBytes3 { get; set; }
/// <summary>
/// 读取的字节数据集合
/// </summary>
public OperateResult<byte[]> SolarOperateResultBytes4 { get; set; }
/// <summary>
/// ScanDevice扫描Task
/// </summary>
static Task ScanDeviceTask { get; set; }
/// <summary>
/// ScanDevice扫描Task
/// </summary>
static Task ReIOScanDeviceTask { get; set; }
/// <summary>
/// YuPuLogicScanTask扫描Task
/// </summary>
static Task YuPuLogicScanTask { get; set; }
/// <summary>
/// YuPuSolarTask扫描Task
/// 光伏通信的任务
/// </summary>
static Task YuPuSolarTask { get; set; }
/// <summary>
/// Meter 电表通信连接状态
/// </summary>
public bool MeterLinkState { get; set; } = true;
/// <summary>
/// ReIO通信连接状态
/// </summary>
public bool ReIOLinkState { get; set; } = true;
/// <summary>
/// Solar 电表通信连接状态
/// 这个状态很重要,很多状态判断使用
/// </summary>
public bool SolarLinkState { get; set; } = true;
/// <summary>
/// 扫描线程使能
/// </summary>
public bool ThreadEnable { get; set; } = true;
/// <summary>
/// 仪表数据驱动
/// </summary>
public ModbusRtu MeterMdDrive { set; get; }
/// <summary>
/// 光伏数据驱动
/// </summary>
public ModbusRtu SolarMdDrive { set; get; }
/// <summary>
/// 远程IO ModbusTcp驱动
/// </summary>
private ModbusTcpNet ModbusTcpNetDrive { get; set; } = new ModbusTcpNet();
/// <summary>
/// 日出
/// </summary>
private string SunriseTimeStr { get; set; } = "5:00";
/// <summary>
/// 日落
/// </summary>
private string SunsetTimeStr { get; set; } = "23:00";
//public DayNightEnum DayNight { get; set; }
private DayNightEnum _DayNight;
/// <summary>
/// 白天和晚上
/// Day
/// Night
/// </summary>
public DayNightEnum DayNight
{
get { return _DayNight; }
set
{
if (_DayNight != value)
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【动作】-【DayNight】-【{_DayNight.ToString()}】->【{value.ToString()}】 白天和晚上的切换");
_DayNight = value;
}
}
}
/// <summary>
/// 开关1
/// </summary>
public SwitchModel QFSwitch1 { get; set; }
/// <summary>
/// 开关2
/// </summary>
public SwitchModel QFSwitch2 { get; set; }
/// <summary>
/// 开关3
/// </summary>
public SwitchModel QFSwitch3 { get; set; }
/// <summary>
/// 开关4
/// </summary>
public SwitchModel QFSwitch4 { get; set; }
/// <summary>
/// 光伏并网柜 5号
/// 税务大楼的负载
/// 注意方向
/// </summary>
public EleMeter SolarEleMeter5 { get; set; } = new EleMeter();
/// <summary>
/// 光伏并网柜 2号
/// 并网到储能并网柜
/// 注意方向
/// </summary>
public EleMeter SolarEleMeter2 { get; set; } = new EleMeter();
/// <summary>
/// 光伏并网柜 3号
/// 输出到税务大楼
/// 注意方向
/// </summary>
public EleMeter SolarEleMeter3 { get; set; } = new EleMeter();
/// <summary>
/// 储能并网柜 1号
/// 输出到管理大楼
/// 注意方向
/// </summary>
public EleMeter EsEleMeter1 { get; set; } = new EleMeter();
/// <summary>
/// 储能并网柜 4号
/// 管理大楼的市电
/// 无论电量来自于主储能柜还是来自于从储能柜,都是输出或者输入到管理大楼的的能量
/// 注意方向
/// </summary>
public EleMeter EsEleMeter4 { get; set; } = new EleMeter();
///// <summary>
///// 光伏并网柜 5号
///// 税务大楼的市电
///// 注意方向
///// </summary>
//public EleMeter EsEleMeter5 { get; set; } = new EleMeter();
/// <summary>
/// 日志服务
/// </summary>
public ILogService LogService { get; }
/// <summary>
/// 配置服务信息
/// </summary>
public ConfigDataService ConfigDataService { get; }
public BmsDataService BmsDataService { get; }
public FFService FFService { get; }
public InPowerPCSDataService InPowerPCSDataService { get; }
/// <summary>
/// 判断时间是否在预定的时间区间内
/// </summary>
/// <param name="timeStr"></param>
/// <param name="startTime"></param>
/// <param name="endTime"></param>
/// <returns></returns>
public static bool IsBetweenTime(string timeStr, string startTime, string endTime)
{
//判断当前时间是否在工作时间段内
try
{
TimeSpan startSpan = DateTime.Parse(startTime).TimeOfDay;
TimeSpan endSpan = DateTime.Parse(endTime).TimeOfDay;
//string time1 = "2017-2-17 8:10:00";
DateTime t1 = Convert.ToDateTime(timeStr);
TimeSpan dspNow = t1.TimeOfDay;
if (dspNow > startSpan && dspNow < endSpan)
{
return true;
}
return false;
}
catch (Exception e)
{
return false;
}
}
}
/// <summary>
/// 白天还是晚上
/// </summary>
public enum DayNightEnum
{
Day = 1,
Night = 2
}
public enum ESChargInfo
{
/// <summary>
/// 初始状态
/// </summary>
Inital = 111,
/// <summary>
/// 主储能箱体放电给管理大楼
/// 从储能接受光伏的充电
/// </summary>
Master = 1,
/// <summary>
/// 从储能箱体放电给管理大楼
/// 主储能接受光伏的充电
/// </summary>
Slave = 2,
/// <summary>
/// 晚上
/// 主储能箱体从管理大楼充电
/// </summary>
Night_Master = 3,
/// <summary>
/// 晚上
/// 从储能箱体从管理大楼充电
/// </summary>
Night_Slave = 31,
/// <summary>
/// 等待
/// 各个状态切换时中间的等待状态
/// </summary>
Wait = 4,
/// <summary>
/// 无光伏参与
/// 负载很小,光伏比较大,并且两个储能柜体已经充满时停止光伏的参与发电,让储能给负载放电
/// </summary>
NoSolar = 5,
}
public enum ESChargInfoTrig
{
/// <summary>
/// 初始状态
/// </summary>
InitalTrig = 111,
/// <summary>
/// 主储能箱体放电给管理大楼 触发
/// 从储能接受光伏的充电
/// </summary>
MasterTrig = 1,
/// <summary>
/// 从储能箱体放电给管理大楼 触发
/// 主储能接受光伏的充电
/// </summary>
SlaveTrig = 2,
/// <summary>
/// 晚上 触发
/// 主储能箱体从管理大楼充电
/// </summary>
Night_MasterTrig = 3,
/// <summary>
/// 晚上 触发
/// 从储能箱体从管理大楼充电
/// </summary>
Night_SlaveTrig = 31,
/// <summary>
/// 等待
/// 各个状态切换时中间的等待状态
/// </summary>
WaitTrig = 4,
/// <summary>
/// 无光伏参与
/// 负载很小,光伏比较大,并且两个储能柜体已经充满时停止光伏的参与发电,让储能给负载放电
/// </summary>
NoSolarTrig = 5,
}
/// <summary>
/// 手自动
/// </summary>
public enum YuPuAutoHand
{
Hand = 0,
Auto = 1
}
}