Files
DCU_DesktopController/scripts/browser_check_floor.mjs
T

131 lines
8.5 KiB
JavaScript
Raw 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.
/**
* 浏览器实测:ECAT 刷新速度下限档 6ms(不限速档)。
* 单测只验了纯逻辑;这里验真机行为:
* - 输入框 min/step 随链路变(否则浏览器把合法的 6ms 标成 :invalid)
* - 6ms 真的比 50ms 快,且拿到实测频率而不是理论 166Hz
* - 存量缓存里的旧 1ms 会被抬到 6ms(绘制上限之上没有消费者)
* - 不限速档**不**亮卡顿黄标(每拍撞上在途请求是预期行为)
* - 切回 WiFi 时 6ms 不会被带过去(wifi 下限 50ms)
*/
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");
/* 页面服务端口可传参:node scripts/xxx.mjs [port](默认 2625) */
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"); "ok"`);
await send("Page.navigate", { url: PAGE }); await sleep(3500);
/* 请求计数探针 */
await ev(`(() => {
window.__cnt = 0;
if (!window.__origFetch) window.__origFetch = window.fetch;
window.fetch = function (u) { if (String(u).includes("motor/state")) window.__cnt++; return window.__origFetch.apply(this, arguments); };
return "ok";
})()`);
async function measure(sec) {
await ev(`window.__cnt = 0; "ok"`);
await sleep(sec * 1000);
return await ev(`window.__cnt`) / sec;
}
const pre = JSON.parse(await ev(`JSON.stringify({tp: motorDbg.state.tpId, conn: motorDbg.state.connected, eff: motorDbg.state.refreshIntervalMs})`));
out.push("\n[前置] " + JSON.stringify(pre));
check("已在 ecat 链路且已连接", pre.tp === "ecat" && pre.conn === true, JSON.stringify(pre));
out.push("\n[A] ECAT 下输入框的 min/step 应放开到 6/1");
let dom = JSON.parse(await ev(`JSON.stringify({min: document.querySelector("#refreshRate").min, step: document.querySelector("#refreshRate").step})`));
check("min=6(否则浏览器校验会把 6ms 判成非法)", dom.min === "6", JSON.stringify(dom));
check("step=1(下限档要能逐 ms 微调)", dom.step === "1", JSON.stringify(dom));
out.push("\n[B] 基线:ECAT 默认 50ms");
const hz50 = await measure(3);
out.push(` 实测 ${hz50.toFixed(1)} Hz`);
check("50ms 档约 20Hz", hz50 > 12 && hz50 < 30, `实测 ${hz50.toFixed(1)}`);
out.push("\n[C] 设成 6ms —— 应被接受且明显更快");
await ev(`(() => { const i=document.querySelector("#refreshRate"); i.value="6"; i.dispatchEvent(new Event("change")); return "ok"; })()`);
await sleep(800);
let st = JSON.parse(await ev(`JSON.stringify({eff: motorDbg.state.refreshIntervalMs, val: document.querySelector("#refreshRate").value, valid: document.querySelector("#refreshRate").checkValidity(), map: motorDbg.state.refreshByTp})`));
check("6ms 被接受(没被钳回 50)", st.eff === 6, JSON.stringify(st));
check("输入框显示 6", st.val === "6", JSON.stringify(st));
check("通过浏览器原生校验(min 已同步)", st.valid === true, JSON.stringify(st));
check("只写进 ecat 那一份,wifi 仍 250", st.map.ecat === 6 && st.map.wifi === 250, JSON.stringify(st));
const hz1 = await measure(4);
out.push(` 实测 ${hz1.toFixed(1)} Hz(每拍一次 HTTP 往返,6ms 档约 160Hz 量级)`);
check("6ms 明显快于 50ms 档", hz1 > hz50 * 1.5, `6ms=${hz1.toFixed(1)} vs 50ms=${hz50.toFixed(1)}`);
check("但达不到理论 166Hz 以上(受每拍 HTTP 往返限制)", hz1 < 200, `实测 ${hz1.toFixed(1)}`);
out.push("\n[C2] 硬填旧的 1ms —— 应被抬到下限 6ms");
await ev(`(() => { const i=document.querySelector("#refreshRate"); i.value="1"; i.dispatchEvent(new Event("change")); return "ok"; })()`);
await sleep(500);
st = JSON.parse(await ev(`JSON.stringify({eff: motorDbg.state.refreshIntervalMs, val: document.querySelector("#refreshRate").value})`));
check("旧 1ms 被抬到 6ms", st.eff === 6, JSON.stringify(st));
check("输入框回填 6", st.val === "6", JSON.stringify(st));
out.push("\n[D] 不限速档不应亮卡顿黄标");
st = JSON.parse(await ev(`JSON.stringify({stuck: motorDbg.state.tickStuck, cls: document.querySelector("#chSelect").classList.contains("stuck"), title: document.querySelector("#chSelect").title})`));
check("tickStuck 保持 0(每拍撞上在途请求是预期行为,不算卡顿)", st.stuck === 0, JSON.stringify(st));
check("通道下拉框没有 .stuck 黄标", st.cls === false, JSON.stringify(st));
check("没有误导性的卡顿提示文案", !st.title, JSON.stringify(st));
out.push("\n[E] STATUS 面板的 Hz 应是实测值,不是理论 166Hz");
const hzText = await ev(`document.querySelector("#statusIdLabel").textContent`);
out.push(` 面板显示: ${hzText}`);
const m = String(hzText).match(/([\d.]+)\s*Hz/);
check("面板有 Hz 读数", !!m || /\u2026/.test(String(hzText)), hzText);
if (m) {
const shown = parseFloat(m[1]);
check("显示的不是谎报的理论值", shown < 200, `显示 ${shown}Hz`);
check("显示值与实测频率同量级(±60%)",
Math.abs(shown - hz1) / Math.max(hz1, 1) < 0.6, `显示 ${shown} vs 实测 ${hz1.toFixed(1)}`);
}
out.push("\n[F] 切到 WiFi:6ms 不能带过去(wifi 下限 50ms)");
await ev(`(() => { const s=document.querySelector("#transportSelect"); s.value="wifi"; s.dispatchEvent(new Event("change")); return "ok"; })()`);
await sleep(1500);
st = JSON.parse(await ev(`JSON.stringify({eff: motorDbg.state.refreshIntervalMs, val: document.querySelector("#refreshRate").value, min: document.querySelector("#refreshRate").min, step: document.querySelector("#refreshRate").step})`));
check("WiFi 生效值 250(不是 ECAT 的 6)", st.eff === 250, JSON.stringify(st));
check("WiFi 下 min 收回 50", st.min === "50", JSON.stringify(st));
check("WiFi 下 step 收回 50", st.step === "50", JSON.stringify(st));
/* 在 WiFi 下硬填 1ms,应被钳到 50 */
await ev(`(() => { const i=document.querySelector("#refreshRate"); i.value="1"; i.dispatchEvent(new Event("change")); return "ok"; })()`);
await sleep(500);
st = JSON.parse(await ev(`JSON.stringify({eff: motorDbg.state.refreshIntervalMs, val: document.querySelector("#refreshRate").value})`));
check("WiFi 下填 1ms 被钳到 50(无线往返撑不住)", st.eff === 50, JSON.stringify(st));
out.push("\n[G] 切回 ECAT:仍记着自己的 6ms");
await ev(`(() => { const s=document.querySelector("#transportSelect"); s.value="ecat"; s.dispatchEvent(new Event("change")); return "ok"; })()`);
await sleep(2500);
st = JSON.parse(await ev(`JSON.stringify({eff: motorDbg.state.refreshIntervalMs, val: document.querySelector("#refreshRate").value, min: document.querySelector("#refreshRate").min})`));
check("ECAT 生效值回到 6ms", st.eff === 6, JSON.stringify(st));
check("min 重新放开到 6", st.min === "6", JSON.stringify(st));
/* 刷新页面后 6ms 应持久化 */
await send("Page.navigate", { url: PAGE }); await sleep(3000);
st = JSON.parse(await ev(`JSON.stringify({eff: motorDbg.state.refreshIntervalMs, val: document.querySelector("#refreshRate").value, map: motorDbg.state.refreshByTp})`));
check("刷新页面后 ECAT 仍是 6ms(持久化 + 加载时不被钳高)", st.eff === 6, JSON.stringify(st));
check("刷新后 wifi 那一份仍是 50(上面手动改过)", st.map.wifi === 50, JSON.stringify(st));
console.log(out.join("\n"));
console.log("\n" + "=".repeat(56));
console.log(`通过 ${pass} 项,失败 ${fail} 项`);
sock.close();
process.exit(fail === 0 ? 0 : 1);