ESP32cam系列教程003:ESP32cam实现远程 HTTP_OTA 自动升级
本教程是 ESP32cam 的系列教程之三,使用 Arduino IDE 对 ESP32cam 开发板进行开发。
本教程代码同样使用与其他 ESP32 开发板。
1.什么是 OTA
OTA 即空中下载技术(Over-the-Air Technology),其可以安全方便地升级设备的固件或软件。远程升级还可以大大降低成本,节省资源,它已成为物联网设备和产品制造商的关键技术之一。
ESP32 开发板支持 3 种 OTA 方式:
- Arduino IDE :主要用于软件开发阶段,实现不接线固件烧录
- Web_OTA:通过 Web 浏览器手动提供应用程序更新模块
- HTTP_OTA:固件存放到 http 服务器端,设备自动判断是否需要联网下载固件升级
本文主要介绍:HTTP_OTA 的原理与实现。
2. ESP32cam HTTP_OTA 本地准备
2.1 HTTP OTA 升级原理
- 本地程序在开机连接 WIFI 后发送 http 请求获取远程服务器中的升级 json 文件。
- 通过对比 json 中的远程版本信息与本地的版本信息判断是否一致。
- 若远程版本信息与本地版本不一致,则本地需要更新程序。
- 通过 json 中的版本信息在远程服务器中拉取需要更新的程序的
.bin
文件。 - 依据下载下来的
.bin
自动完成版本的升级,然后自动重启开发板。 - 重复第一步获取远程 json 文件判断是否需要更新。
2.2 开发板本地基准程序(程序版本:1_0_0)
本地 1_0_0 版本程序主要内容如下:
- 当前版本(非常重要,升级依据)
- 远程升级的 json 链接与远程固件的文件夹链接
- 获取并解析 json 的函数 httpGETRequest
- 依据 json 判断是否需要更新的函数 isOrNotNeedUpdate
- 以及其他基础信息组成
#include <WiFi.h>
#include <HTTPClient.h>
#include <ESP32httpUpdate.h>
#include <Arduino_JSON.h>
/**********根据实际修改**********/
const char* wifi_ssid = "TP-LINK_1760"; // WIFI名称,区分大小写,不要写错
const char* wifi_password = "987654321"; // WIFI密码
// 特别重要,升级依据!!!
// 设置当前代码版本 格式 1_0_0
char* version = "1_0_0";
//远程固件链接,只支持http
const char* baseUpdateUrl = "http://example.cn/esp32/";
const char* updateJson = "http://example.cn/esp32/esp32_update.json";
// esp32_update.json
// {
// "version":"1_0_1"
// }
/**********根据实际修改**********/
int need_ota_update = 0;
int i = 0;
String jsonBuffer;
// 获取远程 json 升级文件
String httpGETRequest(const char* serverName) {
WiFiClient client;
HTTPClient http;
String payload = "";
//连接目标网址
http.begin(client, serverName);
//发送HTTP站点请求
int httpCode = http.GET();
if (httpCode > 0) {
Serial.printf("[HTTP] GET... code: %d\n", httpCode);
payload = http.getString();
} else {
Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end(); //关闭连接
//返回获得的数据用于Json处理
return payload;
}
// 依据json文件中版本号与本地版本号,判断是否需要进行更新
void isOrNotNeedUpdate(){
// 获取远程的升级 json ,判断内部版本与本地是否相同,判断是否需要升级
jsonBuffer = httpGETRequest(updateJson);
Serial.println(jsonBuffer);
//将解析的Json对象值储存在Jsonu缓冲区中
JSONVar myObject = JSON.parse(jsonBuffer);
Serial.println(myObject);
// Serial.println(myObject["version"]);
const char* ota_version = myObject["version"];
// Serial.println(ota_version);
Serial.println("---");
Serial.print("远程版本: ");
Serial.println(ota_version);
Serial.print("本地版本: ");
Serial.println(version);
// char * 与 const char * 比较
// 判断远程版本与本地版本是否相同
if (String(version) == String(ota_version)) {
need_ota_update = 0;
Serial.println("无需升级。。。");
} else {
need_ota_update = 1;
Serial.println("需要升级。。。");
Serial.print("OTA 升级地址为:");
// 升级的完整链接, 例如:http://example.cn/esp32/esp32_1_0_1.bin
String fullUpdateUrl = String(baseUpdateUrl) + "esp32_" + ota_version + ".bin";
Serial.println(String(fullUpdateUrl));
// 获取远程 bin 文件进行升级
t_httpUpdate_return ret = ESPhttpUpdate.update(fullUpdateUrl);
Serial.println(ret);
switch (ret) {
case HTTP_UPDATE_FAILED:
Serial.printf("HTTP_UPDATE_FAILED Error (%d): %s\n", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str());
break;
case HTTP_UPDATE_NO_UPDATES:
Serial.println("HTTP_UPDATE_NO_UPDATES");
break;
case HTTP_UPDATE_OK:
Serial.println("HTTP_UPDATE_OK");
break;
default:
Serial.println(ret);
}
// version=(char *)ota_version;
}
need_ota_update = 0;
}
void setup() {
Serial.begin(115200); //波特率115200
Serial.print("Connection WIFI");
WiFi.begin(wifi_ssid, wifi_password); //连接wifi
while (WiFi.status() != WL_CONNECTED) { //等待连接wifi
delay(500);
Serial.print(".");
}
Serial.println("");
// 调用判断是否需要升级函数
isOrNotNeedUpdate();
}
void loop() {
// 主程序
Serial.println(i);
i++;
delay(2000);
}
2.3 开发板升级程序(程序版本:1_0_1)
本测试升级程序如下,仅仅在程序版本与主程序中做了调整,以便更清楚的看出是否OTA升级成功。
#include <WiFi.h>
#include <HTTPClient.h>
#include <ESP32httpUpdate.h>
#include <Arduino_JSON.h>
/**********根据实际修改**********/
const char* wifi_ssid = "TP-LINK_1760"; // WIFI名称,区分大小写,不要写错
const char* wifi_password = "987654321"; // WIFI密码
// 特别重要,升级依据!!!
// 设置当前代码版本 格式 1_0_0
char* version = "1_0_1";
//远程固件链接,只支持http
const char* baseUpdateUrl = "http://example.cn/esp32/";
const char* updateJson = "http://example.cn/esp32/esp32_update.json";
// esp32_update.json
// {
// "version":"1_0_1"
// }
/**********根据实际修改**********/
int need_ota_update = 0;
int i = 0;
String jsonBuffer;
// 获取远程 json 升级文件
String httpGETRequest(const char* serverName) {
WiFiClient client;
HTTPClient http;
String payload = "";
//连接目标网址
http.begin(client, serverName);
//发送HTTP站点请求
int httpCode = http.GET();
if (httpCode > 0) {
Serial.printf("[HTTP] GET... code: %d\n", httpCode);
payload = http.getString();
} else {
Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end(); //关闭连接
//返回获得的数据用于Json处理
return payload;
}
// 依据json文件中版本号与本地版本号,判断是否需要进行更新
void isOrNotNeedUpdate(){
// 获取远程的升级 json ,判断内部版本与本地是否相同,判断是否需要升级
jsonBuffer = httpGETRequest(updateJson);
Serial.println(jsonBuffer);
//将解析的Json对象值储存在Jsonu缓冲区中
JSONVar myObject = JSON.parse(jsonBuffer);
Serial.println(myObject);
// Serial.println(myObject["version"]);
const char* ota_version = myObject["version"];
// Serial.println(ota_version);
Serial.println("---");
Serial.print("远程版本: ");
Serial.println(ota_version);
Serial.print("本地版本: ");
Serial.println(version);
// char * 与 const char * 比较
// 判断远程版本与本地版本是否相同
if (String(version) == String(ota_version)) {
need_ota_update = 0;
Serial.println("无需升级。。。");
} else {
need_ota_update = 1;
Serial.println("需要升级。。。");
Serial.print("OTA 升级地址为:");
// 升级的完整链接, 例如:http://example.cn/esp32/esp32_1_0_1.bin
String fullUpdateUrl = String(baseUpdateUrl) + "esp32_" + ota_version + ".bin";
Serial.println(String(fullUpdateUrl));
// 获取远程 bin 文件进行升级
t_httpUpdate_return ret = ESPhttpUpdate.update(fullUpdateUrl);
Serial.println(ret);
switch (ret) {
case HTTP_UPDATE_FAILED:
Serial.printf("HTTP_UPDATE_FAILED Error (%d): %s\n", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str());
break;
case HTTP_UPDATE_NO_UPDATES:
Serial.println("HTTP_UPDATE_NO_UPDATES");
break;
case HTTP_UPDATE_OK:
Serial.println("HTTP_UPDATE_OK");
break;
default:
Serial.println(ret);
}
// version=(char *)ota_version;
}
need_ota_update = 0;
}
void setup() {
Serial.begin(115200); //波特率115200
Serial.print("Connection WIFI");
WiFi.begin(wifi_ssid, wifi_password); //连接wifi
while (WiFi.status() != WL_CONNECTED) { //等待连接wifi
delay(500);
Serial.print(".");
}
Serial.println("");
// 调用判断是否需要升级函数
isOrNotNeedUpdate();
}
void loop() {
// 主程序
Serial.println(i);
Serial.println("OTA 升级成功");
i++;
delay(2000);
}
2.4 本地 HTTP_OTA 升级测试
2.4.1 本地运行一个 HTTP 服务
这里使用 vscode 进行:
- 用 vscode 打开一个空白文件夹
- 在文件夹中新建目录
esp32
,文件index.html
,目录下新建文件esp32_update.json
esp32_update.json
中内容是{"version":"1_0_1"}
,表明当前远程的版本为1_0_1
index.html
中为标准html结构文件- 在
index.html
界面中右键>Open with Live Server 打开
- 替换
127.0.0.1
为本地192.168.1.XXX
并拼接/esp32/esp32_update.json
,如下图所示
2.4.2 替换远程链接并将要升级的程序打包成 .bin
文件
-
将 arduino IDE 中的程序的远程链接替换成本地 HTTP 服务器链接
-
工具中 开发板和
Partition Scheme
选择如下图:
-
项目中选择导出已编译的二进制文件,导出的二进制文件在同级目录下。
-
将导出的
.bin
文件重命名为esp32_1_0_x.bin
样式,并复制到 2.4.1 节中的 esp32目录中,保证使用http://192.168.1.x/esp32/esp32_1_0_x.bin
能够下载到该文件。
2.4.3 替换远程链接并烧录基准程序(版本为:1_0_0 的程序)测试升级是否成功
- 将 arduino IDE 中的程序的远程链接替换成本地 HTTP 服务器链接
- 将 2.4.1 节中的
esp32_update.json
内部版本改为 1_0_0 ,保证一开始不升级。 - 将程序烧录进 esp32 开发板中。然后打开串口监视器
- 串口调试器中显示不需要升级
- 将 2.4.1 节中的
esp32_update.json
内部版本改为 1_0_1 ,然后重启开发板。
- 由上图可知,开发板自动判断是否需要升级并自动OTA升级成功。
3. HTTP_OTA 升级展望
3.1 后期版本更新可通过 HTTP_OTA 实现
通过第二节可知,可以通过 HTTP_OTA 实现 esp32 开发板的隔空升级,这样可以在一台设备上测试好了程序后,上传 .bin
文件到第 2.4.1 节中的 HTTP 服务器文件夹中,实现其他开发板批量升级。
3.2 借助网络云平台实现远程 HTTP_OTA 升级
第 2.4 节是使用本地 HTTP 服务器进行升级的,我们也可以使用云服务厂商的对象云存储服务,将需要升级的 .bin
文件与 esp32_update.json
放到云服务厂商的对象云存储服务中,使用提供的公网域名替换程序代码中的远程固件连接,真正实现远程 OTA 快速自动升级服务。
注意:
- 本地 HTTP_OTA 升级时,本机电脑需要和 esp32 开发板连在同一个网络下,否则 esp32 开发板无法访问固件地址。
- 使用云服务厂商的对象云存储服务,对象云存储需要设置禁止缓存,否则可能会获取之前缓存的版本而不是最新版,导致不必要的错误。