仪器注册 & 硬件调试 调用关系梳理

TapProject · Plugin.DeviceConfig ↔ Plugin.DeviceDebug 交互架构说明
目录 1. 整体架构(三层) 2. 数据层耦合:两个插件通过 data.db 共享 3. 完整调用链路(用户视角) 4. 关键接口依赖(Aging.Infrastructure) 5. 会话状态机(跨模块的核心协调点) 6. 集成测试项目里的组合演示 7. 时序图:完整数据流动 8. 核心设计思想

1. 整体架构(三层)

graph TB subgraph UI层["UI 层 - Plugin.*"] A["Plugin.DeviceConfig
仪器仪表注册"] B["Plugin.DeviceDebug
硬件调试"] B1["PowerSupplyDebugPanel"] B2["CanDebugPanel"] B3["PlcDebugPanel"] end subgraph 基础设施层["Aging.Infrastructure
共享基础设施"] C["IDeviceManager
设备生命周期"] D["IDeviceService
命令执行"] E["IResourceManager
资源预约/租约"] F["DebugSessionManager
调试会话状态机"] G["DriverFactory
驱动工厂"] H["IDeviceDriver
CAN/ModBus/S7/SCPI/Tcp"] end subgraph 数据层["SQLite"] DB1[("data.db
DeviceDefinition
DeviceChannel
SlotDeviceMapping
DeviceConfigVersion
DeviceGroupTemplate")] DB2[("debug.db
debug_session
debug_operation")] end A -->|Save/Query| DB1 B -->|读设备列表| DB1 B -->|写会话/审计| DB2 B --> B1 & B2 & B3 B -->|Connect/Command| C C -->|Create| G G -->|Instantiate| H B -->|Validate maintenance| F F -->|Reserve/Release| E D -->|SendCommandAsync| H
三层职责:

2. 数据层耦合:两个插件通过 data.db 共享

最关键的设计: DeviceConfig 是生产方(写入),DeviceDebug 是消费方(读取)。 两者不通过 API 直接调用,而是通过共享的 data.db 隐式契约。
表名谁写入谁读取说明
DeviceDefinition DeviceConfig DeviceDebug 设备主表(DeviceId / Kind / Transport / Endpoint / DriverType)
DeviceChannel DeviceConfig DeviceDebug 设备通道(ChannelId / Capability / 电压电流上限)
SlotDeviceMapping DeviceConfig DeviceDebug Slot ↔ 设备通道 的映射表
DeviceConfigVersion DeviceConfig DeviceDebug 配置版本(Draft/Published/Effective)
debug_session DeviceDebug DeviceDebug 调试会话(含状态机数据)
debug_operation DeviceDebug DeviceDebug 调试操作审计日志

共享机制源码

DeviceDebug.xaml.cs 第 95 行附近:
_deviceConfigDbPath = Path.Combine(BaseFolder, "data.db");   // 与 DeviceConfig 同目录
var sqliteHelper = new SqliteHelper(_deviceConfigDbPath);
var dt = sqliteHelper.GetDataTable(
    "SELECT DeviceId, Kind FROM DeviceDefinition ORDER BY DeviceId");
关键点:BaseFolder 是主程序在 PluginLoader 里统一注入的目录,两个插件都读写同一个 data.db。

3. 完整调用链路(用户视角)

场景 A:注册设备(DeviceConfig)

sequenceDiagram participant U as 用户 participant UI as DeviceConfig
UI participant M as DeviceEditModal participant Model as DeviceConfigModel participant DB as data.db U->>UI: 点击"新增设备" UI->>M: OpenDeviceEditModal(new DeviceDefinition) M->>M: 填表 + 通道配置 U->>M: 点击"保存" M->>Model: SaveDevice(device) Model->>DB: INSERT OR REPLACE DeviceDefinition M->>Model: SaveChannel(ch) Model->>DB: INSERT OR REPLACE DeviceChannel M-->>UI: DialogResult = true UI->>UI: RefreshAllData()

场景 B:调试设备(DeviceDebug)

sequenceDiagram participant U as 用户 participant DD as DeviceDebug
UI participant SM as DebugSessionManager participant DM as DeviceManager participant DF as DriverFactory participant DRV as CanDriverAdapter participant SIM as CanSimulator participant DB as SQLite U->>DD: 选择 CAN-001 下拉项 DD->>DB: SELECT * FROM DeviceDefinition
(读取 data.db) DD->>SM: OpenSession(...) SM->>DB: INSERT debug_session (state=ReadOnly) DD->>DM: ConnectAsync("CAN-001") DM->>DF: CreateDriver("CAN", "CAN_BUS_1:0") DF->>DRV: new CanDriverAdapter DRV->>SIM: ConnectAsync → 注册节点到总线 Note over DD: 加载 CanDebugPanel
AutoConnectOnLoad=true U->>DD: 进入维护模式 DD->>SM: RequestMaintenance(sessionId) SM->>DB: UPDATE debug_session SET state=Acquiring SM->>SM: ResourceManager.Reserve(...) DD->>SM: ActivateMaintenance(sessionId) SM->>DB: UPDATE debug_session SET state=MaintenanceActive DD->>DD: LoadPanel 重新加载
CanDebugPanel.IsMaintenanceMode = true U->>DD: 发送 CAN 帧 DD->>SM: ValidateCommand(sessionId, cmd) SM-->>DD: true DD->>DRV: SendCommandAsync("SEND:0x100:AA BB CC DD") DRV->>SIM: CanNode.SendFrame(frame) SIM-->>DRV: DriverFrameReceived (自动响应) DRV-->>DD: 事件回调 DD->>DB: INSERT debug_operation (审计)

4. 关键接口依赖(Aging.Infrastructure)

说明:DeviceConfig 只做数据管理,不直接调用接口;DeviceDebug 通过接口调用底层服务。
接口定义文件谁用作用
IDeviceDriver Contracts/IDeviceDriver.cs 所有驱动适配器 统一的连接/命令/查询接口
IDeviceManager Contracts/IDeviceManager.cs DeviceDebug 设备生命周期管理(注册/连接/断开)
IDeviceService Contracts/IDeviceService.cs DeviceDebug 命令执行(含 OwnerToken 权限检查)
IResourceManager Contracts/IResourceManager.cs DebugSessionManager 资源预约/释放,防止调试与生产冲突
IOperationLease Contracts/IOperationLease.cs ResourceManager 操作租约(超时自动释放)
ResourceOwnerToken Contracts/ResourceOwnerToken.cs 整个基础设施层 Owner 类型 + ID(如 Maintenance:sessionId)

DriverFactory 支持的传输类型

TransportAdapter底层依赖Endpoint 格式
TcpTcpRawDriverAdapterConnectionUtil.TcpClientIP:Port
SCPIScpiDriverAdapterSCPI over TCPIP:Port
ModBusModBusDriverAdapterHslCommunication.ModbusTcpNetIP:Port:slaveId
S7S7DriverAdapterS7netplusIP:Port
CANCanDriverAdapterCanSimulatorBusName:NodeIndex

5. 会话状态机(跨模块的核心协调点)

stateDiagram-v2 [*] --> ReadOnly: OpenSession ReadOnly --> AcquiringMaintenance: RequestMaintenance
(检查资源预约) AcquiringMaintenance --> MaintenanceActive: ActivateMaintenance AcquiringMaintenance --> ReadOnly: 拒绝
(被生产预约) MaintenanceActive --> Closing: CloseSession
(安全状态检查) Closing --> Closed Closed --> [*] MaintenanceActive --> Faulted: FaultSession
(通信/硬件错误) Faulted --> RecoveryRequired: MarkRecoveryRequired RecoveryRequired --> ReadOnly: 恢复后重新连接 RecoveryRequired --> [*]: 无法恢复则关闭

状态对上层的影响

状态可发送命令UI 显示资源占用
ReadOnly❌ 只读监控面板按钮禁用无
AcquiringMaintenance❌ 申请中过渡状态正在抢占
MaintenanceActive✅ 允许写面板按钮启用持有 Maintenance 租约
Closing❌ 关闭中过渡状态释放中
Faulted❌ 故障红色告警已释放
RecoveryRequired❌ 待恢复橙色警告无
Closed—会话结束无
设计巧妙之处:会话状态机同时决定了

6. 集成测试项目里的组合演示

graph LR subgraph "Plugin.IntegrationTest
_testDir = %LOCALAPPDATA%/PluginIntegrationTest" Tab1["Tab 1: DeviceConfig
(嵌入实例)"] Tab2["Tab 2: DeviceDebug
(嵌入实例)"] Tab3["Tab 3: CanDebugPanel
(独立实例)"] DB[("data.db
+ debug.db")] end Tab1 -->|写入| DB Tab2 -->|读取| DB Tab3 -->|驱动调用| SIM["CanSimulator
(内存虚拟总线)"]
关键点:三个 Tab 共享同一个 _testDir 作为 BaseFolder,所以在 Tab 1 添加的设备 → 刷新后 Tab 2 立即可见 → 选中即自动连接 Tab 3 的模拟总线。

测试数据流

  1. 预置阶段:SeedPresetCanDevices() 写入 CAN-001 / CAN-002
  2. 注册阶段:在 Tab 1 中新增设备(如 CAN-003 → CAN_BUS_1:2)
  3. 调试阶段:在 Tab 2 中选择 CAN-003,CanDebugPanel 自动连接总线
  4. 模拟阶段:在 Tab 3 中启动总线,开启自动响应
  5. 验证阶段:Tab 2 发送帧 → 收到 Tab 3 的自动响应

7. 时序图:完整数据流动

sequenceDiagram autonumber participant U as 用户 participant DC as DeviceConfig
UI participant DM1 as DeviceConfigModel participant DB1 as data.db participant DD as DeviceDebug
UI participant SM as SessionManager participant DM2 as DeviceDebugModel participant DB2 as debug.db participant MGR as DeviceManager participant SVC as DeviceService participant DRV as CanDriverAdapter participant SIM as CanSimulator
总线 rect rgb(255, 248, 225) Note over U,DB1: 阶段1:注册设备 U->>DC: 新增 CAN-001 DC->>DM1: SaveDevice DM1->>DB1: INSERT DeviceDefinition
Endpoint=CAN_BUS_1:0 end rect rgb(227, 242, 253) Note over U,SIM: 阶段2:打开调试会话 U->>DD: 选择 CAN-001 DD->>DB1: SELECT DeviceDefinition DB1-->>DD: {Kind=CanDevice, Endpoint=CAN_BUS_1:0} DD->>SM: OpenSession SM->>DM2: CreateSession DM2->>DB2: INSERT debug_session (ReadOnly) DD->>MGR: ConnectAsync("CAN-001") MGR->>DRV: DriverFactory.CreateDriver("CAN",...) DRV->>SIM: 注册节点0到CAN_BUS_1总线 end rect rgb(232, 245, 233) Note over U,DRV: 阶段3:进入维护模式 U->>DD: 进入维护 DD->>SM: RequestMaintenance SM->>DB2: UPDATE state=Acquiring SM->>SM: ResourceManager.Reserve DD->>SM: ActivateMaintenance SM->>DB2: UPDATE state=MaintenanceActive DD->>DD: LoadPanel(kind)
IsMaintenanceMode=true end rect rgb(255, 224, 178) Note over U,SIM: 阶段4:发送调试命令 U->>DD: 点击"发送"帧 DD->>SM: ValidateCommand(sessionId, cmd) SM-->>DD: ✅ true DD->>SVC: ExecuteCommandAsync SVC->>DRV: SendCommandAsync("SEND:0x100:AA BB CC") DRV->>SIM: CanNode.SendFrame(frame) SIM->>SIM: 路由到其他节点
+ 检查 AutoResponse SIM-->>DRV: DriverFrameReceived (响应帧) DRV-->>SVC: 结果 SVC-->>DD: ExecuteResult DD->>DM2: RecordOperation(op) DM2->>DB2: INSERT debug_operation DD->>DD: 更新审计列表 end rect rgb(255, 205, 210) Note over U,DB2: 阶段5:关闭会话 U->>DD: 退出维护 DD->>SM: CloseSession SM->>DB2: UPDATE state=Closed
SET closed_at=now SM->>SM: ResourceManager.ReleaseReservation SM->>DRV: DisconnectAsync DRV->>SIM: 从总线注销节点 end

8. 核心设计思想

🎯 数据共享 = 隐式契约

DeviceDebug 只依赖 DeviceConfig 写好的 data.db,不需要 API 调用。两个插件的 BaseFolder 一致是核心前提,主程序通过 PluginLoader 注入。

🎯 状态集中 = DebugSessionManager

所有权限检查走同一个状态机。UI、驱动、审计三处都通过状态查询同步。避免各处零散判断导致不一致。

🎯 驱动抽象 = IDeviceDriver

上层面板不知道底层是 TCP、Modbus、S7、SCPI 还是模拟 CAN 总线。DriverFactory.CreateDevice(id, transport, endpoint) 统一分派。

🎯 资源预约 = IResourceManager

调试模式和生产模式互斥,通过 ResourceOwnerToken + 租约机制保证:
- 生产任务运行中 → 调试请求失败
- 调试持有租约 → 生产任务被拒绝
- 租约超时未续 → 强制释放,避免死锁

🎯 审计闭环 = debug_operation

每一次命令都记录:SessionId · CommandType · Parameters · Outcome · Readback · Error。事后可以完整重现调试过程,是维护责任追溯的关键。

🎯 版本管理 = DeviceConfigVersion

配置不是"改了就生效",而是走 Draft → Published → Effective 三阶段:
  1. DeviceConfig 保存草稿 → Draft
  2. 提交发布 → Published(附带配置哈希)
  3. 应用到生产 → Effective(旧的 Published 回退)
这样能保证生产环境配置稳定,调试时可回滚。

附录:文件路径速查

组件路径
仪器仪表注册插件d:\TestPlatform\TapProject\Plugin.DeviceConfig\
硬件调试插件d:\TestPlatform\TapProject\Plugin.DeviceDebug\
基础设施 & 驱动d:\TestPlatform\TapProject\Aging.Infrastructure\
CAN 模拟器核心d:\TestPlatform\TapProject\CanSimulator\
集成测试环境d:\TestPlatform\TapProject\Plugin.IntegrationTest\
持久化数据目录%LOCALAPPDATA%\PluginIntegrationTest\data.db