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:
+68
-60
@@ -1,17 +1,27 @@
|
||||
// ==================== BLE OTA 文件准备与分包 ====================
|
||||
import { state, els, bleLog, currentTarget, currentPayloadSize, setConnectionState } from './state.js';
|
||||
//
|
||||
// ★ 上传中拦截策略(全文件统一):file.js 是 BLE 通道文件操作的唯一入口,
|
||||
// transfer.js / state.js / session.js / 外部 bleOta.notifyFileChange 都汇聚到这里。
|
||||
// isBleUploading()(state.js)= state.uploading || state.autoFlashing;
|
||||
// window.isUploading = WiFi 通道 OTA 会话是否处于 UPLOADING(ota/session.js 维护)。
|
||||
// 两者任一为真即视为上传中,禁止重置进度条/阶段/清空文件,避免覆盖进行中的 OTA。
|
||||
import { state, els, bleLog, currentTarget, currentPayloadSize, setConnectionState, isBleUploading } from './state.js';
|
||||
import { setBleStage, setBleStageText, resetBleStages } from './stages.js';
|
||||
import { setProgress } from './transfer.js';
|
||||
import { hideBleVerifyInfo } from './verify.js';
|
||||
import { formatBytes } from '../common/utils.js';
|
||||
import { formatBytes, bytesToHex } from '../common/utils.js';
|
||||
import { showToast } from '../common/toast.js';
|
||||
|
||||
/** 任一通道上传中(BLE 或 WiFi)。focus/change 等不分通道的事件用此判定。 */
|
||||
function isAnyChannelUploading() {
|
||||
return isBleUploading() || (typeof window.isUploading !== 'undefined' && !!window.isUploading);
|
||||
}
|
||||
|
||||
export function updateFileInfo(file) {
|
||||
// ★ 上传进行中禁止重置进度/阶段:目标切换、模式切换、syncBleFileFromSelectedTarget
|
||||
// 等事件会在上传期间调用本函数,若不拦截会触发 finalizeFileInfo → setProgress(0, ...)
|
||||
// 把进度条从"已确认 N/M 包"错误地刷回"等待选择文件 0%"。
|
||||
var isUploading = state.uploading || state.autoFlashing;
|
||||
if (isUploading && file && state.selectedFile) {
|
||||
if (isBleUploading() && file && state.selectedFile) {
|
||||
// 上传中且文件未变:仅刷新元数据(文件名/大小等),不碰进度条和阶段
|
||||
bleLog('FILE', '上传进行中,跳过 updateFileInfo 进度重置', 'D');
|
||||
return Promise.resolve();
|
||||
@@ -33,13 +43,12 @@ export function updateFileInfo(file) {
|
||||
else els.fileInfo.classList.remove('show');
|
||||
}
|
||||
|
||||
// ★ HEX → BIN 自动转换:复用 app.js 公共函数 convertHexFileToBin
|
||||
if (file && file.name.toLowerCase().endsWith('.hex') && typeof convertHexFileToBin === 'function') {
|
||||
// ★ HEX → BIN 自动转换:复用 app.js 公共函数 window.convertHexFileToBin
|
||||
if (file && file.name.toLowerCase().endsWith('.hex') && typeof window.convertHexFileToBin === 'function') {
|
||||
return convertHexFileToBin(file).then(function(result) {
|
||||
if (!result.converted) {
|
||||
bleLog('FILE', file.name + ' 扩展名为 .hex 但内容不像 HEX 格式,按原始文件处理', 'W');
|
||||
finalizeFileInfo(file);
|
||||
computeFileSha256(file);
|
||||
finalizeAndHash(file);
|
||||
return;
|
||||
}
|
||||
state.selectedFile = result.file;
|
||||
@@ -52,8 +61,7 @@ export function updateFileInfo(file) {
|
||||
els.fileSize.textContent = formatBytes(result.file.size);
|
||||
var newPackets = Math.ceil(result.file.size / payloadSize);
|
||||
els.packetCount.textContent = String(newPackets);
|
||||
finalizeFileInfo(result.file);
|
||||
computeFileSha256(result.file);
|
||||
finalizeAndHash(result.file);
|
||||
}).catch(function(err) {
|
||||
bleLog('FILE', 'HEX 解析失败: ' + err.message, 'E');
|
||||
showToast('HEX 文件解析失败: ' + err.message, 'error');
|
||||
@@ -65,16 +73,20 @@ export function updateFileInfo(file) {
|
||||
}
|
||||
|
||||
// 非 HEX 文件直接完成
|
||||
finalizeAndHash(file);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/** finalizeFileInfo + computeFileSha256 的统一出口:HEX 转换完成分支与非 HEX 分支共用。 */
|
||||
function finalizeAndHash(file) {
|
||||
finalizeFileInfo(file);
|
||||
computeFileSha256(file);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
function finalizeFileInfo(file) {
|
||||
// ★ 上传进行中禁止重置进度条/阶段:防止目标切换等事件在上传期间
|
||||
// 通过 finalizeFileInfo → setProgress(0, '等待选择文件') 覆盖上传进度。
|
||||
var isUploading = state.uploading || state.autoFlashing;
|
||||
if (isUploading) {
|
||||
if (isBleUploading()) {
|
||||
bleLog('FILE', '上传进行中,跳过 finalizeFileInfo 进度重置', 'D');
|
||||
return;
|
||||
}
|
||||
@@ -82,10 +94,6 @@ function finalizeFileInfo(file) {
|
||||
if (file) {
|
||||
setProgress(0, `已选择 ${file.name}`);
|
||||
bleLog('FILE', `已选择: ${file.name} (${formatBytes(file.size)}), 目标=${currentTarget().label}`);
|
||||
if (state.server) {
|
||||
setBleStage(3, '计算哈希...', 'active');
|
||||
setBleStageText('正在计算文件 SHA-256...');
|
||||
}
|
||||
} else {
|
||||
setProgress(0, '等待选择文件');
|
||||
if (state.server) {
|
||||
@@ -96,42 +104,51 @@ function finalizeFileInfo(file) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算文件 SHA-256 并推进 BLE stage 流。
|
||||
* stage 推进逻辑收敛在此函数内,避免与 finalizeFileInfo 隐式耦合:
|
||||
* - 已连接且 file 非空:进入「计算中」态(stage 3 active + 状态栏文案)
|
||||
* - 哈希算完:stage 3 done + 推进 stage 4 + 状态栏「文件就绪」
|
||||
* - file 为空 / 未连接:不碰 stage(由 finalizeFileInfo 的 else 分支 + clearSelectedFile 负责)
|
||||
*/
|
||||
function computeFileSha256(file) {
|
||||
var shaElem = document.getElementById('fileSha256');
|
||||
if (file && shaElem) {
|
||||
shaElem.textContent = '计算中...';
|
||||
file.arrayBuffer().then(function(buf) {
|
||||
return crypto.subtle.digest('SHA-256', buf);
|
||||
}).then(function(hashBuffer) {
|
||||
var hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||
var hashHex = hashArray.map(function(b) { return b.toString(16).padStart(2, '0'); }).join('');
|
||||
shaElem.textContent = hashHex.toUpperCase();
|
||||
state.fileSha256 = hashHex.toUpperCase();
|
||||
bleLog('FILE', 'SHA-256: ' + hashHex.toUpperCase());
|
||||
// If connected, advance to stage 3 done
|
||||
if (state.server) {
|
||||
setBleStage(3, 'SHA-256 已计算', null);
|
||||
setBleStage(4, '', null);
|
||||
setBleStageText('文件就绪,可以开始刷写');
|
||||
}
|
||||
}).catch(function(err) {
|
||||
shaElem.textContent = '计算失败: ' + err.message;
|
||||
state.fileSha256 = null;
|
||||
});
|
||||
} else if (shaElem) {
|
||||
shaElem.textContent = '-';
|
||||
if (!file || !shaElem) {
|
||||
if (shaElem) shaElem.textContent = '-';
|
||||
state.fileSha256 = null;
|
||||
return;
|
||||
}
|
||||
// 已连接:进入「计算中」态(旧逻辑放在 finalizeFileInfo 里,现收敛至此)
|
||||
if (state.server) {
|
||||
setBleStage(3, '计算哈希...', 'active');
|
||||
setBleStageText('正在计算文件 SHA-256...');
|
||||
}
|
||||
shaElem.textContent = '计算中...';
|
||||
file.arrayBuffer().then(function(buf) {
|
||||
return crypto.subtle.digest('SHA-256', buf);
|
||||
}).then(function(hashBuffer) {
|
||||
var hashHex = bytesToHex(new Uint8Array(hashBuffer)).toUpperCase();
|
||||
shaElem.textContent = hashHex;
|
||||
state.fileSha256 = hashHex;
|
||||
bleLog('FILE', 'SHA-256: ' + hashHex);
|
||||
if (state.server) {
|
||||
setBleStage(3, 'SHA-256 已计算', null);
|
||||
setBleStage(4, '', null);
|
||||
setBleStageText('文件就绪,可以开始刷写');
|
||||
}
|
||||
}).catch(function(err) {
|
||||
shaElem.textContent = '计算失败: ' + err.message;
|
||||
state.fileSha256 = null;
|
||||
});
|
||||
}
|
||||
|
||||
export async function syncSelectedFileFromInput() {
|
||||
// ★ 上传进行中禁止同步:window focus / file input change 事件会在上传期间
|
||||
// 触发本函数,若不拦截会通过 updateFileInfo 把进度条重置为"等待选择文件 0%"。
|
||||
// ★ 必须同时拦截 WiFi 通道上传 (app.js 全局 isUploading):focus 事件不分通道,
|
||||
// WiFi 刷写时点击网页/切标签回来会触发本函数 → finalizeFileInfo → setProgress(0),
|
||||
// 把进度"闪一下"归零,直到下一次 XHR progress 事件才恢复。
|
||||
if (state.uploading || state.autoFlashing) return;
|
||||
if (typeof isUploading !== 'undefined' && isUploading) return;
|
||||
// ★ 必须同时拦截 WiFi 通道上传:focus 事件不分通道,WiFi 刷写时点击网页/切标签
|
||||
// 回来会触发本函数 → finalizeFileInfo → setProgress(0),把进度"闪一下"归零,
|
||||
// 直到下一次 XHR progress 事件才恢复。
|
||||
if (isAnyChannelUploading()) return;
|
||||
// FSAPI 选择的文件以 selectedFileHandle 为准,不从 input 元素同步,避免切标签页回来误清空
|
||||
if (state.selectedFileHandle) return;
|
||||
const file = (els.firmwareFile && els.firmwareFile.files && els.firmwareFile.files[0]) || null;
|
||||
@@ -174,7 +191,7 @@ export async function pickFileViaFSAPI() {
|
||||
export function clearSelectedFile() {
|
||||
// ★ 上传进行中禁止清除文件:防止目标切换/模式切换在上传期间清空文件引用,
|
||||
// 导致进度条被重置为"等待选择文件 0%"并中断上传数据源。
|
||||
if (state.uploading || state.autoFlashing) {
|
||||
if (isBleUploading()) {
|
||||
bleLog('FILE', '上传进行中,拒绝清除文件', 'W');
|
||||
return;
|
||||
}
|
||||
@@ -184,32 +201,23 @@ export function clearSelectedFile() {
|
||||
state.fileSha256 = null;
|
||||
state.deviceSha256 = null;
|
||||
if (els.firmwareFile) els.firmwareFile.value = '';
|
||||
updateFileInfo(null);
|
||||
updateFileInfo(null); // 内部 finalizeFileInfo(null) 已重置进度条并恢复「连接就绪」stage
|
||||
hideBleVerifyInfo();
|
||||
// Reset stages back to connection-ready state
|
||||
if (state.server) {
|
||||
resetBleStages();
|
||||
setBleStage(3, '', null);
|
||||
setBleStageText('BLE 连接就绪,请选择固件文件');
|
||||
}
|
||||
bleLog('FILE', '文件已清除');
|
||||
}
|
||||
|
||||
// 别名导出,与 window.bleOta.clearFile 对齐
|
||||
export { clearSelectedFile as clearFile };
|
||||
|
||||
/**
|
||||
* 外部文件变更通知(index.html 拖拽 / console-mode 切换等)。
|
||||
* ★ 守卫集中在此:file.js 是 BLE 文件操作唯一入口,外部包装层(ble_ota.js)不再重复守卫。
|
||||
*/
|
||||
export async function notifyFileChange(file, handle) {
|
||||
// ★ 上传进行中拒绝外部文件变更通知:防止 index.html 的 syncBleFileFromSelectedTarget
|
||||
// 在上传期间调用本函数 → updateFileInfo → setProgress(0, ...) 覆盖上传进度。
|
||||
if (state.uploading || state.autoFlashing) {
|
||||
if (isAnyChannelUploading()) {
|
||||
bleLog('FILE', '上传进行中,忽略外部 notifyFileChange', 'D');
|
||||
return;
|
||||
}
|
||||
// ★ WiFi 通道上传期间同样拒绝:setConsoleMode('ble') 等外部触发会覆盖 WiFi 进度
|
||||
if (typeof isUploading !== 'undefined' && isUploading) {
|
||||
bleLog('FILE', 'WiFi 上传进行中,忽略外部 notifyFileChange', 'D');
|
||||
return;
|
||||
}
|
||||
if (file) {
|
||||
if (handle) state.selectedFileHandle = handle;
|
||||
await updateFileInfo(file);
|
||||
|
||||
Reference in New Issue
Block a user