VScode+ESP-IDF在ESP32上搭建web server,并实现Captive Portal - 连接WiFi自动弹出认证页面,此页面输入WIFI账号密码一键配网
ZIMOXIXUAN 2024-07-16 11:03:01 阅读 67
准备:
装好ESP-IDF插件的VScode;ESP32开发板(ESP32-S2、ESP32-S3都行)。ESP-IDF 4.4版本以上
第一步 用WIFI-STA示例项目改造
打开VScode,按Shift + Ctrl + P,输入Show Examples Projects后,搜索station,创建station例程。这是一个添加ssid和密码后就能连接无线网络的例程。
修改station_example_main.c代码,连接热点的SSID与PASS改成自定义的数组。
修改事件处理函数,判断STA开始的标志语句内创建一键配网的任务。
station_example_main.c 完整代码
<code>extern uint8_t wifi_userid[20];
extern uint8_t wifi_password[20];
#define EXAMPLE_ESP_MAXIMUM_RETRY 6
#if CONFIG_ESP_WIFI_AUTH_OPEN
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_OPEN
#elif CONFIG_ESP_WIFI_AUTH_WEP
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WEP
#elif CONFIG_ESP_WIFI_AUTH_WPA_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA2_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA_WPA2_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_WPA2_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA3_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA3_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA2_WPA3_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_WPA3_PSK
#elif CONFIG_ESP_WIFI_AUTH_WAPI_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WAPI_PSK
#endif
/* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group;
/* The event group allows multiple bits for each event, but we only care about two events:
* - we are connected to the AP with an IP
* - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static const char *TAG = "wifi station";
static int s_retry_num = 0;
static void smartconfig_task(void * parm);
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) {
xTaskCreate(smartconfig_task, "smartconfig_task", 4096, NULL, 3, NULL);
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "retry to connect to the AP");
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
ESP_LOGI(TAG,"connect to the AP fail");
} 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));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
extern void webserver_init(void);
void wifi_init_sta(void)
{
s_wifi_event_group = xEventGroupCreate();
// esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&event_handler,
NULL,
&instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL,
&instance_got_ip));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);
/* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
* happened. */
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "WiFi Connected to ap");// 成功连接路由器
xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT); //清除连接成功事件位
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGI(TAG, "WiFi Connected Failed");// 失败连接路由器
s_retry_num = 0;
xEventGroupClearBits(s_wifi_event_group, WIFI_FAIL_BIT); //清除连接失败事件位
esp_wifi_restore(); // 恢复WiFi出厂设置
webserver_init(); //开启WebServer
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
s_retry_num = 0;
xEventGroupClearBits(s_wifi_event_group, WIFI_FAIL_BIT); //清除连接失败事件位
esp_wifi_restore();
webserver_init();
}
/* The event will not be processed after unregister */
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip));
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));
vEventGroupDelete(s_wifi_event_group);
}
static void smartconfig_task(void * parm)
{
wifi_config_t myconfig = { 0};
ESP_LOGI(TAG, "creat smartconfig_task");
// 获取wifi配置信息,如果配置过,就直接连接wifi
esp_wifi_get_config(ESP_IF_WIFI_STA, &myconfig);
if (strlen((char*)myconfig.sta.ssid) > 0)
{
ESP_LOGI(TAG, "alrealy set, SSID is :%s,start connect", myconfig.sta.ssid);
esp_wifi_connect();
}
// 如果没有配置过,就进行配网操作
else
{
ESP_LOGI(TAG, "have no set, start to config");
wifi_config_t wifi_config = {
.sta = {
/* Setting a password implies station will connect to all security modes including WEP/WPA.
* However these modes are deprecated and not advisable to be used. Incase your Access point
* doesn't support WPA2, these mode can be enabled by commenting below line */
.threshold.authmode = ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD,
.sae_pwe_h2e = WPA3_SAE_PWE_BOTH,
},
};
strcpy((char*)wifi_config.sta.ssid, (char*)wifi_userid);
strcpy((char*)wifi_config.sta.password, (char*)wifi_password);
ESP_ERROR_CHECK(esp_wifi_disconnect());
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
esp_wifi_connect();
ESP_LOGI(TAG, "wifi_init_sta finished.");
esp_restart();
}
vTaskDelete(NULL);// 关闭线程
}
第二步 menuconfig选项设置
点击menuconfig后搜索“websocket”,勾选“WebSocket server support”以启用web socket,保存,退出。
修改Max HTTP Request Header Length为2048,以保证HTTP的报头发出不报错。
然后 保存 menuconfig
第三步 创建主程序和DNS服务器程序文件
在左侧main目录下创建 main.c、dns_server.h、root.html。添加这些文件到mian目录下“CMakeLists.txt”中,其中root.html的路径添加为EMBED_FILES,如果设计的页面有图片,图片路径也要添加其中,用空格隔开。
CMakeLists.txt 导入全部文件
<code>idf_component_register(SRCS "station_example_main.c" "main.c" "dns_server.c"
INCLUDE_DIRS "include"
EMBED_FILES root.html)
DNS服务器,该服务器将所有查询重定向到软接入点(softAP)的IP地址,用于实现Captive Portal - 连接WiFi自动弹出认证页面。dns_server.c 完整代码
#define DNS_PORT (53)
#define DNS_MAX_LEN (256)
#define OPCODE_MASK (0x7800)
#define QR_FLAG (1 << 7)
#define QD_TYPE_A (0x0001)
#define ANS_TTL_SEC (300)
static const char *TAG = "example_dns_redirect_server";
// DNS Header Packet
typedef struct __attribute__((__packed__))
{
uint16_t id;
uint16_t flags;
uint16_t qd_count;
uint16_t an_count;
uint16_t ns_count;
uint16_t ar_count;
} dns_header_t;
// DNS Question Packet
typedef struct {
uint16_t type;
uint16_t class;
} dns_question_t;
// DNS Answer Packet
typedef struct __attribute__((__packed__))
{
uint16_t ptr_offset;
uint16_t type;
uint16_t class;
uint32_t ttl;
uint16_t addr_len;
uint32_t ip_addr;
} dns_answer_t;
/*
Parse the name from the packet from the DNS name format to a regular .-seperated name
returns the pointer to the next part of the packet
*/
static char *parse_dns_name(char *raw_name, char *parsed_name, size_t parsed_name_max_len)
{
char *label = raw_name;
char *name_itr = parsed_name;
int name_len = 0;
do {
int sub_name_len = *label;
// (len + 1) since we are adding a '.'
name_len += (sub_name_len + 1);
if (name_len > parsed_name_max_len) {
return NULL;
}
// Copy the sub name that follows the the label
memcpy(name_itr, label + 1, sub_name_len);
name_itr[sub_name_len] = '.';
name_itr += (sub_name_len + 1);
label += sub_name_len + 1;
} while (*label != 0);
// Terminate the final string, replacing the last '.'
parsed_name[name_len - 1] = '\0';
// Return pointer to first char after the name
return label + 1;
}
// Parses the DNS request and prepares a DNS response with the IP of the softAP
static int parse_dns_request(char *req, size_t req_len, char *dns_reply, size_t dns_reply_max_len)
{
if (req_len > dns_reply_max_len) {
return -1;
}
// Prepare the reply
memset(dns_reply, 0, dns_reply_max_len);
memcpy(dns_reply, req, req_len);
// Endianess of NW packet different from chip
dns_header_t *header = (dns_header_t *)dns_reply;
ESP_LOGD(TAG, "DNS query with header id: 0x%X, flags: 0x%X, qd_count: %d",
ntohs(header->id), ntohs(header->flags), ntohs(header->qd_count));
// Not a standard query
if ((header->flags & OPCODE_MASK) != 0) {
return 0;
}
// Set question response flag
header->flags |= QR_FLAG;
uint16_t qd_count = ntohs(header->qd_count);
header->an_count = htons(qd_count);
int reply_len = qd_count * sizeof(dns_answer_t) + req_len;
if (reply_len > dns_reply_max_len) {
return -1;
}
// Pointer to current answer and question
char *cur_ans_ptr = dns_reply + req_len;
char *cur_qd_ptr = dns_reply + sizeof(dns_header_t);
char name[128];
// Respond to all questions with the ESP32's IP address
for (int i = 0; i < qd_count; i++) {
char *name_end_ptr = parse_dns_name(cur_qd_ptr, name, sizeof(name));
if (name_end_ptr == NULL) {
ESP_LOGE(TAG, "Failed to parse DNS question: %s", cur_qd_ptr);
return -1;
}
dns_question_t *question = (dns_question_t *)(name_end_ptr);
uint16_t qd_type = ntohs(question->type);
uint16_t qd_class = ntohs(question->class);
ESP_LOGD(TAG, "Received type: %d | Class: %d | Question for: %s", qd_type, qd_class, name);
if (qd_type == QD_TYPE_A) {
dns_answer_t *answer = (dns_answer_t *)cur_ans_ptr;
answer->ptr_offset = htons(0xC000 | (cur_qd_ptr - dns_reply));
answer->type = htons(qd_type);
answer->class = htons(qd_class);
answer->ttl = htonl(ANS_TTL_SEC);
esp_netif_ip_info_t ip_info;
esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_AP_DEF"), &ip_info);
ESP_LOGD(TAG, "Answer with PTR offset: 0x%" PRIX16 " and IP 0x%" PRIX32, ntohs(answer->ptr_offset), ip_info.ip.addr);
answer->addr_len = htons(sizeof(ip_info.ip.addr));
answer->ip_addr = ip_info.ip.addr;
}
}
return reply_len;
}
/*
Sets up a socket and listen for DNS queries,
replies to all type A queries with the IP of the softAP
*/
void dns_server_task(void *pvParameters)
{
char rx_buffer[128];
char addr_str[128];
int addr_family;
int ip_protocol;
while (1) {
struct sockaddr_in dest_addr;
dest_addr.sin_addr.s_addr = htonl(INADDR_ANY);
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(DNS_PORT);
addr_family = AF_INET;
ip_protocol = IPPROTO_IP;
inet_ntoa_r(dest_addr.sin_addr, addr_str, sizeof(addr_str) - 1);
int sock = socket(addr_family, SOCK_DGRAM, ip_protocol);
if (sock < 0) {
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
break;
}
ESP_LOGI(TAG, "Socket created");
int err = bind(sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
if (err < 0) {
ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
}
ESP_LOGI(TAG, "Socket bound, port %d", DNS_PORT);
while (1) {
ESP_LOGI(TAG, "Waiting for data");
struct sockaddr_in6 source_addr; // Large enough for both IPv4 or IPv6
socklen_t socklen = sizeof(source_addr);
int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &socklen);
// Error occurred during receiving
if (len < 0) {
ESP_LOGE(TAG, "recvfrom failed: errno %d", errno);
close(sock);
break;
}
// Data received
else {
// Get the sender's ip address as string
if (source_addr.sin6_family == PF_INET) {
inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr.s_addr, addr_str, sizeof(addr_str) - 1);
} else if (source_addr.sin6_family == PF_INET6) {
inet6_ntoa_r(source_addr.sin6_addr, addr_str, sizeof(addr_str) - 1);
}
// Null-terminate whatever we received and treat like a string...
rx_buffer[len] = 0;
char reply[DNS_MAX_LEN];
int reply_len = parse_dns_request(rx_buffer, len, reply, DNS_MAX_LEN);
ESP_LOGI(TAG, "Received %d bytes from %s | DNS reply with len: %d", len, addr_str, reply_len);
if (reply_len <= 0) {
ESP_LOGE(TAG, "Failed to prepare a DNS reply");
} else {
int err = sendto(sock, reply, reply_len, 0, (struct sockaddr *)&source_addr, sizeof(source_addr));
if (err < 0) {
ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
break;
}
}
}
}
if (sock != -1) {
ESP_LOGE(TAG, "Shutting down socket");
shutdown(sock, 0);
close(sock);
}
}
vTaskDelete(NULL);
}
void start_dns_server(void)
{
xTaskCreate(dns_server_task, "dns_server", 4096, NULL, 5, NULL);
}
在root.html中添加网页布局HTML代码,设计了一个简单WIFI信息传输的页面,寻到roott.html文件存放目录,双击运行。若只是要修改页面效果,可将客户端连接的地址固定,在VScode修改保存后在浏览器中按F5刷新,能直接看到最新设计效果。用JavaScript语言,创建客户端套接字“ws_client”,JavaScript代码添加在HTML代码的下方。此处设计的功能为:将从web server收到的字符串数据打印log,并回发给ESP32服务器。此脚本嵌入在ESP32的flash后,在使用时,将会在被HTTP客户端请求时发出去。
<!DOCTYPE html>
<html lang="en">code>
<head>
<meta charset="UTF-8">code>
<meta name="viewport" content="width=device-width, initial-scale=1.0">code>
<title>OAK Wi-Fi Configuration</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 400px;
margin: 50px auto;
padding: 20px;
background-color: #f4f4f4;
border-radius: 5px;
}
h1 {
text-align: center;
color: #333;
margin-top: -30px; /* 上移标题 */
}
form {
display: flex;
flex-direction: column;
}
label {
display: block;
margin-bottom: 5px;
color: #666;
}
input[type="text"] { code>
width: 100%;
height: 38px;
padding: 8px;
margin-bottom: 15px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
input[type="password"] { code>
width: 100%;
height: 38px;
padding: 8px;
margin-bottom: 15px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
button {
width: 60%;
padding: 12px 0; /* 调整垂直内边距以保持文本居中,水平内边距已隐含为0 */
margin: 0 auto; /* 添加这行代码使按钮水平居中 */
background-color: #4CAF50;
color: white;
border: none;
cursor: pointer;
border-radius: 5px;
margin-top: 12px; /* 下移按钮 */
}
button:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<h1>OAK Wi-Fi 设置</h1>
<form onsubmit="event.preventDefault(); sendData();">code>
<label for="UserID">Wi-Fi 名称:</label>code>
<input type="text" id="UserID" name="UserID" placeholder="请输入Wi-Fi账号...">code>
<label for="PassWord">Wi-Fi 密码:</label>code>
<input type="password" id="PassWord" name="PassWord" placeholder="请输入Wi-Fi密码...">code>
<button type="submit">发送WIFI数据至OAK测试盒</button>code>
</form>
</body>
</html>
<script>
// 服务器地址
const ws_client = new WebSocket("ws://" + window.location.host + "/ws");
// const ws_client = new WebSocket("ws://192.168.4.1/ws");
/* ws_client连接成功事件 */
ws_client.onopen = function (event) {
};
/* ws_client错误事件 */
ws_client.onerror = function (error) {
};
/* ws_client接收数据 */
ws_client.onmessage = function (event) {
};
/* 发送数据到服务器 */
function sendData() {
const userid = document.getElementById("UserID").value;
const password = document.getElementById("PassWord").value;
if (userid.trim()) {
ws_client.send(userid);
}
if (password.trim()) {
ws_client.send(password);
}
console.log(`Wi-Fi 名称: ${ userid}, Wi-Fi 密码: ${ password}`);
}
</script>
页面效果
重点便是Web Server的代码实现部分。首先在主程序app_main里,需要先进入STA的连接模式,如果同一时间段连续6次连接失败,再打开WebServer,配置为AP模式,传输本地WIFI的相关信息。
当成功从HTTP页面接收到WIFI名称和WIFI密码时,再次重新进入STA连接模式。
main.c 完整代码
<code>#define EXAMPLE_ESP_WIFI_SSID
#define EXAMPLE_ESP_WIFI_PASS
#define EXAMPLE_MAX_STA_CONN 2
extern const char root_start[] asm("_binary_root_html_start");
extern const char root_end[] asm("_binary_root_html_end");
static const char *TAG = "main";
#define BUFFER_LEN 1024
typedef struct
{
char data[BUFFER_LEN];
int len;
int client_socket;
}DATA_PARCEL;
static QueueHandle_t ws_server_rece_queue = NULL;//收到的消息传给任务处理
httpd_handle_t server = NULL; //httpd_handle_t
void stop_webserver(httpd_handle_t server);
static void wifi_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
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 " join, 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 " leave, AID=%d",
MAC2STR(event->mac), event->aid);
}
}
static void wifi_init_softap(void)
{
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL));
wifi_config_t wifi_config = {
.ap = {
.ssid = EXAMPLE_ESP_WIFI_SSID,
.ssid_len = strlen(EXAMPLE_ESP_WIFI_SSID),
.password = EXAMPLE_ESP_WIFI_PASS,
.max_connection = EXAMPLE_MAX_STA_CONN,
.authmode = WIFI_AUTH_WPA_WPA2_PSK
},
};
if (strlen(EXAMPLE_ESP_WIFI_PASS) == 0) {
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
}
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
esp_netif_ip_info_t ip_info;
esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_AP_DEF"), &ip_info);
char ip_addr[16];
inet_ntoa_r(ip_info.ip.addr, ip_addr, 16);
ESP_LOGI(TAG, "Set up softAP with IP: %s", ip_addr);
ESP_LOGI(TAG, "wifi_init_softap finished. SSID:'%s' password:'%s'",
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
}
// HTTP GET Handler
static esp_err_t root_get_handler(httpd_req_t *req)
{
const uint32_t root_len = root_end - root_start;
ESP_LOGI(TAG, "Serve root");
httpd_resp_set_type(req, "text/html");
httpd_resp_send(req, root_start, root_len);
return ESP_OK;
}
static const httpd_uri_t root = {
.uri = "/",
.method = HTTP_GET,
.handler = root_get_handler
};
/*ws服务器接收数据*/
static DATA_PARCEL ws_rece_parcel;
static esp_err_t ws_server_rece_data(httpd_req_t *req)
{
if (req->method == HTTP_GET) {
// ws_client_list_add(httpd_req_to_sockfd(req));
return ESP_OK;
}
esp_err_t ret = ESP_FAIL;
httpd_ws_frame_t ws_pkt;
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
memset(&ws_rece_parcel, 0, sizeof(DATA_PARCEL));
ws_pkt.type = HTTPD_WS_TYPE_TEXT;
ws_pkt.payload = (uint8_t*)ws_rece_parcel.data; //指向缓存区
ret = httpd_ws_recv_frame(req, &ws_pkt, 0);//设置参数max_len = 0来获取帧长度
if (ret != ESP_OK) {
printf("ws_server_rece_data data receiving failure!");
return ret;
}
if (ws_pkt.len>0)
{
ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len);/*设置参数max_len 为 ws_pkt.len以获取帧有效负载 */
if (ret != ESP_OK) {
printf("ws_server_rece_data data receiving failure!");
return ret;
}
ws_rece_parcel.len = ws_pkt.len;
ws_rece_parcel.client_socket = httpd_req_to_sockfd(req);
if (xQueueSend(ws_server_rece_queue ,&ws_rece_parcel,pdMS_TO_TICKS(1))){
ret = ESP_OK;
}
}
else
{
printf("ws_pkt.len<=0");
}
return ret;
}
/*WEB SOCKET*/
static const httpd_uri_t ws = {
.uri = "/ws",
.method = HTTP_GET,
.handler = ws_server_rece_data,
.user_ctx = NULL,
.is_websocket = true
};
/*数据接收处理任务*/
static DATA_PARCEL rece_buffer;
uint8_t wifi_userid[20] = "12345678"; //默认WIFI名称
uint8_t wifi_password[20] = "12345678"; //默认WIFI密码
uint8_t wifi_config = 0; //WIFI配置标志 0:未配置 1:已配置
extern void wifi_init_sta(void);
static void ws_server_rece_task(void *p)
{
int rece_step = 1;
while (1)
{
if(xQueueReceive(ws_server_rece_queue,&rece_buffer,portMAX_DELAY))
{
if(rece_step == 1)
{
strcpy((char*)wifi_userid, rece_buffer.data);
wifi_userid[rece_buffer.len] = '\0';
rece_step = 2;
}
else if(rece_step == 2)
{
strcpy((char*)wifi_password, rece_buffer.data);
wifi_password[rece_buffer.len] = '\0';
rece_step = 3;
wifi_config = 1;
stop_webserver(server);
}
printf("socket : %d\tdata len : %d\tpayload : %s\r\n",rece_buffer.client_socket,rece_buffer.len,rece_buffer.data);
}
if(wifi_config == 1)
{
wifi_init_sta();
vTaskDelete(NULL);// 关闭线程
}
}
}
// HTTP Error (404) Handler - Redirects all requests to the root page
esp_err_t http_404_error_handler(httpd_req_t *req, httpd_err_code_t err)
{
// Set status
httpd_resp_set_status(req, "302 Temporary Redirect");
// Redirect to the "/" root directory
httpd_resp_set_hdr(req, "Location", "/");
// iOS requires content in the response to detect a captive portal, simply redirecting is not sufficient.
httpd_resp_send(req, "Redirect to the captive portal", HTTPD_RESP_USE_STRLEN);
ESP_LOGI(TAG, "Redirecting to root");
return ESP_OK;
}
void start_webserver(void)
{
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.max_open_sockets = 13;
config.lru_purge_enable = true;
// Start the httpd server
ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
if (httpd_start(&server, &config) == ESP_OK) {
// Set URI handlers
httpd_register_uri_handler(server, &root);
httpd_register_uri_handler(server, &ws);//注册uri处理程序
httpd_register_err_handler(server, HTTPD_404_NOT_FOUND, http_404_error_handler);
}
/*创建接收队列*/
ws_server_rece_queue = xQueueCreate( 3 , sizeof(DATA_PARCEL));
if (ws_server_rece_queue == NULL )
{
printf("ws_server_rece_queue ERROR\r\n");
}
BaseType_t xReturn ;
/*创建接收处理任务*/
xReturn = xTaskCreatePinnedToCore(ws_server_rece_task,"ws_server_rece_task",4096,NULL,15, NULL, tskNO_AFFINITY);
if(xReturn != pdPASS)
{
printf("xTaskCreatePinnedToCore ws_server_rece_task error!\r\n");
}
}
void stop_webserver(httpd_handle_t server)
{
// Stop the httpd server
httpd_stop(server);
}
void webserver_init(void)
{
// Initialize Wi-Fi including netif with default config
esp_netif_create_default_wifi_ap();
// Initialise ESP32 in SoftAP mode
wifi_init_softap();
// Start the server for the first time
start_webserver();
// Start the DNS server that will redirect all queries to the softAP IP
start_dns_server();
}
void app_main(void)
{
/*
Turn of warnings from HTTP server as redirecting traffic will yield
lots of invalid requests
*/
esp_log_level_set("httpd_uri", ESP_LOG_ERROR);
esp_log_level_set("httpd_txrx", ESP_LOG_ERROR);
esp_log_level_set("httpd_parse", ESP_LOG_ERROR);
// Initialize networking stack
ESP_ERROR_CHECK(esp_netif_init());
// Create default event loop needed by the main app
ESP_ERROR_CHECK(esp_event_loop_create_default());
// Initialize NVS needed by Wi-Fi
ESP_ERROR_CHECK(nvs_flash_init());
esp_netif_create_default_wifi_sta();
wifi_init_sta();
}
为了适应修改后的WIFI配置,Kconfig.projbuild里的内容要进行特定修改。
menu "Example Configuration"
config ESP_WIFI_SSID
string "SoftAP SSID"
default "esp32_ssid"
help
SSID (network name) to set up the softAP with.
config ESP_WIFI_PASSWORD
string "SoftAP Password"
default "esp32_pwd"
help
WiFi password (WPA or WPA2) for the example to use for the softAP.
config ESP_MAX_STA_CONN
int "Maximal STA connections"
default 4
help
Max number of the STA connects to AP.
config ESP_WIFI_SSID
string "WiFi SSID"
default "myssid"
help
SSID (network name) for the example to connect to.
config ESP_WIFI_PASSWORD
string "WiFi Password"
default "mypassword"
help
WiFi password (WPA or WPA2) for the example to use.
config ESP_MAXIMUM_RETRY
int "Maximum retry"
default 5
help
Set the Maximum retry to avoid station reconnecting to the AP unlimited when the AP is really inexistent.
choice ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD
prompt "WiFi Scan auth mode threshold"
default ESP_WIFI_AUTH_OPEN
help
The weakest authmode to accept in the scan mode.
config ESP_WIFI_AUTH_OPEN
bool "OPEN"
config ESP_WIFI_AUTH_WEP
bool "WEP"
config ESP_WIFI_AUTH_WPA_PSK
bool "WPA PSK"
config ESP_WIFI_AUTH_WPA2_PSK
bool "WPA2 PSK"
config ESP_WIFI_AUTH_WPA_WPA2_PSK
bool "WPA/WPA2 PSK"
config ESP_WIFI_AUTH_WPA3_PSK
bool "WPA3 PSK"
config ESP_WIFI_AUTH_WPA2_WPA3_PSK
bool "WPA2/WPA3 PSK"
config ESP_WIFI_AUTH_WAPI_PSK
bool "WAPI PSK"
endchoice
endmenu
调试效果
连续6次连接失败跳转至WebServer。
在HTTP页面完成WIFI配置,成功连接WIFI。
结束语
未完待续…
上一篇: Web大学生网页作业成品:个人博客主页 (纯HTML+CSS代码) (1)
下一篇: [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent..
本文标签
此页面输入WIFI账号密码一键配网 并实现Captive Portal - 连接WiFi自动弹出认证页面 VScode+ESP-IDF在ESP32上搭建web server
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。