仪器调用架构与数据流转说明

聚焦"一次仪器调用从入口到硬件、再回到调用方"的完整路径与类引用结构。V3.1(TCP 模拟器 + 统一 CanDeviceDriver + 多设备并发)。与代码同步 2026-11-09。

目录

1 调用入口全景

仪器调用只有两类入口,但都汇聚到同一套"服务层 → 驱动层 → 传输层",最终落到模拟器服务里的总线:

入口触发者首跳 API令牌类型
自动化测试程序TestPlan / 步骤脚本ICanBusService / IAutoDeviceFacadeOwnerType.Production
调试硬件调试页人工(DeviceDebugTab)面板直调驱动 或 IDeviceServiceOwnerType.Maintenance

两条入口互斥于同一 arb.db 租约:某设备被生产预约时维护进不去,反之亦然(INV 由资源层栅栏保证)。

2 分层类引用结构

【入口层】
  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。

3 自动化调用数据流转

3.1 一次性调用(OneShot)

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}

3.2 作用域租约(一步内多次调用)

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
心跳差异:自动化作用域租约自带 8s 心跳续期(AutoDeviceLease.StartHeartbeat);人工维护会话的心跳在 DebugSessionManager(120s),两条路径都要续期,否则到 TTL 过期触发 RECOVERY_REQUIRED。

4 调试调用数据流转

硬件调试页每个设备一个 DeviceDebugTab(独立会话)。CAN 与 非CAN 的调用路径不同:

4.1 CAN 面板(直调驱动,不经 DeviceService)

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

4.2 电源/PLC 面板(经 DeviceService)

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 + 操作日志
CAN 走"面板直调驱动 + 会话级维护门"是为了低延迟实时收发;电源/PLC 走"服务层"以获得统一权限校验与操作审计。二者最终都经 IDeviceDriver 落到设备。

5 驱动 → 传输 → 服务端 逐跳

CanDeviceDriver 把命令交给选定的 ICanTransport,两条后端各自到总线的物理路径不同:

5.1 Local(NodeChannelTransport)

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

5.2 Broker(BrokerRpcTransport)

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 轮询保证最终显示。

6 一次 CAN 发送完整时序

6.1 自动化 · Local 设备 CAN-001

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 释放

6.2 调试 · Broker 设备 CAN-003

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 回显给宿主节点→宿主暂存→本次/下次响应捎带回客户端)

7 权限 / 租约在链路中的位置

入口            门面/编排           服务层(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 → OperationLeaseResourceConflictException
Broker 出队栅栏PhaseOf==Active 且有 holderRejectedStaleLease / RECOVERY_REQUIRED
配置一致configHash 对账RejectedConfigMismatch(INV-04)
租约续期Coordinator.Renew(grant,ttl)失败≥2 → LeaseLost / 过期 RECOVERY_REQUIRED

8 关键数据结构一览

类型所在作用
ResourceOwnerTokenContracts调用者身份(OwnerType+OwnerId+TokenId GUID);租约按 TokenId 索引,须复用同一实例
ResourceKeyContracts(deviceId,"Generic") 资源键,仲裁与预约的粒度
IOperationLease/OperationLease资源层单次操作互斥租约(DeviceService 内 using)
IAutoDeviceLease/AutoDeviceLeaseAutoDeviceFacade作用域租约 + 8s 心跳 + LeaseLost
DeviceCommandResultContracts{Success,Readback,RawResponse,ErrorCode,LatencyMs}
DebugCommand(+子类)DeviceDebug.Entity面板→服务的命令对象(PowerSet/CanSend/PlcWrite…)
DebugSessionDeviceDebug.Entity会话状态机持久化(debug.db)
SubmitBody/ResultBody/IpcEnvelopeResourceContracts.BrokerIpcBroker RPC 线格式(设备/操作码/载荷/资源键/客户端实例;状态+读回+epoch)
LeaseGrantResourceContractsarb.db 租约凭据(LeaseId/Generation/Epoch/Expires)
命令串 / EV:RECVSimulatorFraming 协议SEND:/QUERY:/HELLO: 与推送行;4B 大端长度+UTF8 分帧
CanFrame/CanFrameDisplayCanSimulator / DeviceDebug总线帧模型 / 面板显示行

9 类引用关系总图

        [自动化]  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(进程拓扑/协议/应答器/多设备/心跳全景)。