using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; using log4net; namespace CanSimulator { /// /// CAN 总线状态 /// public enum CanBusState { /// 停止状态 Stopped, /// 运行中 Running, /// 暂停 Paused, /// 错误 Error } /// /// CAN 总线模拟器事件参数 /// public sealed class CanBusFrameEventArgs : EventArgs { public CanFrame Frame { get; } public string SourceNodeId { get; } public CanBusFrameEventArgs(CanFrame frame, string sourceNodeId) { Frame = frame; SourceNodeId = sourceNodeId; } } /// /// CAN 总线模拟器 /// 模拟一条虚拟 CAN 总线,多个虚拟节点可以挂载到总线上进行通讯 /// public sealed class CanBusSimulator : IDisposable { private static readonly ILog Log = LogManager.GetLogger(typeof(CanBusSimulator)); /// /// 总线名称 /// public string BusName { get; } /// /// 总线状态 /// public CanBusState State { get; private set; } = CanBusState.Stopped; /// /// 波特率(仅用于模拟显示,不影响实际逻辑) /// public int BaudRate { get; set; } = 500000; // 默认 500kbps /// /// 模拟延迟(毫秒)- 模拟真实总线传输延迟 /// public int SimulatedLatencyMs { get; set; } = 0; /// /// 总线级应答器:开启后,任何节点发出的帧会被原样回显给发送者自身 /// (模拟单个从设备应答,无需第二个节点在线)。来源标记为 RESPONDER。 /// public bool EchoResponderToSender { get; set; } = false; /// /// 已注册的节点 /// private readonly ConcurrentDictionary _nodes = new(); /// /// 总线日志(最近 N 条帧) /// private readonly ConcurrentQueue _busLog = new(); private int _maxLogSize = 5000; /// /// 总线统计 /// public long TotalFramesTransmitted { get; private set; } public long TotalFramesReceived { get; private set; } public DateTime? LastActivityTime { get; private set; } /// /// 帧在总线上传输事件(可用于监控/日志) /// public event EventHandler? FrameTransmitted; /// /// 总线状态变化事件 /// public event EventHandler? StateChanged; public CanBusSimulator(string busName = "CAN_BUS_1") { BusName = busName ?? throw new ArgumentNullException(nameof(busName)); } /// /// 启动总线 /// 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); } /// /// 停止总线 /// public void Stop() { if (State == CanBusState.Stopped) return; State = CanBusState.Stopped; Log.Info($"[CanBus] 总线 {BusName} 已停止"); StateChanged?.Invoke(this, State); } /// /// 暂停总线(帧仍在保留但不路由) /// public void Pause() { if (State != CanBusState.Running) return; State = CanBusState.Paused; Log.Info($"[CanBus] 总线 {BusName} 已暂停"); StateChanged?.Invoke(this, State); } /// /// 恢复总线 /// public void Resume() { if (State != CanBusState.Paused) return; State = CanBusState.Running; Log.Info($"[CanBus] 总线 {BusName} 已恢复"); StateChanged?.Invoke(this, State); } /// /// 注册节点到总线 /// 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; } /// /// 注销节点 /// 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; } /// /// 获取节点 /// public CanNode? GetNode(string nodeId) { _nodes.TryGetValue(nodeId, out var node); return node; } /// /// 获取所有已注册节点 /// public IReadOnlyList GetAllNodes() { return _nodes.Values.ToList(); } /// /// 向总线发送帧(由节点发送,总线负责路由) /// 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++; // 接收方自动响应:模拟从设备收到命令后答复(面板“自动响应”语义) if (kvp.Value.AutoResponseEnabled) SendAutoResponse(kvp.Value, frame); } catch (Exception ex) { Log.Error($"[CanBus] 路由帧到节点 {kvp.Key} 失败: {ex.Message}"); } } // 总线级应答器:把该帧原样回显给发送者自身(绕过"不回源",直接投递) if (EchoResponderToSender && _nodes.TryGetValue(frame.SourceNodeId, out var senderNode)) { try { var echo = new CanFrame { FrameId = frame.FrameId, Format = frame.Format, Type = frame.Type, Data = (byte[])frame.Data.Clone(), SourceNodeId = "RESPONDER", Timestamp = DateTime.Now }; senderNode.ReceiveFrame(echo); TotalFramesReceived++; } catch (Exception ex) { Log.Error($"[CanBus] 应答器回显给 {frame.SourceNodeId} 失败: {ex.Message}"); } } Log.Debug($"[CanBus] 帧 {frame.FrameIdHex} 从 {frame.SourceNodeId} 路由到 {routed} 个节点"); FrameTransmitted?.Invoke(this, new CanBusFrameEventArgs(frame, frame.SourceNodeId)); } /// /// 接收节点自动响应:把收到帧原样回给其它节点(含发帧方),并记入自身邮箱与总线日志 /// private void SendAutoResponse(CanNode responder, CanFrame incoming) { try { // 模拟响应延迟 if (responder.AutoResponseDelayMs > 0) { Thread.Sleep(responder.AutoResponseDelayMs); } var responseFrame = new CanFrame { FrameId = incoming.FrameId, Format = incoming.Format, Type = incoming.Type, Data = (byte[])incoming.Data.Clone(), SourceNodeId = responder.NodeId, Timestamp = DateTime.Now }; // 响应帧路由到其它节点(含最初的发帧方) foreach (var kvp in _nodes) { if (kvp.Key == responder.NodeId) continue; try { kvp.Value.ReceiveFrame(responseFrame); TotalFramesReceived++; } catch (Exception ex) { Log.Error($"[CanBus] 路由响应帧到节点 {kvp.Key} 失败: {ex.Message}"); } } // 响应帧送入应答节点自身接收邮箱(调试页可见自己发出的响应) responder.ReceiveFrame(responseFrame); TotalFramesReceived++; // 记录响应帧到总线日志 _busLog.Enqueue(responseFrame); TotalFramesTransmitted++; Log.Debug($"[CanBus] 接收节点 {responder.NodeId} 自动响应帧 {responseFrame.FrameIdHex}"); } catch (Exception ex) { Log.Error($"[CanBus] 节点 {responder.NodeId} 自动响应失败: {ex.Message}"); } } /// /// 直接注入帧到总线(用于模拟外部设备发送) /// 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} 到总线"); } /// /// 获取总线日志 /// public List GetBusLog(int maxCount = 100) { var all = _busLog.ToList(); if (all.Count <= maxCount) return all; return all.Skip(all.Count - maxCount).ToList(); } /// /// 清空总线日志 /// public void ClearBusLog() { while (_busLog.TryDequeue(out _)) { } } /// /// 重置所有统计 /// public void ResetStatistics() { TotalFramesTransmitted = 0; TotalFramesReceived = 0; LastActivityTime = null; foreach (var node in _nodes.Values) { node.ResetCounters(); } } /// /// 获取总线状态摘要 /// 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(); } } }