/*
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_http_server.h"
#include "esp_netif.h"
#include "esp_spiffs.h"
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
/* FreeRTOS event group to signal when we are connected & ready to make a request */
static EventGroupHandle_t wifi_event_group;
/* The event group allows multiple bits for each event,
but we only care about one event - are we connected
to the AP with an IP? */
const int WIFI_CONNECTED_BIT = BIT0;
const int AP_STARTED_BIT = BIT1;
static const char *TAG = "wifi_config";
/* Structure to hold received WiFi configuration */
typedef struct {
char ssid[32];
char password[64];
bool valid;
} wifi_config_storage_t;
static wifi_config_storage_t wifi_config_storage;
/* An HTTP server that serves a configuration form and handles form submissions */
static httpd_handle_t server = NULL;
/* HTML form for WiFi configuration */
// 简化HTML页面(移除不必要的空格和换行)
// 最小化HTML页面(移除DOCTYPE和所有不必要的空格)
const char *wifi_config_page = "<html><body><form action=/config method=post>SSID:<input name=ssid><br>Password:<input type=password name=password><br><input type=submit value=Connect></form></body></html>";
/* HTML page for success message */
const char *success_page = "<html><body>"
"<h1>Configuration Successful</h1>"
"<p>ESP32 will now attempt to connect to the WiFi network.</p>"
"<p>You can close this page.</p></body></html>";
/* HTML page for error message */
const char *error_page = "<html><body>"
"<h1>Configuration Error</h1>"
"<p>Failed to save WiFi configuration. Please try again.</p>"
"<a href=\"/\">Back to configuration</a></body></html>";
static void connect_to_wifi_task(void *pvParameters);
/* Handler for root endpoint (/) */
static esp_err_t root_get_handler(httpd_req_t *req)
{
httpd_resp_send(req, wifi_config_page, strlen(wifi_config_page));
return ESP_OK;
}
/* Handler for configuration endpoint (/config) */
static esp_err_t config_post_handler(httpd_req_t *req)
{
char temp_buf[128] = {0}; // 减小缓冲区大小
int ret;
// 读取并丢弃请求头(只处理POST数据)
while ((ret = httpd_req_recv(req, temp_buf, sizeof(temp_buf))) > 0) {
// 查找表单数据边界(跳过请求头)
char *body_start = strstr(temp_buf, "\r\n\r\n");
if (body_start) {
body_start += 4; // 跳过\r\n\r\n
// 解析SSID和密码
char *ssid_start = strstr(body_start, "ssid=");
char *pass_start = strstr(body_start, "password=");
if (ssid_start && pass_start) {
ssid_start += 5; // 跳过"ssid="
pass_start += 9; // 跳过"password="
// 查找参数结束位置
char *ssid_end = strchr(ssid_start, '&');
char *pass_end = strchr(pass_start, '&');
// 复制SSID
if (ssid_end) {
size_t len = MIN(ssid_end - ssid_start, sizeof(wifi_config_storage.ssid) - 1);
strncpy(wifi_config_storage.ssid, ssid_start, len);
} else {
strncpy(wifi_config_storage.ssid, ssid_start, MIN(strlen(ssid_start), sizeof(wifi_config_storage.ssid) - 1));
}
// 复制密码(通常是最后一个参数)
strncpy(wifi_config_storage.password, pass_start, MIN(strlen(pass_start), sizeof(wifi_config_storage.password) - 1));
// 验证并处理
if (strlen(wifi_config_storage.ssid) > 0) {
wifi_config_storage.valid = true;
httpd_resp_send(req, success_page, strlen(success_page));
httpd_stop(server);
xTaskCreate(connect_to_wifi_task, "connect_to_wifi", 4096, NULL, 5, NULL);
return ESP_OK;
}
}
}
}
// 处理失败
httpd_resp_send(req, error_page, strlen(error_page));
return ESP_OK;
}
static esp_err_t favicon_get_handler(httpd_req_t *req)
{
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Favicon not found");
return ESP_OK;
}
/* HTTP server configuration */
static httpd_uri_t uri_get = {
.uri = "/",
.method = HTTP_GET,
.handler = root_get_handler
};
static httpd_uri_t uri_post = {
.uri = "/config",
.method = HTTP_POST,
.handler = config_post_handler
};
// 添加到 app_main() 前面的 URI handlers 中:
static httpd_uri_t uri_favicon = {
.uri = "/favicon.ico",
.method = HTTP_GET,
.handler = favicon_get_handler
};
/* Start the HTTP server */
static void start_webserver(void)
{
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.server_port = 80;
config.max_open_sockets = 5;
config.max_uri_handlers = 4;
config.max_resp_headers = 8;
config.backlog_conn = 10;
config.lru_purge_enable = true;
ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
if (httpd_start(&server, &config) == ESP_OK) {
ESP_LOGI(TAG, "Registering URI handlers");
httpd_register_uri_handler(server, &uri_get);
httpd_register_uri_handler(server, &uri_post);
httpd_register_uri_handler(server, &uri_favicon);
} else {
ESP_LOGE(TAG, "Error starting server!");
}
}
/* Event handler for WiFi and IP events */
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
esp_wifi_connect();
xEventGroupClearBits(wifi_event_group, WIFI_CONNECTED_BIT);
ESP_LOGI(TAG, "Connect to the AP failed");
} else if (event_base == IP_EVENT && 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(wifi_event_group, WIFI_CONNECTED_BIT);
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_START) {
xEventGroupSetBits(wifi_event_group, AP_STARTED_BIT);
ESP_LOGI(TAG, "AP Started");
start_webserver();
}
}
/* Task to connect to the configured WiFi network */
static void connect_to_wifi_task(void *pvParameters)
{
// Wait for a little while to let the client receive the success page
vTaskDelay(pdMS_TO_TICKS(1000));
// Configure the ESP32 to connect to the specified WiFi network
wifi_config_t wifi_config = {
.sta = {
.threshold.authmode = WIFI_AUTH_OPEN,
},
};
strncpy((char*)wifi_config.sta.ssid, wifi_config_storage.ssid, sizeof(wifi_config.sta.ssid));
strncpy((char*)wifi_config.sta.password, wifi_config_storage.password, sizeof(wifi_config.sta.password));
ESP_LOGI(TAG, "Connecting to SSID: %s", wifi_config.sta.ssid);
// Set WiFi mode to station
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
// Apply the configuration
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
// Start the WiFi interface
ESP_ERROR_CHECK(esp_wifi_start());
// Wait for WiFi connection
EventBits_t bits = xEventGroupWaitBits(wifi_event_group,
WIFI_CONNECTED_BIT,
pdFALSE,
pdTRUE,
portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "Connected to WiFi");
// Save the configuration to NVS for future use
nvs_handle_t nvs_handle;
esp_err_t err = nvs_open("wifi_config", NVS_READWRITE, &nvs_handle);
if (err == ESP_OK) {
// 强制转换为 const char* 以匹配函数参数类型
nvs_set_str(nvs_handle, "ssid", (const char*)wifi_config.sta.ssid);
nvs_set_str(nvs_handle, "password", (const char*)wifi_config.sta.password);
nvs_commit(nvs_handle);
nvs_close(nvs_handle);
ESP_LOGI(TAG, "WiFi configuration saved to NVS");
}
}
vTaskDelete(NULL);
}
/* Initialize WiFi as AP */
static void init_wifi_ap(void)
{
// Set static IP for the AP interface
esp_netif_create_default_wifi_ap();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// Register event handler
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));
// Configure the AP
wifi_config_t wifi_config = {
.ap = {
.ssid = "ESP32-Config",
.ssid_len = strlen("ESP32-Config"),
.password = "12345678",
.max_connection = 4,
.authmode = WIFI_AUTH_WPA_WPA2_PSK
},
};
// Set WiFi mode to AP
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
// Apply the configuration
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config));
// Start the WiFi interface
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "WiFi AP initialized with SSID: ESP32-Config, Password: 12345678");
}
/* Check if WiFi configuration exists in NVS */
static bool check_saved_wifi_config(void)
{
nvs_handle_t nvs_handle;
esp_err_t err = nvs_open("wifi_config", NVS_READONLY, &nvs_handle);
if (err != ESP_OK) {
ESP_LOGI(TAG, "No saved WiFi configuration found");
return false;
}
size_t required_size = 0;
err = nvs_get_str(nvs_handle, "ssid", NULL, &required_size);
if (err != ESP_OK || required_size == 0) {
nvs_close(nvs_handle);
return false;
}
char *ssid = malloc(required_size);
err = nvs_get_str(nvs_handle, "ssid", ssid, &required_size);
if (err != ESP_OK) {
free(ssid);
nvs_close(nvs_handle);
return false;
}
// Check if password exists
required_size = 0;
err = nvs_get_str(nvs_handle, "password", NULL, &required_size);
if (err != ESP_OK || required_size == 0) {
free(ssid);
nvs_close(nvs_handle);
return false;
}
free(ssid);
nvs_close(nvs_handle);
return true;
}
/* Try to connect to saved WiFi configuration */
static bool try_connect_saved_wifi(void)
{
nvs_handle_t nvs_handle;
esp_err_t err = nvs_open("wifi_config", NVS_READONLY, &nvs_handle);
if (err != ESP_OK) {
return false;
}
// Get SSID
size_t ssid_size = 0;
err = nvs_get_str(nvs_handle, "ssid", NULL, &ssid_size);
if (err != ESP_OK || ssid_size == 0) {
nvs_close(nvs_handle);
return false;
}
char *ssid = malloc(ssid_size);
err = nvs_get_str(nvs_handle, "ssid", ssid, &ssid_size);
if (err != ESP_OK) {
free(ssid);
nvs_close(nvs_handle);
return false;
}
// Get password
size_t password_size = 0;
err = nvs_get_str(nvs_handle, "password", NULL, &password_size);
if (err != ESP_OK || password_size == 0) {
free(ssid);
nvs_close(nvs_handle);
return false;
}
char *password = malloc(password_size);
err = nvs_get_str(nvs_handle, "password", password, &password_size);
if (err != ESP_OK) {
free(ssid);
free(password);
nvs_close(nvs_handle);
return false;
}
// Configure the ESP32 to connect to the saved WiFi network
wifi_config_t wifi_config = {
.sta = {
.threshold.authmode = WIFI_AUTH_OPEN,
},
};
strncpy((char*)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid));
strncpy((char*)wifi_config.sta.password, password, sizeof(wifi_config.sta.password));
free(ssid);
free(password);
nvs_close(nvs_handle);
ESP_LOGI(TAG, "Trying to connect to saved SSID: %s", wifi_config.sta.ssid);
// Set WiFi mode to station
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
// Apply the configuration
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
// Start the WiFi interface
ESP_ERROR_CHECK(esp_wifi_start());
// Wait for WiFi connection (with timeout)
EventBits_t bits = xEventGroupWaitBits(wifi_event_group,
WIFI_CONNECTED_BIT,
pdFALSE,
pdTRUE,
pdMS_TO_TICKS(10000)); // 10-second timeout
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "Successfully connected to saved WiFi");
return true;
} else {
ESP_LOGI(TAG, "Failed to connect to saved WiFi");
// Reset WiFi to stop any ongoing connection attempts
ESP_ERROR_CHECK(esp_wifi_stop());
return false;
}
}
void app_main(void)
{
// Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// Initialize WiFi event group
wifi_event_group = xEventGroupCreate();
// Initialize TCP/IP stack
esp_netif_init();
// Initialize the event loop
ESP_ERROR_CHECK(esp_event_loop_create_default());
// Check if saved WiFi configuration exists and try to connect
if (check_saved_wifi_config()) {
ESP_LOGI(TAG, "Saved WiFi configuration found, attempting to connect");
if (try_connect_saved_wifi()) {
// Connection successful, proceed with normal operation
ESP_LOGI(TAG, "WiFi connection successful, starting normal operation");
// Add your application code here
return;
}
}
// If no saved configuration or connection failed, start AP mode for configuration
ESP_LOGI(TAG, "Starting AP mode for WiFi configuration");
init_wifi_ap();
// Wait for AP to start
xEventGroupWaitBits(wifi_event_group, AP_STARTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
// The web server is automatically started when the AP is initialized
// The user can now connect to the ESP32-Config network and configure WiFi
}这是源代码,当手机在网页中输入wifi名和密码时,网页显示configuration error:failed to save wifi configuration please try again,但是wifi名和密码是正确的