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>
|
/// 总线级应答器:开启后,任何节点发出的帧会被原样回显给发送者自身
|
/// (模拟单个从设备应答,无需第二个节点在线)。来源标记为 RESPONDER。
|
/// </summary>
|
public bool EchoResponderToSender { get; set; } = false;
|
|
/// <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++;
|
|
// 接收方自动响应:模拟从设备收到命令后答复(面板“自动响应”语义)
|
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));
|
}
|
|
/// <summary>
|
/// 接收节点自动响应:把收到帧原样回给其它节点(含发帧方),并记入自身邮箱与总线日志
|
/// </summary>
|
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}");
|
}
|
}
|
|
/// <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();
|
}
|
}
|
}
|