feat: 添加局域网设备发现功能和链路胶囊优化
- 实现局域网设备发现面板,支持探测ESP32中继板并自动切换端点 - 添加POST /api/lan/probe接口用于设备探测,返回三态判决结果 - 实现快扫和全段扫描两种模式,支持种子地址优先探测 - 添加端点唯一写手机制,防止多处同时修改设备IP - 优化链路胶囊UI显示WiFi/EtherCAT/BLE连接状态和设备名称 - 修复EtherCAT链路OTA刷写期间PDO循环暂停和AL状态处理问题 - 调整PDO周期设置范围为0.125-4ms,支持亚毫秒显示为微秒 - 添加ESP32 WiFi使能开关控件样式和数据来源角标显示
This commit is contained in:
@@ -68,15 +68,63 @@ esp32_app.vbs esp32_web.vbs serve.bat 三个启动入口
|
||||
| `pages/motor.html` | 电机控制台(CAN FD,WiFi / EtherCAT 双链路) | `js/motor/main.js`、`js/motor/transport*.js`、`js/motor/tick.worker.js` |
|
||||
| `lab/spi_test.html` | 【临时功能 SPI-SPEEDTEST】SPI/ECAT 通信测速(标签名「SPI/ECAT 通信测速」;含 EtherCAT 接收看板,完全自包含,删文件即可移除) | 内联 |
|
||||
|
||||
## 设备发现与选择(局域网)
|
||||
|
||||
顶栏 `🔍 设备`(`tabs.html`)与 OTA 页 IP 行的 `🔍 发现`(`pages/index.html`)打开同一个面板:
|
||||
扫出局域网里哪几台是这块 ESP32 中继板,点一行就把当前设备端点改成它,六个标签一起跟着切。
|
||||
|
||||
- **探测原语在服务侧且无状态**:`POST /api/lan/probe {"ips":[≤64],"ports":[80],"timeout_ms":750,"local":true}`
|
||||
→ 逐 IP 一条判决 + `scan` 计数 + `local`(本机默认路由地址与派生 /24)。实现 `lan_probe.py`,
|
||||
不建 job 表、不常驻线程、不落盘;分批/进度/取消/缓存全在 `js/common/lan-picker.js`。
|
||||
- **判据只有一处**(`lan_probe.classify`),页面不复算。探测固定打 `/system/status?lite=1`
|
||||
(固件里这条路径 256 B 栈缓冲、不抢 JSON 工作锁、不发 SPI 事务)。三态判决:
|
||||
`board`(形状对得上,STA 需自报 IP 与探测地址一致)/ `rejected`(**有反证**:非 200、非 JSON、
|
||||
无 `wifi` 对象、STA 自称在别处)/ `unknown`(连不上、读超时、或形状合但落不进前两类)。
|
||||
超时**不算**"不是板子",也不算"没找到",单独列出来。
|
||||
- **两档扫描**:`快扫` 先打种子地址(当前端点、192.168.31.127、192.168.31.1、上次发现到的,≤8 个,实测 ~0.1–1s),
|
||||
**种子没命中就自动扩到整段**再扫一次——设备换了 DHCP 地址正是快扫最常见的失效场景;扩段后仍没命中才说"本轮没扫到"。
|
||||
`全段` 直接打默认路由派生的 /24(实测 253 个地址:服务端各批耗时之和 6.0s,页面双路并发墙钟约 3.5s)。
|
||||
耗时由"不存在的地址等满 750ms 截止"主导,服务侧批内并发 48(跨不同地址,一块板子全程只被占 1 个 socket)。
|
||||
面板上报的"用时"是页面墙钟,不是各批累加。CIDR 可手填;
|
||||
非默认路由的网段(比如 EtherCAT 那块 I210)永远不自动扫。
|
||||
- **不打扰正在跑的东西**:当前已选端点不进候选表(它的活体判决就是 `device-link` 那条 1Hz
|
||||
`/system/status` 轮询,不再另开 socket——设备 httpd 只有 6 个 socket 且开了 `lru_purge`,
|
||||
多开会踢掉网页自己的 keep-alive);`flashBusyNow()`(localStorage `dcu_flash_busy_until`,跨 iframe)
|
||||
为真时既不能发起扫描也不能换端点;离线自动补扫额外要求 ECAT 环没在跑且距上次 ≥60s,
|
||||
且**只列候选、绝不自动换设备**。
|
||||
- **端点只有一个写手**:`deviceLink.setIp / selectDevice`,IPv4 校验在 `js/common/base-url.js:saveDeviceIp`
|
||||
单点做,非法值**拒绝且不改写现值**,各页输入框只是视图。选中时记一份设备指纹(版本 / git_hash),
|
||||
Owner 之后每次拉 status 都核 `snap.endpointCheck = match|mismatch|unverified`——设备换了、
|
||||
或这地址被另一块板占了,会被标出来而不是悄悄当成同一台。
|
||||
- **已知限制(面板里也这么写)**:固件 `/system/status` 不上报 mac/chip_id,两块同款板只能靠 IP 与
|
||||
`uptime_sec` 区分(认领办法:按板上复位键后快扫,新上电那台 uptime < 30s)。STA 失败回退 AP 的板子
|
||||
自称 `192.168.31.1`,PC 还连着路由器 WLAN 时它在三层上不可达,扫不到是正确行为,要真人连
|
||||
`ESP32_OTA_RELAY`。mDNS(README 旧版曾写 `esp32-ota-relay.local`)**没有**编进固件。
|
||||
- 自测:`python scripts/test_lan_probe.py`(离线,假板绑本机网卡地址跑真 socket,含"起/关服务判决必须翻转"
|
||||
的灵敏度对照);`python scripts/check_lan_discovery.py --expect 1`(真网段门槛,
|
||||
用 RFC 5737 `192.0.2.0/28` 做反向对照,前置不满足报 BLOCKED/exit 2 而不算通过)。
|
||||
|
||||
## 链路胶囊(WiFi / ECAT / BLE)的灯色与文案
|
||||
|
||||
`connection.js:updateLinkPills()` 是**唯一**写这三颗 pill 的 class 与文字的地方,判据全部取自
|
||||
`device-link` 快照。历史上 ECAT 那颗有三个写手(快照一条、`app.js` 的 0x1008 probe 一条、
|
||||
重连边沿检测一条),节奏和判据都不同,实测出现过"绿点 + 文字离线"的自相矛盾;现在
|
||||
`app.js:refreshEcatPillName()` 只把读到的名字公布到 `window.__ecatProbeName`,不再碰 DOM。
|
||||
ECAT 分级:`!busOpen` → 离线(后端/网卡未开);`busOpen && loop` → 在线(绿,显示从站名);
|
||||
`busOpen && !loop` → 琥珀并标 AL 档位(1=INIT 2=PREOP 3=BOOT 4=SAFEOP 8=OP,见快照
|
||||
`links.ecat.slaveState`)。注意 `links.ecat.online` 仍是老判据(status 有回且带 slave 对象即真),
|
||||
只表示"总线侧有应答",不代表能跑——`updateConnectionUi()` 的整体在线判定还在用它,
|
||||
要收紧得单独定(会影响 OTA 页那张大在线卡)。
|
||||
|
||||
## 目录约定
|
||||
|
||||
- `css/` —— `base`(变量/基础)、`layout`、`components`、`can`、`motor`、`responsive`。
|
||||
**所有权**:`.btn` 共享 chrome 在 `layout.css`;`components.css` 管跨页 `.toast`
|
||||
(居中单条);`motor.css` 的 `.btn` / `.toast-container` 是电机页主题覆盖与
|
||||
堆叠 Toast,**不要**再往 layout/components 抄一份电机专用规则。
|
||||
- `js/common/` —— 跨页公用:`base-url`(设备 IP)、`connection`、`polling`、`log`、
|
||||
- `js/common/` —— 跨页公用:`base-url`(设备 IP + IPv4 校验单点)、`connection`、`polling`、`log`、
|
||||
`toast`(居中)、`toast-stack`(容器堆叠,电机页)、`ecat-bus`(ECAT 接入策略单点)、
|
||||
`device-link`、`utils`、`heap-low-history`。
|
||||
`device-link`(端点唯一写手 + 全链路 Owner)、`lan-picker`(设备发现面板)、`utils`、`heap-low-history`。
|
||||
- `js/ota/`、`js/ble/`、`js/can/`、`js/motor/` —— 按功能域拆分的 ES module,跨域引用只走 `js/common/`。
|
||||
- 子系统配色:ESP32 = 青绿 · GD32 = 琥珀 · BLE = 蓝 · CAN = 绿 · 内存/调试 = 紫。
|
||||
|
||||
@@ -117,12 +165,19 @@ esp32_app.vbs esp32_web.vbs serve.bat 三个启动入口
|
||||
而本页 API 是 POST + JSON 属非简单请求,浏览器必发预检,跨端口直连必失败。
|
||||
改那边要动另一个 git 仓库,把页面搬过去又会让两种链路分裂成两份页面,
|
||||
所以在 `esp32_server.py` 里同源转发(端口在 8090-8095 间自动探测,见 `_ecat_proxy`)。
|
||||
- EtherCAT 链路的三处已知差异(都在 `transport-ecat.js` 头注释里写明):
|
||||
- EtherCAT 链路的已知差异(都在 `transport-ecat.js` 头注释里写明):
|
||||
1. 每通道 TEC/REC/err 过程数据里没有 → 一律回 `null` 让界面显示 `--`,
|
||||
不编 0 冒充正常(区分"通信正常但无此数据"与"真的是 0");
|
||||
2. CONFIG(`0x000N0101`) / 版本(`0x000N00FF`) / 节点 ID 应答不在状态窗口里,
|
||||
由传输层从批量接收区累积后回填,上层无感;
|
||||
3. 没有固件上传 + GD32 relay 通路 → `supportsOta=false`,OTA 页签自动置灰。
|
||||
3. 电机 OTA 的**进料口**不同:WiFi 走 `POST /motor/ota`(ESP32 存 flash,回读算
|
||||
SHA-256 比对)再经 SPI 0xD1 喂;EtherCAT 走 CoE 邮箱 `0x2511/0x2510`
|
||||
把明文灌进 GD32 的 48KB 暂存区,**从站在电机进 BOOT 之前**核分块 CRC32 与
|
||||
整镜像 CRC32(`DCU_APP/comm/ecat_motor_ota.cpp`)。AES 握手、CAN 单发停等、
|
||||
重发、`0AAC` 运行确认这套引擎两条链路完全同一个,`ota.js` 的流程也只写一遍。
|
||||
刷写期间 native worker 独占总线并停 PDO 循环(各通道运动帧一起暂停,与 WiFi
|
||||
模式刷写等效),结束后自动补一次 PREOP→SAFEOP→OP——从站的 SM2 过程数据看门狗
|
||||
会在这十几秒里以 AL `0x001B` 把 OP 拖回 SAFEOP,不补就是"刷完假在线"。
|
||||
- EtherCAT 的 PDO 周期由后台循环独立维持(从站 SM2 有 **1000ms 过程数据看门狗**,
|
||||
停超 1 秒会 OP→SAFEOP 掉线报 AL `0x001B`),与界面刷新率无关;
|
||||
故切走链路时**不停**后台循环。
|
||||
@@ -308,8 +363,8 @@ esp32_app.vbs esp32_web.vbs serve.bat 三个启动入口
|
||||
把真实的 99.2% 报成 96%,一度被误读成"HTTP 轮询负载吃掉 3 个点"。修复后读数与
|
||||
cadence_diag 的拍间时间线(不依赖 cycles 轮询)互相印证。教训:**两条独立测量
|
||||
路径对不上时,先怀疑测量本身,再怀疑系统**;带载/空载的区分要用拍间时间线做基准。
|
||||
- 前端:`motorDbg.state.tp.setPdoPeriod(0.25)`(恢复默认 `setPdoPeriod(4)`)。
|
||||
取值存 localStorage `dcu_ecat_period_ms`,后端重启后由
|
||||
- 前端:`motorDbg.state.tp.setPdoPeriod(0.5)`(合法档 0.125–4ms,DC 与 period 同值;
|
||||
默认 `setPdoPeriod(0.25)`)。取值存 localStorage `dcu_ecat_period_ms`,后端重启后由
|
||||
`ensureStarted` 自动带回;状态栏 <1ms 显示 µs(`PDO 250µs`,与后端
|
||||
`_fmt_period` 同口径),且每拍从快照同步周期 —— 别的标签页或脚本改的
|
||||
不会显示成过期值。
|
||||
@@ -414,7 +469,7 @@ esp32_app.vbs esp32_web.vbs serve.bat 三个启动入口
|
||||
`node scripts/browser_check_period.mjs [port]` 验内核级周期热调(12 项:
|
||||
`setPdoPeriod(0.25)` 走 `/motor/period` 不碰 `/motor/start`、状态栏显示
|
||||
`PDO 250µs`(亚毫秒用 µs)、自动自旋标注、localStorage 持久化、页面重载后
|
||||
`ensureStarted` 自动带回、换回 4ms 自旋标注消失)。
|
||||
`ensureStarted` 自动带回、`setPdoPeriod(4)` 可热改到 4ms 档)。
|
||||
- 监控 UI 需区分 `valid`(通信是否成功)与 `count > 0`(是否有数据),
|
||||
以便分辨「正常空闲」和「真实通信错误」。
|
||||
- 响应式 grid 表隐藏列用 `0fr` 宽度实现,不要改变 grid track 数量。
|
||||
|
||||
Reference in New Issue
Block a user