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