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。

在这里插入图片描述

结束语

未完待续…



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。