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();
}
}
}