From f598b9acb3c88a0768fdce6e82f2d7fb2cf2e0d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E9=BE=99=E8=99=BE?= Date: Tue, 5 May 2026 04:48:31 +0800 Subject: [PATCH] =?UTF-8?q?docs(readme):=20=E6=9B=B4=E6=96=B0=E8=B0=83?= =?UTF-8?q?=E8=AF=95=E6=97=A5=E5=BF=97=E4=B8=8E=E4=B8=8A=E4=BD=8D=E6=9C=BA?= =?UTF-8?q?=E9=80=9A=E4=BF=A1=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更新了 README.md 中关于调试日志与上位机通信的部分,详细说明了当 前通信架构分为两类: 1. 业务通信:HTTP 接口用于 WebUI 与 ESP32 通信 2. 调试镜像:UART0 + UDP 用于独立调试接收器 明确区分了 WebUI 使用的 HTTP 接口功能,并移除了不再需要的心跳包 说明。 refactor(health): 移除健康监控模块的备份文件 删除了 _assistant_backups 目录中的 health_monitor.cpp 和 health_monitor.h 备份文件,清理项目结构。 --- README.md | 19 +- .../health_monitor.cpp.bak_2026-05-04_2233 | 233 ----- .../health_monitor.h.bak_2026-05-04_2233 | 37 - .../wifi_ota.cpp.bak_2026-05-04_2132 | 972 ------------------ docs/COMMUNICATION_AUDIT_2026-05-05.md | 74 ++ main/core/main.cpp | 18 +- main/monitor/debug_log.cpp | 74 +- main/monitor/debug_log.h | 51 +- main/monitor/health_monitor.cpp | 67 +- main/monitor/health_monitor.h | 41 +- main/relay/ota_relay.cpp | 141 ++- main/relay/ota_relay.h | 64 +- main/server/ble_ota.cpp | 78 +- main/server/ble_ota.h | 26 +- main/server/wifi_ota.cpp | 199 +++- main/server/wifi_ota.h | 32 +- main/transport/protocol.cpp | 54 +- main/transport/protocol.h | 101 +- main/transport/spi_master.cpp | 23 +- main/transport/spi_master.h | 71 +- 20 files changed, 955 insertions(+), 1420 deletions(-) delete mode 100644 _assistant_backups/2026-05-04/health_monitor.cpp.bak_2026-05-04_2233 delete mode 100644 _assistant_backups/2026-05-04/health_monitor.h.bak_2026-05-04_2233 delete mode 100644 _assistant_backups/2026-05-04/wifi_ota.cpp.bak_2026-05-04_2132 create mode 100644 docs/COMMUNICATION_AUDIT_2026-05-05.md diff --git a/README.md b/README.md index 85de683..8badd19 100644 --- a/README.md +++ b/README.md @@ -113,9 +113,24 @@ curl -X POST http://192.168.31.1/can/send \ | ota_relay | configMAX_PRIORITIES - 2 | 8192 | OTA 中继 | | wifi_ota | configMAX_PRIORITIES - 3 | 8192 | WiFi OTA | -## 调试日志 +## 调试日志与上位机通信 -UDP 输出到上位机,端口 6666。上位机 WebUI 自动监听并显示。 +当前与上位机相关的通信分成两类: + +1. **业务通信:HTTP** + - WebUI 通过 HTTP 接口与 ESP32 通信 + - 典型接口:`/system/status`、`/logs`、`/can/send`、`/ota/*` + - 其中日志面板实际通过 **轮询 `GET /logs`** 获取日志 + +2. **调试镜像:UART0 + UDP** + - 所有 `DEBUG_*` 日志会同时写到 UART0、UDP、以及内部环形缓冲区 + - UDP 目标端口为 `6666`,适合独立调试接收器使用 + - 但 **当前 tools WebUI 不直接监听 UDP 6666** + +因此,当前 WebUI 场景下: +- `/system/status` 用于在线状态/健康状态展示 +- `/logs` 用于日志展示 +- 不再需要额外的 `{"t":"hb"}` 假日志心跳包 ## 编译 diff --git a/_assistant_backups/2026-05-04/health_monitor.cpp.bak_2026-05-04_2233 b/_assistant_backups/2026-05-04/health_monitor.cpp.bak_2026-05-04_2233 deleted file mode 100644 index ee9b967..0000000 --- a/_assistant_backups/2026-05-04/health_monitor.cpp.bak_2026-05-04_2233 +++ /dev/null @@ -1,233 +0,0 @@ -/* - * health_monitor.cpp - 系统健康监控 - * 包含:健康状态管理 + WiFi RSSI 监控 + 温度监控 + 电压监控 - */ -#include -#include -#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 "driver/temperature_sensor.h" - -#include "health_monitor.h" -#include "debug_log.h" - -health_state_t g_health_state = SYS_NORMAL; - -static SemaphoreHandle_t g_health_mutex = NULL; -static temperature_sensor_handle_t s_temp_handle = NULL; - -// ---------------------------------------- -// 健康状态管理 -// ---------------------------------------- -void health_monitor_init(void) -{ - g_health_mutex = xSemaphoreCreateMutex(); - if (g_health_mutex == NULL) { - DEBUG_ERR("HEALTH", "创建健康状态互斥锁失败"); - } -} - -health_state_t health_get_current(void) -{ - health_state_t state; - if (g_health_mutex) { - xSemaphoreTake(g_health_mutex, portMAX_DELAY); - } - state = g_health_state; - if (g_health_mutex) { - xSemaphoreGive(g_health_mutex); - } - return state; -} - -// ---------------------------------------- -// 温度监控(定时器回调) -// ---------------------------------------- -static void temp_monitor_callback(void *arg) -{ - float temp = 0.0f; - esp_err_t err = temperature_sensor_get_celsius(s_temp_handle, &temp); - if (err != ESP_OK) { - DEBUG_WARN("TEMP", "读取温度失败: %s", esp_err_to_name(err)); - return; - } - - DEBUG_DBG("TEMP", "芯片温度: %.1f°C", temp); - - if (temp >= TEMP_CRITICAL) { - g_health_state = SYS_SAFE_SHUTDOWN; - DEBUG_ERR("TEMP", "!!! 危险 !!! 温度 %.1f°C >= %d°C,触发安全关机", temp, TEMP_CRITICAL); - DEBUG_ERR("TEMP", "健康状态变更为: SYS_SAFE_SHUTDOWN"); - } else if (temp >= TEMP_WARNING) { - if (g_health_state < SYS_WARNING) { - g_health_state = SYS_WARNING; - DEBUG_WARN("TEMP", "警告: 温度 %.1f°C >= %d°C", temp, TEMP_WARNING); - DEBUG_WARN("TEMP", "健康状态变更为: SYS_WARNING"); - } - } -} - -// ---------------------------------------- -// 电压监控(启动时调用一次) -// ---------------------------------------- -static void voltage_monitor_callback(void *arg) -{ - float voltage = 3.30f; // ESP32-C3 无内置电压传感器,使用估算 - DEBUG_DBG("VOLT", "VDD 电压: %.2fV (估算)", voltage); - - if (voltage < 2.3f) { - g_health_state = SYS_SAFE_SHUTDOWN; - DEBUG_ERR("VOLT", "!!! 危险 !!! 电压 %.2fV < 2.3V,触发安全关机", voltage); - DEBUG_ERR("VOLT", "健康状态变更为: SYS_SAFE_SHUTDOWN"); - } else if (voltage < 2.5f) { - if (g_health_state < SYS_WARNING) { - g_health_state = SYS_WARNING; - DEBUG_WARN("VOLT", "警告: 电压 %.2fV < 2.5V", voltage); - DEBUG_WARN("VOLT", "健康状态变更为: SYS_WARNING"); - } - } else { - DEBUG_INFO("VOLT", "电压正常: %.2fV", voltage); - } -} - -// ---------------------------------------- -// 温度电压监控任务入口 -// ---------------------------------------- -void health_monitor_task(void *pvParameters) -{ - DEBUG_INFO("MON", "=== 健康监控任务启动 ==="); - - // 初始化内置温度传感器(测量范围 20°C ~ 80°C) - temperature_sensor_config_t temp_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(20, 80); - esp_err_t err = temperature_sensor_install(&temp_config, &s_temp_handle); - if (err != ESP_OK) { - DEBUG_ERR("MON", "温度传感器初始化失败: %s", esp_err_to_name(err)); - } else { - ESP_ERROR_CHECK(temperature_sensor_enable(s_temp_handle)); - DEBUG_INFO("MON", "温度传感器已启用 (范围 20°C ~ 80°C)"); - } - - // 定时器,每5秒读取一次温度 - esp_timer_handle_t temp_timer; - esp_timer_create_args_t timer_args = {}; - timer_args.callback = &temp_monitor_callback; - timer_args.arg = nullptr; - timer_args.name = "temp_monitor"; - timer_args.dispatch_method = ESP_TIMER_TASK; - timer_args.skip_unhandled_events = false; - esp_timer_create(&timer_args, &temp_timer); - esp_timer_start_periodic(temp_timer, 5 * 1000000); // 5秒 - - DEBUG_INFO("MON", "温度监控定时器已启动 (间隔 5 秒)"); - - // 启动时检查一次电压 - voltage_monitor_callback(NULL); - - // 每5秒打印一次健康状态 - uint32_t loop_count = 0; - while (1) { - loop_count++; - - uint32_t free_heap = esp_get_free_heap_size(); - - const char *state_str; - switch (g_health_state) { - case SYS_NORMAL: state_str = "正常"; break; - case SYS_WARNING: state_str = "警告"; break; - case SYS_CRITICAL: state_str = "危险"; break; - case SYS_SAFE_SHUTDOWN: state_str = "安全关机"; break; - default: state_str = "未知"; break; - } - - DEBUG_INFO("MON", "--- 健康状态 #%lu ---", (unsigned long)loop_count); - DEBUG_INFO("MON", "健康状态: %s (%d)", state_str, g_health_state); - DEBUG_INFO("MON", "空闲堆: %lu 字节", (unsigned long)free_heap); - - // 堆内存警告 - if (free_heap < HEAP_WARNING_MIN) { - DEBUG_WARN("MON", "空闲堆内存不足: %lu < %d", (unsigned long)free_heap, HEAP_WARNING_MIN); - } - - DEBUG_INFO("MON", "==================="); - - vTaskDelay(pdMS_TO_TICKS(5000)); - } -} - -// ---------------------------------------- -// 健康事件日志 -// ---------------------------------------- -void health_log_event(const char *event, int value) -{ - DEBUG_INFO("HEALTH", "[健康事件] %s: %d", event, value); -} - -// ---------------------------------------- -// OTA 传输前的健康检查 -// ---------------------------------------- -health_status_t health_check_before_ota(void) -{ - health_status_t status = HEALTH_MON_OK; - - if (g_health_state >= SYS_SAFE_SHUTDOWN) { - DEBUG_WARN("HEALTH", "OTA 前检查: SYS_SAFE_SHUTDOWN,禁止传输"); - return HEALTH_MON_SHUTDOWN; - } else if (g_health_state >= SYS_CRITICAL) { - DEBUG_WARN("HEALTH", "OTA 前检查: SYS_CRITICAL,禁止传输"); - return HEALTH_MON_CRITICAL; - } else if (g_health_state >= SYS_WARNING) { - status = HEALTH_MON_WARNING; - DEBUG_WARN("HEALTH", "OTA 前检查: SYS_WARNING,继续传输但需注意"); - } - - wifi_ap_record_t ap_info; - if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) { - if (ap_info.rssi < WIFI_RSSI_CRITICAL) { - g_health_state = SYS_CRITICAL; - DEBUG_WARN("HEALTH", "OTA 前检查: WiFi RSSI %d < %d (危险阈值)", - ap_info.rssi, WIFI_RSSI_CRITICAL); - return HEALTH_MON_CRITICAL; - } else if (ap_info.rssi < WIFI_RSSI_NORMAL) { - if (status < HEALTH_MON_WARNING) { - status = HEALTH_MON_WARNING; - } - DEBUG_WARN("HEALTH", "OTA 前检查: WiFi RSSI %d < %d (警告阈值)", - ap_info.rssi, WIFI_RSSI_NORMAL); - } - } - - if (status == HEALTH_MON_OK) { - DEBUG_DBG("HEALTH", "OTA 前检查: 正常"); - } - - return status; -} - -health_status_t health_update_from_wifi_rssi(int8_t rssi) -{ - if (rssi < WIFI_RSSI_CRITICAL) { - g_health_state = SYS_CRITICAL; - DEBUG_WARN("HEALTH", "WiFi RSSI 危险: %d dBm < %d dBm", rssi, WIFI_RSSI_CRITICAL); - health_log_event("WIFI危险", rssi); - return HEALTH_MON_CRITICAL; - } else if (rssi < WIFI_RSSI_NORMAL) { - if (g_health_state < SYS_WARNING) { - g_health_state = SYS_WARNING; - DEBUG_WARN("HEALTH", "WiFi RSSI 警告: %d dBm < %d dBm", rssi, WIFI_RSSI_NORMAL); - health_log_event("WIFI警告", rssi); - } - return HEALTH_MON_WARNING; - } - - if (g_health_state == SYS_WARNING && rssi >= WIFI_RSSI_NORMAL) { - g_health_state = SYS_NORMAL; - DEBUG_INFO("HEALTH", "WiFi RSSI 恢复: %d dBm >= %d dBm", rssi, WIFI_RSSI_NORMAL); - health_log_event("WIFI恢复", rssi); - } - - return HEALTH_MON_OK; -} diff --git a/_assistant_backups/2026-05-04/health_monitor.h.bak_2026-05-04_2233 b/_assistant_backups/2026-05-04/health_monitor.h.bak_2026-05-04_2233 deleted file mode 100644 index 34574c5..0000000 --- a/_assistant_backups/2026-05-04/health_monitor.h.bak_2026-05-04_2233 +++ /dev/null @@ -1,37 +0,0 @@ -/* - * health_monitor.h - 系统健康监控头文件 - */ -#pragma once - -#include -#include "app_config.h" - -// T9.3: 健康状态等级 -typedef enum { - SYS_NORMAL = 0, // 正常 - SYS_WARNING = 1, // 警告 - SYS_CRITICAL = 2, // 危险 - SYS_SAFE_SHUTDOWN = 3 // 安全关机 -} health_state_t; - -typedef enum { - HEALTH_MON_OK = 0, - HEALTH_MON_WARNING = 1, - HEALTH_MON_CRITICAL = 2, - HEALTH_MON_SHUTDOWN = 3 -} health_status_t; - -// 外部共享状态(互斥锁保护) -extern health_state_t g_health_state; - -// WiFi RSSI 阈值(来自 app_config.h) -#define WIFI_RSSI_NORMAL WIFI_RSSI_WARNING -#define WIFI_RSSI_WEAK WIFI_RSSI_CRITICAL - -// 函数声明 -void health_monitor_init(void); -void health_monitor_task(void *pvParameters); -health_status_t health_check_before_ota(void); -void health_log_event(const char *event, int value); -health_state_t health_get_current(void); -health_status_t health_update_from_wifi_rssi(int8_t rssi); diff --git a/_assistant_backups/2026-05-04/wifi_ota.cpp.bak_2026-05-04_2132 b/_assistant_backups/2026-05-04/wifi_ota.cpp.bak_2026-05-04_2132 deleted file mode 100644 index 1581306..0000000 --- a/_assistant_backups/2026-05-04/wifi_ota.cpp.bak_2026-05-04_2132 +++ /dev/null @@ -1,972 +0,0 @@ -/* - * wifi_ota.c - WiFi OTA 服务器实现 - * T4.1 - T4.5 - */ -#include -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/semphr.h" -#include "esp_wifi.h" -#include "esp_netif.h" -#include "esp_http_server.h" -#include "esp_system.h" -#include "esp_timer.h" -#include "esp_ota_ops.h" -#include "nvs_flash.h" -#include "lwip/netdb.h" -#include "esp_netif.h" -#include "esp_netif_types.h" -#include "esp_event.h" -#include "esp_mac.h" - -#include "app_config.h" -#include "wifi_ota.h" -#include "ota_relay.h" -#include "health_monitor.h" -#include "monitor/debug_log.h" -#include "spi_master.h" -#include "cJSON.h" - -static ota_wifi_mode_t s_wifi_mode = OTA_WIFI_MODE_AP; -static bool s_relay_triggered = false; -static bool s_sta_connected = false; -static int s_sta_retry_count = 0; -static SemaphoreHandle_t s_sta_connected_sem = NULL; -static SemaphoreHandle_t s_sta_got_ip_sem = NULL; - -// ESP32 OTA 状态 -static bool s_esp32_ota_active = false; -static esp_ota_handle_t s_esp32_ota_handle = 0; -static const esp_partition_t *s_esp32_update_partition = NULL; -static size_t s_esp32_received = 0; - -// GD32 OTA 存储状态 -static bool s_gd32_storage_done = false; - -static httpd_handle_t s_http_server = NULL; - -static void wifi_set_static_ip(void); - -// ---------------------------------------- -// WiFi/IP 事件回调(事件驱动连接) -// ---------------------------------------- -static void wifi_event_handler(void* arg, esp_event_base_t event_base, - int32_t event_id, void* event_data) -{ - if (event_base == WIFI_EVENT) { - switch (event_id) { - case WIFI_EVENT_STA_START: - DEBUG_INFO("WIFI", "STA 已启动,开始连接..."); - esp_wifi_connect(); - break; - - case WIFI_EVENT_STA_CONNECTED: - DEBUG_INFO("WIFI", "STA 已关联到 AP"); - s_sta_connected = true; - s_sta_retry_count = 0; - if (s_sta_connected_sem) { - xSemaphoreGive(s_sta_connected_sem); - } - break; - - case WIFI_EVENT_STA_DISCONNECTED: { - wifi_event_sta_disconnected_t* disconn = (wifi_event_sta_disconnected_t*)event_data; - DEBUG_WARN("WIFI", "STA 断开, reason=%d, retry=%d", disconn->reason, s_sta_retry_count); - s_sta_connected = false; - - if (s_sta_retry_count < WIFI_STA_MAX_RETRY) { - s_sta_retry_count++; - DEBUG_INFO("WIFI", "重连第 %d 次...", s_sta_retry_count); - esp_wifi_connect(); - } else { - DEBUG_ERR("WIFI", "STA 重连失败 %d 次,放弃", s_sta_retry_count); - } - break; - } - - case WIFI_EVENT_AP_STACONNECTED: { - wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*)event_data; - DEBUG_INFO("WIFI", "AP: 客户端 " MACSTR " 连入, AID=%d", MAC2STR(event->mac), event->aid); - break; - } - - case WIFI_EVENT_AP_STADISCONNECTED: { - wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*)event_data; - DEBUG_INFO("WIFI", "AP: 客户端 " MACSTR " 离开, AID=%d, reason=%d", - MAC2STR(event->mac), event->aid, event->reason); - break; - } - - default: - break; - } - } else if (event_base == IP_EVENT) { - switch (event_id) { - case IP_EVENT_STA_GOT_IP: { - ip_event_got_ip_t* event = (ip_event_got_ip_t*)event_data; - DEBUG_INFO("WIFI", "STA 获取 IP: " IPSTR, IP2STR(&event->ip_info.ip)); - if (s_sta_got_ip_sem) { - xSemaphoreGive(s_sta_got_ip_sem); - } - break; - } - - default: - break; - } - } -} - -// ---------------------------------------- -// WiFi AP 模式初始化 -// ---------------------------------------- -static esp_err_t wifi_init_ap(void) -{ - DEBUG_INFO("WIFI", "=== 初始化 WiFi AP 模式 ==="); - - s_wifi_mode = OTA_WIFI_MODE_AP; - - // 创建 AP netif 并设置自定义 IP - esp_netif_t *ap_netif = esp_netif_get_handle_from_ifkey("WIFI_AP_DEF"); - if (ap_netif == NULL) { - ap_netif = esp_netif_create_default_wifi_ap(); - } - if (ap_netif != NULL) { - // 停止 DHCP 服务器(必须先停才能改 IP) - esp_netif_dhcps_stop(ap_netif); - - esp_netif_ip_info_t ip_info; - memset(&ip_info, 0, sizeof(ip_info)); - // 方案 A:SoftAP 使用 192.168.31.0/24 网段 - // ESP32 自己作为 AP 网关,电脑连入后拿到 192.168.31.x - inet_pton(AF_INET, "192.168.31.1", &ip_info.ip); - inet_pton(AF_INET, "192.168.31.1", &ip_info.gw); - inet_pton(AF_INET, "255.255.255.0", &ip_info.netmask); - ESP_ERROR_CHECK(esp_netif_set_ip_info(ap_netif, &ip_info)); - - // 重新启动 DHCP 服务器,给连接的客户端分配 IP - esp_netif_dhcps_start(ap_netif); - DEBUG_INFO("WIFI", "AP IP 已设置: 192.168.31.1"); - } - - wifi_config_t wifi_config = {}; - memcpy(wifi_config.ap.ssid, WIFI_AP_SSID, strlen(WIFI_AP_SSID) + 1); - wifi_config.ap.ssid_len = strlen(WIFI_AP_SSID); - memcpy(wifi_config.ap.password, WIFI_AP_PASSWORD, strlen(WIFI_AP_PASSWORD) + 1); - wifi_config.ap.max_connection = WIFI_AP_MAX_CONNECT; - wifi_config.ap.authmode = WIFI_AUTH_WPA_WPA2_PSK; - wifi_config.ap.channel = WIFI_AP_CHANNEL; - - DEBUG_INFO("WIFI", "AP SSID: %s, Channel: %d", WIFI_AP_SSID, WIFI_AP_CHANNEL); - - ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); - ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config)); - ESP_ERROR_CHECK(esp_wifi_start()); - - DEBUG_INFO("WIFI", "WiFi AP 已启动: %s", WIFI_AP_SSID); - DEBUG_INFO("WIFI", "AP IP 地址: 192.168.31.1"); - DEBUG_INFO("WIFI", "AP 网关地址: 192.168.31.1"); - DEBUG_INFO("WIFI", "最大连接数: %d", WIFI_AP_MAX_CONNECT); - - return ESP_OK; -} - -// ---------------------------------------- -// WiFi STA 模式初始化 -// ---------------------------------------- -static esp_err_t wifi_init_sta(void) -{ - DEBUG_INFO("WIFI", "=== 初始化 WiFi STA 模式 ==="); - - s_wifi_mode = OTA_WIFI_MODE_STA; - s_sta_retry_count = 0; - - // ★ 设置国家代码:中国,允许信道 1-13 - // ESP32 默认国家是 US(只允许 1-11),中国路由可能用 12/13 - wifi_country_t country = {}; - memcpy(country.cc, "CN", 3); - country.schan = 1; - country.nchan = 13; - country.policy = WIFI_COUNTRY_POLICY_MANUAL; - - wifi_config_t wifi_config = {}; - wifi_config.sta.scan_method = WIFI_ALL_CHANNEL_SCAN; - wifi_config.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL; - wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK; - wifi_config.sta.pmf_cfg.capable = true; - wifi_config.sta.pmf_cfg.required = false; - wifi_config.sta.listen_interval = 3; - memcpy(wifi_config.sta.ssid, WIFI_STA_SSID, strlen(WIFI_STA_SSID) + 1); - memcpy(wifi_config.sta.password, WIFI_STA_PASSWORD, strlen(WIFI_STA_PASSWORD) + 1); - - DEBUG_INFO("WIFI", "STA SSID: %s (全信道扫描, CN 国家代码)", WIFI_STA_SSID); - - ESP_ERROR_CHECK(esp_wifi_set_country(&country)); - ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); - ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); - - // 静态 IP 必须在 start/connect 之前配置,避免与 STA_START 自动连接发生竞态 - wifi_set_static_ip(); - - ESP_ERROR_CHECK(esp_wifi_start()); - ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); - - DEBUG_INFO("WIFI", "WiFi STA 启动, SSID: %s, 事件驱动连接...", WIFI_STA_SSID); - - return ESP_OK; -} - -// 等待 STA 连接成功并获取 IP(静态 IP 等 CONNECTED,DHCP 等 GOT_IP) -static esp_err_t wifi_wait_sta_connected(char *ip_str, size_t len) -{ - SemaphoreHandle_t wait_sem = WIFI_STA_USE_STATIC_IP ? s_sta_connected_sem : s_sta_got_ip_sem; - const char *wait_name = WIFI_STA_USE_STATIC_IP ? "STA_CONNECTED" : "STA_GOT_IP"; - - if (wait_sem == NULL) { - DEBUG_ERR("WIFI", "%s 信号量未创建", wait_name); - return ESP_ERR_INVALID_STATE; - } - - TickType_t timeout_ticks = pdMS_TO_TICKS(WIFI_STA_CONNECT_TIMEOUT_MS); - if (xSemaphoreTake(wait_sem, timeout_ticks) == pdTRUE) { - esp_netif_ip_info_t ip_info; - esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"); - if (netif != NULL && esp_netif_get_ip_info(netif, &ip_info) == ESP_OK) { - if (ip_str) { - snprintf(ip_str, len, IPSTR, IP2STR(&ip_info.ip)); - } - DEBUG_INFO("WIFI", "%s 等待成功, 当前 IP: " IPSTR, wait_name, IP2STR(&ip_info.ip)); - } - s_sta_connected = true; - return ESP_OK; - } - - DEBUG_ERR("WIFI", "等待 %s 超时 (%d ms), 重连次数: %d", wait_name, WIFI_STA_CONNECT_TIMEOUT_MS, s_sta_retry_count); - return ESP_ERR_TIMEOUT; -} - -// 设置 STA 静态 IP -static void wifi_set_static_ip(void) -{ - if (WIFI_STA_USE_STATIC_IP) { - esp_netif_t *sta_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"); - if (sta_netif) { - DEBUG_INFO("WIFI", "STA 使用静态 IP 模式,停止 DHCP Client"); - ESP_ERROR_CHECK(esp_netif_dhcpc_stop(sta_netif)); - - esp_netif_ip_info_t info; - memset(&info, 0, sizeof(info)); - inet_pton(AF_INET, WIFI_STA_STATIC_IP, &info.ip); - inet_pton(AF_INET, WIFI_STA_STATIC_GW, &info.gw); - inet_pton(AF_INET, WIFI_STA_STATIC_NETMASK, &info.netmask); - ESP_ERROR_CHECK(esp_netif_set_ip_info(sta_netif, &info)); - DEBUG_INFO("WIFI", "静态 IP 已设置: %s", WIFI_STA_STATIC_IP); - - // 设置 DNS - esp_netif_dns_info_t dns_info; - memset(&dns_info, 0, sizeof(dns_info)); - inet_pton(AF_INET, WIFI_STA_STATIC_DNS, &dns_info.ip.u_addr.ip4.addr); - ESP_ERROR_CHECK(esp_netif_set_dns_info(sta_netif, ESP_NETIF_DNS_MAIN, &dns_info)); - DEBUG_INFO("WIFI", "DNS 已设置: %s", WIFI_STA_STATIC_DNS); - } - } else { - DEBUG_INFO("WIFI", "使用 DHCP 获取 IP"); - } -} - -// ---------------------------------------- -// ESP32 OTA 内部函数 -// ---------------------------------------- -static esp_err_t esp32_ota_begin(size_t total_size) -{ - const esp_partition_t *boot_partition = esp_ota_get_running_partition(); - DEBUG_INFO("OTA", "当前启动分区: %s, size=%u, address=0x%lx", - boot_partition->label, boot_partition->size, boot_partition->address); - - s_esp32_update_partition = esp_partition_find_first( - ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_0, NULL); - if (s_esp32_update_partition == NULL || - s_esp32_update_partition->address == boot_partition->address) { - s_esp32_update_partition = esp_partition_find_first( - ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_1, NULL); - } - - if (s_esp32_update_partition == NULL) { - DEBUG_ERR("OTA", "找不到可用的 OTA 分区"); - return ESP_FAIL; - } - - DEBUG_INFO("OTA", "OTA 目标分区: %s, size=%u, address=0x%lx", - s_esp32_update_partition->label, - s_esp32_update_partition->size, - s_esp32_update_partition->address); - - if (total_size > s_esp32_update_partition->size) { - DEBUG_ERR("OTA", "固件太大: %u > 分区大小: %u", - total_size, s_esp32_update_partition->size); - return ESP_FAIL; - } - - esp_err_t err = esp_ota_begin(s_esp32_update_partition, total_size, &s_esp32_ota_handle); - if (err != ESP_OK) { - DEBUG_ERR("OTA", "esp_ota_begin 失败: %s", esp_err_to_name(err)); - s_esp32_update_partition = NULL; - return err; - } - - s_esp32_received = 0; - s_esp32_ota_active = true; - DEBUG_INFO("OTA", "ESP32 OTA 开始,目标分区: %s", s_esp32_update_partition->label); - return ESP_OK; -} - -static esp_err_t esp32_ota_write(const uint8_t *data, size_t len) -{ - if (!s_esp32_ota_active || s_esp32_update_partition == NULL) { - return ESP_ERR_INVALID_STATE; - } - esp_err_t err = esp_ota_write(s_esp32_ota_handle, data, len); - if (err == ESP_OK) { - s_esp32_received += len; - } - return err; -} - -// HTTP POST /ota/update - 完成 ESP32 OTA 并重启 -static esp_err_t esp32_ota_update_handler(httpd_req_t *req) -{ - (void)req; - if (!s_esp32_ota_active) { - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "OTA not started"); - return ESP_FAIL; - } - - DEBUG_INFO("OTA", "ESP32 OTA 完成, 共 %u 字节, 设置启动分区...", s_esp32_received); - - esp_err_t err = esp_ota_end(s_esp32_ota_handle); - if (err != ESP_OK) { - DEBUG_ERR("OTA", "esp_ota_end 失败: %s", esp_err_to_name(err)); - s_esp32_ota_active = false; - httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, esp_err_to_name(err)); - return ESP_FAIL; - } - - err = esp_ota_set_boot_partition(s_esp32_update_partition); - if (err != ESP_OK) { - DEBUG_ERR("OTA", "esp_ota_set_boot_partition 失败: %s", esp_err_to_name(err)); - s_esp32_ota_active = false; - httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, esp_err_to_name(err)); - return ESP_FAIL; - } - - s_esp32_ota_active = false; - DEBUG_INFO("OTA", "ESP32 OTA 完成,重启中..."); - - const char resp[] = "{\"status\": \"ok\", \"message\": \"OTA finished, rebooting\"}"; - httpd_resp_set_type(req, "application/json"); - httpd_resp_send(req, resp, strlen(resp)); - - vTaskDelay(pdMS_TO_TICKS(500)); - esp_restart(); - return ESP_OK; -} - -// HTTP GET /ota/update - 查询 ESP32 OTA 状态 -static esp_err_t esp32_ota_status_handler(httpd_req_t *req) -{ - char resp[128]; - if (s_esp32_ota_active) { - snprintf(resp, sizeof(resp), - "{\"active\": true, \"received\": %u, \"partition\": \"%s\"}", - (unsigned int)s_esp32_received, - s_esp32_update_partition ? s_esp32_update_partition->label : "null"); - } else { - snprintf(resp, sizeof(resp), "{\"active\": false}"); - } - httpd_resp_set_type(req, "application/json"); - httpd_resp_send(req, resp, strlen(resp)); - return ESP_OK; -} - -// ---------------------------------------- -// 统一 /ota/upload 处理器(自动识别目标) -// ---------------------------------------- -static esp_err_t ota_upload_handler(httpd_req_t *req) -{ - DEBUG_INFO("WIFI", "=== HTTP: POST /ota/upload ==="); - DEBUG_INFO("WIFI", "收到固件上传请求, 内容长度: %d 字节", req->content_len); - - // 先读取一小段判断是 JSON 还是 binary - char header[256]; - int header_read = httpd_req_recv(req, header, sizeof(header) - 1); - if (header_read <= 0) { - httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "接收失败"); - return ESP_FAIL; - } - header[header_read] = '\0'; - - bool is_esp32_ota = false; - uint32_t total_size = 0; - - // 尝试解析 JSON - if (header_read > 0 && header[0] == '{') { - cJSON *root = cJSON_Parse(header); - if (root) { - cJSON *type_item = cJSON_GetObjectItem(root, "type"); - if (type_item && cJSON_IsString(type_item) && - strcmp(cJSON_GetStringValue(type_item), "esp32") == 0) { - is_esp32_ota = true; - DEBUG_INFO("WIFI", "检测到 ESP32 OTA"); - } - cJSON *size_item = cJSON_GetObjectItem(root, "total_size"); - if (size_item) { - total_size = (uint32_t)cJSON_GetNumberValue(size_item); - } - cJSON_Delete(root); - } - } - - if (is_esp32_ota) { - // --- ESP32 OTA --- - if (total_size == 0) { - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "missing total_size"); - return ESP_FAIL; - } - - esp_err_t err = esp32_ota_begin(total_size); - if (err != ESP_OK) { - httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "OTA begin failed"); - return ESP_FAIL; - } - - // 先处理已读的 header - err = esp32_ota_write((const uint8_t *)header, header_read); - if (err != ESP_OK) { - DEBUG_ERR("WIFI", "OTA write 失败"); - return ESP_FAIL; - } - - // 继续读剩余数据 - size_t total_received = header_read; - while (total_received < req->content_len) { - size_t to_read = req->content_len - total_received; - if (to_read > 4096) to_read = 4096; - - int ret = httpd_req_recv(req, header, to_read); - if (ret <= 0) break; - - esp_err_t err = esp32_ota_write((const uint8_t *)header, ret); - if (err != ESP_OK) break; - total_received += ret; - - if (total_size > 0 && s_esp32_received % (total_size / 10 + 1) == 0) { - DEBUG_INFO("WIFI", "ESP32 OTA 进度: %u / %u (%u%%)", - (unsigned int)s_esp32_received, total_size, - (unsigned int)((s_esp32_received * 100) / total_size)); - } - } - - DEBUG_INFO("WIFI", "ESP32 OTA 存储完成: %u 字节", (unsigned int)s_esp32_received); - const char resp[] = "{\"status\": \"ok\", \"message\": \"stored, call POST /ota/update to finish\", \"target\": \"esp32\"}"; - httpd_resp_set_type(req, "application/json"); - httpd_resp_send(req, resp, strlen(resp)); - return ESP_OK; - } - - // --- GD32 OTA(原逻辑)--- - if (req->content_len > OTA_MAX_FIRMWARE_SIZE) { - DEBUG_ERR("WIFI", "固件太大: %d > %d", req->content_len, OTA_MAX_FIRMWARE_SIZE); - httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "固件太大"); - return ESP_FAIL; - } - - uint8_t *buf = (uint8_t *)malloc(OTA_BLOCK_SIZE); - int received = 0; - - // 先处理已读的 header - if (header_read >= 6) { - uint16_t seq = header[0] | (header[1] << 8); - uint32_t fw_size = header[2] | (header[3] << 8) | (header[4] << 16) | (header[5] << 24); - if (seq == 0) { - ota_meta_t meta = { .total_size = fw_size, .stored_size = 0, .last_seq = 0, - .chunk_count = (fw_size + 4095) / 4096, .checksum = {0} }; - meta_save(&meta); - DEBUG_INFO("WIFI", "开始 GD32 固件存储: %u 字节", fw_size); - } - } - - // 继续读剩余数据 - while (received + header_read < req->content_len) { - size_t to_read = req->content_len - received - header_read; - if (to_read > OTA_BLOCK_SIZE) to_read = OTA_BLOCK_SIZE; - - int ret = httpd_req_recv(req, (char *)buf, to_read); - if (ret <= 0) break; - - uint32_t flash_offset = ((received + header_read) / OTA_BLOCK_SIZE) * OTA_BLOCK_SIZE; - esp_err_t err = flash_write_chunk(flash_offset, buf, ret); - if (err == ESP_OK) { - meta_update(flash_offset + ret, (received + header_read) / OTA_BLOCK_SIZE); - } - received += ret; - - uint32_t progress = ((received + header_read) * 100) / req->content_len; - if (progress % 10 == 0) { - DEBUG_INFO("WIFI", "GD32 上传进度: %d / %d (%u%%)", - received + header_read, req->content_len, progress); - } - } - - free(buf); - DEBUG_INFO("WIFI", "GD32 固件存储完成: %d 字节", received + header_read); - - ota_meta_t meta; - meta_load(&meta); - if (meta.stored_size >= meta.total_size) { - s_gd32_storage_done = true; - DEBUG_INFO("WIFI", "GD32 固件已存储,触发转发..."); - wifi_ota_signal_relay(); - } - - const char resp[] = "{\"status\": \"ok\", \"progress\": 100, \"target\": \"gd32\"}"; - httpd_resp_set_type(req, "application/json"); - httpd_resp_send(req, resp, strlen(resp)); - return ESP_OK; -} - -// HTTP POST /ota/relay 处理器 -static esp_err_t ota_relay_trigger_handler(httpd_req_t *req) -{ - (void)req; - DEBUG_INFO("WIFI", "=== HTTP: POST /ota/relay ==="); - DEBUG_INFO("WIFI", "收到触发 OTA 中继请求"); - wifi_ota_signal_relay(); - - const char resp[] = "{\"status\": \"ok\", \"message\": \"OTA relay triggered\"}"; - httpd_resp_set_type(req, "application/json"); - httpd_resp_send(req, resp, strlen(resp)); - - return ESP_OK; -} - -// HTTP GET /ota/status 处理器 -static esp_err_t ota_status_handler(httpd_req_t *req) -{ - DEBUG_INFO("WIFI", "=== HTTP: GET /ota/status ==="); - - ota_meta_t meta; - meta_load(&meta); - - char resp[256]; - int len = snprintf(resp, sizeof(resp), - "{\"total_size\": %lu, \"stored_size\": %lu, \"last_seq\": %lu, " - "\"storage_done\": %s, \"relay_state\": %d}", - (unsigned long)meta.total_size, (unsigned long)meta.stored_size, - (unsigned long)meta.last_seq, - s_gd32_storage_done ? "true" : "false", - g_relay_state); - - DEBUG_INFO("WIFI", "OTA 状态: total=%lu, stored=%lu, done=%s", - (unsigned long)meta.total_size, (unsigned long)meta.stored_size, - s_gd32_storage_done ? "true" : "false"); - - httpd_resp_set_type(req, "application/json"); - httpd_resp_send(req, resp, len); - - return ESP_OK; -} - -// HTTP GET /health 处理器 -static esp_err_t health_handler(httpd_req_t *req) -{ - DEBUG_INFO("WIFI", "=== HTTP: GET /health ==="); - - health_state_t state = health_get_current(); - uint32_t free_heap = esp_get_free_heap_size(); - - char resp[128]; - int len = snprintf(resp, sizeof(resp), - "{\"health\": %d, \"free_heap\": %lu}", - state, (unsigned long)free_heap); - - DEBUG_INFO("WIFI", "健康状态: %d, 空闲堆: %lu", state, (unsigned long)free_heap); - - httpd_resp_set_type(req, "application/json"); - httpd_resp_send(req, resp, len); - - return ESP_OK; -} - -// HTTP POST /can/send 处理器 -// 上位机发送 CAN 帧,经 ESP32 转发到 GD32 -// 请求体: {"ch": 0, "id": 123456, "data": "0102030405060708", "dlc": 8} -// data 为 hex 字符串(可不填,自动用 dlc 决定实际发送长度) -static esp_err_t can_send_handler(httpd_req_t *req) -{ - char buf[256] = {0}; - int ret = httpd_req_recv(req, buf, sizeof(buf) - 1); - if (ret <= 0) { - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "接收数据失败"); - return ESP_FAIL; - } - buf[ret] = '\0'; - - cJSON *root = cJSON_Parse(buf); - if (!root) { - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "JSON 解析失败"); - return ESP_FAIL; - } - - cJSON *ch_item = cJSON_GetObjectItem(root, "ch"); - cJSON *id_item = cJSON_GetObjectItem(root, "id"); - cJSON *data_item = cJSON_GetObjectItem(root, "data"); - cJSON *dlc_item = cJSON_GetObjectItem(root, "dlc"); - - if (!ch_item || !id_item) { - cJSON_Delete(root); - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "缺少 ch 或 id 字段"); - return ESP_FAIL; - } - - can_channel_t ch = (can_channel_t)cJSON_GetNumberValue(ch_item); - uint32_t ext_id = (uint32_t)cJSON_GetNumberValue(id_item); - uint8_t dlc = dlc_item ? (uint8_t)cJSON_GetNumberValue(dlc_item) : 0; - - uint8_t tx_data[CAN_MAX_DLC] = {0}; - uint8_t tx_dlc = 0; - - if (data_item && cJSON_IsString(data_item)) { - const char *hex_str = cJSON_GetStringValue(data_item); - size_t hex_len = strlen(hex_str); - for (size_t i = 0; i < hex_len / 2 && i < CAN_MAX_DLC; i++) { - char byte_str[3] = { hex_str[i * 2], hex_str[i * 2 + 1], '\0' }; - tx_data[i] = (uint8_t)strtol(byte_str, NULL, 16); - tx_dlc++; - } - } - - if (tx_dlc == 0 && dlc > 0) { - tx_dlc = (dlc > CAN_MAX_DLC) ? CAN_MAX_DLC : dlc; - } - - cJSON_Delete(root); - - // 发送 CAN 帧到 GD32 - uint8_t resp_data[CAN_MAX_DLC] = {0}; - int resp_len = gd32_can_send(ch, ext_id, tx_data, tx_dlc, - resp_data, sizeof(resp_data), 1000); - - char resp[512]; - if (resp_len >= 0) { - // 将响应 data 转为 hex 字符串 - char resp_hex[CAN_MAX_DLC * 2 + 1] = {0}; - for (int i = 0; i < resp_len; i++) { - sprintf(&resp_hex[i * 2], "%02X", resp_data[i]); - } - snprintf(resp, sizeof(resp), - "{\"status\": \"ok\", \"ch\": %d, \"id\": %lu, \"sent\": %d, \"recv_len\": %d, \"recv\": \"%s\"}", - ch, (unsigned long)ext_id, tx_dlc, resp_len, resp_hex); - } else { - snprintf(resp, sizeof(resp), - "{\"status\": \"error\", \"code\": %d}", resp_len); - } - - httpd_resp_set_type(req, "application/json"); - httpd_resp_send(req, resp, strlen(resp)); - return ESP_OK; -} - -// HTTP GET /can/status 处理器 -static esp_err_t can_status_handler(httpd_req_t *req) -{ - char resp[128]; - snprintf(resp, sizeof(resp), - "{\"channels\": 3, \"max_dlc\": %d, \"spi_init\": %s}", - CAN_MAX_DLC, - g_spi_device != NULL ? "true" : "false"); - httpd_resp_set_type(req, "application/json"); - httpd_resp_send(req, resp, strlen(resp)); - return ESP_OK; -} - -// HTTP GET /wifi/status 处理器 -static esp_err_t wifi_status_handler(httpd_req_t *req) -{ - char resp[256]; - char ip_str[16] = "0.0.0.0"; - int8_t rssi = -127; - - if (s_wifi_mode == OTA_WIFI_MODE_STA && s_sta_connected) { - esp_netif_ip_info_t ip_info; - esp_netif_t *sta_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"); - if (sta_netif && esp_netif_get_ip_info(sta_netif, &ip_info) == ESP_OK) { - snprintf(ip_str, sizeof(ip_str), IPSTR, IP2STR(&ip_info.ip)); - } - wifi_ap_record_t ap_info; - if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) { - rssi = ap_info.rssi; - } - } - - int len = snprintf(resp, sizeof(resp), - "{\"mode\": \"%s\", \"ip\": \"%s\", \"rssi\": %d, \"connected\": %s}", - s_wifi_mode == OTA_WIFI_MODE_AP ? "AP" : "STA", - ip_str, rssi, - (s_wifi_mode == OTA_WIFI_MODE_STA) ? (s_sta_connected ? "true" : "false") : "N/A"); - - httpd_resp_set_type(req, "application/json"); - httpd_resp_send(req, resp, len); - - return ESP_OK; -} - -// ---------------------------------------- -// 启动 HTTP OTA 服务器 -// ---------------------------------------- -static esp_err_t http_ota_server_start(void) -{ - httpd_config_t config = HTTPD_DEFAULT_CONFIG(); - config.server_port = HTTP_SERVER_PORT; - config.stack_size = HTTP_SERVER_STACK_SIZE; - config.max_uri_handlers = 16; // 默认只有8个槽位,我们有9个端点,不够 - - DEBUG_INFO("WIFI", "=== 启动 HTTP OTA 服务器 ==="); - DEBUG_INFO("WIFI", "HTTP 服务器端口: %d", config.server_port); - - esp_err_t err = httpd_start(&s_http_server, &config); - if (err != ESP_OK) { - DEBUG_ERR("WIFI", "HTTP 服务器启动失败: %s", esp_err_to_name(err)); - return err; - } - - httpd_uri_t upload_uri = { - .uri = "/ota/upload", - .method = HTTP_POST, - .handler = ota_upload_handler, - .user_ctx = NULL - }; - httpd_register_uri_handler(s_http_server, &upload_uri); - - httpd_uri_t relay_uri = { - .uri = "/ota/relay", - .method = HTTP_POST, - .handler = ota_relay_trigger_handler, - .user_ctx = NULL - }; - httpd_register_uri_handler(s_http_server, &relay_uri); - - httpd_uri_t status_uri = { - .uri = "/ota/status", - .method = HTTP_GET, - .handler = ota_status_handler, - .user_ctx = NULL - }; - httpd_register_uri_handler(s_http_server, &status_uri); - - httpd_uri_t esp32_update_uri = { - .uri = "/ota/update", - .method = HTTP_POST, - .handler = esp32_ota_update_handler, - .user_ctx = NULL - }; - httpd_register_uri_handler(s_http_server, &esp32_update_uri); - - httpd_uri_t esp32_status_uri = { - .uri = "/ota/esp32_status", - .method = HTTP_GET, - .handler = esp32_ota_status_handler, - .user_ctx = NULL - }; - httpd_register_uri_handler(s_http_server, &esp32_status_uri); - - httpd_uri_t health_uri = { - .uri = "/health", - .method = HTTP_GET, - .handler = health_handler, - .user_ctx = NULL - }; - httpd_register_uri_handler(s_http_server, &health_uri); - - httpd_uri_t wifi_status_uri = { - .uri = "/wifi/status", - .method = HTTP_GET, - .handler = wifi_status_handler, - .user_ctx = NULL - }; - httpd_register_uri_handler(s_http_server, &wifi_status_uri); - - // CAN 帧转发 - httpd_uri_t can_send_uri = { - .uri = "/can/send", - .method = HTTP_POST, - .handler = can_send_handler, - .user_ctx = NULL - }; - httpd_register_uri_handler(s_http_server, &can_send_uri); - - httpd_uri_t can_status_uri = { - .uri = "/can/status", - .method = HTTP_GET, - .handler = can_status_handler, - .user_ctx = NULL - }; - httpd_register_uri_handler(s_http_server, &can_status_uri); - - DEBUG_INFO("WIFI", "HTTP 服务器已启动"); - DEBUG_INFO("WIFI", "注册端点: /ota/upload, /ota/relay, /ota/update, /ota/status, /ota/esp32_status, /health, /wifi/status, /can/send, /can/status"); - - return ESP_OK; -} - -// ---------------------------------------- -// 公开 API -// ---------------------------------------- -void wifi_ota_signal_relay(void) -{ - if (s_relay_triggered) { - DEBUG_WARN("WIFI", "转发已经触发过"); - return; - } - - s_relay_triggered = true; - - if (g_ota_dispatch_queue) { - uint32_t msg = 1; - xQueueSend(g_ota_dispatch_queue, &msg, pdMS_TO_TICKS(100)); - DEBUG_INFO("WIFI", "OTA 调度信号已发送到中继任务"); - } -} - -ota_wifi_mode_t wifi_get_mode(void) -{ - return s_wifi_mode; -} - -int8_t wifi_get_rssi(void) -{ - if (s_wifi_mode != OTA_WIFI_MODE_STA || !s_sta_connected) { - return -127; - } - wifi_ap_record_t ap_info; - if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) { - return ap_info.rssi; - } - return -127; -} - -bool wifi_is_connected(void) -{ - return s_sta_connected; -} - -// ---------------------------------------- -// WiFi OTA FreeRTOS 任务 -// ---------------------------------------- -void wifi_ota_task(void *pvParameters) -{ - (void)pvParameters; - - DEBUG_INFO("WIFI", "=== WiFi OTA 任务启动 ==="); - - // 初始化 NVS - esp_err_t err = nvs_flash_init(); - if (err != ESP_OK) { - DEBUG_ERR("WIFI", "NVS 初始化失败: %s", esp_err_to_name(err)); - } else { - DEBUG_INFO("WIFI", "NVS 初始化成功"); - } - - // 初始化网络接口(可安全重复调用) - esp_netif_init(); - DEBUG_INFO("WIFI", "网络接口初始化成功"); - - // 创建默认事件循环(必须在 esp_wifi_init 之前) - esp_err_t ev_ret = esp_event_loop_create_default(); - if (ev_ret == ESP_OK) { - DEBUG_INFO("WIFI", "默认事件循环已创建"); - } else if (ev_ret == ESP_ERR_INVALID_STATE) { - DEBUG_INFO("WIFI", "默认事件循环已存在,跳过"); - } else { - DEBUG_ERR("WIFI", "创建事件循环失败: %s", esp_err_to_name(ev_ret)); - } - - // ★ 创建信号量(用于事件驱动等待) - s_sta_connected_sem = xSemaphoreCreateBinary(); - s_sta_got_ip_sem = xSemaphoreCreateBinary(); - if (!s_sta_connected_sem || !s_sta_got_ip_sem) { - DEBUG_ERR("WIFI", "创建信号量失败"); - } - - // ★ 注册 WiFi/IP 事件回调(必须在 esp_wifi_init 之前) - ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL)); - ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL)); - DEBUG_INFO("WIFI", "WiFi/IP 事件回调已注册"); - - // ★ 创建 STA netif(必须在 esp_wifi_init 之前!) - // esp_netif_create_default_wifi_sta() 注册默认 STA 事件处理 - // 如果在 esp_wifi_init() 之后调用,部分内部 handler 不会正确绑定 - esp_netif_t *sta_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"); - if (sta_netif == NULL) { - sta_netif = esp_netif_create_default_wifi_sta(); - if (sta_netif == NULL) { - DEBUG_ERR("WIFI", "创建 STA netif 失败"); - } else { - DEBUG_INFO("WIFI", "STA netif 已创建"); - } - } else { - DEBUG_INFO("WIFI", "STA netif 已存在,跳过创建"); - } - - // 初始化 WiFi driver(必须在 netif 创建之后) - wifi_init_config_t wifi_cfg = WIFI_INIT_CONFIG_DEFAULT(); - ESP_ERROR_CHECK(esp_wifi_init(&wifi_cfg)); - DEBUG_INFO("WIFI", "WiFi driver 初始化成功"); - - // STA 优先,失败自动切 AP(事件驱动连接 + 自动重连) - // 注意:CONFIG_WIFI_MODE 是 constexpr,不能用 #if(预处理器不认识 C++ 变量) - if (CONFIG_WIFI_MODE == 1) { - DEBUG_INFO("WIFI", "工作模式: STA (优先)"); - err = wifi_init_sta(); - if (err == ESP_OK) { - char ip_str[16] = {0}; - - // 等待连接成功 - err = wifi_wait_sta_connected(ip_str, sizeof(ip_str)); - if (err == ESP_OK) { - DEBUG_INFO("WIFI", "STA 已连接, IP: %s", ip_str); - } else { - DEBUG_WARN("WIFI", "STA 连接超时,切换到 AP 模式..."); - esp_wifi_stop(); - s_wifi_mode = OTA_WIFI_MODE_AP; - wifi_init_ap(); - } - } - } else { - DEBUG_INFO("WIFI", "工作模式: AP"); - wifi_init_ap(); - } - - // 启动 HTTP 服务器 - err = http_ota_server_start(); - if (err != ESP_OK) { - DEBUG_ERR("WIFI", "HTTP 服务器启动失败"); - } - - DEBUG_INFO("WIFI", "=== WiFi OTA 服务器就绪 ==="); - - // 定期检查 WiFi 状态 - while (1) { - int8_t rssi = wifi_get_rssi(); - if (rssi != -127) { - health_update_from_wifi_rssi(rssi); - - if (rssi < WIFI_RSSI_CRITICAL) { - DEBUG_WARN("WIFI", "WiFi RSSI 危险: %d dBm (阈值: %d)", rssi, WIFI_RSSI_CRITICAL); - } else if (rssi < WIFI_RSSI_WARNING) { - DEBUG_WARN("WIFI", "WiFi RSSI 较弱: %d dBm", rssi); - } - } - - vTaskDelay(pdMS_TO_TICKS(10000)); - } -} diff --git a/docs/COMMUNICATION_AUDIT_2026-05-05.md b/docs/COMMUNICATION_AUDIT_2026-05-05.md new file mode 100644 index 0000000..e8eb630 --- /dev/null +++ b/docs/COMMUNICATION_AUDIT_2026-05-05.md @@ -0,0 +1,74 @@ +# ESP32_COMM 通信梳理(2026-05-05) + +## 结论 + +当前“ESP32 ↔ 上位机(tools WebUI)”的主通信方式是 **HTTP**,不是 UDP。 + +### WebUI 实际在用的接口 + +- `GET /system/status`:每 1 秒轮询一次,用于在线状态、WiFi、heap、温度、电压、OTA 状态展示 +- `GET /logs`:每 1 秒轮询一次,用于拉取并清空日志环形缓冲区 +- `POST /can/send`:发送 CAN 请求到 GD32 +- `POST /ota/upload` / `POST /ota/relay` / `POST /ota/update`:OTA 相关 + +## 日志链路 + +所有 `DEBUG_*` 日志目前会并行写入三处: + +1. **UART0** + - 本地串口调试 +2. **UDP 单播** → `DEBUG_LOG_IP:DEBUG_LOG_PORT` + - 可供独立 UDP 调试接收端使用 +3. **内部环形缓冲区** + - 被 `GET /logs` 读取 + - 这是当前 WebUI 日志面板真正使用的通道 + +## 为什么 `{"t":"hb"}` 是多余的 + +之前在 `wifi_ota_task()` 里,每 3 秒会发送一次: + +```json +{"t":"hb"} +``` + +它是通过 `debug_log_send()` 发出的,所以会同时进入: +- UART +- UDP +- `/logs` 对应的环形缓冲区 + +这会导致 WebUI 把它显示成一条普通日志: + +- level: `I` +- tag: `ESP32` +- message: `{"t":"hb"}` + +但 WebUI 本来已经每秒轮询 `/system/status`,完全可以判断设备在线,不需要再靠这种“伪日志心跳”。 + +## 已处理 + +已删除 `wifi_ota_task()` 中每 3 秒发送一次的 `{"t":"hb"}`。 + +## 现在是否还有重复/不必要通信 + +### 对 WebUI 来说 + +有两条并行链路,但用途不同,不算重复: + +- `/system/status`:结构化状态 +- `/logs`:文本日志 + +这两条都保留是合理的。 + +### 对底层日志输出来说 + +日志同时发 UART + UDP + ring buffer,属于“一份日志三路镜像”。 + +这对调试友好,但从“最简设计”角度看,**UDP 在当前 WebUI 场景里不是必需的**。 + +也就是说: + +- **必须保留:** ring buffer + `/logs` +- **建议保留:** UART0 +- **可选保留:** UDP 6666 + +如果后续你想继续精简,可以做“方案2”:把 UDP 也做成可关闭,甚至默认关闭。 diff --git a/main/core/main.cpp b/main/core/main.cpp index 347b43d..cef0fb1 100644 --- a/main/core/main.cpp +++ b/main/core/main.cpp @@ -20,9 +20,15 @@ #include "transport/protocol.h" #include "monitor/debug_log.h" -// 状态监控任务 +/** + * @brief 状态监控任务 + * @param pvParameters + */ static void status_monitor_task(void *pvParameters); +/** + * @brief 程序主入口,初始化系统组件并创建各 FreeRTOS 任务。 + */ extern "C" void app_main(void) { esp_chip_info_t chip_info; @@ -33,12 +39,10 @@ extern "C" void app_main(void) ESP_ERROR_CHECK(esp_event_loop_create_default()); // 初始化调试日志(UART0 先就绪,UDP socket 延迟到首次发送时创建) - // 必须在 wifi_early_init 之前,这样 WiFi 初始化日志也能输出到上位机 debug_log_init(); // ★ WiFi 提前初始化(调度器启动前) // 包括:NVS → netif 创建 → event handler 注册 → WiFi driver init → 配置 → connect - // 连接尝试立即开始,事件排队等调度器启动后处理 bool wifi_sta_ok = wifi_early_init(); health_monitor_init(); @@ -68,11 +72,9 @@ extern "C" void app_main(void) DEBUG_INFO("MAIN", "健康监控任务已创建"); } - // 初始化协议层(CRC、AES) protocol_init(); DEBUG_INFO("MAIN", "协议层初始化完成"); - // 初始化 SPI 总线 esp_err_t ret = spi_master_init(); if (ret != ESP_OK) { DEBUG_ERR("MAIN", "SPI 总线初始化失败: %d", ret); @@ -153,7 +155,10 @@ extern "C" void app_main(void) // ============================================================ // 状态监控任务 - 每 10 秒打印系统状态 -// ============================================================ +/** + * @brief 周期性打印系统运行状态,包括内存、WiFi 和 OTA 中继信息。 + * @param pvParameters + */ static void status_monitor_task(void *pvParameters) { (void)pvParameters; @@ -164,7 +169,6 @@ static void status_monitor_task(void *pvParameters) while (1) { loop_count++; - // 获取系统信息 uint32_t free_heap = esp_get_free_heap_size(); uint32_t min_free_heap = esp_get_minimum_free_heap_size(); health_state_t health = health_get_current(); diff --git a/main/monitor/debug_log.cpp b/main/monitor/debug_log.cpp index 7c3dec6..c1ceab1 100644 --- a/main/monitor/debug_log.cpp +++ b/main/monitor/debug_log.cpp @@ -1,16 +1,18 @@ /* - * debug_log.c - WiFi 调试日志输出实现 + * debug_log.c - 调试日志输出实现 + * + * 日志会同时写入三处: + * 1. UART0 串口(本地串口调试) + * 2. UDP 单播到指定上位机(独立 UDP 调试接收端可选) + * 3. 内部环形缓冲区(供 HTTP /logs 拉取,WebUI 实际使用这一条) * - * 通过 UDP socket 将调试日志发送到上位机 - * 上位机需监听 : - * - * 支持双通道输出:UDP + UART0 串口 * UART0: TX=GPIO21, RX=GPIO20, 115200 波特率 + * UDP: : * * 时序说明: * esp_netif_init() 创建 tcpip 线程后才可安全使用 socket * 本模块在首次 debug_log_send() 时才创建 socket(延迟创建) - * WiFi 连接前 sendto() 会失败(无路由),日志仅走 UART + * WiFi 连接前 sendto() 会失败(无路由),日志仍可写入 UART 与 HTTP 日志缓冲 * WiFi 连接后 UDP 立即生效,无需额外操作 */ #include @@ -28,10 +30,14 @@ static const char *TAG = "DEBUG_LOG"; +/** @brief 当前调试日志过滤级别,运行时可动态调整。 */ debug_log_level_t g_debug_log_level = DEBUG_LOG_ALL; +/** @brief UDP 调试日志 socket 句柄,首次发送时延迟创建。 */ static int s_udp_sock = -1; +/** @brief UDP 调试日志目标地址。 */ static struct sockaddr_in s_debug_addr; +/** @brief 日志模块初始化标志。 */ static bool s_initialized = false; // ============================================================ @@ -43,9 +49,13 @@ static char s_log_ring[LOG_RING_SLOTS][LOG_RING_SIZE]; static int s_log_ring_lens[LOG_RING_SLOTS]; static int s_log_ring_head = 0; // 下次写入位置 static int s_log_ring_count = 0; // 已存条数(超过 LOG_RING_SLOTS 后不再增长,覆盖旧数据) +/** @brief 环形缓冲区互斥锁,保护多线程并发读写。 */ static SemaphoreHandle_t s_log_ring_mutex = NULL; -// 获取日志缓冲区中的条数 +/** + * @brief 获取日志缓冲区中的条数 + * @return 当前日志条数。 + */ int debug_log_ring_count(void) { if (!s_log_ring_mutex) return 0; @@ -94,7 +104,6 @@ char *debug_log_ring_drain_json(int *out_len) if (i > 0) json[pos++] = ','; - // JSON 转义日志内容 pos += snprintf(json + pos, buf_size - pos, "\""); for (int j = 0; j < len && pos < (int)buf_size - 10; j++) { char c = s_log_ring[idx][j]; @@ -116,6 +125,7 @@ char *debug_log_ring_drain_json(int *out_len) // 清空缓冲区 s_log_ring_head = 0; + /** @brief 环形缓冲区中已存储的日志条数。 */ s_log_ring_count = 0; xSemaphoreGive(s_log_ring_mutex); @@ -124,7 +134,11 @@ char *debug_log_ring_drain_json(int *out_len) return json; } -// 写入一条日志到环形缓冲区 +/** + * @brief 写入一条日志到环形缓冲区 + * @param data + * @param len + */ static void log_ring_push(const char *data, int len) { if (!s_log_ring_mutex || len <= 0) return; @@ -138,6 +152,7 @@ static void log_ring_push(const char *data, int len) s_log_ring[s_log_ring_head][copy_len] = '\0'; s_log_ring_lens[s_log_ring_head] = copy_len; + /** @brief 环形缓冲区下一个写入位置。 */ s_log_ring_head = (s_log_ring_head + 1) % LOG_RING_SLOTS; if (s_log_ring_count < LOG_RING_SLOTS) s_log_ring_count++; @@ -146,12 +161,16 @@ static void log_ring_push(const char *data, int len) // UART0 状态 static bool s_uart_initialized = false; +/** @brief 调试串口使用的 UART 端口号。 */ static uart_port_t s_uart_port = UART_NUM_0; // ============================================================ // UART0 串口调试 // ============================================================ - +/** + * @brief 初始化 UART0 调试串口,配置波特率和引脚映射。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t uart_debug_init(void) { if (s_uart_initialized) { @@ -187,6 +206,7 @@ esp_err_t uart_debug_init(void) return ret; } + /** @brief UART0 初始化标志。 */ s_uart_initialized = true; ESP_LOGI(TAG, "UART0 调试输出已初始化 (TX=GPIO%d, RX=GPIO%d, %d baud)", UART_DEBUG_TX_GPIO, UART_DEBUG_RX_GPIO, UART_DEBUG_BAUD_RATE); @@ -194,6 +214,12 @@ esp_err_t uart_debug_init(void) return ESP_OK; } +/** + * @brief 通过 UART0 发送一段原始字节数据。 + * @param data 待发送的数据指针。 + * @param len 数据长度。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t uart_debug_send(const uint8_t *data, size_t len) { if (!s_uart_initialized) { @@ -212,7 +238,10 @@ esp_err_t uart_debug_send(const uint8_t *data, size_t len) // UDP 调试日志 // ============================================================ -// 初始化(UART 立即可用,UDP socket 延迟到首次发送时创建) +/** + * @brief 初始化(UART 立即可用,UDP socket 延迟到首次发送时创建) + * @return ESP_OK 成功,其他失败。 + */ esp_err_t debug_log_init(void) { if (s_initialized) { @@ -221,7 +250,7 @@ esp_err_t debug_log_init(void) uart_debug_init(); - // 日志环形缓冲区互斥锁 + /** @brief 环形缓冲区互斥锁,保护多线程并发读写。 */ s_log_ring_mutex = xSemaphoreCreateMutex(); // 目标地址:单播到上位机 @@ -230,19 +259,24 @@ esp_err_t debug_log_init(void) s_debug_addr.sin_port = htons(DEBUG_LOG_PORT); s_debug_addr.sin_addr.s_addr = inet_addr(DEBUG_LOG_IP); + /** @brief 日志模块初始化标志。 */ s_initialized = true; ESP_LOGI(TAG, "调试日志已初始化,上位机 %s:%d", DEBUG_LOG_IP, DEBUG_LOG_PORT); return ESP_OK; } -// 延迟创建 UDP socket(首次发送时调用) +/** + * @brief 延迟创建 UDP socket(首次发送时调用) + * @return + */ static esp_err_t ensure_udp_socket(void) { if (s_udp_sock >= 0) { return ESP_OK; } + /** @brief UDP 调试日志 socket 句柄,首次发送时延迟创建。 */ s_udp_sock = socket(AF_INET, SOCK_DGRAM, 0); if (s_udp_sock < 0) { ESP_LOGE(TAG, "创建 UDP socket 失败"); @@ -253,7 +287,12 @@ static esp_err_t ensure_udp_socket(void) return ESP_OK; } -// 发送原始数据(UDP 单播 + UART 双通道) +/** + * @brief 发送原始数据(UDP 单播 + UART 双通道) + * @param data 日志数据指针。 + * @param len 数据长度。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t debug_log_send(const uint8_t *data, size_t len) { esp_err_t udp_ret = ESP_FAIL; @@ -289,7 +328,12 @@ esp_err_t debug_log_send(const uint8_t *data, size_t len) return ESP_FAIL; } -// 格式化日志输出 +/** + * @brief 格式化日志输出 + * @param level 日志级别。 + * @param tag 日志标签。 + * @param fmt 格式化字符串。 + */ void debug_log(debug_log_level_t level, const char *tag, const char *fmt, ...) { if (!s_initialized) { diff --git a/main/monitor/debug_log.h b/main/monitor/debug_log.h index d96f9b7..495fa51 100644 --- a/main/monitor/debug_log.h +++ b/main/monitor/debug_log.h @@ -1,10 +1,14 @@ /* - * debug_log.h - WiFi 调试日志输出 + * debug_log.h - 调试日志输出 * - * 通过 UDP socket 将调试日志发送到上位机 - * 上位机监听 接收日志 + * 当前日志会并行写入: + * - UART0 串口 + * - UDP 单播(独立调试接收端可选) + * - 环形缓冲区,供 HTTP /logs 拉取 + * + * 注意:当前 tools/WebUI 日志面板走的是 HTTP /logs, + * 不是直接监听 UDP 6666。 * - * 支持双通道输出:UDP + UART0 串口 * UART0: TX=GPIO21, RX=GPIO20, 115200 波特率 */ #ifndef DEBUG_LOG_H @@ -35,26 +39,51 @@ typedef enum { // 当前日志级别(运行时可改) extern debug_log_level_t g_debug_log_level; -// 初始化调试日志系统(启动 UDP 客户端 + UART0) +/** + * @brief 初始化日志系统,包括 UART 输出、UDP 目标和内部缓冲区。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t debug_log_init(void); -// UART0 串口调试初始化 +/** + * @brief 初始化 UART0 调试串口。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t uart_debug_init(void); -// UART0 串口发送 +/** + * @brief 通过 UART0 直接发送一段原始字节数据。 + * @param data 待发送的数据指针。 + * @param len 数据长度。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t uart_debug_send(const uint8_t *data, size_t len); -// 发送原始数据到上位机 +/** + * @brief 发送原始日志数据到底层输出通道。 + * @param data 日志数据指针。 + * @param len 数据长度。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t debug_log_send(const uint8_t *data, size_t len); -// 格式化日志输出(类似 printf) +/** + * @brief 按日志级别与标签格式化输出一条日志。 + * @param level 日志级别。 + * @param tag 日志标签。 + * @param fmt 格式化字符串。 + */ void debug_log(debug_log_level_t level, const char *tag, const char *fmt, ...); -// 日志环形缓冲区(供 HTTP /logs 拉取) +/** + * @brief 获取当前环形日志缓冲区中的日志条数。 + * @return 当前日志条数。 + */ int debug_log_ring_count(void); + +// 读取并清空环形日志缓冲区,返回 JSON 数组字符串,需由调用者 free。 char *debug_log_ring_drain_json(int *out_len); // 调用者 free -// 宏定义方便调用 #define DEBUG_ERR(tag, fmt, ...) debug_log(DEBUG_LOG_ERR, tag, fmt, ##__VA_ARGS__) #define DEBUG_WARN(tag, fmt, ...) debug_log(DEBUG_LOG_WARN, tag, fmt, ##__VA_ARGS__) #define DEBUG_INFO(tag, fmt, ...) debug_log(DEBUG_LOG_INFO, tag, fmt, ##__VA_ARGS__) diff --git a/main/monitor/health_monitor.cpp b/main/monitor/health_monitor.cpp index 7cabfb8..f48355b 100644 --- a/main/monitor/health_monitor.cpp +++ b/main/monitor/health_monitor.cpp @@ -15,24 +15,36 @@ #include "health_monitor.h" #include "debug_log.h" +/** @brief 全局健康状态,受互斥锁保护。 */ health_state_t g_health_state = SYS_NORMAL; +/** @brief 健康状态互斥锁。 */ static SemaphoreHandle_t g_health_mutex = NULL; +/** @brief 内置温度传感器句柄。 */ static temperature_sensor_handle_t s_temp_handle = NULL; +/** @brief 最近一次采样的芯片温度(摄氏度)。 */ static float s_last_temperature = 0.0f; +/** @brief 最近一次估算的系统电压(伏特)。 */ static float s_last_voltage = 3.30f; // ---------------------------------------- // 健康状态管理 -// ---------------------------------------- +/** + * @brief 初始化健康监控模块,创建互斥锁和基础状态。 + */ void health_monitor_init(void) { + /** @brief 健康状态互斥锁。 */ g_health_mutex = xSemaphoreCreateMutex(); if (g_health_mutex == NULL) { DEBUG_ERR("HEALTH", "创建健康状态互斥锁失败"); } } +/** + * @brief 获取当前系统健康状态。 + * @return 当前健康状态枚举值。 + */ health_state_t health_get_current(void) { health_state_t state; @@ -46,19 +58,32 @@ health_state_t health_get_current(void) return state; } +/** + * @brief 返回最近一次采样到的芯片温度(摄氏度)。 + * @return 最近一次温度采样值(摄氏度)。 + */ float health_get_last_temperature(void) { + /** @brief 最近一次采样的芯片温度(摄氏度)。 */ return s_last_temperature; } +/** + * @brief 返回最近一次记录的系统电压估算值。 + * @return 最近一次电压估算值(伏特)。 + */ float health_get_last_voltage(void) { + /** @brief 最近一次估算的系统电压(伏特)。 */ return s_last_voltage; } // ---------------------------------------- // 温度监控(定时器回调) -// ---------------------------------------- +/** + * @brief 温度定时回调,采样并根据阈值更新健康状态。 + * @param arg + */ static void temp_monitor_callback(void *arg) { float temp = 0.0f; @@ -68,14 +93,17 @@ static void temp_monitor_callback(void *arg) return; } + /** @brief 最近一次采样的芯片温度(摄氏度)。 */ s_last_temperature = temp; if (temp >= TEMP_CRITICAL) { + /** @brief 全局健康状态,受互斥锁保护。 */ g_health_state = SYS_SAFE_SHUTDOWN; DEBUG_ERR("TEMP", "!!! 危险 !!! 温度 %.1f°C >= %d°C,触发安全关机", temp, TEMP_CRITICAL); DEBUG_ERR("TEMP", "健康状态变更为: SYS_SAFE_SHUTDOWN"); } else if (temp >= TEMP_WARNING) { if (g_health_state < SYS_WARNING) { + /** @brief 全局健康状态,受互斥锁保护。 */ g_health_state = SYS_WARNING; DEBUG_WARN("TEMP", "警告: 温度 %.1f°C >= %d°C", temp, TEMP_WARNING); DEBUG_WARN("TEMP", "健康状态变更为: SYS_WARNING"); @@ -85,18 +113,24 @@ static void temp_monitor_callback(void *arg) // ---------------------------------------- // 电压监控(启动时调用一次) -// ---------------------------------------- +/** + * @brief 电压定时回调,采样估算电压并根据阈值更新健康状态。 + * @param arg + */ static void voltage_monitor_callback(void *arg) { float voltage = 3.30f; // ESP32-C3 无内置电压传感器,使用估算 + /** @brief 最近一次估算的系统电压(伏特)。 */ s_last_voltage = voltage; if (voltage < 2.3f) { + /** @brief 全局健康状态,受互斥锁保护。 */ g_health_state = SYS_SAFE_SHUTDOWN; DEBUG_ERR("VOLT", "!!! 危险 !!! 电压 %.2fV < 2.3V,触发安全关机", voltage); DEBUG_ERR("VOLT", "健康状态变更为: SYS_SAFE_SHUTDOWN"); } else if (voltage < 2.5f) { if (g_health_state < SYS_WARNING) { + /** @brief 全局健康状态,受互斥锁保护。 */ g_health_state = SYS_WARNING; DEBUG_WARN("VOLT", "警告: 电压 %.2fV < 2.5V", voltage); DEBUG_WARN("VOLT", "健康状态变更为: SYS_WARNING"); @@ -106,12 +140,14 @@ static void voltage_monitor_callback(void *arg) // ---------------------------------------- // 温度电压监控任务入口 -// ---------------------------------------- +/** + * @brief 启动温度和电压监控定时器,周期性巡检系统健康。 + * @param pvParameters FreeRTOS 任务参数,未使用。 + */ void health_monitor_task(void *pvParameters) { DEBUG_INFO("MON", "=== 健康监控任务启动 ==="); - // 初始化内置温度传感器(测量范围 20°C ~ 80°C) temperature_sensor_config_t temp_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(20, 80); esp_err_t err = temperature_sensor_install(&temp_config, &s_temp_handle); if (err != ESP_OK) { @@ -134,7 +170,6 @@ void health_monitor_task(void *pvParameters) DEBUG_INFO("MON", "温度监控定时器已启动 (间隔 1 秒)"); - // 启动时检查一次电压 voltage_monitor_callback(NULL); // 每1秒刷新一次估算电压 @@ -165,7 +200,11 @@ void health_monitor_task(void *pvParameters) // ---------------------------------------- // 健康事件日志 -// ---------------------------------------- +/** + * @brief 记录一条健康监控事件日志。 + * @param event 事件名称字符串。 + * @param value 事件关联的数值。 + */ void health_log_event(const char *event, int value) { DEBUG_INFO("HEALTH", "[健康事件] %s: %d", event, value); @@ -173,7 +212,10 @@ void health_log_event(const char *event, int value) // ---------------------------------------- // OTA 传输前的健康检查 -// ---------------------------------------- +/** + * @brief OTA 传输前执行健康检查,决定是否允许传输。 + * @return 健康检查结果,决定是否允许 OTA。 + */ health_status_t health_check_before_ota(void) { health_status_t status = HEALTH_MON_OK; @@ -192,6 +234,7 @@ health_status_t health_check_before_ota(void) wifi_ap_record_t ap_info; if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) { if (ap_info.rssi < WIFI_RSSI_CRITICAL) { + /** @brief 全局健康状态,受互斥锁保护。 */ g_health_state = SYS_CRITICAL; DEBUG_WARN("HEALTH", "OTA 前检查: WiFi RSSI %d < %d (危险阈值)", ap_info.rssi, WIFI_RSSI_CRITICAL); @@ -212,15 +255,22 @@ health_status_t health_check_before_ota(void) return status; } +/** + * @brief 根据 WiFi RSSI 更新健康状态并返回检查结果。 + * @param rssi 当前 WiFi 信号强度(dBm)。 + * @return 健康检查结果。 + */ health_status_t health_update_from_wifi_rssi(int8_t rssi) { if (rssi < WIFI_RSSI_CRITICAL) { + /** @brief 全局健康状态,受互斥锁保护。 */ g_health_state = SYS_CRITICAL; DEBUG_WARN("HEALTH", "WiFi RSSI 危险: %d dBm < %d dBm", rssi, WIFI_RSSI_CRITICAL); health_log_event("WIFI危险", rssi); return HEALTH_MON_CRITICAL; } else if (rssi < WIFI_RSSI_NORMAL) { if (g_health_state < SYS_WARNING) { + /** @brief 全局健康状态,受互斥锁保护。 */ g_health_state = SYS_WARNING; DEBUG_WARN("HEALTH", "WiFi RSSI 警告: %d dBm < %d dBm", rssi, WIFI_RSSI_NORMAL); health_log_event("WIFI警告", rssi); @@ -229,6 +279,7 @@ health_status_t health_update_from_wifi_rssi(int8_t rssi) } if (g_health_state == SYS_WARNING && rssi >= WIFI_RSSI_NORMAL) { + /** @brief 全局健康状态,受互斥锁保护。 */ g_health_state = SYS_NORMAL; DEBUG_INFO("HEALTH", "WiFi RSSI 恢复: %d dBm >= %d dBm", rssi, WIFI_RSSI_NORMAL); health_log_event("WIFI恢复", rssi); diff --git a/main/monitor/health_monitor.h b/main/monitor/health_monitor.h index d8ea366..d248225 100644 --- a/main/monitor/health_monitor.h +++ b/main/monitor/health_monitor.h @@ -28,12 +28,51 @@ extern health_state_t g_health_state; #define WIFI_RSSI_NORMAL WIFI_RSSI_WARNING #define WIFI_RSSI_WEAK WIFI_RSSI_CRITICAL -// 函数声明 +/** + * @brief 初始化健康监控模块,创建内部互斥锁与基础状态。 + */ void health_monitor_init(void); + +/** + * @brief 健康监控任务入口,负责启动温度/电压定时检测并周期性巡检系统状态。 + * @param pvParameters FreeRTOS 任务参数,未使用。 + */ void health_monitor_task(void *pvParameters); + +/** + * @brief 在 OTA 传输开始前执行健康检查,返回当前是否允许传输。 + * @return 健康检查结果,决定是否允许 OTA。 + */ health_status_t health_check_before_ota(void); + +/** + * @brief 记录一条健康相关事件日志,便于故障追踪与状态回溯。 + * @param event 事件名称字符串。 + * @param value 事件关联的数值。 + */ void health_log_event(const char *event, int value); + +/** + * @brief 获取当前系统健康状态。 + * @return 当前健康状态枚举值。 + */ health_state_t health_get_current(void); + +/** + * @brief 根据最新 WiFi RSSI 更新健康状态,并返回对应健康检查结果。 + * @param rssi 当前 WiFi 信号强度(dBm)。 + * @return 健康检查结果。 + */ health_status_t health_update_from_wifi_rssi(int8_t rssi); + +/** + * @brief 获取最近一次采样到的芯片温度(摄氏度)。 + * @return 最近一次温度采样值(摄氏度)。 + */ float health_get_last_temperature(void); + +/** + * @brief 获取最近一次记录的系统电压估算值(伏特)。 + * @return 最近一次电压估算值(伏特)。 + */ float health_get_last_voltage(void); diff --git a/main/relay/ota_relay.cpp b/main/relay/ota_relay.cpp index 405be3a..5f09a73 100644 --- a/main/relay/ota_relay.cpp +++ b/main/relay/ota_relay.cpp @@ -17,21 +17,54 @@ #include "health_monitor.h" #include "monitor/debug_log.h" +/** @brief 当前 OTA 中继状态机状态。 */ relay_state_t g_relay_state = RELAY_IDLE; static const esp_partition_t *g_gd32_partition = NULL; +/** @brief OTA 认证会话上下文,包含会话密钥和 IV。 */ static session_ctx_t g_session_ctx; +/** @brief OTA 命令分发队列,从 WiFi/BLE 任务接收指令。 */ QueueHandle_t g_ota_dispatch_queue = NULL; +/** @brief OTA 结果回传队列,将执行结果返回给调用方。 */ QueueHandle_t g_ota_result_queue = NULL; +/** + * @brief OTA 握手阶段:向 GD32 发送握手请求并等待进入认证流程。 + * @return 操作结果码。 + */ static ota_result_t send_handshake_req(void); + +/** + * @brief 认证阶段:生成随机数并完成与 GD32 的双向认证。 + * @return 操作结果码。 + */ static ota_result_t send_nonce_and_authenticate(void); + +/** + * @brief 向 GD32 发送待升级固件的总大小。 + * @param size 固件总字节数。 + * @return 操作结果码。 + */ static ota_result_t send_firmware_size(uint32_t size); + +/** + * @brief 从 Flash 逐包读取固件并中继转发给 GD32。 + * @return 操作结果码。 + */ static ota_result_t relay_packets_from_flash(void); + +/** + * @brief OTA 收尾阶段:发送结束命令并等待 GD32 确认。 + * @return 操作结果码。 + */ static ota_result_t finish_ota(void); -// 从 Flash 加载元数据 +/** + * @brief 从 Flash 加载元数据 + * @param meta 输出参数,加载后的元数据结构体。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t meta_load(ota_meta_t *meta) { if (!meta) return ESP_ERR_INVALID_ARG; @@ -73,7 +106,11 @@ esp_err_t meta_load(ota_meta_t *meta) return ESP_OK; } -// 保存元数据到 Flash +/** + * @brief 保存元数据到 Flash + * @param meta 待保存的元数据结构体。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t meta_save(const ota_meta_t *meta) { if (!meta) return ESP_ERR_INVALID_ARG; @@ -98,7 +135,12 @@ esp_err_t meta_save(const ota_meta_t *meta) return ESP_OK; } -// 传输过程中更新元数据 +/** + * @brief 传输过程中更新元数据 + * @param stored_size 已存储的固件大小(字节)。 + * @param last_seq 最后成功的分块序号。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t meta_update(uint32_t stored_size, uint32_t last_seq) { ota_meta_t meta; @@ -111,7 +153,10 @@ esp_err_t meta_update(uint32_t stored_size, uint32_t last_seq) return meta_save(&meta); } -// OTA 完成后或中止时清除元数据 +/** + * @brief OTA 完成后或中止时清除元数据 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t meta_clear(void) { DEBUG_INFO("OTA", "清除 OTA 元数据"); @@ -119,7 +164,13 @@ esp_err_t meta_clear(void) return meta_save(&meta); } -// Flash 写入接口 +/** + * @brief Flash 写入接口 + * @param offset 分区内写入偏移。 + * @param data 待写入的数据指针。 + * @param len 数据长度(字节)。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t flash_write_chunk(uint32_t offset, const uint8_t *data, size_t len) { if (!g_gd32_partition) { @@ -134,6 +185,13 @@ esp_err_t flash_write_chunk(uint32_t offset, const uint8_t *data, size_t len) return esp_partition_write(g_gd32_partition, offset, data, len); } +/** + * @brief 从 OTA 缓存分区读取指定偏移和长度的数据块。 + * @param offset 分区内读取偏移。 + * @param data 读取缓冲区指针。 + * @param len 读取长度(字节)。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t flash_read_chunk(uint32_t offset, uint8_t *data, size_t len) { if (!g_gd32_partition) { @@ -148,7 +206,10 @@ esp_err_t flash_read_chunk(uint32_t offset, uint8_t *data, size_t len) return esp_partition_read(g_gd32_partition, offset, data, len); } -// 阶段1 - 发起与 GD32 的握手 +/** + * @brief 阶段1 - 发起与 GD32 的握手 + * @return 操作结果码。 + */ static ota_result_t send_handshake_req(void) { DEBUG_INFO("OTA", "=== 阶段1: 握手开始 ==="); @@ -160,6 +221,7 @@ static ota_result_t send_handshake_req(void) uint8_t payload[] = {0x00, 0xAC}; frame_build(CMD_HANDSHAKE_REQ, payload, sizeof(payload), tx_frame); + /** @brief 当前 OTA 中继状态机状态。 */ g_relay_state = RELAY_HANDSHAKE_SENT; DEBUG_INFO("OTA", "状态: RELAY_HANDSHAKE_SENT"); @@ -167,6 +229,7 @@ static ota_result_t send_handshake_req(void) rx_frame, sizeof(rx_frame), 3000); if (err != ESP_OK) { DEBUG_ERR("OTA", "HANDSHAKE_REQ 失败, err=%d", err); + /** @brief 当前 OTA 中继状态机状态。 */ g_relay_state = RELAY_ERROR; return OTA_RESULT_ERR_TIMEOUT; } @@ -177,7 +240,10 @@ static ota_result_t send_handshake_req(void) return OTA_RESULT_OK; } -// 发送随机数并与 GD32 相互认证 +/** + * @brief 发送随机数并与 GD32 相互认证 + * @return 操作结果码。 + */ static ota_result_t send_nonce_and_authenticate(void) { uint8_t tx_frame[64]; @@ -192,6 +258,7 @@ static ota_result_t send_nonce_and_authenticate(void) DEBUG_INFO("OTA", "--- 认证步骤 1: 发送 CHALLENGE ---"); DEBUG_INFO("OTA", "发送 nonce_server (16B), timestamp=0x%08X", timestamp); + /** @brief 当前 OTA 中继状态机状态。 */ g_relay_state = RELAY_CHALLENGE_SENT; DEBUG_INFO("OTA", "状态: RELAY_CHALLENGE_SENT"); @@ -201,10 +268,12 @@ static ota_result_t send_nonce_and_authenticate(void) rx_frame, sizeof(rx_frame), 5000); if (err != ESP_OK) { DEBUG_ERR("OTA", "CHALLENGE 发送失败, err=%d", err); + /** @brief 当前 OTA 中继状态机状态。 */ g_relay_state = RELAY_ERROR; return OTA_RESULT_ERR_TIMEOUT; } + /** @brief 当前 OTA 中继状态机状态。 */ g_relay_state = RELAY_CHALLENGE_RESPOND; DEBUG_INFO("OTA", "已收到 GD32 握手数据包"); DEBUG_INFO("OTA", "状态: RELAY_CHALLENGE_RESPOND"); @@ -215,6 +284,7 @@ static ota_result_t send_nonce_and_authenticate(void) DEBUG_INFO("OTA", "--- 认证步骤 2: 发送 SESSION_KEY ---"); DEBUG_INFO("OTA", "发送变换后的随机数 (16B)"); + /** @brief 当前 OTA 中继状态机状态。 */ g_relay_state = RELAY_SESSION_KEY_SENT; DEBUG_INFO("OTA", "状态: RELAY_SESSION_KEY_SENT"); @@ -223,10 +293,12 @@ static ota_result_t send_nonce_and_authenticate(void) rx_frame, sizeof(rx_frame), 3000); if (err2 != ESP_OK) { DEBUG_ERR("OTA", "SESSION_KEY 发送失败, err=%d", err2); + /** @brief 当前 OTA 中继状态机状态。 */ g_relay_state = RELAY_ERROR; return OTA_RESULT_ERR_TIMEOUT; } + /** @brief 当前 OTA 中继状态机状态。 */ g_relay_state = RELAY_AUTH_COMPLETE; DEBUG_INFO("OTA", "状态: RELAY_AUTH_COMPLETE"); DEBUG_INFO("OTA", "=== 阶段1 认证完成: 安全通道已建立 ==="); @@ -237,7 +309,11 @@ static ota_result_t send_nonce_and_authenticate(void) return OTA_RESULT_OK; } -// 向 GD32 发送固件总大小 +/** + * @brief 向 GD32 发送固件总大小 + * @param size 固件总字节数。 + * @return 操作结果码。 + */ static ota_result_t send_firmware_size(uint32_t size) { uint8_t tx_frame[32]; @@ -252,6 +328,7 @@ static ota_result_t send_firmware_size(uint32_t size) size_payload[2] = (uint8_t)((size >> 16) & 0xFF); size_payload[3] = (uint8_t)((size >> 24) & 0xFF); + /** @brief 当前 OTA 中继状态机状态。 */ g_relay_state = RELAY_SIZE_SENT; DEBUG_INFO("OTA", "状态: RELAY_SIZE_SENT"); @@ -260,18 +337,23 @@ static ota_result_t send_firmware_size(uint32_t size) rx_frame, sizeof(rx_frame), 3000); if (err != ESP_OK) { DEBUG_ERR("OTA", "SEND_SIZE 失败, err=%d", err); + /** @brief 当前 OTA 中继状态机状态。 */ g_relay_state = RELAY_ERROR; return OTA_RESULT_ERR_TIMEOUT; } DEBUG_INFO("OTA", "大小已发送: %u 字节,等待 GD32 确认...", size); + /** @brief 当前 OTA 中继状态机状态。 */ g_relay_state = RELAY_SIZE_ACK; DEBUG_INFO("OTA", "状态: RELAY_SIZE_ACK"); return OTA_RESULT_OK; } -// 从 Flash 向 GD32 转发存储的固件(48B/包) +/** + * @brief 从 Flash 向 GD32 转发存储的固件(48B/包) + * @return 操作结果码。 + */ static ota_result_t relay_packets_from_flash(void) { DEBUG_INFO("OTA", "=== 阶段3: 从 Flash 转发固件 ==="); @@ -290,10 +372,10 @@ static ota_result_t relay_packets_from_flash(void) DEBUG_INFO("OTA", "总包数: %u, 每包 48 字节", total_packets); DEBUG_INFO("OTA", "状态: RELAY_RELAY_ACTIVE"); + /** @brief 当前 OTA 中继状态机状态。 */ g_relay_state = RELAY_RELAY_ACTIVE; for (uint32_t seq = 0; seq < total_packets; seq++) { - // 发送每个包前检查健康状态 health_status_t health = health_check_before_ota(); if (health == HEALTH_MON_SHUTDOWN || health == HEALTH_MON_CRITICAL) { DEBUG_ERR("OTA", "健康检查危险,中止 OTA"); @@ -318,6 +400,7 @@ static ota_result_t relay_packets_from_flash(void) tx_buf[2 + chunk_size] = (uint8_t)(crc & 0xFF); tx_buf[2 + chunk_size + 1] = (uint8_t)((crc >> 8) & 0xFF); + /** @brief 当前 OTA 中继状态机状态。 */ g_relay_state = RELAY_PACKET_SENT; uint8_t cmd = (seq == total_packets - 1) ? CMD_DATA_END : CMD_DATA_PACKET; @@ -331,6 +414,7 @@ static ota_result_t relay_packets_from_flash(void) } flash_offset += chunk_size; + /** @brief 当前 OTA 中继状态机状态。 */ g_relay_state = RELAY_PACKET_ACK_OK; // 每 10% 记录一次进度 @@ -341,12 +425,16 @@ static ota_result_t relay_packets_from_flash(void) } } + /** @brief 当前 OTA 中继状态机状态。 */ g_relay_state = RELAY_RECEIVE_DONE; DEBUG_INFO("OTA", "状态: RELAY_RECEIVE_DONE"); return result; } -// 结束 OTA - 发送 0x0A AC 并清理 +/** + * @brief 结束 OTA - 发送 0x0A AC 并清理 + * @return 操作结果码。 + */ static ota_result_t finish_ota(void) { DEBUG_INFO("OTA", "=== 阶段4: 结束 OTA ==="); @@ -358,6 +446,7 @@ static ota_result_t finish_ota(void) uint8_t payload[] = {0x0A, 0xAC}; frame_build(CMD_FINISH_OTA, payload, sizeof(payload), tx_frame); + /** @brief 当前 OTA 中继状态机状态。 */ g_relay_state = RELAY_FINISH_SENT; DEBUG_INFO("OTA", "状态: RELAY_FINISH_SENT"); @@ -365,6 +454,7 @@ static ota_result_t finish_ota(void) rx_frame, sizeof(rx_frame), 5000); if (err != ESP_OK) { DEBUG_ERR("OTA", "FINISH_OTA 失败, err=%d", err); + /** @brief 当前 OTA 中继状态机状态。 */ g_relay_state = RELAY_ERROR; return OTA_RESULT_ERR_TIMEOUT; } @@ -374,6 +464,7 @@ static ota_result_t finish_ota(void) session_key_clear(&g_session_ctx); memset(&g_session_ctx, 0, sizeof(session_ctx_t)); + /** @brief 当前 OTA 中继状态机状态。 */ g_relay_state = RELAY_DONE; DEBUG_INFO("OTA", "状态: RELAY_DONE"); DEBUG_INFO("OTA", "=== OTA 完成 ==="); @@ -383,7 +474,10 @@ static ota_result_t finish_ota(void) return OTA_RESULT_OK; } -// 启动完整 OTA 流程的主入口 +/** + * @brief 启动完整 OTA 流程的主入口 + * @return 操作结果码。 + */ ota_result_t ota_relay_start_handshake(void) { ota_result_t res; @@ -407,7 +501,10 @@ ota_result_t ota_relay_start_handshake(void) return res; } -// 从 Flash 向 GD32 转发固件 +/** + * @brief 从 Flash 向 GD32 转发固件 + * @return 操作结果码。 + */ ota_result_t ota_relay_forward_firmware(void) { ota_meta_t meta; @@ -435,25 +532,37 @@ ota_result_t ota_relay_forward_firmware(void) return res; } -// 获取当前状态 +/** + * @brief 获取当前状态 + * @return 当前中继状态枚举值。 + */ relay_state_t ota_relay_get_state(void) { + /** @brief 当前 OTA 中继状态机状态。 */ return g_relay_state; } -// 重置状态机 +/** + * @brief 重置状态机 + */ void ota_relay_reset(void) { + /** @brief 当前 OTA 中继状态机状态。 */ g_relay_state = RELAY_IDLE; session_key_clear(&g_session_ctx); } -// OTA 中继 FreeRTOS 任务 +/** + * @brief OTA 中继 FreeRTOS 任务 + * @param pvParameters FreeRTOS 任务参数,未使用。 + */ void ota_relay_task(void *pvParameters) { DEBUG_INFO("OTA", "OTA 中继任务启动"); + /** @brief OTA 命令分发队列,从 WiFi/BLE 任务接收指令。 */ g_ota_dispatch_queue = xQueueCreate(4, sizeof(uint32_t)); + /** @brief OTA 结果回传队列,将执行结果返回给调用方。 */ g_ota_result_queue = xQueueCreate(4, sizeof(uint32_t)); uint32_t msg; diff --git a/main/relay/ota_relay.h b/main/relay/ota_relay.h index 05e5bef..a87b2f3 100644 --- a/main/relay/ota_relay.h +++ b/main/relay/ota_relay.h @@ -70,29 +70,79 @@ typedef enum { extern QueueHandle_t g_ota_dispatch_queue; // 从 WiFi/BLE 到 OTA 中继 extern QueueHandle_t g_ota_result_queue; // OTA 结果到 WiFi/BLE -// OTA 中继任务入口 +/** + * @brief OTA 中继任务入口,负责执行握手、认证、分包转发和结束流程。 + * @param pvParameters FreeRTOS 任务参数,未使用。 + */ void ota_relay_task(void *pvParameters); -// T2.3: 发起与 GD32 的握手 +/** + * @brief 发起与 GD32 的握手流程,建立 OTA 传输前的安全会话。 + * @return 操作结果码。 + */ ota_result_t ota_relay_start_handshake(void); -// T3.4: 从 Flash 向 GD32 转发存储的固件(48B/包) +/** + * @brief 从 Flash 中读取已缓存固件并按协议转发给 GD32。 + * @return 操作结果码。 + */ ota_result_t ota_relay_forward_firmware(void); -// T3.7: 获取当前中继状态 +/** + * @brief 获取当前 OTA 中继状态机状态。 + * @return 当前中继状态枚举值。 + */ relay_state_t ota_relay_get_state(void); -// T3.7: 重置中继状态机 +/** + * @brief 重置 OTA 中继状态机,清理当前状态以便重新开始流程。 + */ void ota_relay_reset(void); -// T4.2: Flash 写入接口 +/** + * @brief 向 OTA 缓存分区写入一段固件数据。 + * @param offset 分区内写入偏移。 + * @param data 待写入的数据指针。 + * @param len 数据长度(字节)。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t flash_write_chunk(uint32_t offset, const uint8_t *data, size_t len); + +/** + * @brief 从 OTA 缓存分区读取一段固件数据。 + * @param offset 分区内读取偏移。 + * @param data 读取缓冲区指针。 + * @param len 读取长度(字节)。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t flash_read_chunk(uint32_t offset, uint8_t *data, size_t len); -// T3.3: 元数据文件管理 +/** + * @brief 从持久化文件中加载 OTA 元数据。 + * @param meta 输出参数,加载后的元数据结构体。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t meta_load(ota_meta_t *meta); + +/** + * @brief 将 OTA 元数据保存到持久化文件。 + * @param meta 待保存的元数据结构体。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t meta_save(const ota_meta_t *meta); + +/** + * @brief 更新已存储大小与最后分块序号等 OTA 进度信息。 + * @param stored_size 已存储的固件大小(字节)。 + * @param last_seq 最后成功的分块序号。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t meta_update(uint32_t stored_size, uint32_t last_seq); + +/** + * @brief 清空 OTA 元数据,通常在完成或中止后调用。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t meta_clear(void); // 当前中继状态(供健康监控外部访问) diff --git a/main/server/ble_ota.cpp b/main/server/ble_ota.cpp index c617edb..44b07c4 100644 --- a/main/server/ble_ota.cpp +++ b/main/server/ble_ota.cpp @@ -42,12 +42,19 @@ static bool s_ble_ready = false; // NimBLE 同步完成 static bool s_ble_connected = false; // GATT 客户端已连接 +/** @brief 当前 BLE 连接句柄。 */ static uint16_t s_conn_handle = BLE_HS_CONN_HANDLE_NONE; static uint16_t s_data_out_val_handle = 0; // DATA_OUT 值句柄 (供通知用) // ============================================================ // NimBLE 工具: 从 mbuf 读取原始数据 -// ============================================================ +/** + * @brief 将 NimBLE mbuf 中的数据拷贝到普通字节缓冲区。 + * @param om NimBLE mbuf 数据指针。 + * @param buf 输出缓冲区。 + * @param buf_len 缓冲区最大长度。 + * @return 拷贝的字节数,失败返回 -1。 + */ static int ble_ota_mbuf_to_buf(struct os_mbuf *om, uint8_t *buf, size_t buf_len) { if (!om || !buf) return -1; @@ -184,7 +191,6 @@ static int ble_ota_data_in_access(uint16_t conn_handle, uint16_t attr_handle, return BLE_ATT_ERR_UNLIKELY; } - // --- 更新元数据 --- err = meta_update(flash_offset + data_len, seq); if (err != ESP_OK) { DEBUG_WARN(TAG, "元数据更新失败 (seq=%u)", seq); @@ -241,11 +247,16 @@ static int ble_ota_data_out_access(uint16_t conn_handle, uint16_t attr_handle, // ============================================================ // BLE_UUID16_DECLARE 在 C++ 中产生 "taking address of rvalue" 编译错误。 -// ESP-IDF 6.0 NimBLE 的 C++ 兼容写法: 先定义 static ble_uuid16_t 变量, 再取 .u 地址。 +// 改用 static ble_uuid16_t 变量,再取 .u 地址。 +/** @brief OTA GATT 服务 UUID */ static const ble_uuid16_t GATT_SVC_UUID = BLE_UUID16_INIT(BLE_OTA_SERVICE_UUID); +/** @brief CTRL 特征 UUID,写入 0xF7 触发中继 */ static const ble_uuid16_t GATT_CTRL_UUID = BLE_UUID16_INIT(BLE_OTA_CTRL_UUID); +/** @brief DATA_IN 特征 UUID,接收固件数据块 */ static const ble_uuid16_t GATT_DATA_IN_UUID = BLE_UUID16_INIT(BLE_OTA_DATA_IN_UUID); +/** @brief DATA_OUT 特征 UUID,Notify 推送 GD32 响应 */ static const ble_uuid16_t GATT_DATA_OUT_UUID = BLE_UUID16_INIT(BLE_OTA_DATA_OUT_UUID); +/** @brief VERSION 特征 UUID,返回固件版本字符串 */ static const ble_uuid16_t GATT_VERSION_UUID = BLE_UUID16_INIT(BLE_OTA_VERSION_UUID); // NimBLE C 结构体在 C++ designated initializer 不能显式初始化全部字段。 @@ -296,7 +307,12 @@ static const struct ble_gatt_svc_def gatt_ota_svcs[] = { // ============================================================ // GAP 事件回调 // ============================================================ - +/** + * @brief BLE GAP 事件回调,处理连接和断开事件。 + * @param event + * @param arg + * @return 0 成功。 + */ static int ble_ota_gap_event(struct ble_gap_event *event, void *arg) { (void)arg; @@ -307,10 +323,12 @@ static int ble_ota_gap_event(struct ble_gap_event *event, void *arg) if (event->connect.status == 0) { // 连接成功 s_ble_connected = true; + /** @brief 当前 BLE 连接句柄。 */ s_conn_handle = event->connect.conn_handle; DEBUG_INFO(TAG, "BLE 客户端已连接: handle=%d", s_conn_handle); } else { DEBUG_WARN(TAG, "BLE 连接失败: status=%d", event->connect.status); + /** @brief BLE GATT 客户端连接状态。 */ s_ble_connected = false; } return 0; @@ -318,7 +336,9 @@ static int ble_ota_gap_event(struct ble_gap_event *event, void *arg) case BLE_GAP_EVENT_DISCONNECT: DEBUG_INFO(TAG, "BLE 客户端已断开: reason=%d", event->disconnect.reason); + /** @brief BLE GATT 客户端连接状态。 */ s_ble_connected = false; + /** @brief 当前 BLE 连接句柄。 */ s_conn_handle = BLE_HS_CONN_HANDLE_NONE; // 恢复广播以接受新连接 ble_gap_adv_start(BLE_OWN_ADDR_PUBLIC, NULL, BLE_HS_FOREVER, @@ -352,7 +372,9 @@ static int ble_ota_gap_event(struct ble_gap_event *event, void *arg) // ============================================================ // BLE 广播 // ============================================================ - +/** + * @brief 启动 BLE 广播,使设备可被发现和连接。 + */ static void ble_ota_start_advertising(void) { uint8_t adv_data[31] = {0}; @@ -420,7 +442,9 @@ static void ble_ota_start_advertising(void) // ============================================================ // NimBLE 同步/重置回调 // ============================================================ - +/** + * @brief NimBLE 协议栈同步完成回调,初始化广播。 + */ static void ble_ota_on_sync(void) { DEBUG_INFO(TAG, "NimBLE 协议栈同步完成"); @@ -432,21 +456,28 @@ static void ble_ota_on_sync(void) ble_ota_start_advertising(); } +/** + * @brief NimBLE 协议栈重置回调。 + * @param reason + */ static void ble_ota_on_reset(int reason) { DEBUG_WARN(TAG, "NimBLE 协议栈重置: reason=%d", reason); + /** @brief NimBLE 协议栈同步完成标志。 */ s_ble_ready = false; } // ============================================================ // NimBLE Host 任务(独立 FreeRTOS 任务) // ============================================================ - +/** + * @brief NimBLE 主机任务入口,处理协议栈事件循环。 + * @param param + */ static void ble_ota_nimble_host_task(void *param) { (void)param; DEBUG_INFO(TAG, "NimBLE Host 任务启动"); - // nimble_port_run() 阻塞运行 NimBLE 事件循环 nimble_port_run(); // 正常情况下不会到达这里 DEBUG_INFO(TAG, "NimBLE Host 任务退出"); @@ -455,7 +486,10 @@ static void ble_ota_nimble_host_task(void *param) // ============================================================ // 公开 API // ============================================================ - +/** + * @brief 启动 BLE GATT 服务器并注册 OTA 服务。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t ble_ota_server_start(void) { int rc; @@ -465,7 +499,6 @@ esp_err_t ble_ota_server_start(void) DEBUG_INFO(TAG, " MTU: %" PRIu16, BLE_MTU_SIZE); DEBUG_INFO(TAG, " Svc: 0x%04X", BLE_OTA_SERVICE_UUID); - // 1. 初始化 NimBLE 底层 esp_err_t err = nimble_port_init(); if (err != ESP_OK) { DEBUG_ERR(TAG, "nimble_port_init 失败: %s", esp_err_to_name(err)); @@ -473,11 +506,9 @@ esp_err_t ble_ota_server_start(void) } DEBUG_INFO(TAG, "nimble_port_init 完成"); - // 2. 初始化 GAP / GATT 服务(无需 BLE 存储回调 — 不做 bonding) ble_svc_gap_init(); ble_svc_gatt_init(); - // 4. 设置设备名称 rc = ble_svc_gap_device_name_set(BLE_DEVICE_NAME); if (rc != 0) { DEBUG_WARN(TAG, "设置设备名称失败: %d", rc); @@ -490,7 +521,6 @@ esp_err_t ble_ota_server_start(void) ble_hs_cfg.reset_cb = ble_ota_on_reset; // 不做 bonding,不需要 store 回调 - // 6. 预注册 GATT 服务(在 sync 回调中也会再注册一次确保) rc = ble_gatts_count_cfg(gatt_ota_svcs); if (rc != 0) { DEBUG_ERR(TAG, "GATT 服务计数失败: %d", rc); @@ -503,13 +533,18 @@ esp_err_t ble_ota_server_start(void) return ESP_FAIL; } - // 7. 启动 NimBLE Host 任务(内部创建 FreeRTOS 任务跑 nimble_port_run) nimble_port_freertos_init(ble_ota_nimble_host_task); DEBUG_INFO(TAG, "BLE OTA 服务器初始化完成"); return ESP_OK; } +/** + * @brief 通过 BLE Notify 将 GD32 响应推送给上位机。 + * @param data 待推送的数据指针。 + * @param len 数据长度(字节)。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t ble_ota_notify_response(const uint8_t *data, size_t len) { if (!s_ble_connected) { @@ -522,7 +557,6 @@ esp_err_t ble_ota_notify_response(const uint8_t *data, size_t len) if (len == 0) return ESP_OK; - // 通过 NimBLE mbuf 发送通知 struct os_mbuf *om = ble_hs_mbuf_from_flat(data, len); if (om == NULL) { DEBUG_ERR(TAG, "分配 mbuf 失败 (%zu 字节)", len); @@ -539,11 +573,19 @@ esp_err_t ble_ota_notify_response(const uint8_t *data, size_t len) return ESP_OK; } +/** + * @brief 检查当前是否有 BLE 客户端连接。 + * @return true 已连接,false 未连接。 + */ bool ble_ota_is_connected(void) { + /** @brief BLE GATT 客户端连接状态。 */ return s_ble_connected; } +/** + * @brief 通知中继模块开始向 GD32 转发固件。 + */ void ble_ota_signal_relay(void) { if (g_ota_dispatch_queue == NULL) { @@ -568,13 +610,15 @@ void ble_ota_signal_relay(void) // 实际的 BLE 事件循环在独立的 NimBLE Host 任务中运行。 // 为避免与 ota_relay_task 竞争,此任务不从 g_ota_dispatch_queue 消费。 // ============================================================ - +/** + * @brief BLE OTA FreeRTOS 任务,初始化 NimBLE 并运行 GATT 服务。 + * @param pvParameters FreeRTOS 任务参数,未使用。 + */ void ble_ota_task(void *pvParameters) { (void)pvParameters; DEBUG_INFO(TAG, "=== BLE OTA 任务启动 ==="); - // 初始化并启动 BLE 服务器(同步操作,完成后广播开始) esp_err_t err = ble_ota_server_start(); if (err != ESP_OK) { DEBUG_ERR(TAG, "BLE 服务器启动失败: %s → BLE OTA 不可用", diff --git a/main/server/ble_ota.h b/main/server/ble_ota.h index be70ff1..74ad3d6 100644 --- a/main/server/ble_ota.h +++ b/main/server/ble_ota.h @@ -7,19 +7,35 @@ #include "ota_relay.h" -// BLE OTA 任务入口 +/** + * @brief BLE OTA 任务入口,负责初始化 NimBLE 并运行 GATT OTA 服务。 + * @param pvParameters FreeRTOS 任务参数,未使用。 + */ void ble_ota_task(void *pvParameters); -// 启动 BLE GATT 服务器 +/** + * @brief 启动 BLE GATT 服务器并注册 OTA 相关服务与特征。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t ble_ota_server_start(void); -// 通过 BLE 通知 APP GD32 的响应 +/** + * @brief 通过 BLE Notify 将 GD32 的响应数据发送给上位机 APP。 + * @param data 待推送的数据指针。 + * @param len 数据长度(字节)。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t ble_ota_notify_response(const uint8_t *data, size_t len); -// 检查 BLE 是否已连接 +/** + * @brief 检查当前是否存在已连接的 BLE 客户端。 + * @return true 已连接,false 未连接。 + */ bool ble_ota_is_connected(void); -// 信号通知中继开始(存储完成后调用) +/** + * @brief 在固件存储完成后通知中继模块开始向 GD32 转发。 + */ void ble_ota_signal_relay(void); #endif // BLE_OTA_H \ No newline at end of file diff --git a/main/server/wifi_ota.cpp b/main/server/wifi_ota.cpp index 6479c9e..23657a3 100644 --- a/main/server/wifi_ota.cpp +++ b/main/server/wifi_ota.cpp @@ -28,26 +28,42 @@ #include "spi_master.h" #include "cJSON.h" +/** @brief 当前 WiFi 工作模式(AP 或 STA)。 */ static ota_wifi_mode_t s_wifi_mode = OTA_WIFI_MODE_AP; +/** @brief OTA 中继是否已触发标志。 */ static bool s_relay_triggered = false; +/** @brief STA 模式连接状态。 */ static bool s_sta_connected = false; +/** @brief STA 连接重试次数。 */ static int s_sta_retry_count = 0; +/** @brief STA 连接成功信号量。 */ static SemaphoreHandle_t s_sta_connected_sem = NULL; +/** @brief STA 获取 IP 信号量。 */ static SemaphoreHandle_t s_sta_got_ip_sem = NULL; // ESP32 OTA 状态 static bool s_esp32_ota_active = false; +/** @brief ESP32 OTA 写入句柄。 */ static esp_ota_handle_t s_esp32_ota_handle = 0; static const esp_partition_t *s_esp32_update_partition = NULL; +/** @brief ESP32 OTA 已接收字节数。 */ static size_t s_esp32_received = 0; // GD32 OTA 存储状态 static bool s_gd32_storage_done = false; +/** @brief HTTP 服务器句柄。 */ static httpd_handle_t s_http_server = NULL; +/** + * @brief 预先声明:为 STA 模式配置静态 IP、网关和子网掩码。 + */ static void wifi_set_static_ip(void); +/** + * @brief 为 HTTP 响应添加跨域访问控制头。 + * @param req HTTP 请求对象。 + */ static void http_set_cors_headers(httpd_req_t *req) { httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); @@ -56,6 +72,11 @@ static void http_set_cors_headers(httpd_req_t *req) httpd_resp_set_hdr(req, "Access-Control-Max-Age", "600"); } +/** + * @brief 处理 HTTP OPTIONS 预检请求,返回 204。 + * @param req HTTP 请求对象。 + * @return ESP_OK。 + */ static esp_err_t http_options_handler(httpd_req_t *req) { http_set_cors_headers(req); @@ -79,7 +100,9 @@ static void wifi_event_handler(void* arg, esp_event_base_t event_base, case WIFI_EVENT_STA_CONNECTED: DEBUG_INFO("WIFI", "STA 已关联到 AP"); + /** @brief STA 模式连接状态。 */ s_sta_connected = true; + /** @brief STA 连接重试次数。 */ s_sta_retry_count = 0; if (s_sta_connected_sem) { xSemaphoreGive(s_sta_connected_sem); @@ -89,6 +112,7 @@ static void wifi_event_handler(void* arg, esp_event_base_t event_base, case WIFI_EVENT_STA_DISCONNECTED: { wifi_event_sta_disconnected_t* disconn = (wifi_event_sta_disconnected_t*)event_data; DEBUG_WARN("WIFI", "STA 断开, reason=%d, retry=%d", disconn->reason, s_sta_retry_count); + /** @brief STA 模式连接状态。 */ s_sta_connected = false; if (s_sta_retry_count < WIFI_STA_MAX_RETRY) { @@ -136,32 +160,32 @@ static void wifi_event_handler(void* arg, esp_event_base_t event_base, // ---------------------------------------- // WiFi AP 模式初始化 -// ---------------------------------------- +/** + * @brief 初始化 SoftAP 模式,供局域网 OTA 使用。 + * @return ESP_OK 成功,其他失败。 + */ static esp_err_t wifi_init_ap(void) { DEBUG_INFO("WIFI", "=== 初始化 WiFi AP 模式 ==="); + /** @brief 当前 WiFi 工作模式(AP 或 STA)。 */ s_wifi_mode = OTA_WIFI_MODE_AP; - // 创建 AP netif 并设置自定义 IP esp_netif_t *ap_netif = esp_netif_get_handle_from_ifkey("WIFI_AP_DEF"); if (ap_netif == NULL) { ap_netif = esp_netif_create_default_wifi_ap(); } if (ap_netif != NULL) { - // 停止 DHCP 服务器(必须先停才能改 IP) esp_netif_dhcps_stop(ap_netif); esp_netif_ip_info_t ip_info; memset(&ip_info, 0, sizeof(ip_info)); // 方案 A:SoftAP 使用 192.168.31.0/24 网段 - // ESP32 自己作为 AP 网关,电脑连入后拿到 192.168.31.x inet_pton(AF_INET, "192.168.31.1", &ip_info.ip); inet_pton(AF_INET, "192.168.31.1", &ip_info.gw); inet_pton(AF_INET, "255.255.255.0", &ip_info.netmask); ESP_ERROR_CHECK(esp_netif_set_ip_info(ap_netif, &ip_info)); - // 重新启动 DHCP 服务器,给连接的客户端分配 IP esp_netif_dhcps_start(ap_netif); DEBUG_INFO("WIFI", "AP IP 已设置: 192.168.31.1"); } @@ -190,12 +214,17 @@ static esp_err_t wifi_init_ap(void) // ---------------------------------------- // WiFi STA 模式初始化 -// ---------------------------------------- +/** + * @brief 初始化 STA 模式并连接预配置的路由器。 + * @return ESP_OK 成功,其他失败。 + */ static esp_err_t wifi_init_sta(void) { DEBUG_INFO("WIFI", "=== 初始化 WiFi STA 模式 ==="); + /** @brief 当前 WiFi 工作模式(AP 或 STA)。 */ s_wifi_mode = OTA_WIFI_MODE_STA; + /** @brief STA 连接重试次数。 */ s_sta_retry_count = 0; // ★ 设置国家代码:中国,允许信道 1-13 @@ -222,7 +251,6 @@ static esp_err_t wifi_init_sta(void) ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); - // 静态 IP 必须在 start/connect 之前配置,避免与 STA_START 自动连接发生竞态 wifi_set_static_ip(); ESP_ERROR_CHECK(esp_wifi_start()); @@ -233,7 +261,12 @@ static esp_err_t wifi_init_sta(void) return ESP_OK; } -// 等待 STA 连接成功并获取 IP(静态 IP 等 CONNECTED,DHCP 等 GOT_IP) +/** + * @brief 等待 STA 连接成功并获取 IP(静态 IP 等 CONNECTED,DHCP 等 GOT_IP) + * @param ip_str 输出缓冲区,存放获取到的 IP 字符串。 + * @param len 缓冲区长度。 + * @return ESP_OK 连接成功,其他失败。 + */ static esp_err_t wifi_wait_sta_connected(char *ip_str, size_t len) { SemaphoreHandle_t wait_sem = WIFI_STA_USE_STATIC_IP ? s_sta_connected_sem : s_sta_got_ip_sem; @@ -254,6 +287,7 @@ static esp_err_t wifi_wait_sta_connected(char *ip_str, size_t len) } DEBUG_INFO("WIFI", "%s 等待成功, 当前 IP: " IPSTR, wait_name, IP2STR(&ip_info.ip)); } + /** @brief STA 模式连接状态。 */ s_sta_connected = true; return ESP_OK; } @@ -262,7 +296,9 @@ static esp_err_t wifi_wait_sta_connected(char *ip_str, size_t len) return ESP_ERR_TIMEOUT; } -// 设置 STA 静态 IP +/** + * @brief 设置 STA 静态 IP + */ static void wifi_set_static_ip(void) { if (WIFI_STA_USE_STATIC_IP) { @@ -293,7 +329,11 @@ static void wifi_set_static_ip(void) // ---------------------------------------- // ESP32 OTA 内部函数 -// ---------------------------------------- +/** + * @brief 初始化 ESP32 自身的 OTA 写入流程。 + * @param total_size + * @return ESP_OK 成功,其他失败。 + */ static esp_err_t esp32_ota_begin(size_t total_size) { const esp_partition_t *boot_partition = esp_ota_get_running_partition(); @@ -327,16 +367,25 @@ static esp_err_t esp32_ota_begin(size_t total_size) esp_err_t err = esp_ota_begin(s_esp32_update_partition, total_size, &s_esp32_ota_handle); if (err != ESP_OK) { DEBUG_ERR("OTA", "esp_ota_begin 失败: %s", esp_err_to_name(err)); + /** @brief ESP32 OTA 目标分区。 */ s_esp32_update_partition = NULL; return err; } + /** @brief ESP32 OTA 已接收字节数。 */ s_esp32_received = 0; + /** @brief ESP32 自身 OTA 写入进行中标志。 */ s_esp32_ota_active = true; DEBUG_INFO("OTA", "ESP32 OTA 开始,目标分区: %s", s_esp32_update_partition->label); return ESP_OK; } +/** + * @brief 将接收到的数据块写入 ESP32 OTA 分区。 + * @param data + * @param len + * @return ESP_OK 成功,其他失败。 + */ static esp_err_t esp32_ota_write(const uint8_t *data, size_t len) { if (!s_esp32_ota_active || s_esp32_update_partition == NULL) { @@ -349,7 +398,11 @@ static esp_err_t esp32_ota_write(const uint8_t *data, size_t len) return err; } -// HTTP POST /ota/update - 完成 ESP32 OTA 并重启 +/** + * @brief HTTP POST /ota/update - 完成 ESP32 OTA 并重启 + * @param req + * @return + */ static esp_err_t esp32_ota_update_handler(httpd_req_t *req) { (void)req; @@ -364,6 +417,7 @@ static esp_err_t esp32_ota_update_handler(httpd_req_t *req) esp_err_t err = esp_ota_end(s_esp32_ota_handle); if (err != ESP_OK) { DEBUG_ERR("OTA", "esp_ota_end 失败: %s", esp_err_to_name(err)); + /** @brief ESP32 自身 OTA 写入进行中标志。 */ s_esp32_ota_active = false; httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, esp_err_to_name(err)); return ESP_FAIL; @@ -372,11 +426,13 @@ static esp_err_t esp32_ota_update_handler(httpd_req_t *req) err = esp_ota_set_boot_partition(s_esp32_update_partition); if (err != ESP_OK) { DEBUG_ERR("OTA", "esp_ota_set_boot_partition 失败: %s", esp_err_to_name(err)); + /** @brief ESP32 自身 OTA 写入进行中标志。 */ s_esp32_ota_active = false; httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, esp_err_to_name(err)); return ESP_FAIL; } + /** @brief ESP32 自身 OTA 写入进行中标志。 */ s_esp32_ota_active = false; DEBUG_INFO("OTA", "ESP32 OTA 完成,重启中..."); @@ -389,7 +445,11 @@ static esp_err_t esp32_ota_update_handler(httpd_req_t *req) return ESP_OK; } -// HTTP GET /ota/update - 查询 ESP32 OTA 状态 +/** + * @brief HTTP GET /ota/update - 查询 ESP32 OTA 状态 + * @param req + * @return + */ static esp_err_t esp32_ota_status_handler(httpd_req_t *req) { http_set_cors_headers(req); @@ -408,7 +468,13 @@ static esp_err_t esp32_ota_status_handler(httpd_req_t *req) } // 从 HTTP 流中读取完整的 JSON 头(以 } 结尾) -// 返回 JSON 字符串长度,失败返回 0 +/** + * @brief 返回 JSON 字符串长度,失败返回 0 + * @param req + * @param buf + * @param buf_size + * @return + */ static int recv_json_header(httpd_req_t *req, char *buf, int buf_size) { int total = 0; @@ -429,7 +495,11 @@ static int recv_json_header(httpd_req_t *req, char *buf, int buf_size) // ---------------------------------------- // 统一 /ota/upload 处理器(自动识别目标) -// ---------------------------------------- +/** + * @brief HTTP 文件上传处理器,接收固件并写入 Flash。 + * @param req + * @return ESP_OK 成功,其他失败。 + */ static esp_err_t ota_upload_handler(httpd_req_t *req) { http_set_cors_headers(req); @@ -592,6 +662,7 @@ static esp_err_t ota_upload_handler(httpd_req_t *req) ota_meta_t meta; meta_load(&meta); if (meta.stored_size >= meta.total_size) { + /** @brief GD32 固件存储完成标志。 */ s_gd32_storage_done = true; DEBUG_INFO("WIFI", "GD32 固件已存储,触发转发..."); wifi_ota_signal_relay(); @@ -603,7 +674,11 @@ static esp_err_t ota_upload_handler(httpd_req_t *req) return ESP_OK; } -// HTTP POST /ota/relay 处理器 +/** + * @brief HTTP POST /ota/relay 处理器 + * @param req + * @return + */ static esp_err_t ota_relay_trigger_handler(httpd_req_t *req) { (void)req; @@ -619,7 +694,11 @@ static esp_err_t ota_relay_trigger_handler(httpd_req_t *req) return ESP_OK; } -// HTTP GET /ota/status 处理器 +/** + * @brief HTTP GET /ota/status 处理器 + * @param req + * @return + */ static esp_err_t ota_status_handler(httpd_req_t *req) { http_set_cors_headers(req); @@ -647,7 +726,11 @@ static esp_err_t ota_status_handler(httpd_req_t *req) return ESP_OK; } -// HTTP GET /health 处理器 +/** + * @brief HTTP GET /health 处理器 + * @param req + * @return + */ static esp_err_t health_handler(httpd_req_t *req) { http_set_cors_headers(req); @@ -669,7 +752,11 @@ static esp_err_t health_handler(httpd_req_t *req) return ESP_OK; } -// HTTP GET /logs - 拉取并清空日志缓冲区 +/** + * @brief HTTP GET /logs - 拉取并清空日志缓冲区 + * @param req + * @return + */ static esp_err_t logs_handler(httpd_req_t *req) { http_set_cors_headers(req); @@ -691,7 +778,11 @@ static esp_err_t logs_handler(httpd_req_t *req) // HTTP POST /can/send 处理器 // 上位机发送 CAN 帧,经 ESP32 转发到 GD32 // 请求体: {"ch": 0, "id": 123456, "data": "0102030405060708", "dlc": 8} -// data 为 hex 字符串(可不填,自动用 dlc 决定实际发送长度) +/** + * @brief data 为 hex 字符串(可不填,自动用 dlc 决定实际发送长度) + * @param req + * @return + */ static esp_err_t can_send_handler(httpd_req_t *req) { http_set_cors_headers(req); @@ -768,7 +859,11 @@ static esp_err_t can_send_handler(httpd_req_t *req) return ESP_OK; } -// HTTP GET /can/status 处理器 +/** + * @brief HTTP GET /can/status 处理器 + * @param req + * @return + */ static esp_err_t can_status_handler(httpd_req_t *req) { http_set_cors_headers(req); @@ -782,7 +877,11 @@ static esp_err_t can_status_handler(httpd_req_t *req) return ESP_OK; } -// HTTP GET /wifi/status 处理器 +/** + * @brief HTTP GET /wifi/status 处理器 + * @param req + * @return + */ static esp_err_t wifi_status_handler(httpd_req_t *req) { http_set_cors_headers(req); @@ -814,7 +913,11 @@ static esp_err_t wifi_status_handler(httpd_req_t *req) return ESP_OK; } -// HTTP GET /system/status 处理器 +/** + * @brief HTTP GET /system/status 处理器 + * @param req + * @return + */ static esp_err_t system_status_handler(httpd_req_t *req) { http_set_cors_headers(req); @@ -864,7 +967,10 @@ static esp_err_t system_status_handler(httpd_req_t *req) // ---------------------------------------- // 启动 HTTP OTA 服务器 -// ---------------------------------------- +/** + * @brief 启动 HTTP OTA 服务器,注册各接口路由。 + * @return ESP_OK 成功,其他失败。 + */ static esp_err_t http_ota_server_start(void) { httpd_config_t config = HTTPD_DEFAULT_CONFIG(); @@ -1055,7 +1161,9 @@ static esp_err_t http_ota_server_start(void) // ---------------------------------------- // 公开 API -// ---------------------------------------- +/** + * @brief 通知 OTA 中继开始向 GD32 转发固件。 + */ void wifi_ota_signal_relay(void) { if (s_relay_triggered) { @@ -1063,6 +1171,7 @@ void wifi_ota_signal_relay(void) return; } + /** @brief OTA 中继是否已触发标志。 */ s_relay_triggered = true; if (g_ota_dispatch_queue) { @@ -1072,11 +1181,20 @@ void wifi_ota_signal_relay(void) } } +/** + * @brief 获取当前 WiFi 工作模式(AP 或 STA)。 + * @return 当前 WiFi 模式枚举值。 + */ ota_wifi_mode_t wifi_get_mode(void) { + /** @brief 当前 WiFi 工作模式(AP 或 STA)。 */ return s_wifi_mode; } +/** + * @brief 获取当前 WiFi 信号强度。 + * @return 信号强度(dBm),未连接时返回无效值。 + */ int8_t wifi_get_rssi(void) { if (s_wifi_mode != OTA_WIFI_MODE_STA || !s_sta_connected) { @@ -1089,8 +1207,13 @@ int8_t wifi_get_rssi(void) return -127; } +/** + * @brief 判断 STA 是否已连接到上级 WiFi。 + * @return true 已连接,false 未连接。 + */ bool wifi_is_connected(void) { + /** @brief STA 模式连接状态。 */ return s_sta_connected; } @@ -1098,12 +1221,14 @@ bool wifi_is_connected(void) // ★ WiFi 早期初始化(在 app_main 调度器启动前调用) // 包括:NVS → netif → event handler → WiFi driver → 配置 → connect // 返回:true = STA 模式已启动(正在连接),false = 需要切 AP -// ---------------------------------------- +/** + * @brief 在调度器启动前初始化 WiFi 并尝试 STA 连接。 + * @return true STA 初始化中,false 需回退 AP。 + */ bool wifi_early_init(void) { DEBUG_INFO("WIFI", "=== WiFi 早期初始化 (调度器启动前) ==="); - // NVS(WiFi 校准数据存储依赖 NVS) esp_err_t err = nvs_flash_init(); if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { // NVS 分区损坏或版本不匹配,擦除后重试 @@ -1117,8 +1242,9 @@ bool wifi_early_init(void) DEBUG_INFO("WIFI", "NVS 初始化成功"); } - // 创建信号量 + /** @brief STA 连接成功信号量。 */ s_sta_connected_sem = xSemaphoreCreateBinary(); + /** @brief STA 获取 IP 信号量。 */ s_sta_got_ip_sem = xSemaphoreCreateBinary(); if (!s_sta_connected_sem || !s_sta_got_ip_sem) { DEBUG_ERR("WIFI", "创建信号量失败"); @@ -1129,7 +1255,6 @@ bool wifi_early_init(void) ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL)); DEBUG_INFO("WIFI", "WiFi/IP 事件回调已注册"); - // 创建 STA netif(必须在 esp_wifi_init 之前) esp_netif_t *sta_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"); if (sta_netif == NULL) { sta_netif = esp_netif_create_default_wifi_sta(); @@ -1142,7 +1267,6 @@ bool wifi_early_init(void) DEBUG_INFO("WIFI", "STA netif 已存在,跳过创建"); } - // WiFi driver 初始化(必须在 netif 创建之后) wifi_init_config_t wifi_cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&wifi_cfg)); DEBUG_INFO("WIFI", "WiFi driver 初始化成功"); @@ -1169,7 +1293,10 @@ bool wifi_early_init(void) // WiFi OTA FreeRTOS 任务 // WiFi 初始化已在 app_main 中通过 wifi_early_init() 完成 // 此任务负责:等待连接结果 → 启动 HTTP 服务器 → 状态监控 -// ---------------------------------------- +/** + * @brief WiFi OTA FreeRTOS 任务,提供 HTTP 上传和中继触发。 + * @param pvParameters FreeRTOS 任务参数,未使用。 + */ void wifi_ota_task(void *pvParameters) { (void)pvParameters; @@ -1185,13 +1312,13 @@ void wifi_ota_task(void *pvParameters) } else { DEBUG_WARN("WIFI", "STA 连接超时,切换到 AP 模式..."); esp_wifi_stop(); // 先停 STA,再切 AP + /** @brief 当前 WiFi 工作模式(AP 或 STA)。 */ s_wifi_mode = OTA_WIFI_MODE_AP; wifi_init_ap(); } } // AP 模式:wifi_early_init 已完成,无需额外操作 - // 启动 HTTP 服务器 esp_err_t err = http_ota_server_start(); if (err != ESP_OK) { DEBUG_ERR("WIFI", "HTTP 服务器启动失败"); @@ -1217,18 +1344,12 @@ void wifi_ota_task(void *pvParameters) if (i == 0) vTaskDelay(pdMS_TO_TICKS(200)); } - // 心跳 + WiFi 状态监控 - // 每秒一次循环:每 3 秒发心跳包,每 10 秒检查 RSSI + // WiFi 状态监控 + // 每秒一次循环:每 10 秒检查 RSSI uint32_t tick = 0; while (1) { tick++; - // 轻量心跳:每 3 秒发一次,上位机据此判断 ESP32 是否在线 - if (tick % 3 == 0 && s_sta_connected) { - const char hb[] = "{\"t\":\"hb\"}\n"; - debug_log_send((const uint8_t *)hb, sizeof(hb) - 1); - } - // 每 10 秒检查一次 RSSI if (tick % 10 == 0) { int8_t rssi = wifi_get_rssi(); diff --git a/main/server/wifi_ota.h b/main/server/wifi_ota.h index 490fc07..2bf58f5 100644 --- a/main/server/wifi_ota.h +++ b/main/server/wifi_ota.h @@ -13,24 +13,40 @@ typedef enum { OTA_WIFI_MODE_STA // STA 模式(连接路由器) } ota_wifi_mode_t; -// WiFi OTA 任务入口 +/** + * @brief WiFi OTA 任务入口,负责 HTTP 服务、上传处理和 OTA 触发流程。 + * @param pvParameters FreeRTOS 任务参数,未使用。 + */ void wifi_ota_task(void *pvParameters); -// ★ WiFi 早期初始化(app_main 调度前调用) -// 包括 NVS、WiFi driver、netif、event handler、配置、连接尝试 -// 返回 true = STA 模式启动中,false = 需要在 task 中切换 AP +// 在 app_main 早期执行 WiFi 初始化并尝试以 STA 模式联网。 +/** + * @brief 返回 true 表示 STA 初始化已开始,false 表示后续应回退到 AP 模式。 + * @return true STA 初始化中,false 需回退 AP。 + */ bool wifi_early_init(void); -// 信号通知 OTA 中继开始(存储完成后调用) +/** + * @brief 在固件存储完成后通知 OTA 中继开始向 GD32 转发。 + */ void wifi_ota_signal_relay(void); -// 获取当前 WiFi 模式 +/** + * @brief 获取当前 WiFi 工作模式(AP 或 STA)。 + * @return 当前 WiFi 模式枚举值。 + */ ota_wifi_mode_t wifi_get_mode(void); -// 获取 WiFi RSSI +/** + * @brief 获取当前 WiFi RSSI,未连接时通常返回无效/默认值。 + * @return 信号强度(dBm),未连接时返回无效值。 + */ int8_t wifi_get_rssi(void); -// STA 是否已连接 +/** + * @brief 判断当前 STA 模式下是否已经连接到上级 WiFi。 + * @return true 已连接,false 未连接。 + */ bool wifi_is_connected(void); #endif // WIFI_OTA_H diff --git a/main/transport/protocol.cpp b/main/transport/protocol.cpp index b022720..91270b4 100644 --- a/main/transport/protocol.cpp +++ b/main/transport/protocol.cpp @@ -13,8 +13,12 @@ #include "protocol.h" #include "monitor/debug_log.h" +/** @brief 协议层初始化标志。 */ static bool s_protocol_initialized = false; +/** + * @brief 初始化协议层,准备 CRC 和 AES 模块。 + */ void protocol_init(void) { if (s_protocol_initialized) return; @@ -22,11 +26,17 @@ void protocol_init(void) DEBUG_INFO("PROT", "=== 初始化协议层 ==="); DEBUG_INFO("PROT", "协议层功能: CRC16 Modbus + 硬件 AES-128-CBC"); + /** @brief 协议层初始化标志。 */ s_protocol_initialized = true; DEBUG_INFO("PROT", "协议层就绪"); } -// CRC16 Modbus(LE,低字节在前) +/** + * @brief CRC16 Modbus(LE,低字节在前) + * @param data 待校验的数据指针。 + * @param len 数据长度(字节)。 + * @return CRC16 校验值。 + */ uint16_t crc16_modbus(const uint8_t *data, size_t len) { uint16_t crc = 0xFFFF; @@ -43,7 +53,14 @@ uint16_t crc16_modbus(const uint8_t *data, size_t len) return (crc >> 8) | (crc << 8); } -// 构建 SPI 帧: SOF + CMD + LEN(2LE) + PAYLOAD + CRC16(2LE) +/** + * @brief 构建 SPI 帧: SOF + CMD + LEN(2LE) + PAYLOAD + CRC16(2LE) + * @param cmd 命令码。 + * @param payload 负载数据指针,可为 NULL。 + * @param len 负载长度(字节)。 + * @param out_frame 输出帧缓冲区。 + * @return 帧总长度,失败返回 -1。 + */ int frame_build(uint8_t cmd, const uint8_t *payload, size_t len, uint8_t *out_frame) { if (len > FRAME_MAX_PAYLOAD) { @@ -253,7 +270,12 @@ void scramble_generate(uint32_t timestamp, uint32_t mem_addr, memcpy(nonce_out, state, 16); } -// 会话密钥初始化 +/** + * @brief 会话密钥初始化 + * @param sess 待初始化的会话上下文。 + * @param k_session 128 位会话密钥。 + * @return 0 成功,-1 失败。 + */ int session_key_init(session_ctx_t *sess, const uint8_t *k_session) { if (!sess || !k_session) return -1; @@ -266,6 +288,10 @@ int session_key_init(session_ctx_t *sess, const uint8_t *k_session) return 0; } +/** + * @brief 清除会话上下文中的密钥和状态。 + * @param sess 待清除的会话上下文。 + */ void session_key_clear(session_ctx_t *sess) { if (sess) { @@ -275,6 +301,14 @@ void session_key_clear(session_ctx_t *sess) } } +/** + * @brief 使用会话上下文加密数据。 + * @param sess 会话上下文。 + * @param in 输入明文。 + * @param len 数据长度。 + * @param out 输出密文缓冲区。 + * @return 密文长度,失败返回 -1。 + */ int session_encrypt(session_ctx_t *sess, const uint8_t *in, size_t len, uint8_t *out) { if (!sess || !in || !out) return -1; @@ -291,6 +325,14 @@ int session_encrypt(session_ctx_t *sess, const uint8_t *in, size_t len, uint8_t return ret; } +/** + * @brief 使用会话上下文解密数据。 + * @param sess 会话上下文。 + * @param in 输入密文。 + * @param len 数据长度。 + * @param out 输出明文缓冲区。 + * @return 明文长度,失败返回 -1。 + */ int session_decrypt(session_ctx_t *sess, const uint8_t *in, size_t len, uint8_t *out) { if (!sess || !in || !out) return -1; @@ -307,7 +349,11 @@ int session_decrypt(session_ctx_t *sess, const uint8_t *in, size_t len, uint8_t return ret; } -// 使用 ESP-IDF 硬件 RNG 生成随机数 +/** + * @brief 使用 ESP-IDF 硬件 RNG 生成随机数 + * @param nonce 输出缓冲区。 + * @param len 随机数字节数。 + */ void generate_random_nonce(uint8_t *nonce, size_t len) { esp_fill_random(nonce, len); diff --git a/main/transport/protocol.h b/main/transport/protocol.h index 112e8e2..ad9b9c0 100644 --- a/main/transport/protocol.h +++ b/main/transport/protocol.h @@ -46,48 +46,119 @@ inline constexpr size_t CHIP_UID_LEN = 12; typedef struct { uint8_t key[16]; // 会话密钥 K_session uint8_t iv[16]; // CBC 模式 IV - int used; + int used; // 会话是否已初始化标志 } session_ctx_t; -// 初始化协议层 +/** + * @brief 初始化协议层资源,建立 CRC/AES 等基础能力的可用状态。 + */ void protocol_init(void); -// CRC16 Modbus 计算(低字节在前,LE) +/** + * @brief 计算一段数据的 CRC16-Modbus 校验值。 + * @param data 待校验的数据指针。 + * @param len 数据长度(字节)。 + * @return CRC16 校验值。 + */ uint16_t crc16_modbus(const uint8_t *data, size_t len); -// 构建帧: 返回总帧长度 -// 输出格式: SOF + CMD + LEN(2) + PAYLOAD + CRC16(2) +/** + * @brief 按协议格式构建一帧数据,返回生成后的总帧长度。 + * @param cmd 命令码。 + * @param payload 负载数据指针,可为 NULL。 + * @param len 负载长度(字节)。 + * @param out_frame 输出帧缓冲区。 + * @return 帧总长度,失败返回 -1。 + */ int frame_build(uint8_t cmd, const uint8_t *payload, size_t len, uint8_t *out_frame); -// 解析帧: 返回 payload 起始偏移,失败返回 -1 -// 检查 SOF,验证 CRC16,提取 CMD 和 LEN +/** + * @brief 解析一帧协议数据,提取命令字、负载和长度,失败返回 -1。 + * @param frame 待解析的帧数据。 + * @param frame_len 帧数据总长度。 + * @param out_cmd 输出参数,解析出的命令码。 + * @param out_payload 输出缓冲区,存放解析出的负载。 + * @param out_len 输出参数,负载实际长度。 + * @return 0 成功,-1 失败。 + */ int frame_parse(const uint8_t *frame, size_t frame_len, uint8_t *out_cmd, uint8_t *out_payload, size_t *out_len); -// T2.2: AES-128 CBC 加密 -// 使用预设主密钥或会话密钥 -// 注意: 函数名带 protocol_ 前缀以避免与 ESP-IDF wpa_supplicant 组件冲突 +/** + * @brief 使用 AES-128-CBC 对明文进行加密,返回密文长度,失败返回负值。 + * @param plaintext 明文数据。 + * @param len 明文长度(字节)。 + * @param key 128 位 AES 密钥。 + * @param iv 128 位 CBC 初始向量。 + * @param ciphertext_out 输出缓冲区,存放密文。 + * @return 密文长度,失败返回 -1。 + */ int protocol_aes_encrypt(const uint8_t *plaintext, size_t len, const uint8_t *key, const uint8_t *iv, uint8_t *ciphertext_out); +/** + * @brief 使用 AES-128-CBC 对密文进行解密,返回去除填充后的明文长度。 + * @param ciphertext 密文数据。 + * @param len 密文长度(字节,须为 16 的倍数)。 + * @param key 128 位 AES 密钥。 + * @param iv 128 位 CBC 初始向量。 + * @param plaintext_out 输出缓冲区,存放解密后的明文。 + * @return 明文长度,失败返回 -1。 + */ int protocol_aes_decrypt(const uint8_t *ciphertext, size_t len, const uint8_t *key, const uint8_t *iv, uint8_t *plaintext_out); -// T2.1: 搅伴算法 - 生成随机数 -// 输入: 时间戳 + 内存地址 + rand_seed -// 输出: 16 字节 nonce_server +/** + * @brief 根据时间戳、内存地址和随机种子生成 16 字节随机数。 + * @param timestamp 时间戳种子。 + * @param mem_addr 内存地址种子。 + * @param rand_seed 随机数种子。 + * @param nonce_out 输出缓冲区,存放生成的 16 字节随机数。 + */ void scramble_generate(uint32_t timestamp, uint32_t mem_addr, uint32_t rand_seed, uint8_t *nonce_out); -// T2.4: 会话密钥管理 +/** + * @brief 用给定会话密钥初始化会话上下文。 + * @param sess 待初始化的会话上下文。 + * @param k_session 128 位会话密钥。 + * @return 0 成功,-1 失败。 + */ int session_key_init(session_ctx_t *sess, const uint8_t *k_session); + +/** + * @brief 清除会话上下文中的密钥与状态信息。 + * @param sess 待清除的会话上下文。 + */ void session_key_clear(session_ctx_t *sess); + +/** + * @brief 使用当前会话上下文对输入数据执行加密。 + * @param sess 会话上下文。 + * @param in 输入明文。 + * @param len 数据长度。 + * @param out 输出密文缓冲区。 + * @return 密文长度,失败返回 -1。 + */ int session_encrypt(session_ctx_t *sess, const uint8_t *in, size_t len, uint8_t *out); + +/** + * @brief 使用当前会话上下文对输入数据执行解密。 + * @param sess 会话上下文。 + * @param in 输入密文。 + * @param len 数据长度。 + * @param out 输出明文缓冲区。 + * @return 明文长度,失败返回 -1。 + */ int session_decrypt(session_ctx_t *sess, const uint8_t *in, size_t len, uint8_t *out); -// 使用 ESP-IDF 硬件 RNG 生成随机数 +/** + * @brief 使用 ESP-IDF 硬件 RNG 生成指定长度的随机数。 + * @param nonce 输出缓冲区。 + * @param len 随机数字节数。 + */ void generate_random_nonce(uint8_t *nonce, size_t len); #endif // PROTOCOL_H \ No newline at end of file diff --git a/main/transport/spi_master.cpp b/main/transport/spi_master.cpp index 747bc12..946c818 100644 --- a/main/transport/spi_master.cpp +++ b/main/transport/spi_master.cpp @@ -15,9 +15,13 @@ #include "spi_master.h" #include "monitor/debug_log.h" +/** @brief SPI2 从设备句柄。 */ spi_device_handle_t g_spi_device = NULL; -// SPI2 总线初始化 +/** + * @brief SPI2 总线初始化 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t spi_master_init(void) { DEBUG_INFO("SPI", "=== 初始化 SPI2 (VSPI) ==="); @@ -100,12 +104,27 @@ esp_err_t spi_sync_transfer(uint8_t cmd, const uint8_t *payload, } // 仅发送 +/** + * @brief 调用同步传输接口发送数据,不要求接收返回内容。 + * @param cmd SPI 命令码。 + * @param payload 发送数据指针。 + * @param len 发送数据长度。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t spi_send(uint8_t cmd, const uint8_t *payload, size_t len) { return spi_sync_transfer(cmd, payload, len, NULL, 0, 3000); } // 仅接收 +/** + * @brief 发送哑数据产生时钟,从从设备同步读取指定长度数据。 + * @param cmd SPI 命令码。 + * @param recv_buf 接收缓冲区。 + * @param len 接收长度。 + * @param timeout_ms 超时时间(毫秒)。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t spi_recv(uint8_t cmd, uint8_t *recv_buf, size_t len, uint32_t timeout_ms) { uint8_t dummy[64] = {0}; @@ -182,6 +201,7 @@ static int can_frame_send(can_channel_t channel, uint32_t ext_id, return 0; } +// 对外暴露的 GD32 CAN 发送接口,会在必要时修正默认 DLC。 int gd32_can_send(can_channel_t channel, uint32_t ext_id, const uint8_t *data, uint8_t dlc, uint8_t *recv_buf, uint8_t recv_max, @@ -193,6 +213,7 @@ int gd32_can_send(can_channel_t channel, uint32_t ext_id, return can_frame_send(channel, ext_id, data, dlc, recv_buf, recv_max, timeout_ms); } +// 对外暴露的无响应 GD32 CAN 发送接口。 int gd32_can_send_no_reply(can_channel_t channel, uint32_t ext_id, const uint8_t *data, uint8_t dlc) { diff --git a/main/transport/spi_master.h b/main/transport/spi_master.h index c9b2d8f..20f4c93 100644 --- a/main/transport/spi_master.h +++ b/main/transport/spi_master.h @@ -38,47 +38,74 @@ typedef enum { typedef struct { can_channel_t channel; // CAN 通道 (0~2) uint32_t ext_id; // 扩展帧 ID (29bit, Big-Endian 存储) - uint8_t data[CAN_MAX_DLC]; + uint8_t data[CAN_MAX_DLC]; // CAN 数据负载 (最大 64 字节) uint8_t dlc; // 数据长度 (1~64) } can_frame_t; -// ============================================================ -// 发送 CANFD 帧 -// channel: CAN 通道 (0, 1, 2) -// ext_id: 扩展帧 ID (29bit) -// data: 数据内容 (1~64 字节) -// dlc: 数据长度 (自动识别,可传 0 由 data 长度决定) -// recv_buf: 接收缓冲区 (可传 NULL) -// recv_max: 期望接收最大字节数 -// timeout_ms: 超时时间 (ms) -// 返回: >= 0 成功(实际接收字节数), < 0 失败 -// ============================================================ +/** + * @brief 发送一帧 CANFD 数据,可选等待并读取响应内容。 + * @param channel CAN 通道(0~2)。 + * @param ext_id 29 位扩展帧 ID。 + * @param data 数据负载指针。 + * @param dlc 数据长度,0 则自动推断。 + * @param recv_buf 接收缓冲区,可为 NULL。 + * @param recv_max 期望最大接收字节数。 + * @param timeout_ms 超时时间(毫秒)。 + * @return >= 0 接收字节数,< 0 失败。 + */ int gd32_can_send(can_channel_t channel, uint32_t ext_id, const uint8_t *data, uint8_t dlc, uint8_t *recv_buf, uint8_t recv_max, uint32_t timeout_ms); -// 快速发送接口(不带响应接收) -// 用于广播/通知类 CAN 帧 +/** + * @brief 发送一帧无需响应的 CANFD 数据,适合广播或通知场景。 + * @param channel CAN 通道(0~2)。 + * @param ext_id 29 位扩展帧 ID。 + * @param data 数据负载指针。 + * @param dlc 数据长度。 + * @return >= 0 成功,< 0 失败。 + */ int gd32_can_send_no_reply(can_channel_t channel, uint32_t ext_id, const uint8_t *data, uint8_t dlc); -// ============================================================ - -// 初始化 SPI2 (VSPI) 总线和设备 +/** + * @brief 初始化 SPI2 总线与目标从设备。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t spi_master_init(void); -// 同步 SPI 传输(阻塞式) -// 发送 cmd + payload,接收响应到 recv_buf -// 返回: ESP_OK 成功, ESP_ERR_TIMEOUT 超时 +/** + * @brief 执行一次同步 SPI 传输,发送命令和负载并可选接收返回数据。 + * @param cmd SPI 命令码。 + * @param payload 发送数据指针。 + * @param payload_len 发送数据长度。 + * @param recv_buf 接收缓冲区,可为 NULL。 + * @param recv_len 期望接收长度。 + * @param timeout_ms 超时时间(毫秒)。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t spi_sync_transfer(uint8_t cmd, const uint8_t *payload, size_t payload_len, uint8_t *recv_buf, size_t recv_len, uint32_t timeout_ms); -// 仅发送(不期望接收) +/** + * @brief 仅发送 SPI 数据,不关心返回内容。 + * @param cmd SPI 命令码。 + * @param payload 发送数据指针。 + * @param len 发送数据长度。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t spi_send(uint8_t cmd, const uint8_t *payload, size_t len); -// 仅接收(发送哑数据) +/** + * @brief 仅接收 SPI 数据,底层会发送哑数据产生时钟。 + * @param cmd SPI 命令码。 + * @param recv_buf 接收缓冲区。 + * @param len 接收长度。 + * @param timeout_ms 超时时间(毫秒)。 + * @return ESP_OK 成功,其他失败。 + */ esp_err_t spi_recv(uint8_t cmd, uint8_t *recv_buf, size_t len, uint32_t timeout_ms); #endif // SPI_MASTER_H \ No newline at end of file