1029 lines
38 KiB
C
1029 lines
38 KiB
C
#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_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";
|
||
|
||
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 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 =
|
||
"<!doctype html><html><head><meta charset='utf-8'/>"
|
||
"<meta name='viewport' content='width=device-width, initial-scale=1'/>"
|
||
"<title>设备联网控制台</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:20px;box-shadow:0 8px 20px rgba(0,0,0,.08);}"
|
||
"h1{font-size:22px;margin:0 0 16px;color:#111827;}"
|
||
"p{margin:0 0 12px;color:#6b7280;}"
|
||
"button{border:none;border-radius:10px;padding:10px 14px;font-size:14px;cursor:pointer;transition:background .2s;}"
|
||
"button:active{opacity:0.8;}"
|
||
".btn{background:#2563eb;color:#fff;}"
|
||
".btn2{background:#e5e7eb;color:#111827;margin-left:8px;}"
|
||
".btn3{background:#ef4444;color:#fff;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;}"
|
||
".hidden{display:none !important;}"
|
||
".stat-box{background:#f9fafb;padding:14px;border-radius:10px;margin-top:10px;display:flex;justify-content:space-between;border:1px solid #e5e7eb;font-size:15px;}"
|
||
".stat-box span{color:#4b5563;}"
|
||
".stat-box strong{color:#111827;}"
|
||
"#status{margin-top:12px;font-size:13px;color:#1f2937;min-height:18px;text-align:center;}"
|
||
"#error{margin-top:6px;font-size:13px;color:#dc2626;min-height:18px;text-align:center;}"
|
||
"</style></head><body><div class='card'>"
|
||
"<h1 id='main-title'>设备配网</h1>"
|
||
"<div id='wifi-view'>"
|
||
"<p>请选择网络并输入密码连接路由器。</p>"
|
||
"<div style='display:flex;'><button class='btn' style='flex:1' onclick='loadScan()'>扫描网络</button><button class='btn2' style='flex:1' onclick='pollStatus()'>刷新状态</button></div>"
|
||
"<label>网络名称</label><select id='ssid'></select>"
|
||
"<label>Wi-Fi 密码</label><input id='pwd' type='password' placeholder='请输入密码'/>"
|
||
"<div style='margin-top:16px'><button class='btn' style='width:100%' onclick='connectWifi()'>开始连接</button></div>"
|
||
"</div>"
|
||
"<div id='dash-view' class='hidden'>"
|
||
"<div style='text-align:center;margin-bottom:20px;'><svg style='width:48px;height:48px;color:#10b981;margin:0 auto;' fill='none' stroke='currentColor' viewBox='0 0 24 24'><path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z'></path></svg><h2 style='font-size:18px;margin:8px 0 0;color:#10b981;'>已成功连接网络</h2></div>"
|
||
"<div class='stat-box'><span>局域网 IP</span><strong id='dash-ip'>-</strong></div>"
|
||
"<div class='stat-box'><span>连续运行</span><strong id='dash-up'>-</strong></div>"
|
||
"<div class='stat-box'><span>可用内存</span><strong id='dash-mem'>-</strong></div>"
|
||
"<div style='margin-top:20px;display:flex;'><button class='btn' style='flex:1' onclick='fetchInfo()'>获取状态</button><button class='btn3' style='flex:1' onclick='clearSaved()'>清除网络</button></div>"
|
||
"</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');"
|
||
"let isConnected=false;"
|
||
"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(){if(isConnected)return;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(){if(!confirm('确定要清除记录吗?这会断开当前网络。')){return;}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('配置已清除,设备即将进入重新配网...');"
|
||
"setTimeout(()=>{location.reload();},2000);}catch(e){setError('清除超时,可能网络已断开');setStatus('');}finally{setBusy(false);}}"
|
||
"async function fetchInfo(){"
|
||
"try{const r=await fetch('/api/sysinfo');const d=await r.json();"
|
||
"let sec=d.uptime;let m=Math.floor(sec/60);let h=Math.floor(m/60);sec=sec%60;m=m%60;"
|
||
"let timeStr='';if(h>0)timeStr+=h+'小时';if(m>0)timeStr+=m+'分';timeStr+=sec+'秒';"
|
||
"document.getElementById('dash-up').textContent=timeStr;document.getElementById('dash-mem').textContent=d.free_heap+' KB';}catch(e){}}"
|
||
"async function pollStatus(){"
|
||
"try{const r=await fetch('/api/status');const d=await r.json();"
|
||
"if(d.status==='connected'){"
|
||
" if(!isConnected){isConnected=true;"
|
||
" if(location.hostname==='192.168.4.1' && d.ip && !window.redirecting){"
|
||
" setStatus('配置成功!设备IP: '+d.ip+'。3秒后自动跳转...');window.redirecting=true;setTimeout(()=>{window.location.href='http://'+d.ip;}, 3000);"
|
||
" }else{"
|
||
" document.getElementById('wifi-view').classList.add('hidden');"
|
||
" document.getElementById('dash-view').classList.remove('hidden');"
|
||
" document.getElementById('main-title').textContent='智能设备配网 - 控制台';"
|
||
" document.getElementById('dash-ip').textContent=d.ip||location.hostname;"
|
||
" setStatus('');setError('');fetchInfo();"
|
||
" }"
|
||
" }"
|
||
"}else{"
|
||
" isConnected=false;document.getElementById('dash-view').classList.add('hidden');document.getElementById('wifi-view').classList.remove('hidden');"
|
||
" document.getElementById('main-title').textContent='设备配网';"
|
||
" setStatus('当前状态: '+statusText(d.status));setError(d.error||'');"
|
||
"} }catch(e){if(!isConnected)setError('无法获取设备状态,检查连接');}}"
|
||
"pollStatus();"
|
||
"setInterval(()=>{if(!isConnected)pollStatus();},2500);"
|
||
"setInterval(()=>{if(isConnected)fetchInfo();},3000);"
|
||
"</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 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_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)
|
||
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;
|
||
}
|
||
|
||
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}");
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
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}");
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
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)
|
||
{
|
||
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);
|
||
|
||
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;
|
||
}
|
||
|
||
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)
|
||
{
|
||
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);
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
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)
|
||
{
|
||
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)
|
||
{
|
||
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);
|
||
}
|
||
|
||
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);
|
||
}
|
||
}
|
||
|
||
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]);
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
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();
|
||
}
|