赞
踩
上一篇提到了配网的简单方式,采用的json格式传递wifi账户和密码。这种方式优势是可以在esp32端直接用cjson库解析出json数据。但是不好的地方在于,html网页会复杂一点,需要将输入框中的数据转换成为json格式再发送。发送方式为post请求。那么有没有办法直接解析post默认格式数据呢。这一点本文将进行探讨。
另外一点,上一篇wifi从ap模式切换到station模式采用的是延时。这种方式也是不是很合理,这里进行了优化。
这里对配网的整个流程进行梳理。
上电->wifi初始化为ap模式->开启http服务器->用户连上esp32wifi->浏览器输入esp网关地址默认是192.168.1.4->在页面上输入要连接的wifi名称和密码->点击页面上的发送按钮->浏览器通过post请求将wifi名称和密码发送到esp32->esp32解析出wifi名称和密码->退出wifiap模式,关闭http服务器->将wifi名称和密码作为参数,将wifi初始化为station模式。 配网完成。
网页文件的内容本质上是很长的字符串。那最简单的方法就是定义一个字符串数组。将数组内容填充为网页内容。
const char index_string[] =
"<!DOCTYPE html> \
<head> \
<meta charset=\"utf-8\"> \
<title>wifi config</title> \
</head>";
比如采用这样定义,这种方式一般用于给网页的反馈信息,比如404信息等。但是对于复杂一点的网页,这种方式显然就不太方便。要是能直接将用html工具设计生成的.htm格式的文件直接编译那不是更好了。这就是另外一种比较推荐的方式。
这次是直接借用了一个半开源的esp32桌面小电视的配网网页。如图上图。直接编译需要两个步骤。
1、修改CMakeList.txt文件
idf_component_register(SRCS ${main_src}
INCLUDE_DIRS "."
EMBED_FILES "upload_script.html" "wifi.html"
)
添加EMBED_FILES
2、调用编译出来的文件。
这个wifi.html编译出出来的文本文件怎么使用呢。wifi.html编译出来一般名称是默认的_binary_名称_类型_start。这个指针代编译出来文件的起始地址。_binary_名称_类型_end,代表结束地址。wifi.html的引用方式如下。
extern const unsigned char script_start[] asm("_binary_wifi_html_start");
extern const unsigned char script_end[] asm("_binary_wifi_html_end");
const size_t script_size = (script_end - script_start);
通过上述方式,便可以得到wifi.html这个大的数组。
上面有了引用wifi.html的方法,但怎么能让其在浏览器中显示呢?这就要用到http服务器。
在浏览器中输入地址,浏览器会默认使用GET方法向http服务器请求数据。http服务器收到GET命令后,将要在浏览器中显示的数据发送给浏览器,这样浏览器就能显示出网页了。
在配网中,至少需要定义两个页面,一个是根页面,一个是点击网页上配网的按钮会触发进入的页面。根页面就是访问192.168.4.1进入的网页。具体定义如下:
定义页面结构体:
httpd_uri_t index_page = {
.uri = "/", //192.168.1.4
.method = HTTP_GET,
.handler = index_get_handler,
/* Let's pass response string in user
* context to demonstrate it's usage */
.user_ctx = NULL,
};
其中uri为根目录,就是192.168.4.1这个页面。method定义该页面的触发方法,为GET方法。handler定义进入该页面后需要运行的函数。当浏览器访问了192.168.4.1的时候index_get_handler函数就会运行。
定义index_get_handler
static esp_err_t index_get_handler(httpd_req_t *req)
{
extern const unsigned char upload_script_start[] asm("_binary_wifi_html_start");
extern const unsigned char upload_script_end[] asm("_binary_wifi_html_end");
const size_t upload_script_size = (upload_script_end - upload_script_start);
/* Add file upload form and script which on execution sends a POST request to /upload */
httpd_resp_set_type(req,HTTPD_TYPE_TEXT);
httpd_resp_send(req, (const char *)upload_script_start, upload_script_size);
return ESP_OK;
}
这个函数就是将前文说到的wifi.html这个文件的内容返回给浏览器进行显示。
实际页面显示效果如下:
现在再说下当点击了保存并连接需要做的事情。点击这个按钮后,浏览器会调用get方法,将数据发送给http服务器。所以点击保存并连接后需要显示的页面定义及处理方法如下:
static const httpd_uri_t echo = { .uri = "/", .method = HTTP_POST, .handler = echo_post_handler, .user_ctx = NULL }; static esp_err_t echo_post_handler(httpd_req_t *req) { char buf[100]; // char ssid[10]; // char pswd[10]; int ret, remaining = req->content_len; while (remaining > 0) { /* Read the data for the request */ if ((ret = httpd_req_recv(req, buf, MIN(remaining, sizeof(buf)))) <= 0) { if (ret == HTTPD_SOCK_ERR_TIMEOUT) { /* Retry receiving if timeout occurred */ continue; } return ESP_FAIL; } /* Send back the same data */ httpd_resp_send_chunk(req, buf, ret); remaining -= ret; esp_err_t e = httpd_query_key_value(buf,"ssid",wifi_name,sizeof(wifi_name)); if(e == ESP_OK) { printf("ssid = %s\r\n",wifi_name); } else { printf("error = %d\r\n",e); } e = httpd_query_key_value(buf,"password",wifi_password,sizeof(wifi_password)); if(e == ESP_OK) { printf("pswd = %s\r\n",wifi_password); } else { printf("error = %d\r\n",e); } /* Log data received */ ESP_LOGI(TAG, "=========== RECEIVED DATA =========="); ESP_LOGI(TAG, "%.*s", ret, buf); ESP_LOGI(TAG, "===================================="); } // End response httpd_resp_send_chunk(req, NULL, 0); if(strcmp(wifi_name ,"\0")!=0 && strcmp(wifi_password,"\0")!=0) { xSemaphoreGive(ap_sem); ESP_LOGI(TAG, "set wifi name and password successfully! goto station mode"); } return ESP_OK; }
当点击了保存并连接按钮后,echo_post_handler函数就会运行。这个函数的主要作用是,将受到的数据返回给浏览器进行显示,并且将post字符串中的wifi名称和密码分别解析出来,赋值给
char wifi_name[30]={0};
char wifi_password[30]={0};
这两个字符串数组。最大的名称和密码长度为29字节。
当解析出来的wifi_name和wifi_password值都不为空,则释放信号量,给wifi_station的任务,进入station模式。
当输入名称abc密码123456时,服务端收到的字符串为
ssid=abc&password=123456&citycode=
这是一种post方法通过格式的字符串。esp32提供了相应的解析方法。最早就是不知道这个解析方法,所以才会用到json格式发送wifi名称和密码。解析的函数如下:
esp_err_t e = httpd_query_key_value(buf,“ssid”,wifi_name,sizeof(wifi_name));
第一个参数为要解析的post字符串。
第二参数是传入需要解析的“键值对”中的键。
第三个参数为解析到的数据存储的数组。
最后一个参数为存储数组的长度
当解析成功后。e 的值为ESP_OK,通过这个来判断是否解析成功。
启动服务,将上文的两个页面注册到服务器中。这个比较简单。
httpd_handle_t start_webserver(void) { httpd_handle_t server = NULL; httpd_config_t config = HTTPD_DEFAULT_CONFIG(); 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 ESP_LOGI(TAG, "Registering URI handlers"); httpd_register_uri_handler(server, &echo); httpd_register_uri_handler(server, &index_page); #if CONFIG_EXAMPLE_BASIC_AUTH httpd_register_basic_auth(server); #endif return server; } ESP_LOGI(TAG, "Error starting server!"); return NULL; }
当收到正确的wifi名称和密码后,esp32要将wifi转成station模式,并进行联网。这一部分是如何实现的?这里用到了一个单独的任务。
进入station模式前,需要将http服务器关闭,并且wifi重新初始化。**注意下面的这些函数都不能少。否则station模式会工作不正常。**这也是实验了好久才得出的。
void wifi_station_task(void * pvParameters) { uint32_t result =0; while(1) { result = xSemaphoreTake(ap_sem,portMAX_DELAY); if(result == pdPASS) { esp_wifi_stop(); esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler ); esp_netif_destroy_default_wifi(ap_netif); esp_event_loop_delete_default(); esp_wifi_deinit(); esp_netif_deinit(); httpd_stop(server); printf("hello \r\n"); ESP_LOGI(TAG,"led on"); wifi_init_sta(wifi_name,wifi_password); } printf("hello1 \r\n"); // vTaskDelay(pdMS_TO_TICKS(1000)); } }
具体代码链接如下,开发环境为官方的esp-idf。https://download.csdn.net/download/sinat_36568888/85750520
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。