Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2c78208bf |
@@ -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
|
||||||
|
```
|
||||||
@@ -16,8 +16,11 @@ typedef enum {
|
|||||||
} banban_fullfota2_result_t;
|
} banban_fullfota2_result_t;
|
||||||
|
|
||||||
typedef void (*banban_fullfota2_finished_cb_t)(int result);
|
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,
|
int banban_fullfota2_start(const char *url,
|
||||||
|
banban_fullfota2_progress_cb_t progress_cb,
|
||||||
banban_fullfota2_finished_cb_t finished_cb);
|
banban_fullfota2_finished_cb_t finished_cb);
|
||||||
int banban_fullfota2_is_running(void);
|
int banban_fullfota2_is_running(void);
|
||||||
void banban_fullfota2_discard_staged_package(void);
|
void banban_fullfota2_discard_staged_package(void);
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ typedef struct {
|
|||||||
int app_mqtt_publish(char *buf, int len, int type);
|
int app_mqtt_publish(char *buf, int len, int type);
|
||||||
void build_register(void);
|
void build_register(void);
|
||||||
static void ota_report_pending_success(void);
|
static void ota_report_pending_success(void);
|
||||||
|
static char ota_progress_target_version[16] = {0};
|
||||||
|
|
||||||
enum{
|
enum{
|
||||||
MQTT_WORK_CONNECT,
|
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) {
|
if (result == BANBAN_FULLOTA2_OK) {
|
||||||
return;
|
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);
|
ota_fullfota2_finished);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
RLOGE("start FULLOTA2 task failed: %d", ret);
|
RLOGE("start FULLOTA2 task failed: %d", ret);
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ static volatile u32 g_nfc_tx_mode_deadline = 0;
|
|||||||
#define NFC_TYPE2_READ_SIZE 16
|
#define NFC_TYPE2_READ_SIZE 16
|
||||||
#define NFC_TYPE2_MEMORY_SIZE 160
|
#define NFC_TYPE2_MEMORY_SIZE 160
|
||||||
#define NFC_TYPE2_NDEF_OFFSET 16
|
#define NFC_TYPE2_NDEF_OFFSET 16
|
||||||
|
#define NFC_IMEI_DIGITS 15
|
||||||
|
|
||||||
#define NFC_IRQ_TX 0x40
|
#define NFC_IRQ_TX 0x40
|
||||||
#define NFC_IRQ_RX 0x20
|
#define NFC_IRQ_RX 0x20
|
||||||
@@ -172,11 +173,37 @@ static u8 nfc_card_emulation_send_frame(const u8 *frame, u8 length)
|
|||||||
|
|
||||||
static void nfc_type2_build_ndef(void)
|
static void nfc_type2_build_ndef(void)
|
||||||
{
|
{
|
||||||
static const char uri_suffix[] = "example.com/";
|
char device_id[32] = {0};
|
||||||
size_t uri_len = sizeof(uri_suffix) - 1;
|
char serial_number[32] = {0};
|
||||||
|
char imei[32] = {0};
|
||||||
|
char json[128];
|
||||||
|
int json_len;
|
||||||
u8 *ndef;
|
u8 *ndef;
|
||||||
u8 record_len;
|
u8 record_len;
|
||||||
|
|
||||||
|
if (hal_os_get_device_id(device_id) != 0) {
|
||||||
|
strcpy(device_id, "unknown");
|
||||||
|
}
|
||||||
|
if (hal_os_get_device_sn(serial_number) != 0) {
|
||||||
|
strcpy(serial_number, "unknown");
|
||||||
|
}
|
||||||
|
if (ql_dev_get_imei(imei, sizeof(imei)) != QL_DEV_SUCCESS ||
|
||||||
|
imei[0] == '\0') {
|
||||||
|
strcpy(imei, "unknown");
|
||||||
|
} else {
|
||||||
|
imei[NFC_IMEI_DIGITS] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
json_len = snprintf(json, sizeof(json),
|
||||||
|
"{\"device_id\":\"%s\",\"serial_number\":\"%s\","
|
||||||
|
"\"imei\":\"%s\"}",
|
||||||
|
device_id, serial_number, imei);
|
||||||
|
if (json_len < 0) {
|
||||||
|
json_len = 0;
|
||||||
|
} else if (json_len >= (int)sizeof(json)) {
|
||||||
|
json_len = sizeof(json) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
memset(nfc_type2_memory, 0, sizeof(nfc_type2_memory));
|
memset(nfc_type2_memory, 0, sizeof(nfc_type2_memory));
|
||||||
|
|
||||||
/* Static UID/manufacturer pages followed by a read-only Type 2 CC. */
|
/* Static UID/manufacturer pages followed by a read-only Type 2 CC. */
|
||||||
@@ -190,18 +217,20 @@ static void nfc_type2_build_ndef(void)
|
|||||||
nfc_type2_memory[15] = 0x0F; /* Read-only access. */
|
nfc_type2_memory[15] = 0x0F; /* Read-only access. */
|
||||||
|
|
||||||
ndef = &nfc_type2_memory[NFC_TYPE2_NDEF_OFFSET];
|
ndef = &nfc_type2_memory[NFC_TYPE2_NDEF_OFFSET];
|
||||||
record_len = (u8)(5 + uri_len);
|
record_len = (u8)(7 + json_len);
|
||||||
ndef[0] = 0x03; /* NDEF Message TLV. */
|
ndef[0] = 0x03; /* NDEF Message TLV. */
|
||||||
ndef[1] = record_len;
|
ndef[1] = record_len;
|
||||||
ndef[2] = 0xD1; /* MB, ME, SR, TNF well-known. */
|
ndef[2] = 0xD1; /* MB, ME, SR, TNF well-known. */
|
||||||
ndef[3] = 0x01; /* Type length. */
|
ndef[3] = 0x01; /* Type length. */
|
||||||
ndef[4] = (u8)(1 + uri_len); /* URI prefix code plus suffix. */
|
ndef[4] = (u8)(3 + json_len); /* Text status/lang plus JSON. */
|
||||||
ndef[5] = 'U'; /* NFC Forum URI record. */
|
ndef[5] = 'T';
|
||||||
ndef[6] = 0x04; /* URI prefix: https:// */
|
ndef[6] = 0x02; /* UTF-8, language length 2. */
|
||||||
memcpy(&ndef[7], uri_suffix, uri_len);
|
ndef[7] = 'e';
|
||||||
ndef[7 + uri_len] = 0xFE; /* Terminator TLV. */
|
ndef[8] = 'n';
|
||||||
|
memcpy(&ndef[9], json, json_len);
|
||||||
|
ndef[9 + json_len] = 0xFE; /* Terminator TLV. */
|
||||||
|
|
||||||
printf("NFC Type 2 NDEF URI: https://%s\r\n", uri_suffix);
|
printf("NFC Type 2 NDEF: %s\r\n", json);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void nfc_type2_read_pages(u8 start_page, u8 *response)
|
static void nfc_type2_read_pages(u8 start_page, u8 *response)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char url[BANBAN_FULLOTA2_URL_MAX];
|
char url[BANBAN_FULLOTA2_URL_MAX];
|
||||||
|
banban_fullfota2_progress_cb_t progress_cb;
|
||||||
banban_fullfota2_finished_cb_t finished_cb;
|
banban_fullfota2_finished_cb_t finished_cb;
|
||||||
} banban_fullfota2_request_t;
|
} 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",
|
printf("[FULLOTA2] download progress=%d%% bytes=%d/%d\r\n",
|
||||||
progress, written, content_length);
|
progress, written, content_length);
|
||||||
while (next_progress <= progress) {
|
while (next_progress <= progress) {
|
||||||
|
if (g_fullfota2_request.progress_cb != NULL) {
|
||||||
|
g_fullfota2_request.progress_cb(next_progress);
|
||||||
|
}
|
||||||
next_progress += 10;
|
next_progress += 10;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -219,6 +223,7 @@ failed:
|
|||||||
}
|
}
|
||||||
|
|
||||||
int banban_fullfota2_start(const char *url,
|
int banban_fullfota2_start(const char *url,
|
||||||
|
banban_fullfota2_progress_cb_t progress_cb,
|
||||||
banban_fullfota2_finished_cb_t finished_cb)
|
banban_fullfota2_finished_cb_t finished_cb)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
@@ -239,6 +244,7 @@ int banban_fullfota2_start(const char *url,
|
|||||||
|
|
||||||
memset(&g_fullfota2_request, 0, sizeof(g_fullfota2_request));
|
memset(&g_fullfota2_request, 0, sizeof(g_fullfota2_request));
|
||||||
memcpy(g_fullfota2_request.url, url, url_length + 1);
|
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_request.finished_cb = finished_cb;
|
||||||
g_fullfota2_running = 1;
|
g_fullfota2_running = 1;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user