Files
DCU_DesktopController/js/can/persist.js
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

223 lines
6.6 KiB
JavaScript
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.
/**
* persist.js - CAN 控制台会话持久化(刷新后恢复)
*
* 保存到 localStorage:多 DBC 原文、信号编辑值、勾选/展开、发送节点过滤、
* 最近发送历史、通道/搜索/原始帧表单。
*
* 依赖:dbc.js(state/el/parseDbc/rebuildMergedDbc)
*/
import { state, el, parseDbc, rebuildMergedDbc } from './dbc.js';
var LS_KEY = 'dcu_can_session_v1';
var MAX_HISTORY = 200;
var SAVE_DEBOUNCE_MS = 350;
var saveTimer = null;
var restoring = false;
function safeJsonParse(raw) {
try {
return JSON.parse(raw);
} catch (_) {
return null;
}
}
function transmitterFilterToPlain() {
var out = {};
for (var k in state.transmitterFilter) {
if (state.transmitterFilter[k]) out[k] = true;
}
return out;
}
function collectUiFields() {
return {
defaultChannel: el.defaultChannel ? el.defaultChannel.value : '0',
manualChannel: el.manualChannel ? el.manualChannel.value : '0',
manualId: el.manualId ? el.manualId.value : '',
manualDlc: el.manualDlc ? el.manualDlc.value : '8',
manualData: el.manualData ? el.manualData.value : '',
dbcSearch: el.dbcSearch ? el.dbcSearch.value : ''
};
}
function applyUiFields(ui) {
if (!ui) return;
if (el.defaultChannel && ui.defaultChannel != null) {
el.defaultChannel.value = String(ui.defaultChannel);
}
if (el.manualChannel && ui.manualChannel != null) {
el.manualChannel.value = String(ui.manualChannel);
} else if (el.manualChannel && el.defaultChannel) {
el.manualChannel.value = el.defaultChannel.value;
}
if (el.manualId && ui.manualId != null) el.manualId.value = String(ui.manualId);
if (el.manualDlc && ui.manualDlc != null) el.manualDlc.value = String(ui.manualDlc);
if (el.manualData && ui.manualData != null) el.manualData.value = String(ui.manualData);
if (el.dbcSearch && ui.dbcSearch != null) el.dbcSearch.value = String(ui.dbcSearch);
if (el.canChannelPill && el.defaultChannel) {
el.canChannelPill.textContent = 'CH' + el.defaultChannel.value;
}
}
function buildPayload(options) {
options = options || {};
var sources = (state.dbcSources || []).map(function (s) {
return {
id: s.id,
name: s.name,
size: s.size || 0,
text: s.text || ''
};
}).filter(function (s) { return s.text; });
var history = Array.isArray(state.history) ? state.history.slice(0, MAX_HISTORY) : [];
if (options.historyLimit != null) {
history = history.slice(0, options.historyLimit);
}
return {
v: 1,
ts: Date.now(),
dbcSources: sources,
selectedMessageKey: state.selectedMessageKey || '',
signalValues: state.signalValues || {},
checkedKeys: state.checkedKeys || {},
expandedKeys: state.expandedKeys || {},
transmitterFilter: transmitterFilterToPlain(),
history: history,
ui: collectUiFields()
};
}
function writePayload(payload) {
localStorage.setItem(LS_KEY, JSON.stringify(payload));
}
/** @description 立即写入 localStorage(空间不足时逐步丢弃历史/信号)。 */
export function saveCanSessionNow() {
if (restoring) return;
if (saveTimer) {
clearTimeout(saveTimer);
saveTimer = null;
}
try {
writePayload(buildPayload());
return;
} catch (e1) {
/* quota:先砍历史 */
}
try {
writePayload(buildPayload({ historyLimit: 40 }));
return;
} catch (e2) {
/* 再砍信号值,只保 DBC + 勾选 */
}
try {
var slim = buildPayload({ historyLimit: 0 });
slim.signalValues = {};
slim.history = [];
writePayload(slim);
} catch (e3) {
/* 放弃 */
}
}
/** @description 防抖保存(编辑信号/勾选等高频操作)。 */
export function scheduleCanPersist() {
if (restoring) return;
if (saveTimer) clearTimeout(saveTimer);
saveTimer = setTimeout(function () {
saveTimer = null;
saveCanSessionNow();
}, SAVE_DEBOUNCE_MS);
}
/**
* @description 从 localStorage 恢复会话到 state + 表单。
* @returns {{ restored: boolean, dbcCount: number, historyCount: number }}
*/
export function loadCanSession() {
var empty = { restored: false, dbcCount: 0, historyCount: 0 };
var raw = null;
try {
raw = localStorage.getItem(LS_KEY);
} catch (_) {
return empty;
}
if (!raw) return empty;
var data = safeJsonParse(raw);
if (!data || data.v !== 1) return empty;
restoring = true;
try {
state.dbcSources = [];
var list = Array.isArray(data.dbcSources) ? data.dbcSources : [];
for (var i = 0; i < list.length; i++) {
var src = list[i];
if (!src || !src.text) continue;
try {
var dbc = parseDbc(src.text);
if (!dbc.messages || !dbc.messages.length) continue;
state.dbcSources.push({
id: src.id || ('restored_' + i),
name: src.name || ('saved_' + (i + 1) + '.dbc'),
size: src.size || src.text.length,
text: src.text,
dbc: dbc
});
} catch (_) {
/* 单文件损坏跳过 */
}
}
rebuildMergedDbc();
state.signalValues = (data.signalValues && typeof data.signalValues === 'object')
? data.signalValues : {};
state.checkedKeys = (data.checkedKeys && typeof data.checkedKeys === 'object')
? data.checkedKeys : {};
state.expandedKeys = (data.expandedKeys && typeof data.expandedKeys === 'object')
? data.expandedKeys : {};
state.selectedMessageKey = data.selectedMessageKey || '';
state.history = Array.isArray(data.history) ? data.history.slice(0, MAX_HISTORY) : [];
state.transmitterFilter = Object.create(null);
if (data.transmitterFilter && typeof data.transmitterFilter === 'object') {
Object.keys(data.transmitterFilter).forEach(function (k) {
state.transmitterFilter[k] = !!data.transmitterFilter[k];
});
}
var anyTx = false;
for (var tk in state.transmitterFilter) {
if (state.transmitterFilter[tk]) { anyTx = true; break; }
}
state.transmitterFilterEmpty = !anyTx;
/* 选中键若不存在则清空,交由调用方选第一条 */
if (state.selectedMessageKey &&
(!state.dbc || !state.dbc.messageMap[state.selectedMessageKey])) {
state.selectedMessageKey = '';
}
applyUiFields(data.ui);
return {
restored: true,
dbcCount: state.dbcSources.length,
historyCount: state.history.length
};
} finally {
restoring = false;
}
}
/** @description 绑定 beforeunload / 可见性,降低丢失。 */
export function bindCanPersistLifecycle() {
window.addEventListener('beforeunload', function () {
saveCanSessionNow();
});
document.addEventListener('visibilitychange', function () {
if (document.visibilityState === 'hidden') saveCanSessionNow();
});
}