using System;
|
using System.Collections.Concurrent;
|
using CanSimulator;
|
|
namespace CanSimulatorServer
|
{
|
/// <summary>
|
/// 服务端总线注册表:按名称懒创建 CanBusSimulator(复用进程内路由/自动响应逻辑),
|
/// 并以 (bus, nodeIndex) 为全局唯一键仲裁节点挂载。
|
/// 总线在首次被挂载时自动启动(与旧进程内驱动行为一致;UI 启停按钮可后续接入)。
|
/// </summary>
|
public sealed class CanBusManager
|
{
|
private readonly ConcurrentDictionary<string, CanBusSimulator> _buses =
|
new(StringComparer.OrdinalIgnoreCase);
|
|
/// <summary>挂载节点:同 (bus,nodeIndex) 已被占用时返回 false(NODE_BUSY 语义)。</summary>
|
/// <param name="onFrameReceived">帧到达该节点时的回调(服务端用于 TCP 推送)</param>
|
public bool TryAttach(string busName, int nodeIndex, string clientTag,
|
Action<CanFrame> onFrameReceived, out CanNode? node, out CanBusSimulator? bus)
|
{
|
bus = GetOrCreateBus(busName);
|
string nodeId = NodeIdOf(nodeIndex);
|
|
// 唯一性仲裁:已存在同编号节点则拒绝
|
var existing = bus.GetNode(nodeId);
|
if (existing != null)
|
{
|
node = null;
|
return false;
|
}
|
|
var n = bus.RegisterNode(nodeId, $"{busName}/N{nodeIndex}({clientTag})");
|
n.SetPromiscuousMode(); // 服务端节点默认收所有帧(滤波器命令可在其后覆盖)
|
n.Activate();
|
n.FrameReceived += (_, f) => onFrameReceived(f);
|
|
node = n;
|
return true;
|
}
|
|
/// <summary>注销节点(连接断开时调用),空总线保留(再次挂载零开销)。</summary>
|
public void Detach(string busName, CanNode node)
|
{
|
if (_buses.TryGetValue(busName, out var bus))
|
{
|
bus.UnregisterNode(node.NodeId);
|
}
|
}
|
|
public CanBusSimulator? FindBus(string busName)
|
=> _buses.TryGetValue(busName, out var b) ? b : null;
|
|
public System.Collections.Generic.IReadOnlyCollection<CanBusSimulator> AllBuses()
|
=> (System.Collections.Generic.IReadOnlyCollection<CanBusSimulator>)_buses.Values;
|
|
private CanBusSimulator GetOrCreateBus(string busName)
|
{
|
var bus = _buses.GetOrAdd(busName, name =>
|
{
|
var b = new CanBusSimulator(name);
|
b.Start(); // 自动启动:TCP 模式下总线生命周期由挂载驱动
|
return b;
|
});
|
return bus;
|
}
|
|
/// <summary>节点在总线上的身份:以编号寻址(与真实 CAN 节点地址语义一致)</summary>
|
public static string NodeIdOf(int nodeIndex) => $"{nodeIndex}";
|
}
|
}
|