Files
ESP32_COMM/main/monitor/health_monitor.cpp
T
小龙虾 76782349a9 添加了SPI链接V2支持文件,重构OTA协调器的存储准备流程,
统一WiFi/BLE传输路径的收尾处理逻辑。
feat(ota): 优化OTA性能并增强系统监控功能

- 修改RSSI警告阈值从-75dBm调整为-80dBm,更新相关文档说明
- 增加HTTP服务器任务栈大小至10240字节,解决栈溢出问题
- 将OTA块大小从4096字节降至2048字节以降低峰值堆内存占用
- 实现按需擦除机制替代预擦除,在GD32 OTA中按扇区动态擦除
- 添加任务栈水位主动巡检功能,监控栈使用情况并告警
- 集成FreeRTOS栈溢出钩子,提供栈溢出检测和告警
- 优化ESP32 OTA实现使用顺序写入模式,消除长时间擦除阻塞
- 改进堆内存不足告警机制,添加限流避免刷屏
- 移除不必要的调试日志输出,精简健康监控模块
2026-07-13 03:12:21 +08:00

429 lines
15 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* health_monitor.cpp - 系统健康监控
* 包含:健康状态管理 + WiFi RSSI 监控 + 温度监控
*
* 所有功能封装为 HealthMonitor 类方法。
* 所有对 m_healthState / m_lastTemperature / m_lastWifiRssi 的访问
* 均通过 m_mutex 保护,消除跨任务竞态。
*/
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_wifi.h"
#include "esp_timer.h"
#include "esp_err.h"
#include "esp_heap_caps.h"
#include "esp_system.h"
#include "driver/temperature_sensor.h"
#include "health_monitor.h"
#include "debug_log.h"
#include "task_stack_check.h"
#include "stack_overflow_hook.h"
// ============================================================
// 栈水位主动巡检
// ============================================================
// 告警阈值:剩余高水位 < 栈大小 20% 即告警(复用 /system/status 的任务表)。
// 去重:每个任务只在"水位由安全跌入危险"时告警一次,恢复后再跌入才再次告警,
// 避免 5 秒巡检刷屏。health_mon 任务从不被 OTA 暂停,DEBUG_ERR 经 UDP/UART
// 直发,不受 OTA 门控抑制。
static constexpr float STACK_WARN_RATIO = 0.20f; ///< 剩余水位告警阈值(栈大小比例)
static constexpr size_t STACK_TASK_MAX = 8; ///< 受监控任务上限
static bool s_stackWarned[STACK_TASK_MAX] = {}; ///< 各任务是否已告警(去重)
// ============================================================
// 单例
// ============================================================
/**
* @brief 获取 HealthMonitor 单例实例。
* @return HealthMonitor 引用。
*/
HealthMonitor& HealthMonitor::instance()
{
static HealthMonitor inst;
return inst;
}
// ============================================================
// 初始化
// ============================================================
/**
* @brief 初始化健康监控模块,创建内部互斥锁与基础状态。
*/
void HealthMonitor::init()
{
m_mutex = xSemaphoreCreateMutex();
if (m_mutex == NULL) {
DEBUG_ERR("HEALTH", "创建健康状态互斥锁失败");
}
}
/**
* @brief 反初始化:停止并删除温度监控定时器,回收 esp_timer 资源。
* 当前 HealthMonitor 任务为无限循环,无显式删除路径,
* 本方法预留给未来停用场景(如安全关机后停止监控)使用。
*/
void HealthMonitor::deinit()
{
if (m_tempTimer != nullptr) {
esp_timer_stop(m_tempTimer);
esp_timer_delete(m_tempTimer);
m_tempTimer = nullptr;
DEBUG_INFO("HEALTH", "温度监控定时器已停止并删除");
}
}
// ============================================================
// 线程安全的健康状态读写
// ============================================================
/**
* @brief 加锁读取当前健康状态。
* @return 当前健康状态。
*/
health_state_t HealthMonitor::getState()
{
LockGuard lock(m_mutex);
return m_healthState;
}
/**
* @brief 加锁设置健康状态。
* @param newState 新的健康状态。
*/
void HealthMonitor::setState(health_state_t newState)
{
LockGuard lock(m_mutex);
m_healthState = newState;
}
// ============================================================
// 获取当前健康状态(public 接口)
// ============================================================
/**
* @brief 获取当前系统健康状态(线程安全)。
* @return 当前健康状态。
*/
health_state_t HealthMonitor::getCurrent()
{
return getState();
}
/**
* @brief 获取最近一次芯片温度。
* @return 温度值(摄氏度)。
*/
float HealthMonitor::getLastTemperature()
{
LockGuard lock(m_mutex);
return m_lastTemperature;
}
/**
* @brief 获取最近一次 WiFi RSSI 及其年龄。
* @param out_rssi 输出参数,返回 RSSI 值。
* @param out_age_ms 输出参数,返回样本年龄(毫秒)。
* @return 是否有有效数据。
*/
bool HealthMonitor::getLastWifiRssi(int8_t *out_rssi, uint32_t *out_age_ms)
{
int64_t lastMs;
int8_t rssi;
{
LockGuard lock(m_mutex);
lastMs = m_lastWifiRssiMs;
rssi = m_lastWifiRssi;
}
if (lastMs <= 0) {
return false;
}
int64_t now_ms = esp_timer_get_time() / 1000;
uint32_t age_ms = (now_ms > lastMs) ? (uint32_t)(now_ms - lastMs) : 0;
if (out_rssi) *out_rssi = rssi;
if (out_age_ms) *out_age_ms = age_ms;
return true;
}
// ============================================================
// 温度监控(定时器回调)
// ============================================================
/**
* @brief 温度监控定时器回调函数。
* @param arg 用户参数(未使用)。
*/
void HealthMonitor::tempMonitorCallback(void *arg)
{
auto& self = instance();
float temp = 0.0f;
esp_err_t err = temperature_sensor_get_celsius(self.m_tempHandle, &temp);
if (err != ESP_OK) {
DEBUG_WARN("TEMP", "读取温度失败: %s", esp_err_to_name(err));
return;
}
{
LockGuard lock(self.m_mutex);
self.m_lastTemperature = temp;
}
if (temp >= TEMP_CRITICAL) {
self.setState(SYS_SAFE_SHUTDOWN);
DEBUG_ERR("TEMP", "!!! 危险 !!! 温度 %.1f°C >= %d°C,触发安全关机", temp, TEMP_CRITICAL);
DEBUG_ERR("TEMP", "健康状态变更为: SYS_SAFE_SHUTDOWN");
} else if (temp >= TEMP_WARNING) {
health_state_t cur = self.getState();
if (cur < SYS_WARNING) {
self.setState(SYS_WARNING);
DEBUG_WARN("TEMP", "警告: 温度 %.1f°C >= %d°C", temp, TEMP_WARNING);
DEBUG_WARN("TEMP", "健康状态变更为: SYS_WARNING");
}
}
}
// ============================================================
// FreeRTOS 任务入口
// ============================================================
/**
* @brief 健康监控 FreeRTOS 任务入口。
* @param pvParameters 任务参数。
*/
void HealthMonitor::taskEntry(void *pvParameters)
{
(void)pvParameters;
auto& self = instance();
self.m_taskHandle = xTaskGetCurrentTaskHandle();
DEBUG_INFO("MON", "=== 健康监控任务启动 ===");
temperature_sensor_config_t temp_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(20, 80);
esp_err_t err = temperature_sensor_install(&temp_config, &self.m_tempHandle);
if (err != ESP_OK) {
DEBUG_ERR("MON", "温度传感器初始化失败: %s", esp_err_to_name(err));
} else {
ESP_ERROR_CHECK(temperature_sensor_enable(self.m_tempHandle));
}
// 定时器,每1秒读取一次温度
esp_timer_create_args_t timer_args = {};
timer_args.callback = &tempMonitorCallback;
timer_args.arg = nullptr;
timer_args.name = "temp_monitor";
timer_args.dispatch_method = ESP_TIMER_TASK;
if (esp_timer_create(&timer_args, &self.m_tempTimer) == ESP_OK) {
esp_timer_start_periodic(self.m_tempTimer, 1000000); // 1秒
} else {
self.m_tempTimer = nullptr;
DEBUG_ERR("MON", "创建温度监控定时器失败");
}
// 周期性巡检
while (1) {
// 栈溢出 canary 触发检测:vApplicationStackOverflowHook 置位后,
// 在 health_mon 完好栈上用 DEBUG_ERR 经 UDP/UART 发详细告警。
// 标志只在置位后告警一次,不自动清除(canary 触发即不可逆,需重启恢复)。
if (g_stackOverflowDetected) {
DEBUG_ERR("MON", "!!! 栈溢出已被内核 canary 检测到,请检查任务栈大小 !!!");
// 告警一次后清标志,避免 5 秒刷屏;后续若再次触发会重新置位
g_stackOverflowDetected = false;
}
uint32_t free_heap = esp_get_free_heap_size();
if (free_heap < HEAP_WARNING_MIN) {
/* OTA 期间堆会短暂变紧,限流避免串口/UDP 二次吃堆、刷屏 */
static TickType_t s_last_heap_warn = 0;
TickType_t now = xTaskGetTickCount();
if ((now - s_last_heap_warn) >= pdMS_TO_TICKS(2000)) {
s_last_heap_warn = now;
uint32_t largest_block = heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT);
DEBUG_WARN("MON", "堆内存不足: free=%lu min=%lu largest_blk=%lu",
(unsigned long)free_heap,
(unsigned long)esp_get_minimum_free_heap_size(),
(unsigned long)largest_block);
}
}
health_state_t cur = self.getState();
if (cur >= SYS_CRITICAL) {
DEBUG_ERR("MON", "健康状态危险: %d", cur);
}
// 栈水位主动巡检:复用 /system/status 的任务表,低水位时 DEBUG_ERR 主动告警。
// health_mon 任务不被 OTA 暂停,告警经 UDP/UART 直发,穿透 OTA 门控。
TaskStackMetric metrics[STACK_TASK_MAX];
size_t count = collectTaskStackMetrics(metrics, STACK_TASK_MAX);
for (size_t i = 0; i < count && i < STACK_TASK_MAX; ++i) {
if (metrics[i].handle == NULL || metrics[i].stack_size_bytes == 0) {
continue;
}
UBaseType_t hw = uxTaskGetStackHighWaterMark(metrics[i].handle);
size_t warn_bytes = (size_t)(metrics[i].stack_size_bytes * STACK_WARN_RATIO);
if (hw < (UBaseType_t)warn_bytes) {
if (!s_stackWarned[i]) {
DEBUG_ERR("MON", "!!! 栈水位过低 !!! 任务=%s 剩余=%luB 阈值=%luB(%.0f%%) 栈大小=%luB",
metrics[i].name,
(unsigned long)hw,
(unsigned long)warn_bytes,
STACK_WARN_RATIO * 100.0f,
(unsigned long)metrics[i].stack_size_bytes);
s_stackWarned[i] = true;
}
} else if (s_stackWarned[i]) {
// 水位恢复,清除告警标志,允许下次再次跌入时告警
s_stackWarned[i] = false;
DEBUG_INFO("MON", "栈水位恢复: 任务=%s 剩余=%luB",
metrics[i].name, (unsigned long)hw);
}
}
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
// ============================================================
// OTA 健康检查
// ============================================================
/**
* @brief OTA 传输前执行健康检查。
* @return 健康检查状态。
*/
health_status_t HealthMonitor::checkBeforeOta()
{
health_status_t status = HEALTH_MON_OK;
health_state_t cur = getState();
if (cur >= SYS_SAFE_SHUTDOWN) {
DEBUG_WARN("HEALTH", "OTA 前检查: SYS_SAFE_SHUTDOWN,禁止传输");
return HEALTH_MON_SHUTDOWN;
} else if (cur >= SYS_CRITICAL) {
DEBUG_WARN("HEALTH", "OTA 前检查: SYS_CRITICAL,禁止传输");
return HEALTH_MON_CRITICAL;
} else if (cur >= SYS_WARNING) {
status = HEALTH_MON_WARNING;
DEBUG_DBG("HEALTH", "OTA 前检查: SYS_WARNING,继续传输但需注意");
}
bool wifi_connected;
{
LockGuard lock(m_mutex);
wifi_connected = m_wifiConnected;
}
if (!wifi_connected) {
DEBUG_WARN("HEALTH", "OTA 前检查: WiFi 未连接");
return HEALTH_MON_CRITICAL;
}
int8_t last_rssi = -127;
uint32_t age_ms = 0;
if (getLastWifiRssi(&last_rssi, &age_ms)) {
if (age_ms <= OTA_WIFI_RSSI_SAMPLE_MAX_AGE_MS) {
if (last_rssi < OTA_WIFI_RSSI_CRITICAL) {
DEBUG_WARN("HEALTH", "OTA 前检查: 最近 RSSI %d < %d (样本年龄 %lu ms)",
last_rssi, OTA_WIFI_RSSI_CRITICAL, (unsigned long)age_ms);
return HEALTH_MON_CRITICAL;
} else if (last_rssi < WIFI_RSSI_NORMAL) {
if (status < HEALTH_MON_WARNING) {
status = HEALTH_MON_WARNING;
}
DEBUG_WARN("HEALTH", "OTA 前检查: 最近 RSSI %d < %d (样本年龄 %lu ms)",
last_rssi, WIFI_RSSI_NORMAL, (unsigned long)age_ms);
}
}
}
if (status == HEALTH_MON_OK) {
DEBUG_DBG("HEALTH", "OTA 前检查: 正常");
}
return status;
}
/**
* @brief OTA 中继转发阶段执行健康检查(跳过 WiFi 检查)。
* @return 健康检查状态。
*/
health_status_t HealthMonitor::checkBeforeOtaRelay()
{
health_status_t status = HEALTH_MON_OK;
health_state_t cur = getState();
if (cur >= SYS_SAFE_SHUTDOWN) {
DEBUG_WARN("HEALTH", "OTA 中继检查: SYS_SAFE_SHUTDOWN,禁止传输");
return HEALTH_MON_SHUTDOWN;
} else if (cur >= SYS_CRITICAL) {
/* SYS_CRITICAL 可能是由 WiFi RSSI 过低导致的,
* 但 SPI 中继阶段不依赖 WiFi,只检查温度/硬件等本地状态。
* 温度达到 TEMP_CRITICAL(100°C) 时 m_healthState 会被设为
* SYS_SAFE_SHUTDOWN,在上面的分支已经拦截。
* 所以 SYS_CRITICAL 在中继阶段只可能是 WiFi 导致的,允许继续。 */
DEBUG_WARN("HEALTH", "OTA 中继检查: SYS_CRITICAL (WiFi 相关),SPI 中继允许继续");
} else if (cur >= SYS_WARNING) {
status = HEALTH_MON_WARNING;
DEBUG_DBG("HEALTH", "OTA 中继检查: SYS_WARNING,继续传输");
}
/* 跳过 WiFi 连接状态和 RSSI 检查:
* SPI 中继阶段固件已存储在 ESP32 Flash 中,
* 不依赖 WiFi 连接,无需检查 WiFi 状态。 */
if (status == HEALTH_MON_OK) {
DEBUG_DBG("HEALTH", "OTA 中继检查: 正常");
}
return status;
}
// ============================================================
// WiFi 连接状态推送(由 WiFi 事件回调调用)
// ============================================================
/**
* @brief 由 WiFi 事件回调推送连接状态。
* @param connected true=已连接,false=断开。
*/
void HealthMonitor::setWifiConnected(bool connected)
{
LockGuard lock(m_mutex);
m_wifiConnected = connected;
}
// ============================================================
// WiFi RSSI 更新
// ============================================================
/**
* @brief 根据 WiFi RSSI 更新健康状态。
* @param rssi WiFi RSSI 值。
* @return 健康检查状态。
*/
health_status_t HealthMonitor::updateFromWifiRssi(int8_t rssi)
{
health_state_t cur = getState();
health_state_t next = SYS_NORMAL;
if (rssi < WIFI_RSSI_CRITICAL) {
next = SYS_CRITICAL;
} else if (rssi < WIFI_RSSI_NORMAL) {
next = SYS_WARNING;
}
// 仅在状态发生转换时打印一次,避免同一阈值下反复刷屏。
if (next != cur) {
if (next == SYS_CRITICAL) {
setState(SYS_CRITICAL);
DEBUG_WARN("HEALTH", "WiFi RSSI 危险: %d dBm < %d dBm", rssi, WIFI_RSSI_CRITICAL);
} else if (next == SYS_WARNING) {
setState(SYS_WARNING);
DEBUG_WARN("HEALTH", "WiFi RSSI 警告: %d dBm < %d dBm", rssi, WIFI_RSSI_NORMAL);
} else { // next == SYS_NORMAL,从 WARNING/CRITICAL 恢复
setState(SYS_NORMAL);
DEBUG_INFO("HEALTH", "WiFi RSSI 恢复: %d dBm >= %d dBm (从 %d 降级)", rssi, WIFI_RSSI_NORMAL, (int)cur);
}
}
if (next == SYS_CRITICAL) return HEALTH_MON_CRITICAL;
if (next == SYS_WARNING) return HEALTH_MON_WARNING;
return HEALTH_MON_OK;
}