88 lines
5.1 KiB
JavaScript
88 lines
5.1 KiB
JavaScript
/**
|
||
* 浏览器实测:内核级 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);
|