fix(ui): 修复 HTML 中文编码损坏(PowerShell 默认编码误写)

从 2c6235d/261caf5 恢复 motor/index/can 页 UTF-8,
仅用 Python 以 UTF-8 重写 ?v= 缓存戳;boot_ecat 去掉误写问号。
勿再对 UTF-8 源文件用 Get-Content|Set-Content 默认编码。
This commit is contained in:
cbb
2026-09-21 02:52:54 +08:00
parent 34c3aebf6f
commit 43b2d4bc13
4 changed files with 214 additions and 206 deletions
+1 -1
View File
@@ -682,7 +682,7 @@ export async function bootEcatFlash() {
/* commit 成功只证明 A 区写进去了;邮箱读不到 APP 名 = EtherCAT 没恢复,
* 对用户就是「不能用」。这里禁止画成半成功:进度钉在提交完成那一档,
* 阶段 6/7 全部警告态,文案直接说清副作用。 */
setEcatStage(6, 'warn', '已复位?');
setEcatStage(6, 'warn', '已复位');
setEcatStage(7, 'err', 'EtherCAT 未恢复');
setEcatStatus('err', '刷写已提交,但 EtherCAT 不可用');
setEcatPct(100, '镜像已写入,但总线邮箱读不到 APP —— EtherCAT 当前不可用');
+43 -43
View File
@@ -3,8 +3,8 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ESP32 CAN �帋縑�批��?/title>
<!-- 蝛?favicon嚗𡁻獈甇X�閫�膥�芸𢆡霂瑟� /favicon.ico嚗�𧋦�圈�����⊥�甇斗�隞嗡� 404嚗?-->
<title>ESP32 CAN 通信控制台</title>
<!-- 空 favicon:阻止浏览器自动请求 /favicon.ico(本地静态服务无此文件会 404) -->
<link rel="icon" href="data:,">
<link rel="stylesheet" href="../css/base.css?v=857f83a5">
<link rel="stylesheet" href="../css/layout.css?v=20260924a">
@@ -25,25 +25,25 @@
</svg>
</span>
<div class="brand-text">
<strong>ESP32 CAN �帋縑�批��?/strong>
<strong>ESP32 CAN 通信控制台</strong>
<span class="brand-sub">DCU · Robot_v2 · CAN FD</span>
</div>
</div>
<div class="header-status">
<span class="pill offline" id="canConnPill"><span class="indicator"></span><span id="canConnTxt">�芾��?/span></span>
<span class="pill offline" id="canConnPill"><span class="indicator"></span><span id="canConnTxt">未连接</span></span>
<span class="pill" id="canIpPill">IP --</span>
<span class="pill" id="canRssiPill">RSSI --</span>
<span class="pill" id="canUptimePill">运行 --</span>
<span class="pill" id="canVersionPill">v--</span>
<span class="pill" id="canChannelPill">CH0</span>
<span class="pill" id="dbcSummaryPill">�芸紡�?DBC</span>
<span class="pill" id="dbcSummaryPill">未导入 DBC</span>
<span class="pill" id="messageSummaryPill">0 报文</span>
<span class="pill" id="fbModePill">模式: --</span>
<span class="pill" id="fbPollStatus">轮询: --</span>
</div>
<div class="header-sub can-header-actions">
<a class="pill can-link-pill" href="index.html">餈𥪜� OTA �批��?/a>
<a class="pill can-link-pill" href="can_monitor.html" title="�祉��典��烐綉憿?>�烐綉�典�</a>
<a class="pill can-link-pill" href="index.html">返回 OTA 控制台</a>
<a class="pill can-link-pill" href="can_monitor.html" title="独立全屏监控页">监控全屏</a>
</div>
</header>
@@ -56,7 +56,7 @@
<div class="card-head can-msg-list-head">
<span class="ico">📚</span>
<span>报文列表</span>
<!-- 撌脣紡�伐���恣 + ��辣 chip嚗�虾�𣂷葵蝘駁膄嚗?-->
<!-- 已导入:合计 + 文件 chip(可逐个移除) -->
<div class="dbc-summary-strip dbc-summary-inline" id="dbcFileInfo" hidden>
<span class="dbc-summary-meta">
<span><b id="dbcFileCount">0</b>文件</span>
@@ -72,7 +72,7 @@
<button class="btn btn-primary btn-sm" id="importDbcBtn" type="button">导入 DBC</button>
</div>
</div>
<!-- �𡁻� / �𦦵揣 / �暸�?/ ��� / �寥� �䔶�撌亙��?-->
<!-- 通道 / 搜索 / 勾选 / 节点 / 批量 同一工具栏 -->
<div class="can-msg-toolbar" id="canMsgToolbar">
<label class="can-msg-channel" for="defaultChannel">
<span class="can-msg-channel-label">通道</span>
@@ -84,43 +84,43 @@
</label>
<div class="can-msg-search-wrap">
<input class="input can-msg-search" type="text" id="dbcSearch"
placeholder="�𦦵揣�交��?/ ID / ��� / 靽∪噡" autocomplete="off">
placeholder="搜索报文名 / ID / 节点 / 信号" autocomplete="off">
</div>
<div class="can-batch-actions">
<span class="pill" id="selectedCountPill">撌脣㗲�?0</span>
<span class="pill" id="selectedCountPill">已勾选 0</span>
<div class="can-node-filter" id="nodeFilterWrap">
<button class="btn btn-outline btn-sm can-node-filter-btn" id="nodeFilterBtn" type="button" aria-haspopup="true" aria-expanded="false">
<span class="can-node-filter-label" id="nodeFilterLabel">发送节点:全部</span>
<span class="can-node-filter-caret">�?/span>
<span class="can-node-filter-caret">▾</span>
</button>
<div class="can-node-filter-panel" id="nodeFilterPanel" hidden>
<div class="can-node-filter-head">
<input class="input can-node-filter-search" id="nodeFilterSearch" type="text" placeholder="蝑偦�㕑��孵��?>
<input class="input can-node-filter-search" id="nodeFilterSearch" type="text" placeholder="筛选节点名…">
<div class="can-node-filter-actions">
<button class="btn btn-ghost btn-sm" id="nodeFilterAllBtn" type="button">�券�?/button>
<button class="btn btn-ghost btn-sm" id="nodeFilterAllBtn" type="button">全选</button>
<button class="btn btn-ghost btn-sm" id="nodeFilterNoneBtn" type="button">清空</button>
</div>
</div>
<div class="can-node-filter-list" id="nodeFilterList">
<div class="status-empty">��紡�?DBC�?/div>
<div class="status-empty">先导入 DBC。</div>
</div>
</div>
</div>
<label class="checkbox-inline can-batch-all-label" title="�券�?�𡝗�敶枏�餈�誘����㗇𥁒�?>
<label class="checkbox-inline can-batch-all-label" title="全选/取消当前过滤的所有报文">
<input type="checkbox" id="selectAllBox">
<span>�券�匧��滩�皛?/span>
<span>全选当前过滤</span>
</label>
<button class="btn btn-outline btn-sm" id="expandAllBtn" type="button">展开全部</button>
<button class="btn btn-outline btn-sm" id="collapseAllBtn" type="button">收起全部</button>
<button class="btn btn-primary btn-sm" id="sendCheckedBtn" type="button">�寥��煾��㗲�?/button>
<button class="btn btn-primary btn-sm" id="sendCheckedBtn" type="button">批量发送勾选</button>
</div>
</div>
<div class="card-body can-table-card-body">
<div class="message-list" id="messageList">
<div class="dbc-empty-drop" id="dbcEmptyDrop">
<div class="upload-ico">📥</div>
<h4>撖澆� DBC 撘�憪讠�颲烐𥁒�?/h4>
<p>�孵稬甇文��𡝗��?<code>.dbc</code> ��辣�啣�銵典躹�?/p>
<h4>导入 DBC 开始编辑报文</h4>
<p>点击此处或拖拽 <code>.dbc</code> 文件到列表区域</p>
</div>
</div>
<div class="helper-note" id="dbcHint" hidden></div>
@@ -128,10 +128,10 @@
</section>
<section class="card can-history-card">
<div class="card-head"><span class="ico">��</span> ��餈穃��?/div>
<div class="card-head"><span class="ico">🕘</span> 最近发送</div>
<div class="card-body">
<div class="history-list history-list-compact" id="historyList">
<div class="status-empty">撠𡁏𧊋�煾��遙雿?CAN 撣扼�?/div>
<div class="status-empty">尚未发送任何 CAN 帧。</div>
</div>
</div>
</section>
@@ -139,12 +139,12 @@
<aside class="can-sidepanel">
<section class="card can-preview-card">
<div class="card-head"><span class="ico">�𤌴儭?/span> 撣折�閫?/div>
<div class="card-head"><span class="ico">🛰️</span> 帧预览</div>
<div class="card-body">
<div class="can-frame-panel can-frame-panel-compact">
<div class="can-frame-hex" id="generatedHex">--</div>
<div class="can-byte-strip" id="generatedBytes"></div>
<div class="helper-note" id="encodeHint">�㗇𥋘�交��𠬍�靽∪噡�潔�摰墧𧒄蝻𣇉�銝?CAN �唳旿�?/div>
<div class="helper-note" id="encodeHint">选择报文后,信号值会实时编码为 CAN 数据。</div>
<div class="btn-row can-frame-actions">
<button class="btn btn-outline btn-sm" id="loadPreviewToRawBtn" type="button">载入到原始发送区</button>
<button class="btn btn-primary btn-sm" id="sendSelectedBtn" type="button">发送当前预览帧</button>
@@ -158,7 +158,7 @@
</section>
<section class="card can-raw-card">
<div class="card-head"><span class="ico">�㵪�</span> �笔�撣批��?/div>
<div class="card-head"><span class="ico">✍️</span> 原始帧发送</div>
<div class="card-body">
<div class="row-2 can-raw-grid">
<div class="form-row" style="margin:0;">
@@ -183,7 +183,7 @@
<input class="input" type="text" id="manualData" value="0102030405060708" placeholder="01020304...">
</div>
<div class="helper-note" id="manualHint">
�笔�撣扳芋撘誯���蝏閗� DBC �湔𦻖�?HEX嚗峕��煾����漤�閫�葷�𦒘�甈∪凝靚��?
原始帧模式适合绕过 DBC 直接发 HEX,或发送当前预览帧后二次微调。
</div>
<div class="btn-row">
<button class="btn btn-primary" id="sendManualBtn" type="button">发送原始帧</button>
@@ -214,22 +214,22 @@
<div class="input-group" style="margin-bottom:12px;flex-wrap:wrap;">
<button class="btn btn-primary btn-sm" id="btnModeAllPass" type="button">全收模式</button>
<button class="btn btn-outline btn-sm" id="btnModeFilter" type="button">过滤模式</button>
<button class="btn btn-outline btn-sm" id="btnModeBridge" type="button" title="CAN0 �嗅����銝�撣扳��笔葷�澆��芸𢆡頧砍��?CAN1 銝?CAN2">CAN0�?/2 獢交𦻖</button>
<button class="btn btn-outline btn-sm" id="btnModeBridge" type="button" title="CAN0 收到的每一帧按原帧格式自动转发到 CAN1 与 CAN2">CAN0→1/2 桥接</button>
</div>
<div class="helper-note" id="monHelper" style="margin-bottom:14px;">
�啣�銝𡡞▲�譍��湛�1Hz <code>/can/feedback</code>����𨀣𡂝撅劐����撘��?
地址与顶栏一致;1Hz <code>/can/feedback</code>。悬停抽屉保持展开。
</div>
<div class="status-section-title">銝㕑楝 CAN �𡁻�嚗?Hz嚗?/div>
<div class="status-section-title">三路 CAN 通道(1Hz)</div>
<div class="feedback-grid feedback-grid-drawer">
<div class="feedback-channel collapsed" id="fbCh0">
<div class="feedback-ch-head" role="button" tabindex="0" title="点击展开/折叠详情">
<span class="ch-dot ch0"></span> CAN0
<span class="feedback-expand-caret">�?/span>
<span class="feedback-expand-caret">▾</span>
</div>
<div class="feedback-primary">
<div class="feedback-row"><span class="fb-label">�嗆�?/span><span class="fb-val" id="fb0State">--</span></div>
<div class="feedback-row"><span class="fb-label">�交𤣰�?/span><span class="fb-val" id="fb0Rx">--</span></div>
<div class="feedback-row"><span class="fb-label">状态</span><span class="fb-val" id="fb0State">--</span></div>
<div class="feedback-row"><span class="fb-label">接收数</span><span class="fb-val" id="fb0Rx">--</span></div>
<div class="feedback-row"><span class="fb-label">发送数</span><span class="fb-val" id="fb0Tx">--</span></div>
<div class="feedback-row"><span class="fb-label">丢帧</span><span class="fb-val" id="fb0Drop">--</span></div>
<div class="feedback-row"><span class="fb-label">错误</span><span class="fb-val" id="fb0Err">--</span></div>
@@ -237,7 +237,7 @@
<div class="feedback-detail">
<div class="feedback-row"><span class="fb-label">TEC</span><span class="fb-val" id="fb0Tec">--</span></div>
<div class="feedback-row"><span class="fb-label">REC</span><span class="fb-val" id="fb0Rec">--</span></div>
<div class="feedback-row"><span class="fb-label">�餌瑪�嗆�?/span><span class="fb-val" id="fb0Bus">--</span></div>
<div class="feedback-row"><span class="fb-label">总线状态</span><span class="fb-val" id="fb0Bus">--</span></div>
<div class="feedback-row"><span class="fb-label">最新ID</span><span class="fb-val mono" id="fb0LastId">--</span></div>
<div class="feedback-row"><span class="fb-label">DLC</span><span class="fb-val" id="fb0Dlc">--</span></div>
<div class="feedback-row"><span class="fb-label">FD/BRS/ESI</span><span class="fb-val" id="fb0Flags">--</span></div>
@@ -250,11 +250,11 @@
<div class="feedback-channel collapsed" id="fbCh1">
<div class="feedback-ch-head" role="button" tabindex="0" title="点击展开/折叠详情">
<span class="ch-dot ch1"></span> CAN1
<span class="feedback-expand-caret">�?/span>
<span class="feedback-expand-caret">▾</span>
</div>
<div class="feedback-primary">
<div class="feedback-row"><span class="fb-label">�嗆�?/span><span class="fb-val" id="fb1State">--</span></div>
<div class="feedback-row"><span class="fb-label">�交𤣰�?/span><span class="fb-val" id="fb1Rx">--</span></div>
<div class="feedback-row"><span class="fb-label">状态</span><span class="fb-val" id="fb1State">--</span></div>
<div class="feedback-row"><span class="fb-label">接收数</span><span class="fb-val" id="fb1Rx">--</span></div>
<div class="feedback-row"><span class="fb-label">发送数</span><span class="fb-val" id="fb1Tx">--</span></div>
<div class="feedback-row"><span class="fb-label">丢帧</span><span class="fb-val" id="fb1Drop">--</span></div>
<div class="feedback-row"><span class="fb-label">错误</span><span class="fb-val" id="fb1Err">--</span></div>
@@ -262,7 +262,7 @@
<div class="feedback-detail">
<div class="feedback-row"><span class="fb-label">TEC</span><span class="fb-val" id="fb1Tec">--</span></div>
<div class="feedback-row"><span class="fb-label">REC</span><span class="fb-val" id="fb1Rec">--</span></div>
<div class="feedback-row"><span class="fb-label">�餌瑪�嗆�?/span><span class="fb-val" id="fb1Bus">--</span></div>
<div class="feedback-row"><span class="fb-label">总线状态</span><span class="fb-val" id="fb1Bus">--</span></div>
<div class="feedback-row"><span class="fb-label">最新ID</span><span class="fb-val mono" id="fb1LastId">--</span></div>
<div class="feedback-row"><span class="fb-label">DLC</span><span class="fb-val" id="fb1Dlc">--</span></div>
<div class="feedback-row"><span class="fb-label">FD/BRS/ESI</span><span class="fb-val" id="fb1Flags">--</span></div>
@@ -275,11 +275,11 @@
<div class="feedback-channel collapsed" id="fbCh2">
<div class="feedback-ch-head" role="button" tabindex="0" title="点击展开/折叠详情">
<span class="ch-dot ch2"></span> CAN2
<span class="feedback-expand-caret">�?/span>
<span class="feedback-expand-caret">▾</span>
</div>
<div class="feedback-primary">
<div class="feedback-row"><span class="fb-label">�嗆�?/span><span class="fb-val" id="fb2State">--</span></div>
<div class="feedback-row"><span class="fb-label">�交𤣰�?/span><span class="fb-val" id="fb2Rx">--</span></div>
<div class="feedback-row"><span class="fb-label">状态</span><span class="fb-val" id="fb2State">--</span></div>
<div class="feedback-row"><span class="fb-label">接收数</span><span class="fb-val" id="fb2Rx">--</span></div>
<div class="feedback-row"><span class="fb-label">发送数</span><span class="fb-val" id="fb2Tx">--</span></div>
<div class="feedback-row"><span class="fb-label">丢帧</span><span class="fb-val" id="fb2Drop">--</span></div>
<div class="feedback-row"><span class="fb-label">错误</span><span class="fb-val" id="fb2Err">--</span></div>
@@ -287,7 +287,7 @@
<div class="feedback-detail">
<div class="feedback-row"><span class="fb-label">TEC</span><span class="fb-val" id="fb2Tec">--</span></div>
<div class="feedback-row"><span class="fb-label">REC</span><span class="fb-val" id="fb2Rec">--</span></div>
<div class="feedback-row"><span class="fb-label">�餌瑪�嗆�?/span><span class="fb-val" id="fb2Bus">--</span></div>
<div class="feedback-row"><span class="fb-label">总线状态</span><span class="fb-val" id="fb2Bus">--</span></div>
<div class="feedback-row"><span class="fb-label">最新ID</span><span class="fb-val mono" id="fb2LastId">--</span></div>
<div class="feedback-row"><span class="fb-label">DLC</span><span class="fb-val" id="fb2Dlc">--</span></div>
<div class="feedback-row"><span class="fb-label">FD/BRS/ESI</span><span class="fb-val" id="fb2Flags">--</span></div>
@@ -299,7 +299,7 @@
</div>
<div class="status-section-title" style="margin-top:16px;display:flex;align-items:center;gap:10px;flex-wrap:wrap;">
<span>瘥?ID 敹怎�銵?/span>
<span>每 ID 快照表</span>
<select class="input" id="idsnapChannel" style="width:100px;text-transform:none;">
<option value="0">CAN0</option>
<option value="1">CAN1</option>
@@ -325,7 +325,7 @@
</aside>
<div class="toast" id="toast">
<span class="toast-ico" id="toastIco">�?/span>
<span class="toast-ico" id="toastIco">✓</span>
<span id="toastMsg">操作成功</span>
</div>
+90 -90
View File
@@ -1,16 +1,16 @@
<!--
index.html - ESP32 OTA 中继控制台主页面(DCU Mission Control · 美化版)
�舀� WiFi OTA嚗𠄌SP32 �祆㦤 + GD32 銝剔誧嚗匧� BLE OTA �屸�𡁻��箔辣��漣�?
支持 WiFi OTA(ESP32 本机 + GD32 中继)和 BLE OTA 双通道固件升级。
配套脚本:app.js (WiFi OTA), ble_ota.js (BLE OTA)
摮鞟頂蝏罸��莎�ESP32=�垍遛 繚 GD32=�亦� 繚 BLE=�?繚 CAN=蝏?繚 ���/靚��=蝝?
子系统配色:ESP32=青绿 · GD32=琥珀 · BLE=蓝 · CAN=绿 · 内存/调试=紫
-->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ESP32 OTA 銝剔誧�批��?/title>
<!-- 蝛?favicon嚗𡁻獈甇X�閫�膥�芸𢆡霂瑟� /favicon.ico嚗�𧋦�圈�����⊥�甇斗�隞嗡� 404嚗?-->
<title>ESP32 OTA 中继控制台</title>
<!-- 空 favicon:阻止浏览器自动请求 /favicon.ico(本地静态服务无此文件会 404) -->
<link rel="icon" href="data:,">
<link rel="stylesheet" href="../css/base.css?v=857f83a5">
<link rel="stylesheet" href="../css/layout.css?v=20260924a">
@@ -21,7 +21,7 @@
<body>
<div class="container">
<!-- ====== HEADER: �賭誘�?====== -->
<!-- ====== HEADER: 命令栏 ====== -->
<header class="header-bar">
<div class="header-brand">
<span class="brand-logo" aria-hidden="true">
@@ -32,19 +32,19 @@
</svg>
</span>
<div class="brand-text">
<strong>ESP32 OTA 銝剔誧�批��?/strong>
<strong>ESP32 OTA 中继控制台</strong>
<span class="brand-sub">DCU · Robot_v2 · Relay</span>
</div>
</div>
<div class="header-status">
<span class="pill offline" id="pillConn"><span class="indicator"></span><span id="pillConnTxt">�芾��?/span></span>
<span class="pill offline" id="pillConn"><span class="indicator"></span><span id="pillConnTxt">未连接</span></span>
<span class="pill" id="pillVer">v--</span>
<span class="pill" id="pillIP">--</span>
<span class="pill" id="pillRSSI">--</span>
<span class="pill" id="pillUptime">运行 --</span>
</div>
<div class="header-sub">
<span class="pill">霈曉��? <b class="pill-val" id="deviceName">-</b></span>
<span class="pill">设备名: <b class="pill-val" id="deviceName">-</b></span>
<span class="pill">设备ID: <b class="pill-val" id="deviceId">-</b></span>
</div>
</header>
@@ -69,7 +69,7 @@
<span class="target-tab-txt">BLE 蓝牙</span>
</button>
<button id="modeEcatBtn" class="btn target-tab-btn btn-outline" data-action="set-console-mode" data-arg="ecat" type="button">
<span class="target-tab-ico" aria-hidden="true">�?/span>
<span class="target-tab-ico" aria-hidden="true">⚡</span>
<span class="target-tab-txt">EtherCAT</span>
</button>
</div>
@@ -82,7 +82,7 @@
</div>
</div>
</div>
<!-- BLE 璅∪�嚗饝aunch 憌擧聢餈墧𦻖�厰僼嚗Ếheckbox 撽勗𢆡嚗𡁜㗲�?撌脰��伐� -->
<!-- BLE 模式:launch 风格连接按钮(checkbox 驱动:勾选=已连接) -->
<div id="bleModeCards" style="display:none;">
<div class="ble-launch-wrap">
<label class="ble-launch-btn" for="bleConnectToggle" data-ble-state="idle">
@@ -120,22 +120,22 @@
</svg>
</div>
<div class="launch">
<div style="--i:1">�?/div>
<div style="--i:2">�?/div>
<div style="--i:3">餈?/div>
<div style="--i:4">�?/div>
<div style="--i:1">蓝</div>
<div style="--i:2">牙</div>
<div style="--i:3">连</div>
<div style="--i:4">接</div>
</div>
<div class="start">
<div class="text1">等待连接</div>
<div class="text2">
<span style="--n:0">撌?/span>
<span style="--n:1">餈?/span>
<span style="--n:2">�?/span>
<span style="--n:0">已</span>
<span style="--n:1">连</span>
<span style="--n:2">接</span>
<span style="--n:3">.</span>
<span style="--n:4">.</span>
<span style="--n:5">.</span>
</div>
<div class="text3">餈墧𦻖銝?..</div>
<div class="text3">连接中...</div>
</div>
</div>
</div>
@@ -145,29 +145,29 @@
<div class="ble-launch-hint">点击按钮连接 / 断开蓝牙设备</div>
</div>
</div>
<!-- EtherCAT 璅∪�嚗鋫PP �?BOOT 閫坿𠧧敺賜� + 銝��桀��?-->
<!-- EtherCAT 模式:APP ↔ BOOT 角色徽章 + 一键切换 -->
<div id="ecatModeCards" style="display:none;">
<div class="jump-role-panel">
<div class="jump-role-badge" id="bootEcatRoleBadge" data-role="unknown"
data-action="boot-ecat-probe" role="button" tabindex="0"
title="�孵稬�齿鰵�X�撖寧垢閫坿𠧧嚗㇁PP / BOOT嚗?>
title="点击重新探测对端角色(APP / BOOT)">
<span class="jump-role-label">当前角色 · 点击探测</span>
<strong id="bootEcatRoleTxt">�芣䔝瘚?/strong>
<span class="jump-role-sw" id="bootEcatRoleSw">�?/span>
<strong id="bootEcatRoleTxt">未探测</strong>
<span class="jump-role-sw" id="bootEcatRoleSw">—</span>
</div>
<div class="jump-role-meta">
<div><span>霈曉��?0x1008</span><b id="bootEcatName">�?/b></div>
<div><span>頧臭辣��𧋦 0x100A</span><b id="bootEcatSw">�?/b></div>
<div><span>隞𡒊��曇�</span><b id="bootEcatArm">�?/b></div>
<div><span>銝餌�蝻枏� / 撌脣� flash</span><b id="bootEcatRecv">�?/b></div>
<div><span>设备名 0x1008</span><b id="bootEcatName">—</b></div>
<div><span>软件版本 0x100A</span><b id="bootEcatSw">—</b></div>
<div><span>从站放行</span><b id="bootEcatArm">—</b></div>
<div><span>主站缓存 / 已写 flash</span><b id="bootEcatRecv">—</b></div>
</div>
</div>
<button class="btn btn-primary btn-block jump-btn-primary" id="bootEcatRebootBtn"
data-action="boot-ecat-reboot"
title="�匧笆蝡航澈隞質䌊�券�㗇䲮�𡢅�APP�?x2502 餈?BOOT嚗𡰪OOT�?x2503 頝喳� APP">霈拙��典�撘訫紡蝔见�</button>
title="按对端身份自动选方向:APP→0x2502 进 BOOT;BOOT→0x2503 跳回 APP">让应用回引导程序</button>
</div>
<!-- 餈墧𦻖����芸銁憿嗆� #pillConnTxt �曄內嚗峕迨憭���漤�憭?-->
<!-- 连接文案只在顶栏 #pillConnTxt 显示,此处不再重复 -->
</div>
</div>
@@ -177,7 +177,7 @@
<div class="card-body">
<div class="target-tab-row" id="targetTabRow">
<button id="targetGd32Tab" class="btn target-tab-btn btn-outline" data-action="select-flash-target" data-arg="gd32" type="button">
<span class="target-tab-ico" aria-hidden="true">�?/span>
<span class="target-tab-ico" aria-hidden="true">⚙</span>
<span class="target-tab-txt">GD32</span>
</button>
<button id="targetEsp32Tab" class="btn target-tab-btn btn-primary" data-action="select-flash-target" data-arg="esp32" type="button">
@@ -189,7 +189,7 @@
<div id="wifiEsp32UploadPane">
<div class="upload-zone upload-zone-sm" id="uploadZoneEsp32" data-action="pick-shared-firmware" data-arg="esp32">
<div class="upload-ico">📤</div>
<h4>�孵稬�𡝗��?ESP32 �箔辣</h4>
<h4>点击或拖拽 ESP32 固件</h4>
</div>
<input type="file" id="fileInputEsp32" accept=".bin" style="display:none;">
<div class="file-info" id="fileInfoEsp32">
@@ -211,8 +211,8 @@
<div id="wifiGd32UploadPane" style="display:none;">
<div class="upload-zone upload-zone-sm" id="uploadZoneGd32" data-action="pick-shared-firmware" data-arg="gd32">
<div class="upload-ico">📤</div>
<h4>�孵稬�𡝗��?GD32 �箔辣</h4>
<p>�舀� .bin �?.hex嚗�䌊�刻蓮�g�</p>
<h4>点击或拖拽 GD32 固件</h4>
<p>支持 .bin 和 .hex(自动转换)</p>
</div>
<input type="file" id="fileInputGd32" accept=".hex,.bin" style="display:none;">
<div class="file-info" id="fileInfoGd32">
@@ -235,7 +235,7 @@
<div class="upload-zone upload-zone-sm" id="uploadZone" style="margin-bottom:12px;" data-action="ble-pick-file">
<div class="upload-ico">📤</div>
<h4>点击或拖拽文件到此处</h4>
<p>�舀� .bin �?.hex �澆�</p>
<p>支持 .bin 和 .hex 格式</p>
</div>
<input id="firmwareFile" type="file" accept=".bin,.hex,application/octet-stream" style="display:none;">
<div class="file-info" id="fileInfo">
@@ -246,20 +246,20 @@
<div class="file-info-grid">
<div class="file-meta-item"><span>类型</span><b id="fileType">-</b></div>
<div class="file-meta-item"><span>大小</span><b id="fileSize">0 B</b></div>
<div class="file-meta-item"><span>�餃��?/span><b id="packetCount">0</b></div>
<div class="file-meta-item"><span>撌脣��?/span><b id="sentCount">0</b></div>
<div class="file-meta-item"><span>总包数</span><b id="packetCount">0</b></div>
<div class="file-meta-item"><span>已写包</span><b id="sentCount">0</b></div>
</div>
<div class="file-meta-item" style="margin-top:8px;">
<span>SHA-256</span>
<b id="fileSha256" style="font-size:10px;word-break:break-all;font-family:var(--font-mono);">-</b>
</div>
</div>
<div class="footer-note">撱箄悅雿輻鍂 Chrome / Edge����?Payload 銝舘圻�穃𦶢隞斤眏蝟餌��厩𤌍��䌊�刻挽蝵柴�?/div>
<div class="footer-note">建议使用 Chrome / Edge。每包 Payload 与触发命令由系统按目标自动设置。</div>
</div>
<div style="height:12px"></div>
<div id="wifiEsp32ActionPane">
<button class="btn btn-primary btn-block" id="uploadEsp32Btn" data-action="start-upload" data-arg="esp32" disabled>
銝𠹺�撟嗅�蝥?ESP32
上传并升级 ESP32
</button>
</div>
<div id="wifiGd32ActionPane" style="display:none;">
@@ -268,13 +268,13 @@
</button>
</div>
<div class="row-2" id="bleActionPane" style="display:none;">
<button id="autoFlashBtn" class="btn btn-primary" disabled>銝��桀��?/button>
<button id="autoFlashBtn" class="btn btn-primary" disabled>一键刷写</button>
<button id="abortBtn" class="btn btn-danger" disabled>中止任务</button>
</div>
<!-- EtherCAT �渡��X踎嚗𡁜蘨�具�諹挽憭���?= EtherCAT�齿𧒄�曄內嚗ÒpplySharedModeUi �批�嚗剹�?
�箔辣憭滨鍂�䔶�銝?#fileInputGd32 銝𤾸�銝��?processFile 頧祆揢/�⊿�嚗?
��隞亥��䔶��箇緵蝚砌�隞?HEX 閫���剁�"�賭��賜�"�芰眏 BOOT �𣬚� ecatArmed()
鋆��嚗��蝡臭�憭滚��斗旿嚗�� js/ota/boot_ecat.js 憭湔釣嚗剹�?-->
<!-- EtherCAT 直灌面板:只在「设备连接 = EtherCAT」时显示(applySharedModeUi 控制)。
固件复用同一个 #fileInputGd32 与同一条 processFile 转换/校验,
所以这里不出现第二份 HEX 解析器;"能不能灌"只由 BOOT 里的 ecatArmed()
裁决,前端不复制判据(见 js/ota/boot_ecat.js 头注)。 -->
<div id="bootEcatPane" style="display:none;">
<button class="btn btn-block" id="bootEcatRunBtn" data-action="boot-ecat-run" disabled>EtherCAT 刷写 APP</button>
<button class="btn btn-danger btn-block" id="bootEcatAbortBtn"
@@ -284,15 +284,15 @@
</div>
</div>
<!-- 4. Flash Progress + Stage Flow嚗ÁiFi/BLE/EtherCAT �梁鍂�䔶�撘㰘�摨血㨃嚗?-->
<!-- 4. Flash Progress + Stage Flow(WiFi/BLE/EtherCAT 共用同一张进度卡) -->
<div class="card card--esp32" id="progressCard">
<div class="card-head"><span class="ico">�?/span> �瑕�餈𥕦漲銝擧�蝔钅𧫴畾?/div>
<div class="card-head"><span class="ico">⚡</span> 刷写进度与流程阶段</div>
<div class="card-body">
<!-- Phase step indicators -->
<div class="progress-phase-row">
<div class="pp-step active" id="ppEsp32"><span class="pp-ico">📦</span><span class="pp-txt">ESP32 OTA</span></div>
<div class="pp-step" id="ppGd32Ota"><span class="pp-ico">📤</span><span class="pp-txt">GD32 OTA</span></div>
<div class="pp-step" id="ppEcatOta"><span class="pp-ico">�?/span><span class="pp-txt">EtherCAT</span></div>
<div class="pp-step" id="ppEcatOta"><span class="pp-ico">⚡</span><span class="pp-txt">EtherCAT</span></div>
</div>
<!-- Unified progress bar -->
<div class="progress-wrap show" id="progressWrap">
@@ -309,7 +309,7 @@
<div class="stage-node" id="es2"><div class="stage-dot">2</div><div class="stage-label">OTA 写入</div><div class="stage-detail" id="esd2"></div></div>
<div class="stage-node" id="es3"><div class="stage-dot">3</div><div class="stage-label">SHA-256 校验</div><div class="stage-detail" id="esd3"></div></div>
<div class="stage-node" id="es4"><div class="stage-dot">4</div><div class="stage-label">应用固件</div><div class="stage-detail" id="esd4"></div></div>
<div class="stage-node" id="es5"><div class="stage-dot">5</div><div class="stage-label">�滚鍳銝?/div><div class="stage-detail" id="esd5"></div></div>
<div class="stage-node" id="es5"><div class="stage-dot">5</div><div class="stage-label">重启中</div><div class="stage-detail" id="esd5"></div></div>
<div class="stage-node" id="es6"><div class="stage-dot">6</div><div class="stage-label">启动完成</div><div class="stage-detail" id="esd6"></div></div>
</div>
<!-- BLE OTA stages -->
@@ -334,7 +334,7 @@
<div class="stage-flow" id="ecatStages" style="display:none;">
<div class="stage-node" id="ec1"><div class="stage-dot">1</div><div class="stage-label">读取镜像</div><div class="stage-detail" id="ecd1"></div></div>
<div class="stage-node" id="ec2"><div class="stage-dot">2</div><div class="stage-label">推到主站</div><div class="stage-detail" id="ecd2"></div></div>
<div class="stage-node" id="ec3"><div class="stage-dot">3</div><div class="stage-label">鈭斗𦻖餈?BOOT</div><div class="stage-detail" id="ecd3"></div></div>
<div class="stage-node" id="ec3"><div class="stage-dot">3</div><div class="stage-label">交接进 BOOT</div><div class="stage-detail" id="ecd3"></div></div>
<div class="stage-node" id="ec4"><div class="stage-dot">4</div><div class="stage-label">确认引导程序</div><div class="stage-detail" id="ecd4"></div></div>
<div class="stage-node" id="ec5"><div class="stage-dot">5</div><div class="stage-label">邮箱灌写</div><div class="stage-detail" id="ecd5"></div></div>
<div class="stage-node" id="ec6"><div class="stage-dot">6</div><div class="stage-label">从站复位</div><div class="stage-detail" id="ecd6"></div></div>
@@ -362,35 +362,35 @@
<!-- Primary status -->
<div class="card card--status">
<div class="card-head"><span class="ico">�摱</span> 霈曉��嗆�?/div>
<div class="card-head"><span class="ico">📡</span> 设备状态</div>
<div class="card-body">
<div class="badge-grid">
<div class="badge-stat">
<div class="v err" id="bConn">离线</div>
<div class="k">餈墧𦻖�嗆�?/div>
<div class="k">连接状态</div>
</div>
<div class="badge-stat badge-stat-click" id="healthBadge"
role="button" tabindex="0" title="点击查看启动自检"
aria-expanded="false" aria-controls="healthBootPanel">
<div class="v" id="mHealth">--</div>
<div class="k">�亙熒�嗆�?<span class="health-boot-hint">�?�芣�</span></div>
<div class="k">健康状态 <span class="health-boot-hint">▾ 自检</span></div>
</div>
<div class="badge-stat">
<div class="v err" id="connBadge">offline</div>
<div class="k">BLE 连接</div>
</div>
<div class="badge-stat">
<div class="v" id="gattState" style="font-size:16px;">�芸停蝏?/div>
<div class="k">GATT �嗆�?/div>
<div class="v" id="gattState" style="font-size:16px;">未就绪</div>
<div class="k">GATT 状态</div>
</div>
</div>
<!-- �臬𢆡�芣�嚗𡁶��亙熒�嗆���撘�嚗屸�霈斗��?-->
<!-- 启动自检:点健康状态展开,默认折叠 -->
<div class="health-boot-panel collapsed" id="healthBootPanel" hidden>
<div class="health-boot-head">
<span class="health-boot-title">启动自检</span>
<span class="health-boot-summary" id="mBootResult">--</span>
</div>
<div class="health-boot-failed">憭梯揖/頝唾�嚗?b id="mBootFailed">--</b></div>
<div class="health-boot-failed">失败/跳过:<b id="mBootFailed">--</b></div>
<div class="boot-module-list" id="bootModuleList" aria-label="启动模块列表"></div>
</div>
</div>
@@ -402,10 +402,10 @@
<div class="card-body">
<div class="metric-grid">
<div class="metric"><div class="k">ESP32 温度</div><div class="v" id="mTemp">--</div></div>
<div class="metric"><div class="k">GD32 皜拙漲</div><div class="v" id="mGd32Temp" title="SPI 0xC4嚗𤤿���萱摨?>--</div></div>
<div class="metric"><div class="k">GD32 韐蠘蝸</div><div class="v" id="mGd32Load" title="DCU_APP CPU 韐蠘蝸���銝剜鱏+�𧼮辣餈笔��?隞餃𦛚瘣曉�/OTA銝剔誧)�惩����嚗䔶��怠虜撘�頧株砭銝𡒊征頧穿�EtherCAT 颲枏� PDO 畾?嚗?s 蝒堒藁">--</div></div>
<div class="metric"><div class="k">颲枏� VCC</div><div class="v" id="mVcc" title="PA6 ����齿綫嚗间CC�?7k�𨀞DC�?.7k�浧ND">--</div></div>
<div class="metric"><div class="k">VDDA</div><div class="v" id="mVdda" title="��� VREFINT �齿綫�?MCU 璅⊥�靘𤤿㩞">--</div></div>
<div class="metric"><div class="k">GD32 温度</div><div class="v" id="mGd32Temp" title="SPI 0xC4;片内温度">--</div></div>
<div class="metric"><div class="k">GD32 负载</div><div class="v" id="mGd32Load" title="DCU_APP CPU 负载率:中断+非延迟处理(任务派发/OTA中继)占墙钟比,不含常开轮询与空转;EtherCAT 输入 PDO 段5,1s 窗口">--</div></div>
<div class="metric"><div class="k">输入 VCC</div><div class="v" id="mVcc" title="PA6 分压反推:VCC—47k—ADC—4.7k—GND">--</div></div>
<div class="metric"><div class="k">VDDA</div><div class="v" id="mVdda" title="片内 VREFINT 反推的 MCU 模拟供电">--</div></div>
<div class="metric"><div class="k">GD32 APP</div><div class="v" id="mGd32App">--</div></div>
<div class="metric"><div class="k">GD32 BOOT</div><div class="v" id="mGd32Boot">--</div></div>
<div class="metric"><div class="k">GD32 自检</div><div class="v" id="mGd32Init" title="GD32 APP 上电初始化状态(SPI 0xA3;悬停看失败模块/复位源)">--</div></div>
@@ -415,13 +415,13 @@
<div class="card card--mem" style="display:none">
<div class="card-head">
<span class="ico">�屁</span> 蝟餌��嗆�?(/system/status)
<span class="ico">🧾</span> 系统状态 (/system/status)
<button class="btn btn-ghost btn-sm card-head-btn" data-action="refresh-system-status">刷新</button>
</div>
<div class="card-body">
<div class="status-kv-grid">
<div class="status-kv"><span class="k">OTA �嗆�?/span><b id="sOtaState">--</b></div>
<div class="status-kv"><span class="k">Relay �嗆�?/span><b id="sRelayState">--</b></div>
<div class="status-kv"><span class="k">OTA 状态</span><b id="sOtaState">--</b></div>
<div class="status-kv"><span class="k">Relay 状态</span><b id="sRelayState">--</b></div>
<div class="status-kv"><span class="k">刷新时间</span><b id="sStatusUpdated">--</b></div>
</div>
</div>
@@ -430,7 +430,7 @@
<!-- Log panel -->
<div class="card card--mem log-panel">
<div class="card-head" style="justify-content:space-between;">
<span><span class="ico">�棅儭?/span> 靚���亙�</span>
<span><span class="ico">🖥️</span> 调试日志</span>
<span style="font-size:11px;font-weight:400;color:var(--text-label);text-transform:none;letter-spacing:0;font-family:var(--font-mono);">UDP :6666</span>
</div>
<div class="card-body">
@@ -444,10 +444,10 @@
<button class="btn btn-ghost btn-sm" onclick="toggleAutoScroll()" id="autoScrollBtn" title="切换自动滚动">📌 自动滚动</button>
</div>
<div class="log-scroll" id="logScroll">
<div class="log-line"><span class="lt">[--:--:--]</span><span class="lv I">I</span><span class="lg">[蝟餌�]</span><span class="lm">蝑匧�銝𠹺��箄��?..</span></div>
<div class="log-line"><span class="lt">[--:--:--]</span><span class="lv I">I</span><span class="lg">[系统]</span><span class="lm">等待上位机连接...</span></div>
</div>
<div class="log-foot">
<span>�?<span id="logCount">0</span> �?/span>
<span>共 <span id="logCount">0</span> 条</span>
<button class="btn btn-ghost btn-sm" onclick="clearLogs()">清除日志</button>
</div>
</div>
@@ -457,15 +457,15 @@
</div>
</div>
<!-- �喃儒皛穃枂嚗𡁜�銝𦒘遙�⊥�嚗��霈日蠧�箔��∴��砍�撅訫�嚗?-->
<aside class="sys-drawer" id="sysDrawer" aria-label="���隞餃𦛚�?>
<div class="sys-drawer-rail" title="�砍�撅訫����隞餃𦛚�?>
<!-- 右侧滑出:堆与任务栈(默认露出一条,悬停展开) -->
<aside class="sys-drawer" id="sysDrawer" aria-label="堆与任务栈">
<div class="sys-drawer-rail" title="悬停展开堆与任务栈">
<span class="sys-drawer-rail-ico">🧠</span>
<span class="sys-drawer-rail-text">���隞餃𦛚�?/span>
<span class="sys-drawer-rail-text">堆与任务栈</span>
</div>
<div class="sys-drawer-panel card card--mem">
<div class="card-head sys-drawer-head">
<span class="ico">��</span> ���隞餃𦛚�?
<span class="ico">🧠</span> 堆与任务栈
<button class="btn btn-ghost btn-sm card-head-btn" type="button" onclick="refreshSystemStatusNow(event)">刷新</button>
</div>
<div class="card-body sys-drawer-body">
@@ -480,19 +480,19 @@
<div class="heap-detail-row">
<span>空闲: <b id="cpuIdlePct">--</b></span>
<span>窗口: <b id="cpuSampleMs">--</b></span>
<span>隞餃𦛚�? <b id="cpuTaskCount">--</b></span>
<span>任务数: <b id="cpuTaskCount">--</b></span>
</div>
</div>
<div class="status-task-list cpu-task-list list-scroll-top3" id="cpuTaskList" title="�匧��券�摨𧶏�暺䁅恕�曄內�?3 憿對�銝𧢲��亦��嗡�">
<div class="status-empty">蝑匧�銝衤�甈∠𠶖����?..</div>
<div class="status-task-list cpu-task-list list-scroll-top3" id="cpuTaskList" title="按占用降序,默认显示前 3 项,下滑查看其余">
<div class="status-empty">等待下一次状态刷新...</div>
</div>
<div class="status-section-title">内存概况</div>
<div class="heap-group">
<div class="heap-group-title">�舐� SRAM �餉� <span style="font-weight:400;color:var(--text-dim);font-size:10px;">(ESP32-C3 繚 �蹱��挾�舀��?</span></div>
<div class="heap-group-title">芯片 SRAM 总览 <span style="font-weight:400;color:var(--text-dim);font-size:10px;">(ESP32-C3 · 静态段可拆分)</span></div>
<div class="heap-bar-row">
<span class="heap-bar-text" id="memSramUsage">--</span>
<span class="heap-bar-track mem-sram-track" id="memSramTrack" title="蝝?IRAM 繚 璈?.data 繚 �?.bss 繚 �?��歇�?繚 �?��征�?>
<span class="heap-bar-track mem-sram-track" id="memSramTrack" title="紫=IRAM · 橙=.data · 灰=.bss · 青=堆已用 · 暗=堆空闲">
<span class="mem-seg mem-seg-iram" id="memSegIram"></span>
<span class="mem-seg mem-seg-data" id="memSegData"></span>
<span class="mem-seg mem-seg-bss" id="memSegBss"></span>
@@ -505,17 +505,17 @@
<span class="heap-legend-item"><i class="mem-dot mem-dot-iram"></i>IRAM <b id="mIram">--</b></span>
<span class="heap-legend-item"><i class="mem-dot mem-dot-data"></i>.data <b id="mData">--</b></span>
<span class="heap-legend-item"><i class="mem-dot mem-dot-bss"></i>.bss <b id="mBss">--</b></span>
<span class="heap-legend-item"><i class="mem-dot mem-dot-used"></i>��歇�?<b id="mHeapUsed">--</b></span>
<span class="heap-legend-item"><i class="mem-dot mem-dot-free"></i>��征�?<b id="mHeapFree">--</b></span>
<span class="heap-legend-item"><i class="mem-dot mem-dot-used"></i>堆已用 <b id="mHeapUsed">--</b></span>
<span class="heap-legend-item"><i class="mem-dot mem-dot-free"></i>堆空闲 <b id="mHeapFree">--</b></span>
</div>
<div class="heap-detail-row">
<span>SOC 窗口: <b id="mSramSoc">--</b></span>
<span>尾部保留: <b id="mReservedTail">--</b></span>
<span>隞餃𦛚���霈? <b id="mTaskStackTotal">--</b></span>
<span>任务栈合计: <b id="mTaskStackTotal">--</b></span>
</div>
</div>
<div class="heap-group">
<div class="heap-group-title">��� DRAM �?<span style="font-weight:400;color:var(--text-dim);font-size:10px;">(malloc �唳旿�?</span></div>
<div class="heap-group-title">内部 DRAM 堆 <span style="font-weight:400;color:var(--text-dim);font-size:10px;">(malloc 数据堆)</span></div>
<div class="heap-bar-row">
<span class="heap-bar-text" id="memInternalUsage">--</span>
<span class="heap-bar-track"><span class="heap-bar-fill" id="memInternalFill"></span></span>
@@ -523,26 +523,26 @@
</div>
<div class="heap-detail-row">
<span>空闲: <b id="mInternalHeap">--</b></span>
<span>��雿𡒊征�? <b id="mInternalMinHeap">--</b></span>
<span>最低空闲: <b id="mInternalMinHeap">--</b></span>
<span>最大块: <b id="mInternalLargestBlock">--</b></span>
</div>
<div class="heap-detail-row">
<span>DMA 空闲: <b id="mDmaFree">--</b></span>
<span>RTC ��征�? <b id="mRtcFree">--</b></span>
<span>RTC 堆空闲: <b id="mRtcFree">--</b></span>
<span>分配块数: <b id="mAllocBlocks">--</b></span>
</div>
</div>
<div class="status-section-title">隞餃𦛚��偌雿?<span style="font-weight:400;text-transform:none;letter-spacing:0;color:var(--text-dim);">嚗�陸�澆��?Top嚗䔶�皛烐䰻�页�</span></div>
<div class="status-task-list list-scroll-top3" id="taskStackList" title="�匧陸�澆��券�摨𧶏�暺䁅恕�曄內�?3 憿對�銝𧢲��亦��嗡�">
<div class="status-empty">蝑匧��嗆����?..</div>
<div class="status-section-title">任务栈水位 <span style="font-weight:400;text-transform:none;letter-spacing:0;color:var(--text-dim);">(峰值占用 Top,下滑查看)</span></div>
<div class="status-task-list list-scroll-top3" id="taskStackList" title="按峰值占用降序,默认显示前 3 项,下滑查看其余">
<div class="status-empty">等待状态刷新...</div>
</div>
<div class="status-subfold collapsed" id="statusJsonFold">
<div class="status-subfold-head" role="button" tabindex="0"
onclick="this.parentElement.classList.toggle('collapsed')"
onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();this.parentElement.classList.toggle('collapsed');}">
<span class="status-section-title status-subfold-title">原始 JSON</span>
<span class="collapse-arrow">�?/span>
<span class="collapse-arrow">▼</span>
</div>
<div class="status-subfold-body">
<pre class="status-json" id="statusJson">--</pre>
@@ -554,20 +554,20 @@
<!-- Toast -->
<div class="toast" id="toast">
<span class="toast-ico" id="toastIco">�?/span>
<span class="toast-ico" id="toastIco">✓</span>
<span id="toastMsg">操作成功</span>
</div>
<!-- app 窗口租约:本页会自动拉起 EtherCAT PDO 循环(GD32 负载),
敹�◆���蝘毺漲嚗�炏�蹱��∠垢�函�蝥血��嗅捐�鞉��𦒘��𠰴儐�臬��𨀣��?
銝?tabs.html / motor.html 撟嗅��嗉��交㺭>1嚗䔶�銝滚蔣�溻�?-->
必须持有租约,否则服务端在租约归零宽限期后会把循环又停掉。
与 tabs.html / motor.html 并存时连接数>1,互不影响。 -->
<script src="../js/common/app-lease.js"></script>
<script type="module" src="../js/app.js?v=20260919a"></script>
<script type="module" src="../js/ota/console-mode.js?v=20260916d"></script>
<script type="module" src="../js/ble_ota.js?v=20260901d"></script>
<!-- ����𨅯�嚗𡁜�甇?BLE �厰僼 data-ble-state 銝?checkbox.checked
銝滢�韏?ES 璅∪�嚗��璅∪��航�鋡急�閫�膥蝻枏�銝箸唂���銝滚鉄 data-ble-state �餉�嚗剹�?
connecting ��眏 session.js click handler 霈曄蔭嚗峕迨憭�歲餈��閬���?-->
<!-- 内联兜底:同步 BLE 按钮 data-ble-state 与 checkbox.checked
不依赖 ES 模块(子模块可能被浏览器缓存为旧版,不含 data-ble-state 逻辑)。
connecting 态由 session.js click handler 设置,此处跳过不覆盖。 -->
<script type="module">
setInterval(function() {
var btn = document.querySelector('.ble-launch-btn');
+80 -72
View File
@@ -3,14 +3,14 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>电机控制�?�?Motor CAN FD Console</title>
<!-- �?favicon:阻止浏览器自动请求 /favicon.ico(本地静态服务无此文件会 404�?-->
<title>电机控制台 — Motor CAN FD Console</title>
<!-- 空 favicon:阻止浏览器自动请求 /favicon.ico(本地静态服务无此文件会 404) -->
<link rel="icon" href="data:,">
<link rel="stylesheet" href="../css/motor.css?v=20260924a">
</head>
<body>
<!-- ══════════�?TOOLBAR ══════════�?-->
<!-- ═══════════ TOOLBAR ═══════════ -->
<header class="toolbar">
<div class="toolbar-brand">
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" aria-hidden="true">
@@ -19,17 +19,17 @@
<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>电机控制台</span>
<span class="sub">Motor CAN FD</span>
</div>
<div class="toolbar-sep"></div>
<div class="conn-group" title="�?OTA 主页共用设备 IP,本页不提供连接配置">
<div class="conn-group" title="与 OTA 主页共用设备 IP,本页不提供连接配置">
<span class="led" id="connLed"></span>
<span class="conn-ip-label" id="ipLabel">IP --</span>
<span style="font-size:.72rem;color:var(--fg-dim);font-family:var(--font-mono)" id="devStatus">探测中�?/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" title="控制链路:WiFi �?ESP32 固件 HTTP API;EtherCAT 走过程数据(自动拉起本地服务、打开网卡并进 OP,无需手工准备)。两种链路的界面与解码完全相同,仅数据通路不同">
<div class="conn-group" title="控制链路:WiFi 走 ESP32 固件 HTTP API;EtherCAT 走过程数据(自动拉起本地服务、打开网卡并进 OP,无需手工准备)。两种链路的界面与解码完全相同,仅数据通路不同">
<label for="transportSelect" style="font-size:.72rem;color:var(--fg-muted);font-family:var(--font-mono);letter-spacing:.06em">链路</label>
<select class="ch-select" id="transportSelect">
<option value="wifi">WiFi</option>
@@ -37,7 +37,7 @@
</select>
<input type="text" id="ecatAdapter" class="refresh-input" style="width:74px;display:none"
value="I210" aria-label="EtherCAT 网卡匹配子串"
title="EtherCAT 网卡描述匹配子串(默�?I210)。改这里会立即重连该网口,设置存本地�? />
title="EtherCAT 网卡描述匹配子串(默认 I210)。改这里会立即重连该网口,设置存本地。" />
</div>
<div class="toolbar-sep"></div>
<div class="conn-group">
@@ -49,23 +49,23 @@
</select>
</div>
<div class="toolbar-sep"></div>
<div class="conn-group" title="电机状态刷新间隔(ms)。WiFi �?EtherCAT 各记一份,切链路自动换(默�?WiFi 250ms / EtherCAT 50ms)。下限也按链路:WiFi 50ms,EtherCAT 最�?6ms(≈160Hz,再快就超过屏幕绘制上限,实际频率见 STATUS 面板�?>
<div class="conn-group" title="电机状态刷新间隔(ms)。WiFi 与 EtherCAT 各记一份,切链路自动换(默认 WiFi 250ms / EtherCAT 50ms)。下限也按链路:WiFi 50ms,EtherCAT 最低 6ms(≈160Hz,再快就超过屏幕绘制上限,实际频率见 STATUS 面板)">
<label for="refreshRate" class="refresh-hint">刷新速度</label>
<input type="number" id="refreshRate" class="refresh-input" value="250" min="50" max="5000" step="50" aria-label="电机状态刷新间隔(毫秒,按链路分别记忆�? />
<input type="number" id="refreshRate" class="refresh-input" value="250" min="50" max="5000" step="50" aria-label="电机状态刷新间隔(毫秒,按链路分别记忆)" />
<span class="refresh-hint">ms</span>
</div>
<div class="toolbar-right">
<a class="btn btn-sm" href="index.html" title="返回 OTA 主页配置连接">返回 OTA 控制�?/a>
<a class="btn btn-sm" href="index.html" title="返回 OTA 主页配置连接">返回 OTA 控制台</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">未连�?�?使用主页共享 IP 自动探测中�?请先�?OTA 主页配置并测试连�?/span>
<span id="netBannerText">未连接 — 使用主页共享 IP 自动探测中… 请先在 OTA 主页配置并测试连接</span>
</div>
<!-- ══════════�?MAIN 3-ZONE GRID ══════════�?-->
<!-- ═══════════ MAIN 3-ZONE GRID ═══════════ -->
<main class="main-grid">
<!-- LEFT: NODE OVERVIEW -->
@@ -75,15 +75,15 @@
<span style="margin-left:auto;font-size:.68rem;color:var(--fg-dim);font-family:var(--font-mono)" id="nodeSummary">0/9 在线 · CAN FD</span>
</div>
<div class="panel-body">
<!-- 独立 BOOT 状态:总线检测到 0x103 心跳时显示,不归属任�?Nx
显示/隐藏只靠 .boot-badge / .boot-badge.show,禁止内�?display -->
<!-- 独立 BOOT 状态:总线检测到 0x103 心跳时显示,不归属任何 Nx
显示/隐藏只靠 .boot-badge / .boot-badge.show,禁止内联 display -->
<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-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 固定渲染,离线也可选中刷写 -->
<!-- N1–N9 由 JS 固定渲染,离线也可选中刷写 -->
</div>
</div>
</aside>
@@ -99,16 +99,16 @@
<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>
<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>
<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>
@@ -128,15 +128,15 @@
</div>
</div>
<div class="field" style="margin-top:var(--sp-4)">
<span class="field-label">PID 增益 <span class="field-unit">P × 3 �?/span></span>
<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 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="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>
@@ -152,10 +152,10 @@
<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
发送 CMD
</button>
<button class="btn" id="sendRepeatBtn">周期发�?/button>
<label class="repeat-period" title="周期发送间隔(最�?80ms�?>
<button class="btn" id="sendRepeatBtn">周期发送</button>
<label class="repeat-period" title="周期发送间隔(最小 80ms)">
<input type="number" id="repeatPeriod" value="100" min="80" max="10000" step="10" />
<span>ms</span>
</label>
@@ -211,7 +211,7 @@
<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,DBC v0.71) · GD32 解析�?0xC6 同步</p>
<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,DBC v0.71) · GD32 解析后 0xC6 同步</p>
</div>
<!-- NODE ID TAB -->
<div class="tab-page" id="tab-nodeid">
@@ -223,34 +223,38 @@
<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>
<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,选择�?0x0000)。写入后 CAN ID 前缀变为 0x000Nxxxx�? </p>
SET_NODE_ID (0x000N0400 DLC2) / GET_NODE_ID (0x000N0401 DLC2,选择器 0x0000)。写入后 CAN ID 前缀变为 0x000Nxxxx。
</p>
<!-- 反馈刷新率:界面单位 Hz,协议值是周期 ms�?1000÷Hz,电�?1ms 节拍分频)�? 配置值以「读取」回读电机为准(0x0401),「写入」经 0x0400 下发并掉电保�?-->
<!-- 反馈刷新率:界面单位 Hz,协议值是周期 ms(=1000÷Hz,电机 1ms 节拍分频)。
配置值以「读取」回读电机为准(0x0401),「写入」经 0x0400 下发并掉电保存 -->
<div class="nodeid-row" style="margin-top:var(--sp-4)">
<span class="field-label">反馈刷新�?<span class="field-unit">Hz · 0=停发 · 写入掉电保存</span></span>
<span class="field-label">反馈刷新率 <span class="field-unit">Hz · 0=停发 · 写入掉电保存</span></span>
</div>
<div class="nodeid-row">
<div class="field" style="min-width:140px">
<span class="field-label">STATUS �?<span class="field-unit">默认 1000 Hz</span></span>
<input type="number" id="periodStatusInput" min="0" max="1000" value="1000" aria-label="STATUS 帧刷新率(赫兹,0=停发�? />
<span class="field-label">STATUS 帧 <span class="field-unit">默认 1000 Hz</span></span>
<input type="number" id="periodStatusInput" min="0" max="1000" value="1000" aria-label="STATUS 帧刷新率(赫兹,0=停发)" />
</div>
<button class="btn btn-sm" id="getPeriodStatusBtn">读取</button>
<button class="btn btn-primary btn-sm" id="setPeriodStatusBtn">写入</button>
</div>
<div class="nodeid-row">
<div class="field" style="min-width:140px">
<span class="field-label">CONFIG �?<span class="field-unit">默认 250 Hz</span></span>
<input type="number" id="periodConfigInput" min="0" max="1000" value="250" aria-label="CONFIG 帧刷新率(赫兹,0=停发�? />
<span class="field-label">CONFIG 帧 <span class="field-unit">默认 250 Hz</span></span>
<input type="number" id="periodConfigInput" min="0" max="1000" value="250" aria-label="CONFIG 帧刷新率(赫兹,0=停发)" />
</div>
<button class="btn btn-sm" id="getPeriodConfigBtn">读取</button>
<button class="btn btn-primary btn-sm" id="setPeriodConfigBtn">写入</button>
</div>
<div class="nodeid-result" id="periodResult">连接设备后读取当前刷新率</div>
<p style="margin-top:var(--sp-3);font-size:.72rem;color:var(--fg-dim);line-height:1.6">
输入为频率(Hz),协议�?周期(ms) = 1000 ÷ Hz 换算下发�?x000N0400 DLC4 [选择器][周期] /
0x000N0401 DLC2 [选择器](选择�?0x0100=STATUS · 0x0101=CONFIG)。当前值以「读取」回读为准�? 停发 STATUS 后状态面板会显示离线,电机仍可控可刷写�? </p>
输入为频率(Hz),协议按 周期(ms) = 1000 ÷ Hz 换算下发:0x000N0400 DLC4 [选择器][周期] /
0x000N0401 DLC2 [选择器](选择器 0x0100=STATUS · 0x0101=CONFIG)。当前值以「读取」回读为准。
停发 STATUS 后状态面板会显示离线,电机仍可控可刷写。
</p>
</div>
<!-- OTA TAB -->
<div class="tab-page" id="tab-ota">
@@ -266,15 +270,15 @@
<button class="btn btn-sm" id="otaFileBtn">选择固件 (.bin)</button>
<input type="file" id="otaFileInput" accept=".bin" hidden />
<span class="ota-file-name" id="otaFileName">未选择固件</span>
<button class="btn btn-primary btn-sm" id="otaStartBtn">开�?OTA</button>
<button class="btn btn-primary btn-sm" id="otaStartBtn">开始 OTA</button>
<button class="btn btn-danger btn-sm" id="otaCancelBtn" disabled>取消</button>
</div>
<div class="ota-progress-wrap">
<div class="ota-progress-bar"><div class="ota-progress-fill" id="otaProgressFill"></div></div>
<div class="ota-stats">
<span>�?<span class="hl" id="otaPktNum">0</span> / <span id="otaPktTotal">0</span></span>
<span>速率 <span class="hl" id="otaSpeed">�?/span></span>
<span>状�?<span id="otaCrc">�?/span></span>
<span>包 <span class="hl" id="otaPktNum">0</span> / <span id="otaPktTotal">0</span></span>
<span>速率 <span class="hl" id="otaSpeed">—</span></span>
<span>状态 <span id="otaCrc">—</span></span>
</div>
</div>
<div class="ota-log-toolbar">
@@ -282,8 +286,8 @@
<button id="otaLogClearBtn" title="清空日志">清空</button>
</div>
<div class="ota-log" id="otaLog" aria-live="polite">
<div class="log-info">[就绪] 电机 Bootloader OTA · 先上传固件到 ESP32 �?�?0x000N0000 �?BOOT �?触发转发 �?GD32 本地 relay(AES-128 + CAN stop-and-wait�?/div>
<div>[信息] 选固�?�?上传并比�?flash SHA-256(失败重传≤2次)�?发进BOOT命令 �?触发 /motor/ota/relay �?GD32 完成握手/加密/传输 · 每包 48 B · 48 KB 上限</div>
<div class="log-info">[就绪] 电机 Bootloader OTA · 先上传固件到 ESP32 → 发 0x000N0000 进 BOOT → 触发转发 → GD32 本地 relay(AES-128 + CAN stop-and-wait)</div>
<div>[信息] 选固件 → 上传并比对 flash SHA-256(失败重传≤2次)→ 发进BOOT命令 → 触发 /motor/ota/relay → GD32 完成握手/加密/传输 · 每包 48 B · 48 KB 上限</div>
</div>
</div>
<!-- 命令序列 TAB -->
@@ -292,18 +296,22 @@
<label class="repeat-period" title="整序列循环次数,0 = 无限循环">
<span>循环</span>
<input type="number" id="seqLoops" value="0" min="0" max="9999" step="1" />
<span>次(0=无限�?/span>
<span>次(0=无限)</span>
</label>
<button class="btn btn-sm" id="seqAddBtn">+ 添加步骤</button>
<button class="btn btn-sm" id="seqClearBtn">清空</button>
<button class="btn" id="seqStartBtn">�?开始序�?/button>
<span class="seq-status" id="seqStatus">未运�?/span>
<button class="btn" id="seqStartBtn">▶ 开始序列</button>
<span class="seq-status" id="seqStatus">未运行</span>
</div>
<div class="seq-list" id="seqList"></div>
<p class="seq-hint">
命令序列:每步为一个「同步动作组」,组内每个动作 = 一台电机目标(通道/节点/模式/电压/电流/速度/角度/PID);
执行本步时组内全部动作经一次突发请求近同时下发(同组偏差微秒级;超过单次容量自动分组,组间�?1-2ms),
发送成功后保持「保�?ms」再进入下一步。每步可�?」添加多个电机动作,支持多通道、多电机混合搭配,独立于左侧选中节点�? 每步的绿色「▶」按钮为单发:只把该步全部动作下发一次,不循环、不推进步骤、不等待保持时间�? 循环 0 = 无限循环,与「周期发送」互斥。�? 添加步骤」会复制当前 CMD 表单已提交的值及当前通道/选中节点�? 按住每步左侧「⠿」把手即可上下拖动排序(点选把手后�?�?�?同样可移);点「▸」折叠本步,只留一行功能摘要(通道·节点 / 模式 / 目标值);拖动过程中全部步骤自动折叠,方便看清插入位置�? </p>
执行本步时组内全部动作经一次突发请求近同时下发(同组偏差微秒级;超过单次容量自动分组,组间约 1-2ms),
发送成功后保持「保持 ms」再进入下一步。每步可「+」添加多个电机动作,支持多通道、多电机混合搭配,独立于左侧选中节点。
每步的绿色「▶」按钮为单发:只把该步全部动作下发一次,不循环、不推进步骤、不等待保持时间。
循环 0 = 无限循环,与「周期发送」互斥。「+ 添加步骤」会复制当前 CMD 表单已提交的值及当前通道/选中节点。
按住每步左侧「⠿」把手即可上下拖动排序(点选把手后用 ↑/↓ 同样可移);点「▸」折叠本步,只留一行功能摘要(通道·节点 / 模式 / 目标值);拖动过程中全部步骤自动折叠,方便看清插入位置。
</p>
</div>
</div>
</section>
@@ -317,7 +325,7 @@
<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:.85rem" id="offlineTitle">节点离线 — 未收到 STATUS 帧</span>
<span style="font-size:.72rem" id="offlineSub">检查电机供电与 CAN 总线连接</span>
</div>
<div class="status-live hide" id="statusLive">
@@ -325,33 +333,33 @@
<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-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-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-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-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-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>
@@ -376,7 +384,7 @@
<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占用�?>
<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>
@@ -386,18 +394,18 @@
</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 class="flag-item" id="flagItemInit"><span class="flag-led" id="flagInit"></span>InitStatus 初始�?span class="flag-state" id="flagInitState">�?/span></div>
<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 class="flag-item" id="flagItemInit"><span class="flag-led" id="flagInit"></span>InitStatus 初始化<span class="flag-state" id="flagInitState">—</span></div>
</div>
</div>
</div>
</section>
</main>
<!-- ══════════�?STATUS BAR ══════════�?-->
<!-- ═══════════ 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>
@@ -406,7 +414,7 @@
<div class="sb-item">ERR <span class="sb-val" id="sbErr">--</span> · TEC <span id="sbTec">--</span> / REC <span id="sbRec">--</span></div>
<div class="sb-sep"></div>
<div class="sb-item">BUS <span class="sb-val" id="sbBus">--</span></div>
<span class="sb-last-send" id="sbLastSend">尚未发�?/span>
<span class="sb-last-send" id="sbLastSend">尚未发送</span>
</footer>
<!-- TOAST & MODAL -->
@@ -423,7 +431,7 @@
</div>
<script type="module" src="../js/motor/main.js?v=20260924a"></script>
<!-- 单独打开本页(不在标签外壳�?�? 由本页自己持�?app 租约 -->
<!-- 单独打开本页(不在标签外壳里)时, 由本页自己持有 app 租约 -->
<script src="../js/common/app-lease.js"></script>
</body>
</html>