ESP32-Web-Server编程综合项目1-结合 Web Server 实现 WiFi 配网和网页 OTA 更新

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 Data
    var 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 Request
        var 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 partition
    if(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 successful
        flash_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)下一篇:

(码字不易感谢点赞或收藏)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/214201.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

4R技术(AR、VR、MR、XR)傻傻分不清,看完这篇你就懂了!

在数字化革命的浪潮下&#xff0c;涌现了许多VR、AR和MR产品&#xff0c;尽管大家对VR比较熟悉&#xff0c;但对AR、MR和XR的了解相对较少&#xff0c;这几者同时存在会更令人困惑。下面我们就来了解一下这4种技术的区别。先用一张图来区分它们的区别&#xff1a; 1.虚拟现实技…

inux基础项目开发1:量产工具——业务系统(七)

前言&#xff1a; 前面我们已经构造出来显示系统、输入系统、文字系统、UI系统、页面系统&#xff0c;这个项目百分之八十需要实现的都已经构建出来了&#xff0c;最后让我们对这个项目进行最后一项系统的搭建&#xff0c;也就是业务系统&#xff0c;说到业务大家应该就知道我们…

Python安装步骤介绍

本文将介绍Python安装的详细步骤如下&#xff1a; 下载 python安装 python配置环境变量&#xff08;安装时勾选配置环境变量的则无需此步骤&#xff09; 一、python下载 官网&#xff1a;Download Python | Python.org 根据电脑位数下载所需的版本 二、Python安装 1.打开安…

31-WEB漏洞-文件操作之文件包含漏洞全解

31-WEB漏洞-文件操作之文件包含漏洞全解 一、本地包含1.1、无限制包含漏洞文件1.2、有限制包含漏洞文件1.2.1、绕过方法1.2.1.1、%00截断1.2.1.2、长度截断 二、远程包含2.1、无限制包含漏洞文件2.2、有限制包含漏洞文件 三、各种协议流提交流3.1、各协议的利用条件和方法3.1.1…

mysql的几种索引

mysql可以在表的一列、或者多列上创建索引&#xff0c;索引的类型可以选择&#xff0c;如下&#xff1a; 普通索引&#xff08;KEY&#xff09; 普通索引可以提高查询效率。在表的一列、或者多列上创建索引。 每个表可以创建多个普通索引。 例如&#xff0c;下面示例&#…

计算机网络TCP篇②

一、TCP 重传、滑动窗口、流量控制、拥塞控制 1.1、重传机制 在 TCP 中&#xff0c;当发送端的数据达到接受主机时&#xff0c;接收端主机会返回一个确认应答消息&#xff0c;表示已收到消息。但是在复杂的网络中&#xff0c;并一定能顺利正常的进行数据传输&#xff0c;&…

固态硬盘与机械硬盘的区别

盘、磁道、扇区、柱面&#xff0c;这些都是机械硬盘的概念&#xff0c;固态硬盘没有这些东西&#xff0c;固态硬盘和机械硬盘虽然都叫硬盘&#xff0c;但是在原理层面有着本质上的区别。 速印机&#xff08;理想、荣大等&#xff09;、复印机&#xff08;夏普、东芝、理光、佳能…

51爱心流水灯32灯炫酷代码

源代码摘自远眺883的文章&#xff0c;大佬是30个灯的&#xff0c;感兴趣的铁汁们可以去看看哦~&#xff08;已取得原作者的许可&#xff09;&#xff1a;基于STC89C51单片机设计的心形流水灯软件代码部分_单片机流水灯代码_远眺883的博客-CSDN博客 由于博主是个小菜鸡&#xff…

perl脚本批量处理代码中的中文注释乱码的问题

代码中统一使用utf-8编码是最好的&#xff0c;但是有一些多人合作的项目或者一些历史遗留代码&#xff0c;常见一些中文注释乱码的问题。这里以一个开源项目evpp为例子 evpp。以项目中的一个commit id为例&#xff1a; 477033f938fd47dfecde43c82257cd286d9fa38e &#xff0c; …

种群和种群之间连接的设计

我们知道神经元的创建方式是以种群为基础的&#xff0c;一个种群内的所有神经元的参数都一样&#xff0c;而种群与种群之间的连接也是随机概率的。所以我们首先应该设计一个Population的结构&#xff0c;考虑其需要的元素有神经元gid集合和种群好&#xff0c;所设计数据结构如下…

Python必备工具shelve与dbm全面解析!

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 当涉及存储大量数据并且需要高效访问时&#xff0c;Python开发人员常常寻找适当的工具。shelve和dbm模块是Python中用于本地持久化存储数据的两个强大工具。它们允许开发人员以键值对的形式存储数据&#xff0c;…

【开源】基于JAVA的医院门诊预约挂号系统

项目编号&#xff1a; S 033 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S033&#xff0c;文末获取源码。} 项目编号&#xff1a;S033&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 功能性需求2.1.1 数据中心模块2.1.2…

Web前端JS如何控制 Video/Audio 视音频声道(左右声道|多声道)、视音频轨道、音频流数据

写在前面&#xff1a; 接上篇博文&#xff1a;Web前端JS如何获取 Video/Audio 视音频声道(左右声道|多声道)、视音频轨道、音频流数据 讲解了如何根据视频链接地址&#xff0c;实现在播放时实时的显示该视频的音频轨道情况&#xff0c;并实时的将各音频轨道数据以可视化&#x…

使用OpenMVS重建模型

1、数据格式转换 首先将生成的稠密点云以及图片信息转换成openmvs支持的.mvs文件。在openmvs_sample中的bin文件内打开终端 作者&#xff1a;舞曲的小水瓶 https://www.bilibili.com/read/cv25019877/ 出处&#xff1a;bilibili interfaceCOLMAP.exe -i D:\desktop\test\toy\…

28.线段树与树状数组基础

一、线段树 1.区间问题 线段树是一种在算法竞赛中常用来维护区间的数据结构。它思想非常简单&#xff0c;就是借助二叉树的结构进行分治&#xff0c;但它的功能却非常强大&#xff0c;因此在很多类型的题目中都有它的变种&#xff0c;很多题目都需要以线段树为基础进行发展。…

【RotorS仿真系列】Ardrone模型介绍

ardrone是rotors仿真框架提供的一款机型&#xff0c;因为该机型与我们实际使用的机型参数相近&#xff0c;所以这里对它的参数做特别整理和记录。 一、模型参数总结 ardrone的gazebo模型如下图所示&#xff1a; 根据ardrone.yaml&#xff0c;其关键参数如下所示&#xff1a…

基于YOLOv7算法的的高精度实时通用目标检测识别系统(PyTorch+Pyside6+YOLOv7)

摘要&#xff1a;基于YOLOv7算法的高精度实时检测识别系统可用于日常生活中检测与定位多种目标&#xff0c;此系统可完成对输入图片、视频、文件夹以及摄像头方式的目标检测与识别&#xff0c;同时本系统还支持检测结果可视化与导出。本系统采用YOLOv7目标检测算法来训练数据集…

Python继承的设计及演化

Python中的继承 文章目录 Python中的继承概念明确MRO深度优先算法&#xff08;Python2.2之前及Python2.2的经典类中使用&#xff09;优化版的深度优先算法&#xff08;只在Python2.2版本的新式类中使用&#xff09;广度优先算法&#xff08;Python任何版本都从未使用过&#xf…

Difference between getc(), getchar(), and gets()

getc(): 从输入中只能读单个字符 getchar()&#xff1a;从标准输入流中输入都单个字符。 两者基本等同&#xff0c;唯一不一样的是getc()是任何输入流&#xff0c;而getchar()是标准输入流。 gets:可以读入含有空格的字符串 // Example for getc() in C #include <stdio.h…

【数电笔记】06-码制

目录 说明&#xff1a; 二进制代码 1. 二 - 十进制码 2. 常用二 - 十进制代码表 2.1 例题 可靠性代码 1. 格雷码 2. 奇偶校验码 3. 8421奇偶校验码表 说明&#xff1a; 笔记配套视频来源&#xff1a;B站&#xff1b;本系列笔记并未记录所有章节&#xff0c;只对个人认…