2026-09-04 12:18:06 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* transport.js — 电机控制台的传输选择层
|
|
|
|
|
|
*
|
|
|
|
|
|
* 电机页支持两条链路,业务逻辑(解码、节点列表、STATUS 面板、波形、命令序列、
|
|
|
|
|
|
* 流控)**完全共用**,只有"数据怎么进出"这一层不同:
|
|
|
|
|
|
*
|
|
|
|
|
|
* wifi ESP32 固件 80 端口的 HTTP API(原有实现,行为未变)
|
|
|
|
|
|
* ecat EtherCAT 过程数据,经本地页面服务器 /ecat/api/* 代理到
|
2026-09-05 03:56:52 +08:00
|
|
|
|
* tools/ecat 的 ecat_native.exe(C/SOEM 守护进程)
|
2026-09-04 12:18:06 +08:00
|
|
|
|
*
|
|
|
|
|
|
* 传输实例必须实现的接口(详见两个实现文件的注释):
|
|
|
|
|
|
* id / label / supportsOta / supportsPeriodSync
|
|
|
|
|
|
* sendHint(ch) -> string
|
|
|
|
|
|
* probe() -> statusData 连接探测,抛错=没连上
|
|
|
|
|
|
* fetchStatus(wantFull) -> statusData 健康检查
|
|
|
|
|
|
* describeStatus(data) -> string[] #devStatus 文本片段
|
|
|
|
|
|
* reportedIp(data) -> string|null 设备回报 IP(ECAT 恒 null)
|
|
|
|
|
|
* fetchMotorState() -> {have_data, age_ms, active_count, boot_active, motors[]}
|
|
|
|
|
|
* fetchFeedback() -> {channels:[{ch,enabled,valid,rx,tx,err,tec,rec,bus_state}]}
|
2026-10-13 00:48:34 +08:00
|
|
|
|
* bus_state 位图:bit0=init bit1=控制器睡眠 bit2=error_passive bit3=bus_off
|
|
|
|
|
|
* bit4=error_warning bit5=只听不发(收发器 Silent,本端 S 脚)
|
2026-09-04 12:18:06 +08:00
|
|
|
|
* sendFrames(frames, {ch}) -> results[] frames=[{id,dlc,data}]
|
|
|
|
|
|
* sendBurst(frames, {suffix,dlc}) -> results[]
|
2026-09-20 22:58:00 +08:00
|
|
|
|
* fetchVersionNode(node) -> {node,seq,version,at?}|null
|
|
|
|
|
|
* 可选:drainPendingRx() -> Promise 版本等待期强制消费批量接收区(ECAT)
|
2026-09-04 12:18:06 +08:00
|
|
|
|
* setPeriod(ms) -> void 不支持则空实现
|
|
|
|
|
|
* teardown() -> void 切走时收尾
|
|
|
|
|
|
* 可选:takeNodeIdReport(node)、debug()
|
|
|
|
|
|
*
|
2026-10-13 00:48:34 +08:00
|
|
|
|
* 电机 OTA(supportsOta=true 时必须整套实现;流程只在 ota.js 里写一遍):
|
|
|
|
|
|
* otaHostSendsBootFrame: bool 进 BOOT 帧由上位机发(WiFi)还是 GD32 在已加锁
|
|
|
|
|
|
* 的通道上自己发(EtherCAT)
|
|
|
|
|
|
* otaIntegrity: string 完整性口径,UI 文案照它写,不许互相冒充:
|
|
|
|
|
|
* 'sha256-esp32-flash' | 'crc32-verify-in-slave'
|
|
|
|
|
|
* otaUploadAttempt(bytes, shaHex, {ch, node}) -> {ok, note, size?}
|
|
|
|
|
|
* otaStartRelay(ch, node) -> {ok} 抛错=没起来
|
|
|
|
|
|
* otaStatus() -> {state, error, session_error, packets_acked, total_packets, note}
|
|
|
|
|
|
* state 用 WiFi 那套字符串(ECAT 由传输层翻译)
|
|
|
|
|
|
* otaCancel() -> {ok, note?}
|
|
|
|
|
|
*
|
2026-09-04 12:18:06 +08:00
|
|
|
|
* motors[] 每项的字段契约(ECAT 实现负责补平差异):
|
|
|
|
|
|
* {node, ch, online, dlc, rx_count, seq, data(64hex), config_data(48hex|"")}
|
|
|
|
|
|
*/
|
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
2026-10-13 00:48:34 +08:00
|
|
|
|
import { createWifiTransport } from "./transport-wifi.js?v=20261011b";
|
|
|
|
|
|
import { createEcatTransport } from "./transport-ecat.js?v=20261011b";
|
2026-09-04 12:18:06 +08:00
|
|
|
|
|
|
|
|
|
|
/** localStorage key:记住上次选的传输,刷新页面后保持 */
|
|
|
|
|
|
export const LS_TRANSPORT = "dcu_motor_transport";
|
|
|
|
|
|
|
|
|
|
|
|
export const TRANSPORT_IDS = ["wifi", "ecat"];
|
|
|
|
|
|
|
|
|
|
|
|
/** @description 按 id 创建传输实例。未知 id 回退到 wifi。 */
|
|
|
|
|
|
export function createTransport(id, ctx) {
|
|
|
|
|
|
return (id === "ecat") ? createEcatTransport(ctx) : createWifiTransport(ctx);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @description 读上次选择的传输 id(默认 wifi —— 不改变老用户的既有行为)。 */
|
|
|
|
|
|
export function loadTransportId() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const v = localStorage.getItem(LS_TRANSPORT);
|
|
|
|
|
|
if (v && TRANSPORT_IDS.includes(v)) return v;
|
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
return "wifi";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @description 保存传输选择。 */
|
|
|
|
|
|
export function saveTransportId(id) {
|
|
|
|
|
|
try { localStorage.setItem(LS_TRANSPORT, id); } catch (_) {}
|
|
|
|
|
|
}
|