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 作为历史根节点

---
 CanSimulator/CanBusSimulator.cs |  403 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 403 insertions(+), 0 deletions(-)

diff --git a/CanSimulator/CanBusSimulator.cs b/CanSimulator/CanBusSimulator.cs
new file mode 100644
index 0000000..3497aac
--- /dev/null
+++ b/CanSimulator/CanBusSimulator.cs
@@ -0,0 +1,403 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using log4net;
+
+namespace CanSimulator
+{
+    /// <summary>
+    /// CAN 鎬荤嚎鐘舵��
+    /// </summary>
+    public enum CanBusState
+    {
+        /// <summary>鍋滄鐘舵��</summary>
+        Stopped,
+        /// <summary>杩愯涓�</summary>
+        Running,
+        /// <summary>鏆傚仠</summary>
+        Paused,
+        /// <summary>閿欒</summary>
+        Error
+    }
+
+    /// <summary>
+    /// CAN 鎬荤嚎妯℃嫙鍣ㄤ簨浠跺弬鏁�
+    /// </summary>
+    public sealed class CanBusFrameEventArgs : EventArgs
+    {
+        public CanFrame Frame { get; }
+        public string SourceNodeId { get; }
+
+        public CanBusFrameEventArgs(CanFrame frame, string sourceNodeId)
+        {
+            Frame = frame;
+            SourceNodeId = sourceNodeId;
+        }
+    }
+
+    /// <summary>
+    /// CAN 鎬荤嚎妯℃嫙鍣�
+    /// 妯℃嫙涓�鏉¤櫄鎷� CAN 鎬荤嚎锛屽涓櫄鎷熻妭鐐瑰彲浠ユ寕杞藉埌鎬荤嚎涓婅繘琛岄�氳
+    /// </summary>
+    public sealed class CanBusSimulator : IDisposable
+    {
+        private static readonly ILog Log = LogManager.GetLogger(typeof(CanBusSimulator));
+
+        /// <summary>
+        /// 鎬荤嚎鍚嶇О
+        /// </summary>
+        public string BusName { get; }
+
+        /// <summary>
+        /// 鎬荤嚎鐘舵��
+        /// </summary>
+        public CanBusState State { get; private set; } = CanBusState.Stopped;
+
+        /// <summary>
+        /// 娉㈢壒鐜囷紙浠呯敤浜庢ā鎷熸樉绀猴紝涓嶅奖鍝嶅疄闄呴�昏緫锛�
+        /// </summary>
+        public int BaudRate { get; set; } = 500000; // 榛樿 500kbps
+
+        /// <summary>
+        /// 妯℃嫙寤惰繜锛堟绉掞級- 妯℃嫙鐪熷疄鎬荤嚎浼犺緭寤惰繜
+        /// </summary>
+        public int SimulatedLatencyMs { get; set; } = 0;
+
+        /// <summary>
+        /// 宸叉敞鍐岀殑鑺傜偣
+        /// </summary>
+        private readonly ConcurrentDictionary<string, CanNode> _nodes = new();
+
+        /// <summary>
+        /// 鎬荤嚎鏃ュ織锛堟渶杩� N 鏉″抚锛�
+        /// </summary>
+        private readonly ConcurrentQueue<CanFrame> _busLog = new();
+        private int _maxLogSize = 5000;
+
+        /// <summary>
+        /// 鎬荤嚎缁熻
+        /// </summary>
+        public long TotalFramesTransmitted { get; private set; }
+        public long TotalFramesReceived { get; private set; }
+        public DateTime? LastActivityTime { get; private set; }
+
+        /// <summary>
+        /// 甯у湪鎬荤嚎涓婁紶杈撲簨浠讹紙鍙敤浜庣洃鎺�/鏃ュ織锛�
+        /// </summary>
+        public event EventHandler<CanBusFrameEventArgs>? FrameTransmitted;
+
+        /// <summary>
+        /// 鎬荤嚎鐘舵�佸彉鍖栦簨浠�
+        /// </summary>
+        public event EventHandler<CanBusState>? StateChanged;
+
+        public CanBusSimulator(string busName = "CAN_BUS_1")
+        {
+            BusName = busName ?? throw new ArgumentNullException(nameof(busName));
+        }
+
+        /// <summary>
+        /// 鍚姩鎬荤嚎
+        /// </summary>
+        public void Start()
+        {
+            if (State == CanBusState.Running)
+            {
+                Log.Warn($"[CanBus] 鎬荤嚎 {BusName} 宸插湪杩愯涓�");
+                return;
+            }
+
+            State = CanBusState.Running;
+            Log.Info($"[CanBus] 鎬荤嚎 {BusName} 宸插惎鍔� (娉㈢壒鐜�: {BaudRate}bps)");
+            StateChanged?.Invoke(this, State);
+        }
+
+        /// <summary>
+        /// 鍋滄鎬荤嚎
+        /// </summary>
+        public void Stop()
+        {
+            if (State == CanBusState.Stopped) return;
+
+            State = CanBusState.Stopped;
+            Log.Info($"[CanBus] 鎬荤嚎 {BusName} 宸插仠姝�");
+            StateChanged?.Invoke(this, State);
+        }
+
+        /// <summary>
+        /// 鏆傚仠鎬荤嚎锛堝抚浠嶅湪淇濈暀浣嗕笉璺敱锛�
+        /// </summary>
+        public void Pause()
+        {
+            if (State != CanBusState.Running) return;
+
+            State = CanBusState.Paused;
+            Log.Info($"[CanBus] 鎬荤嚎 {BusName} 宸叉殏鍋�");
+            StateChanged?.Invoke(this, State);
+        }
+
+        /// <summary>
+        /// 鎭㈠鎬荤嚎
+        /// </summary>
+        public void Resume()
+        {
+            if (State != CanBusState.Paused) return;
+
+            State = CanBusState.Running;
+            Log.Info($"[CanBus] 鎬荤嚎 {BusName} 宸叉仮澶�");
+            StateChanged?.Invoke(this, State);
+        }
+
+        /// <summary>
+        /// 娉ㄥ唽鑺傜偣鍒版�荤嚎
+        /// </summary>
+        public CanNode RegisterNode(string nodeId, string? name = null)
+        {
+            if (string.IsNullOrWhiteSpace(nodeId))
+                throw new ArgumentException("鑺傜偣 ID 涓嶈兘涓虹┖", nameof(nodeId));
+
+            var node = new CanNode(nodeId, name);
+            node.FrameSent += OnNodeFrameSent;
+
+            if (!_nodes.TryAdd(nodeId, node))
+            {
+                Log.Warn($"[CanBus] 鑺傜偣 {nodeId} 宸插瓨鍦紝灏嗚鏇挎崲");
+                _nodes.TryRemove(nodeId, out var oldNode);
+                oldNode?.Deactivate();
+                _nodes.TryAdd(nodeId, node);
+            }
+
+            // 濡傛灉鎬荤嚎宸茶繍琛岋紝鑷姩婵�娲昏妭鐐�
+            if (State == CanBusState.Running)
+            {
+                node.Activate();
+            }
+
+            Log.Info($"[CanBus] 鑺傜偣 {nodeId} ({name ?? nodeId}) 宸叉敞鍐屽埌鎬荤嚎 {BusName}");
+            return node;
+        }
+
+        /// <summary>
+        /// 娉ㄩ攢鑺傜偣
+        /// </summary>
+        public bool UnregisterNode(string nodeId)
+        {
+            if (_nodes.TryRemove(nodeId, out var node))
+            {
+                node.FrameSent -= OnNodeFrameSent;
+                node.Deactivate();
+                Log.Info($"[CanBus] 鑺傜偣 {nodeId} 宸蹭粠鎬荤嚎 {BusName} 娉ㄩ攢");
+                return true;
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// 鑾峰彇鑺傜偣
+        /// </summary>
+        public CanNode? GetNode(string nodeId)
+        {
+            _nodes.TryGetValue(nodeId, out var node);
+            return node;
+        }
+
+        /// <summary>
+        /// 鑾峰彇鎵�鏈夊凡娉ㄥ唽鑺傜偣
+        /// </summary>
+        public IReadOnlyList<CanNode> GetAllNodes()
+        {
+            return _nodes.Values.ToList();
+        }
+
+        /// <summary>
+        /// 鍚戞�荤嚎鍙戦�佸抚锛堢敱鑺傜偣鍙戦�侊紝鎬荤嚎璐熻矗璺敱锛�
+        /// </summary>
+        private void OnNodeFrameSent(object? sender, CanFrame frame)
+        {
+            if (State != CanBusState.Running) return;
+
+            // 妯℃嫙寤惰繜
+            if (SimulatedLatencyMs > 0)
+            {
+                Thread.Sleep(SimulatedLatencyMs);
+            }
+
+            // 璁板綍鍒版�荤嚎鏃ュ織
+            while (_busLog.Count >= _maxLogSize)
+            {
+                _busLog.TryDequeue(out _);
+            }
+            _busLog.Enqueue(frame);
+            TotalFramesTransmitted++;
+            LastActivityTime = DateTime.Now;
+
+            // 璺敱鍒版墍鏈夊叾浠栬妭鐐�
+            int routed = 0;
+            foreach (var kvp in _nodes)
+            {
+                if (kvp.Key == frame.SourceNodeId) continue; // 涓嶈矾鐢卞洖婧愯妭鐐�
+
+                try
+                {
+                    kvp.Value.ReceiveFrame(frame);
+                    routed++;
+                    TotalFramesReceived++;
+                }
+                catch (Exception ex)
+                {
+                    Log.Error($"[CanBus] 璺敱甯у埌鑺傜偣 {kvp.Key} 澶辫触: {ex.Message}");
+                }
+            }
+
+            // 妫�鏌ユ簮鑺傜偣鏄惁鍚敤鑷姩鍝嶅簲妯″紡
+            if (_nodes.TryGetValue(frame.SourceNodeId, out var sourceNode) && sourceNode.AutoResponseEnabled)
+            {
+                try
+                {
+                    // 妯℃嫙鍝嶅簲寤惰繜
+                    if (sourceNode.AutoResponseDelayMs > 0)
+                    {
+                        Thread.Sleep(sourceNode.AutoResponseDelayMs);
+                    }
+
+                    // 鍒涘缓鍝嶅簲甯э紙澶嶅埗鍘熷甯ф暟鎹紝鏉ユ簮涓烘簮鑺傜偣锛�
+                    var responseFrame = new CanFrame
+                    {
+                        FrameId = frame.FrameId,
+                        Format = frame.Format,
+                        Type = frame.Type,
+                        Data = (byte[])frame.Data.Clone(),
+                        SourceNodeId = sourceNode.NodeId,
+                        Timestamp = DateTime.Now
+                    };
+
+                    // 灏嗗搷搴斿抚璺敱鍒版墍鏈夊叾浠栬妭鐐癸紙涓嶅寘鎷搷搴旀簮鑷韩锛�
+                    foreach (var kvp in _nodes)
+                    {
+                        if (kvp.Key == sourceNode.NodeId) continue;
+                        try
+                        {
+                            kvp.Value.ReceiveFrame(responseFrame);
+                            TotalFramesReceived++;
+                        }
+                        catch (Exception ex)
+                        {
+                            Log.Error($"[CanBus] 璺敱鍝嶅簲甯у埌鑺傜偣 {kvp.Key} 澶辫触: {ex.Message}");
+                        }
+                    }
+
+                    // 灏嗗搷搴斿抚閫佸叆婧愯妭鐐圭殑鎺ユ敹閭锛堣婧愯妭鐐硅兘鐪嬪埌鑷繁鐨勫搷搴旓級
+                    sourceNode.ReceiveFrame(responseFrame);
+                    TotalFramesReceived++;
+
+                    // 璁板綍鍝嶅簲甯у埌鎬荤嚎鏃ュ織
+                    _busLog.Enqueue(responseFrame);
+                    TotalFramesTransmitted++;
+
+                    Log.Debug($"[CanBus] 鑺傜偣 {sourceNode.NodeId} 鑷姩鍝嶅簲甯� {responseFrame.FrameIdHex}");
+                }
+                catch (Exception ex)
+                {
+                    Log.Error($"[CanBus] 婧愯妭鐐硅嚜鍔ㄥ搷搴斿け璐�: {ex.Message}");
+                }
+            }
+
+            Log.Debug($"[CanBus] 甯� {frame.FrameIdHex} 浠� {frame.SourceNodeId} 璺敱鍒� {routed} 涓妭鐐�");
+            FrameTransmitted?.Invoke(this, new CanBusFrameEventArgs(frame, frame.SourceNodeId));
+        }
+
+        /// <summary>
+        /// 鐩存帴娉ㄥ叆甯у埌鎬荤嚎锛堢敤浜庢ā鎷熷閮ㄨ澶囧彂閫侊級
+        /// </summary>
+        public void InjectFrame(CanFrame frame)
+        {
+            if (State != CanBusState.Running)
+            {
+                Log.Warn($"[CanBus] 鎬荤嚎鏈繍琛岋紝鏃犳硶娉ㄥ叆甯�");
+                return;
+            }
+
+            frame.Timestamp = DateTime.Now;
+
+            // 璁板綍鍒版棩蹇�
+            while (_busLog.Count >= _maxLogSize)
+            {
+                _busLog.TryDequeue(out _);
+            }
+            _busLog.Enqueue(frame);
+            TotalFramesTransmitted++;
+            LastActivityTime = DateTime.Now;
+
+            // 璺敱鍒版墍鏈夎妭鐐�
+            foreach (var kvp in _nodes)
+            {
+                try
+                {
+                    kvp.Value.ReceiveFrame(frame);
+                    TotalFramesReceived++;
+                }
+                catch (Exception ex)
+                {
+                    Log.Error($"[CanBus] 娉ㄥ叆甯ц矾鐢卞埌鑺傜偣 {kvp.Key} 澶辫触: {ex.Message}");
+                }
+            }
+
+            FrameTransmitted?.Invoke(this, new CanBusFrameEventArgs(frame, frame.SourceNodeId));
+            Log.Debug($"[CanBus] 娉ㄥ叆甯� {frame.FrameIdHex} 鍒版�荤嚎");
+        }
+
+        /// <summary>
+        /// 鑾峰彇鎬荤嚎鏃ュ織
+        /// </summary>
+        public List<CanFrame> GetBusLog(int maxCount = 100)
+        {
+            var all = _busLog.ToList();
+            if (all.Count <= maxCount) return all;
+            return all.Skip(all.Count - maxCount).ToList();
+        }
+
+        /// <summary>
+        /// 娓呯┖鎬荤嚎鏃ュ織
+        /// </summary>
+        public void ClearBusLog()
+        {
+            while (_busLog.TryDequeue(out _)) { }
+        }
+
+        /// <summary>
+        /// 閲嶇疆鎵�鏈夌粺璁�
+        /// </summary>
+        public void ResetStatistics()
+        {
+            TotalFramesTransmitted = 0;
+            TotalFramesReceived = 0;
+            LastActivityTime = null;
+            foreach (var node in _nodes.Values)
+            {
+                node.ResetCounters();
+            }
+        }
+
+        /// <summary>
+        /// 鑾峰彇鎬荤嚎鐘舵�佹憳瑕�
+        /// </summary>
+        public string GetStatusSummary()
+        {
+            return $"Bus={BusName}, State={State}, BaudRate={BaudRate}, Nodes={_nodes.Count}, " +
+                   $"Tx={TotalFramesTransmitted}, Rx={TotalFramesReceived}, " +
+                   $"LastActivity={LastActivityTime?.ToString("HH:mm:ss") ?? "N/A"}";
+        }
+
+        public void Dispose()
+        {
+            Stop();
+            foreach (var node in _nodes.Values)
+            {
+                node.Deactivate();
+            }
+            _nodes.Clear();
+        }
+    }
+}

--
Gitblit v1.9.1