DeviceGroupHandler.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. using Edge.Core.IndustryStandardInterface.PhotoVoltaicInverter;
  2. using Edge.Core.Parser.BinaryParser.Util;
  3. using Edge.Core.Processor;
  4. using Edge.Core.Processor.Communicator;
  5. using Edge.Core.Processor.Dispatcher.Attributes;
  6. using Edge.Core.UniversalApi;
  7. using Microsoft.Extensions.DependencyInjection;
  8. using Microsoft.Extensions.Logging;
  9. using Microsoft.Extensions.Logging.Abstractions;
  10. using GroWattInverter.MessageEntity;
  11. using System;
  12. using System.Collections;
  13. using System.Collections.Generic;
  14. using System.Linq;
  15. using System.Threading.Tasks;
  16. using System.Timers;
  17. using System.Text;
  18. namespace GroWattInverter
  19. {
  20. /// <summary>
  21. /// the device could support RS485 and Ethernet.
  22. /// For RS485, multiple devices could be on the bus, that's why here implement the handler as a group.
  23. /// For Ethernet, each handler can only handle one device.
  24. /// </summary>
  25. [MetaPartsRequired(typeof(HalfDuplexActivePollingDeviceProcessor<,>))]
  26. [MetaPartsRequired(typeof(ComPortCommunicator<>))]
  27. [MetaPartsRequired(typeof(TcpClientCommunicator<>))]
  28. [MetaPartsDescriptor(
  29. "lang-zh-cn:古瑞瓦特 光伏逆变器lang-en-us:GroWatt PhotoVoltaic Inverter",
  30. "lang-zh-cn:用于驱动 古瑞瓦特-X及储能系列逆变器,日志名称 DynamicPrivate_GroWattInverterlang-en-us:Used for driven 古瑞瓦特-X及储能系列逆变器 PhotoVoltaic Inverter",
  31. new[] { "lang-zh-cn:光伏lang-en-us:PhotoVoltaic" })]
  32. public class DeviceGroupHandler : TestableActivePollingDeviceHandler<byte[], GroWattInverter.MessageEntity.MessageBase>, IPhotoVoltaicInverterHandler, IDisposable
  33. {
  34. private List<InverterDevice> inverterDevices = new List<InverterDevice>();
  35. private IContext<byte[], MessageBase> context;
  36. private ILogger logger = NullLogger.Instance;
  37. private DeviceGroupConfigV1 deviceGroupConfig;
  38. private IServiceProvider services;
  39. private int deviceTreatAsOfflineTimeThresholdByMs = 8000;
  40. public event EventHandler OnDeviceError;
  41. public class DeviceGroupConfigV1
  42. {
  43. public List<DeviceConfigV1> DeviceConfigs { get; set; }
  44. }
  45. public class DeviceConfigV1
  46. {
  47. public string DeviceName { get; set; }
  48. public byte SlaveAddress { get; set; }
  49. public string Description { get; set; }
  50. }
  51. [ParamsJsonSchemas("ctorParamsJsonSchema")]
  52. public DeviceGroupHandler(DeviceGroupConfigV1 deviceGroupConfig, IServiceProvider services)
  53. {
  54. this.deviceGroupConfig = deviceGroupConfig;
  55. this.services = services;
  56. var loggerFactory = services.GetRequiredService<ILoggerFactory>();
  57. this.logger = loggerFactory.CreateLogger("DynamicPrivate_SunGrowInverter");
  58. this.inverterDevices = this.deviceGroupConfig.DeviceConfigs.Select(dc => new InverterDevice(dc.DeviceName, dc.SlaveAddress)).ToList();
  59. }
  60. public override void Init(IContext<byte[], MessageBase> context)
  61. {
  62. base.Init(context);
  63. this.context = context;
  64. var timeWindowWithActivePollingOutgoing =
  65. this.context.Outgoing as TimeWindowWithActivePollingOutgoing<byte[], MessageBase>;
  66. int previousPolledDeviceIndex = 0;
  67. timeWindowWithActivePollingOutgoing.PollingMsgProducer = () =>
  68. {
  69. try
  70. {
  71. //no need too fast to detect online&offline state
  72. if (DateTime.Now.Second % 3 == 0)
  73. {
  74. foreach (var d in this.inverterDevices)
  75. {
  76. var silentLastingTime = DateTime.Now.Subtract(d.LastIncomingMessageReceivedTime ?? DateTime.MinValue).TotalMilliseconds;
  77. if (d.IsOnline
  78. && silentLastingTime >= this.deviceTreatAsOfflineTimeThresholdByMs)
  79. {
  80. d.IsOnline = false;
  81. var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
  82. var __ = universalApiHub.FirePersistGenericAlarmIfNotExists(this.context.Processor,
  83. new GenericAlarm()
  84. {
  85. Title = $"地址为{d.SlaveAddress}的光伏逆变器连接断开",
  86. Category = $"光伏逆变器",
  87. Detail = $"地址为{d.SlaveAddress}的光伏逆变器于 {DateTime.Now} 连接断开, 上次收到它的消息是 {(int)(silentLastingTime / 1000)}秒 前",
  88. Severity = GenericAlarmSeverity.Warning
  89. }, ga => ga.Detail,
  90. ga => ga.Detail).Result;
  91. }
  92. }
  93. }
  94. if (this.deviceGroupConfig.DeviceConfigs.Count <= previousPolledDeviceIndex)
  95. previousPolledDeviceIndex = 0;
  96. var target = this.deviceGroupConfig.DeviceConfigs[previousPolledDeviceIndex++];
  97. //poll:
  98. // 45 Sys Year 系统时间-年
  99. // 46 Sys Month 系统时间
  100. // 47 Sys Day 系
  101. // 48 Sys Hour 系统时间-小时
  102. // 49 Sys Min 系统时间-分
  103. // 50 Sys Sec 系统时间-秒
  104. return new OutgoingQueryMessage() { SlaveAddress = target.SlaveAddress, FunctionCode = FunctionCodeEnum.保持寄存器, StartingRegAddress = 45, NoOfRegAddress = 6 };
  105. }
  106. catch (Exception exxx)
  107. {
  108. logger.LogError($"Exceptioned (previousPolledHandlerIndex: {previousPolledDeviceIndex}): {exxx}");
  109. return null;
  110. }
  111. };
  112. }
  113. public override async Task Process(IContext<byte[], MessageBase> context)
  114. {
  115. this.context = context;
  116. var targetDevice = this.inverterDevices.FirstOrDefault(id => id.SlaveAddress == context.Incoming.Message.SlaveAddress);
  117. if (targetDevice == null)
  118. {
  119. var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
  120. await universalApiHub.FirePersistGenericAlarmIfNotExists(this.context.Processor,
  121. new GenericAlarm()
  122. {
  123. Title = $"地址为{targetDevice.SlaveAddress}的光伏逆变器未配置",
  124. Category = $"光伏逆变器",
  125. Detail = $"地址为{targetDevice.SlaveAddress}的光伏逆变器:{targetDevice.Description ?? ""} 于 {DateTime.Now} 成功连接,但本地未对其进行配置,请打开FCC配置页面添加此设备",
  126. Severity = GenericAlarmSeverity.Warning
  127. }, ga => ga.Detail,
  128. ga => ga.Detail);
  129. }
  130. else if (targetDevice.IsOnline == false)
  131. {
  132. targetDevice.LastIncomingMessageReceivedTime = DateTime.Now;
  133. targetDevice.IsOnline = true;
  134. var deviceDetailInfoResponse = await context.Outgoing.WriteAsync(
  135. new OutgoingQueryMessage()
  136. {
  137. SlaveAddress = targetDevice.SlaveAddress,
  138. FunctionCode = FunctionCodeEnum.保持寄存器,
  139. StartingRegAddress = 34,
  140. NoOfRegAddress = 1
  141. },
  142. (testRequest, testResponse) => testResponse is IncomingQueryMessage, 5000) as IncomingQueryMessage;
  143. if (deviceDetailInfoResponse != null)
  144. {
  145. var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
  146. await universalApiHub.FirePersistGenericAlarmIfNotExists(this.context.Processor,
  147. new GenericAlarm()
  148. {
  149. Title = $"地址为{targetDevice.SlaveAddress}的光伏逆变器成功连接",
  150. Category = $"光伏逆变器",
  151. Detail = $"地址为{targetDevice.SlaveAddress}的光伏逆变器于 {DateTime.Now} 成功连接,制造商信息: {Encoding.ASCII.GetString(deviceDetailInfoResponse.RawData.ToArray()) }",
  152. Severity = GenericAlarmSeverity.Information
  153. }, ga => ga.Detail,
  154. ga => ga.Detail);
  155. }
  156. else
  157. {
  158. var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
  159. await universalApiHub.FirePersistGenericAlarmIfNotExists(this.context.Processor,
  160. new GenericAlarm()
  161. {
  162. Title = $"地址为{targetDevice.SlaveAddress}的光伏逆变器成功连接",
  163. Category = $"光伏逆变器",
  164. Detail = $"地址为{targetDevice.SlaveAddress}的光伏逆变器于 {DateTime.Now} 成功连接, 但获取设置详细信息失败",
  165. Severity = GenericAlarmSeverity.Information
  166. }, ga => ga.Detail,
  167. ga => ga.Detail);
  168. }
  169. }
  170. await base.Process(context);
  171. return;
  172. }
  173. public void Dispose()
  174. {
  175. }
  176. public IEnumerable<InverterDevice> GetDevices()
  177. {
  178. return this.inverterDevices;
  179. }
  180. [UniversalApi]
  181. public virtual async Task<InverterDeviceRealTimeData> ReadRealTimeDataAsync(byte deviceSlaveAddress)
  182. {
  183. var 有功response = await this.context.Outgoing.WriteAsync(new OutgoingQueryMessage()
  184. {
  185. SlaveAddress = deviceSlaveAddress,
  186. FunctionCode = FunctionCodeEnum.只读寄存器,
  187. StartingRegAddress = 53,
  188. NoOfRegAddress = 2
  189. }, (_, testResponse) => testResponse is IncomingQueryMessage, 6000) as IncomingQueryMessage;
  190. if (有功response == null)
  191. throw new TimeoutException($"ReadRealTimeDataAsync read 有功response timedout for device with slaveAddress: {deviceSlaveAddress}");
  192. var 无功response = await this.context.Outgoing.WriteAsync(new OutgoingQueryMessage()
  193. {
  194. SlaveAddress = deviceSlaveAddress,
  195. FunctionCode = FunctionCodeEnum.只读寄存器,
  196. StartingRegAddress = 232,
  197. NoOfRegAddress = 2
  198. }, (_, testResponse) => testResponse is IncomingQueryMessage, 6000) as IncomingQueryMessage;
  199. if (无功response == null)
  200. throw new TimeoutException($"ReadRealTimeDataAsync read 无功response timedout for device with slaveAddress: {deviceSlaveAddress}");
  201. var realtimeData = this.Parse(有功response, 无功response);
  202. return realtimeData;
  203. }
  204. [UniversalApi]
  205. public async Task<object> RawSendOutgoingQueryMessageAsync(byte deviceSlaveAddress, FunctionCodeEnum functionCode, int startingRegAddress, byte noOfRegAddress)
  206. {
  207. var response = await this.context.Outgoing.WriteAsync(new OutgoingQueryMessage()
  208. {
  209. SlaveAddress = deviceSlaveAddress,
  210. FunctionCode = functionCode,
  211. StartingRegAddress = startingRegAddress,
  212. NoOfRegAddress = noOfRegAddress
  213. }, (_, testResponse) => testResponse is IncomingQueryMessage, 6000) as IncomingQueryMessage;
  214. if (response == null)
  215. throw new TimeoutException("long time no see incoming response");
  216. return response.ToLogString();
  217. }
  218. private InverterDeviceRealTimeData Parse(IncomingQueryMessage 有功response, IncomingQueryMessage 无功response)
  219. {
  220. //0.1kWh
  221. var 日发电量 = ((decimal)(BitConverter.ToUInt32(有功response.RawData.Take(4).Reverse().ToArray()))) / 10;
  222. //0.1kWh
  223. decimal 日无功发电量 = -9999;
  224. if (无功response != null)
  225. 日无功发电量 = ((decimal)(BitConverter.ToUInt32(无功response.RawData.Take(4).Reverse().ToArray()))) / 10;
  226. return new InverterDeviceRealTimeData()
  227. {
  228. //Device = device,
  229. 日有功发电量 = 日发电量,
  230. 日无功发电量 = 日无功发电量,
  231. //Description = $"设备类型编码: 0x{设备类型编码.ToHexLogString()}, 额定有功功率: {额定有功功率}(kW), 输出类型: {输出类型}, 总发电量: {总发电量}(kWh)"
  232. };
  233. }
  234. }
  235. }