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:
cbb
2026-10-13 00:48:34 +08:00
parent 514070b36d
commit eac3717f9a
41 changed files with 4229 additions and 934 deletions
+21 -1
View File
@@ -70,6 +70,9 @@ const REQUIRED = [
"probe", "fetchStatus", "describeStatus", "reportedIp",
"fetchMotorState", "fetchFeedback", "sendFrames", "sendBurst",
"fetchVersionNode", "setPeriod", "teardown",
/* 电机 OTA 整套:少一个就是"点开始才崩",流程只在 ota.js 写一遍,两条链路都得给 */
"otaHostSendsBootFrame", "otaIntegrity",
"otaUploadAttempt", "otaStartRelay", "otaStatus", "otaCancel",
];
const wifi = createWifiTransport(CTX);
const ecat = createEcatTransport(CTX);
@@ -120,7 +123,17 @@ section("[2] WiFi /motor/state?bin=1 二进制解析");
/* ── 3. 能力位 ── */
section("[3] 链路能力位");
check("WiFi 支持 OTA", wifi.supportsOta === true);
check("EtherCAT 不支持 OTA(没有固件上传+relay 通路)", ecat.supportsOta === false);
/* EtherCAT 的料走 CoE 邮箱进 GD32 暂存区(DCU_APP comm/ecat_motor_ota.cpp),
* 中继引擎与 WiFi 同一个 ⇒ 能力位必须为真。曾经这里是 false,
* 因为那条链路只有 ESP32 上传这一条进料路(纯网线打不通设备 IP)。 */
check("EtherCAT 支持 OTA(邮箱进料 + 同一个 GD32 relay)", ecat.supportsOta === true);
/* 两处真实差异必须被钉住,别被"看着一样"糊过去:
* - 进 BOOT 帧谁发(EtherCAT 必须 GD32 在已加锁通道上发,否则运动帧抢邮箱)
* - 完整性口径(WiFi 回读 SHA-256,EtherCAT 从站核 CRC32,不许互相冒充) */
check("WiFi 由上位机先进 BOOT", wifi.otaHostSendsBootFrame === true);
check("EtherCAT 由 GD32 在已加锁通道上先进 BOOT", ecat.otaHostSendsBootFrame === false);
check("WiFi 完整性口径 = ESP32 flash 回读 SHA-256", wifi.otaIntegrity === "sha256-esp32-flash");
check("EtherCAT 完整性口径 = 从站核 CRC32(进 BOOT 前)", ecat.otaIntegrity === "crc32-verify-in-slave");
check("WiFi 支持 0xC5 周期同步", wifi.supportsPeriodSync === true);
check("EtherCAT 不做周期同步(PDO 周期由后台循环维持)", ecat.supportsPeriodSync === false);
check("EtherCAT 不回报设备 IP", ecat.reportedIp({}) === null);
@@ -499,6 +512,13 @@ section("[8] 快照→契约翻译(构造快照,跑生产代码)");
JSON.stringify({ periodHits, startHits2 }));
check("(i4) 周期值持久化到 localStorage(服务重启后不用重敲)",
globalThis.localStorage.getItem("dcu_ecat_period_ms") === "0.25");
/* 放开热改:0.5ms 档必须原样下发,不得被前端钳回 0.25 */
const got05 = await tp.setPdoPeriod(0.5);
check("(i4) setPdoPeriod(0.5) 不钉死 0.25:原样下发并持久化",
got05 === 0.5 && periodBody.period_ms === 0.5
&& globalThis.localStorage.getItem("dcu_ecat_period_ms") === "0.5",
JSON.stringify({ got05, periodBody }));
await tp.setPdoPeriod(0.25); /* 回落到默认档,后续 ensureStarted 用例口径不变 */
await tp.probe();
check("(i4) 循环重启(ensureStarted)自动带上持久化的周期",
startHits2 === 1 && startBody2.period_ms === 0.25, JSON.stringify(startBody2));