支持同一节点号在多个CAN通道上的电机状态管理,采用node×channel双键存储机制。 - 修改motor_state.cpp中的槽位索引逻辑,从单节点索引改为(node-1)*MOTOR_CHANNELS+channel索引 - 增加MOTOR_CHANNELS和MOTOR_MAX_SLOTS宏定义,支持9节点×3通道共27个槽位 - 更新http_handlers.cpp中的二进制响应缓冲区为static类型,避免大缓冲区占用栈空间 - 调整循环遍历范围从MOTOR_MAX_NODES改为MOTOR_MAX_SLOTS - 添加HTML页面的空favicon配置,避免浏览器404请求 - 新增CSS样式用于显示节点在其他通道在线的状态提示
520 lines
29 KiB
HTML
520 lines
29 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=7bc7d5db">
|
||
<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> 设备连接
|
||
<span id="bleHeadActions" style="display:none;margin-left:auto;">
|
||
<button id="connectBtn" class="btn btn-sm btn-primary">连接设备</button>
|
||
<button id="disconnectBtn" class="btn btn-sm btn-outline" disabled>断开连接</button>
|
||
</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>
|
||
<!-- 连接文案只在顶栏 #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>
|
||
</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>
|
||
|
||
<!-- CAN workspace entry -->
|
||
<div class="card card--can collapsed">
|
||
<div class="card-head" onclick="toggleCollapse(this)">
|
||
<span class="ico">📨</span> CAN 通信页
|
||
<span class="collapse-arrow">▼</span>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="can-launch-card">
|
||
<div class="can-launch-copy">
|
||
<div class="can-launch-top">
|
||
<div>
|
||
<div class="can-launch-kicker">CAN Workspace</div>
|
||
<h3>独立 CAN 通信工作台</h3>
|
||
</div>
|
||
<span class="can-launch-badge">DBC / HEX</span>
|
||
</div>
|
||
<p class="can-launch-desc">导入 DBC 后自动读取报文和信号,按物理值实时组帧,经 <code>/can/send_batch</code> 发送(单帧或多帧)。</p>
|
||
<div class="can-launch-points">
|
||
<span class="can-launch-point">自动解析 DBC</span>
|
||
<span class="can-launch-point">信号实时组帧</span>
|
||
<span class="can-launch-point">保留原始 HEX 发送</span>
|
||
</div>
|
||
</div>
|
||
<div class="can-launch-actions">
|
||
<a class="btn btn-primary can-launch-primary" href="can.html">进入 CAN 页面</a>
|
||
<button class="btn btn-outline can-launch-secondary" type="button" onclick="window.open('can.html','_blank','noopener')">新窗口打开</button>
|
||
<a class="btn btn-outline can-launch-secondary" href="can.html" title="发送页右侧抽屉内嵌监控">CAN 发送 + 监控</a>
|
||
<a class="btn btn-outline can-launch-secondary" href="can_monitor.html">监控全屏</a>
|
||
</div>
|
||
<div class="can-launch-foot">
|
||
<span class="can-launch-foot-label">说明</span>
|
||
<span>手工 CAN 表单已迁到独立页面,避免和 OTA 控制流混在一起。</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Motor workspace entry -->
|
||
<div class="card card--motor collapsed">
|
||
<div class="card-head" onclick="toggleCollapse(this)">
|
||
<span class="ico">⚙️</span> 电机控制页
|
||
<span class="collapse-arrow">▼</span>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="can-launch-card motor-launch-card">
|
||
<div class="can-launch-copy">
|
||
<div class="can-launch-top">
|
||
<div>
|
||
<div class="can-launch-kicker">Motor Workspace</div>
|
||
<h3>电机 CAN FD 控制台</h3>
|
||
</div>
|
||
<span class="can-launch-badge">CMD / STATUS</span>
|
||
</div>
|
||
<p class="can-launch-desc">节点总览、CMD 控制、CONFIG/VERSION、电机 OTA。与主页共用设备 IP(<code>dcu_esp_ip</code>),本页不提供连接配置,进入后自动探测并轮询 <code>/motor/state</code>。</p>
|
||
<div class="can-launch-points">
|
||
<span class="can-launch-point">多节点在线状态</span>
|
||
<span class="can-launch-point">周期/单次 CMD</span>
|
||
<span class="can-launch-point">共用主页连接</span>
|
||
</div>
|
||
</div>
|
||
<div class="can-launch-actions">
|
||
<a class="btn btn-primary can-launch-primary motor-launch-primary" href="motor.html">进入电机控制</a>
|
||
<button class="btn btn-outline can-launch-secondary" type="button" onclick="window.open('motor.html','_blank','noopener')">新窗口打开</button>
|
||
</div>
|
||
<div class="can-launch-foot">
|
||
<span class="can-launch-foot-label">说明</span>
|
||
<span>请先在上方「设备连接」配置并测试 ESP32 IP;电机页只消费同一连接,不单独建连。</span>
|
||
</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=20260727a"></script>
|
||
<script type="module" src="js/ota/console-mode.js?v=06b7189e"></script>
|
||
<script type="module" src="js/ble_ota.js?v=01d3cb4e"></script>
|
||
</body>
|
||
</html>
|