/* * 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)); } }