From 9d1660270272180cd71147d6ddd53d1382643d2e Mon Sep 17 00:00:00 2001
From: wenxiangchao <wenxiangchao@ihengxin.com.cn>
Date: 星期一, 02 十一月 2026 18:00:14 +0800
Subject: [PATCH] merge: 并入远程 Initial commit 作为历史根节点
---
Aging.Infrastructure/Drivers/S7DriverAdapter.cs | 407 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 407 insertions(+), 0 deletions(-)
diff --git a/Aging.Infrastructure/Drivers/S7DriverAdapter.cs b/Aging.Infrastructure/Drivers/S7DriverAdapter.cs
new file mode 100644
index 0000000..b635e2d
--- /dev/null
+++ b/Aging.Infrastructure/Drivers/S7DriverAdapter.cs
@@ -0,0 +1,407 @@
+using System;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+using Aging.Infrastructure.Contracts;
+using log4net;
+using S7Driver;
+
+namespace Aging.Infrastructure.Drivers
+{
+ /// <summary>
+ /// 瑗块棬瀛� S7 PLC 椹卞姩閫傞厤鍣ㄣ��
+ /// 灏佽 S7Driver.PLCS7Driver锛屽疄鐜扮粺涓�鐨� IDeviceDriver 鎺ュ彛銆�
+ /// 鍛戒护鏍煎紡绾﹀畾:
+ /// 鍐欏叆: "WRITE:DBx.DBXy.z:value" 濡� "WRITE:DB1.DBX0.0:TRUE"
+ /// 璇诲彇: "READ:DBx.DBWy" 濡� "READ:DB1.DBD0"
+ /// 鏀寔鏁版嵁绫诲瀷: BOOL, INT, REAL
+ /// </summary>
+ 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;
+
+ /// <inheritdoc/>
+ public string DeviceId { get; }
+
+ /// <inheritdoc/>
+ public bool IsConnected => _driver != null;
+
+ /// <inheritdoc/>
+ public event EventHandler<DeviceConnectionEventArgs>? ConnectionChanged;
+
+ /// <summary>
+ /// 鍒濆鍖� S7 PLC 椹卞姩閫傞厤鍣�
+ /// </summary>
+ /// <param name="deviceId">璁惧鍞竴鏍囪瘑绗�</param>
+ /// <param name="ip">PLC IP 鍦板潃</param>
+ /// <param name="port">PLC 绔彛鍙凤紙閫氬父涓� 102锛�</param>
+ public S7DriverAdapter(string deviceId, string ip, int port)
+ {
+ DeviceId = deviceId ?? throw new ArgumentNullException(nameof(deviceId));
+ _ip = ip ?? throw new ArgumentNullException(nameof(ip));
+ _port = port;
+ }
+
+ /// <inheritdoc/>
+ public Task ConnectAsync(CancellationToken ct)
+ {
+ ThrowIfDisposed();
+ Log.Info($"[S7] 璁惧 {DeviceId} 姝e湪杩炴帴 {_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;
+ }
+
+ /// <inheritdoc/>
+ public Task DisconnectAsync()
+ {
+ ThrowIfDisposed();
+ Log.Info($"[S7] 璁惧 {DeviceId} 姝e湪鏂紑杩炴帴...");
+
+ 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;
+ }
+
+ /// <inheritdoc/>
+ public Task<string> SendCommandAsync(string command, CancellationToken ct)
+ {
+ ThrowIfDisposed();
+ if (!IsConnected) throw new InvalidOperationException("[S7] 璁惧鏈繛鎺�");
+
+ _latencyStopwatch.Restart();
+ try
+ {
+ Log.Debug($"[S7] 璁惧 {DeviceId} 鍙戦�佸懡浠�: {command}");
+
+ // 瑙f瀽鍛戒护鏍煎紡: "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");
+ }
+
+ // 瑙f瀽鍦板潃鍜屽�硷紝鎵ц鍐欏叆
+ 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;
+ }
+ }
+
+ /// <inheritdoc/>
+ public Task<string> QueryAsync(string query, CancellationToken ct)
+ {
+ ThrowIfDisposed();
+ if (!IsConnected) throw new InvalidOperationException("[S7] 璁惧鏈繛鎺�");
+
+ _latencyStopwatch.Restart();
+ try
+ {
+ Log.Debug($"[S7] 璁惧 {DeviceId} 鍙戦�佹煡璇�: {query}");
+
+ // 瑙f瀽鏌ヨ鏍煎紡: "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");
+ }
+
+ // 瑙f瀽鍦板潃骞舵墽琛岃鍙�
+ 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;
+ }
+ }
+
+ /// <inheritdoc/>
+ public DeviceDriverStatus GetStatus()
+ {
+ return new DeviceDriverStatus
+ {
+ IsConnected = IsConnected,
+ IsHealthy = IsConnected && string.IsNullOrEmpty(_lastError),
+ LastError = _lastError,
+ LastErrorTime = _lastErrorTime,
+ LatencyMs = _lastLatencyMs,
+ ReconnectCount = _reconnectCount
+ };
+ }
+
+ /// <inheritdoc/>
+ public void Dispose()
+ {
+ if (_disposed) return;
+ _disposed = true;
+
+ try
+ {
+ _driver?.Dispose();
+ }
+ catch (Exception ex)
+ {
+ Log.Error($"[S7] 璁惧 {DeviceId} 閲婃斁璧勬簮寮傚父: {ex.Message}", ex);
+ }
+ }
+
+ /// <summary>
+ /// 瑙f瀽鍦板潃骞舵墽琛屽啓鍏ユ搷浣�
+ /// </summary>
+ /// <param name="address">S7 鍦板潃锛屽 "DB1.DBX0.0" 鎴� "DB1.DBW10"</param>
+ /// <param name="value">瑕佸啓鍏ョ殑鍊�</param>
+ /// <param name="msg">閿欒淇℃伅</param>
+ /// <returns>鏄惁鎴愬姛</returns>
+ private bool ParseAndWrite(string address, string value, out string msg)
+ {
+ msg = null;
+
+ try
+ {
+ // 瑙f瀽鍦板潃鏍煎紡: 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"))
+ {
+ // 灏濊瘯瑙f瀽涓轰笉鍚岀被鍨�
+ 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;
+ }
+ }
+
+ /// <summary>
+ /// 瑙f瀽鍦板潃骞舵墽琛岃鍙栨搷浣�
+ /// </summary>
+ /// <param name="address">S7 鍦板潃锛屽 "DB1.DBX0.0" 鎴� "DB1.DBW10"</param>
+ /// <returns>璇诲彇鐨勫�煎瓧绗︿覆</returns>
+ 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}");
+ }
+ }
+
+ /// <summary>
+ /// 瑙f瀽 BOOL 鍦板潃鏍煎紡: DBx.DBXy.z
+ /// </summary>
+ 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);
+ }
+
+ /// <summary>
+ /// 瑙f瀽 Word 鍦板潃鏍煎紡: DBx.DBWy 鎴� DBx.DBDy
+ /// </summary>
+ 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);
+ }
+
+ /// <summary>
+ /// 瑙﹀彂杩炴帴鐘舵�佸彉鍖栦簨浠�
+ /// </summary>
+ private void OnConnectionChanged(bool isConnected, string reason)
+ {
+ ConnectionChanged?.Invoke(this, new DeviceConnectionEventArgs
+ {
+ DeviceId = DeviceId,
+ IsConnected = isConnected,
+ Reason = reason
+ });
+
+ if (!isConnected)
+ {
+ _reconnectCount++;
+ }
+ }
+
+ /// <summary>
+ /// 妫�鏌ユ槸鍚﹀凡閲婃斁
+ /// </summary>
+ private void ThrowIfDisposed()
+ {
+ if (_disposed) throw new ObjectDisposedException(nameof(S7DriverAdapter));
+ }
+ }
+}
--
Gitblit v1.9.1