ESP32-WiFi配网(AP和STA模式及配置界面)上
浏览器显示wifi配置界面;AP模式(ESP32自己是热点);STA模式(ESP32是终端,去连接别人热点);AP&STA共存模式(自己既是热点也可以连接别人热点)
1.配置界面
这一部分主要做浏览器的显示界面,在这个界面里设置ESP32的wifi信息,手机可以连接到ESP32的热点。 (默认的前提是esp32在AP模式,我们手机连上了esp32的热点)
显示界面代码如下:
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>demo1</title>
<style>
* {
margin: 0;
padding: 0;
}
body {
background-color: rgb(235, 235, 235);
}
form {
width: 400px;
height: 200px;
background-color: white;
padding: 5px;
box-sizing: border-box;
position: absolute;
left: 50%;
top: 1%;
/* 水平居中 */
transform: translateX(-50%);
/* transform: translate(-50%, -50%); */
/* 相对于现在所处位置的位移变化,x便偏移自己宽度的50%,y偏移自己高度的50% */
}
h2 {
margin-bottom: 10px;
text-align: center;
}
form input {
width: 100%;
height: 30px;
display: block;
margin-bottom: 8px;
padding-left: 10px;
box-sizing: border-box;
}
.mya {
width: 100%;
height: 30px;
margin-bottom: 20x;
}
.mya a:nth-child(1) {
float: left;
}
.mya a:nth-child(2) {
float: right;
}
button {
width: 100%;
height: 40px;
background-color: rgb(235, 235, 235);
border: none;
}
button:active {
box-shadow: 0 0 3px rgb(173, 172, 172);
/* x偏移 y偏移 模糊值 颜色 */
}
</style>
</head>
/*第二部分,主要设置一下传输的数据*/
<body>
<form action="">
<h2>WiFi 密码配置</h2>
<input id="wifi" type="text" placeholder="请输入WiFi账号">
<input id="code" type="text" placeholder="请输入WiFi密码">
<button id="set_wifi" type="button" onclick="send_wifi()">提交</button>
<button id="back" type="button" onclick="send_back()">退出</button>
</form>
</body>
/*调用的函数*/
<script>
function setpath() {
var default_path = document.getElementById("newfile").files[0].name;
document.getElementById("filepath").value = default_path;
}
function send_wifi() {
var input_ssid = document.getElementById("wifi").value;
var input_code = document.getElementById("code").value;
var xhttp = new XMLHttpRequest();
xhttp.open("POST", "/wifi_data", true);
xhttp.onreadystatechange = function() {
if (xhttp.readyState == 4) {
if (xhttp.status == 200) {
alert("WiFi设置成功!")
console.log(xhttp.responseText);
location.reload()
} else if (xhttp.status == 0) {
alert("设置失败,请检查网络连接!");
location.reload()
return
} else {
alert(xhttp.status + " Error!\n" + xhttp.responseText);
location.reload()
return
}
}
};
var data = {
"wifi_name":input_ssid,
"wifi_code":input_code
}
xhttp.send(JSON.stringify(data));
}
function send_back() {
var xhttp = new XMLHttpRequest();
xhttp.open("POST", "/back", true);
xhttp.onreadystatechange = function() {
if (xhttp.readyState == 4) {
if (xhttp.status == 200) {
alert("退出设置成功!")
console.log(xhttp.responseText);
location.reload()
} else if (xhttp.status == 0) {
alert("设置失败,请检查网络连接!");
location.reload()
} else {
alert(xhttp.status + " Error!\n" + xhttp.responseText);
location.reload()
}
}
};
var data = {
"back":"back",
}
xhttp.send(JSON.stringify(data));
}
</script>
这个代码主要可分三部分看:第一部分是设置页面的尺寸,后面一部分是传输数据,最后一部分是调用的函数。
在编写之前,看一下官方给的post代码:
/* An HTTP POST handler */
static esp_err_t echo_post_handler(httpd_req_t *req)
{
char buf[100];
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;
/* 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);
return ESP_OK;
}
static const httpd_uri_t echo = {
.uri = "/echo",
.method = HTTP_POST,
.handler = echo_post_handler,
.user_ctx = NULL
};
翻到结构体的定义:
typedef struct httpd_uri {
const char *uri; /*!< The URI to handle */
httpd_method_t method; /*!< Method supported by the URI */
/**
* Handler to call for supported request method. This must
* return ESP_OK, or else the underlying socket will be closed.
*/
esp_err_t (*handler)(httpd_req_t *r);
/**
* Pointer to user context data which will be available to handler
*/
void *user_ctx;
} httpd_uri_t;
url就是我们需要设置的路径,比如在界面中特别标识出来的/wifi_data就是我们设置的url的路径,method是post方法,handler是我们要调用的函数,user_ctx是我们要传递的数据,handler中的函数就是专门处理传递过来的数据。比如刚刚在界面中特别圈出来的两个wifi_name和wifi_code就是会在该函数里面处理。
2.嵌入html网页
我们在上面写好了html网页,但是怎么显示出来呢?第一种最简单的方法就是定义一个数组,内容填充为网页内容,但是对于复杂的网页这个方法不合适。第二种方法是新建一个文件夹,将我们的文件添加进来,文件名是setting.html。然后修改CMakeList.txt文件,把带路径的文件名添加进EMBED_FILES。然后定义结构体,也是利用URL,把这个文件放在根目录这里,让程序一开机就访问这个路径;在定义函数解析文件得到数组,这样界面就能显示出来。
定义结构体代码:
/* URI handler for getting uploaded files */
httpd_uri_t file_download = {
.uri = "/", // Match all URIs of type /path/to/file
.method = HTTP_GET,
.handler = download_get_handler,
.user_ctx = NULL
};
定义函数:
static esp_err_t download_get_handler(httpd_req_t *req)
{
extern const unsigned char upload_script_start[] asm("_binary_setting_html_start");
extern const unsigned char upload_script_end[] asm("_binary_setting_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;
}
3. 获取wifi信息
用json来进行格式解析(注意:ESP32判断字符串完成的标志是‘\0’,所以要在末尾加上)
static esp_err_t send_wifi_handler(httpd_req_t *req)
{
int total_len = req->content_len;
int cur_len = 0;
char *buf = ((struct file_server_data *)(req->user_ctx))->scratch;
int received = 0;
if (total_len >= SCRATCH_BUFSIZE) {
/* Respond with 500 Internal Server Error */
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "content too long");
return ESP_FAIL;
}
while (cur_len < total_len) {
received = httpd_req_recv(req, buf + cur_len, total_len);
if (received <= 0) {
/* Respond with 500 Internal Server Error */
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to post control value");
return ESP_FAIL;
}
cur_len += received;
}
buf[total_len] = '\0';
printf("recived data length is :%d\n",total_len);
for (int i = 0; i <total_len ; i++){
putchar(buf[i]);
}
printf("\r\nwifi data recived!\r\n");
cJSON *root = cJSON_Parse(buf);
char *ssid = cJSON_GetObjectItem(root, "wifi_name")->valuestring;
char *psw = cJSON_GetObjectItem(root, "wifi_code")->valuestring;
int ssid_len = strlen(ssid);
int psw_len = strlen(psw);
set_system_data_wifi_info(ssid,ssid_len,psw ,psw_len);
print_system_data_wifi_info();
cJSON_Delete(root);
httpd_resp_sendstr(req, "Post control value successfully");
return ESP_OK;
}
编写结构体并注册
httpd_uri_t wifi_data = {
.uri = "/wifi_data", // Match all URIs of type /delete/path/to/file
.method = HTTP_POST,
.handler = send_wifi_handler,
.user_ctx = server_data // Pass server data as context
};
httpd_register_uri_handler(server, &wifi_data);
其中,server_data是这么定义的
struct file_server_data {
/* Base path of file storage */
char base_path[ESP_VFS_PATH_MAX + 1];
/* Scratch buffer for temporary storage during file transfer */
char scratch[SCRATCH_BUFSIZE];
};
4.主函数
/* Function to start the file server */
esp_err_t start_file_server(const char *base_path)
{
static struct file_server_data *server_data = NULL;
/* Validate file storage base path */
if (!base_path || strcmp(base_path, "/spiffs") != 0) {
ESP_LOGE(TAG, "File server presently supports only '/spiffs' as base path");
return ESP_ERR_INVALID_ARG;
}
if (server_data) {
ESP_LOGE(TAG, "File server already started");
return ESP_ERR_INVALID_STATE;
}
/* Allocate memory for server data */
server_data = calloc(1, sizeof(struct file_server_data));
if (!server_data) {
ESP_LOGE(TAG, "Failed to allocate memory for server data");
return ESP_ERR_NO_MEM;
}
strlcpy(server_data->base_path, base_path,
sizeof(server_data->base_path));
httpd_handle_t server = NULL;
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
/* Use the URI wildcard matching function in order to
* allow the same handler to respond to multiple different
* target URIs which match the wildcard scheme */
config.uri_match_fn = httpd_uri_match_wildcard;
ESP_LOGI(TAG, "Starting HTTP Server");
if (httpd_start(&server, &config) != ESP_OK) {
ESP_LOGE(TAG, "Failed to start file server!");
return ESP_FAIL;
}
/* URI handler for getting uploaded files */
httpd_uri_t file_download = {
.uri = "/*", // Match all URIs of type /path/to/file
.method = HTTP_GET,
.handler = download_get_handler,
.user_ctx = server_data // Pass server data as context
};
httpd_register_uri_handler(server, &file_download);
httpd_uri_t wifi_data = {
.uri = "/wifi_data", // Match all URIs of type /wifi_data/path/to/file
.method = HTTP_POST,
.handler = send_wifi_handler,
.user_ctx = server_data // Pass server data as context
};
httpd_register_uri_handler(server, &wifi_data);
return ESP_OK;
}
至此,我们实现了wifi配置页面的编写及嵌入,可以在手机浏览器上输入192.168.4.1(ESP32默认)