聚焦"一次仪器调用从入口到硬件、再回到调用方"的完整路径与类引用结构。V3.1(TCP 模拟器 + 统一 CanDeviceDriver + 多设备并发)。与代码同步 2026-11-09。
仪器调用只有两类入口,但都汇聚到同一套"服务层 → 驱动层 → 传输层",最终落到模拟器服务里的总线:
| 入口 | 触发者 | 首跳 API | 令牌类型 |
|---|---|---|---|
| 自动化测试程序 | TestPlan / 步骤脚本 | ICanBusService / IAutoDeviceFacade | OwnerType.Production |
| 调试硬件调试页 | 人工(DeviceDebugTab) | 面板直调驱动 或 IDeviceService | OwnerType.Maintenance |
两条入口互斥于同一 arb.db 租约:某设备被生产预约时维护进不去,反之亦然(INV 由资源层栅栏保证)。
【入口层】
CanBusService ─┐ DeviceDebugTab ─┐
(类型化封装) │ IAutoDeviceFacade │ (每设备一 Tab)
▼ ▼
【门面/编排层】 AutoDeviceFacade ──► IDeviceService ◄── ExecuteViaDeviceService / 面板直调
(预约+释放编排) (DeviceService:校验+租约+分派)
│
【资源层】 IResourceManager ◄───────┤ (ValidateOwner / AcquireOperationAsync)
LayeredResourceManager ───┼─► ILeaseCoordinator ─► DbLeaseStore(arb.db)
│
【设备/驱动层】 IDeviceManager.GetDriver(deviceId) ─► IDeviceDriver
│
CanDeviceDriver(唯一 CAN 驱动,实现 IDeviceDriver)
├─ ICanTransport
│ ├─ NodeChannelTransport ─► TCP ─► CanSimulatorServer
│ └─ BrokerRpcTransport ─► 命名管道 ─► DeviceBroker
│ └►(宿主内) CanDeviceDriver(NodeChannel) ─► TCP ─► 模拟器
└─ 其它驱动:TcpRaw/Scpi/ModBus/S7(非 CAN 仪器)
【服务/总线层】 TcpSimulatorHost ─► ServerSession ─► CanCommandExecutor ─► CanBusSimulator/CanNode
依赖方向自上而下;上层只依赖接口(IAutoDeviceFacade/IDeviceService/IResourceManager/IDeviceManager/IDeviceDriver),实现由 InfrastructureBootstrapper 装配进 UtilContainer。
TestPlan 步骤
→ CanBusService.SendFrameAsync(deviceId, 0x300, data)
· 校验帧(≤0x1FFFFFFF, ≤8字节) → 拼命令串 "SEND:0x300:AA BB"
→ AutoDeviceFacade.ExecuteAsync(deviceId, "SEND:…")
① ReserveCoreAsync: new ResourceOwnerToken(Production, auto_guid)
ResourceManager.ReserveAsync(token, ResourceKey(deviceId,"Generic")) ← 申请预约(写 arb.db, TTL)
② DeviceService.ExecuteCommandAsync(deviceId, cmd, token)
③ finally ReleaseCoreAsync → ReleaseReservationAsync ← 释放预约
→ 返回 DeviceCommandResult{Success, Readback, LatencyMs}
using (var lease = await AutoDeviceFacade.AcquireAsync(deviceId, "Step1"))
{
// 内部 ReserveAsync + 若 LayeredResourceManager 则 lease.StartHeartbeat(Coordinator, grants)
// 心跳:每 8s Coordinator.Renew(grant,30),连续失败≥2 → LeaseLost 事件
await CanBusService.SendFrameAsync(lease, 0x100, d); // lease.ExecuteAsync → DeviceService…
var f = await CanBusService.ReceiveFrameAsync(lease); // lease.QueryAsync("RECV")
} // Dispose: 停心跳 + ReleaseReservationAsync
AutoDeviceLease.StartHeartbeat);人工维护会话的心跳在 DebugSessionManager(120s),两条路径都要续期,否则到 TTL 过期触发 RECOVERY_REQUIRED。硬件调试页每个设备一个 DeviceDebugTab(独立会话)。CAN 与 非CAN 的调用路径不同:
DeviceDebugTab.LoadPanel("CanDevice")
→ CanDebugPanel(多通道壳) → CanChannelView(单通道, ExistingDriver=DeviceManager.GetDriver(deviceId))
连接:DoConnect → 复用已注册 CanDeviceDriver(Broker 代理 或 Local NodeChannel)→ ConnectAsync
发送:SendBtn_Click → _driver.SendCommandAsync("SEND:0x300:…") ← 直接调驱动
接收:驱动 DriverFrameReceived 事件 → AddReceivedFrame → Dispatcher.BeginInvoke 上屏
维护门控:面板 IsMaintenanceMode(由会话状态驱动),进入维护经 DebugSessionManager
PowerSupplyDebugPanel/PlcDebugPanel → OnCommandExecute(DebugCommand)
→ DeviceDebugTab.ExecuteViaDeviceService
· new ResourceOwnerToken(Maintenance, sessionId)
· BuildDeviceCommand(cmd) → 如 "VOLT …;CURR …;OUTP ON" / "WRITE:addr:TRUE"
· DeviceService.ExecuteCommandAsync(deviceId, cmd, token) ← 走服务层校验+租约+驱动
→ DebugOperation 落 debug.db + 操作日志
IDeviceDriver 落到设备。CanDeviceDriver 把命令交给选定的 ICanTransport,两条后端各自到总线的物理路径不同:
CanDeviceDriver.QueryAsync/SendCommandAsync
→ NodeChannelTransport.TransceiveAsync(line)
· 首连 HandshakeAsync: 写 "HELLO:bus:node:tag" 直读 "OK:HELLO…"(读循环未起)
· 之后 RequestLineAsync: _requestLock 串行 → 写命令帧 → 等 TCS(deadline 5s)
· 后台 ReadLoopAsync: 分流 OK/ERR→完成 TCS;EV:RECV→FramePushed 事件
→ TCP 9000 → ServerSession → CanCommandExecutor → CanBusSimulator/CanNode
CanDeviceDriver(Broker).SendCommandAsync("SEND:…")
→ BrokerRpcTransport.TransceiveAsync
· 映射 opCode(SEND/QUERY)+payload → SubmitBody → BrokerClientChannel(命名管道 RPC)
· EnsureConnectedAsync(Hello 握手) → SubmitAsync(configHash 随信封)
→ DeviceBroker.BrokerServer.ExecuteOneShotAsync
① 白名单/域内 ② configHash 对账 ③ 租约栅栏 ④ SessionGate 串行
⑤ 宿主内 CanDeviceDriver(NodeChannel) → TCP → 模拟器路由
⑥ ResultBody.Readback 末尾 AppendPushes("|PUSHED:n|EV:RECV…")
→ 客户端 DrainPiggyback 剥离 PUSHED → FramePushed → 驱动缓冲+事件
Broker 的接收靠"请求驱动捎带":客户端每次 STATUS/SEND 都会让宿主把自上次以来收到的推送带回;面板每 100ms 的 STATUS 轮询保证最终显示。
CanBusService.SendFrameAsync("CAN-001",0x300,[AA BB])
→ AutoDeviceFacade.ExecuteAsync → ReserveAsync(Production) → arb.db 写租约
→ DeviceService.ExecuteCommandAsync
GetDriver("CAN-001")→CanDeviceDriver(NodeChannel)
ValidateOwner→Renew(grant,1) 栅栏校验
AcquireOperationAsync→OperationLease
driver.SendCommandAsync("SEND:0x300:AA BB")
→ TCP HELLO/SEND → ServerSession → CanCommandExecutor.HandleSend
→ CanNode.SendFrame → CanBusSimulator 路由到其它节点(+应答器回显)
→ "OK:TX:0x300:2"
→ DeviceCommandResult{Success=true,Readback="OK:TX…"}
→ ReleaseReservationAsync → arb.db 释放
CanChannelView.SendBtn → _driver(CanDeviceDriver/BrokerRpc).SendCommandAsync("SEND:0x300:AA BB")
→ BrokerRpcTransport → 命名管道 SubmitBody{SEND,"SEND:0x300:AA BB"}
→ BrokerServer 三重关卡+SessionGate → 宿主 CanDeviceDriver(NodeChannel) → TCP → 模拟器路由
→ ResultBody{Readback="OK:TX…" (|PUSHED 若有回显)} 回管道
→ 客户端 DrainPiggyback → 若含 PUSHED 则 FramePushed→DriverFrameReceived→面板 RX 行
(应答器开启时,模拟器把 0x300 回显给宿主节点→宿主暂存→本次/下次响应捎带回客户端)
入口 门面/编排 服务层(DeviceService) 资源层 驱动/传输/服务端
───────────────────────────────────────────────────────────────────────────────────────────────
自动化 ─► AutoDeviceFacade ─► ExecuteCommandAsync ─► ReserveAsync(预约,写租约)
(Reserve/Release) ValidateOwner(栅栏) ─► Renew(grant,1)
AcquireOperationAsync ─► OperationLease
driver.SendCommandAsync ─► (Local TCP / Broker RPC)
调试 ─► DebugSessionManager.RequestMaintenance ─► ReserveAsync(Maintenance) + 120s 心跳
CAN: 面板直调 driver;电源/PLC: 经 DeviceService
Broker 宿主侧 ─► BrokerServer.ExecuteOneShotAsync 出队再校验:
① 白名单 ② configHash(INV-04) ③ PhaseOf/ProbeConflict(INV-01/03) ④ SessionGate
| 校验点 | 方法 | 失败结果 |
|---|---|---|
| 预约归属 | ResourceManager.ValidateOwner → Renew(grant,1) | PERMISSION_DENIED |
| 操作互斥 | AcquireOperationAsync → OperationLease | ResourceConflictException |
| Broker 出队栅栏 | PhaseOf==Active 且有 holder | RejectedStaleLease / RECOVERY_REQUIRED |
| 配置一致 | configHash 对账 | RejectedConfigMismatch(INV-04) |
| 租约续期 | Coordinator.Renew(grant,ttl) | 失败≥2 → LeaseLost / 过期 RECOVERY_REQUIRED |
| 类型 | 所在 | 作用 |
|---|---|---|
ResourceOwnerToken | Contracts | 调用者身份(OwnerType+OwnerId+TokenId GUID);租约按 TokenId 索引,须复用同一实例 |
ResourceKey | Contracts | (deviceId,"Generic") 资源键,仲裁与预约的粒度 |
IOperationLease/OperationLease | 资源层 | 单次操作互斥租约(DeviceService 内 using) |
IAutoDeviceLease/AutoDeviceLease | AutoDeviceFacade | 作用域租约 + 8s 心跳 + LeaseLost |
DeviceCommandResult | Contracts | {Success,Readback,RawResponse,ErrorCode,LatencyMs} |
DebugCommand(+子类) | DeviceDebug.Entity | 面板→服务的命令对象(PowerSet/CanSend/PlcWrite…) |
DebugSession | DeviceDebug.Entity | 会话状态机持久化(debug.db) |
SubmitBody/ResultBody/IpcEnvelope | ResourceContracts.BrokerIpc | Broker RPC 线格式(设备/操作码/载荷/资源键/客户端实例;状态+读回+epoch) |
LeaseGrant | ResourceContracts | arb.db 租约凭据(LeaseId/Generation/Epoch/Expires) |
命令串 / EV:RECV | SimulatorFraming 协议 | SEND:/QUERY:/HELLO: 与推送行;4B 大端长度+UTF8 分帧 |
CanFrame/CanFrameDisplay | CanSimulator / DeviceDebug | 总线帧模型 / 面板显示行 |
[自动化] CanBusService ──uses──► IAutoDeviceFacade(AutoDeviceFacade)
│ uses
[调试] DeviceDebugTab ──┐ ▼
│ │ IDeviceService(DeviceService)
│直调 │ │ uses
▼ ▼ ┌──────────┴───────────┐
IDeviceDriver(CanDeviceDriver) IResourceManager IDeviceManager
│ holds (LayeredResourceManager)│ GetDriver
▼ │ uses ▼
ICanTransport ──┐ ILeaseCoordinator 注册表: deviceId→CanDeviceDriver
│ │ │ │
NodeChannelTransport BrokerRpcTransport │ (Broker 设备=CreateBrokerProxy)
│ │ ▼
TCP 9000 命名管道 DbLeaseStore(arb.db)
│ │
▼ ▼
TcpSimulatorHost BrokerServer ──(宿主内再建)──► CanDeviceDriver(NodeChannel) ──► TCP 9000
└──────┬──────┘
▼
ServerSession → CanCommandExecutor → CanBusSimulator/CanNode
IDeviceDriver;CAN 的唯一实现 CanDeviceDriver 用 ICanTransport 屏蔽"本地 TCP 直连"与"Broker 管道委托"的差异;权限/租约在门面层(预约)与服务层(校验/操作租约)与 Broker 出队栅栏三处把关;数据最终以命令串/推送行经 TCP 落到模拟器服务的同一总线上。配套阅读:程序架构解析报告 V3.1(进程拓扑/协议/应答器/多设备/心跳全景)。