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:
+2
-2
@@ -95,7 +95,7 @@ extern "C" void app_main(void)
|
||||
xRet = xTaskCreatePinnedToCore(
|
||||
OtaRelay::taskEntry,
|
||||
"ota_relay",
|
||||
12288,
|
||||
6144,
|
||||
NULL,
|
||||
configMAX_PRIORITIES - 2,
|
||||
NULL,
|
||||
@@ -127,7 +127,7 @@ extern "C" void app_main(void)
|
||||
xRet = xTaskCreatePinnedToCore(
|
||||
BleOta::taskEntry,
|
||||
"ble_ota",
|
||||
8192,
|
||||
4096,
|
||||
NULL,
|
||||
configMAX_PRIORITIES - 3,
|
||||
NULL,
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
// 固件版本
|
||||
#define FW_VERSION_MAJOR 0
|
||||
#define FW_VERSION_MINOR 3
|
||||
#define FW_VERSION_PATCH 28
|
||||
#define FW_VERSION_STRING "0.3.28"
|
||||
#define FW_VERSION_PATCH 29
|
||||
#define FW_VERSION_STRING "0.3.29"
|
||||
|
||||
// 编译时间
|
||||
#define BUILD_TIME "2026-06-19 03:24:28"
|
||||
#define BUILD_TIME "2026-06-19 23:45:14"
|
||||
|
||||
// Git 信息
|
||||
#define GIT_HASH "7feef70"
|
||||
#define GIT_HASH "59c729d"
|
||||
|
||||
+65
-50
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -41,6 +41,7 @@ gd32_version_t OtaRelay::gd32Version = {};
|
||||
QueueHandle_t OtaRelay::dispatchQueue = NULL;
|
||||
QueueHandle_t OtaRelay::resultQueue = NULL;
|
||||
SemaphoreHandle_t OtaRelay::s_stateMutex = NULL;
|
||||
TaskHandle_t OtaRelay::taskHandle = NULL;
|
||||
|
||||
// ============================================================
|
||||
// 状态表:将每个 relay_state_t 映射到对应的 handler
|
||||
@@ -134,6 +135,11 @@ void OtaRelay::setGd32Version(const gd32_version_t &ver)
|
||||
gd32Version = ver;
|
||||
}
|
||||
|
||||
TaskHandle_t OtaRelay::getTaskHandle()
|
||||
{
|
||||
return taskHandle;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 事件分发核心:processEvent()
|
||||
// ============================================================
|
||||
@@ -1397,6 +1403,7 @@ ota_result_t OtaRelay::doFinishAndVerify()
|
||||
void OtaRelay::taskEntry(void *pvParameters)
|
||||
{
|
||||
(void)pvParameters;
|
||||
taskHandle = xTaskGetCurrentTaskHandle();
|
||||
DEBUG_INFO("OTA", "OTA 中继任务启动");
|
||||
|
||||
initMutex();
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
#include "transport/protocol.h"
|
||||
#include "transport/spi_master.h"
|
||||
#include "esp_partition.h"
|
||||
@@ -137,6 +138,10 @@ public:
|
||||
* @brief 设置GD32固件版本.
|
||||
*/
|
||||
static void setGd32Version(const gd32_version_t &ver);
|
||||
/**
|
||||
* @brief 获取 OTA 中继任务句柄。
|
||||
*/
|
||||
static TaskHandle_t getTaskHandle();
|
||||
|
||||
/**
|
||||
* @brief 重置状态机到 IDLE.
|
||||
@@ -199,6 +204,7 @@ public:
|
||||
static gd32_version_t gd32Version;
|
||||
static QueueHandle_t dispatchQueue;
|
||||
static QueueHandle_t resultQueue;
|
||||
static TaskHandle_t taskHandle;
|
||||
|
||||
// ── 线程安全锁 ──
|
||||
static SemaphoreHandle_t s_stateMutex;
|
||||
|
||||
@@ -1205,6 +1205,7 @@ static void ble_ota_process_data_batch(const uint8_t *buf, size_t recv_len)
|
||||
static void ble_ota_data_worker_task(void *pvParameters)
|
||||
{
|
||||
(void)pvParameters;
|
||||
BleOta::instance().m_workerTaskHandle = xTaskGetCurrentTaskHandle();
|
||||
DEBUG_INFO(TAG, "OTA 数据工作线程已启动");
|
||||
|
||||
while (1) {
|
||||
@@ -1715,6 +1716,7 @@ void BleOta::signalRelay()
|
||||
void BleOta::taskEntry(void *pvParameters)
|
||||
{
|
||||
(void)pvParameters;
|
||||
BleOta::instance().m_taskHandle = xTaskGetCurrentTaskHandle();
|
||||
DEBUG_INFO(TAG, "=== BLE OTA 任务启动 ===");
|
||||
|
||||
// 创建 OTA 数据队列和工作线程(在 NimBLE 初始化之前完成)
|
||||
@@ -1722,7 +1724,7 @@ void BleOta::taskEntry(void *pvParameters)
|
||||
if (s_otaDataQueue) {
|
||||
BaseType_t wk_ok = xTaskCreate(ble_ota_data_worker_task,
|
||||
"ble_ota_worker",
|
||||
8192, // 6KB→8KB,flash 操作需要足够栈空间
|
||||
4096, // 峰值仅 ~1.9KB,4KB 留有充足余量
|
||||
NULL,
|
||||
10, // 中等优先级,避免被 NimBLE host task 过度抢占
|
||||
NULL);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "esp_ota_ops.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
class BleOta {
|
||||
public:
|
||||
@@ -47,6 +48,12 @@ public:
|
||||
*/
|
||||
void signalRelay();
|
||||
|
||||
/** @brief 获取 BLE OTA 守护任务句柄。 */
|
||||
TaskHandle_t getTaskHandle() const { return m_taskHandle; }
|
||||
|
||||
/** @brief 获取 BLE OTA 数据工作线程句柄。 */
|
||||
TaskHandle_t getWorkerTaskHandle() const { return m_workerTaskHandle; }
|
||||
|
||||
// 以下状态为 public,供 C 回调函数访问
|
||||
bool m_bleReady = false;
|
||||
bool m_bleConnected = false;
|
||||
@@ -56,6 +63,8 @@ public:
|
||||
bool m_esp32RebootPending = false; // 收尾任务去重标志
|
||||
uint8_t m_esp32LastCmd = 0; // 最后一次数据包命令(用于补发 ACK)
|
||||
uint16_t m_esp32LastSeq = 0xFFFFU; // 最后一次数据包序号(用于补发 ACK)
|
||||
TaskHandle_t m_taskHandle = nullptr;
|
||||
TaskHandle_t m_workerTaskHandle = nullptr;
|
||||
};
|
||||
|
||||
#endif // BLE_OTA_H
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "health_monitor.h"
|
||||
#include "monitor/debug_log.h"
|
||||
#include "spi_master.h"
|
||||
#include "ble_ota.h"
|
||||
#include "cJSON.h"
|
||||
|
||||
|
||||
@@ -1166,6 +1167,46 @@ static esp_err_t spi_send_handler(httpd_req_t *req)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static constexpr uint32_t INTERNAL_HEAP_CAPS = MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT;
|
||||
|
||||
struct TaskStackMetric {
|
||||
const char* name;
|
||||
TaskHandle_t handle;
|
||||
size_t stack_size_bytes;
|
||||
};
|
||||
|
||||
static size_t get_task_stack_high_watermark_bytes(TaskHandle_t handle)
|
||||
{
|
||||
if (handle == NULL) return 0;
|
||||
UBaseType_t highWaterMark = uxTaskGetStackHighWaterMark(handle);
|
||||
return (size_t)highWaterMark;
|
||||
}
|
||||
|
||||
static size_t get_task_stack_peak_used_bytes(TaskHandle_t handle, size_t stack_size_bytes)
|
||||
{
|
||||
size_t high_watermark = get_task_stack_high_watermark_bytes(handle);
|
||||
return (stack_size_bytes >= high_watermark) ? (stack_size_bytes - high_watermark) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 追加 JSON 内容到响应缓冲区,自动处理 snprintf 截断。
|
||||
*/
|
||||
static int append_jsonf(char* buf, size_t cap, int offset, const char* fmt, ...)
|
||||
{
|
||||
if (!buf || cap == 0 || offset < 0 || (size_t)offset >= cap) return offset;
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
int n = vsnprintf(buf + offset, cap - (size_t)offset, fmt, args);
|
||||
va_end(args);
|
||||
if (n < 0) return offset;
|
||||
size_t written = (size_t)n;
|
||||
if (offset + (int)written >= (int)cap) {
|
||||
buf[cap - 1] = '\0';
|
||||
return (int)(cap - 1);
|
||||
}
|
||||
return offset + (int)written;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief HTTP GET /system/status 处理器
|
||||
* @param req
|
||||
@@ -1236,12 +1277,45 @@ static esp_err_t system_status_handler(httpd_req_t *req)
|
||||
gd32_version_t snapVer2 = {};
|
||||
OtaRelay::getGd32Version(&snapVer2);
|
||||
ota_system_state_t sysState = OtaCoordinator::instance().getSystemState();
|
||||
char resp[1280];
|
||||
size_t free_heap = esp_get_free_heap_size();
|
||||
size_t min_free_heap = esp_get_minimum_free_heap_size();
|
||||
size_t largest_free_block = heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT);
|
||||
size_t internal_free_heap = heap_caps_get_free_size(INTERNAL_HEAP_CAPS);
|
||||
size_t internal_min_free_heap = heap_caps_get_minimum_free_size(INTERNAL_HEAP_CAPS);
|
||||
size_t internal_largest_free_block = heap_caps_get_largest_free_block(INTERNAL_HEAP_CAPS);
|
||||
size_t internal_heap_total = heap_caps_get_total_size(INTERNAL_HEAP_CAPS);
|
||||
size_t default_heap_total = heap_caps_get_total_size(MALLOC_CAP_DEFAULT);
|
||||
TaskStackMetric task_metrics[] = {
|
||||
{ "http_server", xTaskGetCurrentTaskHandle(), HTTP_SERVER_STACK_SIZE },
|
||||
{ "health_mon", HealthMonitor::instance().getTaskHandle(), 4096 },
|
||||
{ "ota_relay", OtaRelay::getTaskHandle(), 6144 },
|
||||
{ "wifi_ota", WifiOta::instance().getTaskHandle(), 8192 },
|
||||
{ "wifi_reconnect", WifiOta::instance().m_reconnectTask, 4096 },
|
||||
{ "ble_ota", BleOta::instance().getTaskHandle(), 4096 },
|
||||
{ "ble_ota_worker", BleOta::instance().getWorkerTaskHandle(), 4096 }
|
||||
};
|
||||
|
||||
char resp[4096];
|
||||
int len = snprintf(resp, sizeof(resp),
|
||||
"{\"version\": \"%s\", \"build_time\": \"%s\", \"git_hash\": \"%s\", \"health\": %d, \"free_heap\": %lu, \"min_free_heap\": %lu, \"temperature\": %.1f, \"uptime_sec\": %lu, \"wifi\": {\"mode\": \"%s\", \"ip\": \"%s\", \"rssi\": %d, \"connected\": %s}, \"ota\": {\"sys_state\": \"%s\", \"sys_state_code\": %d, \"total_size\": %lu, \"stored_size\": %lu, \"storage_done\": %s, \"relay_state\": %d, \"relay_seq\": %lu, \"relay_total_packets\": %lu, \"gd32_app_alive\": %s, \"gd32_app\": \"v%d.%d.%d.%d\", \"gd32_boot\": \"v%d.%d.%d.%d\"}}",
|
||||
"{\"version\": \"%s\", \"build_time\": \"%s\", \"git_hash\": \"%s\", \"health\": %d, "
|
||||
"\"free_heap\": %lu, \"min_free_heap\": %lu, \"largest_free_block\": %lu, "
|
||||
"\"internal_free_heap\": %lu, \"internal_min_free_heap\": %lu, \"internal_largest_free_block\": %lu, "
|
||||
"\"internal_heap_total\": %lu, \"default_heap_total\": %lu, "
|
||||
"\"temperature\": %.1f, \"uptime_sec\": %lu, "
|
||||
"\"wifi\": {\"mode\": \"%s\", \"ip\": \"%s\", \"rssi\": %d, \"connected\": %s}, "
|
||||
"\"ota\": {\"sys_state\": \"%s\", \"sys_state_code\": %d, \"total_size\": %lu, \"stored_size\": %lu, \"storage_done\": %s, "
|
||||
"\"relay_state\": %d, \"relay_seq\": %lu, \"relay_total_packets\": %lu, \"gd32_app_alive\": %s, "
|
||||
"\"gd32_app\": \"v%d.%d.%d.%d\", \"gd32_boot\": \"v%d.%d.%d.%d\"}, "
|
||||
"\"task_stack\": {",
|
||||
FW_VERSION, FW_BUILD_TIME, FW_GIT_HASH, HealthMonitor::instance().getCurrent(),
|
||||
(unsigned long)esp_get_free_heap_size(),
|
||||
(unsigned long)esp_get_minimum_free_heap_size(),
|
||||
(unsigned long)free_heap,
|
||||
(unsigned long)min_free_heap,
|
||||
(unsigned long)largest_free_block,
|
||||
(unsigned long)internal_free_heap,
|
||||
(unsigned long)internal_min_free_heap,
|
||||
(unsigned long)internal_largest_free_block,
|
||||
(unsigned long)internal_heap_total,
|
||||
(unsigned long)default_heap_total,
|
||||
(double)HealthMonitor::instance().getLastTemperature(),
|
||||
(unsigned long)(esp_timer_get_time() / 1000000),
|
||||
WifiOta::instance().m_wifiMode == OTA_WIFI_MODE_AP ? "AP" : "STA",
|
||||
@@ -1262,6 +1336,17 @@ static esp_err_t system_status_handler(httpd_req_t *req)
|
||||
snapVer2.boot_major, snapVer2.boot_minor,
|
||||
snapVer2.boot_patch, snapVer2.boot_build);
|
||||
|
||||
for (size_t i = 0; i < sizeof(task_metrics) / sizeof(task_metrics[0]); ++i) {
|
||||
len = append_jsonf(resp, sizeof(resp), len,
|
||||
"%s\"%s\":{\"stack_size_bytes\":%lu,\"high_watermark_bytes\":%lu,\"peak_used_bytes\":%lu}",
|
||||
i == 0 ? "" : ",",
|
||||
task_metrics[i].name,
|
||||
(unsigned long)task_metrics[i].stack_size_bytes,
|
||||
(unsigned long)get_task_stack_high_watermark_bytes(task_metrics[i].handle),
|
||||
(unsigned long)get_task_stack_peak_used_bytes(task_metrics[i].handle, task_metrics[i].stack_size_bytes));
|
||||
}
|
||||
len = append_jsonf(resp, sizeof(resp), len, "}}");
|
||||
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, resp, len);
|
||||
return ESP_OK;
|
||||
@@ -1648,6 +1733,7 @@ bool WifiOta::earlyInit()
|
||||
void WifiOta::taskEntry(void *pvParameters)
|
||||
{
|
||||
(void)pvParameters;
|
||||
WifiOta::instance().m_taskHandle = xTaskGetCurrentTaskHandle();
|
||||
|
||||
DEBUG_INFO("WIFI", "=== WiFi OTA 任务启动 ===");
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "esp_ota_ops.h"
|
||||
#include "esp_http_server.h"
|
||||
#include "esp_event.h"
|
||||
#include "freertos/task.h"
|
||||
#include <atomic>
|
||||
|
||||
// SHA-256 验证所需类型前向声明
|
||||
@@ -102,6 +103,9 @@ public:
|
||||
/** @brief BLE OTA 开始传输时调用,暂停 WiFi RSSI 轮询等射频活动。 */
|
||||
void setBleOtaActive(bool v) { m_bleOtaActive.store(v); }
|
||||
|
||||
/** @brief 获取 WiFi OTA 任务句柄。 */
|
||||
TaskHandle_t getTaskHandle() const { return m_taskHandle; }
|
||||
|
||||
private:
|
||||
// ── 跨任务共享状态(atomic + m_stateMutex 保护)──
|
||||
std::atomic<bool> m_staConnected{false};
|
||||
@@ -110,6 +114,7 @@ private:
|
||||
std::atomic<bool> m_gd32StorageDone{false};
|
||||
std::atomic<bool> m_relayTriggered{false};
|
||||
SemaphoreHandle_t m_stateMutex = NULL;
|
||||
TaskHandle_t m_taskHandle = nullptr;
|
||||
|
||||
/**
|
||||
* @brief 初始化 SoftAP 模式,供局域网 OTA 使用。
|
||||
|
||||
Reference in New Issue
Block a user