You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

685 lines
22 KiB

using System.Collections.Concurrent;
using Autofac;
6 months ago
using Entity.DbModel.Station;
using HybirdFrameworkCore.Autofac;
using HybirdFrameworkCore.Autofac.Attribute;
using HybirdFrameworkCore.Entity;
using HybirdFrameworkCore.Redis;
using HybirdFrameworkCore.Utils;
7 months ago
using HybirdFrameworkDriver.Session;
using HybirdFrameworkDriver.TcpClient;
6 months ago
using log4net;
using Newtonsoft.Json;
6 months ago
using Repository.Station;
using Service.Charger.Codec;
using Service.Charger.Common;
using Service.Charger.Handler;
6 months ago
using Service.Charger.Msg;
using Service.Charger.Msg.Charger.OutCharger.Req;
using Service.Charger.Msg.Charger.Req;
6 months ago
using Service.Charger.Msg.Charger.Resp;
using Service.Charger.Msg.Host.Req;
using Service.Charger.Msg.Host.Req.Bms;
5 months ago
using Service.Charger.Msg.Host.Req.OutCharger.Req;
7 months ago
namespace Service.Charger.Client;
7 months ago
/// <summary>
6 months ago
/// 示例程序
7 months ago
/// </summary>
[Scope("InstancePerDependency")]
public class ChargerClient : TcpClient<IBaseHandler, Decoder, Encoder>
{
6 months ago
#region 属性
/// <summary>
/// 充电机编号
/// </summary>
public string Sn { get; set; }
public ushort AuthTimes { get; set; } = 0;
public bool IsAuthed { get; set; } = false;
/// <summary>
/// 参考 Service.Charger.Common.ChargingStatus
/// </summary>
public UInt16 ChargingStatus { get; set; }
6 months ago
/// <summary>
/// 是否已经开始充电
/// </summary>
public bool IsCharged { get; set; } = false;
/// <summary>
/// 站外两枪时是否在充电
/// </summary>
public ConcurrentDictionary<byte, bool> GunCharged = new ConcurrentDictionary<byte, bool>
{
[1] = false,
[2] = false
};
5 months ago
/// <summary>
/// 充电桩连接状态
/// </summary>
public ConcurrentDictionary<byte, bool> ChargedPile = new ConcurrentDictionary<byte, bool>
{
[1] = false,
[2] = false
};
public bool IsStopped { get; set; } = false;
public bool IsCanSendStopCmd { get; set; } = true;
public DateTime? ChargingStartTime { get; set; }
public DateTime? ChargingStopTime { get; set; }
/// <summary>
/// 电池包实时数据
/// </summary>
6 months ago
public BatteryPackData? BatteryPackData { get; set; }
/// <summary>
/// 电池包实时单体温度&单体电压数据
/// </summary>
6 months ago
public BatteryPackDataVoltage? BatteryPackDataVoltage { get; set; }
/// <summary>
/// 电池包上报累计充放电电量
/// </summary>
6 months ago
public BatteryPackTotalElectricity? BatteryPackTotalElectricity { get; set; }
/// <summary>
/// 电池包上报充放电口温度
/// </summary>
6 months ago
public BatteryPackPortTemperature? BatteryPackPortTemperature { get; set; }
/// <summary>
/// 电池包内部接触器状态和故障上报
/// </summary>
6 months ago
public BatteryPackStateAndFault? BatteryPackStateAndFault { get; set; }
6 months ago
/// <summary>
/// 充放电设备应答站功率调节指令
/// </summary>
public PowerRegulationRes PowerRegulationRes { get; set; }
6 months ago
6 months ago
/// <summary>
/// 电池包实时遥信上报(站内充电模式有电池包时周期性上传)
/// </summary>
public RemoteSignaling RemoteSignaling { get; set; }
6 months ago
6 months ago
/// <summary>
/// 充电机上报车辆 VIN
/// </summary>
public VehicleVIN VehicleVIN { get; set; }
6 months ago
6 months ago
/// <summary>
/// 充放电机应答辅助控制
/// </summary>
public AuxiliaryPowerRes AuxiliaryPowerRes { get; set; }
6 months ago
6 months ago
/// <summary>
/// 充放电上报交流电表数据(交流电表接到充电机上的情况)
/// </summary>
public AcMeter AcMeter { get; set; }
6 months ago
/// <summary>
///充电机遥信数据
/// </summary>
public UploadRemoteSignalData UploadRemoteSignalData = new UploadRemoteSignalData();
/// <summary>
/// 充电机工作状态-从遥信数据包中得到。00H待机 01H工作 02H工作完成 03H充/放电暂停
/// </summary>
public byte Workstate { get; set; }
6 months ago
/// <summary>
/// 充电机故障-遥信数据包总故障 00H正常、01H故障
/// </summary>
public bool TotalError { get; set; }
6 months ago
/// <summary>
/// 充电机告警-遥信数据包总告警 00H正常、01H告警
/// </summary>
public bool TotalWarning { get; set; }
6 months ago
/// <summary>
/// 充电机遥测数据
/// </summary>
public UploadTelemetryData UploadTelemetryData = new UploadTelemetryData();
/// <summary>
5 months ago
/// 充放电机上传单体动力蓄电池电压极值统计
/// </summary>
public VoltageExtremumStatistics? VoltageExtremumStatistics = new VoltageExtremumStatistics();
/// <summary>
/// 充电桩的遥测
/// </summary>
public ConcurrentDictionary<byte, PileUploadTelemetry> PileUploadTelemetry = new();
/// <summary>
5 months ago
/// 充电桩的遥信
/// </summary>
public ConcurrentDictionary<byte, PileUploadRemoteSignal> PileUploadRemoteSignal = new();
/// <summary>
/// 充电桩状态信息
/// </summary>
public ConcurrentDictionary<byte, ChargerPile> ChargerPile = new();
/// <summary>
///充电机实时充电功率
/// </summary>
public float RealTimeChargePower { get; set; } = 0;
5 months ago
/// <summary>
/// 心跳-桩状态
/// </summary>
public byte PileState { get; set; }
6 months ago
6 months ago
/// <summary>
/// 电池编码
/// </summary>
6 months ago
public string? BatteryNo { get; set; }
/// <summary>
/// 电池厂家
/// </summary>
public byte? BatteryFactory { get; set; }
6 months ago
/// <summary>
6 months ago
/// 电池仓编号
/// </summary>
public string? BinNo { get; set; }
6 months ago
6 months ago
/// <summary>
6 months ago
/// 远程升级-监控网关上送升级完成确认帧
/// </summary>
public UplinkUpgrade UplinkUpgrade { get; set; }
6 months ago
6 months ago
/// <summary>
/// 充电订单号
/// </summary>
6 months ago
public string? ChargeOrderNo { get; set; }
6 months ago
/// <summary>
/// 当前指令
/// </summary>
public string? CurrentCmd { get; set; }
6 months ago
6 months ago
/// <summary>
/// 当前接收报文
/// </summary>
public string? CurrentMsg { get; set; }
6 months ago
6 months ago
#endregion
6 months ago
#region db
private ChargeOrderRepository _chargeOrderRepository;
private BinInfoRepository _binInfoRepository;
6 months ago
#endregion
public ChargerClient(ChargeOrderRepository chargeOrderRepository, BinInfoRepository binInfoRepository)
6 months ago
{
_chargeOrderRepository = chargeOrderRepository;
_binInfoRepository = binInfoRepository;
6 months ago
}
6 months ago
private ILog Log()
{
var name = "Charger" + this.Sn;
ILog logger = LogManager.GetLogger(name);
Console.WriteLine(name + "-" + logger.GetHashCode());
return logger;
}
6 months ago
/// <summary>
///
6 months ago
/// </summary>
/// <param name="asdu"></param>
public void ReceiveMsgHandle(ASDU asdu)
{
6 months ago
this.CurrentMsg = CurrentCmd = JsonConvert.SerializeObject(asdu, Formatting.Indented) + "\r\n" +
BitUtls.BytesToHexStr(asdu.ToBytes());
6 months ago
}
#region 发送指令
private ushort IncreAuthTimes()
{
if (AuthTimes < 65535)
{
AuthTimes += 1;
}
else
{
AuthTimes = 1;
}
return AuthTimes;
}
6 months ago
/// <summary>
/// 发送鉴权
/// </summary>
public Result<bool> SendAuth()
{
if (!Connected)
{
return Result<bool>.Fail($"charger-{BinNo} disconnect");
}
byte authCodeKey = ChargerUtils.GetByteRandomNum(); //鉴码KEY[随机数]
byte[] authCodes = ChargerUtils.GetAuthCodesResult(ChargerConst.AuthCode, authCodeKey); //鉴权码
Auth auth = new Auth(IncreAuthTimes(), authCodes, authCodeKey);
6 months ago
CurrentCmd = JsonConvert.SerializeObject(auth, Formatting.Indented) + "\r\n" +
BitUtls.BytesToHexStr(auth.ToBytes());
this.Channel.WriteAndFlushAsync(auth);
return Result<bool>.Success();
}
/// <summary>
/// 监控平台发送远程开始充电指令
/// </summary>
/// <param name="socLimit">SOC限制.百分比</param>
/// <param name="changePowerCmdType">功率调节指令类型.默认1 绝对功率值</param>
/// <param name="changePower">1kw/位,默认3600</param>
/// <param name="chargeOrderNo">充电流水号</param>
public Result<string> SendRemoteStartCharging(byte socLimit, float changePower = 360, byte changePowerCmdType = 1,
string? chargeOrderNo = null)
7 months ago
{
if (!Connected)
{
return Result<string>.Fail($"充电机{BinNo}未连接");
}
if (string.IsNullOrWhiteSpace(chargeOrderNo))
7 months ago
{
chargeOrderNo = ChargerUtils.GenChargeOrderSn();
7 months ago
}
6 months ago
Log().Info(
$"SendRemoteStartCharging soc={socLimit}, changePower={changePower}, changePowerCmdType={changePowerCmdType}, chargeOrderNo={chargeOrderNo}");
var remoteStartCharging = new RemoteStartCharging(socLimit, changePowerCmdType, changePower, chargeOrderNo);
6 months ago
CurrentCmd = JsonConvert.SerializeObject(remoteStartCharging, Formatting.Indented) + "\r\n" +
BitUtls.BytesToHexStr(remoteStartCharging.ToBytes());
this.Channel.WriteAndFlushAsync(remoteStartCharging);
6 months ago
return Result<string>.Success(chargeOrderNo);
}
7 months ago
/// <summary>
/// 监控平台发送远程停止充电指令
/// </summary>
/// <param name="reason">0 正常停机 1 服务器发现桩异常,强制停机</param>
public Result<bool> SendRemoteStopCharging(byte reason = 0)
{
if (!Connected)
{
return Result<bool>.Fail($"charger-{BinNo} disconnect");
}
RemoteStopCharging remoteStopCharging = new RemoteStopCharging(reason);
6 months ago
CurrentCmd = JsonConvert.SerializeObject(remoteStopCharging, Formatting.Indented) + "\r\n" +
BitUtls.BytesToHexStr(remoteStopCharging.ToBytes());
this.Channel.WriteAndFlushAsync(remoteStopCharging);
return Result<bool>.Success();
}
6 months ago
/// <summary>
/// 监控平台发送功率调节指令
/// </summary>
/// <param name="expectedOperatingPower">期望运行功率</param>
public Result<bool> SendPowerRegulation(float expectedOperatingPower)
6 months ago
{
if (!Connected)
{
return Result<bool>.Fail($"charger-{BinNo} disconnect");
}
6 months ago
PowerRegulation powerRegulation = new PowerRegulation(expectedOperatingPower);
this.Channel.WriteAndFlushAsync(powerRegulation);
return Result<bool>.Success();
6 months ago
}
6 months ago
/// <summary>
/// 倍率 例如0.单5C位该0值.1C为 5 ,1C 时该值为 10
/// </summary>
/// <param name="rate"></param>
public Result<bool> SendAdjustChargeRate(float rate)
6 months ago
{
if (!Connected)
{
return Result<bool>.Fail($"charger-{BinNo} disconnect");
}
6 months ago
AdjustChargeRate adjustChargeRate = new AdjustChargeRate(rate);
this.Channel.WriteAndFlushAsync(adjustChargeRate);
return Result<bool>.Success();
6 months ago
}
6 months ago
/// <summary>
/// 监控平台下发辅源控制指令
/// </summary>
/// <param name="openFlag">打开辅助电源标志 1电池包辅助电源导通 0电池包辅助电源断开</param>
public Result<bool> SendAuxiliaryPower(byte openFlag)
6 months ago
{
if (!Connected)
{
return Result<bool>.Fail($"charger-{BinNo} disconnect");
}
6 months ago
AuxiliaryPower auxiliaryPower = new AuxiliaryPower(openFlag);
6 months ago
CurrentCmd = JsonConvert.SerializeObject(auxiliaryPower, Formatting.Indented) + "\r\n" +
BitUtls.BytesToHexStr(auxiliaryPower.ToBytes());
6 months ago
this.Channel.WriteAndFlushAsync(auxiliaryPower);
return Result<bool>.Success();
6 months ago
}
/// <summary>
/// 监控平台下发电池仓的状态
/// </summary>
/// <param name="battery">是否有电池 0:无电池 1有电池</param>
/// <param name="connectionState">电接头连接状态 0:未连接 1: 已连接</param>
/// <param name="waterCondition">水接头状态 0:未连接 1: 已连接</param>
public Result<bool> SendBatteryHolderStatus(byte battery, byte connectionState, byte waterCondition)
6 months ago
{
if (!Connected)
{
return Result<bool>.Fail($"charger-{BinNo} disconnect");
}
6 months ago
BatteryHolderStatus batteryHolderStatus = new BatteryHolderStatus(battery, connectionState, waterCondition);
6 months ago
CurrentCmd = JsonConvert.SerializeObject(batteryHolderStatus, Formatting.Indented) + "\r\n" +
BitUtls.BytesToHexStr(batteryHolderStatus.ToBytes());
6 months ago
this.Channel.WriteAndFlushAsync(batteryHolderStatus);
return Result<bool>.Success();
6 months ago
}
/// <summary>
/// 站控下发 VIN 鉴权的结果
/// </summary>
/// <param name="vinresult">VIN 鉴权结果 1:通过 2 不通过</param>
public Result<bool> SendAuthenticationVIN(byte vinresult)
6 months ago
{
if (!Connected)
{
return Result<bool>.Fail($"charger-{BinNo} disconnect");
}
6 months ago
AuthenticationVIN authenticationVIN = new AuthenticationVIN(vinresult);
this.Channel.WriteAndFlushAsync(authenticationVIN);
return Result<bool>.Success();
6 months ago
}
6 months ago
6 months ago
/// <summary>
/// 远程升级-站级监控升级请求下发
/// </summary>
/// <param name="executionControl">执行控制 0x01立即执行 0x02空闲执行</param>
/// <param name="downloadTimeout">下载超时时间</param>
/// <param name="versionNumber">版本号</param>
/// <param name="fileName">文件名称</param>
/// <param name="fileSize">文件大小</param>
/// <param name="mD5Verification">MD5校验值</param>
/// <param name="url">URL文件路径</param>
public Result<bool> SendUpgradeRequest(byte executionControl, byte downloadTimeout, string versionNumber,
6 months ago
string fileName, uint fileSize, string mD5Verification, string url)
6 months ago
{
if (!Connected)
{
return Result<bool>.Fail($"charger-{BinNo} disconnect");
}
6 months ago
UpgradeRequest upgradeRequest = new UpgradeRequest(executionControl, downloadTimeout, versionNumber, fileName,
fileSize, mD5Verification, url);
6 months ago
this.Channel.WriteAndFlushAsync(upgradeRequest);
return Result<bool>.Success();
6 months ago
}
6 months ago
/// <summary>
/// 设置尖峰平谷时间段
/// </summary>
/// <param name="setPeakValleyTime"></param>
public Result<bool> SendSetPeakValleyTime(SetPeakValleyTime setPeakValleyTime)
6 months ago
{
if (!Connected)
{
return Result<bool>.Fail($"charger-{BinNo} disconnect");
}
6 months ago
CurrentCmd = JsonConvert.SerializeObject(setPeakValleyTime, Formatting.Indented) + "\r\n" +
BitUtls.BytesToHexStr(setPeakValleyTime.ToBytes());
6 months ago
this.Channel.WriteAndFlushAsync(setPeakValleyTime);
Log().Info($"SendSetPeakValleyTime{CurrentCmd} to chargeOrderNo={BinNo}");
return Result<bool>.Success();
6 months ago
}
/// <summary>
/// 3.4.7 监控平台下发掉线停止充电
6 months ago
/// </summary>
/// <param name="enabled"> 0不使能 1使能</param>
public Result<bool> SendOfflineStopCharging(byte enabled)
6 months ago
{
if (!Connected)
{
return Result<bool>.Fail($"charger-{BinNo} disconnect");
}
6 months ago
OfflineStopCharging offlineStopCharging = new OfflineStopCharging(enabled);
6 months ago
CurrentCmd = JsonConvert.SerializeObject(offlineStopCharging, Formatting.Indented) + "\r\n" +
BitUtls.BytesToHexStr(offlineStopCharging.ToBytes());
6 months ago
this.Channel.WriteAndFlushAsync(offlineStopCharging);
return Result<bool>.Success();
6 months ago
}
/// <summary>
/// 3.4.12 站控设备切换站内/站外充电切换
/// </summary>
/// <param name="chargeMode">00:无效 01:站内 02:站外</param>
public Result<bool> SendChangeChargeMode(byte chargeMode)
6 months ago
{
if (!Connected)
{
return Result<bool>.Fail($"charger-{BinNo} disconnect");
}
6 months ago
ChangeChargeMode req = new ChangeChargeMode(chargeMode);
this.Channel.WriteAndFlushAsync(req);
return Result<bool>.Success();
6 months ago
}
/// <summary>
/// 3.7.1 监控平台远程启动充电桩充电
/// </summary>
/// <param name="pn">充电枪ID号</param>
/// <param name="socValue">SOC 限制</param>
/// <param name="changePower">功率调节指令类型</param>
/// <param name="changePowerCmdType">功率调节参数</param>
/// <param name="chargeOrderNo"></param>
/// <returns>充电流水号</returns>
public Result<string> SendStartOutCharger(byte pn, byte socValue, short changePower=360,byte changePowerCmdType=1 ,
string? chargeOrderNo = null)
{
if (!Connected)
{
return Result<string>.Fail($"充电机{BinNo}未连接");
}
if (string.IsNullOrWhiteSpace(chargeOrderNo))
{
chargeOrderNo = ChargerUtils.GenChargeOrderSn();
}
Log().Info(
$"SendStartOutCharger pn={pn}, socValue={socValue}, changePower={changePower}, changePowerCmdType={changePowerCmdType}, chargeOrderNo={chargeOrderNo}");
PileStartCharge pileStartCharge = new PileStartCharge(pn, socValue, changePowerCmdType, changePower,chargeOrderNo);
this.Channel.WriteAndFlushAsync(pileStartCharge);
return Result<string>.Success(chargeOrderNo);
}
/// <summary>
/// 3.7.3 监控平台远程停止充电桩充电
/// </summary>
/// <param name="pn"></param>
/// <param name="stopReason"></param>
/// <returns></returns>
public Result<string> SendStopOutCharger(byte pn, byte stopReason)
{
if (!Connected)
{
return Result<string>.Fail($"充电机{BinNo}未连接");
}
Log().Info(
$"SendStartOutCharger pn={pn}, stopReason={stopReason}");
PileStopCharge pileStopCharge=new PileStopCharge(pn,stopReason);
this.Channel.WriteAndFlushAsync(pileStopCharge);
return Result<string>.Success();
}
5 months ago
/// <summary>
/// 3.7.9 监控平台发送充电桩功率调节指令
/// </summary>
/// <param name="pn"></param>
/// <param name="expectedOperatingPower"></param>
/// <returns></returns>
public Result<bool> SendPileAdjustPower(byte pn,float expectedOperatingPower)
{
if (!Connected)
{
return Result<bool>.Fail($"charger-{BinNo} disconnect");
}
PileAdjustPower powerRegulation = new PileAdjustPower(pn,expectedOperatingPower);
this.Channel.WriteAndFlushAsync(powerRegulation);
return Result<bool>.Success();
}
/// <summary>
///
/// </summary>
public Result<bool> SendQueryBattery()
{
if (!Connected)
{
return Result<bool>.Fail($"charger-{BinNo} disconnect");
}
QueryBattery queryBattery = new QueryBattery(ChargerConst.BatteryNo);
6 months ago
CurrentCmd = JsonConvert.SerializeObject(queryBattery, Formatting.Indented) + "\r\n" +
BitUtls.BytesToHexStr(queryBattery.ToBytes());
this.Channel.WriteAndFlushAsync(queryBattery);
return Result<bool>.Success();
}
#endregion
6 months ago
#region 启动充电
/// <summary>
///
6 months ago
/// </summary>
public Result<bool> StartCharge(byte chargeSoc, float chargePower)
6 months ago
{
if (string.IsNullOrWhiteSpace(BinNo))
{
return Result<bool>.Fail("charger init error with no BinNo");
}
6 months ago
6 months ago
if (!Connected)
{
return Result<bool>.Fail($"charger-{BinNo} disconnect");
6 months ago
}
BinInfo binInfo = _binInfoRepository.QueryByClause(it => it.Code == BinNo);
if (binInfo == null)
{
return Result<bool>.Fail($"charger-{BinNo} not exist");
}
6 months ago
BatteryNo = binInfo.BatteryNo;
if (string.IsNullOrWhiteSpace(BatteryNo))
6 months ago
{
return Result<bool>.Fail($"charger-{BinNo} battery not exist");
6 months ago
}
if (binInfo.AmtLock == 1)
{
return Result<bool>.Fail($"仓-{BinNo} 被锁定");
}
if (binInfo.CanChargeFlag == 0)
6 months ago
{
return Result<bool>.Fail($"仓-{BinNo} 被禁用");
}
6 months ago
RedisHelper redisHelper = AppInfo.Container.Resolve<RedisHelper>();
string? lockKey = redisHelper.GetStrValue($"chargeNo{BinNo}Start");
if (!string.IsNullOrWhiteSpace(lockKey))
{
return Result<bool>.Success(true, $"charger-{BinNo} is starting");
6 months ago
}
redisHelper.SetKeyValueStr($"chargeNo{BinNo}Start", DateTime.Now.ToString("f"), TimeSpan.FromMinutes(1));
Result<string> chargeOrderNo = SendRemoteStartCharging(chargeSoc, chargePower);
if (!chargeOrderNo.IsSuccess)
{
return Result<bool>.Fail(chargeOrderNo.Msg);
}
6 months ago
6 months ago
ChargeOrderNo = chargeOrderNo.Data;
6 months ago
_chargeOrderRepository.Insert(new ChargeOrder()
{
Sn = ChargeOrderNo,
6 months ago
BatteryNo = BatteryNo,
6 months ago
CmdStatus = 0,
ChargerNo = BinNo,
6 months ago
ChargeMode = 1,
StartMode = 1
});
6 months ago
return Result<bool>.Success(true, "发送成功");
6 months ago
}
#endregion
6 months ago
/// <summary>
///
/// </summary>
/// <returns></returns>
public bool Connect()
{
base.BaseConnect();
Log().Info($"charger {Sn} connect succeed");
return Connected;
}
/// <summary>
///
/// </summary>
/// <param name="sn"></param>
/// <param name="destAddr"></param>
public void SessionAttr(string sn, string destAddr)
{
ChannelUtils.AddAttr(Channel, ChargerConst.ChargerSn, sn);
ChannelUtils.AddAttr(Channel, ChargerConst.EqmTypeNo, sn);
ChannelUtils.AddAttr(Channel, ChargerConst.EqmCode, sn);
ChannelUtils.AddAttr(Channel, ChargerConst.DestAddr, destAddr);
7 months ago
}
}