using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Aging.Infrastructure.Contracts; using HslCommunication; using HslCommunication.ModBus; using log4net; namespace Aging.Infrastructure.Drivers { /// /// ModBus TCP 驱动适配器。 /// 封装 HslCommunication.ModBusTcpNet,实现统一的 IDeviceDriver 接口。 /// 命令格式约定: /// 写入: "WRITE:address:value" 如 "WRITE:40001:100" 或 "WRITE:00001:TRUE" /// 读取: "READ:address" 如 "READ:00001" 或 "READ:40001" /// public sealed class ModBusDriverAdapter : IDeviceDriver { private static readonly ILog Log = LogManager.GetLogger(typeof(ModBusDriverAdapter)); private readonly string _ip; private readonly int _port; private readonly byte _slaveId; private ModbusTcpNet? _client; private bool _disposed; private readonly Stopwatch _latencyStopwatch = new Stopwatch(); private double _lastLatencyMs; private string _lastError = string.Empty; private DateTime? _lastErrorTime; private int _reconnectCount; /// public string DeviceId { get; } /// public bool IsConnected => _client != null; /// public event EventHandler? ConnectionChanged; /// /// 初始化 ModBus TCP 驱动适配器 /// /// 设备唯一标识符 /// 目标 IP 地址 /// 目标端口号 /// 从站地址(默认为 1) public ModBusDriverAdapter(string deviceId, string ip, int port, byte slaveId = 1) { DeviceId = deviceId ?? throw new ArgumentNullException(nameof(deviceId)); _ip = ip ?? throw new ArgumentNullException(nameof(ip)); _port = port; _slaveId = slaveId; } /// public Task ConnectAsync(CancellationToken ct) { ThrowIfDisposed(); Log.Info($"[ModBus] 设备 {DeviceId} 正在连接 {_ip}:{_port} (从站: {_slaveId})..."); try { _client = new ModbusTcpNet(_ip, _port, _slaveId); OperateResult result = _client.ConnectServer(); if (!result.IsSuccess) { _lastError = result.Message; _lastErrorTime = DateTime.Now; throw new InvalidOperationException($"[ModBus] 设备 {DeviceId} 连接失败: {result.Message}"); } _lastError = string.Empty; Log.Info($"[ModBus] 设备 {DeviceId} 已成功连接到 {_ip}:{_port}"); OnConnectionChanged(true, "连接成功"); } catch (Exception ex) { _lastError = ex.Message; _lastErrorTime = DateTime.Now; Log.Error($"[ModBus] 设备 {DeviceId} 连接异常: {ex.Message}", ex); _client?.ConnectClose(); _client?.Dispose(); _client = null; throw; } return Task.CompletedTask; } /// public Task DisconnectAsync() { ThrowIfDisposed(); Log.Info($"[ModBus] 设备 {DeviceId} 正在断开连接..."); try { if (_client != null) { _client.ConnectClose(); _client.Dispose(); _client = null; } Log.Info($"[ModBus] 设备 {DeviceId} 已断开连接"); OnConnectionChanged(false, "主动断开"); } catch (Exception ex) { _lastError = ex.Message; _lastErrorTime = DateTime.Now; Log.Error($"[ModBus] 设备 {DeviceId} 断开连接异常: {ex.Message}", ex); _client = null; } return Task.CompletedTask; } /// public Task SendCommandAsync(string command, CancellationToken ct) { ThrowIfDisposed(); if (!IsConnected) throw new InvalidOperationException("[ModBus] 设备未连接"); _latencyStopwatch.Restart(); try { Log.Debug($"[ModBus] 设备 {DeviceId} 发送命令: {command}"); // 解析命令格式: "WRITE:address:value" string[] parts = command.Split(':'); if (parts.Length < 3) { throw new ArgumentException($"[ModBus] 命令格式错误,应为 WRITE:address:value,实际: {command}"); } string operation = parts[0].ToUpper(); string address = parts[1]; string value = parts[2]; if (operation != "WRITE") { throw new ArgumentException($"[ModBus] 不支持的操作类型: {operation},应为 WRITE"); } // 根据值判断数据类型并写入 OperateResult result; if (bool.TryParse(value, out bool boolVal)) { result = _client.Write(address, boolVal); } else if (short.TryParse(value, out short shortVal)) { result = _client.Write(address, shortVal); } else if (int.TryParse(value, out int intVal)) { result = _client.Write(address, intVal); } else if (float.TryParse(value, out float floatVal)) { result = _client.Write(address, floatVal); } else { throw new ArgumentException($"[ModBus] 无法解析的值: {value}"); } _latencyStopwatch.Stop(); _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; if (!result.IsSuccess) { _lastError = result.Message; _lastErrorTime = DateTime.Now; throw new InvalidOperationException($"[ModBus] 写入失败: {result.Message}"); } string response = "OK"; Log.Debug($"[ModBus] 设备 {DeviceId} 写入成功 (延迟 {_lastLatencyMs:F2}ms)"); return Task.FromResult(response); } catch (Exception ex) { _latencyStopwatch.Stop(); _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; _lastError = ex.Message; _lastErrorTime = DateTime.Now; Log.Error($"[ModBus] 设备 {DeviceId} 发送命令失败: {ex.Message}", ex); throw; } } /// public Task QueryAsync(string query, CancellationToken ct) { ThrowIfDisposed(); if (!IsConnected) throw new InvalidOperationException("[ModBus] 设备未连接"); _latencyStopwatch.Restart(); try { Log.Debug($"[ModBus] 设备 {DeviceId} 发送查询: {query}"); // 解析查询格式: "READ:address" string[] parts = query.Split(':'); if (parts.Length < 2) { throw new ArgumentException($"[ModBus] 查询格式错误,应为 READ:address,实际: {query}"); } string operation = parts[0].ToUpper(); string address = parts[1]; if (operation != "READ") { throw new ArgumentException($"[ModBus] 不支持的操作类型: {operation},应为 READ"); } // 尝试读取不同类型(根据地址范围自动判断) // 00001-09999: 线圈(BOOL) // 10001-19999: 离散输入(BOOL) // 30001-39999: 输入寄存器(INT16) // 40001-49999: 保持寄存器(INT16) string response; int addrNum; if (int.TryParse(address, out addrNum)) { if (addrNum >= 1 && addrNum <= 9999) { // 线圈读取 var result = _client.ReadBool(address); response = result.IsSuccess ? result.Content.ToString() : $"ERROR: {result.Message}"; } else if (addrNum >= 10001 && addrNum <= 19999) { // 离散输入读取 var result = _client.ReadBool(address); response = result.IsSuccess ? result.Content.ToString() : $"ERROR: {result.Message}"; } else if (addrNum >= 30001 && addrNum <= 39999) { // 输入寄存器读取 var result = _client.ReadInt16(address); response = result.IsSuccess ? result.Content.ToString() : $"ERROR: {result.Message}"; } else if (addrNum >= 40001 && addrNum <= 49999) { // 保持寄存器读取 var result = _client.ReadInt16(address); response = result.IsSuccess ? result.Content.ToString() : $"ERROR: {result.Message}"; } else { // 默认读取 INT16 var result = _client.ReadInt16(address); response = result.IsSuccess ? result.Content.ToString() : $"ERROR: {result.Message}"; } } else { // 非数字地址,尝试读取 INT16 var result = _client.ReadInt16(address); response = result.IsSuccess ? result.Content.ToString() : $"ERROR: {result.Message}"; } _latencyStopwatch.Stop(); _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; Log.Debug($"[ModBus] 设备 {DeviceId} 查询响应: {response} (延迟 {_lastLatencyMs:F2}ms)"); return Task.FromResult(response); } catch (Exception ex) { _latencyStopwatch.Stop(); _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; _lastError = ex.Message; _lastErrorTime = DateTime.Now; Log.Error($"[ModBus] 设备 {DeviceId} 查询失败: {ex.Message}", ex); throw; } } /// public DeviceDriverStatus GetStatus() { return new DeviceDriverStatus { IsConnected = IsConnected, IsHealthy = IsConnected && string.IsNullOrEmpty(_lastError), LastError = _lastError, LastErrorTime = _lastErrorTime, LatencyMs = _lastLatencyMs, ReconnectCount = _reconnectCount }; } /// public void Dispose() { if (_disposed) return; _disposed = true; try { if (_client != null) { _client.ConnectClose(); _client.Dispose(); } } catch (Exception ex) { Log.Error($"[ModBus] 设备 {DeviceId} 释放资源异常: {ex.Message}", ex); } } /// /// 触发连接状态变化事件 /// private void OnConnectionChanged(bool isConnected, string reason) { ConnectionChanged?.Invoke(this, new DeviceConnectionEventArgs { DeviceId = DeviceId, IsConnected = isConnected, Reason = reason }); if (!isConnected) { _reconnectCount++; } } /// /// 检查是否已释放 /// private void ThrowIfDisposed() { if (_disposed) throw new ObjectDisposedException(nameof(ModBusDriverAdapter)); } } }