/* * 文件: components/wifi-connect/wifi-connect.c * 角色: Wi-Fi 连接、配网与网络状态管理 * 说明: * - 本文件用于实现当前模块的核心功能或接口定义。 * - 修改前请先确认该模块与其它任务/外设之间的数据流关系。 * - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。 */ #include #include #include #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" #include "driver/gpio.h" #include "esp_check.h" #include "esp_event.h" #include "esp_http_server.h" #include "esp_log.h" #include "esp_mac.h" #include "esp_netif.h" #include "esp_netif_ip_addr.h" #include "esp_timer.h" #include "esp_system.h" #include "esp_wifi.h" #include "nvs.h" #include "nvs_flash.h" #include "lwip/sockets.h" #include "lwip/inet.h" #include "wifi-connect.h" #define WIFI_CONNECT_NVS_NAMESPACE "wifi_connect" #define WIFI_CONNECT_NVS_KEY_SSID "ssid" #define WIFI_CONNECT_NVS_KEY_PASS "pass" #define WIFI_CONNECT_HTTP_BUF_SIZE 256 static const char *TAG = "wifi_connect"; /* 函数: wifi_connect_log_state_i * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static void wifi_connect_log_state_i(const char *state, const char *detail) { if (detail != NULL && detail[0] != '\0') ESP_LOGI(TAG, "【状态】%s:%s", state, detail); else ESP_LOGI(TAG, "【状态】%s", state); } /* 函数: wifi_connect_log_state_w * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static void wifi_connect_log_state_w(const char *state, const char *detail) { if (detail != NULL && detail[0] != '\0') ESP_LOGW(TAG, "【状态】%s:%s", state, detail); else ESP_LOGW(TAG, "【状态】%s", state); } /* 函数: wifi_connect_log_state_e * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static void wifi_connect_log_state_e(const char *state, const char *detail) { if (detail != NULL && detail[0] != '\0') ESP_LOGE(TAG, "【状态】%s:%s", state, detail); else ESP_LOGE(TAG, "【状态】%s", state); } typedef struct { wifi_connect_status_t status; bool initialized; bool wifi_started; bool provisioning_active; bool sta_connected; bool sta_connect_requested; bool auto_connecting; esp_netif_t *sta_netif; esp_netif_t *ap_netif; httpd_handle_t http_server; esp_event_handler_instance_t wifi_event_instance; esp_event_handler_instance_t ip_event_instance; TaskHandle_t dns_task; SemaphoreHandle_t lock; esp_timer_handle_t connect_timer; int dns_sock; bool dns_running; char ap_ssid[32]; char pending_ssid[33]; char pending_password[65]; char last_error[96]; char sta_ip[16]; esp_timer_handle_t ap_stop_timer; } wifi_connect_ctx_t; static wifi_connect_ctx_t s_ctx = { .status = WIFI_CONNECT_STATUS_IDLE, .dns_sock = -1, }; static const char *s_html_page = "" "" "设备联网控制台" "
" "

设备配网

" "
" "

请选择网络并输入密码连接路由器。

" "
" "" "" "
" "
" "" "
" "
"; /* 函数: wifi_connect_set_status_locked * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static void wifi_connect_set_status_locked(wifi_connect_status_t status) { s_ctx.status = status; } /* 函数: wifi_connect_set_error_locked * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static void wifi_connect_set_error_locked(const char *message) { if (message == NULL) { s_ctx.last_error[0] = '\0'; return; } snprintf(s_ctx.last_error, sizeof(s_ctx.last_error), "%s", message); } /* 函数: wifi_connect_save_credentials * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static esp_err_t wifi_connect_save_credentials(const char *ssid, const char *password) { nvs_handle_t handle; ESP_RETURN_ON_ERROR(nvs_open(WIFI_CONNECT_NVS_NAMESPACE, NVS_READWRITE, &handle), TAG, "open nvs failed"); esp_err_t err = nvs_set_str(handle, WIFI_CONNECT_NVS_KEY_SSID, ssid); if (err == ESP_OK) err = nvs_set_str(handle, WIFI_CONNECT_NVS_KEY_PASS, password); if (err == ESP_OK) err = nvs_commit(handle); nvs_close(handle); return err; } /* 函数: wifi_connect_clear_config * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ esp_err_t wifi_connect_clear_config(void) { nvs_handle_t handle; ESP_RETURN_ON_ERROR(nvs_open(WIFI_CONNECT_NVS_NAMESPACE, NVS_READWRITE, &handle), TAG, "open nvs failed"); esp_err_t err_ssid = nvs_erase_key(handle, WIFI_CONNECT_NVS_KEY_SSID); esp_err_t err_pass = nvs_erase_key(handle, WIFI_CONNECT_NVS_KEY_PASS); if (err_ssid != ESP_OK && err_ssid != ESP_ERR_NVS_NOT_FOUND) { nvs_close(handle); return err_ssid; } if (err_pass != ESP_OK && err_pass != ESP_ERR_NVS_NOT_FOUND) { nvs_close(handle); return err_pass; } esp_err_t err = nvs_commit(handle); nvs_close(handle); if (err != ESP_OK) return err; if (s_ctx.initialized && s_ctx.lock != NULL) { bool should_disconnect = false; xSemaphoreTake(s_ctx.lock, portMAX_DELAY); s_ctx.pending_ssid[0] = '\0'; s_ctx.pending_password[0] = '\0'; wifi_connect_set_error_locked(NULL); if (s_ctx.provisioning_active) wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_PROVISIONING); if (s_ctx.status == WIFI_CONNECT_STATUS_CONNECTING) { s_ctx.sta_connect_requested = false; s_ctx.auto_connecting = false; wifi_connect_set_status_locked(s_ctx.provisioning_active ? WIFI_CONNECT_STATUS_PROVISIONING : WIFI_CONNECT_STATUS_IDLE); should_disconnect = true; } xSemaphoreGive(s_ctx.lock); if (should_disconnect) esp_wifi_disconnect(); } wifi_connect_log_state_i("已清除保存的 Wi-Fi 配置", "下次上电将不会自动重连"); return ESP_OK; } /* 函数: wifi_connect_get_config * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ esp_err_t wifi_connect_get_config(wifi_connect_config_t *config) { ESP_RETURN_ON_FALSE(config != NULL, ESP_ERR_INVALID_ARG, TAG, "config is null"); memset(config, 0, sizeof(*config)); nvs_handle_t handle; esp_err_t err = nvs_open(WIFI_CONNECT_NVS_NAMESPACE, NVS_READONLY, &handle); if (err != ESP_OK) return err; size_t ssid_len = sizeof(config->ssid); size_t pass_len = sizeof(config->password); err = nvs_get_str(handle, WIFI_CONNECT_NVS_KEY_SSID, config->ssid, &ssid_len); if (err == ESP_OK) err = nvs_get_str(handle, WIFI_CONNECT_NVS_KEY_PASS, config->password, &pass_len); nvs_close(handle); config->has_config = (err == ESP_OK && config->ssid[0] != '\0'); return err; } /* 函数: wifi_connect_status_to_string * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static const char *wifi_connect_status_to_string(wifi_connect_status_t status) { switch (status) { case WIFI_CONNECT_STATUS_IDLE: return "idle"; case WIFI_CONNECT_STATUS_PROVISIONING: return "provisioning"; case WIFI_CONNECT_STATUS_CONNECTING: return "connecting"; case WIFI_CONNECT_STATUS_CONNECTED: return "connected"; case WIFI_CONNECT_STATUS_FAILED: return "failed"; case WIFI_CONNECT_STATUS_TIMEOUT: return "timeout"; default: return "unknown"; } } /* 函数: wifi_connect_get_status * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ wifi_connect_status_t wifi_connect_get_status(void) { if (!s_ctx.initialized || s_ctx.lock == NULL) return WIFI_CONNECT_STATUS_IDLE; wifi_connect_status_t status; xSemaphoreTake(s_ctx.lock, portMAX_DELAY); status = s_ctx.status; xSemaphoreGive(s_ctx.lock); return status; } /* 函数: wifi_connect_get_ip * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ const char *wifi_connect_get_ip(void) { if (!s_ctx.initialized || s_ctx.lock == NULL) return "0.0.0.0"; static char ip_buf[16]; xSemaphoreTake(s_ctx.lock, portMAX_DELAY); strncpy(ip_buf, s_ctx.sta_ip, sizeof(ip_buf)); xSemaphoreGive(s_ctx.lock); return ip_buf; } /* 函数: wifi_connect_send_json * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static esp_err_t wifi_connect_send_json(httpd_req_t *req, const char *json) { httpd_resp_set_type(req, "application/json"); return httpd_resp_sendstr(req, json); } /* 函数: wifi_connect_json_escape * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static void wifi_connect_json_escape(const char *src, char *dst, size_t dst_size) { size_t j = 0; for (size_t i = 0; src != NULL && src[i] != '\0' && j + 2 < dst_size; ++i) { char c = src[i]; if (c == '"' || c == '\\') { dst[j++] = '\\'; dst[j++] = c; } else if ((unsigned char)c >= 32 && (unsigned char)c <= 126) dst[j++] = c; } dst[j] = '\0'; } /* 函数: wifi_connect_auth_to_string * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static const char *wifi_connect_auth_to_string(wifi_auth_mode_t auth) { switch (auth) { case WIFI_AUTH_OPEN: return "OPEN"; case WIFI_AUTH_WEP: return "WEP"; case WIFI_AUTH_WPA_PSK: return "WPA"; case WIFI_AUTH_WPA2_PSK: return "WPA2"; case WIFI_AUTH_WPA_WPA2_PSK: return "WPA/WPA2"; case WIFI_AUTH_WPA2_ENTERPRISE: return "WPA2-ENT"; case WIFI_AUTH_WPA3_PSK: return "WPA3"; case WIFI_AUTH_WPA2_WPA3_PSK: return "WPA2/WPA3"; default: return "UNKNOWN"; } } /* 函数: wifi_connect_http_scan_handler * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static esp_err_t wifi_connect_http_scan_handler(httpd_req_t *req) { wifi_scan_config_t scan_cfg = {.show_hidden = false}; esp_err_t err = esp_wifi_scan_start(&scan_cfg, true); if (err != ESP_OK) return wifi_connect_send_json(req, "{\"networks\":[],\"error\":\"scan_failed\"}"); uint16_t count = CONFIG_WIFI_CONNECT_MAX_SCAN_RESULTS; wifi_ap_record_t *records = calloc(count, sizeof(wifi_ap_record_t)); if (records == NULL) return ESP_ERR_NO_MEM; err = esp_wifi_scan_get_ap_records(&count, records); if (err != ESP_OK) { free(records); return wifi_connect_send_json(req, "{\"networks\":[],\"error\":\"scan_read_failed\"}"); } size_t out_size = 512 + count * 96; char *out = calloc(1, out_size); if (out == NULL) { free(records); return ESP_ERR_NO_MEM; } size_t used = snprintf(out, out_size, "{\"networks\":["); for (uint16_t i = 0; i < count && used + 96 < out_size; ++i) { char ssid[65] = {0}; char escaped[130] = {0}; snprintf(ssid, sizeof(ssid), "%s", (char *)records[i].ssid); wifi_connect_json_escape(ssid, escaped, sizeof(escaped)); used += snprintf(out + used, out_size - used, "%s{\"ssid\":\"%s\",\"rssi\":%d,\"auth\":\"%s\"}", (i == 0 ? "" : ","), escaped, records[i].rssi, wifi_connect_auth_to_string(records[i].authmode)); } snprintf(out + used, out_size - used, "]}"); err = wifi_connect_send_json(req, out); free(out); free(records); return err; } /* 函数: wifi_connect_extract_json_string * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static bool wifi_connect_extract_json_string(const char *json, const char *key, char *out, size_t out_len) { char pattern[32]; snprintf(pattern, sizeof(pattern), "\"%s\":\"", key); const char *start = strstr(json, pattern); if (start == NULL) return false; start += strlen(pattern); size_t idx = 0; while (*start != '\0' && *start != '"' && idx + 1 < out_len) { if (*start == '\\' && *(start + 1) != '\0') start++; out[idx++] = *start++; } out[idx] = '\0'; return idx > 0 || strcmp(key, "password") == 0; } /* 函数: wifi_connect_apply_sta_credentials * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static esp_err_t wifi_connect_apply_sta_credentials(const char *ssid, const char *password) { wifi_config_t sta_cfg = {0}; snprintf((char *)sta_cfg.sta.ssid, sizeof(sta_cfg.sta.ssid), "%s", ssid); snprintf((char *)sta_cfg.sta.password, sizeof(sta_cfg.sta.password), "%s", password); sta_cfg.sta.scan_method = WIFI_FAST_SCAN; sta_cfg.sta.threshold.authmode = WIFI_AUTH_OPEN; sta_cfg.sta.pmf_cfg.capable = true; sta_cfg.sta.pmf_cfg.required = false; esp_err_t dis_err = esp_wifi_disconnect(); if (dis_err != ESP_OK && dis_err != ESP_ERR_WIFI_NOT_CONNECT) ESP_LOGW(TAG, "Disconnect STA failed: %s", esp_err_to_name(dis_err)); ESP_RETURN_ON_ERROR(esp_wifi_set_config(WIFI_IF_STA, &sta_cfg), TAG, "set sta config failed"); ESP_RETURN_ON_ERROR(esp_wifi_connect(), TAG, "wifi connect failed"); return ESP_OK; } /* 函数: wifi_connect_http_connect_handler * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static esp_err_t wifi_connect_http_connect_handler(httpd_req_t *req) { if (req->content_len <= 0 || req->content_len >= WIFI_CONNECT_HTTP_BUF_SIZE) return wifi_connect_send_json(req, "{\"ok\":false,\"error\":\"invalid_payload\"}"); char body[WIFI_CONNECT_HTTP_BUF_SIZE] = {0}; int received = httpd_req_recv(req, body, sizeof(body) - 1); if (received <= 0) return wifi_connect_send_json(req, "{\"ok\":false,\"error\":\"read_failed\"}"); body[received] = '\0'; char ssid[33] = {0}; char password[65] = {0}; if (!wifi_connect_extract_json_string(body, "ssid", ssid, sizeof(ssid))) return wifi_connect_send_json(req, "{\"ok\":false,\"error\":\"ssid_missing\"}"); wifi_connect_extract_json_string(body, "password", password, sizeof(password)); char req_msg[96] = {0}; snprintf(req_msg, sizeof(req_msg), "收到配网请求,目标网络:%s", ssid); wifi_connect_log_state_i("开始连接路由器", req_msg); xSemaphoreTake(s_ctx.lock, portMAX_DELAY); snprintf(s_ctx.pending_ssid, sizeof(s_ctx.pending_ssid), "%s", ssid); snprintf(s_ctx.pending_password, sizeof(s_ctx.pending_password), "%s", password); wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_CONNECTING); wifi_connect_set_error_locked(NULL); s_ctx.sta_connect_requested = true; s_ctx.auto_connecting = false; xSemaphoreGive(s_ctx.lock); esp_err_t err = wifi_connect_apply_sta_credentials(ssid, password); if (err != ESP_OK) { xSemaphoreTake(s_ctx.lock, portMAX_DELAY); wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_FAILED); wifi_connect_set_error_locked("启动连接失败"); xSemaphoreGive(s_ctx.lock); return wifi_connect_send_json(req, "{\"ok\":false,\"error\":\"connect_start_failed\"}"); } esp_timer_stop(s_ctx.connect_timer); esp_timer_start_once(s_ctx.connect_timer, (uint64_t)CONFIG_WIFI_CONNECT_CONNECT_TIMEOUT_SEC * 1000000ULL); return wifi_connect_send_json(req, "{\"ok\":true}"); } /* 函数: wifi_connect_http_status_handler * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static esp_err_t wifi_connect_http_status_handler(httpd_req_t *req) { wifi_connect_status_t status; char error[96] = {0}; xSemaphoreTake(s_ctx.lock, portMAX_DELAY); status = s_ctx.status; snprintf(error, sizeof(error), "%s", s_ctx.last_error); xSemaphoreGive(s_ctx.lock); char escaped[192] = {0}; wifi_connect_json_escape(error, escaped, sizeof(escaped)); char payload[260]; snprintf(payload, sizeof(payload), "{\"status\":\"%s\",\"error\":\"%s\"}", wifi_connect_status_to_string(status), escaped); return wifi_connect_send_json(req, payload); } /* 函数: wifi_connect_http_clear_handler * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static esp_err_t wifi_connect_http_clear_handler(httpd_req_t *req) { esp_err_t err = wifi_connect_clear_config(); if (err != ESP_OK) return wifi_connect_send_json(req, "{\"ok\":false,\"error\":\"clear_failed\"}"); return wifi_connect_send_json(req, "{\"ok\":true}"); } /* 函数: wifi_connect_http_sysinfo_handler * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static esp_err_t wifi_connect_http_sysinfo_handler(httpd_req_t *req) { char payload[128]; snprintf(payload, sizeof(payload), "{\"uptime\":%lld,\"free_heap\":%" PRIu32 "}", esp_timer_get_time() / 1000000ULL, esp_get_free_heap_size() / 1024); return wifi_connect_send_json(req, payload); } /* 函数: wifi_connect_http_index_handler * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static esp_err_t wifi_connect_http_index_handler(httpd_req_t *req) { httpd_resp_set_hdr(req, "Cache-Control", "no-store, no-cache, must-revalidate, max-age=0"); httpd_resp_set_hdr(req, "Pragma", "no-cache"); httpd_resp_set_type(req, "text/html"); return httpd_resp_send(req, s_html_page, HTTPD_RESP_USE_STRLEN); } /* 函数: wifi_connect_get_ap_http_url * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static void wifi_connect_get_ap_http_url(char *out, size_t out_len) { esp_netif_ip_info_t ip_info = {0}; if (s_ctx.ap_netif != NULL && esp_netif_get_ip_info(s_ctx.ap_netif, &ip_info) == ESP_OK) { uint32_t ip = ntohl(ip_info.ip.addr); snprintf(out, out_len, "http://%" PRIu32 ".%" PRIu32 ".%" PRIu32 ".%" PRIu32 "/", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF); return; } snprintf(out, out_len, "http://192.168.4.1/"); } /* 函数: wifi_connect_http_probe_handler * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static esp_err_t wifi_connect_http_probe_handler(httpd_req_t *req) { char location[48] = {0}; wifi_connect_get_ap_http_url(location, sizeof(location)); httpd_resp_set_status(req, "302 Found"); httpd_resp_set_hdr(req, "Location", location); httpd_resp_set_hdr(req, "Cache-Control", "no-store"); return httpd_resp_send(req, NULL, 0); } static esp_err_t wifi_connect_http_start(void); static void wifi_connect_http_stop(void); /* 函数: wifi_connect_http_start * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static esp_err_t wifi_connect_http_start(void) { if (s_ctx.http_server != NULL) return ESP_OK; httpd_config_t config = HTTPD_DEFAULT_CONFIG(); config.max_uri_handlers = 20; config.uri_match_fn = httpd_uri_match_wildcard; config.max_open_sockets = 2; ESP_RETURN_ON_ERROR(httpd_start(&s_ctx.http_server, &config), TAG, "start http server failed"); const httpd_uri_t handlers[] = { {"/", HTTP_GET, wifi_connect_http_index_handler, NULL}, {"/api/scan", HTTP_GET, wifi_connect_http_scan_handler, NULL}, {"/api/connect", HTTP_POST, wifi_connect_http_connect_handler, NULL}, {"/api/status", HTTP_GET, wifi_connect_http_status_handler, NULL}, {"/api/clear", HTTP_POST, wifi_connect_http_clear_handler, NULL}, {"/api/sysinfo", HTTP_GET, wifi_connect_http_sysinfo_handler, NULL}, {"/generate_204", HTTP_GET, wifi_connect_http_probe_handler, NULL}, {"/hotspot-detect.html", HTTP_GET, wifi_connect_http_probe_handler, NULL}, {"/ncsi.txt", HTTP_GET, wifi_connect_http_probe_handler, NULL}, {"/connecttest.txt", HTTP_GET, wifi_connect_http_probe_handler, NULL}, {"/redirect", HTTP_GET, wifi_connect_http_probe_handler, NULL}, {"/canonical.html", HTTP_GET, wifi_connect_http_probe_handler, NULL}, {"/mobile/status.php", HTTP_GET, wifi_connect_http_probe_handler, NULL}, {"/success.txt", HTTP_GET, wifi_connect_http_probe_handler, NULL}, {"/library/test/success.html", HTTP_GET, wifi_connect_http_probe_handler, NULL}, {"/*", HTTP_GET, wifi_connect_http_probe_handler, NULL}, }; for (size_t i = 0; i < sizeof(handlers) / sizeof(handlers[0]); ++i) { if (httpd_register_uri_handler(s_ctx.http_server, &handlers[i]) != ESP_OK) { httpd_stop(s_ctx.http_server); s_ctx.http_server = NULL; return ESP_FAIL; } } return ESP_OK; } /* 函数: wifi_connect_http_stop * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static void wifi_connect_http_stop(void) { if (s_ctx.http_server != NULL) { httpd_stop(s_ctx.http_server); s_ctx.http_server = NULL; } } /* 函数: wifi_connect_build_dns_response * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static size_t wifi_connect_build_dns_response(const uint8_t *req, size_t req_len, uint8_t *resp, size_t resp_max, uint32_t ip_addr) { if (req_len < 12 || resp_max < 64) return 0; const size_t q_offset = 12; size_t q_name_end = q_offset; while (q_name_end < req_len && req[q_name_end] != 0) q_name_end += req[q_name_end] + 1; if (q_name_end + 5 >= req_len) return 0; size_t question_len = (q_name_end + 5) - q_offset; resp[0] = req[0]; resp[1] = req[1]; resp[2] = 0x81; resp[3] = 0x80; resp[4] = 0x00; resp[5] = 0x01; resp[6] = 0x00; resp[7] = 0x01; resp[8] = 0x00; resp[9] = 0x00; resp[10] = 0x00; resp[11] = 0x00; memcpy(&resp[12], &req[q_offset], question_len); size_t pos = 12 + question_len; if (pos + 16 > resp_max) return 0; resp[pos++] = 0xC0; resp[pos++] = 0x0C; resp[pos++] = 0x00; resp[pos++] = 0x01; resp[pos++] = 0x00; resp[pos++] = 0x01; resp[pos++] = 0x00; resp[pos++] = 0x00; resp[pos++] = 0x00; resp[pos++] = 0x3C; resp[pos++] = 0x00; resp[pos++] = 0x04; resp[pos++] = (ip_addr >> 24) & 0xFF; resp[pos++] = (ip_addr >> 16) & 0xFF; resp[pos++] = (ip_addr >> 8) & 0xFF; resp[pos++] = (ip_addr) & 0xFF; return pos; } /* 函数: wifi_connect_dns_task * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static void wifi_connect_dns_task(void *arg) { uint8_t rx_buf[256], tx_buf[512]; struct sockaddr_in addr = {.sin_family = AF_INET, .sin_port = htons(53), .sin_addr.s_addr = htonl(INADDR_ANY)}; s_ctx.dns_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); if (s_ctx.dns_sock < 0) { s_ctx.dns_running = false; vTaskDelete(NULL); return; } if (bind(s_ctx.dns_sock, (struct sockaddr *)&addr, sizeof(addr)) != 0) { close(s_ctx.dns_sock); s_ctx.dns_sock = -1; s_ctx.dns_running = false; vTaskDelete(NULL); return; } esp_netif_ip_info_t ip_info; esp_netif_get_ip_info(s_ctx.ap_netif, &ip_info); uint32_t ip = ntohl(ip_info.ip.addr); while (s_ctx.dns_running) { struct sockaddr_in from_addr; socklen_t from_len = sizeof(from_addr); struct timeval tv = {.tv_sec = 1, .tv_usec = 0}; setsockopt(s_ctx.dns_sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); int len = recvfrom(s_ctx.dns_sock, rx_buf, sizeof(rx_buf), 0, (struct sockaddr *)&from_addr, &from_len); if (len > 0) { size_t resp_len = wifi_connect_build_dns_response(rx_buf, (size_t)len, tx_buf, sizeof(tx_buf), ip); if (resp_len > 0) sendto(s_ctx.dns_sock, tx_buf, resp_len, 0, (struct sockaddr *)&from_addr, from_len); } } close(s_ctx.dns_sock); s_ctx.dns_sock = -1; vTaskDelete(NULL); } /* 函数: wifi_connect_dns_start * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static esp_err_t wifi_connect_dns_start(void) { if (s_ctx.dns_running) return ESP_OK; s_ctx.dns_running = true; if (xTaskCreate(wifi_connect_dns_task, "wifi_dns", 3072, NULL, 4, &s_ctx.dns_task) != pdPASS) { s_ctx.dns_running = false; return ESP_ERR_NO_MEM; } return ESP_OK; } /* 函数: wifi_connect_dns_stop * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static void wifi_connect_dns_stop(void) { if (!s_ctx.dns_running) return; s_ctx.dns_running = false; if (s_ctx.dns_sock >= 0) shutdown(s_ctx.dns_sock, 0); s_ctx.dns_task = NULL; } /* 函数: wifi_connect_connect_timeout_cb * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static void wifi_connect_connect_timeout_cb(void *arg) { xSemaphoreTake(s_ctx.lock, portMAX_DELAY); if (s_ctx.status == WIFI_CONNECT_STATUS_CONNECTING) { if (s_ctx.auto_connecting) { wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_IDLE); wifi_connect_set_error_locked(NULL); s_ctx.auto_connecting = false; wifi_connect_log_state_w("自动重连超时", "回到待机状态"); } else { wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_FAILED); wifi_connect_set_error_locked("连接超时"); wifi_connect_log_state_w("连接路由器超时", "请确认密码和信号"); } s_ctx.sta_connect_requested = false; esp_wifi_disconnect(); } xSemaphoreGive(s_ctx.lock); } /* 函数: wifi_connect_ap_stop_timer_cb * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static void wifi_connect_ap_stop_timer_cb(void *arg) { xSemaphoreTake(s_ctx.lock, portMAX_DELAY); if (s_ctx.status == WIFI_CONNECT_STATUS_CONNECTED && s_ctx.provisioning_active) { wifi_connect_dns_stop(); esp_wifi_set_mode(WIFI_MODE_STA); s_ctx.provisioning_active = false; wifi_connect_log_state_i("自动关闭热点", "已切换至纯 STA 模式,HTTP 服务继续运行在 STA IP 上"); } xSemaphoreGive(s_ctx.lock); } /* 函数: wifi_connect_event_handler * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static void wifi_connect_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { ip_event_got_ip_t *got_ip = (ip_event_got_ip_t *)event_data; xSemaphoreTake(s_ctx.lock, portMAX_DELAY); bool should_save = (s_ctx.status == WIFI_CONNECT_STATUS_CONNECTING || s_ctx.status == WIFI_CONNECT_STATUS_PROVISIONING); s_ctx.sta_connected = true; s_ctx.sta_connect_requested = false; s_ctx.auto_connecting = false; wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_CONNECTED); wifi_connect_set_error_locked(NULL); char ssid[33], password[65]; snprintf(s_ctx.sta_ip, sizeof(s_ctx.sta_ip), IPSTR, IP2STR(&got_ip->ip_info.ip)); snprintf(ssid, sizeof(ssid), "%s", s_ctx.pending_ssid); snprintf(password, sizeof(password), "%s", s_ctx.pending_password); xSemaphoreGive(s_ctx.lock); char success_msg[128] = {0}; snprintf(success_msg, sizeof(success_msg), "已连接 %s,获取 IP=%s", ssid, s_ctx.sta_ip); wifi_connect_log_state_i("联网成功", success_msg); esp_timer_stop(s_ctx.connect_timer); if (should_save) wifi_connect_save_credentials(ssid, password); // 启动定时器,5秒后关闭AP热点和DNS,仅保留STA和HTTP服务 esp_timer_stop(s_ctx.ap_stop_timer); esp_timer_start_once(s_ctx.ap_stop_timer, 5000000ULL); wifi_connect_log_state_i("网络状态", "5秒后将自动关闭配网热点"); return; } if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { wifi_event_sta_disconnected_t *dis = (wifi_event_sta_disconnected_t *)event_data; xSemaphoreTake(s_ctx.lock, portMAX_DELAY); bool connecting = (s_ctx.status == WIFI_CONNECT_STATUS_CONNECTING); bool auto_connecting = s_ctx.auto_connecting; s_ctx.sta_connected = false; if (connecting) { if (auto_connecting) { wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_IDLE); wifi_connect_set_error_locked(NULL); s_ctx.auto_connecting = false; wifi_connect_log_state_w("自动重连中断", "连接丢失"); } else { wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_FAILED); snprintf(s_ctx.last_error, sizeof(s_ctx.last_error), "连接失败,原因=%d", dis->reason); wifi_connect_log_state_w("连接路由器失败", s_ctx.last_error); } s_ctx.sta_connect_requested = false; esp_timer_stop(s_ctx.connect_timer); } xSemaphoreGive(s_ctx.lock); } } /* 函数: wifi_connect_generate_ap_ssid * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static void wifi_connect_generate_ap_ssid(char *out, size_t out_len) { uint8_t mac[6] = {0}; esp_wifi_get_mac(WIFI_IF_STA, mac); snprintf(out, out_len, "ESP32-%02X%02X%02X%02X%02X%02X-192.168.4.1", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } /* 函数: wifi_connect_start_apsta_locked * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static esp_err_t wifi_connect_start_apsta_locked(void) { wifi_config_t ap_cfg = {0}; wifi_connect_generate_ap_ssid(s_ctx.ap_ssid, sizeof(s_ctx.ap_ssid)); snprintf((char *)ap_cfg.ap.ssid, sizeof(ap_cfg.ap.ssid), "%s", s_ctx.ap_ssid); ap_cfg.ap.ssid_len = strlen(s_ctx.ap_ssid); ap_cfg.ap.channel = 1; ap_cfg.ap.authmode = WIFI_AUTH_OPEN; ap_cfg.ap.max_connection = CONFIG_WIFI_CONNECT_AP_MAX_CONNECTIONS; ap_cfg.ap.pmf_cfg.required = false; ESP_RETURN_ON_ERROR(esp_wifi_set_mode(WIFI_MODE_APSTA), TAG, "set mode apsta failed"); ESP_RETURN_ON_ERROR(esp_wifi_set_config(WIFI_IF_AP, &ap_cfg), TAG, "set ap config failed"); if (!s_ctx.wifi_started) { ESP_RETURN_ON_ERROR(esp_wifi_start(), TAG, "wifi start failed"); s_ctx.wifi_started = true; } return ESP_OK; } /* 函数: wifi_connect_start * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ esp_err_t wifi_connect_start(void) { ESP_RETURN_ON_FALSE(s_ctx.initialized, ESP_ERR_INVALID_STATE, TAG, "not initialized"); xSemaphoreTake(s_ctx.lock, portMAX_DELAY); if (s_ctx.provisioning_active) { xSemaphoreGive(s_ctx.lock); return ESP_OK; } if (wifi_connect_start_apsta_locked() != ESP_OK || wifi_connect_http_start() != ESP_OK || wifi_connect_dns_start() != ESP_OK) { wifi_connect_http_stop(); xSemaphoreGive(s_ctx.lock); return ESP_FAIL; } s_ctx.provisioning_active = true; s_ctx.sta_connect_requested = false; s_ctx.auto_connecting = false; wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_PROVISIONING); wifi_connect_set_error_locked(NULL); xSemaphoreGive(s_ctx.lock); char ap_msg[96] = {0}; snprintf(ap_msg, sizeof(ap_msg), "常驻配网热点已开启,SSID=%s,访问 http://192.168.4.1", s_ctx.ap_ssid); wifi_connect_log_state_i("配网已启动", ap_msg); return ESP_OK; } /* 函数: wifi_connect_stop * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ esp_err_t wifi_connect_stop(void) { if (!s_ctx.initialized) { return ESP_ERR_INVALID_STATE; } xSemaphoreTake(s_ctx.lock, portMAX_DELAY); s_ctx.provisioning_active = false; s_ctx.sta_connect_requested = false; s_ctx.auto_connecting = false; esp_timer_stop(s_ctx.connect_timer); wifi_connect_http_stop(); wifi_connect_dns_stop(); if (s_ctx.sta_connected) { esp_wifi_set_mode(WIFI_MODE_STA); wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_CONNECTED); } else if (s_ctx.status != WIFI_CONNECT_STATUS_TIMEOUT) { wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_IDLE); } xSemaphoreGive(s_ctx.lock); wifi_connect_log_state_i("配网已停止", "热点已关闭,设备继续以 STA 模式运行"); return ESP_OK; } /* 函数: wifi_connect_try_auto_connect * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ static esp_err_t wifi_connect_try_auto_connect(void) { wifi_connect_config_t config = {0}; esp_err_t err = wifi_connect_get_config(&config); if (err != ESP_OK || !config.has_config) { wifi_connect_log_state_i("未发现已保存的 Wi-Fi 配置", "设备保持待机"); return ESP_OK; } xSemaphoreTake(s_ctx.lock, portMAX_DELAY); snprintf(s_ctx.pending_ssid, sizeof(s_ctx.pending_ssid), "%s", config.ssid); snprintf(s_ctx.pending_password, sizeof(s_ctx.pending_password), "%s", config.password); wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_CONNECTING); wifi_connect_set_error_locked(NULL); s_ctx.sta_connect_requested = true; s_ctx.auto_connecting = true; xSemaphoreGive(s_ctx.lock); err = wifi_connect_apply_sta_credentials(config.ssid, config.password); if (err != ESP_OK) { xSemaphoreTake(s_ctx.lock, portMAX_DELAY); s_ctx.sta_connect_requested = false; s_ctx.auto_connecting = false; wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_IDLE); wifi_connect_set_error_locked(NULL); xSemaphoreGive(s_ctx.lock); return err; } esp_timer_stop(s_ctx.connect_timer); esp_timer_start_once(s_ctx.connect_timer, (uint64_t)CONFIG_WIFI_CONNECT_CONNECT_TIMEOUT_SEC * 1000000ULL); char msg[96] = {0}; snprintf(msg, sizeof(msg), "尝试连接已保存网络:%s", config.ssid); wifi_connect_log_state_i("自动重连中", msg); return ESP_OK; } /* 函数: wifi_connect_init * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 */ esp_err_t wifi_connect_init(void) { if (s_ctx.initialized) return ESP_OK; esp_err_t err = nvs_flash_init(); if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_ERROR_CHECK(nvs_flash_erase()); err = nvs_flash_init(); } ESP_RETURN_ON_ERROR(err, TAG, "nvs init failed"); ESP_RETURN_ON_ERROR(esp_netif_init(), TAG, "netif init failed"); err = esp_event_loop_create_default(); if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) ESP_RETURN_ON_ERROR(err, TAG, "event loop create failed"); wifi_init_config_t wifi_init_cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_RETURN_ON_ERROR(esp_wifi_init(&wifi_init_cfg), TAG, "wifi init failed"); ESP_RETURN_ON_ERROR(esp_wifi_set_storage(WIFI_STORAGE_RAM), TAG, "wifi storage set failed"); s_ctx.sta_netif = esp_netif_create_default_wifi_sta(); s_ctx.ap_netif = esp_netif_create_default_wifi_ap(); ESP_RETURN_ON_ERROR(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_connect_event_handler, NULL, &s_ctx.wifi_event_instance), TAG, "register wifi handler failed"); ESP_RETURN_ON_ERROR(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_connect_event_handler, NULL, &s_ctx.ip_event_instance), TAG, "register ip handler failed"); ESP_RETURN_ON_ERROR(esp_wifi_set_mode(WIFI_MODE_STA), TAG, "set mode sta failed"); ESP_RETURN_ON_ERROR(esp_wifi_start(), TAG, "wifi start failed"); s_ctx.wifi_started = true; s_ctx.lock = xSemaphoreCreateMutex(); ESP_RETURN_ON_FALSE(s_ctx.lock != NULL, ESP_ERR_NO_MEM, TAG, "create lock failed"); esp_timer_create_args_t connect_timer_args = { .callback = wifi_connect_connect_timeout_cb, .name = "wifi_conn_to", }; ESP_RETURN_ON_ERROR(esp_timer_create(&connect_timer_args, &s_ctx.connect_timer), TAG, "connect timer create failed"); esp_timer_create_args_t ap_stop_timer_args = { .callback = wifi_connect_ap_stop_timer_cb, .name = "wifi_ap_stop", }; ESP_RETURN_ON_ERROR(esp_timer_create(&ap_stop_timer_args, &s_ctx.ap_stop_timer), TAG, "ap stop timer create failed"); s_ctx.initialized = true; wifi_connect_log_state_i("配网模式", "常驻配网(上电自动开启且不会自动关闭)"); wifi_connect_try_auto_connect(); return wifi_connect_start(); }