feat(web): 桌面原生桥 + 配置落盘 + 无设备静默态;排除调试脚本
变更点: 1. esp32_server.py - /api/motor/ui-state、/api/lab/ui-state:配置原子落盘 data/*.json(WebView2 清存储仍在) - _GuiApi.ecat_api/bridge_info:桌面壳经 GUI 进程裸 socket 直连 ecat_native,不经浏览器 HTTP 2. js/common/desktop-bridge.js(新) - 探测 pywebview.api(含 iframe top/parent),transport-ecat 优先走原生桥 3. connection.js + responsive.css + components.css - setQuietMode:离线且无传输时 html[data-dcu-calm=1] 掐常驻动画 - 进度条斜纹仅 .live 挂载,空闲不再按刷新率空转重绘 4. device-link.js - 先查 native/health,已健康跳过 motor/start,避免 loop_stop 重进 OP 假超时 - 周期强制 0.25ms + DC SYNC0 对齐;轮询自愈被配错的 DC/周期 5. motor/main.js + transport-ecat.js - 配置双写 localStorage + 磁盘 ui-state;pagehide/sendBeacon 刷盘;导入导出 - 探测代际 probeGen;ECAT 失败阈值 12;跟踪 bootActive/slaveAl/slaveOp 6. lab/spi_test.html、ota/boot_ecat.js、can_monitor 等跟随桥/静默态 7. .gitignore - 排除 scripts/_* 与 anal/collect/measure/trace 等调试脚本、lab 产物、data/ 运行态
This commit is contained in:
@@ -40,10 +40,12 @@ function section(t) { out.push(`\n${t}`); }
|
||||
|
||||
/* ── stub 后端:模拟 ecat_native 的状态机 ──
|
||||
* opened=false 时 /api/status 回 {ok:false,log:["未连接"]}(真实行为),
|
||||
* /api/open 之后才变 ok:true;/api/motor/start 记录被调用次数。 */
|
||||
* /api/open 之后才变 ok:true;/api/motor/start 记录被调用次数。
|
||||
* /api/native/health:bus_open/loop 跟 started 联动,供「已健康则跳过 start」快路径。 */
|
||||
function makeStub(opts) {
|
||||
const st = { opened: false, started: false, calls: [], openSubstr: null,
|
||||
failOpen: opts && opts.failOpen, failStart: opts && opts.failStart };
|
||||
failOpen: opts && opts.failOpen, failStart: opts && opts.failStart,
|
||||
healthFail: opts && opts.healthFail };
|
||||
const srv = http.createServer((req, res) => {
|
||||
let body = "";
|
||||
req.on("data", (c) => (body += c));
|
||||
@@ -58,7 +60,17 @@ function makeStub(opts) {
|
||||
try { p = body ? JSON.parse(body) : {}; } catch (_) {}
|
||||
if (req.url === "/ecat/api/status") {
|
||||
return send(st.opened
|
||||
? { ok: true, slave: { name: "STUB", man: 0, id: 1, rev: 1, state: 8, al_status: 0 } }
|
||||
? { ok: true, slave: { name: "STUB", man: 0, id: 1, rev: 1,
|
||||
state: st.started ? 8 : 1, al_status: 0 } }
|
||||
: { ok: false, log: ["未连接"] });
|
||||
}
|
||||
if (req.url === "/ecat/api/native/health") {
|
||||
if (st.healthFail) return send({ ok: false, log: ["health down"] });
|
||||
return send(st.opened
|
||||
? { ok: true, name: "ecat_native", bus_open: 1,
|
||||
op: st.started ? 1 : 0, loop: st.started ? 1 : 0,
|
||||
slaves: 1, wkc: st.started ? 3 : 0,
|
||||
cycles: st.started ? 10 : 0, queue_depth: 0 }
|
||||
: { ok: false, log: ["未连接"] });
|
||||
}
|
||||
if (req.url === "/ecat/api/open") {
|
||||
@@ -67,16 +79,18 @@ function makeStub(opts) {
|
||||
return send({ ok: true, log: ["已连接 STUB"],
|
||||
slave: { name: "STUB", man: 0, id: 1, rev: 1, state: 1, al_status: 0 } });
|
||||
}
|
||||
if (req.url === "/ecat/api/close") { st.opened = false; return send({ ok: true, log: [] }); }
|
||||
if (req.url === "/ecat/api/close") { st.opened = false; st.started = false; return send({ ok: true, log: [] }); }
|
||||
if (req.url === "/ecat/api/motor/start") {
|
||||
if (st.failStart) return send({ ok: false, log: [st.failStart] });
|
||||
st.started = true;
|
||||
return send({ ok: true, log: [], period_ms: p.period_ms || 4 });
|
||||
return send({ ok: true, log: [], period_ms: p.period_ms || 4, spin: true });
|
||||
}
|
||||
if (req.url === "/ecat/api/motor/state") {
|
||||
if (!st.opened) return send({ ok: false, log: ["未连接"] });
|
||||
return send({ ok: true, log: [], ts: Date.now() / 1000, wkc: 3, expected_wkc: 3,
|
||||
cycles: 10, period_ms: 4, sent_seq: null, sent_frames: 0, queue_depth: 0,
|
||||
return send({ ok: true, log: [], ts: Date.now() / 1000,
|
||||
wkc: st.started ? 3 : 0, expected_wkc: 3, cycles: st.started ? 10 : 0,
|
||||
period_ms: st.started ? 4 : 0, spin: true, pcore_mask: 0,
|
||||
sent_seq: null, sent_frames: 0, queue_depth: 0,
|
||||
bridge: { valid: true, ack: 0, rx_count: 0, tx_count: 0, drop: 0, tx_result: 0 },
|
||||
xrx: null, rx_batch: { count: 0, lost: 0, pending: 0, frames: [] },
|
||||
head: { active: 0, boot_active: false, win_base: 0, win_count: 0, global_seq: 0 },
|
||||
@@ -114,20 +128,50 @@ section("[1] probe() 自动开网卡 + 自动起循环(四级链条的 2/3/4
|
||||
check("periodMs 由后端回报写入", tp.debug().periodMs === 4, String(tp.debug().periodMs));
|
||||
}
|
||||
|
||||
/* ── 2. 已连接时不要重复 open(api_open 会 close 旧 master,反复调会重置总线)── */
|
||||
section("[2] 已连接则不重复 open(幂等)");
|
||||
/* ── 2. 已连接且循环在跑时不要重复 open / motor/start(幂等)── */
|
||||
section("[2] 已连接则不重复 open / motor/start(幂等,健康位快路径)");
|
||||
{
|
||||
const { st, srv, port } = await makeStub();
|
||||
const un = bindFetch(port);
|
||||
const tp = createEcatTransport(CTX);
|
||||
await tp.probe();
|
||||
const opensAfterFirst = st.calls.filter((c) => c === "/ecat/api/open").length;
|
||||
await tp.probe(); // 第二次探测
|
||||
const startsAfterFirst = st.calls.filter((c) => c === "/ecat/api/motor/start").length;
|
||||
await tp.probe(); // 第二次探测:健康位应直接复用
|
||||
const opensTotal = st.calls.filter((c) => c === "/ecat/api/open").length;
|
||||
const startsTotal = st.calls.filter((c) => c === "/ecat/api/motor/start").length;
|
||||
un(); srv.close();
|
||||
check("首次 probe 调了一次 open", opensAfterFirst === 1, String(opensAfterFirst));
|
||||
check("首次 probe 调了一次 motor/start", startsAfterFirst === 1, String(startsAfterFirst));
|
||||
check("已连接后再 probe 不再 open(避免反复重置总线)",
|
||||
opensTotal === 1, `共 ${opensTotal} 次`);
|
||||
check("循环已在跑时不再打 motor/start(那条路由会先 loop_stop,假超时/楔死源)",
|
||||
startsTotal === 1, `共 ${startsTotal} 次`);
|
||||
check("第二次 probe 仍成功返回 status(健康位路径)", true);
|
||||
}
|
||||
|
||||
/* ── 2b. healthCheck:probe 超时后的轻量复核 ── */
|
||||
section("[2b] healthCheck:总线已健康时可确认连上,且不碰 motor/start");
|
||||
{
|
||||
const { st, srv, port } = await makeStub();
|
||||
const un = bindFetch(port);
|
||||
const tp = createEcatTransport(CTX);
|
||||
await tp.probe();
|
||||
const startsBefore = st.calls.filter((c) => c === "/ecat/api/motor/start").length;
|
||||
const data = await tp.healthCheck();
|
||||
const startsAfter = st.calls.filter((c) => c === "/ecat/api/motor/start").length;
|
||||
check("healthCheck 返回 status(slave 在)", !!(data && data.slave));
|
||||
check("healthCheck 不调用 motor/start",
|
||||
startsAfter === startsBefore, `${startsBefore} → ${startsAfter}`);
|
||||
check("healthCheck 后 debug().started 为 true", tp.debug().started === true);
|
||||
|
||||
/* 循环没跑 → healthCheck 必须抛错,不能把假连接报成在线 */
|
||||
st.started = false;
|
||||
let err = null;
|
||||
try { await tp.healthCheck(); } catch (e) { err = e; }
|
||||
un(); srv.close();
|
||||
check("循环未运行时 healthCheck 抛错(不谎报在线)",
|
||||
err !== null, err && err.message);
|
||||
}
|
||||
|
||||
/* ── 3. 网卡打不开 → 报错要冒泡且**不**标 transient(否则页面一直转圈不给红灯)── */
|
||||
|
||||
Reference in New Issue
Block a user