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/CanDriverAdapter.cs |  576 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 576 insertions(+), 0 deletions(-)

diff --git a/Aging.Infrastructure/Drivers/CanDriverAdapter.cs b/Aging.Infrastructure/Drivers/CanDriverAdapter.cs
new file mode 100644
index 0000000..1a96682
--- /dev/null
+++ b/Aging.Infrastructure/Drivers/CanDriverAdapter.cs
@@ -0,0 +1,576 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Aging.Infrastructure.Contracts;
+using CanSimulator;
+using log4net;
+
+namespace Aging.Infrastructure.Drivers
+{
+    /// <summary>
+    /// CAN 閫氳椹卞姩閫傞厤鍣ㄣ��
+    /// 灏佽 CanSimulator锛屽疄鐜扮粺涓�鐨� IDeviceDriver 鎺ュ彛銆�
+    /// 鍛戒护鏍煎紡绾﹀畾:
+    ///   鍙戦��: "SEND:frameId:data"  濡� "SEND:0x100:01 02 03 04"
+    ///   鏌ヨ:
+    ///     "RECV"           - 鑾峰彇鎺ユ敹閭涓殑涓嬩竴甯�
+    ///     "RECV_ALL"       - 鑾峰彇鎺ユ敹閭涓殑鎵�鏈夊抚
+    ///     "STATUS"         - 鑾峰彇鑺傜偣/鎬荤嚎鐘舵��
+    ///     "FILTER:id:mask" - 璁剧疆鎺ユ敹婊ゆ尝鍣�
+    ///     "FILTER_CLEAR"   - 娓呴櫎鎵�鏈夋护娉㈠櫒
+    ///     "PROMISCUOUS"    - 璁剧疆娣锋潅妯″紡锛堟帴鏀舵墍鏈夊抚锛�
+    ///     "LOG"            - 鑾峰彇鎬荤嚎鏃ュ織
+    ///     "BUS_STATUS"     - 鑾峰彇鎬荤嚎鐘舵�佹憳瑕�
+    /// </summary>
+    public sealed class CanDriverAdapter : IDeviceDriver
+    {
+        private static readonly ILog Log = LogManager.GetLogger(typeof(CanDriverAdapter));
+
+        /// <summary>
+        /// 鍏ㄥ眬 CAN 鎬荤嚎妯℃嫙鍣ㄥ疄渚嬶紙鎵�鏈� CAN 椹卞姩鍏变韩锛�
+        /// </summary>
+        private static readonly Dictionary<string, CanBusSimulator> _sharedBuses = new();
+        private static readonly object _busLock = new();
+
+        private readonly string _ip;      // 鎬荤嚎鍚嶇О锛堢敤 ip 瀛楁鏄犲皠锛�
+        private readonly int _port;       // 鑺傜偣缂栧彿锛堢敤 port 瀛楁鏄犲皠锛�
+        private CanBusSimulator? _bus;
+        private CanNode? _node;
+        private bool _disposed;
+        private readonly Stopwatch _latencyStopwatch = new();
+        private double _lastLatencyMs;
+        private string _lastError = string.Empty;
+        private DateTime? _lastErrorTime;
+        private int _reconnectCount;
+
+        /// <summary>
+        /// 鎺ユ敹鍒扮殑甯х紦瀛橈紙鐢ㄤ簬 RECV 鏌ヨ锛�
+        /// </summary>
+        private readonly Queue<CanFrame> _recvBuffer = new();
+
+        /// <inheritdoc/>
+        public string DeviceId { get; }
+
+        /// <inheritdoc/>
+        public bool IsConnected => _node != null && _node.IsActive;
+
+        /// <inheritdoc/>
+        public event EventHandler<DeviceConnectionEventArgs>? ConnectionChanged;
+
+        /// <summary>
+        /// 鍒濆鍖� CAN 椹卞姩閫傞厤鍣�
+        /// </summary>
+        /// <param name="deviceId">璁惧鍞竴鏍囪瘑绗�</param>
+        /// <param name="busName">鎬荤嚎鍚嶇О锛堝 CAN_BUS_1锛�</param>
+        /// <param name="nodeIndex">鑺傜偣缂栧彿锛堢敤浜庡尯鍒嗗悓涓�鎬荤嚎涓婄殑涓嶅悓璁惧锛�</param>
+        public CanDriverAdapter(string deviceId, string busName, int nodeIndex)
+        {
+            DeviceId = deviceId ?? throw new ArgumentNullException(nameof(deviceId));
+            _ip = busName ?? "CAN_BUS_1";
+            _port = nodeIndex;
+        }
+
+        /// <summary>
+        /// 鑾峰彇鎴栧垱寤哄叡浜�荤嚎
+        /// </summary>
+        private CanBusSimulator GetOrCreateBus(string busName)
+        {
+            lock (_busLock)
+            {
+                if (!_sharedBuses.TryGetValue(busName, out var bus))
+                {
+                    bus = new CanBusSimulator(busName);
+                    _sharedBuses[busName] = bus;
+                    Log.Info($"[CAN] 鍒涘缓鏂扮殑铏氭嫙鎬荤嚎: {busName}");
+                }
+                return bus;
+            }
+        }
+
+        /// <inheritdoc/>
+        public Task ConnectAsync(CancellationToken ct)
+        {
+            ThrowIfDisposed();
+            Log.Info($"[CAN] 璁惧 {DeviceId} 姝e湪杩炴帴鍒版�荤嚎 {_ip} (鑺傜偣: {_port})...");
+
+            try
+            {
+                // 鑾峰彇鎴栧垱寤哄叡浜�荤嚎
+                _bus = GetOrCreateBus(_ip);
+
+                // 濡傛灉鎬荤嚎鏈惎鍔紝鑷姩鍚姩
+                if (_bus.State != CanBusState.Running)
+                {
+                    _bus.Start();
+                }
+
+                // 娉ㄥ唽鑺傜偣
+                string nodeId = $"{DeviceId}_node{_port}";
+                _node = _bus.RegisterNode(nodeId, DeviceId);
+
+                // 璁剧疆榛樿婊ゆ尝鍣紙鎺ユ敹鎵�鏈夊抚锛�
+                _node.SetPromiscuousMode();
+
+                // 婵�娲昏妭鐐�
+                _node.Activate();
+
+                // 璁㈤槄鎺ユ敹浜嬩欢
+                _node.FrameReceived += OnFrameReceived;
+
+                _lastError = string.Empty;
+                Log.Info($"[CAN] 璁惧 {DeviceId} 宸叉垚鍔熻繛鎺ュ埌鎬荤嚎 {_ip} (鑺傜偣: {_node.NodeId})");
+                OnConnectionChanged(true, "杩炴帴鎴愬姛");
+            }
+            catch (Exception ex)
+            {
+                _lastError = ex.Message;
+                _lastErrorTime = DateTime.Now;
+                Log.Error($"[CAN] 璁惧 {DeviceId} 杩炴帴寮傚父: {ex.Message}", ex);
+                _node?.Deactivate();
+                _node = null;
+                throw;
+            }
+
+            return Task.CompletedTask;
+        }
+
+        /// <inheritdoc/>
+        public Task DisconnectAsync()
+        {
+            ThrowIfDisposed();
+            Log.Info($"[CAN] 璁惧 {DeviceId} 姝e湪鏂紑杩炴帴...");
+
+            try
+            {
+                if (_node != null)
+                {
+                    _node.FrameReceived -= OnFrameReceived;
+                    _node.Deactivate();
+                    _bus?.UnregisterNode(_node.NodeId);
+                    _node = null;
+                }
+
+                Log.Info($"[CAN] 璁惧 {DeviceId} 宸叉柇寮�杩炴帴");
+                OnConnectionChanged(false, "涓诲姩鏂紑");
+            }
+            catch (Exception ex)
+            {
+                _lastError = ex.Message;
+                _lastErrorTime = DateTime.Now;
+                Log.Error($"[CAN] 璁惧 {DeviceId} 鏂紑杩炴帴寮傚父: {ex.Message}", ex);
+                _node = null;
+            }
+
+            return Task.CompletedTask;
+        }
+
+        /// <inheritdoc/>
+        public Task<string> SendCommandAsync(string command, CancellationToken ct)
+        {
+            ThrowIfDisposed();
+            if (!IsConnected) throw new InvalidOperationException("[CAN] 璁惧鏈繛鎺�");
+
+            _latencyStopwatch.Restart();
+            try
+            {
+                Log.Debug($"[CAN] 璁惧 {DeviceId} 鍙戦�佸懡浠�: {command}");
+
+                // 瑙f瀽鍛戒护鏍煎紡: "SEND:frameId:data"
+                string[] parts = command.Split(':');
+                if (parts.Length < 3)
+                {
+                    throw new ArgumentException($"[CAN] 鍛戒护鏍煎紡閿欒锛屽簲涓� SEND:frameId:data锛屽疄闄�: {command}");
+                }
+
+                string operation = parts[0].ToUpper();
+                if (operation != "SEND")
+                {
+                    throw new ArgumentException($"[CAN] 涓嶆敮鎸佺殑鎿嶄綔绫诲瀷: {operation}锛屽簲涓� SEND");
+                }
+
+                // 瑙f瀽甯� ID
+                string frameIdStr = parts[1];
+                uint frameId = CanFrame.ParseFrameId(frameIdStr);
+
+                // 瑙f瀽鏁版嵁
+                string dataStr = parts[2];
+                byte[] data = CanFrame.ParseData(dataStr);
+
+                // 楠岃瘉鏁版嵁闀垮害
+                if (data.Length > 8)
+                {
+                    throw new ArgumentException($"[CAN] 鏁版嵁闀垮害瓒呴檺: {data.Length} 瀛楄妭锛堟渶澶� 8 瀛楄妭锛�");
+                }
+
+                // 鍒涘缓骞跺彂閫佸抚
+                var frame = new CanFrame
+                {
+                    FrameId = frameId,
+                    Format = frameId > 0x7FF ? CanFrameFormat.Extended : CanFrameFormat.Standard,
+                    Type = CanFrameType.Data,
+                    Data = data
+                };
+
+                _node!.SendFrame(frame);
+
+                _latencyStopwatch.Stop();
+                _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds;
+
+                string response = $"OK:TX:{frame.FrameIdHex}:{frame.Dlc}";
+                Log.Debug($"[CAN] 璁惧 {DeviceId} 鍙戦�佹垚鍔� (寤惰繜 {_lastLatencyMs:F2}ms): {response}");
+                return Task.FromResult(response);
+            }
+            catch (Exception ex)
+            {
+                _latencyStopwatch.Stop();
+                _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds;
+                _lastError = ex.Message;
+                _lastErrorTime = DateTime.Now;
+                Log.Error($"[CAN] 璁惧 {DeviceId} 鍙戦�佸懡浠ゅけ璐�: {ex.Message}", ex);
+                throw;
+            }
+        }
+
+        /// <inheritdoc/>
+        public Task<string> QueryAsync(string query, CancellationToken ct)
+        {
+            ThrowIfDisposed();
+            if (!IsConnected) throw new InvalidOperationException("[CAN] 璁惧鏈繛鎺�");
+
+            _latencyStopwatch.Restart();
+            try
+            {
+                Log.Debug($"[CAN] 璁惧 {DeviceId} 鏌ヨ: {query}");
+
+                string[] parts = query.Split(':');
+                string operation = parts[0].ToUpper();
+
+                string response;
+                switch (operation)
+                {
+                    case "RECV":
+                        response = HandleRecv();
+                        break;
+
+                    case "RECV_ALL":
+                        response = HandleRecvAll();
+                        break;
+
+                    case "STATUS":
+                        response = HandleStatus();
+                        break;
+
+                    case "FILTER":
+                        response = HandleFilter(parts);
+                        break;
+
+                    case "FILTER_CLEAR":
+                        _node!.ClearFilters();
+                        response = "OK:婊ゆ尝鍣ㄥ凡娓呴櫎";
+                        break;
+
+                    case "PROMISCUOUS":
+                        _node!.SetPromiscuousMode();
+                        response = "OK:娣锋潅妯″紡宸插惎鐢�";
+                        break;
+
+                    case "AUTO_RESPONSE":
+                        response = HandleAutoResponse(parts);
+                        break;
+
+                    case "RESPONSE_DELAY":
+                        response = HandleResponseDelay(parts);
+                        break;
+
+                    case "LOG":
+                        response = HandleLog();
+                        break;
+
+                    case "BUS_STATUS":
+                        response = _bus?.GetStatusSummary() ?? "鎬荤嚎鏈垵濮嬪寲";
+                        break;
+
+                    default:
+                        throw new ArgumentException($"[CAN] 涓嶆敮鎸佺殑鏌ヨ绫诲瀷: {operation}");
+                }
+
+                _latencyStopwatch.Stop();
+                _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds;
+                Log.Debug($"[CAN] 璁惧 {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($"[CAN] 璁惧 {DeviceId} 鏌ヨ澶辫触: {ex.Message}", ex);
+                throw;
+            }
+        }
+
+        /// <summary>
+        /// 澶勭悊 RECV 鏌ヨ - 鑾峰彇涓嬩竴甯�
+        /// </summary>
+        private string HandleRecv()
+        {
+            // 鍏堜粠缂撳啿鍖哄彇
+            if (_recvBuffer.Count > 0)
+            {
+                var frame = _recvBuffer.Dequeue();
+                return $"RECV:{frame.FrameIdHex}:{frame.DataHex}:{frame.Timestamp:HH:mm:ss.fff}";
+            }
+
+            // 浠庤妭鐐归偖绠卞彇
+            if (_node!.TryDequeueReceived(out var frame2))
+            {
+                return $"RECV:{frame2!.FrameIdHex}:{frame2.DataHex}:{frame2.Timestamp:HH:mm:ss.fff}";
+            }
+
+            return "RECV:EMPTY";
+        }
+
+        /// <summary>
+        /// 澶勭悊 RECV_ALL 鏌ヨ - 鑾峰彇鎵�鏈夊抚
+        /// </summary>
+        private string HandleRecvAll()
+        {
+            var frames = new List<string>();
+
+            // 鍏堝彇缂撳啿鍖�
+            while (_recvBuffer.Count > 0)
+            {
+                var f = _recvBuffer.Dequeue();
+                frames.Add($"{f.FrameIdHex}:{f.DataHex}");
+            }
+
+            // 鍐嶅彇鑺傜偣閭
+            var mailbox = _node!.PeekReceiveMailbox();
+            _node.ClearReceiveMailbox();
+            foreach (var f in mailbox)
+            {
+                frames.Add($"{f.FrameIdHex}:{f.DataHex}:{f.Timestamp:HH:mm:ss.fff}");
+            }
+
+            return frames.Count > 0 ? $"RECV_ALL:{frames.Count}:{string.Join("|", frames)}" : "RECV_ALL:0";
+        }
+
+        /// <summary>
+        /// 澶勭悊 STATUS 鏌ヨ
+        /// </summary>
+        private string HandleStatus()
+        {
+            var nodeStatus = _node?.GetStatusSummary() ?? "鑺傜偣鏈垵濮嬪寲";
+            var busStatus = _bus?.GetStatusSummary() ?? "鎬荤嚎鏈垵濮嬪寲";
+            return $"STATUS:{nodeStatus}|{busStatus}";
+        }
+
+        /// <summary>
+        /// 澶勭悊 FILTER 璁剧疆
+        /// </summary>
+        private string HandleFilter(string[] parts)
+        {
+            if (parts.Length < 3)
+            {
+                throw new ArgumentException("[CAN] FILTER 鍛戒护鏍煎紡閿欒锛屽簲涓� FILTER:id:mask");
+            }
+
+            uint filterId = CanFrame.ParseFrameId(parts[1]);
+            uint mask = CanFrame.ParseFrameId(parts[2]);
+
+            _node!.AddFilter(new AcceptanceFilter
+            {
+                FilterId = filterId,
+                Mask = mask,
+                IsExtended = filterId > 0x7FF
+            });
+
+            return $"OK:婊ゆ尝鍣ㄥ凡娣诲姞 ID=0x{filterId:X3}, Mask=0x{mask:X3}";
+        }
+
+        /// <summary>
+        /// 澶勭悊 AUTO_RESPONSE 鍛戒护
+        /// 鏍煎紡: AUTO_RESPONSE:on|off
+        /// </summary>
+        private string HandleAutoResponse(string[] parts)
+        {
+            if (parts.Length < 2)
+            {
+                // 鏌ヨ褰撳墠鐘舵��
+                bool current = _node!.AutoResponseEnabled;
+                return $"AUTO_RESPONSE:{(current ? "ON" : "OFF")}";
+            }
+
+            string mode = parts[1].Trim().ToUpper();
+            switch (mode)
+            {
+                case "ON":
+                case "1":
+                case "TRUE":
+                case "ENABLE":
+                    _node!.AutoResponseEnabled = true;
+                    Log.Info($"[CAN] 璁惧 {DeviceId} 鍚敤鑷姩鍝嶅簲妯″紡");
+                    return "OK:鑷姩鍝嶅簲妯″紡宸插惎鐢�";
+
+                case "OFF":
+                case "0":
+                case "FALSE":
+                case "DISABLE":
+                    _node!.AutoResponseEnabled = false;
+                    Log.Info($"[CAN] 璁惧 {DeviceId} 绂佺敤鑷姩鍝嶅簲妯″紡");
+                    return "OK:鑷姩鍝嶅簲妯″紡宸茬鐢�";
+
+                default:
+                    throw new ArgumentException($"[CAN] AUTO_RESPONSE 鍙傛暟閿欒: {mode}锛屽簲涓� on/off");
+            }
+        }
+
+        /// <summary>
+        /// 澶勭悊 RESPONSE_DELAY 鍛戒护
+        /// 鏍煎紡: RESPONSE_DELAY:姣鏁�
+        /// </summary>
+        private string HandleResponseDelay(string[] parts)
+        {
+            if (parts.Length < 2)
+            {
+                // 鏌ヨ褰撳墠寤惰繜
+                return $"RESPONSE_DELAY:{_node!.AutoResponseDelayMs}ms";
+            }
+
+            if (!int.TryParse(parts[1].Trim(), out int delayMs) || delayMs < 0)
+            {
+                throw new ArgumentException($"[CAN] RESPONSE_DELAY 鍙傛暟閿欒: {parts[1]}锛屽簲涓洪潪璐熸暣鏁帮紙姣锛�");
+            }
+
+            _node!.AutoResponseDelayMs = delayMs;
+            Log.Info($"[CAN] 璁惧 {DeviceId} 璁剧疆鍝嶅簲寤惰繜: {delayMs}ms");
+            return $"OK:鍝嶅簲寤惰繜宸茶缃负 {delayMs}ms";
+        }
+
+        /// <summary>
+        /// 澶勭悊 LOG 鏌ヨ
+        /// </summary>
+        private string HandleLog()
+        {
+            var log = _bus?.GetBusLog(50) ?? new List<CanFrame>();
+            if (log.Count == 0) return "LOG:EMPTY";
+
+            var entries = log.Select(f =>
+                $"[{f.Timestamp:HH:mm:ss.fff}]{f.SourceNodeId}>{f.FrameIdHex}:{f.DataHex}");
+            return $"LOG:{log.Count}:{string.Join("|", entries)}";
+        }
+
+        /// <summary>
+        /// 甯ф帴鏀朵簨浠讹紙鍏紑缁欏閮ㄨ闃咃級
+        /// </summary>
+        public event EventHandler<CanFrame>? DriverFrameReceived;
+
+        /// <summary>
+        /// 甯ф帴鏀朵簨浠跺鐞�
+        /// </summary>
+        private void OnFrameReceived(object? sender, CanFrame frame)
+        {
+            // 鏀惧叆鎺ユ敹缂撳啿鍖猴紙鐢ㄤ簬 RECV/RECV_ALL 鏌ヨ锛�
+            while (_recvBuffer.Count >= 1000)
+            {
+                _recvBuffer.Dequeue();
+            }
+            _recvBuffer.Enqueue(frame);
+
+            // 瑙﹀彂鍏紑浜嬩欢锛堢敤浜庡嵆鏃舵樉绀猴級
+            DriverFrameReceived?.Invoke(this, frame);
+        }
+
+        /// <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
+            {
+                if (_node != null)
+                {
+                    _node.FrameReceived -= OnFrameReceived;
+                    _node.Deactivate();
+                    _bus?.UnregisterNode(_node.NodeId);
+                }
+            }
+            catch (Exception ex)
+            {
+                Log.Error($"[CAN] 璁惧 {DeviceId} 閲婃斁璧勬簮寮傚父: {ex.Message}", ex);
+            }
+        }
+
+        /// <summary>
+        /// 瑙﹀彂鎵�鏈夋�荤嚎鍜岃妭鐐癸紙鐢ㄤ簬娴嬭瘯娓呯悊锛�
+        /// </summary>
+        public static void ResetAllBuses()
+        {
+            lock (_busLock)
+            {
+                foreach (var bus in _sharedBuses.Values)
+                {
+                    bus.Dispose();
+                }
+                _sharedBuses.Clear();
+                Log.Info("[CAN] 鎵�鏈夎櫄鎷熸�荤嚎宸查噸缃�");
+            }
+        }
+
+        /// <summary>
+        /// 瑙﹀彂鎵�鏈夋�荤嚎瀹炰緥锛堜緵澶栭儴鏌ヨ锛�
+        /// </summary>
+        public static IReadOnlyDictionary<string, CanBusSimulator> GetAllBuses()
+        {
+            lock (_busLock)
+            {
+                return new Dictionary<string, CanBusSimulator>(_sharedBuses);
+            }
+        }
+
+        /// <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(CanDriverAdapter));
+        }
+    }
+}

--
Gitblit v1.9.1