Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 70 additions & 1 deletion docs-site/docs/guides/ota-upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ sidebar_position: 6

本指南说明如何使用 agentic-kit 的 `iot_ota` API 实现设备固件 OTA(Over-The-Air)升级。

SDK 只提供**云端协议原语**——版本上报、升级查询、状态回报;**固件的下载与烧写由应用负责**(例如 ESP-IDF 的 `esp_ota_*` 或厂商自有的 bootloader API)。完整示例见 `examples/esp-idf/ota-demo`。
SDK 只提供**云端协议原语**——版本上报、升级查询、状态回报,以及 APP 确认通知回调;**固件的下载与烧写由应用负责**(例如 ESP-IDF 的 `esp_ota_*` 或厂商自有的 bootloader API)。完整主动查询示例见 `examples/esp-idf/ota-demo`。

如果产品要求“用户在 APP 上确认后才允许升级”,请将云端 OTA 任务配置为 APP 确认模式,并在设备上注册 `ota_confirm_callback`。agentic-kit 不调用 `tuya.device.upgrade.silent.get`,因此不会主动拉取或执行静默升级任务。

## 工作原理

Expand Down Expand Up @@ -44,6 +46,72 @@ SDK 只提供**云端协议原语**——版本上报、升级查询、状态回
| `iot_ota_report_status` | `tuya.device.upgrade.status.update` (v4.1) | 回报升级生命周期状态 |
| `iot_ota_verify_init/update/finish` | — | 流式校验下载固件的 md5/hmac 摘要(见下文) |

## APP 确认后触发升级

APP 确认升级后,云端通过 MQTT 协议号 `15` 通知设备。SDK 解密后读取 `data.firmwareType` 作为固件 channel,并调用 `ota_confirm_callback`:

```c
static volatile bool g_ota_confirmed;
static volatile int g_ota_channel;

static void on_ota_confirmed(int channel, void *user_data)
{
(void)user_data;
g_ota_confirmed = true; /* 只做轻量通知,不阻塞 MQTT process 线程 */
g_ota_channel = channel;
}

iot_client_config_t cfg = {
/* ... */
.ota_confirm_callback = on_ota_confirmed,
.ota_confirm_user_data = NULL,
};
```

应用主循环或专用 OTA 工作线程收到该信号后,再执行升级原语:

```text
APP 点击确认 ──> 云端下发 MQTT protocol 15
v
ota_confirm_callback(channel) /* SDK 只通知,不升级 */
应用 worker 唤醒
v
iot_ota_check_upgrade(client, channel, &info)
有升级?
/ \
否 是
│ │
保持运行 report_status(UPGRADING)
v
下载 + 校验 + 烧写 /* 应用实现 */
成功 / 失败
│ │
report_status report_status
(COMPLETE) (ERROR)
```

```c
iot_ota_upgrade_info_t info = {0};
if (!g_ota_confirmed) {
/* 等待确认信号 */
}

int rc = iot_ota_check_upgrade(client, g_ota_channel, &info);
if (rc == OPRT_OK && info.has_upgrade) {
rc = iot_ota_report_status(client, info.channel, OTA_STATUS_UPGRADING);
/* 在应用线程执行下载、iot_ota_verify_* 校验和平台 OTA 烧写 */
}
iot_ota_upgrade_info_free(client, &info);
```

`ota_confirm_callback` 与 `message_callback` 一样运行在调用 `iot_client_process()` / `iot_client_message_process()` 的线程内,coreMQTT 回调返回后还要继续处理 ack 和网络缓冲。回调中只允许置位标志、释放信号量或投递工作项;不要调用 `iot_ota_check_upgrade()`、下载固件、写 flash,也不要断开或销毁 IoT client。未注册该回调时,protocol 15 会继续透传给 `message_callback`,兼容旧应用自行解析的用法。

### `iot_ota_check_upgrade` 返回的升级信息

```c
Expand Down Expand Up @@ -289,6 +357,7 @@ idf flash monitor
## 注意事项

- **SDK 不下载/不烧写**——`iot_ota` 只负责云端协议;下载校验、分区管理、防回滚全部由应用实现。
- **APP 确认模式**——云端任务需配置为 APP 确认模式;设备侧通过 `ota_confirm_callback` 接收 protocol 15,再由应用 worker 查询并执行升级。SDK 不调用静默升级接口。
- **栈要足够大**——TLS 握手 + HTTP 缓冲 + `esp_ota_write` 需要较大栈空间(demo 用 16KB)。
- **回报时机**——`UPGRADING` 在下载前、`COMPLETE` 在重启前、`ERROR` 在失败时;漏报会导致云端升级面板状态不准。
- **MD5/HMAC 摘要校验**——`iot_ota_verify_init/update/finish` 在下载时流式计算摘要,`esp_ota_set_boot_partition` **之前**完成校验;不匹配必须中止升级(见上文「固件摘要校验」)。
4 changes: 4 additions & 0 deletions docs-site/docs/reference/iot-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ IoT Client 模块(CMake 目标 `tuya_iot_client`,产物 `libtuya_iot_client.
| `message_callback` | `iot_message_callback_t` | MQTT 消息回调,可为 NULL |
| `reset_callback` | `iot_reset_callback_t` | 云端解绑/恢复出厂(protocol 11)通知回调,可为 NULL。注册后 protocol 11 消息由 SDK 消费,不再进入 `message_callback` |
| `reset_user_data` | `void *` | 透传给 `reset_callback` 的用户指针,可为 NULL |
| `ota_confirm_callback` | `iot_ota_confirm_callback_t` | APP 确认 OTA 升级(protocol 15)通知回调,可为 NULL。注册后 protocol 15 消息由 SDK 消费,不再进入 `message_callback` |
| `ota_confirm_user_data` | `void *` | 透传给 `ota_confirm_callback` 的用户指针,可为 NULL |
| `schema` | `const char *` | 重启时用于恢复的 DP schema JSON(调用方持有,NULL = 不恢复 / 宽松模式) |
| `schema_id` | `const char *` | 持久化的 schema id(schema 升级查询的稳定 key,可为 NULL) |
| `dp_state` | `const char *` | 持久化的 DP 当前状态 `{"dps":{...}}`,用于恢复(不置脏、不上报,可为 NULL) |
Expand Down Expand Up @@ -137,6 +139,8 @@ IoT Client 模块(CMake 目标 `tuya_iot_client`,产物 `libtuya_iot_client.
| `message_callback` | `iot_message_callback_t` | MQTT 消息回调 |
| `reset_callback` | `iot_reset_callback_t` | 云端解绑/恢复出厂(protocol 11)通知回调,可为 NULL。注册后 protocol 11 消息由 SDK 消费,不再进入 `message_callback` |
| `reset_user_data` | `void *` | 透传给 `reset_callback` 的用户指针,可为 NULL |
| `ota_confirm_callback` | `iot_ota_confirm_callback_t` | APP 确认 OTA 升级(protocol 15)通知回调,可为 NULL。注册后 protocol 15 消息由 SDK 消费,不再进入 `message_callback` |
| `ota_confirm_user_data` | `void *` | 透传给 `ota_confirm_callback` 的用户指针,可为 NULL |
| `sw_ver` | `const char *` | 应用固件版本号(如 `"1.2.3"`),激活后自动上报供云端 OTA 比较;NULL 表示使用 SDK 默认 `IOT_SDK_SW_VER`。详见 [OTA 升级](../guides/ota-upgrade.md) |

### `iot_client_t`(返回实例)
Expand Down
11 changes: 11 additions & 0 deletions examples/posix/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,17 @@ add_executable(ota_demo
target_include_directories(ota_demo PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/ota-demo")
target_link_libraries(ota_demo PRIVATE tuya_iot_client)

add_executable(ota_confirm_demo
"${CMAKE_CURRENT_SOURCE_DIR}/ota-demo/ota_demo.c"
"${CMAKE_CURRENT_SOURCE_DIR}/ota-confirm/ota_confirm_demo.c"
"${CMAKE_CURRENT_SOURCE_DIR}/ota-confirm/ota_confirm_demo_main.c"
)
target_include_directories(ota_confirm_demo PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/ota-confirm"
"${CMAKE_CURRENT_SOURCE_DIR}/ota-demo"
)
target_link_libraries(ota_confirm_demo PRIVATE tuya_iot_client)

# -- ai / rtc-client (prebuilt lib, optional) --------------------------------
if(TARGET tuya_steam_client)
add_executable(chat_demo
Expand Down
51 changes: 51 additions & 0 deletions examples/posix/ota-confirm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# APP-confirmed OTA demo (macOS/POSIX)

This demo uses an already-activated device, so it does not run pairing or
activation. It connects the device to MQTT with `devid`, `secret_key`, and
`local_key`, waits for the app user to confirm the cloud OTA task, and then
performs `tuya.device.upgrade.get` from the application thread.

## Build on macOS

```bash
cmake -S examples/posix -B build/examples -DCMAKE_BUILD_TYPE=Debug
cmake --build build/examples --target ota_confirm_demo
```

If your Mac's Perl reports an unsupported `C.UTF-8` locale during the mbedTLS
generated-source step, build with:

```bash
LC_ALL=C LANG=C cmake --build build/examples --target ota_confirm_demo
```

## Run

```bash
export OTA_DEVID='your-device-id'
export OTA_SECRET_KEY='your-device-secret-key'
export OTA_LOCAL_KEY='your-device-local-key'
export OTA_REGION='AY' # AY, AZ, UEAZ, EU, WEAZ, IN, or SG

./build/examples/ota_confirm_demo
```

The demo prints `APP-confirmed OTA notice received (channel=N)` when the cloud
pushes MQTT protocol 15. It then queries that channel and prints the returned
firmware metadata.

To download the confirmed image, verify its cloud digest, and report
UPGRADING/COMPLETE/ERROR:

```bash
./build/examples/ota_confirm_demo --download
```

Credentials can also be passed positionally:

```bash
./build/examples/ota_confirm_demo "$OTA_DEVID" "$OTA_SECRET_KEY" "$OTA_LOCAL_KEY" "$OTA_REGION"
```

Keep the cloud OTA task configured as APP-confirm mode. The demo never calls
the silent-upgrade API, so the device should query only after the app confirms.
Loading
Loading