/* * 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 "nvs_flash.h" #include "lwip/netdb.h" #include "esp_netif.h" #include "esp_netif_types.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_ota_storage_done = false; static bool s_relay_triggered = false; static bool s_sta_connected = false; static httpd_handle_t s_http_server = NULL; // ---------------------------------------- // WiFi AP 模式初始化 // ---------------------------------------- static esp_err_t wifi_init_ap(void) { DEBUG_INFO("WIFI", "=== 初始化 WiFi AP 模式 ==="); s_wifi_mode = OTA_WIFI_MODE_AP; 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.4.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; wifi_config_t wifi_config = {}; wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA_WPA2_PSK; wifi_config.sta.pmf_cfg.capable = true; wifi_config.sta.pmf_cfg.required = false; 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", WIFI_STA_SSID); ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); ESP_ERROR_CHECK(esp_wifi_start()); DEBUG_INFO("WIFI", "WiFi STA 连接中, SSID: %s...", WIFI_STA_SSID); return ESP_OK; } // 等待 STA 连接成功并获取 IP static esp_err_t wifi_wait_sta_connected(char *ip_str, size_t len) { int retry = 0; while (retry < 60) { // 最多等 6 秒 vTaskDelay(pdMS_TO_TICKS(100)); wifi_ap_record_t ap_info; if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) { s_sta_connected = true; 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)); } } return ESP_OK; } retry++; } return ESP_ERR_TIMEOUT; } // 设置 STA 静态 IP static void wifi_set_static_ip(void) { #if WIFI_STA_STATIC_IP_IS_ENABLED esp_netif_t *sta_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"); if (sta_netif) { 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); } #else DEBUG_INFO("WIFI", "使用 DHCP 获取 IP"); #endif } // ---------------------------------------- // HTTP POST /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); 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[OTA_BLOCK_SIZE]; int received = 0; int first_chunk = 1; while (received < req->content_len) { int remaining = req->content_len - received; int to_read = remaining < sizeof(buf) ? remaining : sizeof(buf); int ret = httpd_req_recv(req, (char *)buf, to_read); if (ret <= 0) { DEBUG_ERR("WIFI", "HTTP 请求接收失败"); httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "接收失败"); return ESP_FAIL; } if (first_chunk && ret >= 6) { uint16_t seq = buf[0] | (buf[1] << 8); uint32_t total_size = buf[2] | (buf[3] << 8) | (buf[4] << 16) | (buf[5] << 24); if (seq == 0) { ota_meta_t meta = { .total_size = total_size, .stored_size = 0, .last_seq = 0, .chunk_count = (total_size + 4095) / 4096, .checksum = {0} }; meta_save(&meta); DEBUG_INFO("WIFI", "开始固件存储: %u 字节, %u 块", total_size, meta.chunk_count); } first_chunk = 0; } uint32_t flash_offset = (received / OTA_BLOCK_SIZE) * OTA_BLOCK_SIZE; esp_err_t err = flash_write_chunk(flash_offset, buf, ret); if (err != ESP_OK) { DEBUG_ERR("WIFI", "Flash 写入失败, offset=%u, err=%d", flash_offset, err); } meta_update(flash_offset + ret, received / OTA_BLOCK_SIZE); received += ret; // 每 10% 打印进度 if (received % (req->content_len / 10 + 1) < OTA_BLOCK_SIZE) { uint32_t progress = (received * 100) / req->content_len; DEBUG_INFO("WIFI", "上传进度: %d / %d 字节 (%u%%)", received, req->content_len, progress); } } DEBUG_INFO("WIFI", "固件存储完成: %d 字节", received); ota_meta_t meta; meta_load(&meta); if (meta.stored_size >= meta.total_size) { s_ota_storage_done = true; DEBUG_INFO("WIFI", "全部固件已存储,触发转发..."); wifi_ota_signal_relay(); } const char resp[] = "{\"status\": \"ok\", \"progress\": 100}"; 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_ota_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_ota_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; 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 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/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_ERROR_CHECK(esp_netif_init()); DEBUG_INFO("WIFI", "网络接口初始化成功"); // STA 优先,失败自动切 AP #if CONFIG_WIFI_MODE_IS_STA DEBUG_INFO("WIFI", "工作模式: STA (优先)"); err = wifi_init_sta(); if (err == ESP_OK) { char ip_str[16] = {0}; // 设置静态 IP(如果配置了) wifi_set_static_ip(); // 等待连接成功 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(); #endif // 启动 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)); } }