ESP32的OTA升级详解:2. OTA低层组件app_update介绍
一、app_update,esp_https_ota组件关系和区别
在ESP-IDF 框架中,app_update 和 esp_https_ota 都是与固件更新相关的组件。
1. app_update:底层分区操作和固件切换 API
-
定位: 这是 最底层、最核心 的 OTA 功能库。
-
功能:
- 提供 API 来写入数据到特定的 OTA 应用分区 (esp_ota_write)。
- 提供 API 来设置下次启动时使用的引导分区 (esp_ota_set_boot_partition)。
- 提供 API 来获取当前运行的分区信息 (esp_ota_get_running_partition, esp_ota_get_next_update_partition)。
- 管理 OTA 分区表(查找分区、获取分区信息)。
- 处理固件校验。
-
作用: 它是 OTA 更新的基础引擎。无论通过HTTP, HTTPS或自定义协议方式获取到新的固件二进制数据,最终都需要调用 app_update 提供的 API 将这些数据写入到 Flash 的 OTA 分区,并在验证后设置引导分区。
-
使用场景: 当需要实现自定义的 OTA 传输协议时,例如通过 MQTT、CoAP、私有 TCP/UDP 协议、串口、蓝牙传输固件时,需要在代码中调用 app_update 的 API 来完成最终的固件写入和启动切换。
2. esp_https_ota:基于 HTTPS 的完整 OTA 更新客户端
- 定位: 这是一个高级别、封装好的 OTA 客户端组件,专门用于通过 HTTPS 协议从远程服务器下载和安装固件更新。
- 功能:
- 建立到指定 HTTPS URL 的安全 TLS 连接。
- 执行 HTTP GET 请求 下载固件。
- 处理 HTTP 响应状态码、Headers。
- 分块接收固件数据。
- 调用 app_update 的 API 将下载到的数据块写入正确的 OTA 分区。
- 在下载和写入完成后,自动设置新的引导分区。
- 提供基本的错误处理和状态回调。
- 优化: 隐藏了 HTTPS 协议处理、网络通信、数据分块接收以及底层 app_update API 调用的所有复杂性。只需要提供一个 HTTPS URL 和必要的配置如证书,调用简单函数就能完成整个 OTA 更新流程。
- 依赖: 它内部依赖 app_update 组件来完成实际的 Flash 写入操作。esp_https_ota 是构建在 app_update 基础之上的一个应用层组件。
- 使用场景: 需要从HTTPS 的 Web 服务器进行 OTA 更新时,这是最简单、最推荐的方式。只需几行代码即可实现安全可靠的 OTA。
特性 | app_update | esp_https_ota |
---|---|---|
层级 | 底层核心 (操作分区/Flash) | 高层应用 (完整 HTTPS OTA 客户端) |
主要职责 | 写分区、设引导区、管理分区信息 | 通过 HTTPS 下载固件并安装 |
网络通信 | 完全不处理 | 处理 HTTPS 协议栈 (依赖 esp_http_client, esp_tls) |
TLS/SSL 安全 | 不涉及 | 核心功能,处理证书验证等 |
调用 appupdate | 提供API给别人调用 | 内部调用 appupdate API 来写 Flash |
固件来源 | 无限制 | 仅限于 HTTPS URL |
复杂度 | 低 (只做 Flash 操作) | 高 (封装了网络、协议、安全、Flash 操作) |
使用场景 | 实现自定义传输协议的 OTA | 实现标准 HTTPS 服务器的 OTA |
开发者工作 | 需实现所有网络传输、协议解析逻辑 | 只需配置 URL/证书,调用 perform 函数 |
3. 总结
- app_update 是“写 Flash 和切分区”的工具箱。
- esp_https_ota 是一个“从 HTTPS 网址下载更新包并用 app_update 工具箱安装好”的自动化程序。
二、app_update的API解释用法
1. 获取应用描述信息
const esp_app_desc_t *esp_ota_get_app_description(void);
- 作用:返回当前运行应用的描述信息(如版本号、项目名称等)。
- 状态:已弃用,建议使用 esp_app_get_description。
2. 获取应用 ELF 文件的 SHA256 值
int esp_ota_get_app_elf_sha256(char* dst, size_t size);
- 作用:将当前运行应用的 ELF 文件的 SHA256 值(十六进制字符串)写入缓冲区。
- 状态:已弃用,建议使用 esp_app_get_elf_sha256。
3. 开始 OTA 更新会话
esp_err_t esp_ota_begin(const esp_partition_t* partition, size_t image_size, esp_ota_handle_t* out_handle);
- 作用:初始化 OTA 更新,擦除目标分区并返回操作句柄。
- 参数:
- partition:目标 OTA 分区。
- image_size:新固件大小(未知时用 OTA_SIZE_UNKNOWN)。
- out_handle:输出句柄,用于后续写入操作。
- 返回值:状态码(如 ESP_OK 表示成功)。
4. 持续的写入 OTA 数据
esp_err_t esp_ota_write(esp_ota_handle_t handle, const void* data, size_t size);
- 作用:将数据顺序写入 OTA 分区,在esp_ota_begin擦除之后调用,写入位置指针会自动增加。
- 参数:
- handle:esp_ota_begin 返回的句柄。
- data:待写入数据的缓冲区。
- size:数据大小。
5. 偏移量写入 OTA 数据
esp_err_t esp_ota_write_with_offset(esp_ota_handle_t handle, const void *data, size_t size, uint32_t offset);
- 作用:将数据写入分区的指定偏移位置,支持非连续写入。
- 参数:
- offset:分区内的写入偏移量。
- 适用场景:数据包乱序到达时使用。
6. 结束 OTA 更新并验证
esp_err_t esp_ota_end(esp_ota_handle_t handle);
- 作用:结束 OTA 会话,验证固件有效性,并释放资源。
- 注意:无论成功与否,句柄在此后均无效。
7. 中止 OTA 更新
esp_err_t esp_ota_abort(esp_ota_handle_t handle);
- 作用:中止 OTA 更新,释放句柄及相关资源。
- 适用场景:更新过程中发生错误需取消时。
8. 设置启动分区
esp_err_t esp_ota_set_boot_partition(const esp_partition_t* partition);
- 作用:设置下次启动时使用的分区,可以设置新固件所在分区。
- 注意:成功后需重启 (esp_restart) 生效。
9. 获取当前配置的启动分区
const esp_partition_t* esp_ota_get_boot_partition(void);
- 作用:返回当前配置的启动分区,不一定是当前运行的分区。
- 注意:可能与 esp_ota_get_running_partition 结果不同(如回滚时)。
10. 获取当前运行分区
const esp_partition_t* esp_ota_get_running_partition(void);
- 作用:返回当前正在运行的应用所在的分区。
11. 获取下一个更新分区
const esp_partition_t* esp_ota_get_next_update_partition(const esp_partition_t *start_from);
- 作用:返回下一个可用于 OTA 更新的分区(轮询逻辑)。
- 参数:start_from 指定起点分区(通常为当前运行分区)。
- start_from: 查找的起始分区指针。如果为 NULL,则从第一个 OTA 分区开始查找。
假设有 4 个 OTA 分区:ota0, ota1, ota2, ota3,当前运行在 ota2 分区:
- 如果传入 start_from 为 NULL:
- 从 ota0 开始查找
- ota0 不是当前运行分区 → 返回 ota0- 如果传入 start_from 为 ota2 分区指针:
- 从 ota2 开始查找下一个
- ota3 不是当前运行分区 → 返回 ota3- 如果传入 start_from 为 ota3 分区指针:
- 从 ota3 开始查找下一个
- 循环回到 ota0
- ota0 不是当前运行分区 → 返回 ota0
- 例子:
// 获取下一个可用的OTA分区
const esp_partition_t *update_partition = esp_ota_get_next_update_partition(NULL);
if (update_partition == NULL) {ESP_LOGE(TAG, "No valid OTA partition found");return ESP_FAIL;
}// 开始OTA更新
esp_ota_handle_t update_handle;
esp_err_t err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle);
if (err != ESP_OK) {ESP_LOGE(TAG, "OTA begin failed");return err;
}
12. 获取分区描述信息
esp_err_t esp_ota_get_partition_description(const esp_partition_t *partition, esp_app_desc_t *app_desc);
- 作用:读取指定分区的应用描述信息(版本号等)。
- 参数:partition 必须是应用分区。
13. 获取引导加载程序 (Bootloader) 描述
esp_err_t esp_ota_get_bootloader_description(const esp_partition_t *bootloader_partition, esp_bootloader_desc_t *desc);
- 作用:读取 Bootloader 的描述信息。
- 参数:bootloader_partition 可为 NULL(使用默认位置)。
14. 获取 OTA 分区数量
uint8_t esp_ota_get_app_partition_count(void);
- 作用:返回分区表中 OTA 应用分区的总数。
15. 标记应用为有效
esp_err_t esp_ota_mark_app_valid_cancel_rollback(void);
- 作用:确认当前应用运行正常,取消回滚标记。
- 调用时机:启动后首次验证应用正常时调用。
16. 回滚到之前版本并重启
esp_err_t esp_ota_mark_app_invalid_rollback_and_reboot(void);
- 作用:将当前应用标记为无效,回滚到之前版本并重启。
- 条件:需存在有效的回滚分区。
- 流程:
- 该函数会将当前运行的应用程序标记为无效
- 触发系统重启
- 引导加载程序(bootloader)会选择上一个已知良好的OTA分区启动
- 如果所有OTA分区都无效,才会回退到factory分区
17. 获取最后一个无效分区
const esp_partition_t* esp_ota_get_last_invalid_partition(void);
- 作用:返回状态为 INVALID 或 ABORTED 的最后一个分区。
18. 获取分区状态
esp_err_t esp_ota_get_state_partition(const esp_partition_t *partition, esp_ota_img_states_t *ota_state);
- 作用:查询指定分区的 OTA 状态(如 PENDING_VERIFY、VALID 等)。
19. 擦除上一次启动的分区
esp_err_t esp_ota_erase_last_boot_app_partition(void);
- 作用:擦除上一次启动的应用分区及关联的 OTA 数据。
- 条件:当前应用必须已被标记为 VALID。
20. 检查回滚可能性
bool esp_ota_check_rollback_is_possible(void);
- 作用:检查是否存在有效的回滚分区(除当前运行分区外)。
- 返回值:true 表示可回滚。
三、回滚到factory分区
- 下列场景时:
- 新固件启动后检测到严重错误时
- 安全验证失败时(如签名验证)
- 需要紧急回退到稳定版本时
- 使用 esp_ota_set_boot_partition() 直接指定factory分区,或者调用 esp_ota_mark_app_valid_cancel_rollback() 配合其他方法
// 直接指定factory分区
const esp_partition_t *factory = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL);
if (factory) {esp_ota_set_boot_partition(factory);esp_restart();
}