Files
DCU_DesktopController/pages/can.html
T
cbb eac3717f9a feat: 添加局域网设备发现功能和链路胶囊优化
- 实现局域网设备发现面板,支持探测ESP32中继板并自动切换端点
- 添加POST /api/lan/probe接口用于设备探测,返回三态判决结果
- 实现快扫和全段扫描两种模式,支持种子地址优先探测
- 添加端点唯一写手机制,防止多处同时修改设备IP
- 优化链路胶囊UI显示WiFi/EtherCAT/BLE连接状态和设备名称
- 修复EtherCAT链路OTA刷写期间PDO循环暂停和AL状态处理问题
- 调整PDO周期设置范围为0.125-4ms,支持亚毫秒显示为微秒
- 添加ESP32 WiFi使能开关控件样式和数据来源角标显示
2026-10-13 00:48:34 +08:00

344 lines
20 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<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=20260926a">
<link rel="stylesheet" href="../css/components.css?v=20260924a">
<link rel="stylesheet" href="../css/can.css?v=20260926a">
<link rel="stylesheet" href="../css/responsive.css?v=quiet20260919a">
</head>
<body class="can-page">
<div class="container can-container">
<header class="header-bar can-header">
<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 CAN 通信控制台</strong>
<span class="brand-sub">DCU · Robot_v2 · CAN FD</span>
</div>
</div>
<div class="header-status">
<span class="pill offline link-pill" id="canNetPill" title="ESP32 WiFi(/system/status)">
<span class="indicator"></span><span id="canNetTxt">未连接</span>
</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="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>
</div>
</header>
<!-- DBC 并入报文列表:无文件时列表内大拖放区;有文件时标题栏紧凑摘要 + 更换 -->
<input type="file" id="dbcFileInput" accept=".dbc,text/plain" multiple style="display:none;">
<div class="can-editor-layout">
<div class="can-main-col">
<section class="card can-table-card" id="dbcDropZone">
<div class="card-head can-msg-list-head">
<span class="ico">📚</span>
<span>报文列表</span>
<!-- 已导入:合计 + 文件 chip(可逐个移除) -->
<div class="dbc-summary-strip dbc-summary-inline" id="dbcFileInfo" hidden>
<span class="dbc-summary-meta">
<span><b id="dbcFileCount">0</b>文件</span>
<span><b id="dbcMessageCount">0</b>报文</span>
<span><b id="dbcSignalCount">0</b>信号</span>
<span><b id="dbcNodeCount">0</b>节点</span>
<span id="dbcFileSize">0 B</span>
</span>
<span class="dbc-file-chips" id="dbcFileChips"></span>
</div>
<div class="can-msg-head-actions">
<button class="btn btn-ghost btn-sm" id="clearDbcBtn" type="button" title="清除全部 DBC" hidden>全部清除</button>
<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>
<select class="input can-msg-channel-select" id="defaultChannel" title="默认发送通道">
<option value="0">CH0</option>
<option value="1">CH1</option>
<option value="2">CH2</option>
</select>
</label>
<div class="can-msg-search-wrap">
<input class="input can-msg-search" type="text" id="dbcSearch"
placeholder="搜索报文名 / ID / 节点 / 信号" autocomplete="off">
</div>
<div class="can-batch-actions">
<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>
</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="筛选节点名…">
<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="nodeFilterNoneBtn" type="button">清空</button>
</div>
</div>
<div class="can-node-filter-list" id="nodeFilterList">
<div class="status-empty">先导入 DBC。</div>
</div>
</div>
</div>
<label class="checkbox-inline can-batch-all-label" title="全选/取消当前过滤的所有报文">
<input type="checkbox" id="selectAllBox">
<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>
</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>
</div>
</div>
<div class="helper-note" id="dbcHint" hidden></div>
</div>
</section>
<section class="card can-history-card">
<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>
</div>
</section>
</div>
<aside class="can-sidepanel">
<section class="card can-preview-card">
<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="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>
</div>
<div class="can-response" id="dbcResp" style="display:none;">
<div class="can-response-label">设备响应</div>
<div id="dbcRespText"></div>
</div>
</div>
</div>
</section>
<section class="card can-raw-card">
<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;">
<label class="form-label" for="manualChannel">通道</label>
<select class="input" id="manualChannel">
<option value="0">CH0</option>
<option value="1">CH1</option>
<option value="2">CH2</option>
</select>
</div>
<div class="form-row" style="margin:0;">
<label class="form-label" for="manualId">CAN ID</label>
<input class="input" type="text" id="manualId" value="0x18DA00F1" placeholder="支持 0x 或十进制">
</div>
</div>
<div class="form-row" style="margin-top:10px;">
<label class="form-label" for="manualDlc">DLC</label>
<input class="input" type="number" id="manualDlc" value="8" min="0" max="64">
</div>
<div class="form-row" style="margin-top:10px;">
<label class="form-label" for="manualData">HEX 数据</label>
<input class="input" type="text" id="manualData" value="0102030405060708" placeholder="01020304...">
</div>
<div class="helper-note" id="manualHint">
原始帧模式适合绕过 DBC 直接发 HEX,或发送当前预览帧后二次微调。
</div>
<div class="btn-row">
<button class="btn btn-primary" id="sendManualBtn" type="button">发送原始帧</button>
<button class="btn btn-outline" id="resetManualBtn" type="button">重置</button>
</div>
<div class="can-response" id="manualResp" style="display:none;">
<div class="can-response-label">设备响应</div>
<div id="manualRespText"></div>
</div>
</div>
</section>
</aside>
</div>
</div>
<!-- 右侧滑出:CAN 接收监控(与 OTA 页「堆与任务栈」同样交互) -->
<aside class="sys-drawer can-monitor-drawer" id="canMonitorDrawer" aria-label="CAN 接收监控">
<div class="sys-drawer-rail" title="悬停展开 CAN 接收监控">
<span class="sys-drawer-rail-ico">📊</span>
<span class="sys-drawer-rail-text">CAN 接收监控</span>
</div>
<div class="sys-drawer-panel card">
<div class="card-head sys-drawer-head">
<span class="ico">📊</span> CAN 接收监控
</div>
<div class="card-body sys-drawer-body">
<div class="status-section-title">接收模式</div>
<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→1/2 桥接</button>
</div>
<div class="status-section-title">发送静默(只听不发)</div>
<div class="input-group" style="margin-bottom:12px;flex-wrap:wrap;">
<button class="btn btn-outline btn-sm" id="btnSilent0" type="button" title="切换 CH0 只听不发">CH0 只听不发</button>
<button class="btn btn-outline btn-sm" id="btnSilent1" type="button" title="切换 CH1 只听不发">CH1 只听不发</button>
<button class="btn btn-outline btn-sm" id="btnSilent2" type="button" title="切换 CH2 只听不发">CH2 只听不发</button>
</div>
<div class="helper-note" style="margin-bottom:14px;">
只听不发=把该通道收发器的驱动器关掉(GD32 拉高 S 脚),<b>接收与上报照常</b>;按钮高亮表示当前正处于只听不发,状态来自 1Hz 反馈的 <code>bus_state</code> bit5,不是本地猜测。静默期间该通道发送会被拒绝(错误码 0xE6),上电后默认不静默。
</div>
<div class="helper-note" id="monHelper" style="margin-bottom:14px;">
地址与顶栏一致;1Hz <code>/can/feedback</code>。悬停抽屉保持展开。
</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>
</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="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>
</div>
<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">最新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>
<div class="feedback-row"><span class="fb-label">Seq</span><span class="fb-val" id="fb0Seq">--</span></div>
<div class="feedback-row"><span class="fb-label">Δrx/s</span><span class="fb-val" id="fb0Delta">--</span></div>
<div class="feedback-row"><span class="fb-label">最新HEX</span><span class="fb-val mono fb-data-cell" id="fb0LastData">--</span></div>
</div>
</div>
<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>
</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="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>
</div>
<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">最新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>
<div class="feedback-row"><span class="fb-label">Seq</span><span class="fb-val" id="fb1Seq">--</span></div>
<div class="feedback-row"><span class="fb-label">Δrx/s</span><span class="fb-val" id="fb1Delta">--</span></div>
<div class="feedback-row"><span class="fb-label">最新HEX</span><span class="fb-val mono fb-data-cell" id="fb1LastData">--</span></div>
</div>
</div>
<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>
</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="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>
</div>
<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">最新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>
<div class="feedback-row"><span class="fb-label">Seq</span><span class="fb-val" id="fb2Seq">--</span></div>
<div class="feedback-row"><span class="fb-label">Δrx/s</span><span class="fb-val" id="fb2Delta">--</span></div>
<div class="feedback-row"><span class="fb-label">最新HEX</span><span class="fb-val mono fb-data-cell" id="fb2LastData">--</span></div>
</div>
</div>
</div>
<div class="status-section-title" style="margin-top:16px;display:flex;align-items:center;gap:10px;flex-wrap:wrap;">
<span>每 ID 快照表</span>
<select class="input" id="idsnapChannel" style="width:100px;text-transform:none;">
<option value="0">CAN0</option>
<option value="1">CAN1</option>
<option value="2">CAN2</option>
</select>
<span class="pill" id="idsnapStatus">--</span>
</div>
<div class="idsnap-table-wrap">
<table class="idsnap-table" id="idsnapTable">
<thead>
<tr>
<th>#</th><th>CAN ID</th><th>DLC</th>
<th>FD</th><th>BRS</th><th>ESI</th><th>数据 (HEX)</th>
</tr>
</thead>
<tbody id="idsnapBody">
<tr><td colspan="7" class="status-empty">等待数据...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</aside>
<div class="toast" id="toast">
<span class="toast-ico" id="toastIco">✓</span>
<span id="toastMsg">操作成功</span>
</div>
<script type="module" src="../js/can.js?v=20261010a"></script>
</body>
</html>