日期:2026-11-09
状态:已与用户逐节确认
关联代码基线:V3.0 混合架构(Local + DeviceBroker),进程内 CanSimulator
现状问题:CanDriverAdapter 用进程内 static 字典 _sharedBuses 管理总线实例,
主程序(CAN-001/002)与 DeviceBroker 宿主(CAN-003/004)各自持有一套同名但隔离的
CAN_BUS_1 实例,跨进程不可见,只能靠"同宿主对端设备"变通验证收发。
目标:将 CAN 模拟器改造为独立 TCP 服务进程(总线权威),模仿物理 CAN 分析仪行为,
实现跨进程多通道并发测试;同时把 CAN 驱动收敛为**唯一类** CanDeviceDriver,
Local/Broker 差异下沉为驱动内部传输后端,自动化程序只面向 IDeviceDriver 编程。
| 决策点 | 结论 |
|---|---|
| 协议取向 | 命令串直传(SEND:/RECV_ALL/AUTO_RESPONSE:ON 等现有语义原样传输) |
| 分帧方式 | 4 字节大端长度 + UTF8 文本(与 BrokerIpc 同惯用法) |
| 服务端形态 | 独立 WPF 进程 CanSimulatorServer.exe(带 UI) |
| 地址来源 | 全局 App.config(CanSimulator.Host / CanSimulator.Port) |
| 模式取舍 | 全面切 TCP,驱动删除进程内总线分支,无回退开关 |
| 服务端实现 | 复用现有 CanBusSimulator/CanNode 路由与自动响应逻辑,零重写 |
| 会话模型 | 方案 A:请求-响应(保序)+ 服务端主动推送 EV:RECV(双工) |
| 驱动收敛 | 唯一 CAN 驱动类 CanDeviceDriver,内部 ICanTransport 双实现 |
| 面板改造 | 抽 CanChannelView + CanDebugPanel 壳化为 TabControl 多通道 |
| 启动方式 | 模拟器服务纯手动启动,主程序与 Broker 均不拉起 |
┌───────────────────────── CanSimulatorServer.exe(新建,WPF) ─────────────────────────┐
│ TcpListener(默认 127.0.0.1:9000) │
│ ├─ TcpAcceptor:每连接一个 ConnectionSession │
│ ├─ CanBusManager:懒创建 CanBusSimulator;(bus,nodeIndex) 唯一性仲裁; │
│ │ 节点 FrameReceived → 会话推送 的胶水 │
│ └─ UI:节点树 | 实时帧表 | 总线启停/统计 | 日志 │
└────────────────────────────────────────────────────────────────────────────────────────┘
▲ TCP(NodeChannelTransport) ▲ TCP
│ │
┌──────┴──────────────┐ ┌──────────┴──────────────┐
│ 主程序 │ 命名管道RPC │ DeviceBroker.exe │
│ CAN-001/002 │ ◄────────────► │ CAN-003/004 │
│ CanDeviceDriver │ BrokerRpc │ CanDeviceDriver │
│ (NodeChannelTransport)│ Transport │ (NodeChannelTransport) │
└──────────────────────┘ └─────────────────────────┘
CanDeviceDriver(NodeChannelTransport)。DeviceManager.GetDriver(deviceId) 拿到的永远是 CanDeviceDriver,分帧:4 字节大端长度 + UTF8 文本行;文本按冒号分段。
| 消息 | 说明 |
|---|---|
HELLO:{bus}:{nodeIndex}:{clientTag} |
握手并挂载节点;确认后节点激活、开始接收总线帧 |
SEND:{frameId}:{data} |
发送帧(现行命令串透发) |
QUERY:{命令串} |
RECV / RECV_ALL / STATUS / AUTO_RESPONSE:ON / FILTER:id:mask / LOG 等透发 |
DISCONNECT |
注销本连接节点 |
| 消息 | 说明 |
|---|---|
OK:{payload} / ERR:{code}:{message} |
对上一条请求的响应(恰好一行,保序) |
EV:RECV:{frameId}:{data}:{HH:mm:ss.fff}:{srcNodeIndex} |
总线路由推送的接收帧(主动推送,无请求关联) |
EV:STATE:{connected\|lost} |
对端上下线通知,MVP 仅定义格式不发送 |
一个总线节点 = (busName, nodeIndex),服务端以 nodeIndex 为总线上唯一寻址
(与真实 CAN 节点地址语义一致)。同一 nodeIndex 被其他连接占用时握手返回ERR:NODE_BUSY 拒绝挂载;连接断开自动释放。两个设备配置同 (bus,node) 属配置错误,
靠错误文案引导修改端点,不做自动避让。
ERR:UNKNOWN_CMD / ERR:BAD_FRAME,不断连接HELLO 前发其它命令 → ERR:NOT_HANDSHAKEDConnectAsync 抛连接异常,文案含"请先启动 CAN 模拟器服务 {Host}:{Port}"IDeviceDriver(现有契约,不变)
▲
CanDeviceDriver 全系统唯一 CAN 驱动类(由 CanDriverAdapter 更名重构)
├─ 命令串解析、_recvBuffer、RECV/RECV_ALL 组装、DriverFrameReceived 事件、
│ STATUS/统计、连接状态机、Dispose
└─ ICanTransport _transport(构造时按设备 ExecutionMode 选择)
ICanTransport(内部接口)
├─ ConnectAsync / TransceiveAsync(cmd)→响应行 / Dispose
├─ event FramePushed(EV:RECV 行到达;NodeChannel 为 TCP 直推,
│ BrokerRpc 为随宿主响应捎带到达,见 §5.4)
├─ event TransportBroken
│
├─ NodeChannelTransport TCP 直连服务端;读循环分流响应/推送;
│ 断线指数退避重连(0.5s 起、上限 5s)、重连自动重发 HELLO
└─ BrokerRpcTransport 现 BrokeredCanDriver 的 RPC 逻辑原样迁入
(Hello 握手 / SubmitTask / ResultBody 态→异常映射不变:
RejectedStaleLease→LEASE_STALE、ConfigMismatch、
Unknown→TimeoutException 禁止假定未执行)
DriverFactory.CreateDriver("CAN", …) 统一返回 CanDeviceDriver;工厂读 data.db 该设备ExecutionMode/BrokerId 选择传输后端。InfrastructureBootstrapper.RegisterBrokeredDrivers 取消——Broker 设备与 Local 设备LoadDevicesFromConfig 注册路径。BrokeredCanDriver 类(逻辑入 BrokerRpcTransport)、CanDriverAdapter._sharedBusesDeviceManager.GetDriver 契约、DeviceService/CanBusService/AutoDeviceFacade 自动化链、ExistingDriver 复用逻辑均不改。GetType().Name 区分驱动的日志/断言改为按传输后端类型区分。DrainBrokerMailbox(RECV_ALL 1s 轮询) 降级为兜底(重连窗口期补显示),BUS@tcp / BUS@broker。宿主内驱动的 NodeChannelTransport 把 EV:RECV 追加到 pendingPushes 列表(有界 1000,
满丢最老);BrokerServer 响应 QUERY/SEND 时在 readback 末尾追加 |PUSHED:n + 推送行;
BrokerRpcTransport 解析后回调 FramePushed 并从宿主列表移除(一次性消费,不丢不重)。
空闲无请求时推送滞留宿主,下一次任意请求捎带——Broker 路径实时性仍为请求驱动,
与现状一致,面板 1s RECV_ALL 兜底保证最终显示。
CanSimulatorServer\,SDK 风格 WPF(net472;UseWPF=true),WPFProject\TapProject.sln。App.config:ServerPort(默认 9000)、ListenOnAllInterfaces(默认 false)。TcpAcceptor / CanBusManager / ConnectionSession(每连接 MPSC 有界发送队列MainWindow(节点树、环形 2000 行帧表 TX/RX 分色、CanDebugPanel(类名/入口不变,壳化为 TabControl)
├─ 工具条:[+ 添加通道] [关闭当前通道];Tab 头 "CAN-001 @CAN_BUS_1:0"
└─ TabControl → CanChannelView × N(新 UserControl,承载现单通道全部 UI)
CanDeviceDriver 后总线/节点输入只读(设备表权威);| 键 | 位置 | 默认 |
|---|---|---|
CanSimulator.Host |
主程序 + DeviceBroker App.config | 127.0.0.1 |
CanSimulator.Port |
同上 | 9000 |
ServerPort / ListenOnAllInterfaces |
服务端 App.config | 9000 / false |
UseTcpMode 取消;Endpoint 格式 CAN_BUS_1:0 不变;data.db 零迁移。
启动顺序:用户手起服务 → 主程序(照常拉起 DeviceBroker)→ CAN 按需连接;
主程序退出只关自己的连接,服务常驻。
| 故障 | 行为 |
|---|---|
| 服务不可达(连接时) | 抛连接异常含启动提示文案 |
| 中途断线 | IsConnected=false → 指数退避重连 → HELLO 重发;期间发送快速失败 NOT_CONNECTED;ConnectionChanged 上抛 |
| 在途请求撞断线 | TimeoutException,语义等同 Unknown(INV-06),禁止自动重试 SEND |
| 节点被占 | ERR:NODE_BUSY,提示修改设备端点 |
| 推送队列满 | 丢最老+UI 计数;客户端 1s RECV_ALL 兜底补账 |
| Broker 管道断/宿主死 | 沿用现异常;宿主内 TCP 重连对客户端透明 |
CanBusManager + TcpListener(随机端口)WPFProject\TapProject.sln),/p:Platform=x64(老式工程 XAML 编译链路限制)。