diff --git a/docs/OTA进度上报协议与后端接入说明.md b/docs/OTA进度上报协议与后端接入说明.md new file mode 100644 index 0000000..bdeaf48 --- /dev/null +++ b/docs/OTA进度上报协议与后端接入说明.md @@ -0,0 +1,168 @@ +# OTA 下载进度上报协议说明 + +## 1. 目的 + +在当前 OTA 协议基础上增加下载进度上报。 + +本次只增加进度,不修改以下内容: + +- OTA 下发主题不变; +- OTA 下发报文不变; +- OTA 响应主题不变; +- `msg_id` 继续使用 `003`; +- 当前的 `accepted` 和 `success` 响应保持不变; +- 不增加任务 ID、错误码、新主题或新的 OTA 状态机。 + +设备端可以先发布。后端尚未处理进度消息时,不影响设备执行 OTA;后端后续只需增加对 `status=updating` 的解析。 + +## 2. 当前 OTA 下发协议 + +下发主题: + +```text +device/{imei}/command +``` + +下发报文保持不变: + +```json +{ + "msg_id": "003", + "time": "1791853200000", + "params": { + "url": "http://banban.api.talkingq.com:8080/path/fota.bin", + "version": "2.0.4" + } +} +``` + +## 3. OTA 上报主题 + +设备继续使用当前响应主题: + +```text +device/{imei}/response +``` + +## 4. 接受 OTA + +设备接受 OTA 命令后,继续发送当前报文: + +```json +{ + "msg_id": "003", + "status": "accepted", + "data": { + "current_version": "2.0.3", + "target_version": "2.0.4" + } +} +``` + +该报文表示设备已经接受 OTA 命令,不表示下载已经完成。 + +## 5. 新增下载进度上报 + +设备下载 FOTA 包时,每增加 10% 上报一次: + +```json +{ + "msg_id": "003", + "status": "updating", + "data": { + "target_version": "2.0.4", + "progress": 30 + } +} +``` + +字段说明: + +| 字段 | 类型 | 说明 | +|---|---|---| +| `msg_id` | string | 固定为 `003` | +| `status` | string | 下载进度消息固定为 `updating` | +| `data.target_version` | string | 本次 OTA 目标版本 | +| `data.progress` | number | FOTA 包下载百分比,范围为 `0~100` | + +设备建议上报以下进度值: + +```text +10、20、30、40、50、60、70、80、90、100 +``` + +不需要每下载一个数据块就上报,避免产生过多 MQTT 消息。 + +示例: + +```json +{"msg_id":"003","status":"updating","data":{"target_version":"2.0.4","progress":10}} +``` + +```json +{"msg_id":"003","status":"updating","data":{"target_version":"2.0.4","progress":50}} +``` + +```json +{"msg_id":"003","status":"updating","data":{"target_version":"2.0.4","progress":100}} +``` + +注意:`progress=100` 只表示 FOTA 包下载完成,不表示新固件已经启动成功。 + +## 6. OTA 成功 + +设备下载、校验、重启并进入新 APP 后,继续发送当前成功报文: + +```json +{ + "msg_id": "003", + "status": "success", + "data": { + "new_version": "2.0.4" + } +} +``` + +后端只有收到 `status=success` 时,才能将本次 OTA 判断为最终成功。 + +## 7. 后端需要修改的内容 + +后端只需要在现有 `003` 响应解析中增加一种状态: + +```text +status = updating +``` + +解析逻辑: + +1. 收到 `accepted`:记录设备已接受 OTA。 +2. 收到 `updating`:读取 `data.progress` 并更新下载进度。 +3. 收到 `success`:记录设备已经进入目标版本,OTA 最终成功。 + +MQTT 当前使用 QoS 1,同一条进度消息可能重复到达。后端收到重复进度时直接覆盖原进度即可,不要重复创建 OTA 任务。 + +如果后端收到的进度小于已经保存的进度,可以忽略较小的进度,避免进度倒退。 + +## 8. 设备端实现约定 + +1. 当前 `accepted` 和 `success` 报文不修改。 +2. 下载任务每达到一个新的 10% 档位,上报一次 `updating`。 +3. 进度上报仍发送到当前 `response` 主题。 +4. 进度上报失败只记录日志,不中止 OTA 下载。 +5. 下载完成后上报 `progress=100`。 +6. `progress=100` 后设备继续执行文件校验和重启。 +7. 新 APP 启动后仍使用当前 `success` 报文确认最终成功。 + +## 9. 完整交互示例 + +```text +后端 -> 设备:003 OTA 命令 +设备 -> 后端:003 accepted +设备 -> 后端:003 updating progress=10 +设备 -> 后端:003 updating progress=20 +设备 -> 后端:003 updating progress=30 +... +设备 -> 后端:003 updating progress=100 +设备重启并安装新 APP +设备 -> 后端:003 success +``` diff --git a/ql-application/threadx/interface/custom_app/inc/ota_fullfota2.h b/ql-application/threadx/interface/custom_app/inc/ota_fullfota2.h index 2277d93..898fc1d 100644 --- a/ql-application/threadx/interface/custom_app/inc/ota_fullfota2.h +++ b/ql-application/threadx/interface/custom_app/inc/ota_fullfota2.h @@ -16,8 +16,11 @@ typedef enum { } banban_fullfota2_result_t; typedef void (*banban_fullfota2_finished_cb_t)(int result); +/* Download percentage in 10% steps; reporting must not cancel the download. */ +typedef void (*banban_fullfota2_progress_cb_t)(int progress); int banban_fullfota2_start(const char *url, + banban_fullfota2_progress_cb_t progress_cb, banban_fullfota2_finished_cb_t finished_cb); int banban_fullfota2_is_running(void); void banban_fullfota2_discard_staged_package(void); diff --git a/ql-application/threadx/interface/custom_app/mqtt.c b/ql-application/threadx/interface/custom_app/mqtt.c index 534c1b8..483cefe 100644 --- a/ql-application/threadx/interface/custom_app/mqtt.c +++ b/ql-application/threadx/interface/custom_app/mqtt.c @@ -123,6 +123,7 @@ typedef struct { int app_mqtt_publish(char *buf, int len, int type); void build_register(void); static void ota_report_pending_success(void); +static char ota_progress_target_version[16] = {0}; enum{ MQTT_WORK_CONNECT, @@ -260,7 +261,39 @@ static void ota_state_clear(void) } } -static void ota_fullfota2_finished(int result) +static void ota_fullfota2_progress(int progress) +{ + cJSON *root = cJSON_CreateObject(); + cJSON *data = cJSON_CreateObject(); + char *payload; + + if (root == NULL || data == NULL) { + cJSON_Delete(root); + cJSON_Delete(data); + RLOGE("allocate OTA progress response failed"); + return; + } + + cJSON_AddStringToObject(root, "msg_id", "003"); + cJSON_AddStringToObject(root, "status", "updating"); + cJSON_AddStringToObject(data, "target_version", ota_progress_target_version); + cJSON_AddNumberToObject(data, "progress", progress); + cJSON_AddItemToObject(root, "data", data); + payload = cJSON_PrintUnformatted(root); + cJSON_Delete(root); + if (payload == NULL) { + RLOGE("build OTA progress response failed"); + return; + } + + /* Progress is best effort; a publish failure must not abort FULLOTA2. */ + if (app_mqtt_publish(payload, strlen(payload), 0) != 0) { + RLOGE("OTA progress publish failed: %d%%", progress); + } + free(payload); +} + +static void ota_fullfota2_finished(int result) { if (result == BANBAN_FULLOTA2_OK) { return; @@ -1415,7 +1448,10 @@ static void handle_ota_upgrade(command_t *cmd) } } - ret = banban_fullfota2_start(cmd->params.ota.url, + snprintf(ota_progress_target_version, sizeof(ota_progress_target_version), + "%s", cmd->params.ota.version); + ret = banban_fullfota2_start(cmd->params.ota.url, + ota_fullfota2_progress, ota_fullfota2_finished); if (ret != 0) { RLOGE("start FULLOTA2 task failed: %d", ret); diff --git a/ql-application/threadx/interface/custom_app/ota_fullfota2.c b/ql-application/threadx/interface/custom_app/ota_fullfota2.c index 64915ff..2ebf9b3 100644 --- a/ql-application/threadx/interface/custom_app/ota_fullfota2.c +++ b/ql-application/threadx/interface/custom_app/ota_fullfota2.c @@ -22,6 +22,7 @@ typedef struct { char url[BANBAN_FULLOTA2_URL_MAX]; + banban_fullfota2_progress_cb_t progress_cb; banban_fullfota2_finished_cb_t finished_cb; } banban_fullfota2_request_t; @@ -171,6 +172,9 @@ static void banban_fullfota2_task(void *argument) printf("[FULLOTA2] download progress=%d%% bytes=%d/%d\r\n", progress, written, content_length); while (next_progress <= progress) { + if (g_fullfota2_request.progress_cb != NULL) { + g_fullfota2_request.progress_cb(next_progress); + } next_progress += 10; } } @@ -219,6 +223,7 @@ failed: } int banban_fullfota2_start(const char *url, + banban_fullfota2_progress_cb_t progress_cb, banban_fullfota2_finished_cb_t finished_cb) { int ret; @@ -239,6 +244,7 @@ int banban_fullfota2_start(const char *url, memset(&g_fullfota2_request, 0, sizeof(g_fullfota2_request)); memcpy(g_fullfota2_request.url, url, url_length + 1); + g_fullfota2_request.progress_cb = progress_cb; g_fullfota2_request.finished_cb = finished_cb; g_fullfota2_running = 1;