本教程是 ESP32cam 的系列教程之三,使用 Arduino IDE 对 ESP32cam 开发板进行开发。
本教程代码同样使用与其他 ESP32 开发板。

1.什么是 OTA

OTA 即空中下载技术(Over-the-Air Technology),其可以安全方便地升级设备的固件或软件。远程升级还可以大大降低成本,节省资源,它已成为物联网设备和产品制造商的关键技术之一。

ESP32 开发板支持 3 种 OTA 方式:

  1. Arduino IDE :主要用于软件开发阶段,实现不接线固件烧录
  2. Web_OTA:通过 Web 浏览器手动提供应用程序更新模块
  3. HTTP_OTA:固件存放到 http 服务器端,设备自动判断是否需要联网下载固件升级

本文主要介绍:HTTP_OTA 的原理与实现。

2. ESP32cam HTTP_OTA 本地准备


2.1 HTTP OTA 升级原理

  1. 本地程序在开机连接 WIFI 后发送 http 请求获取远程服务器中的升级 json 文件。
  2. 通过对比 json 中的远程版本信息与本地的版本信息判断是否一致。
  3. 若远程版本信息与本地版本不一致,则本地需要更新程序。
  4. 通过 json 中的版本信息在远程服务器中拉取需要更新的程序的 .bin 文件。
  5. 依据下载下来的 .bin 自动完成版本的升级,然后自动重启开发板。
  6. 重复第一步获取远程 json 文件判断是否需要更新。

2.2 开发板本地基准程序(程序版本:1_0_0)

本地 1_0_0 版本程序主要内容如下:

  1. 当前版本(非常重要,升级依据)
  2. 远程升级的 json 链接与远程固件的文件夹链接
  3. 获取并解析 json 的函数 httpGETRequest
  4. 依据 json 判断是否需要更新的函数 isOrNotNeedUpdate
  5. 以及其他基础信息组成
#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 进行:

  1. 用 vscode 打开一个空白文件夹
  2. 在文件夹中新建目录 esp32,文件 index.html ,目录下新建文件 esp32_update.json
  3. esp32_update.json 中内容是 {"version":"1_0_1"} ,表明当前远程的版本为 1_0_1
  4. index.html 中为标准html结构文件
  5. index.html 界面中右键>Open with Live Server 打开
  1. 替换 127.0.0.1 为本地 192.168.1.XXX 并拼接 /esp32/esp32_update.json ,如下图所示

2.4.2 替换远程链接并将要升级的程序打包成 .bin 文件

  1. 将 arduino IDE 中的程序的远程链接替换成本地 HTTP 服务器链接

  2. 工具中 开发板和 Partition Scheme 选择如下图:

  3. 项目中选择导出已编译的二进制文件,导出的二进制文件在同级目录下。

  4. 将导出的 .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 的程序)测试升级是否成功

  1. 将 arduino IDE 中的程序的远程链接替换成本地 HTTP 服务器链接
  2. 将 2.4.1 节中的 esp32_update.json 内部版本改为 1_0_0 ,保证一开始不升级。
  3. 将程序烧录进 esp32 开发板中。然后打开串口监视器
  4. 串口调试器中显示不需要升级
  5. 将 2.4.1 节中的 esp32_update.json 内部版本改为 1_0_1 ,然后重启开发板。
  6. 由上图可知,开发板自动判断是否需要升级并自动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 快速自动升级服务。

注意:

  1. 本地 HTTP_OTA 升级时,本机电脑需要和 esp32 开发板连在同一个网络下,否则 esp32 开发板无法访问固件地址。
  2. 使用云服务厂商的对象云存储服务,对象云存储需要设置禁止缓存,否则可能会获取之前缓存的版本而不是最新版,导致不必要的错误。