628 lines
22 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 "wifi_configuration_ap.h"
#include <cstdio>
#include <memory>
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include <esp_err.h>
#include <esp_event.h>
#include <esp_wifi.h>
#include <esp_log.h>
#include <esp_mac.h>
#include <esp_netif.h>
#include <lwip/ip_addr.h>
#include <nvs.h>
#include <nvs_flash.h>
#include <cJSON.h>
#include <esp_smartconfig.h>
#include "ssid_manager.h"
#define TAG "WifiConfigurationAp"
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
extern const char index_html_start[] asm("_binary_wifi_configuration_html_start");
extern const char done_html_start[] asm("_binary_wifi_configuration_done_html_start");
WifiConfigurationAp& WifiConfigurationAp::GetInstance() {
static WifiConfigurationAp instance;
return instance;
}
WifiConfigurationAp::WifiConfigurationAp()
{
event_group_ = xEventGroupCreate();
language_ = "zh-CN";
}
WifiConfigurationAp::~WifiConfigurationAp()
{
if (scan_timer_) {
esp_timer_stop(scan_timer_);
esp_timer_delete(scan_timer_);
}
if (event_group_) {
vEventGroupDelete(event_group_);
}
// Unregister event handlers if they were registered
if (instance_any_id_) {
esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id_);
}
if (instance_got_ip_) {
esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip_);
}
}
void WifiConfigurationAp::SetLanguage(const std::string &&language)
{
language_ = language;
}
void WifiConfigurationAp::SetSsidPrefix(const std::string &&ssid_prefix)
{
ssid_prefix_ = ssid_prefix;
}
void WifiConfigurationAp::Start()
{
// Register event handlers
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&WifiConfigurationAp::WifiEventHandler,
this,
&instance_any_id_));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&WifiConfigurationAp::IpEventHandler,
this,
&instance_got_ip_));
StartAccessPoint();
StartWebServer();
// Start scan immediately
esp_wifi_scan_start(nullptr, false);
// Setup periodic WiFi scan timer
esp_timer_create_args_t timer_args = {
.callback = [](void* arg) {
auto* self = static_cast<WifiConfigurationAp*>(arg);
if (!self->is_connecting_) {
esp_wifi_scan_start(nullptr, false);
}
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "wifi_scan_timer",
.skip_unhandled_events = true
};
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &scan_timer_));
// Start scanning every 10 seconds
ESP_ERROR_CHECK(esp_timer_start_periodic(scan_timer_, 10000000));
}
std::string WifiConfigurationAp::GetSsid()
{
// Get MAC and use it to generate a unique SSID
uint8_t mac[6];
ESP_ERROR_CHECK(esp_read_mac(mac, ESP_MAC_WIFI_SOFTAP));
char ssid[32];
snprintf(ssid, sizeof(ssid), "%s-%02X%02X", ssid_prefix_.c_str(), mac[4], mac[5]);
return std::string(ssid);
}
std::string WifiConfigurationAp::GetWebServerUrl()
{
// http://192.168.4.1
return "http://192.168.4.1";
}
void WifiConfigurationAp::StartAccessPoint()
{
// Get the SSID
std::string ssid = GetSsid();
// Initialize the TCP/IP stack
ESP_ERROR_CHECK(esp_netif_init());
// Create the default event loop
ap_netif_ = esp_netif_create_default_wifi_ap();
// Set the router IP address to 192.168.4.1
esp_netif_ip_info_t ip_info;
IP4_ADDR(&ip_info.ip, 192, 168, 4, 1);
IP4_ADDR(&ip_info.gw, 192, 168, 4, 1);
IP4_ADDR(&ip_info.netmask, 255, 255, 255, 0);
esp_netif_dhcps_stop(ap_netif_);
esp_netif_set_ip_info(ap_netif_, &ip_info);
esp_netif_dhcps_start(ap_netif_);
// Start the DNS server
dns_server_.Start(ip_info.gw);
// Initialize the WiFi stack in Access Point mode
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// Set the WiFi configuration
wifi_config_t wifi_config = {};
strcpy((char *)wifi_config.ap.ssid, ssid.c_str());
wifi_config.ap.ssid_len = ssid.length();
wifi_config.ap.max_connection = 4;
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
// Start the WiFi Access Point
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
ESP_ERROR_CHECK(esp_wifi_start());
#ifdef CONFIG_SOC_WIFI_SUPPORT_5G
// Temporarily use only 2.4G Wi-Fi.
ESP_ERROR_CHECK(esp_wifi_set_band_mode(WIFI_BAND_MODE_2G_ONLY));
#endif
ESP_LOGI(TAG, "Access Point started with SSID %s", ssid.c_str());
}
void WifiConfigurationAp::StartWebServer()
{
// Start the web server
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.max_uri_handlers = 20;
config.uri_match_fn = httpd_uri_match_wildcard;
ESP_ERROR_CHECK(httpd_start(&server_, &config));
// Register the index.html file
httpd_uri_t index_html = {
.uri = "/",
.method = HTTP_GET,
.handler = [](httpd_req_t *req) -> esp_err_t {
httpd_resp_send(req, index_html_start, strlen(index_html_start));
return ESP_OK;
},
.user_ctx = NULL
};
ESP_ERROR_CHECK(httpd_register_uri_handler(server_, &index_html));
// Register the /saved/list URI
httpd_uri_t saved_list = {
.uri = "/saved/list",
.method = HTTP_GET,
.handler = [](httpd_req_t *req) -> esp_err_t {
auto ssid_list = SsidManager::GetInstance().GetSsidList();
std::string json_str = "[";
for (const auto& ssid : ssid_list) {
json_str += "\"" + ssid.ssid + "\",";
}
if (json_str.length() > 1) {
json_str.pop_back(); // Remove the last comma
}
json_str += "]";
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, json_str.c_str(), HTTPD_RESP_USE_STRLEN);
return ESP_OK;
},
.user_ctx = NULL
};
ESP_ERROR_CHECK(httpd_register_uri_handler(server_, &saved_list));
// Register the /saved/set_default URI
httpd_uri_t saved_set_default = {
.uri = "/saved/set_default",
.method = HTTP_GET,
.handler = [](httpd_req_t *req) -> esp_err_t {
std::string uri = req->uri;
auto pos = uri.find("?index=");
if (pos != std::string::npos) {
int index = std::stoi(uri.substr(pos + 7));
ESP_LOGI(TAG, "Set default item %d", index);
SsidManager::GetInstance().SetDefaultSsid(index);
}
// send {}
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, "{}", HTTPD_RESP_USE_STRLEN);
return ESP_OK;
},
.user_ctx = NULL
};
ESP_ERROR_CHECK(httpd_register_uri_handler(server_, &saved_set_default));
// Register the /saved/delete URI
httpd_uri_t saved_delete = {
.uri = "/saved/delete",
.method = HTTP_GET,
.handler = [](httpd_req_t *req) -> esp_err_t {
std::string uri = req->uri;
auto pos = uri.find("?index=");
if (pos != std::string::npos) {
int index = std::stoi(uri.substr(pos + 7));
ESP_LOGI(TAG, "Delete saved list item %d", index);
SsidManager::GetInstance().RemoveSsid(index);
}
// send {}
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, "{}", HTTPD_RESP_USE_STRLEN);
return ESP_OK;
},
.user_ctx = NULL
};
ESP_ERROR_CHECK(httpd_register_uri_handler(server_, &saved_delete));
// Register the /scan URI
httpd_uri_t scan = {
.uri = "/scan",
.method = HTTP_GET,
.handler = [](httpd_req_t *req) -> esp_err_t {
uint16_t ap_num = 0;
esp_wifi_scan_get_ap_num(&ap_num);
if (ap_num == 0) {
ESP_LOGI(TAG, "No APs found, scanning...");
esp_wifi_scan_start(nullptr, true);
esp_wifi_scan_get_ap_num(&ap_num);
}
auto ap_records = std::make_unique<wifi_ap_record_t[]>(ap_num);
if (!ap_records) {
return ESP_FAIL;
}
esp_wifi_scan_get_ap_records(&ap_num, ap_records.get());
// Send the scan results as JSON
httpd_resp_set_type(req, "application/json");
httpd_resp_sendstr_chunk(req, "[");
for (int i = 0; i < ap_num; i++) {
ESP_LOGI(TAG, "SSID: %s, RSSI: %d, Authmode: %d",
(char *)ap_records[i].ssid, ap_records[i].rssi, ap_records[i].authmode);
char buf[128];
snprintf(buf, sizeof(buf), "{\"ssid\":\"%s\",\"rssi\":%d,\"authmode\":%d}",
(char *)ap_records[i].ssid, ap_records[i].rssi, ap_records[i].authmode);
httpd_resp_sendstr_chunk(req, buf);
if (i < ap_num - 1) {
httpd_resp_sendstr_chunk(req, ",");
}
}
httpd_resp_sendstr_chunk(req, "]");
httpd_resp_sendstr_chunk(req, NULL);
return ESP_OK;
},
.user_ctx = NULL
};
ESP_ERROR_CHECK(httpd_register_uri_handler(server_, &scan));
// Register the form submission
httpd_uri_t form_submit = {
.uri = "/submit",
.method = HTTP_POST,
.handler = [](httpd_req_t *req) -> esp_err_t {
char *buf;
size_t buf_len = req->content_len;
if (buf_len > 1024) { // 限制最大请求体大小
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Payload too large");
return ESP_FAIL;
}
buf = (char *)malloc(buf_len + 1);
if (!buf) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to allocate memory");
return ESP_FAIL;
}
int ret = httpd_req_recv(req, buf, buf_len);
if (ret <= 0) {
free(buf);
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
httpd_resp_send_408(req);
} else {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Failed to receive request");
}
return ESP_FAIL;
}
buf[ret] = '\0';
// 解析 JSON 数据
cJSON *json = cJSON_Parse(buf);
free(buf);
if (!json) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON");
return ESP_FAIL;
}
cJSON *ssid_item = cJSON_GetObjectItemCaseSensitive(json, "ssid");
cJSON *password_item = cJSON_GetObjectItemCaseSensitive(json, "password");
if (!cJSON_IsString(ssid_item) || (ssid_item->valuestring == NULL)) {
cJSON_Delete(json);
httpd_resp_send(req, "{\"success\":false,\"error\":\"无效的 SSID\"}", HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
std::string ssid_str = ssid_item->valuestring;
std::string password_str = "";
if (cJSON_IsString(password_item) && (password_item->valuestring != NULL)) {
password_str = password_item->valuestring;
}
// 获取当前对象
auto *this_ = static_cast<WifiConfigurationAp *>(req->user_ctx);
if (!this_->ConnectToWifi(ssid_str, password_str)) {
cJSON_Delete(json);
httpd_resp_send(req, "{\"success\":false,\"error\":\"无法连接到 WiFi\"}", HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
this_->Save(ssid_str, password_str);
cJSON_Delete(json);
// 设置成功响应
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, "{\"success\":true}", HTTPD_RESP_USE_STRLEN);
return ESP_OK;
},
.user_ctx = this
};
ESP_ERROR_CHECK(httpd_register_uri_handler(server_, &form_submit));
// Register the done.html page
httpd_uri_t done_html = {
.uri = "/done.html",
.method = HTTP_GET,
.handler = [](httpd_req_t *req) -> esp_err_t {
httpd_resp_send(req, done_html_start, strlen(done_html_start));
return ESP_OK;
},
.user_ctx = NULL
};
ESP_ERROR_CHECK(httpd_register_uri_handler(server_, &done_html));
// Register the reboot endpoint
httpd_uri_t reboot = {
.uri = "/reboot",
.method = HTTP_POST,
.handler = [](httpd_req_t *req) -> esp_err_t {
auto* this_ = static_cast<WifiConfigurationAp*>(req->user_ctx);
// 设置响应头,防止浏览器缓存
httpd_resp_set_type(req, "application/json");
httpd_resp_set_hdr(req, "Cache-Control", "no-store");
// 发送响应
httpd_resp_send(req, "{\"success\":true}", HTTPD_RESP_USE_STRLEN);
// 创建一个延迟重启任务
ESP_LOGI(TAG, "Rebooting...");
xTaskCreate([](void *ctx) {
// 等待200ms确保HTTP响应完全发送
vTaskDelay(pdMS_TO_TICKS(200));
// 停止Web服务器
auto* self = static_cast<WifiConfigurationAp*>(ctx);
if (self->server_) {
httpd_stop(self->server_);
}
// 再等待100ms确保所有连接都已关闭
vTaskDelay(pdMS_TO_TICKS(100));
// 执行重启
esp_restart();
}, "reboot_task", 4096, this_, 5, NULL);
return ESP_OK;
},
.user_ctx = this
};
ESP_ERROR_CHECK(httpd_register_uri_handler(server_, &reboot));
auto captive_portal_handler = [](httpd_req_t *req) -> esp_err_t {
auto *this_ = static_cast<WifiConfigurationAp *>(req->user_ctx);
std::string url = this_->GetWebServerUrl() + "/?lang=" + this_->language_;
// Set content type to prevent browser warnings
httpd_resp_set_type(req, "text/html");
httpd_resp_set_status(req, "302 Found");
httpd_resp_set_hdr(req, "Location", url.c_str());
httpd_resp_send(req, NULL, 0);
return ESP_OK;
};
// Register all common captive portal detection endpoints
const char* captive_portal_urls[] = {
"/hotspot-detect.html", // Apple
"/generate_204", // Android
"/mobile/status.php", // Android
"/check_network_status.txt", // Windows
"/ncsi.txt", // Windows
"/fwlink/", // Microsoft
"/connectivity-check.html", // Firefox
"/success.txt", // Various
"/portal.html", // Various
"/library/test/success.html" // Apple
};
for (const auto& url : captive_portal_urls) {
httpd_uri_t redirect_uri = {
.uri = url,
.method = HTTP_GET,
.handler = captive_portal_handler,
.user_ctx = this
};
ESP_ERROR_CHECK(httpd_register_uri_handler(server_, &redirect_uri));
}
ESP_LOGI(TAG, "Web server started");
}
bool WifiConfigurationAp::ConnectToWifi(const std::string &ssid, const std::string &password)
{
if (ssid.empty()) {
ESP_LOGE(TAG, "SSID cannot be empty");
return false;
}
if (ssid.length() > 32) { // WiFi SSID 最大长度
ESP_LOGE(TAG, "SSID too long");
return false;
}
is_connecting_ = true;
esp_wifi_scan_stop();
xEventGroupClearBits(event_group_, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT);
wifi_config_t wifi_config;
bzero(&wifi_config, sizeof(wifi_config));
strcpy((char *)wifi_config.sta.ssid, ssid.c_str());
strcpy((char *)wifi_config.sta.password, password.c_str());
wifi_config.sta.scan_method = WIFI_ALL_CHANNEL_SCAN;
wifi_config.sta.failure_retry_cnt = 1;
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
auto ret = esp_wifi_connect();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to connect to WiFi: %d", ret);
is_connecting_ = false;
return false;
}
ESP_LOGI(TAG, "Connecting to WiFi %s", ssid.c_str());
// Wait for the connection to complete for 5 seconds
EventBits_t bits = xEventGroupWaitBits(event_group_, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdTRUE, pdFALSE, pdMS_TO_TICKS(10000));
is_connecting_ = false;
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "Connected to WiFi %s", ssid.c_str());
esp_wifi_disconnect();
return true;
} else {
ESP_LOGE(TAG, "Failed to connect to WiFi %s", ssid.c_str());
return false;
}
}
void WifiConfigurationAp::Save(const std::string &ssid, const std::string &password)
{
ESP_LOGI(TAG, "Save SSID %s %d", ssid.c_str(), ssid.length());
SsidManager::GetInstance().AddSsid(ssid, password);
}
void WifiConfigurationAp::WifiEventHandler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
{
WifiConfigurationAp* self = static_cast<WifiConfigurationAp*>(arg);
if (event_id == WIFI_EVENT_AP_STACONNECTED) {
wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
ESP_LOGI(TAG, "Station " MACSTR " joined, AID=%d", MAC2STR(event->mac), event->aid);
} else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
ESP_LOGI(TAG, "Station " MACSTR " left, AID=%d", MAC2STR(event->mac), event->aid);
} else if (event_id == WIFI_EVENT_STA_CONNECTED) {
xEventGroupSetBits(self->event_group_, WIFI_CONNECTED_BIT);
} else if (event_id == WIFI_EVENT_STA_DISCONNECTED) {
xEventGroupSetBits(self->event_group_, WIFI_FAIL_BIT);
}
}
void WifiConfigurationAp::IpEventHandler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
{
WifiConfigurationAp* self = static_cast<WifiConfigurationAp*>(arg);
if (event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "Got IP:" IPSTR, IP2STR(&event->ip_info.ip));
xEventGroupSetBits(self->event_group_, WIFI_CONNECTED_BIT);
}
}
void WifiConfigurationAp::StartSmartConfig()
{
// 注册SmartConfig事件处理器
ESP_ERROR_CHECK(esp_event_handler_instance_register(SC_EVENT, ESP_EVENT_ANY_ID,
&WifiConfigurationAp::SmartConfigEventHandler, this, &sc_event_instance_));
// 初始化SmartConfig配置
smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT();
// cfg.esp_touch_v2_enable_crypt = true;
// cfg.esp_touch_v2_key = "1234567890123456"; // 设置16字节加密密钥
// 启动SmartConfig服务
ESP_ERROR_CHECK(esp_smartconfig_start(&cfg));
ESP_LOGI(TAG, "SmartConfig started");
}
void WifiConfigurationAp::SmartConfigEventHandler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
WifiConfigurationAp *self = static_cast<WifiConfigurationAp *>(arg);
if (event_base == SC_EVENT){
switch (event_id){
case SC_EVENT_SCAN_DONE:
ESP_LOGI(TAG, "SmartConfig scan done");
break;
case SC_EVENT_FOUND_CHANNEL:
ESP_LOGI(TAG, "Found SmartConfig channel");
break;
case SC_EVENT_GOT_SSID_PSWD:{
ESP_LOGI(TAG, "Got SmartConfig credentials");
smartconfig_event_got_ssid_pswd_t *evt = (smartconfig_event_got_ssid_pswd_t *)event_data;
char ssid[32], password[64];
memcpy(ssid, evt->ssid, sizeof(evt->ssid));
memcpy(password, evt->password, sizeof(evt->password));
ESP_LOGI(TAG, "SmartConfig SSID: %s, Password: %s", ssid, password);
// 尝试连接WiFi会失败故不连接
self->Save(ssid, password);
xTaskCreate([](void *ctx){
ESP_LOGI(TAG, "Restarting in 3 second");
vTaskDelay(pdMS_TO_TICKS(3000));
esp_restart();
}, "restart_task", 4096, NULL, 5, NULL);
break;
}
case SC_EVENT_SEND_ACK_DONE:
ESP_LOGI(TAG, "SmartConfig ACK sent");
esp_smartconfig_stop();
break;
}
}
}
void WifiConfigurationAp::Stop() {
// 停止SmartConfig服务
if (sc_event_instance_) {
esp_event_handler_instance_unregister(SC_EVENT, ESP_EVENT_ANY_ID, sc_event_instance_);
sc_event_instance_ = nullptr;
}
esp_smartconfig_stop();
// 停止定时器
if (scan_timer_) {
esp_timer_stop(scan_timer_);
esp_timer_delete(scan_timer_);
scan_timer_ = nullptr;
}
// 停止Web服务器
if (server_) {
httpd_stop(server_);
server_ = nullptr;
}
// 停止DNS服务器
dns_server_.Stop();
// 释放网络接口资源
if (ap_netif_) {
esp_netif_destroy(ap_netif_);
ap_netif_ = nullptr;
}
// 停止WiFi并重置模式
esp_wifi_stop();
esp_wifi_deinit();
esp_wifi_set_mode(WIFI_MODE_NULL);
// 注销事件处理器
if (instance_any_id_) {
esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id_);
instance_any_id_ = nullptr;
}
if (instance_got_ip_) {
esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip_);
instance_got_ip_ = nullptr;
}
ESP_LOGI(TAG, "Wifi configuration AP stopped");
}