ESP32-Web-Server编程综合项目1-结合 Web Server 实现 WiFi 配网和网页 OTA 更新
概述
前述的内容多是一个个小功能的演示,本章节讲述一些实际项目中使用到的综合项目。
首先要讲述的案例是通过ESP32 上的 Web Server 实现对 ESP32 的 WiFi 配网和网页 OTA 更新功能。
需求及功能解析
项目的主要功能有:
- 通过菜单控制多网页的切换
- 在多网页中分别实现 WiFi 配网、控制设备重启、通过网页下发 OTA 更新需要的新固件的功能。
WiFi 配网
当用户初次使用设备时,设备完全不知道要连接的路由器信息,此时可以通过建立一个 SoftAP (什么是 SoftAP 参考:AP、STA的概念以及AP+STA的实现),让用户向连接路由器一样连接该默认的 AP,然后打开配网网页,让 ESP32 连接指定的路由器,最终使得 ESP32 设备能够正常上网。
设备重启
设备配网信息下发后,可以通过网页或者设备的按钮重启设备(重新上电也行),设备将在重启后检测到已经下发的配网信息(网络名称和密码),然后使用该配网信息进行联网。
此外,当设备 OTA 结束时,也需要设备重启以加载新的固件。
OTA 更新
OTA 是更新设备固件的技术,可以认为是通过网络的方式传输固件进行软件更新的一种方法。
在路由器设计中,经常看到路由器的本地网页上支持通过网页上的输入框选中一个新的固件下发给路由器,对路由器的固件进行更新。
示例解析
目录结构
├── CMakeLists.txt
├── main
│ ├── CMakeLists.txt
│ └── main.c User application
├── components
│ └── fs_image└── index.html└── ...
| └── url_handlers└── url_handlers.c└── ...
└── README.md This is the file you are currently reading
- 目录结构主要包含主目录 main,以及组件目录 components.
- 其中组件目录components中包含了用于存储网页文件的 fs_image 目录(即前端文件)。
前端代码
多网页菜单设计
在 components/fs_image/web_image/index.html
中设计三个子菜单:wifimanager、ota、Home:
<div class="topnav-right"><a href="wifimanager">WiFi Manager</a><a href="ota">OTA</a><a href="/">Home</a>
</div>
当点击对应的 href 时,浏览器会向 ESP32 Web Server 发送对该 href 的 Get 请求。
SoftAP 配网界面
在 components/fs_image/web_image/wifimanager_softap.html
中设计 SoftAP 配网的界面:
<form action="/wifi_config" method="POST"><p><label for="ssid">SSID</label><input type="text" id ="ssid" name="ssid"><br><label for="pass">Password</label><input type="text" id ="pass" name="pass"><br><label for="ip">IP Address</label><input type="text" id ="ip" name="ip" value="192.168.1.xxx"><input type ="submit" value ="Submit"></p>
</form>
上述配网界面非常简单,提供了输入路由器 WiFi 名称和密码的输入框。
当用户输入 WiFi 名称和密码后,将通过 http 的 POST 方法向 /wifi_config 推送配网信息。
WiFi Manager 界面
在配网成功,设备连接路由器后,我们可以像往常一样。让手机或者电脑连接到同一个路由器,然后通过局域网打开 ESP32 设备上的控制网页。
在 components/fs_image/web_image/wifimanager.html
界面,我们可以重新下发设备要连接的路由器的信息:
<form action="/wifi_config" method="POST"><p><label for="ssid">SSID</label><input type="text" id ="ssid" name="ssid"><br><label for="pass">Password</label><input type="text" id ="pass" name="pass"><br><!-- <label for="ip">IP Address</label><input type="text" id ="ip" name="ip" value="192.168.1.200"> --><input type ="submit" value ="Submit"></p>
</form>
OTA 更新界面
可以通过子菜单跳转到 OTA 更新的网页。
在 components/fs_image/web_image/ota.html
中,建立了一个 input file 的输入框。,并设置了一个 submit 按钮:
<h2>ESP32 Firmware Update Page</h2><h4 id="latest_firmware"></h4><input type="file" id="selectedFile" accept=".bin" style="display: none;" onchange="myFunction()" />
<input type="button" value="Browse..." onclick="document.getElementById('selectedFile').click();" />
<h3 id="file_info"></h3>
<input type='submit' id="upload_button" value='Update Firmware' onclick="updateFirmware()"><br>
<p>
<button onclick="rebootButton()">Reboot</button>
</p>
选中文件后,通过ota.html
中的 updateFirmware()
实现向 /update
以 POST 方法推送新固件数据:
function updateFirmware() {// Form Datavar formData = new FormData();var fileSelect = document.getElementById("selectedFile");if (fileSelect.files && fileSelect.files.length == 1) {var file = fileSelect.files[0];formData.set("file", file, file.name);document.getElementById("status").innerHTML = "Uploading " + file.name + " , Please Wait...";// Http Requestvar request = new XMLHttpRequest();request.upload.addEventListener("progress", updateProgress);request.open('POST', "/update");request.responseType = "blob";request.send(formData);} else {window.alert('Select A File First')}
}
此外,为了反映 OTA 更新过程的进度,在 ota.html
中的 script 中添加了更新进度的函数:
// progress on transfers from the server to the client (downloads)
function updateProgress(oEvent) {if (oEvent.lengthComputable) {getstatus();} else {window.alert('total size is unknown')}
}
后端代码
后端代码除建立前述基于 spiffs 的 Web Server 外,重点是设计对应前端文件的各个 URL 的 handlers.
这里的 URL 分为两组,一组是还未进行 WiFi 配网(比如设备刚出厂,用户首次使用时)时手机通过连接 ESP32 的 SoftAP 时打开的网页 httpd_uri_array_softap_only[]
、一组是配网后,ESP32 设备连接到路由器后,用户通过手机或者电脑连接至同一个路由器时通过局域网打开的网页httpd_uri_array[]
:
httpd_uri_t httpd_uri_array_softap_only[] = {{"/wifi_config", HTTP_POST, wifi_config_post_handler, rest_context},{"/*", HTTP_GET, softap_wifi_html_get_handler,rest_context}};httpd_uri_t httpd_uri_array[] = {{"/wifimanager", HTTP_GET, wifi_manage_html_get_handler, rest_context},{"/ota", HTTP_GET, ota_html_get_handler, rest_context},{"/wifi_config", HTTP_POST, wifi_config_post_handler, rest_context},{"/update", HTTP_POST, OTA_update_post_handler, rest_context},{"/status", HTTP_POST, OTA_update_status_handler, rest_context},{"/reboot", HTTP_GET, reboot_html_get_handler, rest_context},{"/*", HTTP_GET, rest_common_get_handler,rest_context}//Catch-all callback function for the filesystem, this must be set to the array last one
};if (!s_sta_connected) { // 首次配网时uris = httpd_uri_array_softap_only;uris_len = sizeof(httpd_uri_array_softap_only)/sizeof(httpd_uri_t);
} else { // 已经配过网时uris = httpd_uri_array;uris_len = sizeof(httpd_uri_array)/sizeof(httpd_uri_t);
}for(int i = 0; i < uris_len; i++){if (httpd_register_uri_handler(server, &uris[i]) != ESP_OK) {ESP_LOGE(TAG, "httpd register uri_array[%d] fail", i);}
}
配网处理
配网处理在 main/main.c
中的 wifi_config_post_handler()
中,接收网页端 POST 的路由器 WiFi 的名称和密码,并使用 wifi_config_store()
将其存储在设备上。设备重新上电后将检测到已经存储了WiFi 的名称和密码,然后触发向该路由器的 WiFi 连接。
if (recv_post_data(req, buf) != ESP_OK) {ESP_LOGE(TAG, "recv post data error");return ESP_FAIL;
}str_len = httpd_find_arg(buf, PARAM_INPUT_1, temp_str, sizeof(temp_str));
if ((str_len != -1) && (strlen((char *)temp_str) != 0)) {// snprintf((char *)wifi_config.sta.ssid, 32, "%s", temp_str);memcpy((char *)wifi_config.sta.ssid, temp_str, 32);ESP_LOGI(TAG, "ssid:%s", (char *)wifi_config.sta.ssid);
}memset(temp_str, '\0', sizeof(temp_str));str_len = httpd_find_arg(buf, PARAM_INPUT_2, temp_str, sizeof(temp_str));
if ((str_len != -1) && (strlen((char *)temp_str) != 0)) {memcpy((char *)wifi_config.sta.password, temp_str, 64);ESP_LOGI(TAG, "pwd:%s", (char *)wifi_config.sta.password);
}
if(!wifi_config_store(&wifi_config)) {return ESP_FAIL;
}
关于 WiFi Manager
WiFI 管理主要是负责管理 WiFi 的工作模式,WiFi 配网信息。
默认情况下,设备将建立一个名称为 IoT_Old_Wang
、密码为 123456789
、IP 地址默认为 192.168.4.1
的 SoftAP,用户可以通过手机或者电脑搜索该热点,然后连接到该默认 AP,并使用浏览器打开 192.168.4.1 网址,查看配网界面。
如果需要更改默认 SoftAP 的信息,可以在编译程序时通过 idf.py menuconfig
打开配置菜单配置下述信息:
OTA 的后端处理
OTA 更新的后端处理在 main/web_ota.c
中,在 OTA_update_post_handler()
内,主要是接收从网页下发的固件数据:
if (esp_ota_end(ota_handle) == ESP_OK)
{// Lets update the partitionif(esp_ota_set_boot_partition(update_partition) == ESP_OK) {const esp_partition_t *boot_partition = esp_ota_get_boot_partition();// Webpage will request status when complete // This is to let it know it was successfulflash_status = 1;ESP_LOGI("OTA", "Next boot partition subtype %d at offset 0x%x", boot_partition->subtype, boot_partition->address);ESP_LOGI("OTA", "Please Restart System...");}else{ESP_LOGI("OTA", "\r\n\r\n !!! Flashed Error !!!");}
}
接收完固件将设置标志位 flash_status=1
,当网页端重新请求固件的 status 时,将在 OTA_update_status_handler()
中触发创建一个定时重启设备的定时器:
if (flash_status == 1)
{// We cannot directly call reboot here because we need the // browser to get the ack back. Send message to another task or create a create_a_restart_timer();// xEventGroupSetBits(reboot_event_group, REBOOT_BIT);
}
示例效果
连接 SoftAP 执行配网
下图分别为连接对应热点,浏览器输入 192.168.4.1
打开配网界面:
注意:
-
AP 模式下打不开网页就关闭手机的其他网络通路,比如 4G 网络。
-
AP 配网(这里配网成功可以通过 GPIO 控制 LED灯闪烁来提示用户,或者在网页端跳出弹窗提示配置成功)示例这里以 Log 提示用户。
重启设备后,连接电脑到同一个路由器,打开多网页菜单
设备连接到路由器后,登录路由器查看 ESP32 设备的 IP 地址,如下该设备的 IP 地址为 192.168.47.100
:
打开 wifi manager 界面
点击菜单栏的 WiFi Manager 打开wifi manager 界面:
打开 OTA 界面
点击菜单栏的 OTA 打开 OTA 界面:
OTA 完成后重启设备
点击 OTA 更新界面的 Browse
按钮选择需要上传的 OTA 新固件,然后点击 Updata Firmware,开始 OTA 更新。OTA 更新后可以重启设备以便于在下次重启设备时加载新的固件。
流程示意图:
讨论
1)示例程序中有 version.txt 文件,该文件可以用于控制固件的版本信息。读者可以更改该文件的内容,记录固件的版本信息。
2)示例使用的 ota APIs 可以参考 ESP-IDF OTA 开发指南。
总结
1)本节主要是介绍基于 ESP-IDF 的原始 API,实现综合项目1- 通过Web Server 实现 WiFi 配网和网页 OTA 更新。
2)示例设计了多网页子菜单,实现管理 WiFi 配网、OTA 固件更新、设备重启的功能。更多综合项目敬请期待。
资源链接
1)ESP32-Web-Server ESP-IDF系列博客介绍
2)对应示例的 code 链接 (点击直达代码仓库)
3)下一篇:
(码字不易感谢点赞或收藏)