This commit is contained in:
tang1219
2026-06-12 17:03:10 +08:00
parent e488cd3216
commit 9eaffcc9ff
31 changed files with 6829 additions and 0 deletions
+357
View File
@@ -0,0 +1,357 @@
# 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` | 完成百分比 (0100)。若 `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 Timer10Hz)中:
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` | 注销所有回调 |