Files
ESP32_COMM/_assistant_backups/2026-05-04/wifi_ota.cpp.bak_2026-05-04_2132
T
小龙虾 478e5e5a31 feat(ota-relay): 更新ESP32-C3 OTA中继固件配置和功能增强
- 修改WiFi配置默认为STA模式连接路由器,并调整相关参数
- 优化调试日志系统,添加环形缓冲区支持HTTP日志拉取功能
- 重构健康监控,提高温度监控频率至1秒并添加电压监控定时器
- 更新分区表配置以适应4MB Flash布局
- 添加OTA更新组件依赖和蓝牙支持
- 修复UDP socket延迟创建时机,确保网络接口初始化正确
- 调整WiFi信号强度告警阈值,提升连接稳定性
- 更新README文档中的IP地址配置示例
- 修改VSCode端口配置为COM13
2026-05-05 03:28:44 +08:00

973 lines
33 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* wifi_ota.c - WiFi OTA 服务器实现
* T4.1 - T4.5
*/
#include <string.h>
#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));
}
}