Files
DCU_DesktopController/lab/spi_test.html
T
cbb 31b90f68b3 refactor: 按角色分层目录, HTML 不再平铺根目录
根目录原本平铺 7 个 HTML, 而 js/ 已按功能域分子目录, 两级结构不对称;
且自包含实验页与主功能页平级摆放, 看不出可随时删除。现按角色分三层:

  tabs.html  标签外壳留在根 = 工具入口
  pages/     index / can / can_monitor / motor  主功能页
  lab/       continuity_test / spi_test  自包含实验页(零外部依赖)
  scripts/   build_mem.bat

随迁修正(全部实跑验证):
- pages/ 下 4 页共 22 处 css/js 引用加 ../ 前缀; 页面互链同目录无需改
- js/motor/main.js 的 Worker URL 改用 import.meta.url 解析: 原文档相对
  路径 "js/motor/tick.worker.js" 在 pages/ 下会解析成 /pages/js/... 而
  404, 静默退化到 setTimeout 兜底、丢失后台不节流能力(已验证旧写法 404)
- tabs.html 的 TABS 登记新路径; pageToTab 改按文件名匹配, 旧版
  localStorage 存的裸文件名(can.html)自动迁移到 pages/can.html
- esp32_server.py: GET / 与 /index.html 302 到 /tabs.html(根下已无
  index.html); 注入脚本的 ⌂ 主页判定基准由 index.html 改为 tabs.html
- serve.bat 打印外壳与 OTA 页新地址; README 补目录结构与路径约定

验证: 39/39 静态资源 200, 7 页 200 且注入生效, 逐页解析引用 0 坏链,
路由 / 与 /index.html 302、未知页 404, node --check 与 py_compile 通过。
2026-09-03 21:51:17 +08:00

176 lines
6.7 KiB
HTML
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.
<!DOCTYPE html>
<!--
【临时功能 SPI-SPEEDTEST】SPI 链路测速独立页面
─────────────────────────────────────────────
完全自包含(无外部 css/js 依赖),不被任何其他页面引用。
删除方法:直接删掉本文件即可(固件端点删除方法见
main/monitor/spi_speed_test.cpp 文件头)。
用法:浏览器直接打开本文件,或经 serve.bat 访问
http://localhost:8080/spi_test.html
-->
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SPI 测速(临时工具)</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: "Segoe UI", "Microsoft YaHei", sans-serif;
background: #14171c; color: #d8dee6;
max-width: 760px; margin: 0 auto; padding: 24px 16px;
}
h1 { font-size: 20px; margin-bottom: 4px; }
.sub { color: #7a8494; font-size: 12px; margin-bottom: 20px; }
.card {
background: #1c2129; border: 1px solid #2a313c; border-radius: 8px;
padding: 16px; margin-bottom: 16px;
}
label { font-size: 13px; color: #9aa5b3; margin-right: 6px; }
input {
background: #12151a; color: #d8dee6; border: 1px solid #2a313c;
border-radius: 4px; padding: 6px 8px; font-size: 14px; width: 130px;
}
input.narrow { width: 70px; }
.row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; margin-bottom: 12px; }
button {
background: #2563eb; color: #fff; border: 0; border-radius: 6px;
padding: 8px 18px; font-size: 14px; cursor: pointer;
}
button:hover { background: #1d4ed8; }
button:disabled { background: #3a4250; cursor: wait; }
button.alt { background: #374151; }
button.alt:hover { background: #4b5563; }
pre {
background: #12151a; border: 1px solid #2a313c; border-radius: 6px;
padding: 12px; font-size: 14px; line-height: 1.7; white-space: pre-wrap;
min-height: 120px;
}
pre.fail { border-color: #b91c1c; color: #fca5a5; }
table { width: 100%; border-collapse: collapse; font-size: 13px; }
th, td { text-align: left; padding: 5px 8px; border-bottom: 1px solid #2a313c; }
th { color: #7a8494; font-weight: normal; }
td.bad { color: #f87171; }
.hint { color: #7a8494; font-size: 12px; line-height: 1.7; margin-top: 8px; }
</style>
</head>
<body>
<h1>SPI 链路测速</h1>
<div class="sub">临时诊断工具 [SPI-SPEEDTEST] — 探针 0xA1 APP PING,删除时直接删掉本文件</div>
<div class="card">
<div class="row">
<label>设备 IP</label><input id="ip" value="192.168.31.127">
<label>帧数</label><input id="frames" class="narrow" value="200">
<label>载荷 B</label><input id="payload" class="narrow" value="236">
</div>
<div class="row">
<button id="btnRun">开始测速</button>
<button id="btnRtt" class="alt">空载 RTT(500 帧)</button>
<button id="btnStress" class="alt">压力测试(2000 帧满载)</button>
</div>
<div class="hint">
满帧 236B = 等效电机 OTA 0xD1 数据节奏;载荷 0 = 纯握手往返。<br>
判读:失败必须为 0;「最快」是链路物理极限周期;「平均-最快」差值是软件调度开销。
</div>
</div>
<div class="card">
<pre id="out">等待测试…</pre>
</div>
<div class="card">
<table>
<thead><tr><th>时间</th><th>帧数</th><th>载荷</th><th>失败</th><th>平均</th><th>最快</th><th>吞吐</th></tr></thead>
<tbody id="hist"></tbody>
</table>
<div class="hint">本页历史仅存内存,刷新即清空——方便改参数前后对比。</div>
</div>
<script>
"use strict";
(function () {
var out = document.getElementById("out");
var hist = document.getElementById("hist");
var btns = [
document.getElementById("btnRun"),
document.getElementById("btnRtt"),
document.getElementById("btnStress")
];
var ipInput = document.getElementById("ip");
var savedIp = null;
try { savedIp = window.localStorage.getItem("spi_test_ip"); } catch (e) {}
if (savedIp) { ipInput.value = savedIp; }
function setBusy(busy) {
for (var i = 0; i < btns.length; i++) { btns[i].disabled = busy; }
}
function pad2(n) { return (n < 10 ? "0" : "") + n; }
function addHistory(frames, payload, text) {
/* 从设备中文文本里提取关键数字做历史行 */
var fail = (text.match(/失败:\s*(\d+)/) || [])[1] || "?";
var avg = (text.match(/平均\s*([\d.]+)\s*µs/) || [])[1] || "?";
var min = (text.match(/最快\s*([\d.]+)\s*µs/) || [])[1] || "?";
var tput = (text.match(/等效吞吐:\s*([\d.]+)\s*KB\/s/) || [])[1] || "-";
var d = new Date();
var tr = document.createElement("tr");
var cells = [
pad2(d.getHours()) + ":" + pad2(d.getMinutes()) + ":" + pad2(d.getSeconds()),
String(frames), String(payload) + "B", fail,
avg + "µs", min + "µs", tput === "-" ? "-" : tput + "KB/s"
];
for (var i = 0; i < cells.length; i++) {
var td = document.createElement("td");
td.textContent = cells[i];
if (i === 3 && fail !== "0") { td.className = "bad"; }
tr.appendChild(td);
}
hist.insertBefore(tr, hist.firstChild);
}
function run(frames, payload) {
var ip = ipInput.value.trim();
try { window.localStorage.setItem("spi_test_ip", ip); } catch (e) {}
var url = "http://" + ip + "/spi/speedtest?frames=" + frames + "&payload=" + payload;
setBusy(true);
out.className = "";
out.textContent = "测试中… (" + frames + " 帧 × " + payload + "B)";
fetch(url)
.then(function (r) {
if (!r.ok) { throw new Error("HTTP " + r.status); }
return r.text();
})
.then(function (text) {
var failed = /失败:\s*[1-9]/.test(text);
out.className = failed ? "fail" : "";
out.textContent = text;
addHistory(frames, payload, text);
})
.catch(function (err) {
out.className = "fail";
out.textContent = "请求失败: " + err.message + "\n请检查设备 IP 与网络连通性。";
})
.then(function () { setBusy(false); });
}
function clampInt(v, lo, hi, dflt) {
var n = parseInt(v, 10);
if (isNaN(n)) { return dflt; }
return Math.min(hi, Math.max(lo, n));
}
btns[0].onclick = function () {
run(clampInt(document.getElementById("frames").value, 1, 2000, 200),
clampInt(document.getElementById("payload").value, 0, 236, 236));
};
btns[1].onclick = function () { run(500, 0); };
btns[2].onclick = function () { run(2000, 236); };
})();
</script>
</body>
</html>