453 lines
13 KiB
C++
453 lines
13 KiB
C++
/*
|
|||
|
|
* 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 "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 "debug_log.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));
|
||
|
|
wifi_config.ap.ssid_len = strlen(WIFI_AP_SSID);
|
||
|
|
memcpy(wifi_config.ap.password, WIFI_AP_PASSWORD, strlen(WIFI_AP_PASSWORD));
|
||
|
|
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;
|
||
|
|
|
||
|
|
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");
|
||
|
|
|
||
|
|
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, sizeof(wifi_config.sta.ssid));
|
||
|
|
memcpy(wifi_config.sta.password, WIFI_STA_PASSWORD, sizeof(wifi_config.sta.password));
|
||
|
|
|
||
|
|
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_USE_STATIC_IP
|
||
|
|
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", "收到固件上传请求, 内容长度: %d", req->content_len);
|
||
|
|
|
||
|
|
if (req->content_len > OTA_MAX_FIRMWARE_SIZE) {
|
||
|
|
DEBUG_ERR("WIFI", "固件太大: %d 字节", req->content_len);
|
||
|
|
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", "请求接收失败");
|
||
|
|
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 字节", total_size);
|
||
|
|
}
|
||
|
|
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 写入失败 at offset %u", flash_offset);
|
||
|
|
}
|
||
|
|
|
||
|
|
meta_update(flash_offset + ret, received / OTA_BLOCK_SIZE);
|
||
|
|
|
||
|
|
received += ret;
|
||
|
|
DEBUG_DBG("WIFI", "已接收: %d / %d 字节", received, req->content_len);
|
||
|
|
}
|
||
|
|
|
||
|
|
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", "收到触发 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)
|
||
|
|
{
|
||
|
|
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);
|
||
|
|
|
||
|
|
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)
|
||
|
|
{
|
||
|
|
health_state_t state = health_get_current();
|
||
|
|
|
||
|
|
char resp[128];
|
||
|
|
int len = snprintf(resp, sizeof(resp),
|
||
|
|
"{\"health\": %d, \"free_heap\": %lu}",
|
||
|
|
state, (unsigned long)esp_get_free_heap_size());
|
||
|
|
|
||
|
|
httpd_resp_set_type(req, "application/json");
|
||
|
|
httpd_resp_send(req, resp, len);
|
||
|
|
|
||
|
|
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 服务器,端口 %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);
|
||
|
|
|
||
|
|
DEBUG_INFO("WIFI", "HTTP 服务器已启动");
|
||
|
|
|
||
|
|
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));
|
||
|
|
}
|
||
|
|
|
||
|
|
// 初始化网络接口
|
||
|
|
ESP_ERROR_CHECK(esp_netif_init());
|
||
|
|
|
||
|
|
// STA 优先,失败自动切 AP
|
||
|
|
#ifdef CONFIG_WIFI_MODE_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
|
||
|
|
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", rssi);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
vTaskDelay(pdMS_TO_TICKS(10000));
|
||
|
|
}
|
||
|
|
}
|