feat(link): ConnectionHub 统一 WiFi/ECAT/BLE 端点与健康态

各页不再各自读写 dcu_esp_ip / dcu_ecat_adapter:deviceLink 提供
wifiBaseUrl/ecatPost/adapter API,OTA/CAN/motor/ecat_load/lab 走同一入口。
连接灯只覆盖在线布尔,保留调用方语义文案。
This commit is contained in:
cbb
2026-09-16 12:12:24 +08:00
parent 353d936abb
commit 033ece2cca
11 changed files with 210 additions and 110 deletions
+45 -28
View File
@@ -3,56 +3,63 @@ feature: device-link
status: delivered
updated: 2026-09-16
branch: feat/device-link
commits: be26f4daa27cba70b4baeaeb3865f38ed51b2e71..6b5a9f9
commits: 353d936..HEAD
---
# Device Link(全链路统一连接层)
# Device Link / ConnectionHub(全链路统一连接层)
## Report
**What was built** — `js/common/device-link.js` 在 `tabs.html` 以 Owner 身份常驻轮询
ESP32 `/system/status` 与 EtherCAT `/ecat/api/status`+`app/load`,经
`BroadcastChannel('dcu-device-link')` 广播快照;功能页 Client 订阅。
`connection.js` 的连接灯在快照 <5s 时只覆盖**在线布尔**,保留「设备忙」等语义文案。
LAN9252 页 SPI 失败显示「设备在线 · SPI 失败」,不再误标设备离线。
BLE GATT 在通知开启成功后 `reportBle(true)`,失败/断开上报 false。
**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`:device-link.js / connection.js / gatt.js PASS
- tabs.html、lan9252_pm 内联 script 抽出 `node --check` PASS
- 独立评审后修复:ECAT `Promise.all` 补 `.catch`+必重挂;去掉跨 iframe 无效的
`window.__dcuDeviceLinkSnap`;BLE 半开连接不再 sticky online
- `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
- 外壳 Owner + 页内 Client 双 BC 实例符合 BroadcastChannel 语义(同上下文不自发自收)
- SPI/业务失败与设备离线必须分文案,否则子系统故障会伪装成掉线
- ECAT 轮询的 reschedule 必须在 `.catch` 之后,否则一次异常永久停摆
- Hub subscribe 只覆盖在线布尔,绝不替换调用方语义文案
- 禁止在读路径调 setIp:Owner 会把每次 set-ip 当成立即重探
- 嵌入 can.html 时 hub 不得写 fbPollStatus(那是 feedback 轮询自己的状态)
## [S1] Problem
多 iframe 保活后,各页仍**各自** `fetch` 设备,只是共用 `localStorage.dcu_esp_ip`:
多 iframe 保活后,各页仍**各自**判定「设备连上了没有」,只是共用一个 IP 字符串:
- OTA 页轮询 `/system/status` → 顶栏「WiFi 已连接」
- LAN9252 页轮询 `/spi/send` → 同一 IP 却显示「连接失败 · signal timed out」
- EtherCAT 负载/状态又是一条独立轮询
- LAN9252 页轮询 `/spi/send` → 同一 IP 却显示「连接失败」
- EtherCAT 状态/负载又是一条独立轮询,网卡子串在 4 处重复读 `localStorage`
- BLE 在页内 GATT,其它页完全不知
- CAN / can_monitor / motor 各有一套 `getBaseUrl` 包装
用户直觉是「同一条设备连接」,实现却是 N 条互不证明的 HTTP/蓝牙会话。
用户直觉是「同一条设备连接」,实现却是 N 条互不证明的 HTTP/蓝牙会话和重复端点配置。
## [S2] Design
### 原则
1. **外壳持有链路健康轮询**(tabs.html 常驻),功能页不再各自判定「设备在线」。
1. **外壳持有链路健康轮询**(tabs.html Owner),功能页不再各自判定「设备在线」。
2. **BroadcastChannel**(`dcu-device-link`)广播快照;页内只订阅渲染。
3. **命令仍页内发**,但**连接灯只认 DeviceLink**(布尔优先,文案保留调用方语义)。
4. SPI 失败 →「设备在线 · SPI 失败」;DeviceLink offline 才显示离线。
5. 独立直开页:无快照时回退本地轮询。
3. **端点配置只在 ConnectionHub**:IP(`dcu_esp_ip`)、网卡(`dcu_ecat_adapter`)读写集中。
4. **命令仍页内发**,连接灯只认 DeviceLink 布尔(文案保留调用方语义)。
5. BLE 为一等通道但单独适配:只广播状态,GATT 细节留在 `ble/*`。
### 模块
### ConnectionHub 客户端 API(`deviceLink`)
`js/common/device-link.js` Owner+Client;消息:`hello` / `snapshot` / `set-ip` / `ble-state`。
| 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`。
### 接入
@@ -60,7 +67,14 @@ BLE GATT 在通知开启成功后 `reportBle(true)`,失败/断开上报 false
|------|------|
| `tabs.html` | Owner |
| `connection.js` | 快照新鲜时覆盖 connected 布尔 |
| `lan9252_pm.html` | 订阅灯 + `setSpiFail` |
| `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
@@ -68,7 +82,8 @@ BLE GATT 在通知开启成功后 `reportBle(true)`,失败/断开上报 false
- 不串行化 SPI/CAN/OTA 命令队列
- 不迁 SharedWorker
- 不改固件协议
- BLE GATT 仍留在页内(只广播状态)
- 不把 BLE GATT 强行对齐 HTTP/ECAT 请求接口
- spi_test 保持可整文件删除,不注入业务请求逻辑
## Tasks
@@ -78,3 +93,5 @@ BLE GATT 在通知开启成功后 `reportBle(true)`,失败/断开上报 false
- [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)