using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Aging.Infrastructure.Contracts; using log4net; using S7Driver; namespace Aging.Infrastructure.Drivers { /// /// 西门子 S7 PLC 驱动适配器。 /// 封装 S7Driver.PLCS7Driver,实现统一的 IDeviceDriver 接口。 /// 命令格式约定: /// 写入: "WRITE:DBx.DBXy.z:value" 如 "WRITE:DB1.DBX0.0:TRUE" /// 读取: "READ:DBx.DBWy" 如 "READ:DB1.DBD0" /// 支持数据类型: BOOL, INT, REAL /// public sealed class S7DriverAdapter : IDeviceDriver { private static readonly ILog Log = LogManager.GetLogger(typeof(S7DriverAdapter)); private readonly string _ip; private readonly int _port; private PLCS7Driver? _driver; 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 => _driver != null; /// public event EventHandler? ConnectionChanged; /// /// 初始化 S7 PLC 驱动适配器 /// /// 设备唯一标识符 /// PLC IP 地址 /// PLC 端口号(通常为 102) public S7DriverAdapter(string deviceId, string ip, int port) { DeviceId = deviceId ?? throw new ArgumentNullException(nameof(deviceId)); _ip = ip ?? throw new ArgumentNullException(nameof(ip)); _port = port; } /// public Task ConnectAsync(CancellationToken ct) { ThrowIfDisposed(); Log.Info($"[S7] 设备 {DeviceId} 正在连接 {_ip}:{_port}..."); try { _driver = new PLCS7Driver(_ip, _port); bool success = _driver.Connect(out string errMsg); if (!success) { _lastError = errMsg; _lastErrorTime = DateTime.Now; throw new InvalidOperationException($"[S7] 设备 {DeviceId} 连接失败: {errMsg}"); } _lastError = string.Empty; Log.Info($"[S7] 设备 {DeviceId} 已成功连接到 {_ip}:{_port}"); OnConnectionChanged(true, "连接成功"); } catch (Exception ex) { _lastError = ex.Message; _lastErrorTime = DateTime.Now; Log.Error($"[S7] 设备 {DeviceId} 连接异常: {ex.Message}", ex); _driver?.Dispose(); _driver = null; throw; } return Task.CompletedTask; } /// public Task DisconnectAsync() { ThrowIfDisposed(); Log.Info($"[S7] 设备 {DeviceId} 正在断开连接..."); try { if (_driver != null) { _driver.DisConnect(out string errMsg); _driver.Dispose(); _driver = null; } Log.Info($"[S7] 设备 {DeviceId} 已断开连接"); OnConnectionChanged(false, "主动断开"); } catch (Exception ex) { _lastError = ex.Message; _lastErrorTime = DateTime.Now; Log.Error($"[S7] 设备 {DeviceId} 断开连接异常: {ex.Message}", ex); _driver?.Dispose(); _driver = null; } return Task.CompletedTask; } /// public Task SendCommandAsync(string command, CancellationToken ct) { ThrowIfDisposed(); if (!IsConnected) throw new InvalidOperationException("[S7] 设备未连接"); _latencyStopwatch.Restart(); try { Log.Debug($"[S7] 设备 {DeviceId} 发送命令: {command}"); // 解析命令格式: "WRITE:DBx.DBXy.z:value" 或 "WRITE:DBx.DBWy:value" string[] parts = command.Split(':'); if (parts.Length < 3) { throw new ArgumentException($"[S7] 命令格式错误,应为 WRITE:address:value,实际: {command}"); } string operation = parts[0].ToUpper(); string address = parts[1]; string value = parts[2]; if (operation != "WRITE") { throw new ArgumentException($"[S7] 不支持的操作类型: {operation},应为 WRITE"); } // 解析地址和值,执行写入 bool success = ParseAndWrite(address, value, out string msg); _latencyStopwatch.Stop(); _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; if (!success) { _lastError = msg; _lastErrorTime = DateTime.Now; throw new InvalidOperationException($"[S7] 写入失败: {msg}"); } string response = "OK"; Log.Debug($"[S7] 设备 {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($"[S7] 设备 {DeviceId} 发送命令失败: {ex.Message}", ex); throw; } } /// public Task QueryAsync(string query, CancellationToken ct) { ThrowIfDisposed(); if (!IsConnected) throw new InvalidOperationException("[S7] 设备未连接"); _latencyStopwatch.Restart(); try { Log.Debug($"[S7] 设备 {DeviceId} 发送查询: {query}"); // 解析查询格式: "READ:DBx.DBWy" 或 "READ:DBx.DBXy.z" string[] parts = query.Split(':'); if (parts.Length < 2) { throw new ArgumentException($"[S7] 查询格式错误,应为 READ:address,实际: {query}"); } string operation = parts[0].ToUpper(); string address = parts[1]; if (operation != "READ") { throw new ArgumentException($"[S7] 不支持的操作类型: {operation},应为 READ"); } // 解析地址并执行读取 string response = ParseAndRead(address); _latencyStopwatch.Stop(); _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; Log.Debug($"[S7] 设备 {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($"[S7] 设备 {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 { _driver?.Dispose(); } catch (Exception ex) { Log.Error($"[S7] 设备 {DeviceId} 释放资源异常: {ex.Message}", ex); } } /// /// 解析地址并执行写入操作 /// /// S7 地址,如 "DB1.DBX0.0" 或 "DB1.DBW10" /// 要写入的值 /// 错误信息 /// 是否成功 private bool ParseAndWrite(string address, string value, out string msg) { msg = null; try { // 解析地址格式: DBx.DBXy.z (BOOL) 或 DBx.DBWy (INT/REAL) if (address.Contains(".DBX")) { // BOOL 类型: DB1.DBX0.0 if (bool.TryParse(value, out bool boolVal)) { var addrParts = ParseBoolAddress(address); return _driver.Simens_S7_WriteBool(addrParts.db, addrParts.startAddr, addrParts.pos, boolVal, out msg); } throw new ArgumentException($"[S7] BOOL 值解析失败: {value}"); } else if (address.Contains(".DBW")) { // 尝试解析为不同类型 if (float.TryParse(value, out float realVal)) { var addrParts = ParseWordAddress(address); return _driver.Simens_S7_WriteReal(addrParts.db, addrParts.startAddr, realVal, out msg); } else if (short.TryParse(value, out short shortVal)) { var addrParts = ParseWordAddress(address); return _driver.Simens_S7_WriteInt(addrParts.db, addrParts.startAddr, shortVal, out msg); } throw new ArgumentException($"[S7] 值解析失败: {value}"); } else { msg = $"[S7] 不支持的地址格式: {address}"; return false; } } catch (Exception ex) { msg = ex.Message; return false; } } /// /// 解析地址并执行读取操作 /// /// S7 地址,如 "DB1.DBX0.0" 或 "DB1.DBW10" /// 读取的值字符串 private string ParseAndRead(string address) { if (address.Contains(".DBX")) { // BOOL 类型读取 var addrParts = ParseBoolAddress(address); if (_driver.Simens_S7_ReadBool(addrParts.db, addrParts.startAddr, addrParts.pos, out bool value, out string msg)) { return value.ToString(); } throw new InvalidOperationException($"[S7] 读取失败: {msg}"); } else if (address.Contains(".DBD")) { // REAL 类型读取 (DBD = Double Word) var addrParts = ParseWordAddress(address); if (_driver.Simens_S7_ReadReal(addrParts.db, addrParts.startAddr, out float realValue, out string msg)) { return realValue.ToString(); } throw new InvalidOperationException($"[S7] 读取失败: {msg}"); } else if (address.Contains(".DBW")) { // INT 类型读取 var addrParts = ParseWordAddress(address); if (_driver.Simens_S7_ReadInt(addrParts.db, addrParts.startAddr, out int value, out string msg)) { return value.ToString(); } throw new InvalidOperationException($"[S7] 读取失败: {msg}"); } else { throw new ArgumentException($"[S7] 不支持的地址格式: {address}"); } } /// /// 解析 BOOL 地址格式: DBx.DBXy.z /// private (int db, int startAddr, int pos) ParseBoolAddress(string address) { // 格式: DB1.DBX0.0 string upper = address.ToUpper(); int dbStart = upper.IndexOf("DB") + 2; int dbxStart = upper.IndexOf(".DBX"); int db = int.Parse(upper.Substring(dbStart, dbxStart - dbStart)); int posStart = dbxStart + 5; // .DBX 后 int dotPos = address.IndexOf('.', posStart); int startAddr = int.Parse(address.Substring(posStart, dotPos - posStart)); int pos = int.Parse(address.Substring(dotPos + 1)); return (db, startAddr, pos); } /// /// 解析 Word 地址格式: DBx.DBWy 或 DBx.DBDy /// private (int db, int startAddr) ParseWordAddress(string address) { // 格式: DB1.DBW10 或 DB1.DBD0 string upper = address.ToUpper(); int dbStart = upper.IndexOf("DB") + 2; int dbwStart = upper.Contains(".DBW") ? upper.IndexOf(".DBW") : upper.IndexOf(".DBD"); int db = int.Parse(upper.Substring(dbStart, dbwStart - dbStart)); int addrStart = dbwStart + 5; // .DBW 或 .DBD 后 int startAddr = int.Parse(address.Substring(addrStart)); return (db, startAddr); } /// /// 触发连接状态变化事件 /// 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(S7DriverAdapter)); } } }