--- feature: pdo-fast-send status: delivered updated: 2026-09-23 branch: main commits: working-tree (uncommitted; dual-repo main authorized) --- # PDO 快发(网页按钮 → 原生入队二进制直通) ## Report **What was built** — 桌面 APP(esp32_app)下,电机页 EtherCAT 链路的**全部** motor 发送**强制**走 ESF1 二进制快发:JS `desktopEcatSendRaw` → GUI 进程 `ecat_send_raw` 打包 → native 持久 TCP 口(health.`fast_send_port`)入 `g_cmd_q`。 **已堵死慢发**:Python 桥删除 HTTP `send_raw` / JSON `motor/send` 回落;前端在 `hasDesktopFastSend()` 为真时失败直接 `ok:false`,不再 `api("motor/send")`。 浏览器无壳标签仍走 HTTP `/ecat/api/motor/send`(脚本/诊断同理)。布局:请求 magic `ESF1` + nframes + 72B/帧;响应 `FSOK` 8 字节。 **Verification** — - `build_native.bat` PASS;health `fast_send_port` 存在,loop=1 wkc=3 - `scripts/test_esf_pack.py` PASS;`py_compile esp32_server.py` PASS - `node --check` desktop-bridge / transport-ecat / transport / main PASS - `scripts/check_esf_latency.py`:TCP FSOK p50≈0.046–0.08ms(HTTP 对照仍在 native 路由上) - 源码检查:`ecat_send_raw_frames` 无 `motor/send`/`http_send_raw` 回落; `sendViaFastOrJson` 有桥时不调 `api("motor/send")` - `test_motor_transport.mjs` 68/68 PASS(Node 无 pywebview → 走 HTTP 契约路径) - `test_motor_refresh.mjs` 62/62 PASS - 独立评审 CRITICAL/MAJOR(锁/静态缓冲/端口缓存)此前已修复 **Journey log** — - 用户期望「纳秒进 PDO」不可达;可压的是控制面,入队后仍是 250µs 一拍 PDO - pywebview JS→Python hop 仍在,TCP 快发已把 native 侧压到 ~50–80µs - 沙箱拦 `git worktree add` → 用户明确授权双仓 main - 持久 TCP 必须 send+recv 整段持锁,否则 FSOK 错配 - **2026-09-23:用户要求堵死慢发**——桌面强制 ESF1、桥内删除 HTTP/JSON 回落; 浏览器/脚本仍保留 native HTTP 路由 ## [S1] Problem 电机控制台在 EtherCAT 链路上点发送时,信号要走很长控制面才进 `ecat_native` 的 `g_cmd_q`:浏览器 fetch→代理→JSON,桌面 APP 再叠 pywebview 桥。用户要 「一点按钮极短 CPU 周期内进 PDO」。纳秒不可达;native 入队后到总线由 PDO 周期决定(250µs 档),可压缩的是控制面。HTTP 路径还存在 **8–12ms 调度尾部**, 必须防止桌面壳误走慢路径。 ## [S2] Design ### 目标行为(2026-09-23 修订:堵死慢发) 1. 桌面 APP + 桥可用:ECAT 全部 motor 发送 **只走** ESF1 TCP;失败 `ok:false`, **无** HTTP/JSON 回落。 2. Python `ecat_send_raw_frames`:**仅** TCP ESF1(失败可重探端口后再试 TCP 一次)。 3. 浏览器标签(无 pywebview):仍走 `/ecat/api/motor/send`。 4. native `/api/motor/send` 与 `/api/motor/send_raw` **保留**(浏览器/诊断脚本)。 5. UI 不新增专用开关;`sendHint` / `desktopBridgeLabel` 反映通道形态。 6. 不承诺纳秒。 ### 工作区(用户授权覆盖) - ESP32 与 tools/ecat 均在 **main** 上改(2026-09-23 授权;沙箱拦 worktree add)。 - native:`tools/ecat/ecat_native.c` + `build_native.bat`。 ### 二进制帧布局(小端) **请求**: ``` u32 magic = 0x31465345 /* ESF1 */ u8 nframes /* 1..64 */ u8 flags u16 reserved repeat nframes: /* 72B each */ u8 ch, u8 dlc, u16 pad, u32 id, u8 data[64] ``` **响应**:`u32 FSOK(0x4B4F5346) | u8 ok | u8 queued | u16 backlog` ### Native - `127.0.0.1` `bind(0)` 快发口;health 增加 `fast_send_port`。 - 持久连接读 ESF1 → `parse_esf` → 共用 `enqueue_frames` → FSOK。 - HTTP `/api/motor/send_raw` 与 JSON `/api/motor/send` 仍服务浏览器/脚本。 - `send_raw` 解析缓冲为栈上(HTTP 多线程)。 ### GUI 桥(esp32_server.py) - `pack_esf` / `unpack_fsok`;`_GuiApi.ecat_send_raw`。 - **仅**持久 TCP;connect+sendall+recv 整段持锁。 - 失败:关 socket + `g_fast_send_port=0` → 重探后再试 TCP 一次 → 仍败则 ok:false。 - **无** HTTP/JSON 回落代码。 ### 前端 - `desktop-bridge.js`:`desktopEcatSendRaw` / `hasDesktopFastSend`。 - `transport-ecat.js`:`sendViaFastOrJson` 有桥时只调快发。 - 缓存版本 `?v=20260923i`。 ### Out of Scope - 纳秒;共享内存;预装载+位触发;浏览器强制禁 HTTP;删除 native JSON 路由; 改 PDO 周期;WiFi 链路。 ## Tasks - [x] T1: native 快发口 + `/api/motor/send_raw` + health 字段 (covers: S2) - [x] T2: Python 打包 + `ecat_send_raw` TCP-only(无回落) (covers: S2; depends: T1) - [x] T3: desktop-bridge + transport-ecat 有桥强制快发 (covers: S2; depends: T2) - [x] T4: 延迟脚本 + 文档 + 堵死慢发修订 (covers: S2; depends: T1)