feat(ble): 完善BLE OTA架构并优化性能
- 实现BLE OTA数据路径分离:添加ble_ota_data.cpp处理重排序/工作线程/DATA_IN, 和ble_ota_host.cpp处理GATT/GAP/广播/主机功能 - 将滑动ACK窗口从32增大到64,实测Windows WebBT环境下固件传输速度提升约10% (从40KB/s提升到44KB/s),内存消耗约30KB重排序缓冲区 - 优化调试日志级别,将OTA相关信息从INFO调整为DBG以减少日志冗余 refactor(can): 改进CAN反馈监听机制 - 将CAN反馈任务从1Hz主动轮询模式改为被动事件监听模式, 使用SpiMaster::takeEvent()替代主动发送0xC1轮询请求 - 更新相关头文件注释说明架构变更,将"周期性轮询"改为"被动接收推送" - 修改调试日志级别,将暂停/恢复状态日志从INFO调整为DBG
This commit is contained in:
+34
-113
@@ -1,7 +1,11 @@
|
||||
/**
|
||||
* upload-gd32.js - GD32 中继 OTA 触发 + relay 态跟踪
|
||||
* upload-gd32.js - GD32 中继 OTA 触发 + 进度辅助
|
||||
*
|
||||
* 包含:relay 进度观察定时器、markGd32OtaUiComplete、triggerGd32Relay。
|
||||
* 进度/完成/失败 UI 的唯一应用路径:pollStatus → updateSystemStatusUi(stages.js)。
|
||||
* 本文件只负责:
|
||||
* - 触发 /ota/relay
|
||||
* - 中继期加速 status 轮询(400ms)
|
||||
* - emitRelayProgress / markGd32OtaUiComplete 等被 stages 调用的辅助函数
|
||||
*
|
||||
* 共享状态(通过 window.* 引用):
|
||||
* window.gd32CompleteAnnounced / window.relayWatchLastSeq / window.gd32RelaySessionLive
|
||||
@@ -9,7 +13,7 @@
|
||||
* window.otaSessionState / window.lastSysStateCode / window.lastRelayState
|
||||
*
|
||||
* 复用的函数(通过 window.* 引用):
|
||||
* window.otaSessionTransition / window.OtaSessionState / window.otaSessionActive
|
||||
* window.otaSessionTransition / window.OtaSessionState
|
||||
* window.markPhaseDone / window.setProgress / window.updateSystemStatusUi
|
||||
* window.cleanupAfterOtaFailure / window.scheduleStatusPoll / window.pollLogs
|
||||
* window.resumePollingAfterUpload / window.getBaseUrl / window.showToast / window.addLog
|
||||
@@ -17,28 +21,22 @@
|
||||
import { showToast } from '../common/toast.js';
|
||||
import { addLog } from '../common/log.js';
|
||||
import {
|
||||
otaSessionTransition, otaSessionActive, OtaSessionState
|
||||
otaSessionTransition, OtaSessionState
|
||||
} from './session.js';
|
||||
import { markPhaseDone, setProgress } from './progress.js';
|
||||
|
||||
/**
|
||||
* GD32 中继进度观察:以 /system/status 为唯一真相源。
|
||||
* 日志环只有 64 条,中继/擦除期间 INFO 会被挤掉,时间戳还是浏览器本地时间——
|
||||
* 绝不能用「最后一条日志停在 SEND_SIZE」判断卡死。
|
||||
*
|
||||
* 时序约定(避免「设备已 FINISH / 完成,UI 还在 94%」):
|
||||
* 1) 包进度变化先刷进度条 + 日志;
|
||||
* 2) 一旦 rseq>=rtot 或 rs>=4(FINISHING),先强制打出 100% 再进收尾/完成;
|
||||
* 3) markGd32OtaUiComplete 前再兜底一次 100%。
|
||||
*/
|
||||
let relayWatchTimerId = null;
|
||||
/** 中继期 status 轮询间隔(ms);结束后由 stopRelayProgressWatch 恢复 1000 */
|
||||
const RELAY_STATUS_POLL_MS = 400;
|
||||
const NORMAL_STATUS_POLL_MS = 1000;
|
||||
|
||||
window.gd32CompleteAnnounced = false;
|
||||
window.relayWatchLastSeq = -1;
|
||||
/** 本会话是否已观察到真实中继态(1~4),避免旧 relay_state=5 残留导致一点击就“完成” */
|
||||
window.gd32RelaySessionLive = false;
|
||||
|
||||
/**
|
||||
* 包进度变更时更新进度条并写一条「转发」日志(同 seq 不重复)。
|
||||
* 包进度变更时更新进度条(同 seq 不重复)。
|
||||
* 由 stages.js 在 status 轮询路径调用;勿在本文件再起独立定时器刷进度。
|
||||
* @param {number} rseq
|
||||
* @param {number} rtot
|
||||
* @param {{force100?: boolean}} [opts]
|
||||
@@ -79,7 +77,7 @@ export function ensureRelayProgressComplete() {
|
||||
}
|
||||
|
||||
export function markGd32OtaUiComplete(msg) {
|
||||
// 已公告过:禁止再改 className / setProgress / toast,否则定时器重触发动效会「疯狂跳 ✓」
|
||||
// 已公告过:禁止再改 className / setProgress / toast,否则 status 重触发动效会「疯狂跳 ✓」
|
||||
if (window.gd32CompleteAnnounced) {
|
||||
return;
|
||||
}
|
||||
@@ -111,106 +109,33 @@ export function markGd32OtaUiComplete(msg) {
|
||||
showToast(msg || 'GD32 刷写完成', 'success');
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束中继加速轮询,恢复常态 1s status 间隔。
|
||||
* 不再持有独立 setInterval(进度/完成/失败统一由 stages 在 pollStatus 路径处理)。
|
||||
*/
|
||||
export function stopRelayProgressWatch() {
|
||||
if (relayWatchTimerId) {
|
||||
clearInterval(relayWatchTimerId);
|
||||
relayWatchTimerId = null;
|
||||
}
|
||||
/* 中继结束:恢复常态 1s status 轮询 */
|
||||
if (typeof window.setStatusPollIntervalMs === 'function') {
|
||||
window.setStatusPollIntervalMs(1000);
|
||||
window.setStatusPollIntervalMs(NORMAL_STATUS_POLL_MS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 中继进度观察:只读 pollStatus 写入的 latestSystemStatus,不再另起 /system/status。
|
||||
* 中继期 status 加速到 400ms,使「转发 N/M」更贴近设备真实进度。
|
||||
* 中继进度跟踪:只加速 /system/status 轮询,不再另起 400ms 定时器。
|
||||
* UI 真相源仍是 pollStatus → updateSystemStatusUi(stages.js)。
|
||||
*/
|
||||
export function startRelayProgressWatch() {
|
||||
stopRelayProgressWatch();
|
||||
if (window.pollingPausedForUpload) {
|
||||
window.resumePollingAfterUpload();
|
||||
}
|
||||
/* 中继期加速 status,缩小 UI 进度相对设备日志的滞后 */
|
||||
if (typeof window.setStatusPollIntervalMs === 'function') {
|
||||
window.setStatusPollIntervalMs(400);
|
||||
window.setStatusPollIntervalMs(RELAY_STATUS_POLL_MS);
|
||||
}
|
||||
if (typeof window.scheduleStatusPoll === 'function') {
|
||||
window.scheduleStatusPoll(0);
|
||||
}
|
||||
if (typeof window.pollLogs === 'function') {
|
||||
window.pollLogs();
|
||||
}
|
||||
window.scheduleStatusPoll(0);
|
||||
window.pollLogs();
|
||||
addLog('I', 'OTA', '跟踪中继进度:看进度条与 relay_seq(/system/status),勿只看日志');
|
||||
relayWatchTimerId = setInterval(function () {
|
||||
var active = window.otaSessionState === OtaSessionState.RELAYING ||
|
||||
(window.otaSessionState === OtaSessionState.FAILED &&
|
||||
(window.lastSysStateCode === 3 || window.lastRelayState >= 1 && window.lastRelayState <= 4));
|
||||
if (!active) {
|
||||
if (window.otaSessionState === OtaSessionState.DONE) {
|
||||
stopRelayProgressWatch();
|
||||
}
|
||||
return;
|
||||
}
|
||||
var data = window.latestSystemStatus;
|
||||
if (!data || !data.ota) return;
|
||||
var o = data.ota || {};
|
||||
var code = Number(o.sys_state_code);
|
||||
var rstate = Number(o.relay_state);
|
||||
var alive = !!o.gd32_app_alive;
|
||||
var rseq = Number(o.relay_seq) || 0;
|
||||
var rtot = Number(o.relay_total_packets) || 0;
|
||||
if (rstate >= 1 && rstate <= 4) {
|
||||
window.gd32RelaySessionLive = true;
|
||||
}
|
||||
/* 中继失败:必须把前端会话从 RELAYING 拉到 FAILED,否则无法新开上传 */
|
||||
if (code === 8 || rstate === 255) {
|
||||
stopRelayProgressWatch();
|
||||
window.pollLogs();
|
||||
if (window.otaSessionState === OtaSessionState.RELAYING ||
|
||||
window.otaSessionState === OtaSessionState.UPLOADING) {
|
||||
var failMsg = 'GD32 中继失败(relay_state=' + rstate +
|
||||
', sys=' + (o.sys_state || code) + ')';
|
||||
if (typeof window.cleanupAfterOtaFailure === 'function') {
|
||||
window.cleanupAfterOtaFailure(failMsg);
|
||||
} else {
|
||||
otaSessionTransition(OtaSessionState.FAILED, failMsg);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* ERROR 可能在 status 路径被软清为 IDLE,前端收不到 code=8。
|
||||
* 本会话已见过真实中继,设备却已回 IDLE 且未宣布完成 → 按失败清理。
|
||||
*/
|
||||
var idleAfterRelay = (code === 0 || code === 2) &&
|
||||
(rstate === 0 || rstate === 5) &&
|
||||
window.gd32RelaySessionLive &&
|
||||
!window.gd32CompleteAnnounced &&
|
||||
window.otaSessionState === OtaSessionState.RELAYING;
|
||||
if (idleAfterRelay) {
|
||||
stopRelayProgressWatch();
|
||||
window.pollLogs();
|
||||
if (typeof window.cleanupAfterOtaFailure === 'function') {
|
||||
window.cleanupAfterOtaFailure('GD32 中继已结束但未成功(设备已回 IDLE)');
|
||||
} else {
|
||||
otaSessionTransition(OtaSessionState.FAILED, 'GD32 中继已结束但未成功');
|
||||
}
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* 顺序:先刷包进度(含强制 100%),再判定完成。
|
||||
* FINISHING/DONE 时若 status 滞后未到 total,仍按 total 打 100%。
|
||||
*/
|
||||
var transferDone = rtot > 0 && (rseq >= rtot || rstate >= 4 || code === 4);
|
||||
if (transferDone) {
|
||||
emitRelayProgress(rtot, rtot, { force100: true });
|
||||
} else if (rtot > 0 && rseq > 0) {
|
||||
emitRelayProgress(rseq, rtot);
|
||||
}
|
||||
if (window.gd32RelaySessionLive &&
|
||||
(code === 4 || rstate === 5 ||
|
||||
(alive && rtot > 0 && rseq >= rtot && rstate >= 3))) {
|
||||
markGd32OtaUiComplete('GD32 刷写完成');
|
||||
window.pollLogs();
|
||||
}
|
||||
}, 400);
|
||||
}
|
||||
|
||||
/** @description 触发 GD32 中继 OTA 流程(通过 HTTP 接口)。 */
|
||||
@@ -222,8 +147,7 @@ export async function triggerGd32Relay() {
|
||||
window.cleanupAfterOtaFailure('SHA-256 缺失');
|
||||
return;
|
||||
}
|
||||
addLog('I', 'OTA', 'GD32 SHA-256: ' + sha256 + ' → 触发中继...');
|
||||
showToast('正在触发 GD32 OTA', 'success');
|
||||
addLog('I', 'OTA', '触发 GD32 中继…');
|
||||
setProgress(48, '校验 SHA 并触发 GD32 中继…');
|
||||
try {
|
||||
// 仅触发中继(设备端异步跑),不阻塞到刷写结束;SHA 读分区可能数秒
|
||||
@@ -235,11 +159,7 @@ export async function triggerGd32Relay() {
|
||||
});
|
||||
var text = await resp.text();
|
||||
if (resp.ok) {
|
||||
try {
|
||||
var data = JSON.parse(text);
|
||||
if (data.sha256) addLog('I', 'OTA', '设备端 SHA-256: ' + data.sha256);
|
||||
} catch (_) {}
|
||||
addLog('I', 'OTA', 'GD32 OTA 已触发;进度看状态栏包数(勿只看日志时间戳)');
|
||||
addLog('I', 'OTA', 'GD32 OTA 已触发;进度看状态栏包数');
|
||||
showToast('GD32 OTA 已触发', 'success');
|
||||
setProgress(50, '中继已启动,同步进度…');
|
||||
window.gd32CompleteAnnounced = false;
|
||||
@@ -250,7 +170,8 @@ export async function triggerGd32Relay() {
|
||||
try {
|
||||
var err = JSON.parse(text);
|
||||
if (err.device) {
|
||||
addLog('E', 'OTA', 'GD32 SHA-256 不匹配: 设备=' + err.device + ' 前端=' + sha256);
|
||||
addLog('E', 'OTA', 'GD32 SHA-256 不匹配: 设备=' + err.device +
|
||||
' 前端=' + sha256);
|
||||
showToast('SHA-256 不匹配', 'error');
|
||||
window.cleanupAfterOtaFailure('SHA-256 不匹配');
|
||||
return;
|
||||
@@ -287,4 +208,4 @@ export async function triggerGd32Relay() {
|
||||
showToast('GD32 触发失败', 'error');
|
||||
window.cleanupAfterOtaFailure('触发异常');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user