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
{
///
/// 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.
///
[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, IPhotoVoltaicInverterHandler, IDisposable
{
private List inverterDevices = new List();
private IContext 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 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();
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 context)
{
base.Init(context);
this.context = context;
var timeWindowWithActivePollingOutgoing =
this.context.Outgoing as TimeWindowWithActivePollingOutgoing;
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();
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 context)
{
this.context = context;
var targetDevice = this.inverterDevices.FirstOrDefault(id => id.SlaveAddress == context.Incoming.Message.SlaveAddress);
if (targetDevice == null)
{
var universalApiHub = this.services.GetRequiredService();
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();
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();
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 GetDevices()
{
return this.inverterDevices;
}
[UniversalApi]
public virtual async Task 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