diff --git a/Plugins/Drivers/CNC.Fanuc.H/DeviceFanuc.cs b/Plugins/Drivers/CNC.Fanuc.H/DeviceFanuc.cs index 99f41240..5bf5faa8 100644 --- a/Plugins/Drivers/CNC.Fanuc.H/DeviceFanuc.cs +++ b/Plugins/Drivers/CNC.Fanuc.H/DeviceFanuc.cs @@ -13,6 +13,7 @@ public class DeviceFanuc : IDriver public ILogger _logger { get; set; } private readonly string _device; + public event Func? OnDataReceived; #region 配置参数 @@ -894,4 +895,4 @@ private enum LanguageType 土耳其语 = 17 } } -} \ No newline at end of file +} diff --git a/Plugins/Drivers/CNC.Fanuc/DeviceFanuc.cs b/Plugins/Drivers/CNC.Fanuc/DeviceFanuc.cs index 9b766638..a16f6da0 100644 --- a/Plugins/Drivers/CNC.Fanuc/DeviceFanuc.cs +++ b/Plugins/Drivers/CNC.Fanuc/DeviceFanuc.cs @@ -12,6 +12,7 @@ public class DeviceFanuc : IDriver public ILogger _logger { get; set; } private readonly string _device; + public event Func? OnDataReceived; #region 配置参数 @@ -1074,4 +1075,4 @@ public async Task WriteAsync(string requestId, string method, Drive #endregion 读写方法 } -} \ No newline at end of file +} diff --git a/Plugins/Drivers/CNC.MTConnect/DeviceMTClient.cs b/Plugins/Drivers/CNC.MTConnect/DeviceMTClient.cs index 1d65c753..612c9cd9 100644 --- a/Plugins/Drivers/CNC.MTConnect/DeviceMTClient.cs +++ b/Plugins/Drivers/CNC.MTConnect/DeviceMTClient.cs @@ -11,6 +11,7 @@ public class DeviceMTClient : IDriver private EntityClient? _mClient; public ILogger _logger { get; set; } private readonly string _device; + public event Func? OnDataReceived; #region 配置参数 @@ -117,4 +118,4 @@ public async Task WriteAsync(string requestId, string method, Drive #endregion 读写方法 } -} \ No newline at end of file +} diff --git a/Plugins/Drivers/Mock.TcpClient/DeviceTcpClient.cs b/Plugins/Drivers/Mock.TcpClient/DeviceTcpClient.cs index ac39b818..7597fb22 100644 --- a/Plugins/Drivers/Mock.TcpClient/DeviceTcpClient.cs +++ b/Plugins/Drivers/Mock.TcpClient/DeviceTcpClient.cs @@ -20,6 +20,7 @@ public class DeviceTcpClient : IDriver public ILogger _logger { get; set; } private readonly string _device; + public event Func? OnDataReceived; #region 配置参数 @@ -92,7 +93,30 @@ private void Client_DataReceived(object? sender, Message e) { //如果收到的数据校验正确,则放在内存中 if (e.Data.Length == 8 && e.Data[0] == 0x08) - _latestRcvData = e.Data; + _latestRcvData = e.Data + + var dataArgs = new DataReportEventArgs + { + DeviceId = DeviceId, + VariableName = "接收到数据", + Address = "", + Value = e.Data, + Timestamp = DateTime.Now, + StatusType = VaribaleStatusTypeEnum.Good, + Message = "" + }; + // 在后台线程中异步处理,避免阻塞调用方 + _ = Task.Run(async () => + { + try + { + await OnDataReceived.Invoke(this, dataArgs); + } + catch (Exception ex) + { + _logger.LogError(ex, $"触发式数据上报失败: {dataArgs.VariableName}"); + } + }); } /// @@ -232,4 +256,4 @@ public enum ConnectionType Long, Short } -} \ No newline at end of file +} diff --git a/Plugins/Drivers/OPC.DaClient/DeviceDaClient.cs b/Plugins/Drivers/OPC.DaClient/DeviceDaClient.cs index 5c6c141d..e670c492 100644 --- a/Plugins/Drivers/OPC.DaClient/DeviceDaClient.cs +++ b/Plugins/Drivers/OPC.DaClient/DeviceDaClient.cs @@ -12,6 +12,7 @@ internal class DeviceDaClient : IDriver public ILogger _logger { get; set; } private readonly string _device; + public event Func? OnDataReceived; #region 配置参数 @@ -217,4 +218,4 @@ public async Task WriteAsync(string requestId, string method, Drive #endregion 读写方法 } -} \ No newline at end of file +} diff --git a/Plugins/Drivers/OPC.UaClient/DeviceUaClient.cs b/Plugins/Drivers/OPC.UaClient/DeviceUaClient.cs index 6a7540d6..c31349de 100644 --- a/Plugins/Drivers/OPC.UaClient/DeviceUaClient.cs +++ b/Plugins/Drivers/OPC.UaClient/DeviceUaClient.cs @@ -12,6 +12,7 @@ public class DeviceUaClient : IDriver private OpcUaClientHelper? _opcUaClient; public ILogger _logger { get; set; } private readonly string _device; + public event Func? OnDataReceived; #region 配置参数 @@ -160,4 +161,4 @@ public async Task WriteAsync(string requestId, string method, Drive return rpcResponse; } } -} \ No newline at end of file +} diff --git a/Plugins/Drivers/Other.Toledo/DeviceToledo.cs b/Plugins/Drivers/Other.Toledo/DeviceToledo.cs index 98ded9f9..7a6207c9 100644 --- a/Plugins/Drivers/Other.Toledo/DeviceToledo.cs +++ b/Plugins/Drivers/Other.Toledo/DeviceToledo.cs @@ -25,6 +25,7 @@ public class DeviceToledo : IDriver public ILogger _logger { get; set; } private readonly string _device; + public event Func? OnDataReceived; #region 配置参数 @@ -386,4 +387,4 @@ public enum ConnectionType Long, Short } -} \ No newline at end of file +} diff --git a/Plugins/Drivers/PLC.AllenBradley/DeviceAllenBradley.cs b/Plugins/Drivers/PLC.AllenBradley/DeviceAllenBradley.cs index 0193d3ef..d07f39e7 100644 --- a/Plugins/Drivers/PLC.AllenBradley/DeviceAllenBradley.cs +++ b/Plugins/Drivers/PLC.AllenBradley/DeviceAllenBradley.cs @@ -12,6 +12,7 @@ public class DeviceAllenBradley : IDriver public ILogger _logger { get; set; } private readonly string _device; + public event Func? OnDataReceived; #region 配置参数 @@ -188,4 +189,4 @@ public async Task WriteAsync(string requestId, string method, Drive #endregion 读写方法 } -} \ No newline at end of file +} diff --git a/Plugins/Drivers/PLC.MelsecMc/DeviceMelsecMc.cs b/Plugins/Drivers/PLC.MelsecMc/DeviceMelsecMc.cs index 96ccc730..3cf2017a 100644 --- a/Plugins/Drivers/PLC.MelsecMc/DeviceMelsecMc.cs +++ b/Plugins/Drivers/PLC.MelsecMc/DeviceMelsecMc.cs @@ -15,6 +15,7 @@ public class DeviceMelsecMc : IDriver public ILogger _logger { get; set; } private readonly string _device; + public event Func? OnDataReceived; #region 配置参数 @@ -391,4 +392,4 @@ public async Task WriteAsync(string requestId, string method, Drive #endregion 读写方法 } -} \ No newline at end of file +} diff --git a/Plugins/Drivers/PLC.ModBusMaster/DeviceModBusMaster.cs b/Plugins/Drivers/PLC.ModBusMaster/DeviceModBusMaster.cs index 4afaa006..31c20c6c 100644 --- a/Plugins/Drivers/PLC.ModBusMaster/DeviceModBusMaster.cs +++ b/Plugins/Drivers/PLC.ModBusMaster/DeviceModBusMaster.cs @@ -27,6 +27,7 @@ public class DeviceModBusMaster : IDriver public ILogger _logger { get; set; } private readonly string _device; + public event Func? OnDataReceived; #region 配置参数 @@ -1081,4 +1082,4 @@ private DriverReturnValueModel AnalyzeAddress(DriverAddressIoArgModel ioArg, out #endregion 私有方法 } -} \ No newline at end of file +} diff --git a/Plugins/Drivers/PLC.OmronFins/DeviceOmronFins.cs b/Plugins/Drivers/PLC.OmronFins/DeviceOmronFins.cs index 50a50ef7..336d4fc3 100644 --- a/Plugins/Drivers/PLC.OmronFins/DeviceOmronFins.cs +++ b/Plugins/Drivers/PLC.OmronFins/DeviceOmronFins.cs @@ -11,6 +11,7 @@ public class DeviceOmronFins : IDriver private OmronFinsClient? _plc; public ILogger _logger { get; set; } private readonly string _device; + public event Func? OnDataReceived; #region 配置参数 @@ -219,4 +220,4 @@ public async Task WriteAsync(string requestId, string method, Drive #endregion 读写方法 } -} \ No newline at end of file +} diff --git a/Plugins/Drivers/PLC.SiemensS7/DeviceSiemensS7.cs b/Plugins/Drivers/PLC.SiemensS7/DeviceSiemensS7.cs index 71e1dae8..9a4cf6b7 100644 --- a/Plugins/Drivers/PLC.SiemensS7/DeviceSiemensS7.cs +++ b/Plugins/Drivers/PLC.SiemensS7/DeviceSiemensS7.cs @@ -19,6 +19,7 @@ public class DeviceSiemensS7 : IDriver public ILogger _logger { get; set; } private readonly string _device; + public event Func? OnDataReceived; #region 配置参数 @@ -419,4 +420,4 @@ private string GetString(DataTypeEnum dataType, byte[] strBytes) #endregion 私有方法 } -} \ No newline at end of file +} diff --git a/Plugins/Plugin/DeviceThread.cs b/Plugins/Plugin/DeviceThread.cs index 69993e93..cf72d94d 100644 --- a/Plugins/Plugin/DeviceThread.cs +++ b/Plugins/Plugin/DeviceThread.cs @@ -47,6 +47,9 @@ public DeviceThread(Device device, IDriver driver, string projectId, MessageServ _mqttServer = mqttServer; MethodDelegates = new Dictionary>(); + // 订阅驱动数据上报事件 + Driver.OnDataReceived += OnDriverDataReceived; + // 将带有 MethodAttribute 的方法转换为委托,存入字典,提升调用效率 foreach (var methodInfo in Driver.GetType().GetMethods().Where(x => x.GetCustomAttribute(typeof(MethodAttribute)) != null)) { @@ -78,6 +81,131 @@ public DeviceThread(Device device, IDriver driver, string projectId, MessageServ } } + /// + /// 处理驱动主动上报的数据 + /// + /// + /// + private async Task OnDriverDataReceived(object sender, DataReportEventArgs e) + { + try + { + // 根据变量名或地址查找对应的DeviceVariable + var deviceVariable = Device.DeviceVariables?.FirstOrDefault(v => + v.Name == e.VariableName || + v.DeviceAddress == e.Address || + (!string.IsNullOrEmpty(e.Address) && v.DeviceAddress == e.Address)); + + if (deviceVariable == null) + { + _logger.LogWarning($"未找到对应的设备变量: {e.VariableName}, 地址: {e.Address}"); + return; + } + + // 创建返回值模型 + var ret = new DriverReturnValueModel + { + Value = e.Value, + StatusType = e.StatusType, + Timestamp = e.Timestamp, + Message = e.Message, + VarId = deviceVariable.ID + }; + + // 将原始值加入队列(保存最近3次) + deviceVariable.EnqueueVariable(ret.Value); + + // 表达式计算(如果配置了) + if (ret.StatusType == VaribaleStatusTypeEnum.Good && !string.IsNullOrWhiteSpace(deviceVariable.Expressions?.Trim())) + { + var expressionText = DealMysqlStr(deviceVariable.Expressions) + .Replace("raw", + deviceVariable.Values[0] is bool + ? $"Convert.ToBoolean(\"{deviceVariable.Values[0]}\")" + : deviceVariable.Values[0]?.ToString()) + .Replace("$v", + deviceVariable.Values[0] is bool + ? $"Convert.ToBoolean(\"{deviceVariable.Values[0]}\")" + : deviceVariable.Values[0]?.ToString()) + .Replace("$pv", + deviceVariable.Values[1] is bool + ? $"Convert.ToBoolean(\"{deviceVariable.Values[1]}\")" + : deviceVariable.Values[1]?.ToString()) + .Replace("$ppv", + deviceVariable.Values[2] is bool + ? $"Convert.ToBoolean(\"{deviceVariable.Values[2]}\")" + : deviceVariable.Values[2]?.ToString()); + + try + { + ret.CookedValue = _interpreter!.Eval(expressionText); + } + catch (Exception ex) + { + ret.Message = $"表达式错误:{expressionText}"; + ret.StatusType = VaribaleStatusTypeEnum.ExpressionError; + _logger.LogError(ex, $"表达式计算失败: {expressionText}"); + } + } + else + { + ret.CookedValue = ret.Value; + } + + // 将计算后的值加入队列 + deviceVariable.EnqueueCookedVariable(ret.CookedValue); + + // 更新设备变量的状态 + deviceVariable.Value = ret.Value; + deviceVariable.CookedValue = ret.CookedValue; + deviceVariable.StatusType = ret.StatusType; + deviceVariable.Timestamp = ret.Timestamp; + deviceVariable.Message = ret.Message; + + // 发布到MQTT(用于前端展示) + if (JsonConvert.SerializeObject(deviceVariable.Values[1]) != JsonConvert.SerializeObject(deviceVariable.Values[0]) || + JsonConvert.SerializeObject(deviceVariable.CookedValues[1]) != JsonConvert.SerializeObject(deviceVariable.CookedValues[0])) + { + var msgInternal = new InjectedMqttApplicationMessage( + new MqttApplicationMessage() + { + Topic = $"internal/v1/gateway/telemetry/{Device.DeviceName}/{deviceVariable.Name}", + PayloadSegment = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(ret)) + }); + _mqttServer.InjectApplicationMessage(msgInternal); + } + + // 如果变量配置为上传,则发布遥测数据 + if (deviceVariable.IsUpload && ret.StatusType == VaribaleStatusTypeEnum.Good) + { + var deviceName = string.IsNullOrWhiteSpace(deviceVariable.Alias) ? Device.DeviceName : deviceVariable.Alias; + var sendModel = new Dictionary> + { + { + deviceName, + new List + { + new PayLoad + { + Values = new Dictionary { { deviceVariable.Name, ret.CookedValue } }, + TS = (long)(DateTime.UtcNow - _tsStartDt).TotalMilliseconds, + DeviceStatus = DeviceStatusTypeEnum.Good + } + } + } + }; + + await _messageService.PublishTelemetryAsync(deviceName, Device, sendModel); + } + + _logger.LogDebug($"处理驱动上报数据: {deviceVariable.Name} = {ret.CookedValue}"); + } + catch (Exception ex) + { + _logger.LogError(ex, $"处理驱动上报数据失败: {e.VariableName}"); + } + } + private async Task CreateThreadAsync(CancellationToken token) { try @@ -390,6 +518,8 @@ public void StopThread() } } _messageService.OnExcRpc -= MyMqttClient_OnExcRpc; + // 取消订阅驱动数据上报事件 + Driver.OnDataReceived -= OnDriverDataReceived; _tokenSource.Cancel(); Driver.Close(); } @@ -413,4 +543,4 @@ private string DealMysqlStr(string expression) .Replace(""", "\""); } } -} \ No newline at end of file +} diff --git a/Plugins/PluginInterface/IDriver.cs b/Plugins/PluginInterface/IDriver.cs index c3a28285..43685a02 100644 --- a/Plugins/PluginInterface/IDriver.cs +++ b/Plugins/PluginInterface/IDriver.cs @@ -1,7 +1,19 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; namespace PluginInterface { + // 数据上报事件参数 + public class DataReportEventArgs : EventArgs + { + public string DeviceId { get; set; } = string.Empty; + public string VariableName { get; set; } = string.Empty; + public object? Value { get; set; } + public DateTime Timestamp { get; set; } = DateTime.Now; + public VaribaleStatusTypeEnum StatusType { get; set; } = VaribaleStatusTypeEnum.Good; + public string Message { get; set; } = string.Empty; + public string? Address { get; set; } + } + public interface IDriver : IDisposable { public string DeviceId { get; } @@ -11,6 +23,9 @@ public interface IDriver : IDisposable public ILogger _logger { get; set; } + // 异步数据上报事件 + public event Func? OnDataReceived; + public bool Connect(); public bool Close(); @@ -21,4 +36,4 @@ public interface IDriver : IDisposable //Rpc写入 public Task WriteAsync(string RequestId, string Method, DriverAddressIoArgModel ioArg); } -} \ No newline at end of file +}