feat(ota): 会话 epoch/通道标识 + BLE 中止命令 + NVS 线程安全

- 新增 ota_channel_t(WiFi/BLE) + m_otaEpoch 会话代次,
  abortCurrentIfEpoch 防止同类型重启后旧 handler 误清新会话
- 同通道再次发起 OTA 时自动中止旧会话并重开
- 统一 prepareGd32Storage(validate+begin+erase+meta),
  消除 BLE/WiFi 两路径隐式差异
- OtaRelay 拆分子步骤(doRelayPackets/doFinishAndVerify),
  状态成员私有化消除数据竞争
- Gd32FlashStore NVS 读写加 mutex,metaUpdate 原子化
- BLE OTA 新增 0xFB 中止命令(保持连接)+ 0x20 ABORTED 处理
- HTTP handler 改 per-request buffer 支持并发
- 修复 CRITICAL 状态 RSSI 恢复无法降级的缺陷
- main.cpp 关键任务失败触发看门狗重启
- style.css 增强动画 + prefers-reduced-motion 兼容
This commit is contained in:
小龙虾
2026-06-28 04:32:18 +08:00
parent 6ef97307c3
commit a93dba60d2
41 changed files with 3142 additions and 6349 deletions
+35 -5
View File
@@ -1,4 +1,4 @@
/*
/*
* debug_log.cpp - 调试日志输出实现
*
* 日志会同时写入三处:
@@ -120,6 +120,8 @@ char* DebugLog::ringDrainJson(int *out_len)
int count = ringCount();
int capacity = count * (LOG_RING_SIZE * 2) + 16;
if (capacity < 16) capacity = 16;
// 当 count 较大(如 64)时 capacity 可能达 32KB+,单次 malloc 有风险。
// 当前为简化实现保留单次 malloc;若后续日志量增大,可改为按 8KB 分块 realloc。
char *buf = (char*)malloc(capacity);
if (!buf) return NULL;
@@ -130,10 +132,22 @@ char* DebugLog::ringDrainJson(int *out_len)
int entryLen = 0;
while (ringPopOldest(entry, sizeof(entry), &entryLen) && entryLen > 0) {
if (!first) buf[pos++] = ',';
// 逗号写入前做边界检查,预留 4 字节给 ']' + '\0' 及末尾余量
if (!first) {
if (pos + 1 >= capacity - 4) break;
buf[pos++] = ',';
}
first = false;
buf[pos++] = '"';
// 单条日志转义后超过 LOG_RING_SIZE * 2 时截断,标记 "..." 后缀
int content_start = pos;
bool truncated = false;
for (int i = 0; i < entryLen && pos < capacity - 4; i++) {
if (pos - content_start >= LOG_RING_SIZE * 2) {
truncated = true;
break;
}
char c = entry[i];
if (c == '"') { buf[pos++] = '\\'; buf[pos++] = '"'; }
else if (c == '\\') { buf[pos++] = '\\'; buf[pos++] = '\\'; }
@@ -142,6 +156,14 @@ char* DebugLog::ringDrainJson(int *out_len)
else if (c == '\t') { buf[pos++] = '\\'; buf[pos++] = 't'; }
else if ((unsigned char)c >= 0x20) { buf[pos++] = c; }
}
if (truncated) {
// 确保 "..." + '"' + ']' + '\0' (共 6 字节) 有空间才写入标记
if (pos + 6 <= capacity) {
buf[pos++] = '.';
buf[pos++] = '.';
buf[pos++] = '.';
}
}
buf[pos++] = '"';
}
buf[pos++] = ']';
@@ -216,6 +238,8 @@ esp_err_t DebugLog::uartInit()
}
m_uartInitialized = true;
// 标记 UART 真正可用,send() 依据此标志决定是否调用 uart_write_bytes
m_uartAvailable = true;
ESP_LOGD(TAG, "UART0 调试输出已初始化 (TX=GPIO%d, RX=GPIO%d, %d baud)",
UART_DEBUG_TX_GPIO, UART_DEBUG_RX_GPIO, UART_DEBUG_BAUD_RATE);
@@ -255,7 +279,13 @@ esp_err_t DebugLog::init()
return ESP_OK;
}
uartInit();
// 检查 uartInit 返回值;失败时仍让 ring buffer 模式工作,
// 但记录"UART 不可用"日志,并在 send() 中跳过 uart_write_bytes 调用。
esp_err_t uart_ret = uartInit();
if (uart_ret != ESP_OK) {
ESP_LOGW(TAG, "UART 不可用,仅 ring buffer 模式 (err=%s)", esp_err_to_name(uart_ret));
m_uartAvailable = false;
}
m_logRingMutex = xSemaphoreCreateMutex();
@@ -302,8 +332,8 @@ esp_err_t DebugLog::send(const uint8_t *data, size_t len)
esp_err_t udp_ret = ESP_FAIL;
esp_err_t uart_ret = ESP_FAIL;
// UART 通道
if (m_uartInitialized) {
// UART 通道(UART 不可用时跳过 uart_write_bytes 调用)
if (m_uartAvailable) {
uart_ret = uartSend(data, len);
}