Files
DCU_ECAT_Master/motor.html
T
cbb 85d326d445 feat(motor): 电机控制台前端 + 后端自测脚本
motor.html/motor.js: N1-N9 九节点控制台, 走 EtherCAT 过程数据(不经
WiFi/HTTP)。节点总览、CMD 控制(8 种 Control_Mode)、STATUS 实时解码、
示波器曲线、故障标签。样式复用 ecat.css(它与 ESP32 motor.css md5 一致),
不再拷第三份避免三处漂移; 本页专有的链路状态条/槽扫描指示走内联 style。

test_motor_backend.py: 不接硬件的后端自测, 假从站模拟 GD32
buildInput/processOutput, 覆盖 8 项(见 f09decc)。跑法:
  venv\Scripts\python.exe test_motor_backend.py
2026-09-03 21:42:19 +08:00

412 lines
26 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>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>电机控制台 (EtherCAT) — DCU LAN9252</title>
<!-- 空 favicon:阻止浏览器自动请求 /favicon.ico(本地静态服务无此文件会 404) -->
<link rel="icon" href="data:,">
<!-- 样式直接复用 ecat.css(它本身就是 ESP32 motor.css 的副本,md5 一致),
不再拷第三份,避免以后三处漂移。改 CSS 后记得 bump ?v= -->
<link rel="stylesheet" href="ecat.css?v=20260903b">
<style>
/* 本页专有:EtherCAT 链路状态条 + 槽扫描指示 */
.link-chips{display:flex;gap:var(--sp-2);flex-wrap:wrap;align-items:center}
.lchip{font-family:var(--font-mono);font-size:.68rem;padding:2px 7px;border-radius:var(--radius-sm);
border:1px solid var(--border);color:var(--fg-muted);background:var(--bg-raised)}
.lchip b{color:var(--fg);font-weight:600}
.lchip.ok{border-color:var(--ok);color:var(--ok)}
.lchip.err{border-color:var(--danger);color:var(--danger)}
.scan-bar{display:flex;gap:3px;margin-top:var(--sp-2)}
.scan-cell{flex:1;height:4px;border-radius:2px;background:var(--border);transition:background .12s}
.scan-cell.hit{background:var(--primary)}
</style>
</head>
<body>
<!-- ═══════════ TOOLBAR ═══════════ -->
<header class="toolbar">
<div class="toolbar-brand">
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" aria-hidden="true">
<rect x="1" y="1" width="24" height="24" rx="6" stroke="var(--primary)" stroke-width="1.5" opacity=".6"/>
<circle cx="13" cy="13" r="5.5" stroke="var(--primary)" stroke-width="1.8"/>
<path d="M13 4v3.5M13 18.5V22M4 13h3.5M18.5 13H22" stroke="var(--primary)" stroke-width="1.5" stroke-linecap="round"/>
<circle cx="13" cy="13" r="1.8" fill="var(--primary)"/>
</svg>
<span>电机控制台</span>
<span class="sub">EtherCAT PDO</span>
</div>
<div class="toolbar-sep"></div>
<div class="conn-group" title="EtherCAT 网卡与从站状态;连接配置在 EtherCAT 主页">
<span class="led" id="connLed"></span>
<span class="conn-ip-label" id="ipLabel">未连接</span>
<span style="font-size:.72rem;color:var(--fg-dim);font-family:var(--font-mono)" id="devStatus">探测中…</span>
</div>
<div class="toolbar-sep"></div>
<div class="conn-group">
<label for="chSelect" style="font-size:.72rem;color:var(--fg-muted);font-family:var(--font-mono);letter-spacing:.06em">通道</label>
<select class="ch-select" id="chSelect">
<option value="0">CH0</option>
<option value="1">CH1</option>
<option value="2">CH2</option>
</select>
</div>
<div class="toolbar-sep"></div>
<div class="conn-group" title="后台 PDO 循环周期(ms,1~100)。这是真实的总线节拍:状态窗口每周期滚动 7 槽,4 个周期扫完 27 槽">
<label for="periodMs" class="refresh-hint">PDO 周期</label>
<input type="number" id="periodMs" class="refresh-input" value="4" min="1" max="100" step="1" aria-label="PDO 循环周期(毫秒)" />
<span class="refresh-hint">ms</span>
</div>
<div class="conn-group" title="页面读取快照的间隔(ms)。读快照不碰总线,可以比 PDO 周期慢">
<label for="refreshRate" class="refresh-hint">界面刷新</label>
<input type="number" id="refreshRate" class="refresh-input" value="100" min="30" max="2000" step="10" aria-label="界面刷新间隔(毫秒)" />
<span class="refresh-hint">ms</span>
</div>
<div class="toolbar-right">
<a class="btn btn-sm" href="/" title="返回 EtherCAT 主页配置连接">返回主页</a>
<span class="node-badge" id="nodeBadge">NODE -</span>
</div>
</header>
<div class="net-banner" id="netBanner">
<span class="led led-err led-pulse" style="width:7px;height:7px"></span>
<span id="netBannerText">未连接 — 请先在 EtherCAT 主页点「连接」,再回到本页启动 PDO 循环</span>
</div>
<!-- ═══════════ MAIN 3-ZONE GRID ═══════════ -->
<main class="main-grid">
<!-- LEFT: NODE OVERVIEW -->
<aside class="panel nodes-panel">
<div class="panel-head">
<span class="panel-title">节点总览</span>
<span style="margin-left:auto;font-size:.68rem;color:var(--fg-dim);font-family:var(--font-mono)" id="nodeSummary">0/9 在线 · PDO</span>
</div>
<div class="panel-body">
<!-- 独立 BOOT 状态:总线检测到 0x103 心跳时显示,不归属任何 Nx -->
<div id="bootBadge" class="boot-badge" aria-hidden="true">
<span class="boot-dot" aria-hidden="true"></span>
<span class="boot-txt">电机在 BOOT</span>
<span class="boot-sub" id="bootBadgeSub">0x103</span>
</div>
<div class="node-list" id="nodeList" role="listbox" aria-label="电机节点列表 N1-N9(离线可选)">
<!-- N1–N9 由 JS 固定渲染,离线也可选中 -->
</div>
</div>
</aside>
<!-- CENTER: CONTROL -->
<section class="panel center-panel">
<div class="panel-head">
<span class="panel-title">CMD 控制</span>
<span style="font-size:.68rem;color:var(--fg-dim);font-family:var(--font-mono);margin-left:auto" id="cmdIdLabel">ID 0x000-0200 · DLC 32</span>
</div>
<div class="cmd-section">
<div class="cmd-grid">
<div class="field">
<span class="field-label">Control_Mode <span class="field-unit">控制模式</span></span>
<select id="modeSelect" aria-label="控制模式">
<option value="0">0 — Disable 关闭</option>
<option value="1">1 — Angle 位置控制</option>
<option value="2">2 — Velocity 速度控制</option>
<option value="3" selected>3 — Velocity_Angle 速度+位置</option>
<option value="4">4 — MIT Mode</option>
<option value="5">5 — Current 电流/力矩</option>
<option value="6">6 — 开环速度</option>
<option value="7">7 — 开环速度 (2)</option>
</select>
<span class="mode-desc" id="modeDesc">速度+位置环联合控制,目标速度与目标角度同时生效</span>
</div>
<div class="field">
<span class="field-label">Limit_Voltage <span class="field-unit">V · 0~65.535</span></span>
<input type="number" id="inVoltage" value="24.000" step="0.5" min="0" max="65.535" aria-label="电压限制" />
</div>
<div class="field" id="fldCurrent">
<span class="field-label">Target_Current <span class="field-unit">mA</span></span>
<input type="number" id="inCurrent" value="0" step="100" aria-label="目标电流" />
</div>
<div class="field" id="fldVelocity">
<span class="field-label">Target_Velocity <span class="field-unit">deg/s</span></span>
<input type="number" id="inVelocity" value="360" step="10" aria-label="目标速度" />
</div>
<div class="field" id="fldAngle">
<span class="field-label">Target_Angle <span class="field-unit">deg</span></span>
<input type="number" id="inAngle" value="180.0" step="1" aria-label="目标角度" />
</div>
</div>
<div class="field" style="margin-top:var(--sp-4)">
<span class="field-label">PID 增益 <span class="field-unit">P × 3 环</span></span>
<div class="pid-group">
<div class="field"><span class="field-label" style="font-size:.62rem">电流环 P</span><input type="number" id="pidCurP" value="100" step="1" /></div>
<div class="field"><span class="field-label" style="font-size:.62rem">速度环 P</span><input type="number" id="pidVelP" value="100" step="1" /></div>
<div class="field"><span class="field-label" style="font-size:.62rem">角度环 P</span><input type="number" id="pidAngP" value="100" step="1" /></div>
</div>
</div>
<div class="quick-run" id="quickRun" title="把值写入当前模式的主目标字段并立即发送 CMD">
<span class="qr-label">快捷运行 <span class="qr-target" id="qrTargetHint">→ 目标角度 deg</span></span>
<div class="qr-btns">
<button class="qr-btn" data-val="-9999">-9999</button>
<button class="qr-btn" data-val="-200">-200</button>
<button class="qr-btn" data-val="-100">-100</button>
<button class="qr-btn" data-val="0">0</button>
<button class="qr-btn" data-val="50">50</button>
<button class="qr-btn" data-val="100">100</button>
<button class="qr-btn" data-val="200">200</button>
<button class="qr-btn" data-val="1000">1000</button>
<button class="qr-btn" data-val="9999">9999</button>
</div>
</div>
<div class="cmd-actions">
<button class="btn btn-primary" id="sendCmdBtn">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true"><path d="M1.5 7h9M8 3.5L11.5 7 8 10.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
发送 CMD
</button>
<button class="btn" id="sendRepeatBtn">周期发送</button>
<label class="repeat-period" title="周期发送间隔(最小 20ms,EtherCAT 下不受 HTTP socket 限制)">
<input type="number" id="repeatPeriod" value="50" min="20" max="10000" step="10" />
<span>ms</span>
</label>
<span class="send-hint" id="sendHint">POST /api/motor/send · CH0</span>
</div>
</div>
<!-- TABS -->
<nav class="tabs-bar" aria-label="辅助功能">
<button class="tab-btn active" data-tab="config">配置操作</button>
<button class="tab-btn" data-tab="version">版本信息</button>
<button class="tab-btn" data-tab="nodeid">节点 ID</button>
<button class="tab-btn" data-tab="link">链路诊断</button>
<button class="tab-btn" data-tab="log">日志</button>
</nav>
<div class="tab-content">
<!-- CONFIG TAB -->
<div class="tab-page active" id="tab-config">
<div class="config-grid">
<button class="config-btn danger-action" data-action="motReset">
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" aria-hidden="true"><path d="M4 11a7 7 0 1 1 2 4.9M4 16v-5h5" stroke="var(--danger)" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span class="cb-name">电机复位</span><span class="cb-sig">motReset · bit0</span>
</button>
<button class="config-btn" data-action="saveConfig">
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" aria-hidden="true"><path d="M5 3h9l4 4v12H5V3z" stroke="var(--primary)" stroke-width="1.6" stroke-linejoin="round"/><path d="M8 3v5h6V3M8 18v-5h7v5" stroke="var(--primary)" stroke-width="1.4"/></svg>
<span class="cb-name">保存配置</span><span class="cb-sig">saveConfig · bit2</span>
</button>
<button class="config-btn" data-action="angleReset">
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" aria-hidden="true"><circle cx="11" cy="11" r="7.5" stroke="var(--primary)" stroke-width="1.6"/><path d="M11 6v5l3.5 2" stroke="var(--primary)" stroke-width="1.5" stroke-linecap="round"/></svg>
<span class="cb-name">角度置零</span><span class="cb-sig">angle_reset · bit3</span>
</button>
<button class="config-btn" data-action="zeroCalib">
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" aria-hidden="true"><circle cx="11" cy="11" r="7" stroke="var(--primary)" stroke-width="1.6"/><path d="M11 2v4M11 16v4M2 11h4M16 11h4" stroke="var(--primary)" stroke-width="1.5" stroke-linecap="round"/><circle cx="11" cy="11" r="1.5" fill="var(--primary)"/></svg>
<span class="cb-name">零点校准</span><span class="cb-sig">zero_calibration · bit5</span>
</button>
<button class="config-btn" data-action="sleep">
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" aria-hidden="true"><path d="M17 13.5A7 7 0 0 1 8.5 5a7 7 0 1 0 8.5 8.5z" stroke="var(--warn)" stroke-width="1.6" stroke-linejoin="round"/></svg>
<span class="cb-name">休眠</span><span class="cb-sig">SLEEP · bit56</span>
</button>
</div>
<p style="margin-top:var(--sp-4);font-size:.72rem;color:var(--fg-dim);line-height:1.6">
CMD_CONFIG (0x000<span class="cfgNodeNibble">-</span>0301) · DLC 24 · 每个操作发送单帧脉冲信号,操作前需确认
</p>
</div>
<!-- VERSION TAB -->
<div class="tab-page" id="tab-version">
<button class="btn btn-sm" id="queryVerBtn">
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" aria-hidden="true"><circle cx="6.5" cy="6.5" r="5" stroke="currentColor" stroke-width="1.3"/><path d="M6.5 4.2v.1M6.5 6v3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
查询版本 (GET_VERSION)
</button>
<div class="version-grid" style="margin-top:var(--sp-4)" id="versionGrid">
<div class="ver-card"><div class="ver-type">软件版本</div><div class="ver-date ver-empty" id="verSoft">--</div></div>
<div class="ver-card"><div class="ver-type">硬件版本</div><div class="ver-date ver-empty" id="verHard">--</div></div>
<div class="ver-card"><div class="ver-type">Bootloader</div><div class="ver-date ver-empty" id="verBoot">--</div></div>
</div>
<p style="margin-top:var(--sp-3);font-size:.72rem;color:var(--fg-dim)">
请求/回报共用 0x000<span class="cfgNodeNibble">-</span>00FF(同 ID)· DLC 24 · major.minor.patch.build ×3 (soft/hard/boot)。
回报帧经输入 PDO 段 3「长帧快照」回传 —— 段 1 只有 8 字节,装不下 DLC 24。
</p>
</div>
<!-- NODE ID TAB -->
<div class="tab-page" id="tab-nodeid">
<div class="nodeid-row">
<div class="field" style="min-width:140px">
<span class="field-label">目标 Node ID</span>
<input type="number" id="nodeIdInput" min="1" max="9" value="3" />
</div>
<button class="btn btn-primary btn-sm" id="setNodeIdBtn">设置 ID (0xN0400)</button>
<button class="btn btn-sm" id="getNodeIdBtn">读取 ID (0xN0401)</button>
</div>
<div class="nodeid-result" id="nodeIdResult">连接设备后读取节点 ID</div>
<p style="margin-top:var(--sp-3);font-size:.72rem;color:var(--fg-dim);line-height:1.6">
SET_NODE_ID (0x000N0400 DLC2) / GET_NODE_ID (0x000N0401 DLC2)。写入后 CAN ID 前缀变为 0x000Nxxxx。
</p>
</div>
<!-- LINK DIAG TAB -->
<div class="tab-page" id="tab-link">
<div class="link-chips">
<span class="lchip">从站 <b id="dgSlave">—</b></span>
<span class="lchip">AL <b id="dgAl">—</b></span>
<span class="lchip">WKC <b id="dgWkc">—</b></span>
<span class="lchip">PDO <b id="dgPdo">—</b></span>
<span class="lchip">周期数 <b id="dgCycles">0</b></span>
<span class="lchip">循环 <b id="dgLoop">停止</b></span>
</div>
<div class="link-chips" style="margin-top:var(--sp-3)">
<span class="lchip">ack <b id="dgAck">—</b></span>
<span class="lchip">TX <b id="dgTx">0</b></span>
<span class="lchip">RX <b id="dgRx">0</b></span>
<span class="lchip">丢帧 <b id="dgDrop">0</b></span>
<span class="lchip">积压 <b id="dgBacklog">0</b></span>
<span class="lchip">发送结果 <b id="dgTxRes">—</b></span>
</div>
<div class="link-chips" style="margin-top:var(--sp-3)">
<span class="lchip" title="上一个 PDO 周期实际发出的帧数(批量下发时 >1)">本周期发 <b id="dgSentN">0</b></span>
<span class="lchip" title="待下发队列深度">待发 <b id="dgQueue">0</b></span>
<span class="lchip" title="上一周期从批量接收区回收的帧数">本周期收 <b id="dgRxN">0</b></span>
<span class="lchip" title="从站环形队列仍待回收的帧数,持续 >0 说明该调小 PDO 周期">待收 <b id="dgRxPend">0</b></span>
<span class="lchip" title="从站接收队列满导致的丢帧(增量)">收丢 <b id="dgRxLost">0</b></span>
</div>
<div class="scan-bar" id="scanBar" title="状态窗口滚动扫描进度:每格 = 一个 PDO 周期覆盖的 7 个槽"></div>
<div style="display:flex;gap:var(--sp-3);margin-top:var(--sp-4);flex-wrap:wrap">
<button class="btn btn-primary btn-sm" id="loopStartBtn">启动 PDO 循环</button>
<button class="btn btn-sm" id="loopStopBtn">停止循环</button>
</div>
<p style="margin-top:var(--sp-3);font-size:.72rem;color:var(--fg-dim);line-height:1.7">
过程数据 <b>384B 出 / 640B 入</b>(固件 ≥1.5.56,需重刷 EEPROM 并<b>冷启动</b>)。
这是 LAN9252 的硬上限:邮箱占 0x1000-0x13FF,过程数据区只剩 3072B,
而 SM2/SM3 都是 3-buffer 实占 3×长度 ⇒ 出+入 ≤ 1024B,384+640 恰好用满。
输出 PDO 段 2 是 CANFD 批量发送区(<b id="dgTxCap">332</b>B,
一周期可发 8 帧 DLC32 或 4 帧 DLC64),多轴指令因此能在<b>同一个 PDO 周期</b>下发,
而不是排队一周期一帧;输入 PDO 段 2 每周期回传 7 个电机状态槽,
<b id="dgScanInfo">4 周期</b> 扫完 27 槽(node×channel 双键),
段 4 是批量接收区(<b id="dgRxCap">276</b>B),一周期可回收多帧而非只带最近一帧。
后台循环持续喂 1000ms 过程数据看门狗 —— 停掉循环超过 1 秒,从站会掉出 OP 报 AL 0x001B。
</p>
</div>
<!-- LOG TAB -->
<div class="tab-page" id="tab-log">
<div class="ota-log-toolbar">
<button id="logCopyBtn" title="复制全部日志">复制</button>
<button id="logClearBtn" title="清空日志">清空</button>
</div>
<div class="ota-log" id="opLog" aria-live="polite">
<div class="log-info">[就绪] EtherCAT 电机控制台 · 状态经输入 PDO 段 2 回传,命令经输出 PDO 批量区同周期下发</div>
</div>
</div>
</div>
</section>
<!-- RIGHT: STATUS FEEDBACK -->
<section class="panel status-panel">
<div class="panel-head">
<span class="panel-title">STATUS 实时反馈</span>
<span style="font-size:.68rem;color:var(--fg-dim);font-family:var(--font-mono);margin-left:auto" id="statusIdLabel">0x000-0100 · PDO</span>
</div>
<div class="panel-body">
<div class="offline-overlay show" id="offlineOverlay">
<svg class="off-icon" width="40" height="40" viewBox="0 0 40 40" fill="none" aria-hidden="true"><circle cx="20" cy="20" r="16" stroke="var(--fg-dim)" stroke-width="1.5" stroke-dasharray="4 4"/><path d="M14 26l12-12M26 26L14 14" stroke="var(--fg-dim)" stroke-width="1.5" stroke-linecap="round"/></svg>
<span style="font-size:.85rem" id="offlineTitle">节点离线 — 未收到 STATUS 帧</span>
<span style="font-size:.72rem" id="offlineSub">检查电机供电与 CAN 总线连接</span>
</div>
<div class="status-live hide" id="statusLive">
<div class="big-readings">
<div class="big-reading" data-signal="angle" title="点击展开/收起角度波形">
<div class="br-val" id="rtAngle">--</div>
<div class="br-unit">deg</div>
<div class="br-lbl">RT_Angle <span class="br-toggle">▾ 波形</span></div>
<div class="br-target">目标 <span id="tgtAngle">--</span> deg</div>
<div class="br-scope"><canvas id="miniAngle" aria-label="角度波形" role="img"></canvas></div>
</div>
<div class="big-reading" data-signal="velocity" title="点击展开/收起速度波形">
<div class="br-val" id="rtVelocity">--</div>
<div class="br-unit">deg/s</div>
<div class="br-lbl">RT_Velocity <span class="br-toggle">▾ 波形</span></div>
<div class="br-target">目标 <span id="tgtVelocity">--</span> deg/s</div>
<div class="br-scope"><canvas id="miniVelocity" aria-label="速度波形" role="img"></canvas></div>
</div>
<div class="big-reading" data-signal="current" title="点击展开/收起电流波形">
<div class="br-val" id="rtCurrent">--</div>
<div class="br-unit">mA</div>
<div class="br-lbl">RT_Current <span class="br-toggle">▾ 波形</span></div>
<div class="br-target">目标 <span id="tgtCurrent">--</span> mA</div>
<div class="br-scope"><canvas id="miniCurrent" aria-label="电流波形" role="img"></canvas></div>
</div>
</div>
<div class="status-row">
<div class="temp-block" data-signal="mcuTemp" title="点击展开/收起 MCU 温度曲线">
<div class="temp-head"><span class="temp-name">MCU_Temp<span class="temp-toggle">▾ 曲线</span></span><span class="temp-val" id="mcuTemp">-- °C</span></div>
<div class="temp-bar"><div class="temp-fill" id="mcuTempBar" style="width:0%"></div></div>
<div class="br-scope"><canvas id="miniMcuTemp" aria-label="MCU温度曲线" role="img"></canvas></div>
</div>
<div class="temp-block" data-signal="motTemp" title="点击展开/收起电机温度曲线">
<div class="temp-head"><span class="temp-name">Motor_Temp<span class="temp-toggle">▾ 曲线</span></span><span class="temp-val" id="motTemp">-- °C</span></div>
<div class="temp-bar"><div class="temp-fill" id="motTempBar" style="width:0%"></div></div>
<div class="br-scope"><canvas id="miniMotTemp" aria-label="电机温度曲线" role="img"></canvas></div>
</div>
</div>
<div class="phase-currents">
<div class="pc-head">
<span class="pc-title">三相电流 U / V / W</span>
<div class="pc-live-vals">
<span class="pc-chip" style="--ch-color:var(--primary)">U <b id="valU">+0</b> mA</span>
<span class="pc-chip" style="--ch-color:var(--ok)">V <b id="valV">+0</b> mA</span>
<span class="pc-chip" style="--ch-color:var(--warn)">W <b id="valW">+0</b> mA</span>
</div>
</div>
<div class="scope-wrap">
<canvas id="scopeCanvas" aria-label="三相电流实时波形" role="img"></canvas>
<div class="scope-grid-labels"><span>+5A</span><span>0</span><span>-5A</span></div>
</div>
</div>
<div class="misc-grid">
<div class="misc-item"><span class="mi-lbl">Power_Voltage</span><span class="mi-val" id="pwrVolt">-- V</span></div>
<div class="misc-item"><span class="mi-lbl">test_float</span><span class="mi-val" id="testFloat">--</span></div>
<div class="misc-item"><span class="mi-lbl">test_U16</span><span class="mi-val" id="testU16">--</span></div>
<div class="cpu-ring-wrap" style="grid-column:2;grid-row:2">
<div class="cpu-ring">
<svg width="52" height="52" viewBox="0 0 52 52" role="img" aria-label="CPU占用率">
<circle class="ring-bg" cx="26" cy="26" r="21"/>
<circle class="ring-fg" id="cpuRingFg" cx="26" cy="26" r="21" stroke-dasharray="131.9" stroke-dashoffset="131.9"/>
</svg>
<span class="cpu-pct" id="cpuPct">--%</span>
</div>
<div class="cpu-info"><span class="mi-lbl">CPU_Usage</span><span class="mi-sub" id="cpuSub">-- </span></div>
</div>
</div>
<div class="flag-lights">
<div class="flag-item" id="flagItemMot"><span class="flag-led" id="flagMot"></span>motStatus 使能<span class="flag-state" id="flagMotState">—</span></div>
<div class="flag-item" id="flagItemMag"><span class="flag-led" id="flagMag"></span>magStatus 磁编码<span class="flag-state" id="flagMagState">—</span></div>
<div class="flag-item" id="flagItemThermal"><span class="flag-led" id="flagThermal"></span>thermal 过温<span class="flag-state" id="flagThermalState">—</span></div>
<div class="flag-item" id="flagItemEeprom"><span class="flag-led" id="flagEeprom"></span>EEPROM 存储<span class="flag-state" id="flagEepromState">—</span></div>
</div>
</div>
</div>
</section>
</main>
<!-- ═══════════ STATUS BAR ═══════════ -->
<footer class="statusbar">
<div class="sb-item"><span class="led" id="sbLed" style="width:7px;height:7px"></span> <span id="sbCh">CH0</span> <span class="sb-val" id="sbRx">RX --</span></div>
<div class="sb-sep"></div>
<div class="sb-item">TX <span class="sb-val" id="sbTx">--</span></div>
<div class="sb-sep"></div>
<div class="sb-item">WKC <span class="sb-val" id="sbWkc">--</span></div>
<div class="sb-sep"></div>
<div class="sb-item">状态刷新 <span class="sb-val" id="sbHz">--</span></div>
<span class="sb-last-send" id="sbLastSend">尚未发送</span>
</footer>
<!-- TOAST & MODAL -->
<div class="toast-container" id="toastContainer" aria-live="polite"></div>
<div class="modal-mask" id="modalMask" role="dialog" aria-modal="true">
<div class="modal-box">
<div class="modal-title" id="modalTitle">确认操作</div>
<div class="modal-desc" id="modalDesc"></div>
<div class="modal-actions">
<button class="btn" id="modalCancel">取消</button>
<button class="btn btn-primary" id="modalConfirm">确认执行</button>
</div>
</div>
</div>
<script src="motor.js?v=20260903a"></script>
</body>
</html>