feat(can): 优化批量发送性能并修复竞态问题
- 帧间添加5ms延时,暂停CAN反馈避免与批量SPI竞争导致偶发崩溃 - 单次批量容量从48帧提升至128帧,最大body大小从12KB增至48KB - 单帧超时从400ms降至80ms,失败后不再拖慢整体速度 - 暂停0xC1反馈任务降低worker竞态和WDT风险 - 中途致命错误后跳过后续帧发送,避免每帧等待超时 - 添加详细的JSON错误响应格式 - 修复内存分配失败时的资源清理问题 refactor(wifi): 优化冷启动流程避免电源竞争 - 电源轨稳定等待策略调整:POWERON/BROWNOUT复位时等待200ms - 仅BROWNOUT复位后清除PHY校准数据,避免每次上电全量校准 - 电源稳定等待期间等待400ms后再进行NVS/WiFi操作 - WiFi启动前延时从500ms调整为300ms - BOD阈值适当放宽避免上电瞬间复位 perf(monitor): 调整堆内存监控策略 - 内存紧张时增大健康监控间隔:低于8KB时从3s增至10s - 避免日志输出消耗更多堆内存造成雪崩效应 docs(readme): 更新冷启动优化说明 - 详细描述电源轨稳定等待机制 - 说明仅BROWNOUT后才强制PHY重校准的策略 - 解释避免与GD32抢电流的设计考虑 style(html): 更新前端资源版本号 - 更新CSS和JavaScript文件的版本参数 - 修复页面加载时的资源缓存问题 fix(ble): 修复阶段显示状态问题 - 终态阶段全部显示为完成状态(绿色) - 优化状态栏样式更新逻辑 feat(ota): 优化SPI通信超时处理 - 将总超时拆分为发送和轮询两个阶段 - 禁止轮询单独占用500ms避免短超时批量卡顿 - 优化极短超时情况下的时间分配策略 refactor(can): 减少批量操作时的日志输出 - SPI失败和GD32拒绝时按类型聚合日志 - 每32次相同错误才输出一次完整日志 - 避免大量错误时UDP/格式化耗尽堆内存
This commit is contained in:
+6
-6
@@ -4,11 +4,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ESP32 CAN 通信控制台</title>
|
||||
<link rel="stylesheet" href="css/base.css?v=20260717t">
|
||||
<link rel="stylesheet" href="css/layout.css?v=20260717t">
|
||||
<link rel="stylesheet" href="css/components.css?v=20260717t">
|
||||
<link rel="stylesheet" href="css/can.css?v=20260717t">
|
||||
<link rel="stylesheet" href="css/responsive.css?v=20260717t">
|
||||
<link rel="stylesheet" href="css/base.css?v=20260717w">
|
||||
<link rel="stylesheet" href="css/layout.css?v=20260717w">
|
||||
<link rel="stylesheet" href="css/components.css?v=20260717w">
|
||||
<link rel="stylesheet" href="css/can.css?v=20260717w">
|
||||
<link rel="stylesheet" href="css/responsive.css?v=20260717w">
|
||||
</head>
|
||||
<body class="can-page">
|
||||
<div class="container can-container">
|
||||
@@ -318,6 +318,6 @@
|
||||
<span id="toastMsg">操作成功</span>
|
||||
</div>
|
||||
|
||||
<script type="module" src="js/can.js?v=20260717t"></script>
|
||||
<script type="module" src="js/can.js?v=20260717w"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+8
-8
@@ -9,11 +9,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ESP32 OTA 中继控制台</title>
|
||||
<link rel="stylesheet" href="css/base.css?v=20260716o">
|
||||
<link rel="stylesheet" href="css/layout.css?v=20260716o">
|
||||
<link rel="stylesheet" href="css/components.css?v=20260716o">
|
||||
<link rel="stylesheet" href="css/can.css?v=20260716o">
|
||||
<link rel="stylesheet" href="css/responsive.css?v=20260716o">
|
||||
<link rel="stylesheet" href="css/base.css?v=20260716q">
|
||||
<link rel="stylesheet" href="css/layout.css?v=20260716q">
|
||||
<link rel="stylesheet" href="css/components.css?v=20260716q">
|
||||
<link rel="stylesheet" href="css/can.css?v=20260716q">
|
||||
<link rel="stylesheet" href="css/responsive.css?v=20260716q">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
@@ -425,8 +425,8 @@
|
||||
<span id="toastMsg">操作成功</span>
|
||||
</div>
|
||||
|
||||
<script type="module" src="js/app.js?v=20260716p"></script>
|
||||
<script type="module" src="js/ota/console-mode.js?v=20260716p"></script>
|
||||
<script type="module" src="js/ble_ota.js?v=20260716p"></script>
|
||||
<script type="module" src="js/app.js?v=20260716q"></script>
|
||||
<script type="module" src="js/ota/console-mode.js?v=20260716q"></script>
|
||||
<script type="module" src="js/ble_ota.js?v=20260716q"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -26,11 +26,13 @@ export function resetBleStages() {
|
||||
|
||||
export function setBleStage(stageNum, detail, barState) {
|
||||
// stageNum: 1-6, detail: sub-step text
|
||||
// 终态 stageNum===6:全部 done(绿);中间步当前为 live(蓝)
|
||||
var finished = stageNum >= 6;
|
||||
for (var i = 0; i < bleStageIds.length; i++) {
|
||||
var el = document.getElementById(bleStageIds[i]);
|
||||
if (!el) continue;
|
||||
if (i + 1 < stageNum) el.className = 'stage-node done';
|
||||
else if (i + 1 === stageNum) el.className = 'stage-node live';
|
||||
else if (i + 1 === stageNum) el.className = finished ? 'stage-node done' : 'stage-node live';
|
||||
else el.className = 'stage-node';
|
||||
}
|
||||
// Set detail text
|
||||
@@ -41,7 +43,8 @@ export function setBleStage(stageNum, detail, barState) {
|
||||
// Update status bar
|
||||
var bar = document.getElementById('stageStatusBar');
|
||||
if (bar) {
|
||||
bar.className = 'stage-status-bar' + (barState ? ' ' + barState : (stageNum > 0 ? ' active' : ''));
|
||||
var cls = barState || (finished ? 'ok' : (stageNum > 0 ? 'active' : ''));
|
||||
bar.className = 'stage-status-bar' + (cls ? ' ' + cls : '');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-8
@@ -20,7 +20,7 @@
|
||||
import {
|
||||
state, el, getBaseUrl, getDeviceIp, setDeviceIp, parseDbc, readDbcText, buildSummary,
|
||||
rebuildMergedDbc, makeDbcSourceId
|
||||
} from './can/dbc.js?v=20260717t';
|
||||
} from './can/dbc.js?v=20260717w';
|
||||
import {
|
||||
updateDbcFileInfo,
|
||||
filterMessages,
|
||||
@@ -30,12 +30,12 @@ import {
|
||||
recomputeTransmitterFilterEmpty,
|
||||
updateNodeFilterLabel,
|
||||
updateBatchUi
|
||||
} from './can/messages.js?v=20260717t';
|
||||
} from './can/messages.js?v=20260717w';
|
||||
import {
|
||||
ensureMessageDefaults,
|
||||
renderSelectedMessage,
|
||||
updateEncodedPreview
|
||||
} from './can/signals.js?v=20260717t';
|
||||
} from './can/signals.js?v=20260717w';
|
||||
import {
|
||||
renderHistory,
|
||||
sendSelectedMessage,
|
||||
@@ -44,15 +44,15 @@ import {
|
||||
sendManualFrame,
|
||||
resetManualFields,
|
||||
loadPreviewToManual
|
||||
} from './can/send.js?v=20260717t';
|
||||
} from './can/send.js?v=20260717w';
|
||||
import {
|
||||
scheduleCanPersist,
|
||||
loadCanSession,
|
||||
bindCanPersistLifecycle
|
||||
} from './can/persist.js?v=20260717t';
|
||||
import { escapeHtml as sanitizeHtml, formatUptime } from './common/utils.js?v=20260717t';
|
||||
import { showToast } from './common/toast.js?v=20260717t';
|
||||
import { initCanMonitor } from './can_monitor.js?v=20260717t';
|
||||
} from './can/persist.js?v=20260717w';
|
||||
import { escapeHtml as sanitizeHtml, formatUptime } from './common/utils.js?v=20260717w';
|
||||
import { showToast } from './common/toast.js?v=20260717w';
|
||||
import { initCanMonitor } from './can_monitor.js?v=20260717w';
|
||||
|
||||
// ==================== 本地工具 ====================
|
||||
// 原 can.js 的 formatUptime 输出格式与 common/utils.js 略有差异(d>0 时只到 h),
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
* common/utils.js(escapeHtml/formatBytes)、
|
||||
* signals.js(renderInlineSignalGrid/ensureMessageDefaults/renderSelectedMessage/updateEncodedPreview)。
|
||||
*/
|
||||
import { state, el, padHex, normalizeWhitespace, cssEscape } from './dbc.js?v=20260717t';
|
||||
import { escapeHtml as sanitizeHtml, formatBytes } from '../common/utils.js?v=20260717t';
|
||||
import { renderInlineSignalGrid, ensureMessageDefaults, renderSelectedMessage, updateEncodedPreview } from './signals.js?v=20260717t';
|
||||
import { scheduleCanPersist } from './persist.js?v=20260717t';
|
||||
import { state, el, padHex, normalizeWhitespace, cssEscape } from './dbc.js?v=20260717v';
|
||||
import { escapeHtml as sanitizeHtml, formatBytes } from '../common/utils.js?v=20260717v';
|
||||
import { renderInlineSignalGrid, ensureMessageDefaults, renderSelectedMessage, updateEncodedPreview } from './signals.js?v=20260717v';
|
||||
import { scheduleCanPersist } from './persist.js?v=20260717v';
|
||||
|
||||
// ==================== DBC 文件信息 ====================
|
||||
/** @description 更新 DBC 文件信息卡片(文件名/报文数/信号数/节点数/大小)。 */
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*
|
||||
* 依赖:dbc.js(state/el/parseDbc/rebuildMergedDbc)
|
||||
*/
|
||||
import { state, el, parseDbc, rebuildMergedDbc } from './dbc.js?v=20260717t';
|
||||
import { state, el, parseDbc, rebuildMergedDbc } from './dbc.js?v=20260717v';
|
||||
|
||||
var LS_KEY = 'dcu_can_session_v1';
|
||||
var MAX_HISTORY = 200;
|
||||
|
||||
+21
-10
@@ -12,12 +12,12 @@
|
||||
* 注意:formatTime 保留本地实现(HH:MM:SS,不含毫秒),与 common/utils.js
|
||||
* 的 formatTime(含毫秒)不同,以保证发送历史时间戳输出格式一致。
|
||||
*/
|
||||
import { state, el, getBaseUrl, padHex, bytesToHex, parseNumericId } from './dbc.js?v=20260717t';
|
||||
import { escapeHtml as sanitizeHtml } from '../common/utils.js?v=20260717t';
|
||||
import { showToast } from '../common/toast.js?v=20260717t';
|
||||
import { updateBatchUi } from './messages.js?v=20260717t';
|
||||
import { ensureMessageDefaults, getSignalValueMap, getSelectedMessage, updateEncodedPreview, encodeMessageData } from './signals.js?v=20260717t';
|
||||
import { scheduleCanPersist } from './persist.js?v=20260717t';
|
||||
import { state, el, getBaseUrl, padHex, bytesToHex, parseNumericId } from './dbc.js?v=20260717w';
|
||||
import { escapeHtml as sanitizeHtml } from '../common/utils.js?v=20260717w';
|
||||
import { showToast } from '../common/toast.js?v=20260717w';
|
||||
import { updateBatchUi } from './messages.js?v=20260717w';
|
||||
import { ensureMessageDefaults, getSignalValueMap, getSelectedMessage, updateEncodedPreview, encodeMessageData } from './signals.js?v=20260717w';
|
||||
import { scheduleCanPersist } from './persist.js?v=20260717w';
|
||||
|
||||
// ==================== 本地工具(保持与原 can.js 一致) ====================
|
||||
function formatTime(date) {
|
||||
@@ -109,7 +109,18 @@ async function sendCanBatchRequest(channel, frames) {
|
||||
if (!json.results || !Array.isArray(json.results)) {
|
||||
throw new Error('批量响应缺少 results');
|
||||
}
|
||||
return json.results;
|
||||
/* 设备中途 fatal 会 skipped 剩余帧(少分配堆);前端补齐等长 results */
|
||||
var results = json.results.slice();
|
||||
var need = frames.length - results.length;
|
||||
if (need > 0) {
|
||||
var pad = {
|
||||
status: 'error',
|
||||
code: (json.abort_code != null) ? json.abort_code : -2,
|
||||
gd32_err: (json.abort_gd32_err != null) ? json.abort_gd32_err : 0
|
||||
};
|
||||
for (var p = 0; p < need; p++) results.push(pad);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
// ==================== 响应框 ====================
|
||||
@@ -161,12 +172,12 @@ export function renderHistory() {
|
||||
}
|
||||
|
||||
// ==================== 批量发送 ====================
|
||||
/** 单批最多帧数(与设备 /can/send_batch 上限一致) */
|
||||
var BATCH_CHUNK = 48;
|
||||
/** 单批最多帧数(与设备 /can/send_batch 上限 128 对齐;一次 HTTP 连发,避免分块成“一阵一阵”) */
|
||||
var BATCH_CHUNK = 128;
|
||||
|
||||
/**
|
||||
* @description 批量发送所有勾选的报文。
|
||||
* 优先一次 HTTP /can/send_batch 在设备侧紧接连发;失败则回退单帧紧接。
|
||||
* 优先一次 HTTP /can/send_batch(设备侧帧间 5ms);失败则回退单帧(无额外间隔)。
|
||||
* 批量过程中不重绘历史,结束后统一刷新。
|
||||
*/
|
||||
export async function sendCheckedMessages() {
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
*
|
||||
* 依赖:dbc.js(state/el/padHex/bytesToHex/cssEscape/findSignal)、common/utils.js(escapeHtml)。
|
||||
*/
|
||||
import { state, el, padHex, bytesToHex, cssEscape, findSignal } from './dbc.js?v=20260717t';
|
||||
import { escapeHtml as sanitizeHtml } from '../common/utils.js?v=20260717t';
|
||||
import { scheduleCanPersist } from './persist.js?v=20260717t';
|
||||
import { state, el, padHex, bytesToHex, cssEscape, findSignal } from './dbc.js?v=20260717v';
|
||||
import { escapeHtml as sanitizeHtml } from '../common/utils.js?v=20260717v';
|
||||
import { scheduleCanPersist } from './persist.js?v=20260717v';
|
||||
|
||||
// ==================== 信号值存取 ====================
|
||||
/** @description 确保报文有 signalValues bag,并为每个信号填充默认值。返回 bag。 */
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* can_monitor.js - CAN 接收监控(独立页 can_monitor.html 与 can.html 右侧抽屉共用)
|
||||
*/
|
||||
import { showToast } from './common/toast.js?v=20260717t';
|
||||
import { showToast } from './common/toast.js?v=20260717v';
|
||||
|
||||
var state = { fbTimer: null, fbAbortCtrl: null };
|
||||
|
||||
|
||||
@@ -72,12 +72,16 @@ export function updateEsp32StagesFromPhase(phase) {
|
||||
if (statusText) statusText.textContent = '待机';
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < phase - 1 && i < 6; i++) {
|
||||
if (all[i]) all[i].className = 'stage-node done';
|
||||
}
|
||||
if (phase >= 1 && phase <= 6) {
|
||||
if (all[phase - 1]) all[phase - 1].className = 'stage-node live';
|
||||
if (details[phase - 1]) details[phase - 1].textContent = esp32PhaseLabels[phase] || '';
|
||||
/* phase=N:前 N-1 步 done;第 N 步进行中 live。
|
||||
* phase=6(启动完成/已完成)是终态:全部 done(绿),最后一步不要 live(蓝)。 */
|
||||
for (var i = 0; i < 6; i++) {
|
||||
if (!all[i]) continue;
|
||||
if (i < phase - 1) {
|
||||
all[i].className = 'stage-node done';
|
||||
} else if (i === phase - 1 && phase >= 1) {
|
||||
all[i].className = (phase >= 6) ? 'stage-node done' : 'stage-node live';
|
||||
if (details[i]) details[i].textContent = esp32PhaseLabels[phase] || '';
|
||||
}
|
||||
}
|
||||
if (statusBar && statusText) {
|
||||
if (phase >= 6) {
|
||||
|
||||
Reference in New Issue
Block a user