20260610
This commit is contained in:
@@ -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` | 完成百分比 (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` | 注销所有回调 |
|
||||
Reference in New Issue
Block a user