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
+81 -74
View File
@@ -1,17 +1,27 @@
/*
* ESP32-C3FH4 OTA 中继 - 程序入口
* FreeRTOS 任务:健康监控 / OTA Relay / WiFi Server / BLE GATT / CAN 反馈
*
* 使用各模块单例初始化并创建任务;入口只做装配,不实现协议。
* 初始化顺序(每位成功后 SystemBoot::mark=1,末尾统一 report):
* 1. 冷启动延时
* 2. netif / event_loop / debug_log
* 3. wifi earlyInit
* 4. health_mon + task
* 5. cpu_stats(可选)
* 6. protocol / ota_coord
* 7. spi init + L0 本机自检 → mark spi;HELLO 对端轻检 → mark can_link(非关键)
* 8. ota_relay / wifi_ota / ble_ota 任务
* 9. can_fb 任务(依赖 spi L0)
* 10. ota_mark_valid(可选)
* 11. SystemBoot::report()
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_system.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "esp_ota_ops.h"
#include "system_boot.h"
#include "monitor/health_monitor.h"
#include "monitor/cpu_stats.h"
#include "relay/ota_relay.h"
@@ -25,31 +35,21 @@
#include "ota/esp32_ota_core.h"
#include "monitor/can_feedback_task.h"
/**
* @brief 创建关键任务;失败则打 ERR 并 ESP_ERROR_CHECK 触发重启。
*/
static void createCriticalTask(TaskFunction_t entry, const char *name,
/** @brief 创建关键任务;成功返回 true。 */
static bool createCriticalTask(TaskFunction_t entry, const char *name,
uint32_t stack, UBaseType_t prio)
{
BaseType_t xRet = xTaskCreatePinnedToCore(
entry, name, stack, NULL, prio, NULL, 0);
if (xRet != pdPASS) {
DEBUG_ERR("MAIN", "创建关键任务 %s 失败", name);
ESP_ERROR_CHECK(ESP_FAIL);
}
DEBUG_INFO("MAIN", "任务已创建: %s (prio=%u stack=%lu)",
name, (unsigned)prio, (unsigned long)stack);
return xTaskCreatePinnedToCore(entry, name, stack, NULL, prio, NULL, 0) == pdPASS;
}
/**
* @brief 程序主入口:初始化组件并创建 FreeRTOS 任务。
* @brief 程序主入口:按模块初始化并汇总结果。
*/
extern "C" void app_main(void)
{
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
SystemBoot::reset();
/* 上电/欠压复位:先等电源与 GD32 侧负载稳定,再进 WiFi/SPI 重活 */
/* 上电/欠压复位后短延时再初始化子系统 */
{
esp_reset_reason_t rr = esp_reset_reason();
if (rr == ESP_RST_POWERON || rr == ESP_RST_BROWNOUT) {
@@ -59,79 +59,86 @@ extern "C" void app_main(void)
Esp32OtaCore::initMutex();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
if (esp_netif_init() == ESP_OK) {
SystemBoot::mark(BOOT_NETIF);
}
if (esp_event_loop_create_default() == ESP_OK) {
SystemBoot::mark(BOOT_EVENT_LOOP);
}
DebugLog::instance().init();
SystemBoot::mark(BOOT_DEBUG_LOG);
/* earlyInit:true=已启动 STA 连接流程;false=将走 AP(或 CONFIG 直选 AP) */
/* earlyInit:true=已启动 STA 连接流程;false=将走 AP */
const bool wifi_early_started_sta = WifiOta::instance().earlyInit();
SystemBoot::mark(BOOT_WIFI_EARLY);
HealthMonitor::instance().init();
DEBUG_INFO("MAIN", "健康监控状态已初始化");
DEBUG_INFO("MAIN", "========================================");
DEBUG_INFO("MAIN", "ESP32-C3 OTA Relay %s 启动", FW_VERSION);
DEBUG_INFO("MAIN", "Chip: %s, Rev %d", CONFIG_IDF_TARGET, chip_info.revision);
DEBUG_INFO("MAIN", "Free heap: %lu bytes", (unsigned long)esp_get_free_heap_size());
DEBUG_INFO("MAIN", "CPU Freq: %d MHz", CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ);
DEBUG_INFO("MAIN", "WiFi early: %s(最终 STA/AP 由 wifi_ota 任务确认)",
wifi_early_started_sta ? "STA 连接中" : "非 STA 优先/将切 AP");
DEBUG_INFO("MAIN", "========================================");
SystemBoot::mark(BOOT_HEALTH_MON);
/* 健康监控:温度/RSSI;缺失则运维盲区 → 关键 */
createCriticalTask(HealthMonitor::taskEntry, "health_mon",
TASK_STACK_HEALTH_MON, configMAX_PRIORITIES - 4);
if (createCriticalTask(HealthMonitor::taskEntry, "health_mon",
TASK_STACK_HEALTH_MON, configMAX_PRIORITIES - 4)) {
SystemBoot::mark(BOOT_HEALTH_TASK);
}
/* CPU 1Hz 采样:非致命 */
CpuStats::instance().start();
SystemBoot::mark(BOOT_CPU_STATS);
Protocol::instance().init();
DEBUG_INFO("MAIN", "协议层初始化完成");
SystemBoot::mark(BOOT_PROTOCOL);
OtaCoordinator::instance().init();
OtaCoordinator::instance().setBleCoexListener(&WifiOta::instance());
DEBUG_INFO("MAIN", "OTA 全局协调器已初始化");
SystemBoot::mark(BOOT_OTA_COORD);
/* SPI 是中继/CAN 反馈根基:失败则无法完成核心职责,按关键失败处理 */
bool spi_l0_ok = false;
{
esp_err_t ret = SpiMaster::instance().init();
if (ret != ESP_OK) {
DEBUG_ERR("MAIN", "SPI 总线初始化失败: %s", esp_err_to_name(ret));
ESP_ERROR_CHECK(ret);
}
DEBUG_INFO("MAIN", "SPI 总线初始化完成");
}
createCriticalTask(OtaRelay::taskEntry, "ota_relay",
TASK_STACK_OTA_RELAY, configMAX_PRIORITIES - 2);
createCriticalTask(WifiOta::taskEntry, "wifi_ota",
TASK_STACK_WIFI_OTA, configMAX_PRIORITIES - 3);
createCriticalTask(BleOta::taskEntry, "ble_ota",
TASK_STACK_BLE_OTA, configMAX_PRIORITIES - 3);
if (startCanFeedbackTask()) {
DEBUG_INFO("MAIN", "CAN 反馈轮询任务已创建");
} else {
DEBUG_WARN("MAIN", "CAN 反馈轮询任务创建失败(非致命,OTA 不受影响)");
}
/* 任务就绪后尽快标记 app 有效,避免后续探测拖太久导致回滚窗口拉长 */
{
esp_err_t ota_valid_err = esp_ota_mark_app_valid_cancel_rollback();
if (ota_valid_err == ESP_OK) {
DEBUG_INFO("MAIN", "OTA 回滚保护: 当前固件已标记有效");
} else {
DEBUG_WARN("MAIN", "OTA 回滚保护: 标记失败 (%s),可能非 OTA 启动",
esp_err_to_name(ota_valid_err));
if (ret == ESP_OK && SpiMaster::instance().selfTestL0()) {
SystemBoot::mark(BOOT_SPI);
spi_l0_ok = true;
/* 对端轻检:GD32 可能仍在启动延时,失败不拉垮关键启动 */
if (SpiMaster::instance().selfTestPeerL0()) {
SystemBoot::mark(BOOT_CAN_LINK);
}
}
}
DEBUG_INFO("MAIN", "========================================");
DEBUG_INFO("MAIN", "所有任务创建完成,OTA Relay 运行中");
DEBUG_INFO("MAIN", "GD32 版本探测由 ota_relay 任务负责(hello+probe)");
DEBUG_INFO("MAIN", "调试日志输出: UDP(%s:%d) + UART0(GPIO21/TX, 115200)",
DEBUG_LOG_IP, DEBUG_LOG_PORT);
DEBUG_INFO("MAIN", "========================================");
if (createCriticalTask(OtaRelay::taskEntry, "ota_relay",
TASK_STACK_OTA_RELAY, configMAX_PRIORITIES - 2)) {
SystemBoot::mark(BOOT_OTA_RELAY_TASK);
}
if (createCriticalTask(WifiOta::taskEntry, "wifi_ota",
TASK_STACK_WIFI_OTA, configMAX_PRIORITIES - 3)) {
SystemBoot::mark(BOOT_WIFI_TASK);
}
if (createCriticalTask(BleOta::taskEntry, "ble_ota",
TASK_STACK_BLE_OTA, configMAX_PRIORITIES - 3)) {
SystemBoot::mark(BOOT_BLE_TASK);
}
/* CAN 反馈依赖 SPI 本机 L0 */
if (spi_l0_ok && startCanFeedbackTask()) {
SystemBoot::mark(BOOT_CAN_FB_TASK);
}
if (esp_ota_mark_app_valid_cancel_rollback() == ESP_OK) {
SystemBoot::mark(BOOT_OTA_VALID);
}
DEBUG_INFO("MAIN", "ESP32-C3 OTA Relay %s, heap=%lu, wifi_early=%s",
FW_VERSION,
(unsigned long)esp_get_free_heap_size(),
wifi_early_started_sta ? "STA" : "AP/other");
const bool critical_ok = SystemBoot::report();
if (!critical_ok) {
DEBUG_ERR("MAIN", "critical init FAIL, restart in 3s");
vTaskDelay(pdMS_TO_TICKS(3000));
esp_restart();
}
}
+171
View File
@@ -0,0 +1,171 @@
/**
* @file system_boot.cpp
* @brief 启动模块位图实现。
*/
#include "system_boot.h"
#include "monitor/debug_log.h"
#include <stdio.h>
#include <string.h>
namespace {
struct BootEntry {
uint32_t bit;
const char *name;
bool critical;
};
static const BootEntry kEntries[] = {
{ BOOT_NETIF, "netif", true },
{ BOOT_EVENT_LOOP, "event_loop", true },
{ BOOT_DEBUG_LOG, "debug_log", true },
{ BOOT_WIFI_EARLY, "wifi_early", true },
{ BOOT_HEALTH_MON, "health_mon", true },
{ BOOT_HEALTH_TASK, "health_task", true },
{ BOOT_CPU_STATS, "cpu_stats", false },
{ BOOT_PROTOCOL, "protocol", true },
{ BOOT_OTA_COORD, "ota_coord", true },
{ BOOT_SPI, "spi", true },
{ BOOT_OTA_RELAY_TASK, "ota_relay_task", true },
{ BOOT_WIFI_TASK, "wifi_task", true },
{ BOOT_BLE_TASK, "ble_task", true },
{ BOOT_CAN_LINK, "can_link", false },
{ BOOT_CAN_FB_TASK, "can_fb_task", false },
{ BOOT_OTA_VALID, "ota_mark_valid", false },
};
static uint32_t s_mask = 0U;
static void evaluate(bool *critical_ok, bool *all_ok, char *fail_list, size_t fail_cap)
{
*critical_ok = true;
*all_ok = true;
if (fail_list && fail_cap > 0U) {
fail_list[0] = '\0';
}
size_t fail_len = 0U;
for (size_t i = 0; i < sizeof(kEntries) / sizeof(kEntries[0]); ++i) {
const BootEntry &e = kEntries[i];
const bool ok = (s_mask & e.bit) != 0U;
if (ok) {
continue;
}
*all_ok = false;
if (e.critical) {
*critical_ok = false;
}
if (!fail_list || fail_cap == 0U) {
continue;
}
if (fail_len + 2U < fail_cap) {
if (fail_len > 0U) {
fail_list[fail_len++] = ',';
}
const char *p = e.name;
while (*p != '\0' && fail_len + 1U < fail_cap) {
fail_list[fail_len++] = *p++;
}
fail_list[fail_len] = '\0';
}
}
}
} // namespace
void SystemBoot::reset()
{
s_mask = 0U;
}
void SystemBoot::mark(SystemBootBit bit)
{
s_mask |= static_cast<uint32_t>(bit);
}
bool SystemBoot::report()
{
bool critical_ok = true;
bool all_ok = true;
char fail_list[192] = {0};
evaluate(&critical_ok, &all_ok, fail_list, sizeof(fail_list));
if (critical_ok && all_ok) {
DEBUG_INFO("BOOT", "init: OK (all modules)");
} else if (critical_ok) {
DEBUG_WARN("BOOT", "init: OK (critical), skipped/failed: %s",
fail_list[0] != '\0' ? fail_list : "?");
} else {
DEBUG_ERR("BOOT", "init: FAIL, modules: %s",
fail_list[0] != '\0' ? fail_list : "?");
}
return critical_ok;
}
int SystemBoot::appendStatusJson(char *buf, size_t cap, int offset)
{
if (!buf || cap == 0 || offset < 0 || (size_t)offset >= cap) {
return offset;
}
bool critical_ok = true;
bool all_ok = true;
char fail_list[192] = {0};
evaluate(&critical_ok, &all_ok, fail_list, sizeof(fail_list));
const char *summary;
if (critical_ok && all_ok) {
summary = "OK (all modules)";
} else if (critical_ok) {
summary = "OK (critical)";
} else {
summary = "FAIL";
}
int n = snprintf(buf + offset, cap - (size_t)offset,
",\"boot\":{\"critical_ok\":%s,\"all_ok\":%s,\"mask\":%lu,"
"\"summary\":\"%s\",\"failed\":\"%s\",\"modules\":[",
critical_ok ? "true" : "false",
all_ok ? "true" : "false",
(unsigned long)s_mask,
summary,
fail_list);
if (n < 0) {
return offset;
}
if (offset + n >= (int)cap) {
buf[cap - 1] = '\0';
return (int)(cap - 1);
}
offset += n;
for (size_t i = 0; i < sizeof(kEntries) / sizeof(kEntries[0]); ++i) {
const BootEntry &e = kEntries[i];
const bool ok = (s_mask & e.bit) != 0U;
n = snprintf(buf + offset, cap - (size_t)offset,
"%s{\"name\":\"%s\",\"ok\":%s,\"critical\":%s}",
i == 0 ? "" : ",",
e.name,
ok ? "true" : "false",
e.critical ? "true" : "false");
if (n < 0) {
return offset;
}
if (offset + n >= (int)cap) {
buf[cap - 1] = '\0';
return (int)(cap - 1);
}
offset += n;
}
n = snprintf(buf + offset, cap - (size_t)offset, "]}");
if (n < 0) {
return offset;
}
if (offset + n >= (int)cap) {
buf[cap - 1] = '\0';
return (int)(cap - 1);
}
return offset + n;
}
+50
View File
@@ -0,0 +1,50 @@
/**
* @file system_boot.h
* @brief ESP32 启动阶段模块初始化位图与汇总报告。
*
* 每个模块 init/建任务成功后 mark(bit)=1,启动末尾 report() 打印整体结果。
* /system/status 通过 appendStatusJson() 把同一份结果暴露给网页。
*/
#ifndef SYSTEM_BOOT_H
#define SYSTEM_BOOT_H
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
/** 启动模块位(成功置 1) */
enum SystemBootBit : uint32_t {
BOOT_NETIF = 1u << 0,
BOOT_EVENT_LOOP = 1u << 1,
BOOT_DEBUG_LOG = 1u << 2,
BOOT_WIFI_EARLY = 1u << 3,
BOOT_HEALTH_MON = 1u << 4,
BOOT_HEALTH_TASK = 1u << 5,
BOOT_CPU_STATS = 1u << 6,
BOOT_PROTOCOL = 1u << 7,
BOOT_OTA_COORD = 1u << 8,
BOOT_SPI = 1u << 9, ///< SPI 主机 L0(本机就绪)
BOOT_OTA_RELAY_TASK = 1u << 10,
BOOT_WIFI_TASK = 1u << 11,
BOOT_BLE_TASK = 1u << 12,
BOOT_CAN_LINK = 1u << 13, ///< SPI 对端 L0(HELLO 可达 GD32,非关键)
BOOT_CAN_FB_TASK = 1u << 14, ///< CAN 反馈任务(依赖 SPI L0)
BOOT_OTA_VALID = 1u << 15,
};
class SystemBoot {
public:
static void reset();
static void mark(SystemBootBit bit);
/** 串口汇总;返回关键模块是否全部成功 */
static bool report();
/**
* @brief 追加 boot JSON 字段(含前导逗号)到 status 缓冲。
* @return 新的写入偏移
*/
static int appendStatusJson(char *buf, size_t cap, int offset);
};
#endif /* SYSTEM_BOOT_H */