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