ESP32 OTA升级之 https ota详解

1. 前言

本文以 ESP32 官方demo例程 native_ota_example 为例,详细阐述如何采用https实现esp32的ota升级

  • 第一章节,为本文的前言部分,对文章内容进行大体概述;
  • 第二章节,主要描述了如何在本地将demo例程跑起来,并附带了关于使用demo例程中遇到的相关报错的具体解决措施;
  • 第三章节,主要描述了ESP32的 flash 扇区布局;
  • 第四章节,主要描述了OTA下载的镜像文件的头部数据结构,并对其进行详细分析,镜像文件的头部与OTA息息相关;
  • 第五、六章节,详细描述了OTA升级过程中应用程序的状态切换逻辑以及相关软件版本的使用;

接下来,让我们一起研究ESP32的 OTA 升级是如何实现的吧!相信你一定有所收获!

2. 例程体验

例程目录:esp-idf/examples/system/ota/native_ota_example/
工程地址:https://github.com/espressif/esp-idf/tree/release/v5.1/examples/system/ota/native_ota_example
IDF版本: v5.1

2.1 采用直接跳过ca根证书验证方案

  1. 进入 native_ota_example 工程

  2. 运行 idf.py menuconfig 配置wifi及服务器地址

  3. 配置 wifi

4.配置https服务器地址
如果没有https服务器,填写http服务器地址也可以,http服务器搭建参考:ESP32 OTA升级之HTTP OTA
https服务器,大家可以尝试把升级文件往github、gitee上发布也可以啦,免费的https测试服务器对吧

5.运行 idf.py build 编译工程

6.运行 idf.py flash monitor 下载工程并开启监控

7.网络连接成功之后https下载时会报错,提示 mbdtls ssl 握手失败,错误码 -0x2700

I (5888) native_ota_example: Starting OTA example task
I (5888) native_ota_example: Running partition type 0 subtype 0 (offset 0x00010000)
E (6808) esp-tls-mbedtls: mbedtls_ssl_handshake returned -0x2700
I (6808) esp-tls-mbedtls: Failed to verify peer certificate!
E (6808) esp-tls: Failed to open new connection
E (6818) transport_base: Failed to open a new connection
E (6828) HTTP_CLIENT: Connection failed, sock < 0
E (6828) native_ota_example: Failed to open HTTP connection: ESP_ERR_HTTP_CONNECT
E (6838) native_ota_example: Exiting task due to fatal error...

8.打开 native_ota_example.c 文件,屏蔽 .cert_pem 配置

9.运行 idf.py build 编译工程,之后运行 idf.py flash monitor 开启监控

10.网络连接成功之后https下载时会报错,提示 mbdtls 服务器验证选项 esp_tls_cfg_t 结构体配置失败

I (5888) native_ota_example: Starting OTA example task
I (5888) native_ota_example: Running partition type 0 subtype 0 (offset 0x00010000)
E (6368) esp-tls-mbedtls: No server verification option set in esp_tls_cfg_t structure. Check esp_tls API reference
E (6368) esp-tls-mbedtls: Failed to set client configurations, returned [0x8017] (ESP_ERR_MBEDTLS_SSL_SETUP_FAILED)
E (6378) esp-tls: create_ssl_handle failed
E (6388) esp-tls: Failed to open new connection
E (6388) transport_base: Failed to open a new connection
E (6398) HTTP_CLIENT: Connection failed, sock < 0
E (6398) native_ota_example: Failed to open HTTP connection: ESP_ERR_HTTP_CONNECT
E (6408) native_ota_example: Exiting task due to fatal error...

11.修改 menuconfig ,跳过服务器证书验证

12.运行 idf.py build 编译工程,之后运行 idf.py flash monitor 开启监控

13.网络连接成功之后https下载成功

2.2 采用mbedtls内自带的ca根证书方案

  1. 修改 native_ota_example.c 文件中.cert_pem 配置
   esp_http_client_config_t config = {
       .url = CONFIG_EXAMPLE_FIRMWARE_UPG_URL,
       // .cert_pem = (char *)server_cert_pem_start,
       .crt_bundle_attach = esp_crt_bundle_attach,
       .timeout_ms = CONFIG_EXAMPLE_OTA_RECV_TIMEOUT,
       .keep_alive_enable = true,
   };

2.3 采用对应服务器的根证书文件

此方案,首先需要下载对应服务器的证书文件才可以,下载服务器的证书可以采用以下方案:

首先使用浏览器打开对应的服务器,之后浏览器的地址栏旁边有一个,点击它

按照上述方式,导出对应服务器的证书文件 .pem,之后替换掉工程目录下 server_certs 目录下的ca_cert.pem 文件里面的内容即可

采用此方案有一个需要特别注意的地方是:通过此方案下载的证书为此网站的证书,通常有效期为1年,过了一年之后就无效了!!!

这个时间在我们下载证书的时候其实就可以看到:

因此使用此方案时,需要注意特别处理,推荐两种解决方案:

1)采用升级的手段,定期更新设备端证书文件;

2)使用发证机构(注意选择大品牌的)的根证书文件,通常此类证书的有效期会比较长,我们上一小节中 mbedtls里面的证书其实就是存的一部分发证机构的ca跟证书

参考文档:ESP32空中升级 OTA

3. ESP32 flash分区结构

OTA是基于 Flash 进行的,想要弄清楚 ESP32 的 OTA,首先肯定得了解 ESP32 的 flash 布局结构,关于此部分内容我整理在了另外一篇博客,请查阅:ESP32 分区表(点击跳转),对 ESP32 的 flash 分区布局有所了解的可以直接跳过。

4. 应用程序镜像段结构

应用程序镜像段结构如下,运行命令 esptool.py --chip esp32 image_info build/app.bin

esptool.py v2.3.1
Image version: 1
Entry point: 40080ea4
13 segments

Segment 1: len 0x13ce0 load 0x3f400020 file_offs 0x00000018 SOC_DROM
Segment 2: len 0x00000 load 0x3ff80000 file_offs 0x00013d00 SOC_RTC_DRAM
Segment 3: len 0x00000 load 0x3ff80000 file_offs 0x00013d08 SOC_RTC_DRAM
Segment 4: len 0x028e0 load 0x3ffb0000 file_offs 0x00013d10 DRAM
Segment 5: len 0x00000 load 0x3ffb28e0 file_offs 0x000165f8 DRAM
Segment 6: len 0x00400 load 0x40080000 file_offs 0x00016600 SOC_IRAM
Segment 7: len 0x09600 load 0x40080400 file_offs 0x00016a08 SOC_IRAM
Segment 8: len 0x62e4c load 0x400d0018 file_offs 0x00020010 SOC_IROM
Segment 9: len 0x06cec load 0x40089a00 file_offs 0x00082e64 SOC_IROM
Segment 10: len 0x00000 load 0x400c0000 file_offs 0x00089b58 SOC_RTC_IRAM
Segment 11: len 0x00004 load 0x50000000 file_offs 0x00089b60 SOC_RTC_DATA
Segment 12: len 0x00000 load 0x50000004 file_offs 0x00089b6c SOC_RTC_DATA
Segment 13: len 0x00000 load 0x50000004 file_offs 0x00089b74 SOC_RTC_DATA
Checksum: e8 (valid)
Validation Hash: 407089ca0eae2bbf83b4120979d3354b1c938a49cb7a0c997f240474ef2ec76b (valid)

4.1 镜像文件头格式

  1. 镜像在最后一段之后有一个校验和字节(如上述中的 Checksum: e8 (valid) )。此字节写入十六字节填充边界,因此应用程序镜像可能需要填充。
  2. 如果esp_image_header_t 结构体(见下图)的hash_appended字段被设置,则将附加 SHA256 校验和。SHA256 哈希的值是在从第一个字节到该字段的范围内计算的,该字段的长度为 32 个字节。

应用程序映像由以下结构组成:

  1. 首先是 镜像头 esp_image_header_t 结构数据,描述了 SPI 闪存的模式和内存段的数量。

2.之后是 数据段头 esp_image_segment_header_t 结构数据,此结构数据描述了每个段、其长度及其在 ESP32 内存中的位置,后跟长度为data_len,图像中每个片段的数据偏移量按以下方式计算:

  • 第 0 段的偏移量 = sizeof( esp_image_header_t) + sizeof( esp_image_segment_header_t))。
  • 第 1 Segment 的偏移量 = 0 Segment 的偏移量 + 0 Segment 的长度 + sizeof( esp_image_segment_header_t)。
  • 第 2 Segment 的偏移量 = 1 Segment 的偏移量 + 1 Segment 的长度 + sizeof( esp_image_segment_header_t)。

3.之后是 应用头 esp_app_desc_t 结构数据:

  • 此数据结构位于DORM 段起始位置
  • 当前应用程序可以通过调用esp_app_get_description() 获取应用头结构
  • 其他应用程序可通过调用esp_ota_get_partition_description() 获取其他OTA分区应用头结构
  • 此结构的偏移地址计算为:offset = sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)
  1. 再往后是 用户自定义的应用结构头,如有需要可自行查阅 Adding a Custom Structure to an Application


综上,一个应用程序镜像的头部结构为:

  • 首先是 esp_image_header_t
  • 其次是 esp_image_segment_header_t
  • 之后是 esp_app_desc_t
  • 如果有用户自定义的结构custom_app_desc 则之后是用户自定义结构 custom_app_desc
  • 之后就是实际的程序了

参考文档:App Image Format

5. 应用程序状态切换

应用程序通过一个OTA状态对齐进行维护,通过此状态决定应用程序是否运行。

应用程序可通过调用 esp_err_t esp_ota_get_state_partition(const esp_partition_t *partition, esp_ota_img_states_t *ota_state) 获取对应扇区应用的状态。

应用程序的状态有以下几大类:

关于状态的使用,需要注意一个配置项: CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE

当 CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE 未使能时,应用程序状态通常只会使用 ESP_OTA_IMG_UNDEFINED 这一个状态

(官方IDF文档只是说ESP_OTA_IMG_NEW 和 ESP_OTA_IMG_PENDING_VERIFY 不会使用,但实测发现基本只会使用 ESP_OTA_IMG_UNDEFINED )
当 CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE 使能时,应用程序状态通常会使用除 ESP_OTA_IMG_UNDEFINED 状态之外的其他所有状态

CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE 配置项可通过 menuconfig 配置:

 CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE 未使能时:

CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE 使能时:

Pass: 程序分析小技巧,可以通过python在工程目录下搭建本地http服务器,配置升级地址为服务器地址,这样可以循环升级,并在例程中添加我们需要的打印信息,即可快速分析ota程序!

参考文档:Over The Air Updates (OTA)

6. 应用程序版本修改

应用程序版本存储在 esp_app_desc_t 结构体中。该结构体位于 DROM 扇区,有一个从二进制文件头部计算的固定偏移值。该结构体位于 esp_image_header_t 和 esp_image_segment_header_t 结构体之后。字段 Version 类型为字符串,最大长度为 32 字节。

关于应用程序的版本设置,总共有以下几种方案:

  1.  menuconfig 中设置对应配置项(Application manager选项内)

  1.  CMakeLists.txt 文件中设置 PROJECT_VER 变量,注意需要在 包含project.cmake 之前添加,格式如 set(PROJECT_VER "0.1.0.1")

  1. 在工程目录下新建 version.txt 文档,在此文档中输入对应版本信息

  1. 使用 git describe 检索(实测命令无效)

  2. 若以上均未有设置,则会将版本变量 PROJECT_VER 默认设置为 1

7. 总结

以上就是关于 ESP32 使用 https 进行 OTA 升级的全部内容了,欢迎大家一同探讨关于ESP32的更多知识!


创作不易,转载请注明出处!

关注、点赞+收藏,可快速查收博主有关分享!


8. 补充学习

强烈推荐: