ESP32學(xué)習(xí)筆記(24)——OTA(空中升級)接口使用(原生API)
ESP32學(xué)習(xí)筆記(25)——OTA(空中升級)接口使用(簡化API)
一、概述
ESP32應(yīng)用程序可以在運行時通過Wi-Fi或以太網(wǎng)從特定的服務(wù)器下載新映像,然后將其閃存到某些分區(qū)中,從而進行升級。
在ESP-IDF中有兩種方式可以進行空中(OTA)升級:
- 使用
app_update組件提供的原生API- 使用
esp_https_ota組件提供的簡化API,它在原生OTA API上添加了一個抽象層,以便使用HTTPS協(xié)議進行升級。分別在
native_ota_example和simple_ota_example下的OTA示例中演示了這兩種方法。
1.1 OTA工作流程

1.2 OTA數(shù)據(jù)分區(qū)
ESP32 SPI Flash 內(nèi)有與升級相關(guān)的(至少)四個分區(qū):OTA data、Factory App、OTA_0、OTA_1。其中 FactoryApp 內(nèi)存有出廠時的默認固件。
首次進行 OTA 升級時,OTA Demo 向 OTA_0 分區(qū)燒錄目標(biāo)固件,并在燒錄完成后,更新 OTA data 分區(qū)數(shù)據(jù)并重啟。
系統(tǒng)重啟時獲取 OTA data 分區(qū)數(shù)據(jù)進行計算,決定此后加載 OTA_0 分區(qū)的固件執(zhí)行(而不是默認的 Factory App 分區(qū)內(nèi)的固件),從而實現(xiàn)升級。
同理,若某次升級后 ESP32 已經(jīng)在執(zhí)行 OTA_0 內(nèi)的固件,此時再升級時 OTA Demo 就會向 OTA_1 分區(qū)寫入目標(biāo)固件。再次啟動后,執(zhí)行 OTA_1 分區(qū)實現(xiàn)升級。以此類推,升級的目標(biāo)固件始終在 OTA_0、OTA_1 兩個分區(qū)之間交互燒錄,不會影響到出廠時的 Factory App 固件。

為了簡單起見,OTA示例通過在menuconfig中啟用CONFIG_PARTITION_TABLE_TWO_OTA選項來選擇預(yù)定義的分區(qū)表,該選項支持三個應(yīng)用程序分區(qū):工廠分區(qū)、OTA_0分區(qū)和OTA_1分區(qū)。有關(guān)分區(qū)表的更多信息,請參閱分區(qū)表.
二、API說明
簡化的 API 以通過 HTTPS 執(zhí)行固件升級。它是原生 OTA API 的抽象層。
以下簡化 OTA 接口位于 esp_https_ota/include/esp_https_ota.h。
2.1 esp_https_ota

2.2 esp_https_ota_begin

2.3 esp_https_ota_perform

2.4 esp_https_ota_is_complete_data_received

2.5 esp_https_ota_finish

2.6 esp_https_ota_get_img_desc

2.7 esp_https_ota_get_image_len_read

三、應(yīng)用實例
3.1 OTA詳細過程邏輯

3.2 簡單版
使用 esp-idf\examples\system\ota\simple_ota_example 中的例程
/* OTA example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_ota_ops.h"
#include "esp_http_client.h"
#include "esp_https_ota.h"
#include "protocol_examples_common.h"
#include "string.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "protocol_examples_common.h"
#if CONFIG_EXAMPLE_CONNECT_WIFI
#include "esp_wifi.h"
#endif
static const char *TAG = "simple_ota_example";
extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start");
extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end");
#define OTA_URL_SIZE 256
esp_err_t _http_event_handler(esp_http_client_event_t *evt)
{
switch (evt->event_id) {
case HTTP_EVENT_ERROR:
ESP_LOGD(TAG, "HTTP_EVENT_ERROR");
break;
case HTTP_EVENT_ON_CONNECTED:
ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED");
break;
case HTTP_EVENT_HEADER_SENT:
ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT");
break;
case HTTP_EVENT_ON_HEADER:
ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
break;
case HTTP_EVENT_ON_DATA:
ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
break;
case HTTP_EVENT_ON_FINISH:
ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH");
break;
case HTTP_EVENT_DISCONNECTED:
ESP_LOGD(TAG, "HTTP_EVENT_DISCONNECTED");
break;
}
return ESP_OK;
}
void simple_ota_example_task(void *pvParameter)
{
ESP_LOGI(TAG, "Starting OTA example");
esp_http_client_config_t config = {
.url = CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL,
.cert_pem = (char *)server_cert_pem_start,
.event_handler = _http_event_handler,
};
#ifdef CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN
char url_buf[OTA_URL_SIZE];
if (strcmp(config.url, "FROM_STDIN") == 0) {
example_configure_stdin_stdout();
fgets(url_buf, OTA_URL_SIZE, stdin);
int len = strlen(url_buf);
url_buf[len - 1] = '\0';
config.url = url_buf;
} else {
ESP_LOGE(TAG, "Configuration mismatch: wrong firmware upgrade image url");
abort();
}
#endif
#ifdef CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK
config.skip_cert_common_name_check = true;
#endif
esp_err_t ret = esp_https_ota(&config);
if (ret == ESP_OK) {
esp_restart();
} else {
ESP_LOGE(TAG, "Firmware upgrade failed");
}
while (1) {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void app_main(void)
{
// Initialize NVS.
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
// 1.OTA app partition table has a smaller NVS partition size than the non-OTA
// partition table. This size mismatch may cause NVS initialization to fail.
// 2.NVS partition contains data in new format and cannot be recognized by this version of code.
// If this happens, we erase NVS partition and initialize NVS again.
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
ESP_ERROR_CHECK(err);
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
* Read "Establishing Wi-Fi or Ethernet Connection" section in
* examples/protocols/README.md for more information about this function.
*/
ESP_ERROR_CHECK(example_connect());
#if CONFIG_EXAMPLE_CONNECT_WIFI
/* Ensure to disable any WiFi power save mode, this allows best throughput
* and hence timings for overall OTA operation.
*/
esp_wifi_set_ps(WIFI_PS_NONE);
#endif // CONFIG_EXAMPLE_CONNECT_WIFI
xTaskCreate(&simple_ota_example_task, "ota_example_task", 8192, NULL, 5, NULL);
}
3.3 進階版
如果在 HTTPS OTA 過程中需要更多信息和控制,則可以
使用 esp-idf\examples\system\ota\advanced_https_ota 中的例程
/* Advanced HTTPS OTA example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_ota_ops.h"
#include "esp_http_client.h"
#include "esp_https_ota.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "protocol_examples_common.h"
#if CONFIG_EXAMPLE_CONNECT_WIFI
#include "esp_wifi.h"
#endif
static const char *TAG = "advanced_https_ota_example";
extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start");
extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end");
#define OTA_URL_SIZE 256
static esp_err_t validate_image_header(esp_app_desc_t *new_app_info)
{
if (new_app_info == NULL) {
return ESP_ERR_INVALID_ARG;
}
const esp_partition_t *running = esp_ota_get_running_partition();
esp_app_desc_t running_app_info;
if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK) {
ESP_LOGI(TAG, "Running firmware version: %s", running_app_info.version);
}
#ifndef CONFIG_EXAMPLE_SKIP_VERSION_CHECK
if (memcmp(new_app_info->version, running_app_info.version, sizeof(new_app_info->version)) == 0) {
ESP_LOGW(TAG, "Current running version is the same as a new. We will not continue the update.");
return ESP_FAIL;
}
#endif
return ESP_OK;
}
void advanced_ota_example_task(void *pvParameter)
{
ESP_LOGI(TAG, "Starting Advanced OTA example");
esp_err_t ota_finish_err = ESP_OK;
esp_http_client_config_t config = {
.url = CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL,
.cert_pem = (char *)server_cert_pem_start,
.timeout_ms = CONFIG_EXAMPLE_OTA_RECV_TIMEOUT,
};
#ifdef CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN
char url_buf[OTA_URL_SIZE];
if (strcmp(config.url, "FROM_STDIN") == 0) {
example_configure_stdin_stdout();
fgets(url_buf, OTA_URL_SIZE, stdin);
int len = strlen(url_buf);
url_buf[len - 1] = '\0';
config.url = url_buf;
} else {
ESP_LOGE(TAG, "Configuration mismatch: wrong firmware upgrade image url");
abort();
}
#endif
#ifdef CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK
config.skip_cert_common_name_check = true;
#endif
esp_https_ota_config_t ota_config = {
.http_config = &config,
};
esp_https_ota_handle_t https_ota_handle = NULL;
esp_err_t err = esp_https_ota_begin(&ota_config, &https_ota_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "ESP HTTPS OTA Begin failed");
vTaskDelete(NULL);
}
esp_app_desc_t app_desc;
err = esp_https_ota_get_img_desc(https_ota_handle, &app_desc);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_https_ota_read_img_desc failed");
goto ota_end;
}
err = validate_image_header(&app_desc);
if (err != ESP_OK) {
ESP_LOGE(TAG, "image header verification failed");
goto ota_end;
}
while (1) {
err = esp_https_ota_perform(https_ota_handle);
if (err != ESP_ERR_HTTPS_OTA_IN_PROGRESS) {
break;
}
// esp_https_ota_perform returns after every read operation which gives user the ability to
// monitor the status of OTA upgrade by calling esp_https_ota_get_image_len_read, which gives length of image
// data read so far.
ESP_LOGD(TAG, "Image bytes read: %d", esp_https_ota_get_image_len_read(https_ota_handle));
}
if (esp_https_ota_is_complete_data_received(https_ota_handle) != true) {
// the OTA image was not completely received and user can customise the response to this situation.
ESP_LOGE(TAG, "Complete data was not received.");
}
ota_end:
ota_finish_err = esp_https_ota_finish(https_ota_handle);
if ((err == ESP_OK) && (ota_finish_err == ESP_OK)) {
ESP_LOGI(TAG, "ESP_HTTPS_OTA upgrade successful. Rebooting ...");
vTaskDelay(1000 / portTICK_PERIOD_MS);
esp_restart();
} else {
if (ota_finish_err == ESP_ERR_OTA_VALIDATE_FAILED) {
ESP_LOGE(TAG, "Image validation failed, image is corrupted");
}
ESP_LOGE(TAG, "ESP_HTTPS_OTA upgrade failed %d", ota_finish_err);
vTaskDelete(NULL);
}
}
void app_main(void)
{
// Initialize NVS.
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
// 1.OTA app partition table has a smaller NVS partition size than the non-OTA
// partition table. This size mismatch may cause NVS initialization to fail.
// 2.NVS partition contains data in new format and cannot be recognized by this version of code.
// If this happens, we erase NVS partition and initialize NVS again.
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
ESP_ERROR_CHECK( err );
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
* Read "Establishing Wi-Fi or Ethernet Connection" section in
* examples/protocols/README.md for more information about this function.
*/
ESP_ERROR_CHECK(example_connect());
#if CONFIG_EXAMPLE_CONNECT_WIFI
/* Ensure to disable any WiFi power save mode, this allows best throughput
* and hence timings for overall OTA operation.
*/
esp_wifi_set_ps(WIFI_PS_NONE);
#endif // CONFIG_EXAMPLE_CONNECT_WIFI
xTaskCreate(&advanced_ota_example_task, "advanced_ota_example_task", 1024 * 8, NULL, 5, NULL);
}
四、測試流程
4.1 配置Flash大小
打開項目配置菜單 idf.py menuconfig
選擇串行Flash配置 Serial flasher config --->

根據(jù)模塊Flash大小,更改
Flash size,我使用的是 ESP32-WROVER-B 模組,所以修改為 4 MB。
4.2 配置分區(qū)表
選擇分區(qū)表配置 Partition Table --->

選擇
Factory app, two OTA definitions
4.3 配置服務(wù)器信息
4.3.1 HTTPS類型


4.3.2 HTTP類型

將類型修改為
http


允許 HTTP OTA

在代碼中注釋

4.4 配置連接方式

選擇WIFI連接方式,并修改要連接路由器的SSID和密碼

4.5 配置APP版本號
Note:
native_ota_example中沒有版本號大小檢查,它看到不同的版本就會下載。當(dāng)本地設(shè)備是比OTA服務(wù)器版本號更高的時候,也會下載OTA服務(wù)器的舊版進行更新。這個需要自行添加版本號大小檢查。
4.5.1 通過version.txt控制

4.5.2 通過menuconfig控制
選擇應(yīng)用管理 Application manager --->

勾選
Get the project version from Kconfig 并在下面填寫版本號。
4.6 開啟HTTP服務(wù)器(可選)
編譯鏈內(nèi) Python 有一個內(nèi)置的 HTTP 服務(wù)器,我們這里可以直接使用它。我們將會使用示例 get-started/hello_world 作為需要更新的固件。
4.6.1 生成目標(biāo)固件
使用示例 get-started/hello_world,此處執(zhí)行idf.py build 會生成二進制文件get-started\hello_world\build\hello-world.bin。
4.6.2 創(chuàng)建HTTP服務(wù)器
-
使用工具鏈python創(chuàng)建服務(wù)器
進入存放需要升級的固件(hello-world.bin)目錄。在此處創(chuàng)建HTTP服務(wù)器。
此處執(zhí)行python -m http.server --bind 192.168.61.67 8070會生成一個HTTP本地服務(wù)器。該服務(wù)器下對應(yīng)\build文件夾下所有內(nèi)容。(有的文章執(zhí)行的python命令不同,是由于python版本不同造成的。)
-
使用HFS創(chuàng)建服務(wù)器
鏈接:https://pan.baidu.com/s/1MIAI5m4WQJdAZpqRAdvsXg 提取碼:9m8y
4.7 開啟HTTPS服務(wù)器(可選)
-
Shirt + 鼠標(biāo)右鍵打開Linux shell - 創(chuàng)建一個新的自簽名證書和密鑰
openssl req -x509 -newkey rsa:2048 -keyout ca_key.pem -out ca_cert.pem -days 365 -nodes
依次輸入:(國家)、(洲/省)、(城/鎮(zhèn))、(組織名)、(單位名)、(httpd-ssl.conf中的ServerName 名稱)、(郵箱)
- 將生成的證書
ca_cert.pem復(fù)制到OTA示例目錄中的server_certs目錄下,已存在則替換內(nèi)容
- 創(chuàng)建HTTPS服務(wù)器
openssl s_server -WWW -key ca_key.pem -cert ca_cert.pem -port 8070
4.8 查看打印


? 由 Leung 寫于 2021 年 6 月 15 日
? 參考:ESP32 http OTA 升級實操
第二十章 ESP32的空中升級(OTA)
ESP32 OTA詳解-中文翻譯版


