fix(ota): 修复OtaCoordinator互斥锁死锁和SPI appPing首字节丢失

- OtaCoordinator lock/unlock: 移除共享 m_locked 标志,改为传参传递 take
  结果,消除多任务并发 lock() 超时覆盖持锁状态导致 mutex 永久死锁的问题
- SPI appPing: 发送帧填充至 16 字节防止首字节丢失,增大轮询间隔至 10ms,
  增大接收缓冲区避免字节偏移解析失败
- SPI init: 先创建 mutex 再使用 LockGuard,消除竞态窗口
- 压制 WiFi 框架 INFO 日志 (pp/net80211/wifi/wifi_init),减少启动打印
- 降级 7 条调试日志为 DBG 级别(UART0 初始化、冷启动缓存清理、互斥锁/重连
  任务/回调/netif 创建)
- GD32 版本号读取增加 5 次重试上限和 OTA 繁忙时跳过逻辑
- 移除 forwardFirmware 中已过时注释
This commit is contained in:
小龙虾
2026-06-17 11:52:26 +08:00
parent 98b5cc1ba1
commit 2586582e57
8 changed files with 214 additions and 178 deletions
+22 -27
View File
@@ -507,8 +507,8 @@ function updateSystemStatusUi(data) {
document.getElementById('pillRelayTxt').textContent = relayText;
var pr = document.getElementById('pillRelay');
if (rs === 255) pr.className = 'pill offline';
else if (rs > 0 && rs < 17) pr.className = 'pill processing';
else if (rs === 17 || rs === 18) pr.className = 'pill online';
else if (rs > 0 && rs < 5) pr.className = 'pill processing';
else if (rs === 5) pr.className = 'pill online';
else pr.className = 'pill idle';
// OTA cache progress (GD32 upload to ESP32 flash)
var total = ota.total_size || 0;
@@ -524,44 +524,39 @@ function updateSystemStatusUi(data) {
var relayTotal = ota.relay_total_packets || 0;
var relayProgress = relayTotal > 0 ? Math.min(100, Math.round(relaySeq * 100 / relayTotal)) : 0;
var gd32AppAlive = ota.gd32_app_alive;
if (rs > 0 && rs <= 8) {
// Authentication or size negotiation phase
// ★ 修复:后端实际状态机枚举 (main/relay/ota_relay.h):
// 0=RELAY_IDLE, 1=RELAY_HANDSHAKING, 2=RELAY_SIZE_NEG,
// 3=RELAY_TRANSFERRING, 4=RELAY_FINISHING, 5=RELAY_DONE, 0xFF=RELAY_ERROR
// 旧版 1-18 范围判断已与新状态机不匹配,进度条永远停在认证中阶段。
if (rs === 1) {
// HANDSHAKING: 握手 + 挑战认证
setActivePhase('gd32Relay');
if (rs <= 6) {
setProgress(0, '认证中: ' + (relayStateDetailMap[rs] || ''));
} else {
setProgress(0, '协商固件大小...');
}
} else if (rs >= 9 && rs <= 13) {
// Active data forwarding
setProgress(0, '握手与认证中...');
} else if (rs === 2) {
// SIZE_NEG: 固件大小协商
setActivePhase('gd32Relay');
setProgress(0, '协商固件大小...');
} else if (rs === 3) {
// TRANSFERRING: 数据包传输
setActivePhase('gd32Relay');
setProgress(relayProgress, '已转发 ' + relaySeq + ' / ' + relayTotal + ' 包');
} else if (rs >= 14 && rs <= 15) {
// Transfer complete
setActivePhase('gd32Relay');
setProgress(100, '数据转发完成 (' + relayTotal + ' 包)');
} else if (rs === 16) {
// OTA end sent
setActivePhase('gd32Relay');
setProgress(100, 'OTA 刷写中...');
} else if (rs === 17) {
// OTA complete
setActivePhase('gd32Relay');
setProgress(100, 'OTA 完成,等待 APP 启动...');
} else if (rs === 18) {
// APP verify phase
} else if (rs === 4) {
// FINISHING: 结束OTA + APP存活验证
setActivePhase('gd32Relay');
if (gd32AppAlive) {
markPhaseDone('gd32Relay');
setProgress(100, 'GD32 刷写完成');
// Mark status bar as success
var sb = document.getElementById('stageStatusBar');
var st = document.getElementById('stageStatusText');
if (sb) sb.className = 'stage-status-bar ok';
if (st) st.textContent = 'GD32 刷写完成';
} else {
setProgress(100, '转发完成,验证 APP 存活...');
setProgress(100, '收尾中,等待 APP 启动...');
}
} else if (rs === 5) {
// DONE: 中继完成(metaClear 已清空,relaySeq/relayTotal 会被 reset 为 0)
setActivePhase('gd32Relay');
setProgress(100, 'OTA 完成');
} else if (total > 0 && stored < total && !isUploading) {
// GD32 upload to cache in progress (handled by upload progress if uploading)
setActivePhase('gd32Upload');