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