<!DOCTYPE html>
|
<html lang="zh-CN">
|
<head>
|
<meta charset="UTF-8">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<title>仪器注册与硬件调试调用关系梳理</title>
|
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
|
<style>
|
* { box-sizing: border-box; }
|
body {
|
font-family: -apple-system, "Segoe UI", "Microsoft YaHei", "PingFang SC", sans-serif;
|
max-width: 1200px;
|
margin: 0 auto;
|
padding: 32px 24px 80px;
|
background: #FAFAFA;
|
color: #212121;
|
line-height: 1.75;
|
}
|
h1 {
|
font-size: 32px;
|
color: #1565C0;
|
border-bottom: 3px solid #1976D2;
|
padding-bottom: 12px;
|
margin-bottom: 8px;
|
}
|
h2 {
|
font-size: 24px;
|
color: #0D47A1;
|
margin-top: 40px;
|
padding-left: 12px;
|
border-left: 4px solid #FF9800;
|
}
|
h3 {
|
font-size: 18px;
|
color: #37474F;
|
margin-top: 24px;
|
}
|
.subtitle {
|
color: #757575;
|
font-size: 14px;
|
margin-bottom: 24px;
|
}
|
.card {
|
background: white;
|
border-radius: 8px;
|
padding: 20px 24px;
|
margin: 16px 0;
|
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
}
|
.highlight {
|
background: #FFF8E1;
|
border-left: 4px solid #FFC107;
|
padding: 12px 16px;
|
margin: 12px 0;
|
border-radius: 4px;
|
}
|
.info {
|
background: #E3F2FD;
|
border-left: 4px solid #2196F3;
|
padding: 12px 16px;
|
margin: 12px 0;
|
border-radius: 4px;
|
}
|
.success {
|
background: #E8F5E9;
|
border-left: 4px solid #4CAF50;
|
padding: 12px 16px;
|
margin: 12px 0;
|
border-radius: 4px;
|
}
|
table {
|
width: 100%;
|
border-collapse: collapse;
|
margin: 16px 0;
|
background: white;
|
border-radius: 6px;
|
overflow: hidden;
|
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
}
|
th {
|
background: #1976D2;
|
color: white;
|
padding: 12px 14px;
|
text-align: left;
|
font-weight: 600;
|
}
|
td {
|
padding: 10px 14px;
|
border-bottom: 1px solid #EEEEEE;
|
}
|
tr:nth-child(even) td { background: #FAFAFA; }
|
tr:hover td { background: #E3F2FD; }
|
code {
|
font-family: "Consolas", "Monaco", monospace;
|
background: #F5F5F5;
|
color: #C62828;
|
padding: 2px 6px;
|
border-radius: 3px;
|
font-size: 13px;
|
}
|
pre {
|
background: #263238;
|
color: #ECEFF1;
|
padding: 16px 20px;
|
border-radius: 6px;
|
overflow-x: auto;
|
font-family: "Consolas", "Monaco", monospace;
|
font-size: 13px;
|
line-height: 1.6;
|
}
|
pre code {
|
background: transparent;
|
color: inherit;
|
padding: 0;
|
}
|
.mermaid {
|
background: white;
|
padding: 20px;
|
border-radius: 8px;
|
margin: 20px 0;
|
text-align: center;
|
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
overflow-x: auto;
|
}
|
ul { padding-left: 22px; }
|
li { margin: 4px 0; }
|
.badge {
|
display: inline-block;
|
padding: 2px 8px;
|
border-radius: 10px;
|
font-size: 12px;
|
font-weight: 600;
|
margin: 0 3px;
|
}
|
.badge-write { background: #FFCDD2; color: #B71C1C; }
|
.badge-read { background: #C8E6C9; color: #1B5E20; }
|
.badge-api { background: #BBDEFB; color: #0D47A1; }
|
.toc {
|
background: #F5F5F5;
|
padding: 16px 24px;
|
border-radius: 6px;
|
margin: 20px 0;
|
}
|
.toc a {
|
color: #1976D2;
|
text-decoration: none;
|
display: block;
|
padding: 4px 0;
|
}
|
.toc a:hover { text-decoration: underline; }
|
.arrow { color: #FF9800; font-weight: bold; }
|
.flow-step {
|
background: white;
|
border: 1px solid #E0E0E0;
|
border-radius: 6px;
|
padding: 12px 16px;
|
margin: 8px 0;
|
}
|
.flow-step strong { color: #1976D2; }
|
footer {
|
margin-top: 60px;
|
padding-top: 20px;
|
border-top: 1px solid #E0E0E0;
|
color: #757575;
|
font-size: 12px;
|
text-align: center;
|
}
|
</style>
|
</head>
|
<body>
|
|
<h1>仪器注册 & 硬件调试 调用关系梳理</h1>
|
<div class="subtitle">TapProject · Plugin.DeviceConfig ↔ Plugin.DeviceDebug 交互架构说明</div>
|
|
<div class="toc">
|
<strong>目录</strong>
|
<a href="#sec1">1. 整体架构(三层)</a>
|
<a href="#sec2">2. 数据层耦合:两个插件通过 data.db 共享</a>
|
<a href="#sec3">3. 完整调用链路(用户视角)</a>
|
<a href="#sec4">4. 关键接口依赖(Aging.Infrastructure)</a>
|
<a href="#sec5">5. 会话状态机(跨模块的核心协调点)</a>
|
<a href="#sec6">6. 集成测试项目里的组合演示</a>
|
<a href="#sec7">7. 时序图:完整数据流动</a>
|
<a href="#sec8">8. 核心设计思想</a>
|
</div>
|
|
<!-- ========== Section 1 ========== -->
|
<h2 id="sec1">1. 整体架构(三层)</h2>
|
|
<div class="mermaid">
|
graph TB
|
subgraph UI层["UI 层 - Plugin.*"]
|
A["Plugin.DeviceConfig<br/>仪器仪表注册"]
|
B["Plugin.DeviceDebug<br/>硬件调试"]
|
B1["PowerSupplyDebugPanel"]
|
B2["CanDebugPanel"]
|
B3["PlcDebugPanel"]
|
end
|
|
subgraph 基础设施层["Aging.Infrastructure<br/>共享基础设施"]
|
C["IDeviceManager<br/>设备生命周期"]
|
D["IDeviceService<br/>命令执行"]
|
E["IResourceManager<br/>资源预约/租约"]
|
F["DebugSessionManager<br/>调试会话状态机"]
|
G["DriverFactory<br/>驱动工厂"]
|
H["IDeviceDriver<br/>CAN/ModBus/S7/SCPI/Tcp"]
|
end
|
|
subgraph 数据层["SQLite"]
|
DB1[("data.db<br/>DeviceDefinition<br/>DeviceChannel<br/>SlotDeviceMapping<br/>DeviceConfigVersion<br/>DeviceGroupTemplate")]
|
DB2[("debug.db<br/>debug_session<br/>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
|
</div>
|
|
<div class="info">
|
<strong>三层职责:</strong>
|
<ul>
|
<li><strong>UI 层</strong>:插件的 WPF 界面,负责数据展示和用户交互</li>
|
<li><strong>基础设施层</strong>:Aging.Infrastructure 提供的接口/服务,负责驱动调度、权限、会话管理</li>
|
<li><strong>数据层</strong>:SQLite 数据库,负责持久化设备配置和调试审计</li>
|
</ul>
|
</div>
|
|
<!-- ========== Section 2 ========== -->
|
<h2 id="sec2">2. 数据层耦合:两个插件通过 <code>data.db</code> 共享</h2>
|
|
<div class="highlight">
|
<strong>最关键的设计:</strong> DeviceConfig 是<strong>生产方</strong>(写入),DeviceDebug 是<strong>消费方</strong>(读取)。
|
两者不通过 API 直接调用,而是通过共享的 <code>data.db</code> 隐式契约。
|
</div>
|
|
<table>
|
<thead>
|
<tr><th>表名</th><th>谁写入</th><th>谁读取</th><th>说明</th></tr>
|
</thead>
|
<tbody>
|
<tr>
|
<td><code>DeviceDefinition</code></td>
|
<td><span class="badge badge-write">DeviceConfig</span></td>
|
<td><span class="badge badge-read">DeviceDebug</span></td>
|
<td>设备主表(DeviceId / Kind / Transport / Endpoint / DriverType)</td>
|
</tr>
|
<tr>
|
<td><code>DeviceChannel</code></td>
|
<td><span class="badge badge-write">DeviceConfig</span></td>
|
<td><span class="badge badge-read">DeviceDebug</span></td>
|
<td>设备通道(ChannelId / Capability / 电压电流上限)</td>
|
</tr>
|
<tr>
|
<td><code>SlotDeviceMapping</code></td>
|
<td><span class="badge badge-write">DeviceConfig</span></td>
|
<td><span class="badge badge-read">DeviceDebug</span></td>
|
<td>Slot ↔ 设备通道 的映射表</td>
|
</tr>
|
<tr>
|
<td><code>DeviceConfigVersion</code></td>
|
<td><span class="badge badge-write">DeviceConfig</span></td>
|
<td><span class="badge badge-read">DeviceDebug</span></td>
|
<td>配置版本(Draft/Published/Effective)</td>
|
</tr>
|
<tr>
|
<td><code>debug_session</code></td>
|
<td><span class="badge badge-write">DeviceDebug</span></td>
|
<td><span class="badge badge-read">DeviceDebug</span></td>
|
<td>调试会话(含状态机数据)</td>
|
</tr>
|
<tr>
|
<td><code>debug_operation</code></td>
|
<td><span class="badge badge-write">DeviceDebug</span></td>
|
<td><span class="badge badge-read">DeviceDebug</span></td>
|
<td>调试操作审计日志</td>
|
</tr>
|
</tbody>
|
</table>
|
|
<h3>共享机制源码</h3>
|
|
<div class="flow-step">
|
<strong>DeviceDebug.xaml.cs 第 95 行附近:</strong>
|
<pre><code>_deviceConfigDbPath = Path.Combine(BaseFolder, "data.db"); <span style="color:#4CAF50">// 与 DeviceConfig 同目录</span>
|
var sqliteHelper = new SqliteHelper(_deviceConfigDbPath);
|
var dt = sqliteHelper.GetDataTable(
|
"SELECT DeviceId, Kind FROM DeviceDefinition ORDER BY DeviceId");</code></pre>
|
</div>
|
|
<div class="flow-step">
|
<strong>关键点:</strong>BaseFolder 是主程序在 <code>PluginLoader</code> 里统一注入的目录,两个插件都读写同一个 <code>data.db</code>。
|
</div>
|
|
<!-- ========== Section 3 ========== -->
|
<h2 id="sec3">3. 完整调用链路(用户视角)</h2>
|
|
<h3>场景 A:注册设备(DeviceConfig)</h3>
|
|
<div class="mermaid">
|
sequenceDiagram
|
participant U as 用户
|
participant UI as DeviceConfig<br/>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()
|
</div>
|
|
<h3>场景 B:调试设备(DeviceDebug)</h3>
|
|
<div class="mermaid">
|
sequenceDiagram
|
participant U as 用户
|
participant DD as DeviceDebug<br/>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<br/>(读取 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<br/>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 重新加载<br/>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 (审计)
|
</div>
|
|
<!-- ========== Section 4 ========== -->
|
<h2 id="sec4">4. 关键接口依赖(Aging.Infrastructure)</h2>
|
|
<div class="card">
|
<strong>说明:</strong>DeviceConfig 只做数据管理,不直接调用接口;DeviceDebug 通过接口调用底层服务。
|
</div>
|
|
<table>
|
<thead>
|
<tr><th>接口</th><th>定义文件</th><th>谁用</th><th>作用</th></tr>
|
</thead>
|
<tbody>
|
<tr>
|
<td><code>IDeviceDriver</code></td>
|
<td>Contracts/IDeviceDriver.cs</td>
|
<td>所有驱动适配器</td>
|
<td>统一的连接/命令/查询接口</td>
|
</tr>
|
<tr>
|
<td><code>IDeviceManager</code></td>
|
<td>Contracts/IDeviceManager.cs</td>
|
<td>DeviceDebug</td>
|
<td>设备生命周期管理(注册/连接/断开)</td>
|
</tr>
|
<tr>
|
<td><code>IDeviceService</code></td>
|
<td>Contracts/IDeviceService.cs</td>
|
<td>DeviceDebug</td>
|
<td>命令执行(含 OwnerToken 权限检查)</td>
|
</tr>
|
<tr>
|
<td><code>IResourceManager</code></td>
|
<td>Contracts/IResourceManager.cs</td>
|
<td>DebugSessionManager</td>
|
<td>资源预约/释放,防止调试与生产冲突</td>
|
</tr>
|
<tr>
|
<td><code>IOperationLease</code></td>
|
<td>Contracts/IOperationLease.cs</td>
|
<td>ResourceManager</td>
|
<td>操作租约(超时自动释放)</td>
|
</tr>
|
<tr>
|
<td><code>ResourceOwnerToken</code></td>
|
<td>Contracts/ResourceOwnerToken.cs</td>
|
<td>整个基础设施层</td>
|
<td>Owner 类型 + ID(如 Maintenance:sessionId)</td>
|
</tr>
|
</tbody>
|
</table>
|
|
<h3>DriverFactory 支持的传输类型</h3>
|
|
<table>
|
<thead>
|
<tr><th>Transport</th><th>Adapter</th><th>底层依赖</th><th>Endpoint 格式</th></tr>
|
</thead>
|
<tbody>
|
<tr><td>Tcp</td><td>TcpRawDriverAdapter</td><td>ConnectionUtil.TcpClient</td><td>IP:Port</td></tr>
|
<tr><td>SCPI</td><td>ScpiDriverAdapter</td><td>SCPI over TCP</td><td>IP:Port</td></tr>
|
<tr><td>ModBus</td><td>ModBusDriverAdapter</td><td>HslCommunication.ModbusTcpNet</td><td>IP:Port:slaveId</td></tr>
|
<tr><td>S7</td><td>S7DriverAdapter</td><td>S7netplus</td><td>IP:Port</td></tr>
|
<tr><td><strong>CAN</strong></td><td><strong>CanDriverAdapter</strong></td><td><strong>CanSimulator</strong></td><td><strong>BusName:NodeIndex</strong></td></tr>
|
</tbody>
|
</table>
|
|
<!-- ========== Section 5 ========== -->
|
<h2 id="sec5">5. 会话状态机(跨模块的核心协调点)</h2>
|
|
<div class="mermaid">
|
stateDiagram-v2
|
[*] --> ReadOnly: OpenSession
|
ReadOnly --> AcquiringMaintenance: RequestMaintenance<br/>(检查资源预约)
|
AcquiringMaintenance --> MaintenanceActive: ActivateMaintenance
|
AcquiringMaintenance --> ReadOnly: 拒绝<br/>(被生产预约)
|
MaintenanceActive --> Closing: CloseSession<br/>(安全状态检查)
|
Closing --> Closed
|
Closed --> [*]
|
|
MaintenanceActive --> Faulted: FaultSession<br/>(通信/硬件错误)
|
Faulted --> RecoveryRequired: MarkRecoveryRequired
|
RecoveryRequired --> ReadOnly: 恢复后重新连接
|
RecoveryRequired --> [*]: 无法恢复则关闭
|
</div>
|
|
<h3>状态对上层的影响</h3>
|
|
<table>
|
<thead>
|
<tr><th>状态</th><th>可发送命令</th><th>UI 显示</th><th>资源占用</th></tr>
|
</thead>
|
<tbody>
|
<tr><td>ReadOnly</td><td>❌ 只读监控</td><td>面板按钮禁用</td><td>无</td></tr>
|
<tr><td>AcquiringMaintenance</td><td>❌ 申请中</td><td>过渡状态</td><td>正在抢占</td></tr>
|
<tr><td>MaintenanceActive</td><td>✅ 允许写</td><td>面板按钮启用</td><td>持有 Maintenance 租约</td></tr>
|
<tr><td>Closing</td><td>❌ 关闭中</td><td>过渡状态</td><td>释放中</td></tr>
|
<tr><td>Faulted</td><td>❌ 故障</td><td>红色告警</td><td>已释放</td></tr>
|
<tr><td>RecoveryRequired</td><td>❌ 待恢复</td><td>橙色警告</td><td>无</td></tr>
|
<tr><td>Closed</td><td>—</td><td>会话结束</td><td>无</td></tr>
|
</tbody>
|
</table>
|
|
<div class="success">
|
<strong>设计巧妙之处:</strong>会话状态机同时决定了
|
<ul>
|
<li>UI 层:CanDebugPanel.IsMaintenanceMode → 发送按钮是否可点</li>
|
<li>业务层:DeviceService 是否接受该命令 → OwnerToken 权限验证</li>
|
<li>资源层:ResourceManager 是否仍持有租约 → 防止被生产抢占</li>
|
</ul>
|
</div>
|
|
<!-- ========== Section 6 ========== -->
|
<h2 id="sec6">6. 集成测试项目里的组合演示</h2>
|
|
<div class="mermaid">
|
graph LR
|
subgraph "Plugin.IntegrationTest<br/>_testDir = %LOCALAPPDATA%/PluginIntegrationTest"
|
Tab1["Tab 1: DeviceConfig<br/>(嵌入实例)"]
|
Tab2["Tab 2: DeviceDebug<br/>(嵌入实例)"]
|
Tab3["Tab 3: CanDebugPanel<br/>(独立实例)"]
|
DB[("data.db<br/>+ debug.db")]
|
end
|
|
Tab1 -->|写入| DB
|
Tab2 -->|读取| DB
|
Tab3 -->|驱动调用| SIM["CanSimulator<br/>(内存虚拟总线)"]
|
</div>
|
|
<div class="info">
|
<strong>关键点:</strong>三个 Tab 共享同一个 <code>_testDir</code> 作为 BaseFolder,所以在 Tab 1 添加的设备 → 刷新后 Tab 2 立即可见 → 选中即自动连接 Tab 3 的模拟总线。
|
</div>
|
|
<h3>测试数据流</h3>
|
<ol>
|
<li><strong>预置阶段</strong>:<code>SeedPresetCanDevices()</code> 写入 CAN-001 / CAN-002</li>
|
<li><strong>注册阶段</strong>:在 Tab 1 中新增设备(如 CAN-003 → CAN_BUS_1:2)</li>
|
<li><strong>调试阶段</strong>:在 Tab 2 中选择 CAN-003,CanDebugPanel 自动连接总线</li>
|
<li><strong>模拟阶段</strong>:在 Tab 3 中启动总线,开启自动响应</li>
|
<li><strong>验证阶段</strong>:Tab 2 发送帧 → 收到 Tab 3 的自动响应</li>
|
</ol>
|
|
<!-- ========== Section 7 ========== -->
|
<h2 id="sec7">7. 时序图:完整数据流动</h2>
|
|
<div class="mermaid">
|
sequenceDiagram
|
autonumber
|
participant U as 用户
|
participant DC as DeviceConfig<br/>UI
|
participant DM1 as DeviceConfigModel
|
participant DB1 as data.db
|
participant DD as DeviceDebug<br/>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<br/>总线
|
|
rect rgb(255, 248, 225)
|
Note over U,DB1: 阶段1:注册设备
|
U->>DC: 新增 CAN-001
|
DC->>DM1: SaveDevice
|
DM1->>DB1: INSERT DeviceDefinition<br/>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)<br/>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: 路由到其他节点<br/>+ 检查 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<br/>SET closed_at=now
|
SM->>SM: ResourceManager.ReleaseReservation
|
SM->>DRV: DisconnectAsync
|
DRV->>SIM: 从总线注销节点
|
end
|
</div>
|
|
<!-- ========== Section 8 ========== -->
|
<h2 id="sec8">8. 核心设计思想</h2>
|
|
<div class="card">
|
<h3>🎯 数据共享 = 隐式契约</h3>
|
DeviceDebug 只依赖 DeviceConfig 写好的 <code>data.db</code>,不需要 API 调用。两个插件的 <strong>BaseFolder 一致</strong>是核心前提,主程序通过 PluginLoader 注入。
|
</div>
|
|
<div class="card">
|
<h3>🎯 状态集中 = DebugSessionManager</h3>
|
所有权限检查走同一个状态机。UI、驱动、审计三处都通过状态查询同步。避免各处零散判断导致不一致。
|
</div>
|
|
<div class="card">
|
<h3>🎯 驱动抽象 = IDeviceDriver</h3>
|
上层面板不知道底层是 TCP、Modbus、S7、SCPI 还是模拟 CAN 总线。<code>DriverFactory.CreateDevice(id, transport, endpoint)</code> 统一分派。
|
</div>
|
|
<div class="card">
|
<h3>🎯 资源预约 = IResourceManager</h3>
|
调试模式和生产模式互斥,通过 <code>ResourceOwnerToken</code> + 租约机制保证:<br>
|
- 生产任务运行中 → 调试请求失败<br>
|
- 调试持有租约 → 生产任务被拒绝<br>
|
- 租约超时未续 → 强制释放,避免死锁
|
</div>
|
|
<div class="card">
|
<h3>🎯 审计闭环 = debug_operation</h3>
|
每一次命令都记录:<code>SessionId · CommandType · Parameters · Outcome · Readback · Error</code>。事后可以完整重现调试过程,是维护责任追溯的关键。
|
</div>
|
|
<div class="card">
|
<h3>🎯 版本管理 = DeviceConfigVersion</h3>
|
配置不是"改了就生效",而是走 <strong>Draft → Published → Effective</strong> 三阶段:
|
<ol>
|
<li>DeviceConfig 保存草稿 → Draft</li>
|
<li>提交发布 → Published(附带配置哈希)</li>
|
<li>应用到生产 → Effective(旧的 Published 回退)</li>
|
</ol>
|
这样能保证生产环境配置稳定,调试时可回滚。
|
</div>
|
|
<h2>附录:文件路径速查</h2>
|
|
<table>
|
<thead>
|
<tr><th>组件</th><th>路径</th></tr>
|
</thead>
|
<tbody>
|
<tr><td>仪器仪表注册插件</td><td><code>d:\TestPlatform\TapProject\Plugin.DeviceConfig\</code></td></tr>
|
<tr><td>硬件调试插件</td><td><code>d:\TestPlatform\TapProject\Plugin.DeviceDebug\</code></td></tr>
|
<tr><td>基础设施 & 驱动</td><td><code>d:\TestPlatform\TapProject\Aging.Infrastructure\</code></td></tr>
|
<tr><td>CAN 模拟器核心</td><td><code>d:\TestPlatform\TapProject\CanSimulator\</code></td></tr>
|
<tr><td>集成测试环境</td><td><code>d:\TestPlatform\TapProject\Plugin.IntegrationTest\</code></td></tr>
|
<tr><td>持久化数据目录</td><td><code>%LOCALAPPDATA%\PluginIntegrationTest\data.db</code></td></tr>
|
</tbody>
|
</table>
|
|
<footer>
|
Generated for TapProject · 仪器注册与硬件调试模块架构分析 · 2026-11-02
|
</footer>
|
|
<script>
|
mermaid.initialize({
|
startOnLoad: true,
|
theme: 'default',
|
securityLevel: 'loose',
|
fontFamily: 'Segoe UI, Microsoft YaHei, sans-serif',
|
flowchart: { curve: 'basis', padding: 20 },
|
sequence: { showSequenceNumbers: false, actorMargin: 60 }
|
});
|
</script>
|
|
</body>
|
</html>
|