ESP32 OTA(空中升级)接口使用(简化API)

ESP32应用程序可以在运行时通过Wi-Fi或以太网从特定的服务器下载新映像,然后将其闪存到某些分区中,从而进行升级。

一、概述

ESP32应用程序可以在运行时通过Wi-Fi或以太网从特定的服务器下载新映像,然后将其闪存到某些分区中,从而进行升级。

在ESP-IDF中有两种方式可以进行空中(OTA)升级:

  • 使用 app_update 组件提供的原生API
  • 使用 esp_https_ota 组件提供的简化API,它在原生OTA API上添加了一个抽象层,以便使用HTTPS协议进行升级。

分别在 native_ota_example 和 simple_ota_example 下的OTA示例中演示了这两种方法。

1.1 OTA工作流程


1.2 OTA数据分区

ESP32 SPI Flash 内有与升级相关的(至少)四个分区:OTA dataFactory AppOTA_0OTA_1。其中 FactoryApp 内存有出厂时的默认固件。

首次进行 OTA 升级时,OTA Demo 向 OTA_0 分区烧录目标固件,并在烧录完成后,更新 OTA data 分区数据并重启。

系统重启时获取 OTA data 分区数据进行计算,决定此后加载 OTA_0 分区的固件执行(而不是默认的 Factory App 分区内的固件),从而实现升级。

同理,若某次升级后 ESP32 已经在执行 OTA_0 内的固件,此时再升级时 OTA Demo 就会向 OTA_1 分区写入目标固件。再次启动后,执行 OTA_1 分区实现升级。以此类推,升级的目标固件始终在 OTA_0OTA_1 两个分区之间交互烧录,不会影响到出厂时的 Factory App 固件。

为了简单起见,OTA示例通过在menuconfig中启用CONFIG_PARTITION_TABLE_TWO_OTA选项来选择预定义的分区表,该选项支持三个应用程序分区:工厂分区、OTA_0分区和OTA_1分区。有关分区表的更多信息,请参阅分区表.

二、API说明

简化的 API 以通过 HTTPS 执行固件升级。它是原生 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

三、应用实例

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 --->

根据模块Flash大小,更改 Flash size,我使用的是  ESP32-WROVER-B 模组,所以修改为 4 MB。

4.2 配置分区表

选择分区表配置 Partition Table --->

选择 Factory app, two OTA definitions


4.3 配置服务器信息

4.3.1 HTTPS类型

4.3.2 HTTP类型

将类型修改为 http

允许 HTTP OTA

在代码中注释 

4.4 配置连接方式

选择WIFI连接方式,并修改要连接路由器的SSID和密码

4.5 配置APP版本号

Note: native_ota_example 中没有版本号大小检查,它看到不同的版本就会下载。当本地设备是比OTA服务器版本号更高的时候,也会下载OTA服务器的旧版进行更新。这个需要自行添加版本号大小检查。

4.5.1 通过version.txt控制

4.5.2 通过menuconfig控制

选择应用管理 Application manager --->

勾选 Get the project version from Kconfig 并在下面填写版本号。

4.6 开启HTTP服务器(可选)

编译链内 Python 有一个内置的 HTTP 服务器,我们这里可以直接使用它。我们将会使用示例 get-started/hello_world 作为需要更新的固件。

4.6.1 生成目标固件

使用示例 get-started/hello_world,此处执行idf.py build 会生成二进制文件get-started\hello_world\build\hello-world.bin。

4.6.2 创建HTTP服务器

  • 使用工具链python创建服务器
    进入存放需要升级的固件(hello-world.bin)目录。在此处创建HTTP服务器。
    此处执行 python -m http.server --bind 192.168.61.67 8070会生成一个HTTP本地服务器。该服务器下对应\build文件夹下所有内容。(有的文章执行的python命令不同,是由于python版本不同造成的。)
  • 使用HFS创建服务器
    链接:https://pan.baidu.com/s/1MIAI5m4WQJdAZpqRAdvsXg 提取码:9m8y

4.7 开启HTTPS服务器(可选)

  1. Shirt + 鼠标右键 打开 Linux shell
  2. 创建一个新的自签名证书和密钥
    openssl req -x509 -newkey rsa:2048 -keyout ca_key.pem -out ca_cert.pem -days 365 -nodes
    依次输入:(国家)、(洲/省)、(城/镇)、(组织名)、(单位名)、(httpd-ssl.conf中的ServerName 名称)、(邮箱)
  3. 将生成的证书 ca_cert.pem 复制到OTA示例目录中的 server_certs 目录下,已存在则替换内容
  4. 创建HTTPS服务器
    openssl s_server -WWW -key ca_key.pem -cert ca_cert.pem -port 8070

4.8 查看打印

链接:https://www.jianshu.com/p/f3ce6d9265c4
- 本文内容来自网络,如有侵权,请联系本站处理。

2022-07   阅读(1025)   评论(0)
 标签: maker ESP32 OTA

涨知识
寄存器

寄存器的功能是存储二进制代码,它是由具有存储功能的触发器组合起来构成的。一个触发器可以存储1位二进制代码,故存放n位二进制代码的寄存器,需用n个触发器来构成。

评论:
相关文章
ESP32 MicroPython存储数据到闪存

在MicroPython的ESP32库中,NVS类用于管理非易失性存储,支持 32 位有符号整数和 二进制blob。


Arduino-ESP32 Preferences库使用详解

Arduino-ESP32项目提供的Preferences库是一个专为ESP32设计的非易失性存储解决方案,它替代了传统的Arduino EEPROM库,提供了更强大、更可靠的数据存储功能。


esp32cam开发板烧录micropython固件

‌ESP32-CAM与MicroPython结合可实现摄像头图像采集、视频流传输等功能,不过Micropython官方没有支持ESP32-CAM的固件,需要烧录第三方的专有固件。


小鹏物联网 MicroPython 智能浇花方案

相信很多人都有把绿植给养死的经历,可能是浇水过多、忘记浇水、较长时间不在家不能浇水等,本文介绍一种可以灵活定制的智能浇花方案。


MicroPython 开发ESP32应用之线程介绍及实例分析

MicroPython 在 ESP32 上支持线程(Thread)功能,通过_thread模块实现。线程允许程序并发执行多个任务,适合处理需要同时运行的场景,例如传感器数据采集和网络通信。


盛思发布掌控板3.0

掌控板3.0升级了主控,还主打AI。带有双麦克风阵列,增加了音频解码芯片,板载了一个1W喇叭,还把之前的单色屏幕换成了1.47寸的彩色屏幕,有更多的可玩性。


ESP-TOUCH 用户指南

乐鑫自主研发的 ESP-TOUCH 协议采⽤的是 Smart Config(智能配置)技术,帮助用户将采用 ESP8266 和 ESP32 的设备(以下简称“设备”)连接至 Wi-Fi 网络。


ESP32 使用 SmartConfig

本文将介绍如何将 ESP-Touch 协议用于基于 ESP32 的物联网项目/设备,使用 ESP-Touch,您将不再需要对 Wi-Fi 凭据进行硬编码,因为您可以随时轻松更改它。


ESP32-TinyML:释放嵌入式微型机器学习的强大潜力!

ESP32-TinyML项目为物联网(IoT)设备带来了强大的微型机器学习能力,让您可以在资源受限的ESP32微控制器上运行复杂的机器学习模型。接下来介绍该项目,探讨其功能、使用方法以及在各种应用中的潜力。


ESP32 MicroPython采集模拟传感器数值

使用了 MicroPython 库,通过 定时器(Timer) 和 ADC(模数转换器) 功能来实时读取传感器数据。使用定时器可以实现高精度、非阻塞、低资源消耗的周期性任务,保证实时性和可靠性,特别适用于嵌入式系统中的多任务处理和低功耗场景。

搜索
小鹏STEM教研服务

专属教研服务系统,助您构建STEM课程体系,打造一站式教学环境。