Files
DCU_DesktopController/index.html
T
cbb d2c86c425a init: DCU ESP32 调试控制台前端与本地页面服务
纯前端上位机界面 + esp32_server.py 静态服务/桌面窗口外壳, 不含固件、
不直接访问硬件; 业务 API 由 ESP32_COMM 固件 80 端口跨域提供。

- tabs.html 单窗口多标签外壳, iframe 承载各功能页
- index.html OTA 中继(ESP32 WiFi / GD32 relay / BLE), js/ota + js/ble
- can.html / can_monitor.html CAN 通信与接收监控, js/can
- motor.html 电机 CAN FD 控制台, js/motor
- continuity_test.html LAN9252 通断测试, spi_test.html SPI 测速(临时, 自包含)
- 三入口: esp32_app.vbs 桌面窗口 / esp32_web.vbs 浏览器 / serve.bat 控制台调试
- 补 README(页面与目录约定、?v= 缓存版本等改动须知)、.gitignore、
  .gitattributes(* -text 保持 Windows 换行原样)
2026-09-03 21:30:32 +08:00

522 lines
30 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.
<!--
index.html - ESP32 OTA 中继控制台主页面(DCU Mission Control · 美化版)
支持 WiFi OTA(ESP32 本机 + GD32 中继)和 BLE OTA 双通道固件升级。
配套脚本:app.js (WiFi OTA), ble_ota.js (BLE OTA)
子系统配色: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:阻止浏览器自动请求 /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=acd17001">
<link rel="stylesheet" href="css/components.css?v=a2c3d4e5">
<link rel="stylesheet" href="css/can.css?v=c06f5688">
<link rel="stylesheet" href="css/responsive.css?v=46449483">
</head>
<body>
<div class="container">
<!-- ====== HEADER: 命令栏 ====== -->
<header class="header-bar">
<div class="header-brand">
<span class="brand-logo" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
<rect x="6" y="6" width="12" height="12" rx="2.5"/>
<rect x="9.5" y="9.5" width="5" height="5" rx="1" fill="currentColor" stroke="none" opacity=".85"/>
<path d="M9 2v3M15 2v3M9 19v3M15 19v3M2 9h3M2 15h3M19 9h3M19 15h3"/>
</svg>
</span>
<div class="brand-text">
<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" 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">设备ID: <b class="pill-val" id="deviceId">-</b></span>
</div>
</header>
<!-- ====== MAIN LAYOUT ====== -->
<div class="main-layout">
<!-- ====== LEFT: workflow ====== -->
<div>
<!-- 1. Connection -->
<div class="card card--ble">
<div class="card-head"><span class="ico">🔗</span> 设备连接</div>
<div class="card-body">
<div style="display:flex;gap:6px;margin-bottom:14px;">
<button id="modeWifiBtn" class="btn btn-primary" style="flex:1;font-size:12px;padding:7px 10px;" data-action="set-console-mode" data-arg="wifi">WiFi OTA</button>
<button id="modeBleBtn" class="btn btn-outline" style="flex:1;font-size:12px;padding:7px 10px;" data-action="set-console-mode" data-arg="ble">BLE 蓝牙</button>
</div>
<div id="wifiModeCards">
<div class="form-row">
<label class="form-label" for="espIp">ESP32 IP 地址</label>
<div class="input-group">
<input class="input" type="text" id="espIp" value="192.168.31.127" placeholder="输入 IP 地址">
<button id="testConnBtn" class="btn btn-outline" data-action="test-connection">测试</button>
</div>
</div>
</div>
<!-- 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">
<input id="bleConnectToggle" type="checkbox">
<div class="back">
<div class="spot-shadow"></div>
<div class="mid">
<div class="spot-main"></div>
<div class="ble-dial">
<div class="spot-light"></div>
<div class="center">
<div class="icon">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#bleIconGlow)">
<path d="M7 7 L17 17 L12 22 V2 L17 7 L7 17"
stroke="#BBE7E9" stroke-width="3"
stroke-linecap="round" stroke-linejoin="round" fill="none"
shape-rendering="geometricPrecision"/>
</g>
<defs>
<filter id="bleIconGlow" x="-50%" y="-50%" width="200%" height="200%"
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="bg"/>
<feColorMatrix in="SourceAlpha" type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="a"/>
<feOffset/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="a" operator="out"/>
<feColorMatrix type="matrix"
values="0 0 0 0 0.37 0 0 0 0 0.66 0 0 0 0 1 0 0 0 0.88 0"/>
<feBlend mode="normal" in2="bg" result="g"/>
<feBlend mode="normal" in="SourceGraphic" in2="g" result="shape"/>
</filter>
</defs>
</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>
<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:3">.</span>
<span style="--n:4">.</span>
<span style="--n:5">.</span>
</div>
<div class="text3">连接中...</div>
</div>
</div>
</div>
</div>
</div>
</label>
<div class="ble-launch-hint">点击按钮连接 / 断开蓝牙设备</div>
</div>
</div>
<!-- 连接文案只在顶栏 #pillConnTxt 显示,此处不再重复 -->
</div>
</div>
<!-- ====== Shared Target + Firmware + Actions ====== -->
<div class="card card--gd32">
<div class="card-head"><span class="ico">🎯</span> 升级目标、固件文件与执行操作</div>
<div class="card-body">
<div style="display:flex;gap:6px;margin-bottom:14px;">
<button id="targetGd32Tab" class="btn btn-outline" style="flex:1;font-size:12px;padding:7px 10px;" data-action="select-flash-target" data-arg="gd32">GD32</button>
<button id="targetEsp32Tab" class="btn btn-primary" style="flex:1;font-size:12px;padding:7px 10px;" data-action="select-flash-target" data-arg="esp32">ESP32</button>
</div>
<div style="border-top:1px solid var(--border);margin:0 -16px 14px;padding-top:14px;padding-left:16px;padding-right:16px;">
<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>
</div>
<input type="file" id="fileInputEsp32" accept=".bin" style="display:none;">
<div class="file-info" id="fileInfoEsp32">
<div class="file-info-row">
<span style="font-weight:600;">📄 <span id="fileNameEsp32">-</span></span>
<button class="btn btn-ghost btn-sm" data-action="clear-shared-firmware" data-arg="esp32">清除</button>
</div>
<div class="file-info-grid">
<div class="file-meta-item"><span>类型</span><b id="fileTypeEsp32">-</b></div>
<div class="file-meta-item"><span>大小</span><b id="fileSizeEsp32">-</b></div>
</div>
<div class="file-meta-item" style="margin-top:6px;">
<span>SHA-256</span>
<b id="fileSha256Esp32" style="font-size:10px;word-break:break-all;font-family:monospace;">-</b>
</div>
</div>
</div>
<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(自动转换)</p>
</div>
<input type="file" id="fileInputGd32" accept=".hex,.bin" style="display:none;">
<div class="file-info" id="fileInfoGd32">
<div class="file-info-row">
<span style="font-weight:600;">📄 <span id="fileNameGd32">-</span></span>
<button class="btn btn-ghost btn-sm" data-action="clear-shared-firmware" data-arg="gd32">清除</button>
</div>
<div class="file-info-grid">
<div class="file-meta-item"><span>类型</span><b id="fileTypeGd32">-</b></div>
<div class="file-meta-item"><span>大小</span><b id="fileSizeGd32">-</b></div>
</div>
<div class="file-meta-item" style="margin-top:6px;">
<span>SHA-256</span>
<b id="fileSha256Gd32" style="font-size:10px;word-break:break-all;font-family:monospace;">-</b>
</div>
</div>
</div>
<div id="bleUploadPane" style="display:none;">
<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>
</div>
<input id="firmwareFile" type="file" accept=".bin,.hex,application/octet-stream" style="display:none;">
<div class="file-info" id="fileInfo">
<div class="file-info-row">
<span style="font-weight:600;">📄 <span id="fileName">-</span></span>
<button id="clearFileBtn" class="btn btn-outline btn-sm" data-action="ble-clear-file">清除</button>
</div>
<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>
<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>
<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
</button>
</div>
<div id="wifiGd32ActionPane" style="display:none;">
<button class="btn btn-success btn-block" id="uploadGd32Btn" data-action="start-upload" data-arg="gd32" disabled>
GD32 OTA
</button>
</div>
<div class="row-2" id="bleActionPane" style="display:none;">
<button id="autoFlashBtn" class="btn btn-primary" disabled>一键刷写</button>
<button id="abortBtn" class="btn btn-danger" disabled>中止任务</button>
</div>
</div>
</div>
</div>
<!-- 4. Flash Progress + Stage Flow -->
<div class="card card--esp32">
<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>
<!-- Unified progress bar -->
<div class="progress-wrap show" id="progressWrap">
<div class="progress-track"><div class="progress-fill" id="progressFill"></div></div>
<div class="progress-text">
<span id="progressTxt">等待操作...</span>
<span id="progressPct">0%</span>
</div>
</div>
<div style="height:14px"></div>
<!-- ESP32 OTA stages -->
<div class="stage-flow" id="esp32Stages" style="display:none;">
<div class="stage-node" id="es1"><div class="stage-dot">1</div><div class="stage-label">固件上传</div><div class="stage-detail" id="esd1"></div></div>
<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="es6"><div class="stage-dot">6</div><div class="stage-label">启动完成</div><div class="stage-detail" id="esd6"></div></div>
</div>
<!-- BLE OTA stages -->
<div class="stage-flow" id="bleStages" style="display:none;">
<div class="stage-node" id="bs1"><div class="stage-dot">1</div><div class="stage-label">BLE 连接</div><div class="stage-detail" id="bsd1"></div></div>
<div class="stage-node" id="bs2"><div class="stage-dot">2</div><div class="stage-label">GATT 准备</div><div class="stage-detail" id="bsd2"></div></div>
<div class="stage-node" id="bs3"><div class="stage-dot">3</div><div class="stage-label">文件准备</div><div class="stage-detail" id="bsd3"></div></div>
<div class="stage-node" id="bs4"><div class="stage-dot">4</div><div class="stage-label">数据写入</div><div class="stage-detail" id="bsd4"></div></div>
<div class="stage-node" id="bs5"><div class="stage-dot">5</div><div class="stage-label" id="bsl5">校验比对</div><div class="stage-detail" id="bsd5"></div></div>
<div class="stage-node" id="bs6"><div class="stage-dot">6</div><div class="stage-label" id="bsl6">完成触发</div><div class="stage-detail" id="bsd6"></div></div>
</div>
<!-- GD32 OTA stages -->
<div class="stage-flow" id="gd32Stages">
<div class="stage-node" id="s1"><div class="stage-dot">1</div><div class="stage-label">认证握手</div><div class="stage-detail" id="sd1"></div></div>
<div class="stage-node" id="s2"><div class="stage-dot">2</div><div class="stage-label">传输协商</div><div class="stage-detail" id="sd2"></div></div>
<div class="stage-node" id="s3"><div class="stage-dot">3</div><div class="stage-label">数据转发</div><div class="stage-detail" id="sd3"></div></div>
<div class="stage-node" id="s4"><div class="stage-dot">4</div><div class="stage-label">收尾验证</div><div class="stage-detail" id="sd4"></div></div>
<div class="stage-node" id="s5"><div class="stage-dot">5</div><div class="stage-label">APP 启动</div><div class="stage-detail" id="sd5"></div></div>
<div class="stage-node" id="s6"><div class="stage-dot">6</div><div class="stage-label">OTA完成</div><div class="stage-detail" id="sd6"></div></div>
</div>
<div class="stage-status-bar" id="stageStatusBar">
<span class="ssb-dot" id="stageStatusDot"></span>
<span class="ssb-text" id="stageStatusText">待机</span>
</div>
<div class="stage-verify-info" id="stageVerifyInfo" style="display:none;">
<div class="svi-row"><span class="svi-key">前端 SHA-256</span><span class="svi-val" id="sviFrontSha">-</span></div>
<div class="svi-row"><span class="svi-key">设备 SHA-256</span><span class="svi-val" id="sviDeviceSha">-</span></div>
<div class="svi-row"><span class="svi-key">校验结果</span><span class="svi-val" id="sviResult">-</span></div>
</div>
<div style="margin-top:12px;">
<button class="btn btn-outline btn-block" data-action="restart-esp32">重启 ESP32</button>
</div>
</div>
</div>
</div>
<!-- ====== RIGHT: status + tools ====== -->
<div>
<!-- Primary status -->
<div class="card card--status">
<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>
<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>
<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>
</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="boot-module-list" id="bootModuleList" aria-label="启动模块列表"></div>
</div>
</div>
</div>
<!-- Secondary metrics -->
<div class="card card--mem">
<div class="card-head"><span class="ico">📊</span> 运行指标</div>
<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">输入 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>
</div>
</div>
</div>
<div class="card card--mem" style="display:none">
<div class="card-head">
<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">刷新时间</span><b id="sStatusUpdated">--</b></div>
</div>
</div>
</div>
<!-- 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 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">
<div class="log-toolbar">
<div class="log-filters">
<button class="log-filter active" onclick="setLogFilter('all', this)">全部</button>
<button class="log-filter" onclick="setLogFilter('E', this)">错误</button>
<button class="log-filter" onclick="setLogFilter('W', this)">警告</button>
<button class="log-filter" onclick="setLogFilter('I', this)">信息</button>
</div>
<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>
<div class="log-foot">
<span>共 <span id="logCount">0</span> 条</span>
<button class="btn btn-ghost btn-sm" onclick="clearLogs()">清除日志</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 右侧滑出:堆与任务栈(默认露出一条,悬停展开) -->
<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>
</div>
<div class="sys-drawer-panel card card--mem">
<div class="card-head sys-drawer-head">
<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">
<div class="status-section-title">CPU 占用</div>
<div class="heap-group cpu-group">
<div class="heap-group-title">ESP32 Runtime <span style="font-weight:400;color:var(--text-dim);font-size:10px;">(FreeRTOS)</span></div>
<div class="heap-bar-row">
<span class="heap-bar-text" id="cpuUsageText">等待采样...</span>
<span class="heap-bar-track"><span class="heap-bar-fill cpu-fill" id="cpuUsageFill"></span></span>
<span class="heap-bar-pct" id="cpuUsagePct">--</span>
</div>
<div class="heap-detail-row">
<span>空闲: <b id="cpuIdlePct">--</b></span>
<span>窗口: <b id="cpuSampleMs">--</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>
<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-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="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>
<span class="mem-seg mem-seg-used" id="memSegUsed"></span>
<span class="mem-seg mem-seg-free" id="memSegFree"></span>
</span>
<span class="heap-bar-pct" id="memSramPct">--</span>
</div>
<div class="heap-legend">
<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>
</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>
</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-bar-row">
<span class="heap-bar-text" id="memInternalUsage">--</span>
<span class="heap-bar-track"><span class="heap-bar-fill" id="memInternalFill"></span></span>
<span class="heap-bar-pct" id="memInternalPct">--</span>
</div>
<div class="heap-detail-row">
<span>空闲: <b id="mInternalHeap">--</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>分配块数: <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>
<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>
</div>
<div class="status-subfold-body">
<pre class="status-json" id="statusJson">--</pre>
</div>
</div>
</div>
</div>
</aside>
<!-- Toast -->
<div class="toast" id="toast">
<span class="toast-ico" id="toastIco">✓</span>
<span id="toastMsg">操作成功</span>
</div>
<script type="module" src="js/app.js?v=20260901d"></script>
<script type="module" src="js/ota/console-mode.js?v=20260901d"></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 设置,此处跳过不覆盖。 -->
<script type="module">
setInterval(function() {
var btn = document.querySelector('.ble-launch-btn');
if (!btn || !btn.offsetParent) return;
var st = btn.getAttribute('data-ble-state');
if (st === 'connecting') return;
var toggle = document.getElementById('bleConnectToggle');
if (!toggle) return;
var expected = toggle.checked ? 'connected' : 'idle';
if (st !== expected) btn.setAttribute('data-ble-state', expected);
}, 500);
</script>
</body>
</html>