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.

736 lines
24 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using System.Text;
using Autofac;
using Autofac.Core;
using Common.Util;
using Entity.DbModel.Station;
using HybirdFrameworkCore.Autofac;
using HybirdFrameworkCore.Autofac.Attribute;
using HybirdFrameworkDriver.Common;
using log4net;
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Client.Connecting;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Client.Options;
using MQTTnet.Client.Receiving;
using MQTTnet.Formatter;
using MQTTnet.Protocol;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Org.BouncyCastle.Utilities;
using Repository.Station;
using Service.Cloud.Handler;
using Service.Cloud.Msg;
using Service.Cloud.Msg.Cloud.Req;
using Service.Cloud.Msg.Cloud.Resp;
using Service.Cloud.Msg.Cloud.Resp.OutCharger;
using Service.Cloud.Msg.Host.Req;
using Service.Cloud.Msg.Host.Req.OutCharger;
using Service.Cloud.Msg.Host.Resp;
namespace Service.Cloud.Client;
[Scope("SingleInstance")]
public class CloudClient : IMqttClientConnectedHandler, IMqttApplicationMessageReceivedHandler,
IMqttClientDisconnectedHandler
{
private static readonly ILog Log = LogManager.GetLogger(typeof(CloudClient));
public SwapOrderRepository SwapOrderRepository { get; set;}
#region tcp param
public string ServerIp { get; set; }
public int ServerPort { get; set; }
public string ClientId { get; set; }
public string? Username { get; set; }
public string? Password { get; set; }
public int KeepalivePeriod { get; set; } = 3000;
public int Timeout { get; set; } = 60;
public string MqttVersion { get; set; } = "4.0.0";
public bool IsCleanSession { get; set; } = false;
#endregion
#region property
public bool Connected { get; set; }
public bool AutoReConnect { get; set; }
public string StationNo { get; set; }
public string SubTopic { get; set; }
public string PubTopic { get; set; }
public int Encrypt { get; set; }
public string? AesKey { get; set; }
public bool Authed { get; set; }
#endregion
#region Cmd msg cache
public CarCanStart? CarCanStart { get; set; }
/// <summary>
/// 站端发-云端收
/// </summary>
public MsgPair<CarAuth, CarAuthRes> CarAuth { get; set; } = new();
public MsgPair<CardataReport, CardataReportRes> VehicleData { get; set; } = new();
public MsgPair<SignIn, SignInRes> Sign { get; set; } = new();
public MsgPair<DevList, DevListRes> ReportingDevice { get; set; } = new();
public MsgPair<StaSwapRecord, StaSwapRecordRes> UploadPowerChange { get; set; } = new();
public MsgPair<ChargeRecordReport, ChargeRecordReportRes> ChargeRecord { get; set; } = new();
public MsgPair<StationRunStatus, StationRunStatusRes> HostStatus { get; set; } = new();
public MsgPair<StationChnRunStatus, StationChnRunStatusRes> ChannelStatus { get; set; } = new();
public MsgPair<FaultReport, FaultReportRes> RealTimeFault { get; set; } = new();
public MsgPair<EvmDataInfo, EvmDataInfoRes> TemperatureHumidity { get; set; } = new();
public MsgPair<AcDataInfo, AcDataInfoRes> AirConditioning { get; set; } = new();
public MsgPair<EqmStateStartLogInfo, EqmStateStartLogInfoRes> StartLog { get; set; } = new();
public MsgPair<EqmStateEndLogInfo, EqmStateEndLogInfoRes> EndLog { get; set; } = new();
public MsgPair<ChargeDevDataInfo, ChargeDevDataInfoRes> ChargeDevDataInfo { get; set; } = new();
public MsgPair<BatDataInfo, BatDataInfoRes> BatData { get; set; } = new();
public MsgPair<ChargeRecordUpLoad, ChargeRecordUploadRes> ChargeRecordUpLoad { get; set; } = new();
public MsgPair<BatteryTotal, BatteryTotalRes> BatteryTotal { get; set; } = new();
public MsgPair<ChargingTotalDis, ChargingTotalDisRes> ChargingTotalDis { get; set; } = new();
public MsgPair<PowerTotal, PowerTotalRes> PowerTotal { get; set; } = new();
public MsgPair<StaChargingTotal, StaChargingTotalRes> StaChargingTotal { get; set; } = new();
public MsgPair<MeterEnergyKwh, MeterEnergyKwhRes> MeterEnergyKwh { get; set; } = new();
public MsgPair<MeterDayEnergyVal, MeterDayEnergyValRes> MeterDayEnergyVal { get; set; } = new();
public MsgPair<StaHourEnergyVal, StaHourEnergyValRes> StaHourEnergyVal { get; set; } = new();
public MsgPair<StaDayEnergyVal, StaDayEnergyValRes> StaDayEnergyVal { get; set; } = new();
public MsgPair<StaDayOpeEnergyVal, StaDayOpeEnergyValRes> StaDayOpeEnergyVal { get; set; } = new();
public MsgPair<StaHourAmountVal, StaHourAmountValRes> StaHourAmountVal { get; set; } = new();
/// <summary>
/// 云端发-站端收
/// </summary>
public MsgPair<CarCanStart, CarCanStartRes> CarCanStartInfo { get; set; } = new();
public MsgPair<SetStBaseInfo, SetStBaseInfoRes> SetStBaseInfo { get; set; } = new();
public MsgPair<SetService, SetServiceRes> SetService { get; set; } = new();
public MsgPair<SetOpTime, SetOpTimeRes> SetOpTime { get; set; } = new();
public MsgPair<SetOpModel, SetOpModelRes> SetOpModel { get; set; } = new();
public MsgPair<SetStaPrice, SetStaPriceRes> SetStaPrice { get; set; } = new();
public MsgPair<SetChargePrice, SetChargePriceRes> SetChargePrice { get; set; } = new();
public MsgPair<SetChangeCarData, SetChangeCarDataRes> SetChangeCarData { get; set; } = new();
public MsgPair<SetConfig, SetConfigRes> SetConfig { get; set; } = new();
public MsgPair<GetConfig, GetConfigRes> GetConfig { get; set; } = new();
//站外
public MsgPair<PileEndCharge, PileEndChargeResp> PileEndCharge { get; set; } = new();
public MsgPair<PileChargeRealtime, PileChargeRealtimeResp> PileChargeRealtime { get; set; } = new();
public MsgPair<PileRealtime, PileRealtimeResp> PileRealtime { get; set; } = new();
#endregion
#region basic
private IMqttClient? MqttClient;
private List<IBaseHandler> handlers = new();
private static ushort _incrementId;
private static ushort GetIncrementId()
{
if (_incrementId < 65535)
{
_incrementId += 1;
}
else
{
_incrementId = 1;
}
return _incrementId;
}
public void InitHandler()
{
var list = new List<Type>();
foreach (var reg in AppInfo.Container.ComponentRegistry.Registrations)
foreach (var service in reg.Services)
if (service is TypedService ts)
if (MatchHandlers(ts))
list.Add(ts.ServiceType);
foreach (var type in list)
{
var resolve = AppInfo.Container.Resolve(type);
handlers.Add((IBaseHandler)resolve);
}
}
private bool MatchHandlers(TypedService ts)
{
var interfaces = ts.ServiceType.GetInterfaces();
if (interfaces.Length > 0)
foreach (var type in interfaces)
{
if (type.ToString().Contains("Service.Cloud.Handler"))
{
return true;
}
}
return false;
}
public void Connect()
{
Log.Info($"begin connect cloud {ServerIp}:{ServerPort} with client={ClientId}");
if (MqttClient == null)
{
MqttClient = new MqttFactory().CreateMqttClient();
MqttClient.ConnectedHandler = this;
MqttClient.ApplicationMessageReceivedHandler = this;
MqttClient.DisconnectedHandler = this;
}
try
{
var task = MqttClient.ConnectAsync(BuildOptions());
MqttClientConnectResult result = task.Result;
Log.Info($"connect cloud {result.ResultCode}");
if (result.ResultCode == MqttClientConnectResultCode.Success)
{
Connected = true;
}
else
{
Connected = false;
if (AutoReConnect)
{
Thread.Sleep(5000);
Connect();
}
}
}
catch (Exception e)
{
Log.Error("connect cloud error", e);
if (AutoReConnect)
{
Thread.Sleep(5000);
Connect();
}
}
}
/// <summary>
/// 连接成功回调
/// </summary>
/// <param name="eventArgs"></param>
/// <returns></returns>
public async Task HandleConnectedAsync(MqttClientConnectedEventArgs eventArgs)
{
await DoSubTopic(SubTopic);
/*SendVehicleCertification(new VehicleCertification()
{
dt = DateTime.Now,
en = 1,
mode =1,
rfid= "LZ5NB6D33RB000438",
ty= 2,
vi ="LZ5NB6D33RB000438"
});*/
// SendSignIn(new SignIn()
// {
// sn = StaticStationInfo.StationNo,
// st = "01",
// ss = StaticStationInfo.StationStatus,
// en = 1,
// cn = 7
// });
}
private async Task DoSubTopic(string topic)
{
List<MqttTopicFilter> list = new List<MqttTopicFilter>();
string[] topics = topic.Split(new char[] { ',' });
foreach (string str in topics)
{
MqttTopicFilter topicFilter = new MqttTopicFilter
{
Topic = str,
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce
};
list.Add(topicFilter);
}
await MqttClient.SubscribeAsync(list.ToArray());
Log.Info($"subscribe {topic} success ");
}
public void Publish<T>(T data) where T : ICmd
{
if (MqttClient.IsConnected)
{
Model<T> model = new Model<T>
{
Header = new Header()
{
cmd = data.GetCmd(),
cipherFlag = Encrypt,
id = GetIncrementId(),
sid = StationNo,
timeStamp = (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000
},
body = data
};
model.dataSign = "";//SignData(model);
var settings = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
DateFormatString = "yyyy-MM-dd HH:mm:ss",
NullValueHandling = NullValueHandling.Ignore
};
Log.Info($"send {JsonConvert.SerializeObject(model, settings)}");
var appMsg = new MqttApplicationMessage
{
Topic = PubTopic,
Payload = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(model, settings)),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
Retain = false
};
MqttClient.PublishAsync(appMsg);
}
}
private string SignData<T>(Model<T> model) where T : ICmd
{
var settings = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
DateFormatString = "yyyy-MM-dd HH:mm:ss",
NullValueHandling = NullValueHandling.Ignore
};
string body = JsonConvert.SerializeObject(model.body, settings);
return MD5Util.MD5Encrypt32(body + ":" + model.Header.timeStamp + ":" + model.Header.id).ToLower();
}
/// <summary>
/// 消息接收回调
/// </summary>
/// <param name="eventArgs"></param>
/// <returns></returns>
public Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs)
{
var message = eventArgs.ApplicationMessage;
if (message.Topic != SubTopic)
{
return Task.CompletedTask;
}
if (message.Payload == null)
{
return Task.CompletedTask;
}
string s = Encoding.UTF8.GetString(message.Payload);
if (string.IsNullOrWhiteSpace(s))
{
return Task.CompletedTask;
}
Log.Info($"from cloud receive {s} ");
JObject objResult = JObject.Parse(s);
string headerStr = objResult["header"].ToString();
Header? header = JsonConvert.DeserializeObject<Header>(headerStr);
if (header == null)
{
return Task.CompletedTask;
}
foreach (IBaseHandler handler in handlers)
{
if (handler.CanHandle(header.cmd))
{
string bodyStr = objResult["body"].ToString();
handler.Handle(bodyStr);
break;
}
}
return Task.CompletedTask;
}
/// <summary>
/// 断开回调
/// </summary>
/// <param name="eventArgs"></param>
/// <returns></returns>
public Task HandleDisconnectedAsync(MqttClientDisconnectedEventArgs eventArgs)
{
Log.Info("cloud disconnect");
Connected = false;
if (AutoReConnect)
{
Connect();
}
return Task.CompletedTask;
}
private IMqttClientOptions BuildOptions()
{
MqttClientOptionsBuilder builder =
new MqttClientOptionsBuilder().WithTcpServer(ServerIp, ServerPort).WithClientId(ClientId);
if (!string.IsNullOrWhiteSpace(Username))
{
builder.WithCredentials(Username, Encoding.UTF8.GetBytes(Password));
}
if (IsCleanSession)
{
builder.WithCleanSession();
}
builder.WithKeepAlivePeriod(TimeSpan.FromSeconds(KeepalivePeriod))
.WithCommunicationTimeout(TimeSpan.FromSeconds(Timeout));
switch (MqttVersion)
{
case "3.1.0":
builder.WithProtocolVersion(MqttProtocolVersion.V310);
break;
case "5.0.16":
builder.WithProtocolVersion(MqttProtocolVersion.V500);
break;
default:
builder.WithProtocolVersion(MqttProtocolVersion.V311);
break;
}
return builder.Build();
}
#endregion
#region 主动发送CMD
public CarAuthRes? SendVehicleCertification(CarAuth carAuth,
TimeSpan? timeSpan = null)
{
Log.Info(carAuth);
this.CarAuth.Req = carAuth;
this.Publish(carAuth);
return CarAuth.GetResp(timeSpan);
}
public BatDataInfoRes? SendBatDataInfo(BatDataInfo batDataInfo,
TimeSpan? timeSpan = null)
{
this.BatData.Req = batDataInfo;
this.Publish(batDataInfo);
return BatData.GetResp(timeSpan);
}
/// <summary>
/// 换电站通道状态上报
/// </summary>
/// <param name="stationChnRunStatus"></param>
/// <param name="timeSpan"></param>
/// <returns></returns>
public StationChnRunStatusRes? SendChannelStatusReporting(StationChnRunStatus stationChnRunStatus,
TimeSpan timeSpan)
{
this.ChannelStatus.Req = stationChnRunStatus;
this.Publish(stationChnRunStatus);
return ChannelStatus.GetResp(timeSpan);
}
public AcDataInfoRes? SendAirConditioningData(AcDataInfo acDataInfo,
TimeSpan timeSpan)
{
this.AirConditioning.Req = acDataInfo;
this.Publish(acDataInfo);
return AirConditioning.GetResp(timeSpan);
}
public ChargeRecordReportRes? SendChargeRecordReporting(ChargeRecordReport chargeRecord,
TimeSpan timeSpan)
{
this.ChargeRecord.Req = chargeRecord;
this.Publish(chargeRecord);
return ChargeRecord.GetResp(timeSpan);
}
public EqmStateEndLogInfoRes? SendEndLogMessage(EqmStateEndLogInfo endLogMessage,
TimeSpan timeSpan)
{
this.EndLog.Req = endLogMessage;
this.Publish(endLogMessage);
return EndLog.GetResp(timeSpan);
}
public StationRunStatusRes? SendHostStatusReported(StationRunStatus hostStatusReported,
TimeSpan timeSpan)
{
this.HostStatus.Req = hostStatusReported;
this.Publish(hostStatusReported);
return HostStatus.GetResp(timeSpan);
}
public FaultReportRes? SendRealTimeFaultInfo(FaultReport faultReport,
TimeSpan timeSpan)
{
this.RealTimeFault.Req = faultReport;
this.Publish(faultReport);
return RealTimeFault.GetResp(timeSpan);
}
public DevListRes? SendReportingDeviceList(DevList devList,
TimeSpan timeSpan)
{
this.ReportingDevice.Req = devList;
this.Publish(devList);
return ReportingDevice.GetResp(timeSpan);
}
public SignInRes? SendSignIn(SignIn signIn,
TimeSpan? timeSpan=null)
{
this.Sign.Req = signIn;
this.Publish(signIn);
return Sign.GetResp(timeSpan);
}
public EqmStateStartLogInfoRes? SendStartLogMessage(EqmStateStartLogInfo eqmStateStartLogInfo,
TimeSpan timeSpan)
{
this.StartLog.Req = eqmStateStartLogInfo;
this.Publish(eqmStateStartLogInfo);
return StartLog.GetResp(timeSpan);
}
/// <summary>
/// 温湿度
/// </summary>
/// <param name="evmDataInfo"></param>
/// <param name="timeSpan"></param>
/// <returns></returns>
public EvmDataInfoRes? SendEvmDataInfo(EvmDataInfo evmDataInfo,
TimeSpan timeSpan)
{
this.TemperatureHumidity.Req = evmDataInfo;
this.Publish(evmDataInfo);
return TemperatureHumidity.GetResp(timeSpan);
}
/// <summary>
/// 上传换电订单
/// </summary>
/// <param name="staSwapRecord"></param>
/// <param name="timeSpan"></param>
/// <returns></returns>
public StaSwapRecordRes? SendUploadPowerChangeOrder(StaSwapRecord staSwapRecord, TimeSpan? timeSpan = null)
{
this.UploadPowerChange.Req = staSwapRecord;
this.Publish(staSwapRecord);
return UploadPowerChange.GetResp(timeSpan);
}
public CardataReportRes? SendVehicleDataReporting(CardataReport cardataReport,
TimeSpan timeSpan)
{
this.VehicleData.Req = cardataReport;
this.Publish(cardataReport);
return VehicleData.GetResp(timeSpan);
}
/// <summary>
/// 充电订单
/// </summary>
/// <param name="req"></param>
/// <param name="timeSpan"></param>
/// <returns></returns>
public ChargeRecordUploadRes? SendChargeRecordUpLoad(ChargeRecordUpLoad req, TimeSpan? timeSpan = null)
{
this.ChargeRecordUpLoad.Req = req;
this.Publish(req);
return ChargeRecordUpLoad.GetResp(timeSpan);
}
public ChargeDevDataInfoRes? SendChargeDevDataInfo(ChargeDevDataInfo req, TimeSpan? timeSpan = null)
{
this.ChargeDevDataInfo.Req = req;
this.Publish(req);
return ChargeDevDataInfo.GetResp(timeSpan);
}
public BatteryTotalRes? SendBatteryTotal(BatteryTotal req, TimeSpan? timeSpan = null)
{
this.BatteryTotal.Req = req;
this.Publish(req);
return BatteryTotal.GetResp(timeSpan);
}
public ChargingTotalDisRes? SendChargingTotalDis(ChargingTotalDis req, TimeSpan? timeSpan = null)
{
this.ChargingTotalDis.Req = req;
this.Publish(req);
return ChargingTotalDis.GetResp(timeSpan);
}
/// <summary>
/// 电表累计值
/// </summary>
/// <param name="req"></param>
/// <param name="timeSpan"></param>
/// <returns></returns>
public PowerTotalRes? SendPowerTotal(PowerTotal req, TimeSpan? timeSpan = null)
{
this.PowerTotal.Req = req;
this.Publish(req);
return PowerTotal.GetResp(timeSpan);
}
/// <summary>
/// 电表变化值
/// </summary>
/// <param name="req"></param>
/// <param name="timeSpan"></param>
/// <returns></returns>
public StaChargingTotalRes? SendStaChargingTotal(StaChargingTotal req, TimeSpan? timeSpan = null)
{
this.StaChargingTotal.Req = req;
this.Publish(req);
return StaChargingTotal.GetResp(timeSpan);
}
/// <summary>
/// 电表小时值
/// </summary>
/// <param name="req"></param>
/// <param name="timeSpan"></param>
/// <returns></returns>
public MeterEnergyKwhRes? SendMeterEnergyKwh(MeterEnergyKwh req, TimeSpan? timeSpan = null)
{
this.MeterEnergyKwh.Req = req;
this.Publish(req);
return MeterEnergyKwh.GetResp(timeSpan);
}
/// <summary>
/// 电表天
/// </summary>
/// <param name="req"></param>
/// <param name="timeSpan"></param>
/// <returns></returns>
public MeterDayEnergyValRes? SendMeterDayEnergyVal(MeterDayEnergyVal req, TimeSpan? timeSpan = null)
{
this.MeterDayEnergyVal.Req = req;
this.Publish(req);
return MeterDayEnergyVal.GetResp(timeSpan);
}
public StaHourEnergyValRes? SendStaHourEnergyVal(StaHourEnergyVal req, TimeSpan? timeSpan = null)
{
this.StaHourEnergyVal.Req = req;
this.Publish(req);
return StaHourEnergyVal.GetResp(timeSpan);
}
public StaDayEnergyValRes? SendStaDayEnergyVal(StaDayEnergyVal req, TimeSpan? timeSpan = null)
{
this.StaDayEnergyVal.Req = req;
this.Publish(req);
return StaDayEnergyVal.GetResp(timeSpan);
}
public StaDayOpeEnergyValRes? SendStaDayOpeEnergyVal(StaDayOpeEnergyVal req, TimeSpan? timeSpan = null)
{
this.StaDayOpeEnergyVal.Req = req;
this.Publish(req);
return StaDayOpeEnergyVal.GetResp(timeSpan);
}
public StaHourAmountValRes? SendStaHourAmountVal(StaHourAmountVal req, TimeSpan? timeSpan = null)
{
this.StaHourAmountVal.Req = req;
this.Publish(req);
return StaHourAmountVal.GetResp(timeSpan);
}
public PileEndChargeResp? SendPileEndCharge(PileEndCharge req, TimeSpan? timeSpan = null)
{
this.PileEndCharge.Req = req;
this.Publish(req);
return PileEndCharge.GetResp(timeSpan);
}
public PileChargeRealtimeResp? SendPileChargeRealtime(PileChargeRealtime req, TimeSpan? timeSpan = null)
{
this.PileChargeRealtime.Req = req;
this.Publish(req);
return PileChargeRealtime.GetResp(timeSpan);
}
public PileRealtimeResp? SendPileRealtime(PileRealtime req, TimeSpan? timeSpan = null)
{
this.PileRealtime.Req = req;
this.Publish(req);
return PileRealtime.GetResp(timeSpan);
}
#endregion
#region business func
/// <summary>
///充电订单
/// </summary>
/// <param name="chargeOrder"></param>
/// <param name="op">1 自动; 2 人工手动</param>
/// <param name="timeSpan">超时等待</param>
/// <returns></returns>
public bool PublishChargeOrder(List<ChargeOrder> orders, int op, TimeSpan? timeSpan = null)
{
ChargeOrder chargeOrder = orders[0];
ChargeOrder lastChargeOrder = orders[^1];
string swapOrderSn = chargeOrder.SwapOrderSn;
SwapOrder? swapOrder = SwapOrderRepository.QueryByClause(it => it.Sn == swapOrderSn);
ChargeRecordUpLoad req = new ChargeRecordUpLoad()
{
chrsn = chargeOrder.CloudChargeOrder,
son = swapOrder?.CloudSn,
bid = chargeOrder.BatteryNo,
st = chargeOrder.StartTime ?? DateTime.Now,
et = lastChargeOrder.EndTime ?? DateTime.Now,
ssoc = chargeOrder.StartSoc ?? 0,
esoc = lastChargeOrder.StopSoc ?? 0,
//ssoe = chargeOrder.soe
//esoe
dcce =0,
acce =0,
tp = 0,
pp = 0,
fp = 0,
vp = 0,
ct = 0,
cn = orders.Count,
sfs = op,
vin = swapOrder?.VehicleVin,
sfoc = 0,
};
foreach (ChargeOrder order in orders)
{
req.dcce += Convert.ToSingle(order.StopDcElec ?? 0 - order.StartDcElec ?? 0);
req.acce += Convert.ToSingle(order.StopAcElec ?? 0 - order.StartAcElec ?? 0);
req.tp += Convert.ToSingle(order.SharpElecCount);
req.pp += Convert.ToSingle(order.PeakElecCount);
req.fp += Convert.ToSingle(order.FlatElecCount);
req.vp += Convert.ToSingle(order.ValleyElecCount);
req.ct += ((order.EndTime ?? DateTime.Now).Subtract(order.StartTime ?? DateTime.Now)).Minutes;
}
this.SendChargeRecordUpLoad(req);
return true;
}
#endregion
}