feat(ble-ota): 优化BLE OTA传输性能和可靠性
- 调整NimBLE配置恢复默认缓冲区大小以支持窗口内突发包接收 - 实现BLE连接参数动态调整,支持Windows兼容模式 - 优化OTA数据队列长度与确认窗口匹配 - 改进reorder buffer管理,添加乱序包缓存统计 - 修复ACK触发逻辑,确保按累计进度而非单包序列确认 - 添加GD32 OTA元数据本地缓存避免频繁NVS访问 - 优化前端JavaScript实现,改进Web Bluetooth写入策略 BREAKING CHANGE: BLE连接参数协商机制变更可能影响部分设备兼容性
This commit is contained in:
@@ -728,6 +728,11 @@ function updateSystemStatusUi(data) {
|
||||
mOtaCache.className = 'v';
|
||||
}
|
||||
}
|
||||
// 共享进度条按当前连接模式归属:BLE 模式下由 ble_ota.js 的设备 ACK
|
||||
// 驱动包数、阶段和完成状态,HTTP 状态轮询不能覆盖这套实时进度。
|
||||
if (window.connectionMode === 'ble') {
|
||||
return;
|
||||
}
|
||||
// relay state - skip GD32 relay UI when ESP32 OTA is active
|
||||
if (currentOtaTarget === 'esp32' && esp32OtaPhase > 0) {
|
||||
return;
|
||||
|
||||
+8
-23
@@ -10,7 +10,7 @@
|
||||
version: 0x0004,
|
||||
};
|
||||
|
||||
const UI_BUILD = '2026-06-15-ble-merged-v2';
|
||||
const UI_BUILD = '2026-07-12-ble-reorder-v6';
|
||||
const BLE_OTA_NOTIFY_ACK = 0xA1;
|
||||
const BLE_OTA_NOTIFY_FINISH = 0xB1;
|
||||
const BLE_OTA_NOTIFY_SHA256 = 0xC1;
|
||||
@@ -1228,17 +1228,16 @@
|
||||
state.activePacketTotal = packetTotal;
|
||||
els.sentCount.textContent = '0';
|
||||
setProgress(0, `[${cfg.label}] 等待设备确认 0/${packetTotal} 包`);
|
||||
bleLog('OTA', `开始写入 DATA_IN: target=${cfg.label}, file=${file.name}, total=${totalSize}, packets=${packetTotal}, payload=${payloadSize}, windowAck=${BLE_OTA_ACK_WINDOW}`);
|
||||
bleLog('OTA', `开始写入 DATA_IN: build=${UI_BUILD}, target=${cfg.label}, file=${file.name}, total=${totalSize}, packets=${packetTotal}, payload=${payloadSize}, windowAck=${BLE_OTA_ACK_WINDOW}`);
|
||||
|
||||
const MAX_WINDOW_RETRIES = 3;
|
||||
let bleWriteTs = 0; // 上一次 BLE 写入时间戳,用于节流
|
||||
const BLE_WRITE_INTERVAL = 2; // 最小写入间隔 (ms),避免 BLE 栈过载
|
||||
for (let windowStart = 0; windowStart < packetTotal; windowStart += BLE_OTA_ACK_WINDOW) {
|
||||
if (state.abortRequested) throw new Error('用户中止');
|
||||
|
||||
const endSeq = Math.min(windowStart + BLE_OTA_ACK_WINDOW, packetTotal) - 1;
|
||||
|
||||
// 流水线发送:不逐包 await,减少 BLE 栈 per-packet 开销
|
||||
// Windows Web Bluetooth 同一时刻只允许一个 GATT 操作。逐包等待仅保证
|
||||
// 提交顺序;实际吞吐由设备请求的高速 BLE 连接参数决定。
|
||||
// ★ 不再在循环内 setProgress:进度完全由设备 ACK 后的 state.lastAckBytes 驱动
|
||||
for (let seq = windowStart; seq <= endSeq; seq++) {
|
||||
// ★ 中止检查放在循环内:abortTask 可能在本窗口发送过程中触发
|
||||
@@ -1249,13 +1248,6 @@
|
||||
const payload = fileBytes.slice(start, end);
|
||||
const packet = buildPacket(seq, totalSize, payload, cfg.packetCmd);
|
||||
|
||||
// 节流:避免过快地向 BLE 栈提交写入
|
||||
const now = performance.now();
|
||||
const gap = now - bleWriteTs;
|
||||
if (gap < BLE_WRITE_INTERVAL) {
|
||||
await sleep(BLE_WRITE_INTERVAL - gap);
|
||||
}
|
||||
|
||||
// ★ seq=0 必须用 writeValueWithResponse(带响应):
|
||||
// seq=0 触发设备端 esp_ota_begin()(flash 擦除 + OTA 会话创建),
|
||||
// 若用 writeValueWithoutResponse 在 BLE 链路层丢失,
|
||||
@@ -1271,9 +1263,8 @@
|
||||
// seq=0 写入后等待设备完成 esp_ota_begin()(flash 擦除较耗时)
|
||||
await sleep(200);
|
||||
} else {
|
||||
state.dataInChar.writeValueWithoutResponse(packet); // fire-and-forget
|
||||
await state.dataInChar.writeValueWithoutResponse(packet);
|
||||
}
|
||||
bleWriteTs = performance.now();
|
||||
}
|
||||
|
||||
state.ackGeneration++;
|
||||
@@ -1296,8 +1287,7 @@
|
||||
|
||||
// ★ 进度检测:如果重试后字节数没有增长,说明续传无效
|
||||
if (retryCount > 0 && state.lastAckBytes <= prevAckBytes) {
|
||||
bleLog('OTA', `重试无进展: ackBytes=${state.lastAckBytes} 未增长 (前次=${prevAckBytes}),放弃`, 'E');
|
||||
break;
|
||||
bleLog('OTA', `上次重试暂未推进: ackBytes=${state.lastAckBytes} (前次=${prevAckBytes}),继续按缺口重试`, 'W');
|
||||
}
|
||||
prevAckBytes = state.lastAckBytes;
|
||||
|
||||
@@ -1322,10 +1312,6 @@
|
||||
const end = Math.min(start + payloadSize, totalSize);
|
||||
const payload = fileBytes.slice(start, end);
|
||||
const packet = buildPacket(seq, totalSize, payload, cfg.packetCmd);
|
||||
// 流水线发送 + 节流
|
||||
const now = performance.now();
|
||||
const gap = now - bleWriteTs;
|
||||
if (gap < BLE_WRITE_INTERVAL) await sleep(BLE_WRITE_INTERVAL - gap);
|
||||
// ★ 重试路径中 seq=0 也必须用 writeValueWithResponse
|
||||
if (seq === 0) {
|
||||
try {
|
||||
@@ -1336,9 +1322,8 @@
|
||||
}
|
||||
await sleep(200);
|
||||
} else {
|
||||
state.dataInChar.writeValueWithoutResponse(packet);
|
||||
await state.dataInChar.writeValueWithoutResponse(packet);
|
||||
}
|
||||
bleWriteTs = performance.now();
|
||||
}
|
||||
|
||||
state.ackGeneration++;
|
||||
@@ -1352,7 +1337,7 @@
|
||||
|
||||
if (state.lastAckBytes < expectedBytes) {
|
||||
const missing = expectedBytes - state.lastAckBytes;
|
||||
throw new Error(`BLE 丢包检测: seq=${endSeq} 期望${expectedBytes}字节 实际${state.lastAckBytes}字节 丢失${missing}字节(${(missing / payloadSize).toFixed(1)}包) 已重试${MAX_WINDOW_RETRIES}次`);
|
||||
throw new Error(`BLE 丢包检测: seq=${endSeq} 期望${expectedBytes}字节 实际${state.lastAckBytes}字节 丢失${missing}字节(${(missing / payloadSize).toFixed(1)}包) 已重试${retryCount}次`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -427,7 +427,7 @@
|
||||
<span id="toastMsg">操作成功</span>
|
||||
</div>
|
||||
|
||||
<script src="app.js?v=20260705b"></script>
|
||||
<script src="app.js?v=20260712a"></script>
|
||||
<script>
|
||||
window.connectionMode = window.connectionMode || 'wifi';
|
||||
window.flashTarget = window.flashTarget || 'esp32';
|
||||
@@ -670,6 +670,6 @@ window.sharedFirmwareFiles = window.sharedFirmwareFiles || {
|
||||
applySharedModeUi();
|
||||
})();
|
||||
</script>
|
||||
<script src="ble_ota.js?v=20260626a"></script>
|
||||
<script src="ble_ota.js?v=20260712d"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user