纯前端上位机界面 + 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 换行原样)
522 lines
30 KiB
HTML
522 lines
30 KiB
HTML
<!--
|
||
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>
|