#include "wifi_configuration_ap.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(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(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(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(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(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(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(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(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(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"); }