Files
BotanicalBuddy/components/wifi-connect/wifi-connect.c
Wang Beihong 7261022611 feat: 初始化 BotanicalBuddy 项目并集成 Wi-Fi 配网组件
- 新增 wifi-connect 组件(AP 配网、DNS 劫持、Captive Portal)

- 支持长按按键进入配网,网页配置路由器 SSID/密码

- 增加清除已保存 Wi-Fi 参数能力(API + 页面按钮)

- 增加中文状态日志与中文注释,优化配网交互

- 补充组件文档:README、USER_GUIDE、QUICK_POSTER、BLOG
2026-03-05 12:41:15 +08:00

1180 lines
39 KiB
C
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.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include <unistd.h>
#include <errno.h>
#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_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";
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);
}
}
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);
}
}
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 button_task;
TaskHandle_t dns_task;
SemaphoreHandle_t lock;
esp_timer_handle_t connect_timer;
esp_timer_handle_t idle_timer;
esp_timer_handle_t ap_stop_timer;
int dns_sock;
bool dns_running;
char ap_ssid[32];
char pending_ssid[33];
char pending_password[65];
char last_error[96];
} wifi_connect_ctx_t;
static wifi_connect_ctx_t s_ctx = {
.status = WIFI_CONNECT_STATUS_IDLE,
.dns_sock = -1,
};
// 配网页面(内嵌 HTML + JS
static const char *s_html_page =
"<!doctype html><html><head><meta charset='utf-8'/>"
"<meta name='viewport' content='width=device-width, initial-scale=1'/>"
"<title>ESP32 Wi-Fi Setup</title>"
"<style>"
"body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#f3f4f6;margin:0;padding:16px;color:#111827;}"
".card{max-width:420px;margin:0 auto;background:#fff;border-radius:14px;padding:18px;box-shadow:0 8px 20px rgba(0,0,0,.08);}"
"h1{font-size:20px;margin:0 0 10px;}"
"p{margin:0 0 12px;color:#6b7280;}"
"button{border:none;border-radius:10px;padding:10px 14px;font-size:14px;cursor:pointer;}"
".btn{background:#2563eb;color:#fff;}"
".btn2{background:#e5e7eb;color:#111827;margin-left:8px;}"
"input,select{width:100%;padding:10px;border:1px solid #d1d5db;border-radius:10px;margin-top:8px;font-size:14px;box-sizing:border-box;}"
"label{font-size:13px;color:#374151;margin-top:10px;display:block;}"
"#status{margin-top:12px;font-size:13px;color:#1f2937;min-height:18px;}"
"#error{margin-top:6px;font-size:13px;color:#dc2626;min-height:18px;}"
"</style></head><body><div class='card'>"
"<h1>连接 Wi-Fi</h1><p>请选择网络并输入密码。</p>"
"<div><button class='btn' onclick='loadScan()'>扫描网络</button><button class='btn2' onclick='pollStatus()'>刷新状态</button><button class='btn2' onclick='clearSaved()'>清除已保存</button></div>"
"<label>网络</label><select id='ssid'></select>"
"<label>密码</label><input id='pwd' type='password' placeholder='请输入密码'/>"
"<div style='margin-top:12px'><button class='btn' onclick='connectWifi()'>连接</button></div>"
"<div id='status'></div><div id='error'></div>"
"</div><script>"
"const ssidSel=document.getElementById('ssid');"
"const pwdEl=document.getElementById('pwd');"
"const statusEl=document.getElementById('status');"
"const errorEl=document.getElementById('error');"
"function setStatus(t){statusEl.textContent=t||'';}"
"function setError(t){errorEl.textContent=t||'';}"
"function setBusy(v){document.querySelectorAll('button').forEach(b=>b.disabled=!!v);}"
"function statusText(s){const map={idle:'待机',provisioning:'配网中',connecting:'连接中',connected:'已连接',failed:'连接失败',timeout:'配网超时'};return map[s]||s||'未知';}"
"async function loadScan(){setError('');setStatus('正在扫描...');"
"try{const r=await fetch('/api/scan');const d=await r.json();ssidSel.innerHTML='';"
"(d.networks||[]).forEach(n=>{const o=document.createElement('option');o.value=n.ssid;o.textContent=`${n.ssid} (${n.rssi} dBm)`;ssidSel.appendChild(o);});"
"if(!ssidSel.options.length){setError('未找到网络');}setStatus('扫描完成');}catch(e){setError('扫描失败');setStatus('');}}"
"async function connectWifi(){setError('');const ssid=ssidSel.value;const password=pwdEl.value;"
"if(!ssid){setError('请先选择网络');return;}setStatus('正在连接...');"
"setBusy(true);"
"try{const r=await fetch('/api/connect',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ssid,password})});"
"const d=await r.json();if(!r.ok||!d.ok){setError('提交失败,请重试');setStatus('');return;}"
"setStatus('连接请求已发送,请等待结果...');await pollStatus();}"
"catch(e){setError('提交失败');setStatus('');}finally{setBusy(false);}}"
"async function clearSaved(){setError('');setStatus('正在清除...');setBusy(true);"
"try{const r=await fetch('/api/clear',{method:'POST'});const d=await r.json();"
"if(!r.ok||!d.ok){setError('清除失败');setStatus('');return;}"
"pwdEl.value='';setStatus('已清除保存的 Wi-Fi 配置');await pollStatus();await loadScan();}"
"catch(e){setError('清除失败');setStatus('');}finally{setBusy(false);}}"
"async function pollStatus(){"
"try{const r=await fetch('/api/status');const d=await r.json();setStatus('状态: '+statusText(d.status));setError(d.error||'');"
"if(d.status==='connected'){setStatus('连接成功,配网服务即将关闭');setError('');}}catch(e){setError('状态获取失败');}}"
"loadScan();setInterval(pollStatus,2500);"
"</script></body></html>";
static void wifi_connect_set_status_locked(wifi_connect_status_t status)
{
s_ctx.status = status;
}
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);
}
static void wifi_connect_refresh_idle_timeout(void)
{
if (s_ctx.idle_timer == NULL) {
return;
}
esp_timer_stop(s_ctx.idle_timer);
esp_timer_start_once(s_ctx.idle_timer, (uint64_t)CONFIG_WIFI_CONNECT_IDLE_TIMEOUT_SEC * 1000000ULL);
}
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;
}
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;
}
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;
}
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_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;
}
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);
}
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';
}
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";
}
}
static esp_err_t wifi_connect_http_scan_handler(httpd_req_t *req)
{
wifi_connect_refresh_idle_timeout();
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;
}
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;
}
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) {
char dis_msg[96] = {0};
snprintf(dis_msg, sizeof(dis_msg), "切换网络前断开 STA 失败,错误=%s", esp_err_to_name(dis_err));
wifi_connect_log_state_w("预断开当前连接失败", dis_msg);
}
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;
}
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);
char msg[96] = {0};
snprintf(msg, sizeof(msg), "自动重连启动失败,错误=%s", esp_err_to_name(err));
wifi_connect_log_state_w("自动重连失败", msg);
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;
}
static esp_err_t wifi_connect_http_connect_handler(httpd_req_t *req)
{
wifi_connect_refresh_idle_timeout();
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);
char err_msg[96] = {0};
snprintf(err_msg, sizeof(err_msg), "提交连接失败,错误=%s", esp_err_to_name(err));
wifi_connect_log_state_w("连接启动失败", err_msg);
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}");
}
static esp_err_t wifi_connect_http_status_handler(httpd_req_t *req)
{
wifi_connect_refresh_idle_timeout();
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);
}
static esp_err_t wifi_connect_http_clear_handler(httpd_req_t *req)
{
wifi_connect_refresh_idle_timeout();
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}");
}
static esp_err_t wifi_connect_http_index_handler(httpd_req_t *req)
{
wifi_connect_refresh_idle_timeout();
httpd_resp_set_type(req, "text/html");
return httpd_resp_send(req, s_html_page, HTTPD_RESP_USE_STRLEN);
}
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/");
}
static esp_err_t wifi_connect_http_probe_handler(httpd_req_t *req)
{
wifi_connect_refresh_idle_timeout();
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)
{
esp_err_t ret = ESP_OK;
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;
ESP_RETURN_ON_ERROR(httpd_start(&s_ctx.http_server, &config), TAG, "start http server failed");
const httpd_uri_t index_uri = {
.uri = "/",
.method = HTTP_GET,
.handler = wifi_connect_http_index_handler,
};
const httpd_uri_t scan_uri = {
.uri = "/api/scan",
.method = HTTP_GET,
.handler = wifi_connect_http_scan_handler,
};
const httpd_uri_t connect_uri = {
.uri = "/api/connect",
.method = HTTP_POST,
.handler = wifi_connect_http_connect_handler,
};
const httpd_uri_t status_uri = {
.uri = "/api/status",
.method = HTTP_GET,
.handler = wifi_connect_http_status_handler,
};
const httpd_uri_t clear_uri = {
.uri = "/api/clear",
.method = HTTP_POST,
.handler = wifi_connect_http_clear_handler,
};
const httpd_uri_t probe_1 = {
.uri = "/generate_204",
.method = HTTP_GET,
.handler = wifi_connect_http_probe_handler,
};
const httpd_uri_t probe_2 = {
.uri = "/hotspot-detect.html",
.method = HTTP_GET,
.handler = wifi_connect_http_probe_handler,
};
const httpd_uri_t probe_3 = {
.uri = "/ncsi.txt",
.method = HTTP_GET,
.handler = wifi_connect_http_probe_handler,
};
const httpd_uri_t probe_4 = {
.uri = "/connecttest.txt",
.method = HTTP_GET,
.handler = wifi_connect_http_probe_handler,
};
const httpd_uri_t probe_5 = {
.uri = "/redirect",
.method = HTTP_GET,
.handler = wifi_connect_http_probe_handler,
};
const httpd_uri_t probe_6 = {
.uri = "/canonical.html",
.method = HTTP_GET,
.handler = wifi_connect_http_probe_handler,
};
const httpd_uri_t probe_7 = {
.uri = "/mobile/status.php",
.method = HTTP_GET,
.handler = wifi_connect_http_probe_handler,
};
const httpd_uri_t probe_8 = {
.uri = "/success.txt",
.method = HTTP_GET,
.handler = wifi_connect_http_probe_handler,
};
const httpd_uri_t probe_9 = {
.uri = "/library/test/success.html",
.method = HTTP_GET,
.handler = wifi_connect_http_probe_handler,
};
const httpd_uri_t wildcard = {
.uri = "/*",
.method = HTTP_GET,
.handler = wifi_connect_http_probe_handler,
};
ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &index_uri), fail, TAG, "register / failed");
ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &scan_uri), fail, TAG, "register /api/scan failed");
ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &connect_uri), fail, TAG, "register /api/connect failed");
ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &status_uri), fail, TAG, "register /api/status failed");
ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &clear_uri), fail, TAG, "register /api/clear failed");
ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &probe_1), fail, TAG, "register /generate_204 failed");
ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &probe_2), fail, TAG, "register /hotspot-detect.html failed");
ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &probe_3), fail, TAG, "register /ncsi.txt failed");
ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &probe_4), fail, TAG, "register /connecttest.txt failed");
ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &probe_5), fail, TAG, "register /redirect failed");
ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &probe_6), fail, TAG, "register /canonical.html failed");
ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &probe_7), fail, TAG, "register /mobile/status.php failed");
ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &probe_8), fail, TAG, "register /success.txt failed");
ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &probe_9), fail, TAG, "register /library/test/success.html failed");
ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &wildcard), fail, TAG, "register wildcard failed");
return ESP_OK;
fail:
httpd_stop(s_ctx.http_server);
s_ctx.http_server = NULL;
return ret;
}
static void wifi_connect_http_stop(void)
{
if (s_ctx.http_server != NULL) {
httpd_stop(s_ctx.http_server);
s_ctx.http_server = NULL;
}
}
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;
}
static void wifi_connect_dns_task(void *arg)
{
(void)arg;
uint8_t rx_buf[256];
uint8_t 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) {
wifi_connect_log_state_e("DNS 服务启动失败", "创建 socket 失败");
s_ctx.dns_running = false;
vTaskDelete(NULL);
return;
}
if (bind(s_ctx.dns_sock, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
char err_msg[96] = {0};
snprintf(err_msg, sizeof(err_msg), "绑定 53 端口失败errno=%d", errno);
wifi_connect_log_state_e("DNS 服务启动失败", err_msg);
close(s_ctx.dns_sock);
s_ctx.dns_sock = -1;
s_ctx.dns_running = false;
vTaskDelete(NULL);
return;
}
wifi_connect_log_state_i("DNS 劫持服务已启动", "手机访问任意域名将跳转配网页面");
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) {
continue;
}
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);
}
static esp_err_t wifi_connect_dns_start(void)
{
if (s_ctx.dns_running) {
return ESP_OK;
}
s_ctx.dns_running = true;
BaseType_t ok = xTaskCreate(wifi_connect_dns_task, "wifi_dns", 4096, NULL, 4, &s_ctx.dns_task);
if (ok != pdPASS) {
s_ctx.dns_running = false;
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
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;
}
static void wifi_connect_connect_timeout_cb(void *arg)
{
(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);
}
static void wifi_connect_ap_stop_timer_cb(void *arg)
{
(void)arg;
wifi_connect_stop();
}
static void wifi_connect_idle_timeout_cb(void *arg)
{
(void)arg;
xSemaphoreTake(s_ctx.lock, portMAX_DELAY);
bool should_stop = s_ctx.provisioning_active;
if (should_stop) {
wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_TIMEOUT);
wifi_connect_set_error_locked("配网空闲超时");
wifi_connect_log_state_w("配网超时", "长时间无操作,正在关闭配网热点");
}
xSemaphoreGive(s_ctx.lock);
if (should_stop) {
wifi_connect_stop();
}
}
static void wifi_connect_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
(void)arg;
// STA 获取到 IP判定联网成功并根据配置决定是否关闭配网热点
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);
bool provisioning_active = s_ctx.provisioning_active;
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];
char password[65];
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=" IPSTR, ssid, IP2STR(&got_ip->ip_info.ip));
wifi_connect_log_state_i("联网成功", success_msg);
esp_timer_stop(s_ctx.connect_timer);
if (should_save) {
esp_err_t err = wifi_connect_save_credentials(ssid, password);
if (err != ESP_OK) {
char save_msg[96] = {0};
snprintf(save_msg, sizeof(save_msg), "保存凭据失败,错误=%s", esp_err_to_name(err));
wifi_connect_log_state_w("保存 Wi-Fi 信息失败", save_msg);
}
}
if (provisioning_active) {
if (CONFIG_WIFI_CONNECT_AP_GRACEFUL_STOP_SEC == 0) {
wifi_connect_stop();
} else {
esp_timer_stop(s_ctx.ap_stop_timer);
esp_timer_start_once(s_ctx.ap_stop_timer, (uint64_t)CONFIG_WIFI_CONNECT_AP_GRACEFUL_STOP_SEC * 1000000ULL);
}
}
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;
char dis_msg[96] = {0};
snprintf(dis_msg, sizeof(dis_msg), "自动重连断开,原因=%d", dis->reason);
wifi_connect_log_state_w("自动重连中断", dis_msg);
} else {
wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_FAILED);
snprintf(s_ctx.last_error, sizeof(s_ctx.last_error), "连接失败,原因=%d", dis->reason);
char dis_msg[96] = {0};
snprintf(dis_msg, sizeof(dis_msg), "连接失败,原因=%d", dis->reason);
wifi_connect_log_state_w("连接路由器失败", dis_msg);
}
s_ctx.sta_connect_requested = false;
esp_timer_stop(s_ctx.connect_timer);
}
xSemaphoreGive(s_ctx.lock);
}
}
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", mac[3], mac[4], mac[5]);
}
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;
}
static void wifi_connect_button_task(void *arg)
{
(void)arg;
const TickType_t interval = pdMS_TO_TICKS(CONFIG_WIFI_CONNECT_DEBOUNCE_MS);
int stable_level = gpio_get_level(CONFIG_WIFI_CONNECT_BUTTON_GPIO);
int last_level = stable_level;
TickType_t changed_at = xTaskGetTickCount();
TickType_t low_since = 0;
bool triggered = false;
while (true) {
vTaskDelay(interval);
int level = gpio_get_level(CONFIG_WIFI_CONNECT_BUTTON_GPIO);
TickType_t now = xTaskGetTickCount();
if (level != last_level) {
last_level = level;
changed_at = now;
}
if ((now - changed_at) >= interval && stable_level != level) {
stable_level = level;
if (stable_level == CONFIG_WIFI_CONNECT_BUTTON_ACTIVE_LEVEL) {
low_since = now;
triggered = false;
} else {
low_since = 0;
triggered = false;
}
}
if (stable_level == CONFIG_WIFI_CONNECT_BUTTON_ACTIVE_LEVEL && low_since != 0 && !triggered) {
TickType_t held = now - low_since;
if (held >= pdMS_TO_TICKS(CONFIG_WIFI_CONNECT_LONG_PRESS_MS)) {
triggered = true;
wifi_connect_log_state_i("检测到按键长按", "开始进入配网模式");
wifi_connect_start();
}
}
}
}
esp_err_t wifi_connect_start(void)
{
ESP_RETURN_ON_FALSE(s_ctx.initialized, ESP_ERR_INVALID_STATE, TAG, "not initialized");
// 启动 AP+STA、HTTP 配网页面和 DNS 劫持,进入可配网状态
xSemaphoreTake(s_ctx.lock, portMAX_DELAY);
if (s_ctx.provisioning_active) {
xSemaphoreGive(s_ctx.lock);
return ESP_OK;
}
esp_err_t err = wifi_connect_start_apsta_locked();
if (err != ESP_OK) {
xSemaphoreGive(s_ctx.lock);
return err;
}
err = wifi_connect_http_start();
if (err != ESP_OK) {
xSemaphoreGive(s_ctx.lock);
return err;
}
err = wifi_connect_dns_start();
if (err != ESP_OK) {
wifi_connect_http_stop();
xSemaphoreGive(s_ctx.lock);
return err;
}
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);
wifi_connect_refresh_idle_timeout();
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;
}
esp_err_t wifi_connect_stop(void)
{
if (!s_ctx.initialized) {
return ESP_ERR_INVALID_STATE;
}
// 停止配网相关服务,若已联网则回到 STA 模式
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);
esp_timer_stop(s_ctx.idle_timer);
esp_timer_stop(s_ctx.ap_stop_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;
}
esp_err_t wifi_connect_init(void)
{
if (s_ctx.initialized) {
return ESP_OK;
}
// 一次性初始化 NVS/Wi-Fi/事件/按键任务,并尝试自动连接已保存网络
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;
gpio_config_t io = {
.pin_bit_mask = (1ULL << CONFIG_WIFI_CONNECT_BUTTON_GPIO),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_RETURN_ON_ERROR(gpio_config(&io), TAG, "button gpio config failed");
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_timer_create_args_t idle_timer_args = {
.callback = wifi_connect_idle_timeout_cb,
.name = "wifi_idle_to",
};
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(&connect_timer_args, &s_ctx.connect_timer), TAG, "connect timer create failed");
ESP_RETURN_ON_ERROR(esp_timer_create(&idle_timer_args, &s_ctx.idle_timer), TAG, "idle timer create failed");
ESP_RETURN_ON_ERROR(esp_timer_create(&ap_stop_timer_args, &s_ctx.ap_stop_timer), TAG, "ap stop timer create failed");
BaseType_t ok = xTaskCreate(wifi_connect_button_task, "wifi_btn", 3072, NULL, 4, &s_ctx.button_task);
ESP_RETURN_ON_FALSE(ok == pdPASS, ESP_ERR_NO_MEM, TAG, "button task create failed");
s_ctx.initialized = true;
err = wifi_connect_try_auto_connect();
if (err != ESP_OK) {
char skip_msg[96] = {0};
snprintf(skip_msg, sizeof(skip_msg), "自动重连已跳过,错误=%s", esp_err_to_name(err));
wifi_connect_log_state_w("初始化后自动重连未执行", skip_msg);
}
wifi_connect_log_state_i("wifi-connect 初始化完成", "长按按键可进入配网");
return ESP_OK;
}