feat(main): 添加启动模块自检与汇总功能

- 新增 SystemBoot 类用于跟踪各模块初始化状态,提供位图标记与报告功能
- 在 main.cpp 中重构初始化流程,按顺序初始化各模块并记录状态
- 添加 system_boot.cpp 模块文件到构建配置
- CAN反馈任务增加冷启动延迟,避免与GD32初始化窗口冲突

refactor(ota): 优化OTA协调器线程安全机制

- 修改 lock()/unlock() 方法返回take结果,确保mutex正确释放
- 修复多任务并发时mutex死锁问题,避免vTaskPriorityDisinherit断言
- 统一finalize失败后的状态复位逻辑,确保协调器回到IDLE状态
- 优化忙状态超时检测与自愈机制

perf(boot): 优化冷启动时序与资源管理

- 简化README中冷启动优化说明,聚焦核心逻辑
- 优化BOD阈值设置为LVL5,改进电源稳定性
- 增加BLE OTA工作线程优先级,减少丢包情况
- 优化堆告警节流机制,在OTA期间降低告警频率

fix(relay): 改进GD32应用存活检测逻辑

- 调整GD32 APP存活验证时序,增加会话建立验证
- 修复HELLO建立session后未验证role的问题
- 优化FINISH后等待APP启动的静默时间与重试机制
- 统一GD32探测逻辑到OTA中继任务

refactor(server): 优化HTTP服务端内存与性能

- 使用静态缓冲区替代动态分配,减少堆碎片化风险
- 优化CAN批量发送期间暂停反馈任务的机制
- 调整BLE连接参数请求逻辑,提升传输性能
- 改进错误处理流程,避免堆紧张时的额外分配

bump(version): 更新固件版本至0.3.50

- 更新版本号从0.3.49到0.3.50
- 更新编译时间戳与Git哈希值
This commit is contained in:
小龙虾
2026-07-17 02:02:36 +08:00
parent 6364eacfb1
commit 1b10f89d8c
39 changed files with 851 additions and 331 deletions
+16 -27
View File
@@ -34,6 +34,7 @@
#include "monitor/can_feedback_task.h"
#include "monitor/task_stack_check.h"
#include "ble_ota.h"
#include "system_boot.h"
static size_t parseHexToBytes(const char *hex, uint8_t *out, size_t max_out)
{
@@ -641,7 +642,7 @@ esp_err_t can_feedback_handler(httpd_req_t *req)
valid[i] = getLatestCanFeedback(i, &results[i]);
}
/* ★ 修复:改用静态缓冲区,避免每次请求 malloc 8KB 加剧堆碎片化 */
/* 静态响应缓冲,减少频繁大块堆分配 */
static char s_feedback_json[8192];
char *resp = s_feedback_json;
memset(resp, 0, sizeof(s_feedback_json));
@@ -837,17 +838,15 @@ esp_err_t can_send_handler(httpd_req_t *req)
* @brief POST /can/send_batch — 同通道连续发送多帧,单次 HTTP 完成。
*
* JSON: { "ch": 0, "frames": [ { "id": n, "dlc": n, "data": "HEX" }, ... ] }
* 返回: { "status":"ok", "results":[ { "status":"ok"|"error", ... }, ... ] }
* 帧间 5ms;暂停 CAN 反馈,避免与批量 SPI 竞态导致偶发崩溃。
* 返回: { "status":"ok", "results":[ ... ], 可选 skipped/abort_* }
* 帧间间隔 kInterFrameDelayMs;批量期间暂停 CAN 反馈任务。
*/
esp_err_t can_send_batch_handler(httpd_req_t *req)
{
http_set_cors_headers(req);
/* 单次尽量装下整批,避免前端分块导致“一阵一阵” */
constexpr int kMaxBody = 49152;
constexpr int kMaxFrames = 128;
/* 成功路径通常 <20ms;失败路径不再拖到数百 ms */
constexpr uint32_t kPerFrameTimeoutMs = 80U;
constexpr uint32_t kInterFrameDelayMs = 5U;
@@ -906,7 +905,7 @@ esp_err_t can_send_batch_handler(httpd_req_t *req)
cJSON_AddStringToObject(out_root, "status", "ok");
cJSON_AddItemToObject(out_root, "results", results);
/* 暂停 0xC1 反馈抢 SPI,降低 worker 竞态 / 长时间占线导致的 WDT 风险 */
/* 批量发送期间暂停 CAN 反馈,独占 SPI 业务路径 */
pauseCanFeedbackTask(true);
auto fill_err = [](cJSON *item, int code, uint8_t gd32_err) {
@@ -918,11 +917,6 @@ esp_err_t can_send_batch_handler(httpd_req_t *req)
cJSON_AddNumberToObject(item, "gd32_err", gd32_err);
};
/* 中途致命错误后不再逐帧 SPI 重试(否则每帧再等一整段超时) */
bool abort_rest = false;
int abort_code = -2;
uint8_t abort_gd32 = 0U;
for (int i = 0; i < frame_count; i++) {
(void)esp_task_wdt_reset();
@@ -932,11 +926,6 @@ esp_err_t can_send_batch_handler(httpd_req_t *req)
}
cJSON_AddItemToArray(results, item);
if (abort_rest) {
fill_err(item, abort_code, abort_gd32);
continue; /* 无 SPI、无帧间延时 */
}
cJSON *fr = cJSON_GetArrayItem(frames, i);
if (!cJSON_IsObject(fr)) {
fill_err(item, -1, 0);
@@ -987,25 +976,22 @@ esp_err_t can_send_batch_handler(httpd_req_t *req)
cJSON_AddStringToObject(item, "recv", resp_hex);
} else {
fill_err(item, resp_len, gd32_err);
/* SPI 失败 / BUS_OFF / TEC 过高:后面不再逐帧 SPI,也不再为每帧分配 cJSON */
/* SPI 失败 / BUS_OFF / TEC 过高:中止剩余帧,由 skipped 字段告知前端 */
const bool fatal_spi = (resp_len == -2);
const bool fatal_bus = (resp_len == -3) &&
(gd32_err == 0xE2U || gd32_err == 0xE3U);
if (fatal_spi || fatal_bus) {
abort_rest = true;
abort_code = resp_len;
abort_gd32 = gd32_err;
const int skipped = frame_count - i - 1;
if (skipped > 0) {
cJSON_AddNumberToObject(out_root, "skipped", skipped);
cJSON_AddNumberToObject(out_root, "abort_code", abort_code);
cJSON_AddNumberToObject(out_root, "abort_gd32_err", abort_gd32);
cJSON_AddNumberToObject(out_root, "abort_code", resp_len);
cJSON_AddNumberToObject(out_root, "abort_gd32_err", gd32_err);
}
break; /* 不再为剩余帧 CreateObject,避免堆被打穿 */
break;
}
}
if (i + 1 < frame_count && !abort_rest) {
if (i + 1 < frame_count) {
vTaskDelay(pdMS_TO_TICKS(kInterFrameDelayMs));
}
}
@@ -1018,9 +1004,8 @@ esp_err_t can_send_batch_handler(httpd_req_t *req)
cJSON_Delete(out_root);
out_root = nullptr;
if (!printed) {
/* 堆已紧时勿再走大段错误路径分配;给前端最小 JSON */
static const char kOom[] =
"{\"status\":\"error\",\"code\":\"json_print\",\"message\":\"JSON输出失败(堆不足)\"}";
"{\"status\":\"error\",\"code\":\"json_print\",\"message\":\"JSON输出失败\"}";
httpd_resp_set_status(req, "500 Internal Server Error");
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, kOom, HTTPD_RESP_USE_STRLEN);
@@ -1104,7 +1089,8 @@ esp_err_t spi_send_handler(httpd_req_t *req)
static constexpr uint32_t INTERNAL_HEAP_CAPS = MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT;
/* 状态 JSON 用静态缓冲:OTA 后 largest free 常 <2KB,calloc(8KB) 会 500 "no memory"。 */
static constexpr size_t SYSTEM_STATUS_JSON_CAP = 6144;
/* 含 boot 模块列表与 CPU 任务;OTA 后 largest free 常偏紧,仍用静态缓冲避免 calloc */
static constexpr size_t SYSTEM_STATUS_JSON_CAP = 7168;
static char s_status_json[SYSTEM_STATUS_JSON_CAP];
static SemaphoreHandle_t s_status_json_mux = nullptr;
static portMUX_TYPE s_status_json_init_mux = portMUX_INITIALIZER_UNLOCKED;
@@ -1344,6 +1330,9 @@ esp_err_t system_status_handler(httpd_req_t *req)
(unsigned long)ls.stats.max_resp_latency_us);
}
/* 启动自检位图(与串口 BOOT report 同源) */
len = SystemBoot::appendStatusJson(resp, SYSTEM_STATUS_JSON_CAP, len);
// 前导逗号与前一块分隔;之前因 append_cpu_status_json 尾随逗号叠加
// 形成 "],," 导致前端 JSON.parse 报 "Expected double-quoted property name"
len = append_jsonf(resp, SYSTEM_STATUS_JSON_CAP, len, ",\"task_stack\": {");