12 KiB
12 KiB
OTA Manager 回调文档
版本: 1.0
目标芯片: ESP32-S3
框架: ESP-IDF 5.1.x
模块:ota_manager.h/ota_manager.c
1. 架构概览
┌──────────────────────────────────────────────────────────┐
│ 传输层(USB / 串口 / 蓝牙 / SD / HTTP) │
│ ↓ │
│ ota_begin(size) → ota_write(chunk)×N → ota_end() │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ OTA Manager(本模块) │ │
│ │ · 状态机管理(IDLE → PROGRESS → VERIFY → DONE)│ │
│ │ · esp_ota_ops 封装(分区选择 / 擦除 / 写入) │ │
│ │ · 回调触发(进度 / 状态 / 完成 / 错误) │ │
│ │ · LED 反馈 │ │
│ └──────────────┬──────────────────────────────────┘ │
│ │ 回调(可注册 4 种) │
│ ┌───────────┼───────────┬──────────────┐ │
│ ▼ ▼ ▼ ▼ │
│ progress state complete error │
│ (进度条) (状态灯) (重启逻辑) (告警/日志) │
└──────────────────────────────────────────────────────────┘
设计原则:OTA Manager 只负责底层固件写入与分区切换,不关心固件数据从哪里来。传输层代码只需顺序调用三个 API,并通过回调感知进度。
2. 状态机
ota_begin()
IDLE ──────────────────────────→ IN_PROGRESS
↑ │
│ │ ota_write()
│ ▼ (循环)
│ IN_PROGRESS
│ │
│ ┌─────────────┼──────────────┐
│ │ ota_end() │ (写入失败) │
│ ▼ ▼ │
│ VERIFYING ERROR │
│ │ │ │
│ ┌───────────┤ │ ota_abort() │
│ │ 成功 │ 失败 ▼ │
│ ▼ ▼ IDLE │
│ COMPLETE ERROR │
│ │ │
│ │ esp_restart() │
│ ▼ │
│ (重启) │
└────────────────────────────────────────────────────┘
状态枚举 (ota_state_t):
| 状态 | 含义 | 允许的操作 |
|---|---|---|
OTA_STATE_IDLE |
空闲,就绪 | ota_begin() |
OTA_STATE_IN_PROGRESS |
正在接收固件 | ota_write(), ota_end(), ota_abort() |
OTA_STATE_VERIFYING |
正在校验固件 | 无(内部过程) |
OTA_STATE_COMPLETE |
更新成功 | 重启设备 |
OTA_STATE_ERROR |
发生错误 | ota_abort() |
3. 回调详解
3.1 进度回调 — ota_progress_cb_t
typedef void (*ota_progress_cb_t)(int percent, size_t bytes_written, size_t total_size);
触发时机:每次 ota_write() 成功返回后。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
percent |
int |
完成百分比 (0–100)。若 ota_begin() 传入 total_size=0,则始终为 0 |
bytes_written |
size_t |
累计已写入字节数 |
total_size |
size_t |
声明的固件总字节数(即 ota_begin 的参数,0=未知) |
用途:驱动进度条 UI、串口日志输出。
示例:
static void on_progress(int percent, size_t written, size_t total)
{
printf("\rOTA: %d%% (%zu/%zu bytes)", percent, written, total);
// 或通过 LVGL 更新进度条:
// lv_bar_set_value(bar, percent, LV_ANIM_OFF);
}
3.2 状态变化回调 — ota_state_cb_t
typedef void (*ota_state_cb_t)(ota_state_t old_state, ota_state_t new_state);
触发时机:状态机每次发生转换时。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
old_state |
ota_state_t |
变化前的状态 |
new_state |
ota_state_t |
变化后的状态 |
典型用法:
| 场景 | 检查条件 | 操作 |
|---|---|---|
| 更新成功 | new_state == OTA_STATE_COMPLETE |
自动重启 esp_restart() |
| 更新失败 | new_state == OTA_STATE_ERROR |
显示错误提示、记录日志 |
| 开始更新 | new_state == OTA_STATE_IN_PROGRESS |
禁用用户交互、显示进度 UI |
| 恢复空闲 | new_state == OTA_STATE_IDLE |
恢复用户交互 |
示例:
static void on_state_change(ota_state_t old, ota_state_t new_state)
{
ESP_LOGI("app", "OTA state: %d → %d", old, new_state);
if (new_state == OTA_STATE_COMPLETE) {
ESP_LOGI("app", "Update OK, restarting in 3s...");
vTaskDelay(pdMS_TO_TICKS(3000));
esp_restart();
} else if (new_state == OTA_STATE_ERROR) {
ESP_LOGE("app", "OTA failed, back to idle");
}
}
3.3 完成回调 — ota_complete_cb_t
typedef void (*ota_complete_cb_t)(bool success, ota_error_t error_code);
触发时机:
ota_end()处理完毕后ota_abort()处理完毕后
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
success |
bool |
true = 更新成功,false = 失败或被中止 |
error_code |
ota_error_t |
失败时的错误码(success=true 时始终为 OTA_ERR_NONE) |
与状态变化回调的区别:
ota_state_cb_t关注「状态发生了什么变化」ota_complete_cb_t关注「这次 OTA 最终成功了还是失败了」+ 失败原因
两者可同时使用,但建议至少注册一个来处理重启逻辑。
示例:
static void on_complete(bool success, ota_error_t err)
{
if (success) {
ESP_LOGI("app", "OTA complete, rebooting...");
esp_restart();
} else {
ESP_LOGW("app", "OTA failed, err=%d", err);
// 返回主界面
}
}
3.4 错误回调 — ota_error_cb_t
typedef void (*ota_error_cb_t)(ota_error_t error_code, const char *message);
触发时机:每当发生可恢复或不致命的错误时(写入失败、校验失败等)。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
error_code |
ota_error_t |
错误码枚举值 |
message |
const char * |
人类可读的错误描述(静态字符串,无需释放) |
错误码一览:
| 错误码 | 值 | 含义 |
|---|---|---|
OTA_ERR_NONE |
0 | 无错误 |
OTA_ERR_NOT_INITIALIZED |
1 | 未调用 ota_begin() |
OTA_ERR_ALREADY_IN_PROGRESS |
2 | 已有进行中的 OTA |
OTA_ERR_BEGIN_FAILED |
3 | ota_begin() 失败(分区擦除失败等) |
OTA_ERR_WRITE_FAILED |
4 | 写入 flash 失败 |
OTA_ERR_END_FAILED |
5 | ota_end() 失败 |
OTA_ERR_ABORT_FAILED |
6 | ota_abort() 失败 |
OTA_ERR_SIZE_MISMATCH |
7 | 实写大小与声明不符 |
OTA_ERR_NO_OTA_PARTITION |
8 | 分区表无可用 OTA 分区 |
OTA_ERR_STATE_ERROR |
9 | 当前状态不允许该操作 |
OTA_ERR_VERIFY_FAILED |
10 | 固件校验失败 |
4. 完整注册示例
#include "ota_manager.h"
#include "esp_system.h"
/* ---- 回调实现 ---- */
static void on_progress(int pct, size_t written, size_t total)
{
printf("\rOTA %d%% (%zu/%zu)", pct, written, total);
}
static void on_state_change(ota_state_t old, ota_state_t new_state)
{
if (new_state == OTA_STATE_COMPLETE) {
printf("\nUpdate OK. Rebooting...\n");
esp_restart();
}
}
static void on_complete(bool ok, ota_error_t err)
{
if (!ok) printf("\nOTA failed: %d\n", err);
}
static void on_error(ota_error_t code, const char *msg)
{
ESP_LOGE("app", "OTA error %d: %s", code, msg);
}
/* ---- 注册(在 app_main 初始化阶段调用一次) ---- */
void ota_callbacks_init(void)
{
ota_set_progress_callback(on_progress);
ota_set_state_callback(on_state_change);
ota_set_complete_callback(on_complete);
ota_set_error_callback(on_error);
}
5. 与 LVGL UI 集成要点
5.1 FreeRTOS 线程安全
LVGL 对象操作必须在 UI Task 中进行。OTA 的调用方通常运行在另一个 Task 中,回调在调用线程中触发。因此:
// ❌ 错误:在进度回调中直接操作 LVGL
static void on_progress(int pct, ...) {
lv_bar_set_value(ui_bar, pct, LV_ANIM_OFF); // 非 UI 线程!
}
// ✅ 正确:通过全局变量 + LVGL Timer 轮询
static volatile int g_ota_pct = 0;
static void on_progress(int pct, ...) {
g_ota_pct = pct;
}
// 在 LVGL Timer(10Hz)中:
static void lv_timer_update_ota(lv_timer_t *timer) {
lv_bar_set_value(ui_bar, g_ota_pct, LV_ANIM_ON);
}
5.2 建议的 UI 状态映射
| OTA 状态 | UI 行为 |
|---|---|
| IDLE | 显示「固件升级」入口 |
| IN_PROGRESS | 显示进度条 + 「正在更新…」 |
| VERIFYING | 进度条 100%,文字改为「正在校验…」 |
| COMPLETE | 「更新成功,3 秒后重启」+ 倒计时 |
| ERROR | 红色警告 + 错误描述 + «重试»按钮 |
6. LED 反馈模式
OTA Manager 内置了 LED 反馈(通过 hw_led_set()),无需额外配置。
| 阶段 | LED 行为 | 含义 |
|---|---|---|
begin 调用后 |
常亮 | OTA 进行中 |
每次 write |
短暂熄灭→亮起 | 数据写入闪烁(约 2ms) |
end 成功 |
常亮 | 更新完成,等待重启 |
end 失败 / abort |
熄灭 | 回到空闲 |
7. 错误处理建议
ota_begin() 返回非 ESP_OK?
├─ OTA_ERR_ALREADY_IN_PROGRESS → 先调 ota_abort(),再重试 begin
├─ OTA_ERR_NO_OTA_PARTITION → 检查分区表是否正确烧录
└─ OTA_ERR_BEGIN_FAILED → 检查 flash 是否正常
ota_write() 返回非 ESP_OK?
└─ 调 ota_abort(),提示用户重试
ota_end() 返回非 ESP_OK?
└─ 调 ota_abort(),固件未切换,设备仍运行旧版本
8. 平台兼容性
| 特性 | 要求 |
|---|---|
| ESP-IDF 版本 | ≥ 5.0(本模块使用 v5.1.x API) |
| 分区表 | 必须含 factory + 至少一个 ota_0 类型分区 |
| Flash 大小 | ≥ 2MB(推荐 8MB,双 OTA 槽位) |
hw_led_set() |
需由项目提供(当前在 hw_init.h 中定义) |
9. API 快速参考
| 函数 | 返回值 | 副作用 |
|---|---|---|
ota_begin(size) |
esp_err_t |
擦除 OTA 分区,LED 亮 |
ota_write(data, len) |
esp_err_t |
写入 flash,触发进度回调,LED 闪烁 |
ota_end() |
esp_err_t |
校验固件,切换启动分区,触发完成回调 |
ota_abort() |
esp_err_t |
放弃更新,LED 灭,回到 IDLE |
ota_get_state() |
ota_state_t |
无 |
ota_get_bytes_written() |
size_t |
无 |
ota_get_firmware_size() |
size_t |
无 |
ota_get_running_partition() |
const esp_partition_t * |
无 |
ota_get_update_partition() |
const esp_partition_t * |
无 |
ota_set_progress_callback(cb) |
void |
覆盖之前的回调 |
ota_set_complete_callback(cb) |
void |
覆盖之前的回调 |
ota_set_state_callback(cb) |
void |
覆盖之前的回调 |
ota_set_error_callback(cb) |
void |
覆盖之前的回调 |
ota_clear_callbacks() |
void |
注销所有回调 |