Files

88 lines
5.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 浏览器实测:内核级 PDO 周期热调(250µs=4kHz)+ 持久化 + µs 显示。
* 单测(test_motor_transport.mjs 的 i4 段)只验传输层契约;这里验真页面真后端:
* - setPdoPeriod 走 /motor/period,绝不碰 motor/start
* (后者会停 PDO 循环,停手 >1s 从站掉 OP —— 把板子弄楔死的那条路)
* - 状态栏亚毫秒显示 µs,自旋/E 核标注正确
* - 页面重载后 ensureStarted 自动带回持久化周期(内核级档位跨服务重启存活)
* - 换回 4ms 后自旋标注消失(自动退回内核睡眠,不白烧核)
*/
const CDP = "http://127.0.0.1:9333";
const list = await (await fetch(CDP + "/json/list")).json();
const page = list.find((t) => t.type === "page");
const sock = new WebSocket(page.webSocketDebuggerUrl);
let id = 0; const pending = new Map();
await new Promise((r, j) => { sock.onopen = r; sock.onerror = j; });
sock.onmessage = (e) => { const m = JSON.parse(e.data); if (m.id && pending.has(m.id)) { pending.get(m.id)(m); pending.delete(m.id); } };
const send = (method, params = {}) => { const myId = ++id; return new Promise((res) => { pending.set(myId, res); sock.send(JSON.stringify({ id: myId, method, params })); }); };
async function ev(expr) {
const r = await send("Runtime.evaluate", { expression: expr, awaitPromise: true, returnByValue: true });
return r.result && r.result.result && r.result.result.value;
}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
let pass = 0, fail = 0; const out = [];
const check = (n, c, d) => { if (c) { pass++; out.push(" \u2713 " + n); } else { fail++; out.push(" \u2717 " + n + (d ? " \u2014 " + d : "")); } };
await send("Runtime.enable"); await send("Page.enable");
const PORT = Number(process.argv[2] || 2625);
const PAGE = `http://127.0.0.1:${PORT}/pages/motor.html`;
/* 干净起点:ECAT 链路,清掉周期/节拍残留偏好(后端保持当前值不动) */
await send("Page.navigate", { url: PAGE }); await sleep(2000);
await ev(`localStorage.setItem("dcu_motor_transport","ecat");
localStorage.removeItem("dcu_motor_ui_v2");
localStorage.removeItem("dcu_ecat_period_ms");
localStorage.removeItem("dcu_ecat_spin"); "ok"`);
await send("Page.navigate", { url: PAGE }); await sleep(4000);
/* 请求计数探针:区分 /motor/period 与 /motor/start */
await ev(`(() => {
window.__per = 0; window.__start = 0;
if (!window.__origFetch) window.__origFetch = window.fetch;
window.fetch = function (u) { const s = String(u);
if (s.includes("motor/period")) window.__per++;
if (s.includes("motor/start")) window.__start++;
return window.__origFetch.apply(this, arguments); };
return "ok"; })()`);
const pre = JSON.parse(await ev(`JSON.stringify({tp: motorDbg.state.tpId, conn: motorDbg.state.connected})`));
check("已在 ecat 链路且已连接", pre.tp === "ecat" && pre.conn === true, JSON.stringify(pre));
out.push("\n[A] setPdoPeriod(0.25):走 /motor/period,不碰 motor/start");
const startBefore = await ev(`window.__start`);
const got = await ev(`motorDbg.state.tp.setPdoPeriod(0.25)`);
await sleep(1200);
check("返回新周期 0.25", got === 0.25, String(got));
const cnt = JSON.parse(await ev(`JSON.stringify({per: window.__per, start: window.__start})`));
check("命中 /motor/period 一次", cnt.per === 1, JSON.stringify(cnt));
check("没碰 /motor/start(停循环=看门狗掉OP的楔死路径)",
cnt.start === startBefore, JSON.stringify({ cnt, startBefore }));
let st = JSON.parse(await ev(`JSON.stringify({txt: document.querySelector("#devStatus").textContent, dbg: motorDbg.state.tp.debug()})`));
out.push(" 状态栏: " + st.txt);
check("状态栏显示 PDO 250µs(亚毫秒用 µs 不用 0.25ms)", /PDO 250µs/.test(st.txt), st.txt);
check("亚毫秒周期自动自旋并标注(⚡自旋节拍)", /自旋节拍/.test(st.txt), st.txt);
check("debug().periodMs === 0.25", st.dbg.periodMs === 0.25, JSON.stringify(st.dbg));
check("周期持久化到 localStorage",
(await ev(`localStorage.getItem("dcu_ecat_period_ms")`)) === "0.25");
out.push("\n[B] 页面重载:ensureStarted 自动带回 0.25ms(跨重启档位不丢)");
await send("Page.navigate", { url: PAGE }); await sleep(4000);
st = JSON.parse(await ev(`JSON.stringify({txt: document.querySelector("#devStatus").textContent, per: motorDbg.state.tp.debug().periodMs})`));
check("重载后仍是 PDO 250µs", /PDO 250µs/.test(st.txt) && st.per === 0.25, JSON.stringify(st));
out.push("\n[C] 换回 4ms:µs 与自旋标注都消失(自动退回内核睡眠)");
await ev(`motorDbg.state.tp.setPdoPeriod(4)`);
await sleep(1200);
st = JSON.parse(await ev(`JSON.stringify({txt: document.querySelector("#devStatus").textContent})`));
out.push(" 状态栏: " + st.txt);
check("显示 PDO 4ms", /PDO 4ms/.test(st.txt), st.txt);
check("4ms 自动退回内核睡眠,无自旋标注", !/自旋/.test(st.txt), st.txt);
check("localStorage 更新为 4", (await ev(`localStorage.getItem("dcu_ecat_period_ms")`)) === "4");
console.log(out.join("\n"));
console.log("\n" + "=".repeat(56));
console.log(`通过 ${pass} 项,失败 ${fail} 项`);
sock.close();
process.exit(fail === 0 ? 0 : 1);