Files
DCU_DesktopController/docs/compose/spec/device-link.md
T
cbb f9502579ef feat(link): 外壳启动强制 open+motor/start,不必等电机页切 EtherCAT
DeviceLink Owner 在控制台加载后自动 bootstrapEcatBus:status→open→
motor/start,沿用 hub 网卡与 period/spin 持久化。成功后不重复 start;
掉线 10s 退避重开。有线 ECAT 与 WiFi 不冲突。
2026-09-16 12:24:45 +08:00

101 lines
4.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
feature: device-link
status: delivered
updated: 2026-09-16
branch: feat/device-link
commits: 353d936..033ece2
---
# Device Link / ConnectionHub(全链路统一连接层)
## Report
**What was built** — 在既有 DeviceLink 健康广播之上扩展为 ConnectionHub:`deviceLink` 统一持有
WiFi IP、EtherCAT 网卡子串与 `ecatPost`,并保留 Owner/Client 快照。OTA(app.js)、CAN、
can_monitor、motor、ecat_load、LAN9252 改为从 Hub 读端点/订在线布尔;BLE 仍经 `reportBle`
上报。连接灯只覆盖布尔,不替换调用方语义文案。
**Verification** —
- `node --check` 全部改动 JS → PASS
- `test_motor_transport.mjs` 58/58 PASS
- `test_motor_refresh.mjs` 62/62 PASS
- `test_ecat_autostart.mjs` 19/19 PASS
- 独立评审后修复 3 个 critical:读路径 setIp 风暴、CAN 文案覆盖、嵌入页误标轮询离线
**Journey log** —
- 「共用 IP」≠「共用连接」——健康态必须有单一 Owner
- Hub subscribe 只覆盖在线布尔,绝不替换调用方语义文案
- 禁止在读路径调 setIp:Owner 会把每次 set-ip 当成立即重探
- 嵌入 can.html 时 hub 不得写 fbPollStatus(那是 feedback 轮询自己的状态)
## [S1] Problem
多 iframe 保活后,各页仍**各自**判定「设备连上了没有」,只是共用一个 IP 字符串:
- OTA 页轮询 `/system/status` → 顶栏「WiFi 已连接」
- LAN9252 页轮询 `/spi/send` → 同一 IP 却显示「连接失败」
- EtherCAT 状态/负载又是一条独立轮询,网卡子串在 4 处重复读 `localStorage`
- BLE 在页内 GATT,其它页完全不知
- CAN / can_monitor / motor 各有一套 `getBaseUrl` 包装
用户直觉是「同一条设备连接」,实现却是 N 条互不证明的 HTTP/蓝牙会话和重复端点配置。
## [S2] Design
### 原则
1. **外壳持有链路健康轮询**(tabs.html Owner),功能页不再各自判定「设备在线」。
2. **BroadcastChannel**(`dcu-device-link`)广播快照;页内只订阅渲染。
3. **端点配置只在 ConnectionHub**:IP(`dcu_esp_ip`)、网卡(`dcu_ecat_adapter`)读写集中。
4. **命令仍页内发**,连接灯只认 DeviceLink 布尔(文案保留调用方语义)。
5. BLE 为一等通道但单独适配:只广播状态,GATT 细节留在 `ble/*`。
6. **外壳启动强制开总线**:Owner 首轮即 `status→open→motor/start`;成功后不再重复
`motor/start`,掉线后 10s 退避重试。有线 EtherCAT 与 WiFi 不争网卡。
### ConnectionHub 客户端 API(`deviceLink`)
| API | 用途 |
|-----|------|
| `subscribe` / `getSnapshot` / `channelOnline` | 健康快照 |
| `getIp` / `setIp` / `wifiBaseUrl` | WiFi 端点 |
| `getEcatAdapter` / `setEcatAdapter` | EtherCAT 网卡子串 |
| `ecatPost(path, body, timeoutMs)` | 统一 `/ecat/api/*` 代理请求 |
| `reportBle` | BLE 状态上报 |
Owner 消息:`hello` / `snapshot` / `set-ip` / `ble-state`。
### 接入
| 位置 | 行为 |
|------|------|
| `tabs.html` | Owner |
| Owner `bootstrapEcatBus` | 外壳启动即 open+motor/start,不必等电机页切 EtherCAT |
| `connection.js` | 快照新鲜时覆盖 connected 布尔 |
| `app.js` | IP/baseURL 走 `deviceLink`(保留 `#espIp` 输入回退) |
| `can/dbc.js` + `can.js` | setDeviceIp 广播;顶栏灯订阅 hub |
| `can_monitor.js` | baseURL 走 base-url/hub;灯订阅 hub |
| `motor/main.js` | IP 订阅 hub 同步 `state.ip` |
| `motor/transport-ecat.js` | 网卡子串经 hub 读写 |
| `ota/ecat_load.js` | `deviceLink.ecatPost` |
| `lab/lan9252_pm.html` | 灯订阅 hub;open 网卡经 hub |
| `lab/spi_test.html` | 共享 IP 键(自包含页不深耦合请求层) |
| `js/ble/gatt.js` | 通知成功后 reportBle(true),失败/断 false |
## [S3] Out of Scope
- 不串行化 SPI/CAN/OTA 命令队列
- 不迁 SharedWorker
- 不改固件协议
- 不把 BLE GATT 强行对齐 HTTP/ECAT 请求接口
- spi_test 保持可整文件删除,不注入业务请求逻辑
## Tasks
- [x] T1: device-link.js Owner+Client + BC 协议 (covers: S2)
- [x] T2: tabs.html 启动 Owner (covers: S2)
- [x] T3: OTA connection.js 接入 DeviceLink (covers: S2)
- [x] T4: LAN9252 灯与 SPI 文案 (covers: S2)
- [x] T5: BLE 状态上报 (covers: S2)
- [x] T6: 语法检查 + 评审修复 (covers: S2)
- [x] T7: Hub 扩展 wifiBaseUrl/ecatPost/adapter API (covers: S2)
- [x] T8: OTA/CAN/motor/ecat_load/lab 迁到 Hub 端点 (covers: S2)