Files
DCU_DesktopController/docs/compose/spec/device-link.md
T
cbb 670b833dc6 feat(link): DeviceLink 外壳单例统一 ESP32/ECAT/BLE 健康态
各页只共用 IP、各自 fetch 导致同屏「已连接/连接失败」分裂。
外壳 Owner 轮询 /system/status 与 EtherCAT 状态,BroadcastChannel
广播快照;连接灯只认共享层,SPI 失败单独文案不覆盖设备离线。
2026-09-16 02:50:12 +08:00

4.7 KiB
Raw Blame History

feature, status, updated, branch, commits
feature status updated branch commits
device-link designed 2026-09-16 feat/device-link

Device Link(全链路统一连接层)

Report

[S1] Problem

多 iframe 保活后,各页仍各自 fetch 设备,只是共用 localStorage.dcu_sp_ip:

  • OTA 页轮询 /system/status → 顶栏「WiFi 已连接」
  • LAN9252 页轮询 /spi/send → 同一 IP 却显示「连接失败 · signal timed out」
  • EtherCAT 负载/状态又是一条独立轮询
  • BLE 在页内 GATT,其它页完全不知

用户直觉是「同一条设备连接」,实现却是 N 条互不证明的 HTTP/蓝牙会话。 结果:一页绿灯一页红灯,像系统坏了,其实是健康态没有单一真相源。

[S2] Design

原则

  1. 外壳持有链路健康轮询(tabs.html 常驻),功能页不再各自判定「设备在线」。
  2. BroadcastChannel(dcu-device-link)广播快照;页内只订阅渲染。
  3. 命令仍页内发(SPI 0xA2、CAN send、OTA upload 等),但连接灯只认 DeviceLink。
  4. SPI/业务命令失败显示为 子系统故障(如「SPI 超时」),不得覆盖成「设备离线」——除非 DeviceLink 已判 ESP32 离线。
  5. 页面可独立打开(无外壳):检测不到 owner 时回退本地轮询,行为与改前一致。

模块

js/common/device-link.js 同一文件双角色:

角色 谁加载 职责
Owner tabs.html(window.__dcuDeviceLinkOwner = true) 轮询 ESP32 /system/status、ECAT /ecat/api/status+/api/app/load;维护快照并广播
Client 各功能页 subscribe() 收快照;setIp();reportBle() 上报 GATT 状态

快照契约

{
  ts: 1710000000000,
  ip: "192.168.31.127",
  links: {
    esp32: { online: boolean, ageMs: number, failStreak: number },
    ecat:  { online: boolean, busOpen: boolean, op: boolean, loop: boolean,
             loadValid: boolean, loadPerm: number, ageMs: number },
    ble:   { online: boolean, deviceName: string, owner: string|null }
  },
  esp32Status: object|null   // 最近一次 /system/status 全文(可 null)
}

消息

type 方向 语义
hello Client→Owner 请求立刻回一份 snapshot
snapshot Owner→All 全量快照(ESP32 状态变化或 1s 心跳时发)
set-ip Client→Owner 改设备 IP;Owner 写 localStorage 并立刻重拉
ble-state Client→Owner {online, deviceName} GATT 连/断

Owner 用 BroadcastChannel;无 BC 的环境 client 降级为「仅 localStorage 读 IP + 本地轮询」。

接入点(本轮)

页面/模块 改动
tabs.html 置 owner 标志并 import device-link 启动
pages/index.html + js/common/polling.js/connection.js 有 DeviceLink 时:连接胶囊/健康读快照;pollStatus 仍可保留作数据源或改为消费 esp32Status(见任务)
lab/lan9252_pm.html 连接灯改读 DeviceLink;/spi/send 失败文案改为「SPI 失败」
js/ble/gatt.js 或 session connect/disconnect 时 reportBle
js/ota/ecat_load.js 可选:owner 已有 load 时页内 poller 可只读快照(本轮先并存,避免双写 UI)

超时与判定

  • ESP32:成功 → online=true, failStreak=0;连续失败 ≥3 → online=false
  • ECAT:/api/status ok 且 slave 存在 → 轨道 busOpen;/app/load valid && age_ms≤3000 → load 新鲜
  • BLE:仅 GATT 主人上报;无上报时 online=false

与 keep-alive 的关系

DeviceLink 跑在外壳,与 iframe 是否可见无关;隐藏页仍能显示统一灯(打开即亮)。

[S3] Out of Scope

  • 不把 SPI/CAN/OTA 命令全部塞进共享队列(串行化另开)
  • 不迁 SharedWorker
  • 不改 ESP32/ECAT 固件协议
  • 不合并 BLE GATT 到外壳(本轮只广播状态)

Tasks

  • T1: device-link.js Owner+Client + BroadcastChannel 协议 — acceptance: 单测式手工:外壳起后 postMessage/snapshot 可被 mock client 收到 (covers: S2)
  • T2: tabs.html 启动 Owner — acceptance: 打开外壳后 BC 上出现 esp32/ecat 快照,且关页后不再刷 (covers: S2)
  • T3: OTA 页连接态改读 DeviceLink(独立打开回退) — acceptance: 外壳内 OTA 与 LAN9252 的「在线/离线」一致;LAN9252 SPI 失败不再显示设备离线 (covers: S2)
  • T4: LAN9252 页灯与文案接入 — acceptance: 同 (covers: S2)
  • T5: BLE GATT 状态上报 — acceptance: BLE 连上后快照 ble.online=true,其它页可观察 (covers: S2)
  • T6: 语法检查 + 评审 — acceptance: node --check / 人工路径说明写入 Report (covers: S2)