123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- using Edge.Core.IndustryStandardInterface.PhotoVoltaicInverter;
- using Edge.Core.Parser.BinaryParser.Util;
- using Edge.Core.Processor;
- using Edge.Core.Processor.Communicator;
- using Edge.Core.Processor.Dispatcher.Attributes;
- using Edge.Core.UniversalApi;
- using Microsoft.Extensions.DependencyInjection;
- using Microsoft.Extensions.Logging;
- using Microsoft.Extensions.Logging.Abstractions;
- using GroWattInverter.MessageEntity;
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Linq;
- using System.Threading.Tasks;
- using System.Timers;
- using System.Text;
- namespace GroWattInverter
- {
- /// <summary>
- /// the device could support RS485 and Ethernet.
- /// For RS485, multiple devices could be on the bus, that's why here implement the handler as a group.
- /// For Ethernet, each handler can only handle one device.
- /// </summary>
- [MetaPartsRequired(typeof(HalfDuplexActivePollingDeviceProcessor<,>))]
- [MetaPartsRequired(typeof(ComPortCommunicator<>))]
- [MetaPartsRequired(typeof(TcpClientCommunicator<>))]
- [MetaPartsDescriptor(
- "lang-zh-cn:古瑞瓦特 光伏逆变器lang-en-us:GroWatt PhotoVoltaic Inverter",
- "lang-zh-cn:用于驱动 古瑞瓦特-X及储能系列逆变器,日志名称 DynamicPrivate_GroWattInverterlang-en-us:Used for driven 古瑞瓦特-X及储能系列逆变器 PhotoVoltaic Inverter",
- new[] { "lang-zh-cn:光伏lang-en-us:PhotoVoltaic" })]
- public class DeviceGroupHandler : TestableActivePollingDeviceHandler<byte[], GroWattInverter.MessageEntity.MessageBase>, IPhotoVoltaicInverterHandler, IDisposable
- {
- private List<InverterDevice> inverterDevices = new List<InverterDevice>();
- private IContext<byte[], MessageBase> context;
- private ILogger logger = NullLogger.Instance;
- private DeviceGroupConfigV1 deviceGroupConfig;
- private IServiceProvider services;
- private int deviceTreatAsOfflineTimeThresholdByMs = 8000;
- public event EventHandler OnDeviceError;
- public class DeviceGroupConfigV1
- {
- public List<DeviceConfigV1> DeviceConfigs { get; set; }
- }
- public class DeviceConfigV1
- {
- public string DeviceName { get; set; }
- public byte SlaveAddress { get; set; }
- public string Description { get; set; }
- }
- [ParamsJsonSchemas("ctorParamsJsonSchema")]
- public DeviceGroupHandler(DeviceGroupConfigV1 deviceGroupConfig, IServiceProvider services)
- {
- this.deviceGroupConfig = deviceGroupConfig;
- this.services = services;
- var loggerFactory = services.GetRequiredService<ILoggerFactory>();
- this.logger = loggerFactory.CreateLogger("DynamicPrivate_SunGrowInverter");
- this.inverterDevices = this.deviceGroupConfig.DeviceConfigs.Select(dc => new InverterDevice(dc.DeviceName, dc.SlaveAddress)).ToList();
- }
- public override void Init(IContext<byte[], MessageBase> context)
- {
- base.Init(context);
- this.context = context;
- var timeWindowWithActivePollingOutgoing =
- this.context.Outgoing as TimeWindowWithActivePollingOutgoing<byte[], MessageBase>;
- int previousPolledDeviceIndex = 0;
- timeWindowWithActivePollingOutgoing.PollingMsgProducer = () =>
- {
- try
- {
- //no need too fast to detect online&offline state
- if (DateTime.Now.Second % 3 == 0)
- {
- foreach (var d in this.inverterDevices)
- {
- var silentLastingTime = DateTime.Now.Subtract(d.LastIncomingMessageReceivedTime ?? DateTime.MinValue).TotalMilliseconds;
- if (d.IsOnline
- && silentLastingTime >= this.deviceTreatAsOfflineTimeThresholdByMs)
- {
- d.IsOnline = false;
- var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- var __ = universalApiHub.FirePersistGenericAlarmIfNotExists(this.context.Processor,
- new GenericAlarm()
- {
- Title = $"地址为{d.SlaveAddress}的光伏逆变器连接断开",
- Category = $"光伏逆变器",
- Detail = $"地址为{d.SlaveAddress}的光伏逆变器于 {DateTime.Now} 连接断开, 上次收到它的消息是 {(int)(silentLastingTime / 1000)}秒 前",
- Severity = GenericAlarmSeverity.Warning
- }, ga => ga.Detail,
- ga => ga.Detail).Result;
- }
- }
- }
- if (this.deviceGroupConfig.DeviceConfigs.Count <= previousPolledDeviceIndex)
- previousPolledDeviceIndex = 0;
- var target = this.deviceGroupConfig.DeviceConfigs[previousPolledDeviceIndex++];
- //poll:
- // 45 Sys Year 系统时间-年
- // 46 Sys Month 系统时间
- // 47 Sys Day 系
- // 48 Sys Hour 系统时间-小时
- // 49 Sys Min 系统时间-分
- // 50 Sys Sec 系统时间-秒
- return new OutgoingQueryMessage() { SlaveAddress = target.SlaveAddress, FunctionCode = FunctionCodeEnum.保持寄存器, StartingRegAddress = 45, NoOfRegAddress = 6 };
- }
- catch (Exception exxx)
- {
- logger.LogError($"Exceptioned (previousPolledHandlerIndex: {previousPolledDeviceIndex}): {exxx}");
- return null;
- }
- };
- }
- public override async Task Process(IContext<byte[], MessageBase> context)
- {
- this.context = context;
- var targetDevice = this.inverterDevices.FirstOrDefault(id => id.SlaveAddress == context.Incoming.Message.SlaveAddress);
- if (targetDevice == null)
- {
- var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- await universalApiHub.FirePersistGenericAlarmIfNotExists(this.context.Processor,
- new GenericAlarm()
- {
- Title = $"地址为{targetDevice.SlaveAddress}的光伏逆变器未配置",
- Category = $"光伏逆变器",
- Detail = $"地址为{targetDevice.SlaveAddress}的光伏逆变器:{targetDevice.Description ?? ""} 于 {DateTime.Now} 成功连接,但本地未对其进行配置,请打开FCC配置页面添加此设备",
- Severity = GenericAlarmSeverity.Warning
- }, ga => ga.Detail,
- ga => ga.Detail);
- }
- else if (targetDevice.IsOnline == false)
- {
- targetDevice.LastIncomingMessageReceivedTime = DateTime.Now;
- targetDevice.IsOnline = true;
- var deviceDetailInfoResponse = await context.Outgoing.WriteAsync(
- new OutgoingQueryMessage()
- {
- SlaveAddress = targetDevice.SlaveAddress,
- FunctionCode = FunctionCodeEnum.保持寄存器,
- StartingRegAddress = 34,
- NoOfRegAddress = 1
- },
- (testRequest, testResponse) => testResponse is IncomingQueryMessage, 5000) as IncomingQueryMessage;
- if (deviceDetailInfoResponse != null)
- {
- var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- await universalApiHub.FirePersistGenericAlarmIfNotExists(this.context.Processor,
- new GenericAlarm()
- {
- Title = $"地址为{targetDevice.SlaveAddress}的光伏逆变器成功连接",
- Category = $"光伏逆变器",
- Detail = $"地址为{targetDevice.SlaveAddress}的光伏逆变器于 {DateTime.Now} 成功连接,制造商信息: {Encoding.ASCII.GetString(deviceDetailInfoResponse.RawData.ToArray()) }",
- Severity = GenericAlarmSeverity.Information
- }, ga => ga.Detail,
- ga => ga.Detail);
- }
- else
- {
- var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- await universalApiHub.FirePersistGenericAlarmIfNotExists(this.context.Processor,
- new GenericAlarm()
- {
- Title = $"地址为{targetDevice.SlaveAddress}的光伏逆变器成功连接",
- Category = $"光伏逆变器",
- Detail = $"地址为{targetDevice.SlaveAddress}的光伏逆变器于 {DateTime.Now} 成功连接, 但获取设置详细信息失败",
- Severity = GenericAlarmSeverity.Information
- }, ga => ga.Detail,
- ga => ga.Detail);
- }
- }
- await base.Process(context);
- return;
- }
- public void Dispose()
- {
- }
- public IEnumerable<InverterDevice> GetDevices()
- {
- return this.inverterDevices;
- }
- [UniversalApi]
- public virtual async Task<InverterDeviceRealTimeData> ReadRealTimeDataAsync(byte deviceSlaveAddress)
- {
- var 有功response = await this.context.Outgoing.WriteAsync(new OutgoingQueryMessage()
- {
- SlaveAddress = deviceSlaveAddress,
- FunctionCode = FunctionCodeEnum.只读寄存器,
- StartingRegAddress = 53,
- NoOfRegAddress = 2
- }, (_, testResponse) => testResponse is IncomingQueryMessage, 6000) as IncomingQueryMessage;
- if (有功response == null)
- throw new TimeoutException($"ReadRealTimeDataAsync read 有功response timedout for device with slaveAddress: {deviceSlaveAddress}");
- var 无功response = await this.context.Outgoing.WriteAsync(new OutgoingQueryMessage()
- {
- SlaveAddress = deviceSlaveAddress,
- FunctionCode = FunctionCodeEnum.只读寄存器,
- StartingRegAddress = 232,
- NoOfRegAddress = 2
- }, (_, testResponse) => testResponse is IncomingQueryMessage, 6000) as IncomingQueryMessage;
- if (无功response == null)
- throw new TimeoutException($"ReadRealTimeDataAsync read 无功response timedout for device with slaveAddress: {deviceSlaveAddress}");
- var realtimeData = this.Parse(有功response, 无功response);
- return realtimeData;
- }
- [UniversalApi]
- public async Task<object> RawSendOutgoingQueryMessageAsync(byte deviceSlaveAddress, FunctionCodeEnum functionCode, int startingRegAddress, byte noOfRegAddress)
- {
- var response = await this.context.Outgoing.WriteAsync(new OutgoingQueryMessage()
- {
- SlaveAddress = deviceSlaveAddress,
- FunctionCode = functionCode,
- StartingRegAddress = startingRegAddress,
- NoOfRegAddress = noOfRegAddress
- }, (_, testResponse) => testResponse is IncomingQueryMessage, 6000) as IncomingQueryMessage;
- if (response == null)
- throw new TimeoutException("long time no see incoming response");
- return response.ToLogString();
- }
- private InverterDeviceRealTimeData Parse(IncomingQueryMessage 有功response, IncomingQueryMessage 无功response)
- {
- //0.1kWh
- var 日发电量 = ((decimal)(BitConverter.ToUInt32(有功response.RawData.Take(4).Reverse().ToArray()))) / 10;
- //0.1kWh
- decimal 日无功发电量 = -9999;
- if (无功response != null)
- 日无功发电量 = ((decimal)(BitConverter.ToUInt32(无功response.RawData.Take(4).Reverse().ToArray()))) / 10;
- return new InverterDeviceRealTimeData()
- {
- //Device = device,
- 日有功发电量 = 日发电量,
- 日无功发电量 = 日无功发电量,
- //Description = $"设备类型编码: 0x{设备类型编码.ToHexLogString()}, 额定有功功率: {额定有功功率}(kW), 输出类型: {输出类型}, 总发电量: {总发电量}(kWh)"
- };
- }
- }
- }
|