# 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` ```c 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、串口日志输出。 **示例**: ```c 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` ```c 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` | 恢复用户交互 | **示例**: ```c 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` ```c 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 最终成功了还是失败了」+ 失败原因 两者可同时使用,但建议至少注册一个来处理重启逻辑。 **示例**: ```c 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` ```c 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. 完整注册示例 ```c #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 中,回调在调用线程中触发。因此: ```c // ❌ 错误:在进度回调中直接操作 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` | 注销所有回调 |