Files
ESP32_COMM/main/monitor/health_monitor.h
T
小龙虾 770ae14156
SPI Link v2 Self-Test / selftest (push) Has been cancelled
feat(spi_link): 添加SPI Link v2 PC自测框架并优化协议稳定性
添加SPI Link v2的PC端自测功能,包含交叉校验基准测试,覆盖HELLO/OTA/CAN/PING等
多种协议场景。同时重构了health monitor中的多字段读取逻辑,消除了volatile变量
的撕裂读问题,并增强了BLE OTA的数据传输可靠性。

refactor(health_monitor): 修复heap hook多字段撕裂读问题

采用portMUX_CRITICAL临界区实现多字段一致快照读取,消除任务上下文与heap hook
之间的数据竞争。修改了HookSnap结构体设计,通过一次性拷贝volatile变量避免
跨字段组合时的数据不一致。JSON输出中明确标注estimated值的置信度。

fix(ble_ota): 实现DATA_IN丢包负反馈机制

当malloc失败或队列满时发送缺口ACK通知前端按completed_bytes续传,避免
静默丢包导致的8秒超时重传风暴。增加节流机制防止负ACK在重传叠加时打爆
NimBLE mbuf池,提升低内存环境下的OTA传输稳定性。

refactor(spi_master): 从堆分配改为固定Job池管理

将SPI worker的动态堆分配Job改为固定大小的静态池管理,避免频繁的
calloc/free操作影响实时性。新增acquireJobSlot/releaseJobSlot机制
管理池槽生命周期,提升SPI通信的可靠性和内存使用效率。
2026-07-21 01:16:43 +08:00

196 lines
8.7 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.h - 系统健康监控头文件
*
* HealthMonitor 类封装健康状态管理、温度监控和 WiFi RSSI 监控。
*/
#pragma once
#include <stdint.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "driver/temperature_sensor.h"
#include "esp_timer.h"
#include "app_config.h"
// T9.3: 健康状态等级
typedef enum {
SYS_NORMAL = 0, // 正常
SYS_WARNING = 1, // 警告
SYS_CRITICAL = 2, // 危险
SYS_SAFE_SHUTDOWN = 3 // 安全关机
} health_state_t;
/** @brief 健康监控模块内部状态码,用于函数返回值。 */
typedef enum {
HEALTH_MON_OK = 0, ///< 健康检查通过
HEALTH_MON_WARNING = 1, ///< 健康检查警告(可继续)
HEALTH_MON_CRITICAL = 2, ///< 健康检查危险(建议中止)
HEALTH_MON_SHUTDOWN = 3 ///< 需要安全关机
} health_status_t;
// WiFi RSSI 阈值(来自 app_config.h)
#define WIFI_RSSI_NORMAL WIFI_RSSI_WARNING ///< WiFi 信号正常阈值(别名)
// ============================================================
// 堆低水位快照("最低空闲"创新低时记录上下文,供 /system/heap_low_log 查询)
// ============================================================
/** @brief 单条任务栈水位(用于低水位快照内嵌的任务栈表)。 */
struct HeapLowTaskStack {
char name[16]; ///< 任务名(截断)
uint32_t remaining; ///< 剩余栈字节(uxTaskGetStackHighWaterMark × sizeof(StackType_t))
};
/** @brief 单条"堆低水位"快照记录。仅当 internal_min_free 出现新低时写入。 */
struct HeapLowWaterRecord {
uint64_t ts_ms; ///< 启动后毫秒(esp_timer_get_time / 1000)
uint32_t free_heap; ///< 当时的总空闲堆(esp_get_free_heap_size)
uint32_t min_free_heap; ///< 当时记录到的内部堆最低水位
uint32_t largest_free_block; ///< 最大连续空闲块
uint8_t sys_state; ///< OTA 系统状态码(ota_system_state_t)
uint8_t ota_channel; ///< OTA 通道(0=WIFI, 1=BLE)
int8_t rssi; ///< 最近 WiFi RSSI(dBm,-127=未知)
float temperature; ///< 最近芯片温度(°C)
/* 多任务栈水位表:按"剩余升序"取前 8 个,定位"是谁的栈突然涨了"。
* task_stack_count=0 表示未采集到任务信息。 */
HeapLowTaskStack task_stacks[8];
uint8_t task_stack_count;
/* 触发低水位的"当前任务":取当前 TaskHandle 的名字(taskENTER_CRITICAL 后取),
* 用于推断是谁在分配堆。空串表示未知。 */
char current_task[16];
/* 连接/OTA 进度上下文:区分"无连接 vs 中继期 vs 收尾期" */
uint8_t ble_connected; ///< 0/1
uint8_t wifi_connected; ///< 0/1
uint32_t ota_stored_size; ///< OTA 已存储字节
uint32_t ota_total_size; ///< OTA 总字节
/* 嫌疑资源水位:定位"OTA/IDLE 期低谷由哪个子系统吃堆"。
* 非 OTA 期这些应为 0;非 0 时即对应子系统持有堆缓冲。 */
uint16_t ble_data_queue_depth; ///< BLE OTA 数据队列当前条数
uint16_t ble_reorder_cached; ///< BLE reorder 已缓存乱序包数
uint16_t spi_pending_jobs; ///< SPI master 待处理 job 数
uint16_t reserved; ///< 对齐保留
/* 内存全景:区分"内部碎片"vs"系统级紧"。
* default_free 含 PSRAM;internal_free = free_heap(含 SPIRAM 时与 default 区分)。
* alloc_count = 当前活跃 alloc 数,看"是不是小块大量未释放"。 */
uint32_t default_free_heap; ///< MALLOC_CAP_DEFAULT free(含 PSRAM)
uint32_t alloc_count; ///< 内部堆当前 allocated_blocks(heap_caps_get_info)
/* heap hook:「破新低时的最后一击 alloc」。
* 不记任务名(WiFi IRAM 禁止;且发送路径末包常把任务刷成 wifi,误导主因)。
* hook_task 现为类别串:net? / bulk / tiny(非 FreeRTOS 任务名)。
* hook_caller_addr 用 addr2line 解析。 */
char hook_task[16]; ///< 分配类别 net?/bulk/tiny,不是任务名
uint32_t hook_alloc_size; ///< 最后一击 alloc 字节数
uint32_t hook_caller_addr; ///< 调用者返回地址(addr2line)
uint32_t hook_free_after; ///< 该次后的近似 free(**estimated 低水位近似值**,非精确实测;JSON 输出为 hook_est_free_after + hook_confidence,前端应标注 estimated)
uint32_t hook_fired; ///< 0=本窗口 hook 未命中
/* DHCP/IP 事件计数:每次 STA GOT_IP(含续约)+1。
* 对比两次低谷记录的计数差,确认低谷是不是 DHCP 续约引起。 */
uint32_t ip_event_count; ///< 累计 IP 获得/续约次数
};
/**
* @brief 系统健康监控类(单例)
*
* 封装健康状态管理、温度定时监控和 WiFi RSSI 监控。
*/
class HealthMonitor {
public:
/**
* @brief 获取 HealthMonitor 单例实例。
* @return HealthMonitor 引用。
*/
static HealthMonitor& instance();
/** @brief 初始化健康监控模块,创建内部互斥锁与基础状态。 */
void init();
/** @brief 健康监控 FreeRTOS 任务入口。 */
static void taskEntry(void *pvParameters);
/** @brief OTA 传输前执行健康检查。 */
health_status_t checkBeforeOta();
/** @brief OTA 中继转发阶段执行健康检查(跳过 WiFi 检查)。 */
health_status_t checkBeforeOtaRelay();
/** @brief 获取当前系统健康状态(线程安全)。 */
health_state_t getCurrent();
/** @brief 根据 WiFi RSSI 更新健康状态。 */
health_status_t updateFromWifiRssi(int8_t rssi);
/** @brief 获取最近一次芯片温度。 */
float getLastTemperature();
/** @brief 获取最近一次 WiFi RSSI 及其年龄。 */
bool getLastWifiRssi(int8_t *out_rssi, uint32_t *out_age_ms);
/** @brief 获取健康监控任务句柄。 */
TaskHandle_t getTaskHandle() const { return m_taskHandle; }
// ── 堆低水位快照 ──
/** @brief 检测并可能覆盖写入唯一快照槽(线程安全)。
* 由 health_mon 5s 巡检调用,仅当 internal_min_free 出现新低时写入。
* 设备只保留最新 1 条;多条历史由网页 IndexedDB 累积。 */
void maybeRecordHeapLowWater();
/** @brief 将唯一快照序列化为 JSON:{"count":0|1,"records":[...]}。线程安全。 */
int dumpHeapLowWaterJson(char *buf, size_t cap, int offset) const;
/**
* @brief 由 WiFi 事件回调推送连接状态(避免 HealthMonitor → WifiOta 依赖)。
* WiFi 连接/断开时由 wifi_ota 事件处理器主动调用,
* HealthMonitor 无需主动查询 WifiOta::isConnected()。
* @param connected true=已连接,false=断开。
*/
void setWifiConnected(bool connected);
private:
/** @brief 加锁读取健康状态。 */
health_state_t getState();
/** @brief 加锁设置健康状态。 */
void setState(health_state_t newState);
HealthMonitor() = default;
~HealthMonitor() = default;
HealthMonitor(const HealthMonitor&) = delete;
HealthMonitor& operator=(const HealthMonitor&) = delete;
SemaphoreHandle_t m_mutex = NULL;
temperature_sensor_handle_t m_tempHandle = NULL;
// 温度监控 esp_timer 句柄存为成员,避免任务被删除后定时器仍在运行、
// 回调继续访问 instance() 成员。
esp_timer_handle_t m_tempTimer = nullptr;
health_state_t m_healthState = SYS_NORMAL;
float m_lastTemperature = 0.0f;
int8_t m_lastWifiRssi = -127;
int64_t m_lastWifiRssiMs = 0;
TaskHandle_t m_taskHandle = NULL;
// m_wifiConnected 由 m_mutex 保护(setWifiConnected 写 / checkBeforeOta 读)。
bool m_wifiConnected = false; ///< WiFi 连接状态(由 WiFi 事件推送)
// 堆低水位:设备仅 1 槽(覆盖写)。m_mutex 保护。
HeapLowWaterRecord m_heapLowWater = {};
bool m_heapLowWaterValid = false; ///< 是否有一条有效快照
uint32_t m_heapLowWaterLastMin = 0xFFFFFFFFu; ///< 上次记下的最低值
static void tempMonitorCallback(void *arg);
// RAII 锁守卫(与 SpiMaster::LockGuard 风格一致)
class LockGuard {
public:
explicit LockGuard(SemaphoreHandle_t mutex) : m_lock(mutex) {
if (m_lock) { xSemaphoreTake(m_lock, portMAX_DELAY); }
}
~LockGuard() {
if (m_lock) { xSemaphoreGive(m_lock); }
}
LockGuard(const LockGuard&) = delete;
LockGuard& operator=(const LockGuard&) = delete;
private:
SemaphoreHandle_t m_lock;
};
};