ln:1.新增小程序下发ota命令,待小程序更新
authorluozheng <1732036298@qq.com>
Fri, 7 Nov 2025 09:36:27 +0000 (17:36 +0800)
committerluozheng <1732036298@qq.com>
Fri, 7 Nov 2025 09:36:27 +0000 (17:36 +0800)
2.接口文档更新

code/sdk/application/wearable/ln/nativeapp/nativeui/uiservice/src/ui_service.cpp
code/sdk/zhihui/leiniao/imsl_ln/ln_api.c
code/sdk/zhihui/leiniao/imsl_ln/ln_api.h
code/sdk/zhihui/leiniao/imsl_ln/ln_api_ota.c
code/sdk/zhihui/leiniao/imsl_ln/ln_api_web_ack.c
code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c
code/sdk/zhihui/leiniao/设备API接口文档.md

index c29510e..ca8643d 100644 (file)
@@ -786,7 +786,7 @@ void HandUpgradeDescriptionNotify(Request *request)
     std::string description(static_cast<char*>(request->data), request->len);
     WEARABLE_LOGE(WEARABLE_LOG_MODULE_UI_SERVICE, "HandUpgradeDescriptionNotify description = %s", description.c_str());
     GraphicService::GetInstance()->PostGraphicEvent(
-        std::bind(&OHOS::OtaModel::SetUpgradeDescription,OHOS::OtaModel::GetInstance(), description.c_str()));
+        std::bind(&OHOS::OtaModel::SetUpgradeDescription,OHOS::OtaModel::GetInstance(), description));
 }
 /* -------------------------------------------------------------------------- */
 #ifdef __cplusplus
index 8852939..162a361 100644 (file)
@@ -998,6 +998,15 @@ void ln_ws_process_message(const ws_data_msg_t *data_msg, ws_command_t type)
         }
         break;
     }
+    case WS_CMD_DEVICE_UPGRADE:
+    {
+        printf("[WS] DeviceUpgrade:设备升级\n");
+        if (json_str)
+        {
+            ln_parse_device_upgrade(json_str, (int)strlen(json_str));
+        }
+        break;
+    }
     case WS_CMD_PING:
     {
         printf("[WS] Ping\n");
@@ -1717,6 +1726,7 @@ void do_ln_http_server_send_data(int cmd, char *data, uint32_t len)
 /* 预创建运行期所需目录,避免因目录不存在导致文件创建失败 */
 static void ln_init_required_dirs(void)
 {
+    uapi_delete_files(LN_TOKEN_FILE);
     const char *dirs[] = {
         LN_DATA_PATH,
         LN_DATA_PATH "/token", // 存放token的
index 4a6ae7f..b4b3f6f 100644 (file)
@@ -317,7 +317,8 @@ void ln_parse_download_emoji(const char *json_str, int json_len);
 void ln_parse_delete_emoji(const char *json_str, int json_len);
 /* 上报角色表情组下载状态(从队列数据解析)*/
 void ln_emoji_report_status(char *data);
-
+/* 解析设备升级 */
+void ln_parse_device_upgrade(const char *json_str, int json_len);
 /* 从下发JSON中提取userId */
 void ln_ws_ack_user_id_from_json(const char *json_str);
 /* ========== Ack 发送接口 ========== */
@@ -329,7 +330,7 @@ int ln_ws_send_schedule_reminder_ack(int schedule_id, bool success, const char *
 int ln_ws_send_schedule_reminder_config_ack(int schedule_id, bool success, const char *msg);
 int ln_ws_send_delete_emoji_ack(const char *character_id, const char *eyesColor, const char *eyesShape, bool success, const char *msg);
 int ln_ws_send_download_emoji_ack(const char *character_id, const char *eyesColor, const char *eyesShape, bool success, const char *msg);
-
+int ln_ws_send_device_upgrade_ack(const char *update_id, const char *target_version, bool success, const char *msg);
 /* =================== 时间线状态(Timeline getStatus) =================== */
 /* 查询时间线状态(GET /v1/timeline/getStatus)*/
 uint32_t ln_timeline_get_status_http(const char *device_id,
@@ -408,6 +409,7 @@ typedef enum {
     WS_CMD_AUDIO_RECORDING_START,    /* 开始录音上传 */
     WS_CMD_AUDIO_RECORDING_END,      /* 结束录音上传 */
     WS_CMD_SENTENCE_PLAYBACK_COMPLETE, /* 单句语音播放完成 */
+    WS_CMD_DEVICE_UPGRADE,           /* 设备升级(DeviceUpgrade) */
     WS_CMD_MAX                       /* 最大命令 */
 } ws_command_t;
 
index ea53de2..9695c3e 100644 (file)
@@ -255,6 +255,180 @@ static void ln_handle_ota_task_response(const char *json_buf, int json_len)
     free(text);
 }
 
+/* 解析设备升级命令 DeviceUpgrade */
+void ln_parse_device_upgrade(const char *json_buf, int json_len)
+{
+    WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "解析设备升级命令JSON");
+    if (!json_buf || json_len <= 0)
+    {
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[OTA] parse upgrade command: invalid json");
+        return;
+    }
+
+    char *text = (char *)malloc((size_t)json_len + 1);
+    if (!text) return;
+    memcpy(text, json_buf, (size_t)json_len);
+    text[json_len] = '\0';
+
+    cJSON *root = cJSON_Parse(text);
+    if (!root)
+    {
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[OTA] parse upgrade command: json parse failed");
+        free(text);
+        return;
+    }
+
+    cJSON *data = cJSON_GetObjectItem(root, "data");
+    if (!data || !cJSON_IsObject(data))
+    {
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[OTA] upgrade command data missing");
+        cJSON_Delete(root);
+        free(text);
+        return;
+    }
+
+    /*
+        {
+            "deviceId": "DEVICE_001",
+            "timestamp": 1640995200,
+            "messageId": "msg_013",
+            "command": "DeviceUpgrade",
+            "data": {
+                "deviceId": "DEVICE_001",
+                "updateId": "upgrade_12345",
+                "sourceVersion": "1.0.0",
+                "targetVersion": "1.2.0",
+                "updateType": 1,
+                "packageId": 456,
+                "packageUrl": "https://example.com/firmware/update_1.2.0.bin",
+                "packageMd5": "a1b2c3d4e5f6789012345678901234567890",
+                "packageSize": 10485760,
+                "description": "修复若干bug,优化系统性能"
+            }
+        }
+    */
+
+    cJSON *updateId = cJSON_GetObjectItem(data, "updateId");
+    cJSON *sourceVersion = cJSON_GetObjectItem(data, "sourceVersion");
+    cJSON *targetVersion = cJSON_GetObjectItem(data, "targetVersion");
+    cJSON *updateType = cJSON_GetObjectItem(data, "updateType");
+    cJSON *packageId = cJSON_GetObjectItem(data, "packageId");
+    cJSON *packageUrl  = cJSON_GetObjectItem(data, "packageUrl");
+    cJSON *packageMd5  = cJSON_GetObjectItem(data, "packageMd5");
+    cJSON *packageSize = cJSON_GetObjectItem(data, "packageSize");
+    cJSON *description = cJSON_GetObjectItem(data, "description");
+
+    /* 校验必要字段 */
+    if (!(updateId && cJSON_IsString(updateId) && updateId->valuestring &&
+          targetVersion && cJSON_IsString(targetVersion) && targetVersion->valuestring &&
+          packageUrl && cJSON_IsString(packageUrl) && packageUrl->valuestring &&
+          packageMd5 && cJSON_IsString(packageMd5) && packageMd5->valuestring))
+    {
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[OTA] upgrade command missing required fields");
+        ln_ws_send_device_upgrade_ack(
+            updateId ? updateId->valuestring : "",
+            targetVersion ? targetVersion->valuestring : "",
+            false,
+            "缺少必要的升级信息");
+        cJSON_Delete(root);
+        free(text);
+        return;
+    }
+
+    /* 兼容 updateType 为数字或字符串的两种情况 */
+    int ut = -1;
+    if (cJSON_IsNumber(updateType)) ut = (int)updateType->valuedouble;
+    else if (cJSON_IsString(updateType)) ut = atoi(updateType->valuestring);
+    g_ln_last_update_type = ut;
+
+    //传过来的ota包大小
+    g_ln_last_package_size = 0;
+    if (packageSize && cJSON_IsNumber(packageSize)) {
+        g_ln_last_package_size = (int)packageSize->valuedouble;
+        if (g_ln_last_package_size < 0) g_ln_last_package_size = 0;
+    }
+    /* 检查存储空间(这里仅为示例,实际需根据具体平台实现) */
+    bool storage_ok = true;
+    if (g_ln_last_package_size > 0)
+    {
+        if (can_download_file(UPG_FILE_NAME, g_ln_last_package_size) == 0)
+        {
+            storage_ok = false;
+        }
+        uapi_ln_set_ota_package_size(g_ln_last_package_size);
+    }
+    if (!storage_ok)
+    {
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[OTA] insufficient storage space(存储空间不足,无法升级)");
+        ln_ws_send_device_upgrade_ack(
+            updateId->valuestring,
+            targetVersion->valuestring,
+            false,
+            "存储空间不足,无法升级");
+        cJSON_Delete(root);
+        free(text);
+        return;
+    }
+
+    WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"targetVersion=%s, updateType=%d, url=%s, md5=%s, packagesize=%d, description=%s",
+        targetVersion->valuestring, 
+        g_ln_last_update_type,
+        packageUrl->valuestring ,
+        packageMd5->valuestring ,
+        g_ln_last_package_size,
+        description->valuestring);
+
+    /* 记录必要字段供后续 AT^LN_OTA_DOWNLOAD 使用 */
+    if (g_ln_last_target_version) { free(g_ln_last_target_version); g_ln_last_target_version = NULL; }
+    if (g_ln_last_package_url) { free(g_ln_last_package_url); g_ln_last_package_url = NULL; }
+    if (g_ln_last_package_md5) { free(g_ln_last_package_md5); g_ln_last_package_md5 = NULL; }
+
+    if (targetVersion && cJSON_IsString(targetVersion)) {
+        size_t len = strlen(targetVersion->valuestring);
+        g_ln_last_target_version = (char *)malloc(len + 1);
+        if (g_ln_last_target_version) memcpy(g_ln_last_target_version, targetVersion->valuestring, len + 1);
+    }
+    if (packageUrl && cJSON_IsString(packageUrl)) {
+        size_t len = strlen(packageUrl->valuestring);
+        g_ln_last_package_url = (char *)malloc(len + 1);
+        if (g_ln_last_package_url) memcpy(g_ln_last_package_url, packageUrl->valuestring, len + 1);
+    }
+    if (packageMd5 && cJSON_IsString(packageMd5)) {
+        size_t len = strlen(packageMd5->valuestring);
+        g_ln_last_package_md5 = (char *)malloc(len + 1);
+        if (g_ln_last_package_md5) memcpy(g_ln_last_package_md5, packageMd5->valuestring, len + 1);
+    }
+
+    if (description && cJSON_IsString(description)) {
+        const char *description_ = description->valuestring;
+        push_upgrade_description_notification(description_);
+    } else {
+        push_upgrade_description_notification("");
+    }
+    
+    if (ut == 0)
+    {
+        /* 强制升级:直接触发下载 */
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] force upgrade: trigger download(强制升级:直接触发下载)");
+        push_ota_status(OTA_UI_STATUS_FORCE_START);
+    }
+    else
+    {
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA] optional upgrade: waiting for user confirmation(可选升级:等待用户确认)");
+        push_ota_status(OTA_UI_STATUS_OPTIONAL_START);
+    }
+    
+    /* 发送升级应答,确认任务已触发 */
+    ln_ws_send_device_upgrade_ack(
+        updateId->valuestring,
+        targetVersion->valuestring,
+        true,
+        "升级任务已触发,开始下载升级包");
+
+    cJSON_Delete(root);
+    free(text);
+}
+
 uint32_t ln_ota_get_task_http(const char *device_id,
                               const char *device_token,
                               const char *source_version)
index 5c621a0..7b92553 100644 (file)
@@ -518,4 +518,54 @@ int ln_ws_send_download_emoji_ack(const char *character_id, const char *eyesColo
     cJSON_free(json_string);
     cJSON_Delete(json);
     return ret;
+}
+
+/* 发送设备升级应答 DeviceUpgradeAck */
+int ln_ws_send_device_upgrade_ack(const char *update_id, const char *target_version, bool success, const char *msg)
+{
+    if (ln_ws_get_connection_state() != 2)
+    {
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Cannot send DeviceUpgradeAck, connection state: %d\n", ln_ws_get_connection_state());
+        return -1;
+    }
+
+    uint32_t timestamp;
+    uapi_get_timestampe_ss(&timestamp);
+
+    cJSON *json = cJSON_CreateObject();
+    if (!json)
+        return -1;
+
+    cJSON_AddStringToObject(json, "deviceId", uapi_ln_get_device_id());
+    cJSON_AddNumberToObject(json, "timestamp", (double)timestamp);
+
+    char message_id[64];
+    snprintf(message_id, sizeof(message_id), "ack_upgrade_%u", timestamp);
+    cJSON_AddStringToObject(json, "messageId", message_id);
+    cJSON_AddStringToObject(json, "command", "DeviceUpgradeAck");
+
+    cJSON *data = cJSON_CreateObject();
+    if (!data)
+    {
+        cJSON_Delete(json);
+        return -1;
+    }
+    cJSON_AddStringToObject(data, "updateId", (update_id && update_id[0]) ? update_id : "");
+    cJSON_AddStringToObject(data, "targetVersion", (target_version && target_version[0]) ? target_version : "");
+    cJSON_AddBoolToObject(data, "success", success);
+    cJSON_AddStringToObject(data, "message", msg ? msg : (success ? "升级任务已触发" : "升级任务触发失败"));
+    cJSON_AddItemToObject(json, "data", data);
+
+    char *json_string = cJSON_Print(json);
+    if (!json_string)
+    {
+        cJSON_Delete(json);
+        return -1;
+    }
+
+    int ret = uapi_ws_send_text(json_string);
+    WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Sent DeviceUpgradeAck: %s\n", json_string);
+    cJSON_free(json_string);
+    cJSON_Delete(json);
+    return ret;
 }
\ No newline at end of file
index a61e206..d3d362d 100644 (file)
@@ -1584,6 +1584,8 @@ static ws_command_t ln_ws_parse_command_string(const char *command_str)
         return WS_CMD_CHOOSE_STORY_RESPONSE;
     } else if (strcmp(command_str, "RandomStoryResponse") == 0) {
         return WS_CMD_RANDOM_STORY_RESPONSE;
+    } else if (strcmp(command_str, "DeviceUpgrade") == 0) {
+        return WS_CMD_DEVICE_UPGRADE;
     } else {
         return WS_CMD_UNKNOWN;
     }
index a0cbe82..10b9493 100644 (file)
@@ -1382,7 +1382,7 @@ sequenceDiagram
             Device->>Device: 播放提醒内容\r
         and 哄睡模式询问\r
             Device->>Device: 识别到哄睡意图\r
-            Device->>Server: WebSocket发送<br/>{"command": "SleepModeInquire", "data": {"intention": "START_SLEEP_MODE", "chatId": "..."}}\r
+            Device->>Server: WebSocket发送<br/>{"command": "SleepModeInquire", "data": {"intention": "StartSleepMode", "chatId": "..."}}\r
             Server->>Server: 生成询问内容\r
             Server->>Device: WebSocket推送<br/>{"command": "SleepModeInquireAck", "data": {"textContent": "...", "audioUrl": "...", ...}}\r
             Server->>Device: WebSocket二进制消息<br/>流式发送音频数据\r
@@ -1853,7 +1853,7 @@ WebSocket连接需要设备Token认证,有以下两种方式传递Token:
   "messageId": "sleep_inquire_001",\r
   "command": "SleepModeInquire",\r
   "data": {\r
-    "intention": "START_SLEEP_MODE",\r
+    "intention": "StartSleepMode",\r
     "chatId": "chat_12345"\r
   }\r
 }\r
@@ -2376,6 +2376,57 @@ WebSocket连接需要设备Token认证,有以下两种方式传递Token:
 - 失败可能原因:网络异常、存储空间不足、下载超时等\r
 - 建议设备实现断点续传机制\r
 \r
+#### 设备升级应答 (DeviceUpgradeAck)\r
+**命令描述**:设备端对设备升级命令的应答,确认升级任务触发状态\r
+\r
+**消息格式**:\r
+```json\r
+{\r
+  "deviceId": "设备ID",\r
+  "timestamp": 1640995200,\r
+  "messageId": "消息ID",\r
+  "command": "DeviceUpgradeAck",\r
+  "data": {\r
+    "updateId": "任务ID",\r
+    "targetVersion": "目标版本号",\r
+    "success": true,\r
+    "message": "升级任务已触发"\r
+  }\r
+}\r
+```\r
+\r
+**消息示例**:\r
+```json\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995200,\r
+  "messageId": "ack_upgrade_001",\r
+  "command": "DeviceUpgradeAck",\r
+  "data": {\r
+    "updateId": "upgrade_12345",\r
+    "targetVersion": "1.2.0",\r
+    "success": true,\r
+    "message": "升级任务已触发,开始下载升级包"\r
+  }\r
+}\r
+```\r
+\r
+**data字段说明**:\r
+\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| updateId | String | 任务ID,与升级命令中的updateId对应 |\r
+| targetVersion | String | 目标版本号 |\r
+| success | Boolean | 是否成功触发升级任务 |\r
+| message | String | 结果描述信息 |\r
+\r
+**协议说明**:\r
+- 设备收到DeviceUpgrade命令后,开始下载升级包并发送此应答\r
+- success为true表示成功触发升级任务(开始下载),false表示触发失败\r
+- 此应答仅表示升级任务是否成功触发,不代表升级最终结果\r
+- 失败可能原因:存储空间不足、网络异常、版本校验失败等\r
+- 升级成功后设备会自动重启,重启后以新版本运行\r
+\r
 ### 服务端业务消息\r
 \r
 服务端通过WebSocket连接推送业务消息给设备端,消息采用JSON格式,消息结构如下:\r
@@ -2541,7 +2592,7 @@ WebSocket连接需要设备Token认证,有以下两种方式传递Token:
   "messageId": "sleep_inquire_ack_001",\r
   "command": "SleepModeInquireAck",\r
   "data": {\r
-    "intention": "START_SLEEP_MODE",\r
+    "intention": "StartSleepMode",\r
     "textContent": "你想听故事吗?如果想听,请告诉我你想听什么类型的故事",\r
     "audioUrl": "https://example.com/audio/sleep_inquire.mp3",\r
     "audioDuration": 3000,\r
@@ -2839,7 +2890,9 @@ WebSocket连接需要设备Token认证,有以下两种方式传递Token:
   "messageId": "msg_010",\r
   "command": "ScheduleReminderConfig",\r
   "data": {\r
-      "scheduleName": "七点起床"\r
+      "scheduleName": "七点起床",\r
+      "scheduleId": 1,\r
+      "displayText": "提醒设置成功"\r
   }\r
 }\r
 ```\r
@@ -2849,6 +2902,10 @@ WebSocket连接需要设备Token认证,有以下两种方式传递Token:
 | 字段名 | 类型 | 描述 |\r
 |--------|------|------|\r
 | scheduleName | String | 日程名称 |\r
+| scheduleId | Integer | 日程ID |\r
+| displayText | String | 展示文本,如"提醒设置成功"、"提醒事项已删除" |\r
+\r
+设备端展示“displayText”字段的文本。\r
 \r
 #### 14. 下载表情 (DownloadEmoji)\r
 **命令描述**:通知设备下载指定角色的表情包\r
@@ -2932,6 +2989,73 @@ WebSocket连接需要设备Token认证,有以下两种方式传递Token:
 - 删除完成后,调用`/v1/emoji/reportEmoji`接口上报删除状态\r
 - 删除操作应清理本地缓存的表情文件\r
 \r
+#### 16. 设备升级 (DeviceUpgrade)\r
+**命令描述**:通知设备执行固件升级\r
+**消息示例**:\r
+```json\r
+{\r
+  "deviceId": "DEVICE_001",\r
+  "timestamp": 1640995200,\r
+  "messageId": "msg_013",\r
+  "command": "DeviceUpgrade",\r
+  "data": {\r
+    "deviceId": "DEVICE_001",\r
+    "updateId": "upgrade_12345",\r
+    "sourceVersion": "1.0.0",\r
+    "targetVersion": "1.2.0",\r
+    "updateType": 1,\r
+    "description": "修复若干bug,优化系统性能",\r
+    "packageId": 456,\r
+    "packageUrl": "https://example.com/firmware/update_1.2.0.bin",\r
+    "packageMd5": "a1b2c3d4e5f6789012345678901234567890",\r
+    "packageSize": 10485760\r
+  }\r
+}\r
+```\r
+\r
+**data字段说明**:\r
+\r
+| 字段名 | 类型 | 描述 |\r
+|--------|------|------|\r
+| deviceId | String | 设备ID |\r
+| updateId | String | 任务ID,用于上报更新结果 |\r
+| sourceVersion | String | 源版本号 |\r
+| targetVersion | String | 目标版本号 |\r
+| updateType | Integer | 升级类型:0-手动升级,1-强制升级,2-强制下载 |\r
+| description | String | 升级描述信息 |\r
+| packageId | Integer | 升级包ID |\r
+| packageUrl | String | 升级包下载地址 |\r
+| packageMd5 | String | 升级包MD5校验值 |\r
+| packageSize | Long | 升级包大小(字节) |\r
+\r
+**升级类型说明**:\r
+\r
+| updateType值 | 说明 |\r
+|-------------|------|\r
+| 0 | 紧急更新 | \r
+| 1 | 重要更新 | \r
+| 2 | 普通更新 |\r
+\r
+\r
+**设备处理说明**:\r
+- 设备收到此命令后,根据updateType判断升级策略\r
+- 下载升级包:从packageUrl下载固件文件\r
+- 校验完整性:使用packageMd5校验下载的文件\r
+- 发送应答:下载开始时发送DeviceUpgradeAck应答确认触发结果\r
+- 执行升级:校验通过后,执行固件升级操作\r
+- 设备重启:升级完成后自动重启,以新版本运行\r
+- 错误处理:如果下载失败或校验失败,应在应答中说明失败原因\r
+\r
+**升级流程**:\r
+1. 小程序端触发升级操作\r
+2. 服务端通过Kafka发送升级消息\r
+3. device-api模块消费消息并通过WebSocket推送给设备\r
+4. 设备收到命令,开始下载升级包\r
+5. 设备发送DeviceUpgradeAck应答(表示开始升级)\r
+6. 下载完成后校验MD5\r
+7. 校验通过后执行升级并重启\r
+8. 重启后以新版本重新获取设备Token\r
+\r
 \r
 ## 7. 表情管理接口\r
 \r