feat(config): 优化NimBLE配置以节省内存并提升OTA性能

- 启用BLE连接事件长度优化,允许单CE内传输多包,提升OTA吞吐
- 缩减NimBLE mbuf池配置,单连接OTA场景节省约7KB内存
- 禁用未使用的标准GATT服务,仅保留自定义OTA service,节省8-15KB
- 缩减NimBLE传输缓冲区配置以优化内存使用

refactor(debug_log): 重构环形日志缓冲区API以支持逐条弹出

- 新增ringPopOldest方法,支持弹出最旧日志并从缓冲区移除
- 重构ringDrainJson实现,基于新的逐条弹出机制
- 改进日志转义处理逻辑,支持更多特殊字符转义

perf(tasks): 优化各任务栈大小配置以节省内存

- 将ota_relay任务栈从12288字节缩减至6144字节
- 将ble_ota任务栈从8192字节缩减至4096字节
- 将ble_ota数据工作线程栈从8192字节缩减至4096字节
- 通过栈高水位监控验证实际使用情况,确保安全余量

feat(monitor): 添加任务句柄获取接口以支持系统状态监控

- 为HealthMonitor、OtaRelay、BleOta和WifiOta类添加任务句柄获取方法
- 在任务入口处记录当前任务句柄
- 便于系统状态监控和调试信息收集

feat(status): 扩展系统状态API以提供更详细的内存和任务信息

- 增加内部堆和默认堆的详细统计信息
- 添加各关键任务的栈使用情况监控
- 扩展/system/status端点返回更全面的系统资源使用情况
- 包含最大空闲块、最小空闲内存等关键指标
This commit is contained in:
小龙虾
2026-06-20 00:46:04 +08:00
parent 59c729d77a
commit 3fff959a2b
18 changed files with 701 additions and 103 deletions
+65 -50
View File
@@ -60,74 +60,89 @@ int DebugLog::ringCount()
}
/**
* @brief 读取并清空环形日志缓冲区,返回 JSON 数组字符串。
* @param out_len 输出参数,返回 JSON 字符串长度。
* @return 指向 JSON 字符串的指针,需调用者 free。
* @brief 弹出一条最旧日志并从环形缓冲区移除。
* @param out 输出缓冲区。
* @param out_size 输出缓冲区大小。
* @param out_len 输出日志长度。
* @return true 表示成功弹出一条日志,false 表示缓冲区为空或参数无效。
*/
char *DebugLog::ringDrainJson(int *out_len)
bool DebugLog::ringPopOldest(char *out, size_t out_size, int *out_len)
{
if (!m_logRingMutex) {
if (out_len) {
*out_len = 0;
return NULL;
}
if (!m_logRingMutex || !out || out_size == 0) {
return false;
}
xSemaphoreTake(m_logRingMutex, portMAX_DELAY);
int count = m_logRingCount;
if (count == 0) {
if (m_logRingCount <= 0) {
xSemaphoreGive(m_logRingMutex);
*out_len = 0;
return NULL;
return false;
}
// 估算 JSON 大小:每条 ~280 字节(转义 + JSON 包装)
size_t buf_size = (size_t)count * 280 + 16;
char *json = (char *)malloc(buf_size);
if (!json) {
xSemaphoreGive(m_logRingMutex);
*out_len = 0;
return NULL;
int idx = (m_logRingHead - m_logRingCount + LOG_RING_SLOTS) % LOG_RING_SLOTS;
int len = m_logRingLens[idx];
if (len < 0) {
len = 0;
}
int pos = 0;
json[pos++] = '[';
// 从最旧的开始读
int start = (count < LOG_RING_SLOTS) ? 0 : m_logRingHead;
for (int i = 0; i < count && pos < (int)buf_size - 200; i++) {
int idx = (start + i) % LOG_RING_SLOTS;
int len = m_logRingLens[idx];
if (len <= 0) continue;
if (i > 0) json[pos++] = ',';
pos += snprintf(json + pos, buf_size - pos, "\"");
for (int j = 0; j < len && pos < (int)buf_size - 10; j++) {
char c = m_logRing[idx][j];
if (c == '"' || c == '\\') {
json[pos++] = '\\';
json[pos++] = c;
} else if (c == '\n') {
json[pos++] = '\\';
json[pos++] = 'n';
} else if (c >= 32) {
json[pos++] = c;
}
}
pos += snprintf(json + pos, buf_size - pos, "\"");
int copy_len = len;
if ((size_t)copy_len >= out_size) {
copy_len = (int)out_size - 1;
}
json[pos++] = ']';
json[pos] = '\0';
if (copy_len > 0) {
memcpy(out, m_logRing[idx], (size_t)copy_len);
}
out[copy_len] = '\0';
if (out_len) {
*out_len = copy_len;
}
// 清空缓冲区
m_logRingHead = 0;
m_logRingCount = 0;
m_logRingLens[idx] = 0;
m_logRingCount--;
xSemaphoreGive(m_logRingMutex);
return true;
}
char* DebugLog::ringDrainJson(int *out_len)
{
if (!out_len) return NULL;
*out_len = 0;
int count = ringCount();
int capacity = count * (LOG_RING_SIZE * 2) + 16;
if (capacity < 16) capacity = 16;
char *buf = (char*)malloc(capacity);
if (!buf) return NULL;
int pos = 0;
buf[pos++] = '[';
bool first = true;
char entry[LOG_RING_SIZE];
int entryLen = 0;
while (ringPopOldest(entry, sizeof(entry), &entryLen) && entryLen > 0) {
if (!first) buf[pos++] = ',';
first = false;
buf[pos++] = '"';
for (int i = 0; i < entryLen && pos < capacity - 4; i++) {
char c = entry[i];
if (c == '"') { buf[pos++] = '\\'; buf[pos++] = '"'; }
else if (c == '\\') { buf[pos++] = '\\'; buf[pos++] = '\\'; }
else if (c == '\n') { buf[pos++] = '\\'; buf[pos++] = 'n'; }
else if (c == '\r') { buf[pos++] = '\\'; buf[pos++] = 'r'; }
else if (c == '\t') { buf[pos++] = '\\'; buf[pos++] = 't'; }
else if ((unsigned char)c >= 0x20) { buf[pos++] = c; }
}
buf[pos++] = '"';
}
buf[pos++] = ']';
buf[pos] = '\0';
*out_len = pos;
return json;
return buf;
}
/**
@@ -350,4 +365,4 @@ void DebugLog::log(debug_log_level_t level, const char *tag, const char *fmt, ..
int total = snprintf(log_buf, sizeof(log_buf), "%s %s\n", prefix, msg);
send((const uint8_t *)log_buf, total);
}
}
+3 -2
View File
@@ -72,7 +72,8 @@ public:
/** @brief 获取当前环形日志缓冲区中的日志条数。 */
int ringCount();
/** @brief 读取并清空环形日志缓冲区,返回 JSON 数组字符串(需调用者 free)。 */
/** @brief 弹出一条最旧日志并从环形缓冲区移除。 */
bool ringPopOldest(char *out, size_t out_size, int *out_len);
char* ringDrainJson(int *out_len);
/** @brief 当前日志级别(运行时可改)。 */
@@ -107,4 +108,4 @@ private:
#define DEBUG_INFO(tag, fmt, ...) DebugLog::instance().log(DEBUG_LOG_INFO, tag, fmt, ##__VA_ARGS__)
#define DEBUG_DBG(tag, fmt, ...) DebugLog::instance().log(DEBUG_LOG_DBG, tag, fmt, ##__VA_ARGS__)
#endif // DEBUG_LOG_H
#endif // DEBUG_LOG_H
+2 -1
View File
@@ -16,7 +16,6 @@
#include "esp_err.h"
#include "driver/temperature_sensor.h"
#include "esp_heap_caps.h"
#include "health_monitor.h"
#include "debug_log.h"
#include "server/wifi_ota.h"
@@ -167,7 +166,9 @@ void HealthMonitor::tempMonitorCallback(void *arg)
*/
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);
+6 -1
View File
@@ -8,6 +8,7 @@
#include <stdint.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "driver/temperature_sensor.h"
#include "app_config.h"
@@ -70,6 +71,9 @@ public:
/** @brief 获取最近一次 WiFi RSSI 及其年龄。 */
bool getLastWifiRssi(int8_t *out_rssi, uint32_t *out_age_ms);
/** @brief 获取健康监控任务句柄。 */
TaskHandle_t getTaskHandle() const { return m_taskHandle; }
private:
/** @brief 加锁读取健康状态。 */
health_state_t getState();
@@ -88,6 +92,7 @@ private:
float m_lastTemperature = 0.0f;
int8_t m_lastWifiRssi = -127;
int64_t m_lastWifiRssiMs = 0;
TaskHandle_t m_taskHandle = NULL;
static void tempMonitorCallback(void *arg);
};
};