Files
YuPu-OrpaonEMS/OrpaonEMS.App/Services/EMSService.cs
Tyrone CT 325b24c99f 更改了周五和周六晚上不充电
关闭后进程不关闭的操作
Mqtt的发布,关闭这个功能
2025-03-01 00:19:51 +08:00

1304 lines
52 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 HslCommunication;
using HslCommunication.WebSocket;
using OrpaonEMS.Core.Model;
using OrpaonEMS.Model.MasterSlave;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Text.Json;
using OrpaonEMS.Core.Enums;
using Prism.Mvvm;
using OrpaonEMS.Model.Enums;
using OrpaonEMS.App.PrismEvent;
using Prism.Events;
using OrpaonEMS.App.Models;
namespace OrpaonEMS.App.Services
{
/// <summary>
/// EMS控制
/// 多储能柜的主和从的通信控制
/// </summary>
public class EMSService : BindableBase
{
/// <summary>
/// NLog服务集合
/// </summary>
public ILogService LogService { get; }
public InPowerPCSDataService InPowerPCSDataService { get; }
public BmsDataService BmsDataService { get; }
/// <summary>
/// 系统的配置服务实例
/// </summary>
public ConfigDataService ConfigDataService { get; }
public AcrelMeterDataService AcrelMeterDataService { get; }
public EnergyStorageService EnergyStorageService { get; }
/// <summary>
/// 事件聚合器
/// </summary>
private readonly IEventAggregator _eventAggregator;
/// <summary>
/// 是否是标准的项目
/// </summary>
public bool IsStandardProject { get; set; } = false;
/// <summary>
/// 实例化函数
/// </summary>
/// <param name="logService"></param>
/// <param name="inPowerPCSDataService"></param>
/// <param name="bmsDataService"></param>
/// <param name="configDataService"></param>
/// <param name="acrelMeterDataService"></param>
/// <param name="eventAggregator"></param>
/// <param name="energyStorageService"></param>
public EMSService(ILogService logService,
InPowerPCSDataService inPowerPCSDataService,
BmsDataService bmsDataService,
ConfigDataService configDataService,
AcrelMeterDataService acrelMeterDataService,
IEventAggregator eventAggregator,
YuePuRunModelService yuePuRunModelService,
EnergyStorageService energyStorageService)
{
ListDistClient = new List<DistClient>()
{
DistClient0,
DistClient1
};
_eventAggregator = eventAggregator;
_eventAggregator.GetEvent<PeakValleyTimeEvent>().Subscribe(OnPeakValleyTimeEventMsgReceived);
//yuePuRunModel 赋值实例
yuePuRunModel = yuePuRunModelService;
yuePuRunModel.MasterClient = ListDistClient[0];
yuePuRunModel.SlaveClient = ListDistClient[1];
//获取Bms服务实例
//BmsDataService = bmsDataService;
//Log服务实例赋值
LogService = logService;
InPowerPCSDataService = inPowerPCSDataService;
BmsDataService = bmsDataService;
//获取英博PCS服务实例
//InPowerPCSDataService = inPowerPCSDataService;
//获取配置服务实例
ConfigDataService = configDataService;
AcrelMeterDataService = acrelMeterDataService;
EnergyStorageService = energyStorageService;
//是分布式的话,需要通信控制,否则没有任何意义
if (ConfigDataService.IsMaster)
{
//启动服务
WebSocketServerInit();
//EMS逻辑判断信息
EMSScanStart();
}
}
#region EMS调度方法
/// <summary>
/// 月浦园区运行模型
/// </summary>
public YuePuRunModelService yuePuRunModel { get; set; }
/// <summary>
/// 是否是月浦的项目
/// </summary>
public bool IsYuePuProject { get; set; } = true;
#endregion
/// <summary>
/// 指令集合数据
/// </summary>
//public List<ServerCmd> ListServerCmds { get; set; }=new List<ServerCmd>();
/// <summary>
/// EMS扫描线程执行方法
/// </summary>
private void EMSScanStart()
{
EMSScanTask = Task.Run(async () =>
{
await Task.Delay(5000);
while (ThreadEnable)
{
await Task.Delay(1000);
//Control入口
try
{
//当前主要依据是削峰填谷的哪个时期作为入口
//考虑一充一放
//没有报警的话则进入逻辑判断
if (IsStandardProject)
{
switch (PeakValleySglModel)
{
case ElePVEnum.TopPeak:
PeakValleyByPeakDEMO();
break;
case ElePVEnum.Peak://峰的状态,尽可能的放电
PeakValleyByPeakDEMO();
break;
case ElePVEnum.Valley://谷的状态,只进行充电操作
PeakValleyByValleyDEMO();
break;
case ElePVEnum.Flat://平的阶段 不进行动作使PCS待机状态【储能还是削峰填谷的状态】
PeakValleyByFlatDEMO();
break;
default:
break;
}
}
else
{
if (IsYuePuProject)//月浦项目的处理逻辑
{
}
}
//ListServerCmds.Clear();
//foreach (var item in ListDistClient)
//{
// //if (!item.IsSelf)//本机不需要发送
// //{
// // //
// ListServerCmds.Add(item.ServerCmd);
// //}
//}
ListDistClient.Find(a => a.ServerCmd.Station == 0)!.EMSSocketServerConState.ServerSendState = true;
ListDistClient.Find(a => a.ServerCmd.Station == 1)!.EMSSocketServerConState.ServerSendState = true;
//发布消息
wsServer.PublishAllClientPayload(JsonSerializer.Serialize<List<ServerCmd>>(ListDistClient.Select(a => a.ServerCmd).ToList()));
}
catch (Exception ex)
{
LogService.Error(String.Format("ErrSource : {0} ErrMsg : {1}", ex.StackTrace.ToString(), ex.Message.ToString()));
}
}
});
}
/// <summary>
/// EMSScan扫描Task
/// </summary>
static Task EMSScanTask { get; set; }
/// <summary>
/// 是否为主通信节点
/// </summary>
public bool IsMaster { get; set; } = true;
///// <summary>
///// 是否分布式的配置
///// </summary>
//public bool IsDistributed { get; set; }
#region
private string _PeakValleyRunLogicMsg;
/// <summary>
/// 削峰填谷运行逻辑信息
/// </summary>
public string PeakValleyRunLogicMsg
{
get { return _PeakValleyRunLogicMsg; }
set { _PeakValleyRunLogicMsg = value; RaisePropertyChanged(); }
}
private string _PeakValleyStateMsg;
/// <summary>
/// 削峰填谷的状态消息
/// </summary>
public string PeakValleyStateMsg
{
get { return _PeakValleyStateMsg; }
set { _PeakValleyStateMsg = value; }
}
private string _EsSysPeakVellayStateMsg = "平价";
/// <summary>
/// 再进入到峰谷模式后才能进入
/// 削峰填谷状态
/// </summary>
public string EsSysPeakVellayStateMsg
{
get { return _EsSysPeakVellayStateMsg; }
set { _EsSysPeakVellayStateMsg = value; RaisePropertyChanged(); }
}
/// <summary>
/// 削峰填谷的时间信息
/// </summary>
/// <param name="enum"></param>
/// <exception cref="NotImplementedException"></exception>
private void OnPeakValleyTimeEventMsgReceived(ElePVEnum data)
{
PeakValleySglModel = data;
}
private ElePVEnum _PeakValleySglModel;
/// <summary>
/// 削峰填谷的信号模型
/// 由其他的线程轮训赋值监控
/// </summary>
public ElePVEnum PeakValleySglModel
{
get { return _PeakValleySglModel; }
set
{
if (_PeakValleySglModel != value)//监听线程不停的赋值,变化时触发执行
{
_PeakValleySglModel = value;//削峰填谷不使用状态机
switch (_PeakValleySglModel)
{
case ElePVEnum.TopPeak:
EsSysPeakVellayStateMsg = "顶峰";
PeakValleyStateMsg = "顶峰价状态";
//StateMachinePeakValley.FireAsync(ElePeakValleyTrig.TopPeakTrig);
Console.WriteLine($"时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff")}储能进入削峰填谷的【TopPeak】时间段 ");
break;
case ElePVEnum.Peak:
EsSysPeakVellayStateMsg = "峰价";
PeakValleyStateMsg = "峰价状态";
//StateMachinePeakValley.FireAsync(ElePeakValleyTrig.PeakTrig);
Console.WriteLine($"时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff")}储能进入削峰填谷的【Peak】时间段 ");
break;
case ElePVEnum.Valley:
EsSysPeakVellayStateMsg = "谷价";
PeakValleyStateMsg = "谷价状态";
//StateMachinePeakValley.FireAsync(ElePeakValleyTrig.ValleyTrig);
Console.WriteLine($"时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff")}储能进入削峰填谷的【Valley】时间段 ");
break;
case ElePVEnum.Flat:
EsSysPeakVellayStateMsg = "平价";
PeakValleyStateMsg = "平价状态";
//StateMachinePeakValley.FireAsync(ElePeakValleyTrig.FlatTrig);
Console.WriteLine($"时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff")}储能进入削峰填谷的【Flat】时间段 ");
break;
default:
break;
}
}
}
}
/// <summary>
/// 上游的变压器负载
/// </summary>
public double TransformerLoad { get; set; } = 100000000;
/// <summary>
/// 管理大楼和税务大楼之和与光伏的差值
/// </summary>
private double DiffManageTaxSolar1 { get; set; }
/// <summary>
/// 管理大楼和(税务大楼与光伏)的差值
/// </summary>
private double DiffManageTaxSolar2 { get; set; }
/// <summary>
///
/// </summary>
private double DiffManageTaxSolar3 { get; set; }
private double _ManageRealLoad;
/// <summary>
/// 管理大楼实际负载
/// 去除储能的功率之后的真实负载
/// </summary>
public double ManageRealLoad
{
get { return _ManageRealLoad; }
set { _ManageRealLoad = value; RaisePropertyChanged(); }
}
/// <summary>
/// 税务实际负载-去除储能的功率之后的真实负载
/// </summary>
public double TaxRealLoad { get; set; }
/// <summary>
/// 网络的开关和功率仪表数据服务
/// </summary>
public GridDataService GridDataService { get; set; } = new GridDataService();
/// <summary>
/// 实际负载和目标值的差值
/// </summary>
public double DifLoad { get; set; }
#region
/// <summary>
/// 公共逻辑
/// 优先光伏发电,优先依据实时负载及电网和光伏的搭配
/// 主要协调决定2号开关和3号开关是否断开和闭合最后是储能根据削峰填谷调节
/// 1号开关和4号开关原则上不运行断开防止1号和4号其中一个的并网点流向另一侧的负载影响双方的计费数据
/// </summary>
private void ComLogic()
{
#region Io可以远控的情况下
////ManageRealLoad TaxRealLoad SolarEnergyService.RtPw
////光伏优先满足税务大楼
//if (TaxRealLoad >= SolarEnergyService.RtPw)
//{
// //光伏发电量小于税务大楼的负载即税务大楼即可完全消耗掉光伏则需要断开2号开关
// //ToDo 断开2号开关
// //管理大楼按照正常运行
// //断开2号开关
// GridDataService.SetSolarByMeter2Off();
// //正常的削峰填谷的操作,管理大楼侧已经独立
// switch (PeakValleySglModel)
// {
// case ElePVEnum.TopPeak:
// break;
// case ElePVEnum.Peak://峰的状态,尽可能的放电
// if (ManageRealLoad)
// if (IsCanDisCharg())
// {
// //允许充电的功率是否能满足
// if (GetMaxChargPw() >= DiffManageTaxSolar3)
// {
// //储能吸收光伏多余的电量
// //Todo
// ChargPwCmd(DiffManageTaxSolar3);
// }
// else
// {
// //无法满足-断开2号开关
// GridDataService.SetSolarByMeter2Off();
// }
// }
// else
// {
// //不允许充电-断开2号开关
// GridDataService.SetSolarByMeter2Off();
// }
// break;
// case ElePVEnum.Valley://谷的状态,需要全功率充电
// //PeakValleyByValley();
// if (IsCanCharg())
// {
// //判断充电最大功率能否补全光伏余量
// if (GetMaxChargPw() >= DiffManageTaxSolar3)
// {
// //谷状态,储能最大充电功率能满足光伏的余量,因为处于谷的状态,全功率充电中,一部分吸收来自于光伏的能量,一部分来自于电网
// //Todo
// SendFullChargePwCmd(BmsDataService.MaxChargePowerCell.RtValue * ConfigDataService.energyStorageRunConfig.MaxBatChargRatio);
// }
// else
// {
// //无法满足-光伏余量太大,储能也无法吸收余量,无法避免光伏能量溢出到电网-断开2号开关
// GridDataService.SetSolarByMeter2Off();
// }
// }
// else
// {
// //不允许充电-断开2号开关
// GridDataService.SetSolarByMeter2Off();
// }
// break;
// case ElePVEnum.Flat://平的阶段,不浪费光伏电量,也需要充电
// //PeakValleyByFlat();
// if (IsCanCharg())
// {
// //允许充电的功率是否能满足
// if (GetMaxChargPw() >= DiffManageTaxSolar3)
// {
// //储能吸收光伏多余的电量
// //Todo
// ChargPwCmd(DiffManageTaxSolar3);
// }
// else
// {
// //无法满足-断开2号开关
// GridDataService.SetSolarByMeter2Off();
// }
// }
// else
// {
// //不允许充电-断开2号开关
// GridDataService.SetSolarByMeter2Off();
// }
// break;
// default:
// break;
// }
//}
//else if (TaxRealLoad < SolarEnergyService.RtPw && (TaxRealLoad + ManageRealLoad) > SolarEnergyService.RtPw)
//{
// //光伏发电量大于税务大楼负载并光伏发电量小于税务大楼和管理大楼之和负载则看是否有需要闭合2号开关
// //在闭合2号开关前继续判断
// //判断管理大楼是否有负载
// //税务大楼和管理大楼之和多于光伏发电量的差值
// DiffManageTaxSolar1 = TaxRealLoad + ManageRealLoad - SolarEnergyService.RtPw;
// //光伏发电量多于税务大楼的差值
// DiffManageTaxSolar2 = SolarEnergyService.RtPw - TaxRealLoad;
// //管理大楼的负载能消耗掉光伏的余量所以2号开关需要闭合
// //闭合2号开关
// GridDataService.SetSolarByMeter2On();
// //此时管理大楼的负载还无法满足,需要根据削峰填谷的策略判断是否需要储能放电
// //没有报警的话则进入逻辑判断
// switch (PeakValleySglModel)
// {
// case ElePVEnum.TopPeak:
// break;
// case ElePVEnum.Peak://峰的状态,可以放电
// //PeakValleyByPeak();
// //是否可以放电
// if (IsCanDisCharg())
// {
// //允许放电的功率是否能满足
// if (GetMaxDisChargPw() >= DiffManageTaxSolar1)
// {
// //储能补齐管理大楼负载的剩余电量
// //Todo
// DisChargPwCmd(DiffManageTaxSolar1);
// }
// else
// {
// //储能全功率放电,不足的部分电网自动补全
// SendFullDisChargePwValueCmd(BmsDataService.MaxDisChargePowerCell.RtValue * ConfigDataService.energyStorageRunConfig.MaxBatDisChargRatio);
// }
// }
// else
// {
// //储能无法放电,电网补齐余量-闭合2号开关
// //GridDataService.SetSolarByMeter2On();
// }
// break;
// case ElePVEnum.Valley://谷的状态,只进行充电操作
// //PeakValleyByValley();
// //谷的状态不建议放电,让电网补充
// break;
// case ElePVEnum.Flat://平的阶段 不进行动作使PCS待机状态【储能还是削峰填谷的状态】
// //PeakValleyByFlat();
// //谷的状态不建议放电,让电网补充
// break;
// default:
// break;
// }
//}
//else if (TaxRealLoad < SolarEnergyService.RtPw && (TaxRealLoad + ManageRealLoad) <= SolarEnergyService.RtPw)
//{
// //光伏发电量大于税务大楼负载,并光伏发电量也大于税务大楼和管理大楼之和负载
// DiffManageTaxSolar3 = SolarEnergyService.RtPw - (TaxRealLoad + ManageRealLoad);
// //闭合开关2
// //看储能能否接受到这些剩余的光伏能量,如果不能,则限制光伏的能力
// //优先管理大楼负载消耗光伏,剩余的量DiffManageTaxSolar1 ,如果超过阀值的话,看削峰填谷的情况,如果峰的话,则储能补充上,否则电网补齐。
// //DiffManageTaxSolar1 根据削峰填谷的情况储能补齐
// //当前是光伏有多余的量这些余量无论削峰填谷都要充电的当然如果储能SOC满的话无法进行充电则防止逆流需要断开2号开关
// //没有报警的话则进入逻辑判断
// switch (PeakValleySglModel)
// {
// case ElePVEnum.TopPeak:
// break;
// case ElePVEnum.Peak://峰的状态,光伏电量无法消耗完毕,需要储能充电
// //PeakValleyByPeak();
// break;
// case ElePVEnum.Valley://谷的状态,需要全功率充电
// //PeakValleyByValley();
// if (IsCanCharg())
// {
// //判断充电最大功率能否补全光伏余量
// if (GetMaxChargPw() >= DiffManageTaxSolar3)
// {
// //谷状态,储能最大充电功率能满足光伏的余量,因为处于谷的状态,全功率充电中,一部分吸收来自于光伏的能量,一部分来自于电网
// //Todo
// SendFullChargePwCmd(BmsDataService.MaxChargePowerCell.RtValue * ConfigDataService.energyStorageRunConfig.MaxBatChargRatio);
// }
// else
// {
// //无法满足-光伏余量太大,储能也无法吸收余量,无法避免光伏能量溢出到电网-断开2号开关
// GridDataService.SetSolarByMeter2Off();
// }
// }
// else
// {
// //不允许充电-断开2号开关
// GridDataService.SetSolarByMeter2Off();
// }
// break;
// case ElePVEnum.Flat://平的阶段,不浪费光伏电量,也需要充电
// //PeakValleyByFlat();
// if (IsCanCharg())
// {
// //允许充电的功率是否能满足
// if (GetMaxChargPw() >= DiffManageTaxSolar3)
// {
// //储能吸收光伏多余的电量
// //Todo
// ChargPwCmd(DiffManageTaxSolar3);
// }
// else
// {
// //无法满足-断开2号开关
// GridDataService.SetSolarByMeter2Off();
// }
// }
// else
// {
// //不允许充电-断开2号开关
// GridDataService.SetSolarByMeter2Off();
// }
// break;
// default:
// break;
// }
//}
//else
//{
//}
#endregion
//管理大楼真实的负载
//ManageRealLoad = GridDataService.MeterSwitch1.RtPw + GridDataService.MeterSwitch2.RtPw + InPowerPCSDataService.Power;
}
#endregion
///// <summary>
///// 削峰填谷的逻辑运算
///// 实时循环
///// </summary>
///// <exception cref="NotImplementedException"></exception>
//private async Task PeakValleyCycle()
//{
// Console.WriteLine($"时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff")}:储能进入【削峰填谷】模式-循环Cycle -{PeakValleySglModel.ToString()}");
// //处于系统的峰谷状态时才循环
// while (EsSysRunState == EnergyStorageState.PeakValley)
// {
// try
// {
// //当非常快速时首先判断是否被取消操作
// if (PeakValleyTokenSource.IsCancellationRequested)
// {
// break;
// }
// //先检查系统是否有报警,如果有报警立刻跳出削峰填谷的模式
// if (CheckEnergyStorageModuleLink().Result && CheckEnergyStorageModuleAlarm().Result)
// {
// //检查主要组件的运行状态,主要组件没有开始运行的,削峰填谷无法进行,否则进入待机状态
// if (CheckEnergyStorageModuleRunState().Result)
// {
// if (IsMaster)
// {
// ////主站的话则进入削峰填谷的模式
// //削峰填谷的逻辑运算
// ////没有报警的话则进入逻辑判断
// //switch (PeakValleySglModel)
// //{
// // case ElePVEnum.TopPeak:
// // break;
// // case ElePVEnum.Peak://峰的状态,尽可能的放电
// // PeakValleyByPeak();
// // break;
// // case ElePVEnum.Valley://谷的状态,只进行充电操作
// // PeakValleyByValley();
// // break;
// // case ElePVEnum.Flat://平的阶段 不进行动作使PCS待机状态【储能还是削峰填谷的状态】
// // PeakValleyByFlat();
// // break;
// // default:
// // break;
// //}
// }
// else//不是主站的话,则接受主站通信的数据
// {
// //???????
// }
// }
// else//主要组件没有启动运行,削峰填谷无法正常执行,需要到待机的状态
// {
// if (EnergyStorageStateMachine.State != EnergyStorageState.Standby)
// {
// EnergyStorageStateMachine.Fire(EnergyStorageStateTrig.StandbyTrig);
// }
// }
// }
// else//储能系统报警立刻跳出削峰填谷的模式
// {
// if (EnergyStorageStateMachine.State != EnergyStorageState.Alarm)
// {
// //进入报警模式
// EnergyStorageStateMachine.Fire(EnergyStorageStateTrig.AlarmTrig);
// }
// }
// await Task.Delay(500);
// }
// catch (Exception ex)
// {
// LogService.Info($"时间:{DateTime.Now.ToString()}-【PeakValleyCycle】-{ex.Message}");
// }
// }
//}
#region DEMO
/// <summary>
/// 削峰填谷【峰】的逻辑计算
/// 不考虑光伏
/// </summary>
private void PeakValleyByPeakDEMO()
{
try
{
//判断总负载情况
//一个阀值 有能量进来,如果有太阳能的话这个时候太阳能和风能应该是都已经按照最大的能力发电了,这个值应该是最终的到电网的结果值
//真实的负载
ManageRealLoad = AcrelMeterDataService.Psum + InPowerPCSDataService.Power;
//真实的负载大于一个值时,储能开始介入,并不是总是接入到大楼中的
if (ManageRealLoad > ConfigDataService.EMSActionLimitValue.TargetValue)
{
//储能介入 //需要放电
//判断储能是否可以正常放电 是否有禁放的
if (DistIsDisCharg())
{
//储能可以放电
//储能能否满足总的差值
//距离目标的差值
DifLoad = ManageRealLoad - ConfigDataService.EMSActionLimitValue.TargetValue;
if (DifLoad < DistMaxDisChargPw())
{
//储能发送功率 补全总功率数据
//DisChargPwCmd(AcrelMeters.Psum - EleMeterActionLimitValue.MidValue);
DistByDisChargPw(DifLoad);
}
else //储能不能满足总的差值
{
//满功率放电
//储能全功率放电,不足的部分电网自动补全
DistByDisChargPw(DifLoad * ConfigDataService.energyStorageRunConfig.MaxBatDisChargRatio);//???? 是不是应该满功率的乘以这个比率
}
}
else//储能不能放电
{
//储能处于待机状态,防止其他的状态残留
DistBySendStandby();
}
}
else if (ManageRealLoad <= ConfigDataService.EMSActionLimitValue.TargetValue)//真实的负载小于一个值时,储能停止
{
//首先是给储能先停掉
//储能处于待机状态,防止其他的状态残留
DistBySendStandby();
return;
}
}
catch (Exception ex)
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【PeakValleyByPeak】-{ex.Message}");
}
}
/// <summary>
/// 谷的策略的临时数据
/// </summary>
private double ValleyTemp { get; set; }
/// <summary>
/// 削峰填谷【谷】的逻辑计算
/// 不考虑光伏
/// </summary>
private void PeakValleyByValleyDEMO()
{
try
{
ValleyTemp = DistMaxChargPw();
//真实的负载
ManageRealLoad = AcrelMeterDataService.Psum + InPowerPCSDataService.Power;
//如果变压器等能力小于当前最大充电的能力的话
if (ValleyTemp < (TransformerLoad - ManageRealLoad))
{
DistByChargPw(ValleyTemp * ConfigDataService.energyStorageRunConfig.MaxBatChargRatio);
}
else
{
//用剩余的容量作为充电的依据
DistByChargPw((TransformerLoad - ManageRealLoad));
}
}
catch (Exception ex)
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【PeakValleyByValley】-{ex.Message}");
}
}
/// <summary>
/// 削峰填谷【平】的逻辑计算
/// 不考虑光伏
/// </summary>
private void PeakValleyByFlatDEMO()
{
try
{
//平的时候储能不进行干预操作
//发送PCS为0的指令使它处于待机状态
//发送PCS的待机指令0的指令可以一直发送这个指令会判断变化的
DistBySendStandby();
}
catch (Exception ex)
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【PeakValleyByFlat】-{ex.Message}");
}
}
#endregion
/// <summary>
/// 削峰填谷【峰】的逻辑计算
/// 有光伏参与,光伏能量无法控制,靠逆流装置通断
/// </summary>
private void PeakValleyByPeak()
{
try
{
//判断管理大楼总负载情况
//1号功率表如果有太阳能的话这个时候太阳能应该是都已经按照最大的能力发电了这个值应该是最终的到电网的结果值
//大于一个目标值时
if (GridDataService.MeterSwitch1.RtPw >= ConfigDataService.EMSActionLimitValue.TargetValue)
{
//大于目标值储能开始介入
//储能介入 //需要放电
//判断储能是否可以正常放电 是否有禁放的 SOC问题
if (DistIsDisCharg())
{
//储能可以放电
//储能能否满足总的差值
//距离目标的差值
DifLoad = GridDataService.MeterSwitch1.RtPw - ConfigDataService.EMSActionLimitValue.TargetValue;
if (DifLoad < DistMaxDisChargPw())
{
//储能发送功率 补全总功率数据 分布式多机柜调用
DistByDisChargPw(DifLoad);
}
else//储能不能满足总的差值
{
//满功率放电
//储能全功率放电,不足的部分电网自动补全
DistByDisChargPw(DifLoad * ConfigDataService.energyStorageRunConfig.MaxBatDisChargRatio);
}
}
else//储能不能放电
{
//储能处于待机状态,防止其他的状态残留
DistBySendStandby();
}
}
else//低于目标值不进行放电
{
//SOC 过低,需要停止放电
//发送待机指令
DistBySendStandby();
}
}
catch (Exception ex)
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【PeakValleyByPeak】-{ex.Message}");
}
}
/// <summary>
/// 削峰填谷【平】的逻辑计算
/// </summary>
private void PeakValleyByFlat()
{
try
{
//平的时候储能不进行干预操作
//发送PCS为0的指令使它处于待机状态
//发送PCS的待机指令0的指令可以一直发送这个指令会判断变化的
DistBySendStandby();
}
catch (Exception ex)
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【PeakValleyByFlat】-{ex.Message}");
}
}
/// <summary>
/// 削峰填谷【谷】的逻辑计算 的操作
/// </summary>
private void PeakValleyByValley()
{
try
{
var MaxCharg = DistMaxChargPw();
//如果变压器等能力小于当前最大充电的能力的话
if (MaxCharg < (TransformerLoad - GridDataService.MeterSwitch1.RtPw))
{
DistByChargPw(MaxCharg * ConfigDataService.energyStorageRunConfig.MaxBatChargRatio);
}
else
{
//用剩余的容量作为充电的依据
DistByChargPw((TransformerLoad - GridDataService.MeterSwitch1.RtPw));
}
}
catch (Exception ex)
{
LogService.Info($"时间:{DateTime.Now.ToString()}-【PeakValleyByValley】-{ex.Message}");
}
}
#endregion
#region Master
/// <summary>
/// 客户端信息集合
/// </summary>
public List<DistClient> ListDistClient { get; set; }
// = new List<DistClient>()
//{
// new DistClient(){
// IsSelf = true,
// Topic="Station0",
// ServerCmd=new ServerCmd(){ Station=0}
// },//本机器只有一个
// new DistClient(){
// IsSelf = false,
// Topic="Station1",
// ServerCmd=new ServerCmd(){ Station=1},
// },
//};
/// <summary>
/// Self 本体的客户端的信息
/// </summary>
public DistClient DistClient0 { get; set; } = new DistClient()
{
IsSelf = true,
Topic = "Station0",
ServerCmd = new ServerCmd() { Station = 0 },
ClientInfo = new ClientInfo() { Station = 0 }
};//本机器只有一个
/// <summary>
/// 本体的客户端的信息 站号1
/// </summary>
public DistClient DistClient1 { get; set; } = new DistClient()
{
IsSelf = false,
Topic = "Station1",
ServerCmd = new ServerCmd() { Station = 1 },
ClientInfo = new ClientInfo() { Station = 1 }
};
/// <summary>
/// 在线客户端个数
///
/// </summary>
public ushort OnLineClientNum { get; set; }
/// <summary>
/// 当前的指令功率值
/// </summary>
public double CurCmdPw { get; set; }
/// <summary>
/// 分布式-充电
/// 可实时调用
/// </summary>
/// <returns></returns>
public void DistByChargPw(double CmdValue)
{
//最大的放电能力
var data = DistMaxChargPw();
if (data >= CmdValue)
{
//最大放电能力大于指令值
var ratio = CmdValue / data;
foreach (var item in ListDistClient)
{
if (item.IsCanCharg())
{
item.ServerCmd.CmdType = "Charg";
item.ServerCmd.CmdPw = item.ClientInfo.MaxChargPw * ratio;
}
else//不可以充电的话则直接发送0因为不可以充电可能是无法通信和充满了但是无论如何指令都要发送0
{
item.ServerCmd.CmdType = "Charg";
item.ServerCmd.CmdPw = 0;
}
}
}
else
{
//最大放电能力小于指令值,即指令给的太高,但储能无法满足,那么储能需要满功率放电
foreach (var item in ListDistClient)
{
if (item.IsCanCharg())
{
item.ServerCmd.CmdType = "Charg";
item.ServerCmd.CmdPw = item.ClientInfo.MaxChargPw;
}
else//不可以充电的话则直接发送0因为不可以充电可能是无法通信和充满了但是无论如何指令都要发送0
{
item.ServerCmd.CmdType = "Charg";
item.ServerCmd.CmdPw = 0;
}
}
}
}
/// <summary>
/// 分布式-放电
/// 可实时调用
/// </summary>
/// <returns></returns>
public void DistByDisChargPw(double CmdValue)
{
//最大的放电能力
var data = DistMaxDisChargPw();
if (data >= CmdValue)
{
//最大放电能力大于指令值
var ratio = CmdValue / data;
foreach (var item in ListDistClient)
{
if (item.IsCanDisCharg())
{
item.ServerCmd.CmdType = "DisCharg";
item.ServerCmd.CmdPw = item.ClientInfo.MaxDisChargPw * ratio;
}
else//不可以放电的话则直接发送0因为不可以放电可能是无法通信和亏电但是无论如何指令都要发送0
{
item.ServerCmd.CmdType = "Charg";
item.ServerCmd.CmdPw = 0;
}
}
}
else
{
//最大放电能力小于指令值,即指令给的太高,但储能无法满足,那么储能需要满功率放电
foreach (var item in ListDistClient)
{
if (item.IsCanDisCharg())
{
item.ServerCmd.CmdType = "DisCharg";
item.ServerCmd.CmdPw = item.ClientInfo.MaxDisChargPw;
}
else//不可以放电的话则直接发送0因为不可以放电可能是无法通信和亏电但是无论如何指令都要发送0
{
item.ServerCmd.CmdType = "Charg";
item.ServerCmd.CmdPw = 0;
}
}
}
}
/// <summary>
/// 分布式
/// 待机指令
/// 发送0功率
/// </summary>
public void DistBySendStandby()
{
foreach (var item in ListDistClient)
{
item.ServerCmd.CmdType = "Charg";//功率为0充放电的指令类型无所谓
item.ServerCmd.CmdPw = 0;
}
}
private bool _DistIsDisCharg { get; set; } = true;
/// <summary>
/// 分布式
/// 是否可以放电
/// 判断所有的储能是否可以放电
/// 客户端自己封装是否可以充放电考虑SOC其中SOC的高低阀值由客户端本机设置
/// 理论上来说有一个柜体可以放电代表整个系统就能放电,只不过功率大小而已,具体功率大小由其他的函数提供
/// </summary>
/// <returns></returns>
public bool DistIsDisCharg()
{
_DistIsDisCharg = false;
//Client状态
foreach (DistClient client in ListDistClient)
{
if (client.EMSSocketServerConState.ConResult)
{
_DistIsDisCharg = _DistIsDisCharg || client.ClientInfo.IsDisCharg;
}
}
return _DistIsDisCharg;
}
private bool _DistIsCharg { get; set; } = true;
/// <summary>
/// 分布式
/// 是否可以充电
/// 判断所有的储能是否可以充电
/// 需要考虑SOC
/// </summary>
/// <returns></returns>
public bool DistIsCharg()
{
_DistIsCharg = false;
//Client状态
foreach (DistClient client in ListDistClient)
{
if (client.EMSSocketServerConState.ConResult)
{
_DistIsCharg = _DistIsCharg || client.ClientInfo.IsDisCharg;
}
}
return _DistIsCharg;
}
private double _DistMaxChargPw { get; set; }
/// <summary>
/// 分布式
/// 汇总最大的充电功率
/// </summary>
/// <returns></returns>
public double DistMaxChargPw()
{
_DistMaxChargPw = 0;
foreach (DistClient client in ListDistClient)
{
if (client.IsCanCharg() && client.EMSSocketServerConState.ConResult)
{
_DistMaxChargPw = _DistMaxChargPw + client.ClientInfo.MaxChargPw;
}
}
return _DistMaxChargPw;
}
private double _DistMaxDisChargPw { get; set; }
/// <summary>
/// 分布式
/// 汇总最大的放电功率
/// </summary>
/// <returns></returns>
public double DistMaxDisChargPw()
{
_DistMaxDisChargPw = 0;
foreach (DistClient client in ListDistClient)
{
if (client.IsCanDisCharg() && client.EMSSocketServerConState.ConResult)
{
_DistMaxDisChargPw = _DistMaxDisChargPw + client.ClientInfo.MaxDisChargPw;
}
}
return _DistMaxDisChargPw;
}
private void WebSocketServerInit()
{
try
{
wsServer = new WebSocketServer();
wsServer.OnClientApplicationMessageReceive += WebSocket_OnClientApplicationMessageReceive;
wsServer.OnClientConnected += WsServer_OnClientConnected;
wsServer.OnClientDisConnected += WsServer_OnClientDisConnected;
//wsServer.IsTopicRetain = checkBox2.Checked;
ListDistClient.Find(a => a.ServerCmd.Station == 0)!.EMSSocketServerConState.ServerState = true;
ListDistClient.Find(a => a.ServerCmd.Station == 1)!.EMSSocketServerConState.ServerState = true;
wsServer.ServerStart(1883);
wsServer.LogNet = new HslCommunication.LogNet.LogNetSingle("");
//wsServer.LogNet.BeforeSaveToFile += LogNet_BeforeSaveToFile;
}
catch (Exception ex)
{
ListDistClient.Find(a => a.ServerCmd.Station == 0)!.EMSSocketServerConState.ServerState = false;
ListDistClient.Find(a => a.ServerCmd.Station == 1)!.EMSSocketServerConState.ServerState = false;
//MessageBox.Show(HslCommunication.StringResources.Language.ConnectedFailed + Environment.NewLine + ex.Message);
}
}
/// <summary>
/// 广播消息内容
/// </summary>
/// <param name="Msg"></param>
public void SendClientMsg(string Msg)
{
wsServer.PublishAllClientPayload(Msg);
}
/// <summary>
/// 客户端断开连接事件
/// </summary>
/// <param name="session"></param>
/// <exception cref="NotImplementedException"></exception>
private void WsServer_OnClientDisConnected(WebSocketSession session)
{
ListDistClient.Find(a => a.ServerCmd.Station == 0)!.EMSSocketServerConState.ServerState = false;
ListDistClient.Find(a => a.ServerCmd.Station == 1)!.EMSSocketServerConState.ServerState = false;
}
/// <summary>
/// 客户端连接事件
/// </summary>
/// <param name="session"></param>
/// <exception cref="NotImplementedException"></exception>
private void WsServer_OnClientConnected(WebSocketSession session)
{
ListDistClient.Find(a => a.ServerCmd.Station == 0)!.EMSSocketServerConState.ServerState = true;
ListDistClient.Find(a => a.ServerCmd.Station == 1)!.EMSSocketServerConState.ServerState = true;
}
/// <summary>
/// WebSocket客户端的消息接收
/// </summary>
/// <param name="session"></param>
/// <param name="message"></param>
private void WebSocket_OnClientApplicationMessageReceive(WebSocketSession session, WebSocketMessage message)
{
try
{
//Invoke(new Action(() =>
//{
// WsMessage.AppendText($"OpCode:[{message.OpCode}] Mask:[{message.HasMask}] Payload:[{Encoding.UTF8.GetString(message.Payload)}]" + Environment.NewLine);
//}));
ListDistClient.Find(a => a.ServerCmd.Station == 0)!.EMSSocketServerConState.ServerRecvState = true;
//
var Data = Encoding.UTF8.GetString(message.Payload);
//反序列化数据
ClientInfo data = JsonSerializer.Deserialize<ClientInfo>(Data);
//if (data.Station == 1)
//{
// var dd = 1;
//}
foreach (var item in ListDistClient)
{
if (item.ClientInfo!.Station == data.Station)
{
//if (data.Station == 1)
//{
// var dd = 1;
//}
item.ClientInfo = data;
item.EMSSocketServerConState.ServerRecvState = true;
break;
}
}
//把取得数据给集合
//var getdata = ListDistClient.Find(a => a.ClientInfo.Station == data.Station);
//if (getdata != null)
//{
// ListDistClient.Find(a => a.ClientInfo.Station == data.Station).ClientInfo = data;
//}
//Console.WriteLine($"时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff")}接受【Client】数据-站号:【{data.Station}】 ");
//// 应答客户端连接的情况下是需要进行返回数据的,此处演示返回的是原始的数据,追加一个随机数,你可以自己根据业务来决定返回什么数据
//if (session.IsQASession)
//{
// wsServer.SendClientPayload(session, Encoding.UTF8.GetString(message.Payload));
//}
}
catch (Exception ex)
{
LogService.Error(String.Format("ErrSource : {0} ErrMsg : {1}", ex.StackTrace.ToString(), ex.Message.ToString()));
}
}
/// <summary>
/// 关闭Ws服务数据
/// </summary>
public void WsClose()
{
wsServer.ServerClose();
}
/// <summary>
/// WebSocketServer
/// </summary>
private WebSocketServer wsServer { get; set; }
/// <summary>
/// WebSocketServer 的状态
/// </summary>
private bool WebSocketServerState { get; set; } = false;
public bool ThreadEnable = true;
#endregion
}
}