From fca67101824a04561ffecd04f66901493a3331cc Mon Sep 17 00:00:00 2001 From: luozheng <1732036298@qq.com> Date: Thu, 4 Sep 2025 17:58:23 +0800 Subject: [PATCH] =?utf8?q?=E9=9B=B7=E9=B8=9F=EF=BC=9Aota=E6=96=87=E4=BB=B6?= =?utf8?q?OTA=E5=8D=87=E7=BA=A7=E4=BB=BB=E5=8A=A1=E6=8E=A5=E5=8F=A3at?= =?utf8?q?=E9=AA=8C=E8=AF=81=EF=BC=8Cota=E6=96=87=E4=BB=B6=E5=B7=B2?= =?utf8?q?=E7=BB=8F=E5=8F=AF=E4=BB=A5=E4=B8=8B=E8=BD=BD=EF=BC=8C=E8=BF=98?= =?utf8?q?=E5=B7=AE=E4=B8=8B=E8=BD=BD=E8=BF=9B=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../zhihui/leiniao/imsl_ln/leiniao_api_at.c | 13 + code/sdk/zhihui/leiniao/imsl_ln/ln_api.c | 518 ++++++++++++++++-- code/sdk/zhihui/leiniao/imsl_ln/ln_api.h | 7 + 3 files changed, 501 insertions(+), 37 deletions(-) diff --git a/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c b/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c index 488bc5a6c..5564caa99 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c @@ -624,6 +624,18 @@ static uint32_t ln_ota_get_task_at(uint8_t *para, uint32_t paraLen, char *strCom return ERRCODE_SUCC; } +/* + * 职责单一:仅触发“根据最近一次getTask结果下载并校验MD5”的动作 + * 不负责:查询任务/升级/重启/重取token + */ +static uint32_t ln_ota_download_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + VAD_UNUSED(para); VAD_UNUSED(paraLen); VAD_UNUSED(strCommonCmd); + printf("[LN][OTA_DOWNLOAD]\n"); + do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_OTA_DOWNLOAD, NULL, 0); + return ERRCODE_SUCC; +} + static const at_cmd_table_t g_ln_api_cmd[] = { /* ----------------------------------- VAD ---------------------------------- */ @@ -647,6 +659,7 @@ static const at_cmd_table_t g_ln_api_cmd[] = { {(char *)"AT^LN_DEV_CHARACTER", ln_device_get_character_at, (char *)"获取设备角色信息(JSON,GET): AT^LN_DEV_CHARACTER"}, {(char *)"AT^LN_DEV_USERID", ln_device_get_userid_at, (char *)"获取设备绑定用户ID(JSON,GET): AT^LN_DEV_USERID"}, {(char *)"AT^LN_OTA_GET_TASK", ln_ota_get_task_at, (char *)"获取OTA升级任务(JSON,GET): AT^LN_OTA_GET_TASK"}, + {(char *)"AT^LN_OTA_DOWNLOAD", ln_ota_download_at, (char *)"下载OTA升级包(JSON,GET->下载): AT^LN_OTA_DOWNLOAD"}, }; at_cmd_table_t *get_leiniao_api_cmd_table(uint32_t *cmd_count) diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c index eb8055ebd..e5ad23206 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c @@ -3,6 +3,8 @@ #include "netstack/http/include/httpclient.h" /* for HMAC-SHA256 */ #include "mbedtls/md.h" +/* for MD5 file verify */ +#include "mbedtls/md5.h" /* for JSON parsing */ #include "cJSON.h" #include @@ -23,6 +25,13 @@ static char *g_device_iccid = NULL; static char *g_device_model = NULL; static char *g_device_version = NULL; +/* 记录最近一次 OTA 查询的升级类型与保存路径 */ +static int g_ln_last_update_type = -1; // -1:未知 0:可选 1:强制 +static char g_ln_last_saved_path[256] = {0}; // 最近一次保存的 OTA 包路径 +static char g_ln_last_package_url[512] = {0}; // 最近一次查询到的 OTA 包 URL +static char g_ln_last_target_version[64] = {0}; // 最近一次查询到的 OTA 包目标版本 +static char g_ln_last_package_md5[65] = {0}; // 最近一次查询到的 OTA 包 MD5 + /* 获取设备id: 设备唯一标识 */ char *uapi_ln_get_device_id(void) { @@ -41,7 +50,7 @@ char *uapi_ln_get_device_model(void) g_device_model = "abmybox_1"; return g_device_model; } -/* 获取设备version */ +/* 获取设备固件版本version */ char *uapi_ln_get_device_version(void) { g_device_version = "1.0.0"; @@ -67,6 +76,16 @@ void uapi_ln_clear_device_token(void) g_device_token = NULL; } } +/* 提供给外部查询最近一次 OTA 查询到的升级类型 */ +int uapi_ln_ota_get_update_type(void) +{ + return g_ln_last_update_type; +} +/* 提供给外部查询最近一次保存的 OTA 包路径 */ +const char *uapi_ln_ota_get_saved_path(void) +{ + return (g_ln_last_saved_path[0] != '\0') ? g_ln_last_saved_path : NULL; +} /* ============================== 雷鸟设备Token获取 ============================== */ @@ -92,6 +111,9 @@ void uapi_ln_clear_device_token(void) #define LN_DEVICE_GET_USER_ID_URL LN_BASE_URL LN_DEVICE_GET_USER_ID_PATH #define LN_OTA_GET_TASK_URL LN_BASE_URL LN_OTA_GET_TASK_PATH #define LN_SIGN_SECRET "vDdXVXgPUEUM6_piUjz5AEhMvYTSFzQdPaN-ogKnSus=" +/* OTA 包保存目录(最终文件仍将放在 /user/ 下)*/ +#define LN_OTA_SAVE_DIR "/user" + /* 生成[a-zA-Z0-9]随机串 */ static void ln_gen_nonce(char *out, size_t len) @@ -1189,6 +1211,10 @@ static uint32_t ln_ota_get_task_http(const char *device_id, } if (clientData.responseBuf && clientData.responseBufLen > 0) { + /* 关键打印:返回的响应数据 */ + printf("[OTA] getTask response: %.*s\n", + clientData.responseContentLen > 0 ? clientData.responseContentLen : clientData.responseBufLen, + clientData.responseBuf); int real_len = clientData.responseContentLen > 0 ? clientData.responseContentLen : clientData.responseBufLen; do_ln_http_server_recv_data(LN_HTTP_RECV_OTA_TASK, clientData.responseBuf, (uint32_t)real_len); } @@ -1200,6 +1226,399 @@ end: +/* ============================ OTA 包下载实现 Start ============================ */ +/* 将响应缓冲写入文件 */ +static int ln_write_buffer_to_file(const char *save_path, const char *buf, int content_len, int buf_len) +{ + if (save_path == NULL || buf == NULL) + { + return -1; + } + printf("[OTA] write file: path=%s, content_len=%d, buf_len=%d\n", save_path, content_len, buf_len); + int actual_size = content_len > 0 ? content_len : buf_len; + if (actual_size <= 0) + { + printf("[OTA] write file failed: invalid size\n"); + return -1; + } + FILE *fp = fopen(save_path, "wb"); + if (fp == NULL) + { + printf("[OTA] write file failed: open error\n"); + return -1; + } + size_t written = fwrite(buf, 1, (size_t)actual_size, fp); + fclose(fp); + printf("[OTA] write file done: written=%zu\n", written); + return (written == (size_t)actual_size) ? 0 : -1; +} + +/* 计算文件MD5(小写hex)*/ +static int ln_calc_file_md5_lower_hex(const char *file_path, char out_hex[33]) +{ + if (file_path == NULL || out_hex == NULL) + { + return -1; + } + unsigned char md5_ctx_buf[16] = {0}; + mbedtls_md5_context ctx; + mbedtls_md5_init(&ctx); + if (mbedtls_md5_starts(&ctx) != 0) + { + mbedtls_md5_free(&ctx); + return -1; + } + FILE *fp = fopen(file_path, "rb"); + if (!fp) + { + mbedtls_md5_free(&ctx); + return -1; + } + unsigned char buf[4096]; + size_t n; + while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) + { + if (mbedtls_md5_update(&ctx, buf, n) != 0) + { + fclose(fp); + mbedtls_md5_free(&ctx); + return -1; + } + } + fclose(fp); + if (mbedtls_md5_finish(&ctx, md5_ctx_buf) != 0) + { + mbedtls_md5_free(&ctx); + return -1; + } + mbedtls_md5_free(&ctx); + static const char hex[] = "0123456789abcdef"; + for (int i = 0; i < 16; i++) + { + out_hex[i * 2] = hex[(md5_ctx_buf[i] >> 4) & 0x0F]; + out_hex[i * 2 + 1] = hex[md5_ctx_buf[i] & 0x0F]; + } + out_hex[32] = '\0'; + return 0; +} + +/* 简单下载器:支持 http/https 全URL,保存到 save_path */ +static int ln_download_file_to_path(const char *full_url, const char *save_path) +{ + if (!full_url || !save_path) + { + return -1; + } + printf("[OTA] start download: url=%s -> %s\n", full_url, save_path); + + /* CAT1 网络保活处理 */ + cat1_api_add_cat1_vote(); cat1_api_wakeup_cat1(1); cat1_api_keep_wakeup(1); cat1_api_disable_cat1_cmd(0); + + HttpClient client = (HttpClient){0}; + HttpClientData clientData = (HttpClientData){0}; + + /* 判断协议 */ + bool is_https = (strstr(full_url, "https://") == full_url); + client.isHttp = is_https ? false : true; + client.remotePort = is_https ? 443 : 80; + + HTTPC_RESULT prep = ln_http_prepare_auto(&clientData, 2048, 256 * 1024); + if (prep != HTTP_SUCCESS) + { + return -1; + } + + HTTPC_RESULT res = HttpClientGetRequest(&client, full_url, &clientData); + if (res != HTTP_SUCCESS) + { + printf("[OTA] download request failed: %d\n", res); + HttpClientUnprepare(&clientData); + HttpClientClse(&client); + return -1; + } + + int code = HttpClientGetResponseCode(&client); + if (code != 200) + { + printf("[OTA] download HTTP code=%d\n", code); + HttpClientUnprepare(&clientData); + HttpClientClse(&client); + return -1; + } + + if (clientData.responseBuf == NULL || clientData.responseBufLen <= 0) + { + printf("[OTA] download empty body\n"); + HttpClientUnprepare(&clientData); + HttpClientClse(&client); + return -1; + } + + int content_len = clientData.responseContentLen > 0 ? clientData.responseContentLen : clientData.responseBufLen; + int ret = ln_write_buffer_to_file(save_path, clientData.responseBuf, content_len, clientData.responseBufLen); + if (ret == 0) { + printf("[OTA] download saved: %s (%d bytes)\n", save_path, content_len); + } else { + printf("[OTA] download save failed\n"); + } + + HttpClientUnprepare(&clientData); + HttpClientClse(&client); + return ret; +} + +/* 仅下载:根据最近一次 getTask 的记录字段执行下载,不再主动请求任务 */ +static uint32_t ln_ota_download_flow_http(const char *device_id, + const char *device_token, + const char *source_version) +{ + (void)device_id; + (void)device_token; + (void)source_version; + if (g_ln_last_target_version[0] == '\0' || g_ln_last_package_url[0] == '\0') + { + printf("[OTA] no cached task to download, please GET_TASK first\n"); + return ERRCODE_FAIL; + } + /* 保存路径:LN_OTA_SAVE_DIR/upgrade_.bin */ + char save_path[256]; + snprintf(save_path, sizeof(save_path), LN_OTA_SAVE_DIR "/upgrade_%s.bin", g_ln_last_target_version); + memset(g_ln_last_saved_path, 0, sizeof(g_ln_last_saved_path)); + strncpy(g_ln_last_saved_path, save_path, sizeof(g_ln_last_saved_path) - 1); + + printf("[OTA] download only: version=%s, url=%s -> %s\n", g_ln_last_target_version, g_ln_last_package_url, save_path); + int dres = ln_download_file_to_path(g_ln_last_package_url, save_path); + if (dres != 0) + { + char msg[256]; + int n = snprintf(msg, sizeof(msg), "{\"status\":%d,\"error\":\"download failed\"}", 2); + if (n > 0) do_ln_http_server_recv_data(LN_HTTP_RECV_OTA_DOWNLOAD_MSG, msg, (uint32_t)n); + return ERRCODE_FAIL; + } + + /* 发送完成消息(不在此处做MD5,保持AT^LN_OTA_DOWNLOAD单一职责:下载)*/ + char done[512]; + int m = snprintf(done, sizeof(done), "{\"status\":%d,\"filePath\":\"%s\",\"md5\":\"%s\"}", 1, save_path, g_ln_last_package_md5); + if (m > 0) do_ln_http_server_recv_data(LN_HTTP_RECV_OTA_DOWNLOAD_MSG, done, (uint32_t)m); + return ERRCODE_SUCC; +} + + +/* 在接收线程内使用:解析 OTA 任务 JSON,决定是否触发下载 */ +static void ln_handle_ota_task_response(const char *json_buf, int json_len) +{ + if (!json_buf || json_len <= 0) + { + printf("[OTA] handle ota task: invalid json\n"); + 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) + { + printf("[OTA] handle ota task: json parse failed\n"); + free(text); + return; + } + + cJSON *errorCode = cJSON_GetObjectItem(root, "errorCode"); + if (errorCode && cJSON_IsString(errorCode)) + { + /* 无升级或服务端错误 */ + printf("[OTA] task errorCode=%s\n", errorCode->valuestring); + cJSON_Delete(root); + free(text); + return; + } + + cJSON *data = cJSON_GetObjectItem(root, "data"); + if (!data || !cJSON_IsObject(data)) + { + printf("[OTA] task data missing\n"); + cJSON_Delete(root); + free(text); + return; + } + + cJSON *targetVersion = cJSON_GetObjectItem(data, "targetVersion"); + cJSON *updateType = cJSON_GetObjectItem(data, "updateType"); + cJSON *packageUrl = cJSON_GetObjectItem(data, "packageUrl"); + cJSON *packageMd5 = cJSON_GetObjectItem(data, "packageMd5"); + if (!(targetVersion && cJSON_IsString(targetVersion) && updateType && (cJSON_IsNumber(updateType) || cJSON_IsString(updateType)))) + { + printf("[OTA] task fields invalid\n"); + 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; + + printf("[OTA] task parsed: targetVersion=%s updateType=%d url=%s md5=%s\n", + targetVersion->valuestring, ut, + (packageUrl && cJSON_IsString(packageUrl)) ? packageUrl->valuestring : "", + (packageMd5 && cJSON_IsString(packageMd5)) ? packageMd5->valuestring : ""); + /* 记录必要字段供后续 AT^LN_OTA_DOWNLOAD 使用 */ + memset(g_ln_last_target_version, 0, sizeof(g_ln_last_target_version)); // 目标版本 + memset(g_ln_last_package_url, 0, sizeof(g_ln_last_package_url)); // 升级包URL + memset(g_ln_last_package_md5, 0, sizeof(g_ln_last_package_md5)); // 升级包MD5 + if (targetVersion && cJSON_IsString(targetVersion)) { + strncpy(g_ln_last_target_version, targetVersion->valuestring, sizeof(g_ln_last_target_version) - 1); + } + if (packageUrl && cJSON_IsString(packageUrl)) { + strncpy(g_ln_last_package_url, packageUrl->valuestring, sizeof(g_ln_last_package_url) - 1); + } + if (packageMd5 && cJSON_IsString(packageMd5)) { + strncpy(g_ln_last_package_md5, packageMd5->valuestring, sizeof(g_ln_last_package_md5) - 1); + } + + if (ut == 1) + { + /* 强制升级:直接触发下载 */ + printf("[OTA] force upgrade: trigger download(强制升级:直接触发下载)\n"); + do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_OTA_DOWNLOAD, NULL, 0); + } + else if (ut == 0) + { + /* 可选升级:此处仅提示,实际是否下载由上层UI决定(例如用户确认后再触发)*/ + printf("[OTA] optional upgrade: waiting for user confirmation(可选升级:等待用户确认)\n"); + /* 如需自动下载,可取消下面注释 */ + // do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_OTA_DOWNLOAD, NULL, 0); + } + else + { + printf("[OTA] unknown updateType, skip\n"); + } + + cJSON_Delete(root); + free(text); +} +/* OTA下载状态枚举(对应时序图中的下载阶段)*/ +typedef enum { + OTA_DOWNLOAD_STATUS_DOWNLOADING = 0, /* 下载中 */ + OTA_DOWNLOAD_STATUS_COMPLETED = 1, /* 下载完成 */ + OTA_DOWNLOAD_STATUS_FAILED = 2, /* 下载失败 */ + OTA_DOWNLOAD_STATUS_PROGRESS = 3 /* 进度更新 */ +} OtaDownloadStatus; + +/* OTA下载消息结构体(简化版)*/ +typedef struct { + OtaDownloadStatus status; /* 下载状态 */ + int progress; /* 进度(0-100) */ + char file_path[256]; /* 文件路径 */ + char error_msg[128]; /* 错误信息 */ + char md5[65]; /* MD5(来自任务)*/ +} OtaDownloadMsg; + +/* 解析OTA下载消息JSON(简化实现)*/ +static int parse_ota_download_msg(const char *json_str, OtaDownloadMsg *msg) +{ + if (!json_str || !msg) return -1; + cJSON *root = cJSON_Parse(json_str); + if (!root) return -1; + + memset(msg, 0, sizeof(*msg)); + + cJSON *status = cJSON_GetObjectItem(root, "status"); + if (status && cJSON_IsNumber(status)) { + msg->status = (OtaDownloadStatus)status->valueint; + } + cJSON *progress = cJSON_GetObjectItem(root, "progress"); + if (progress && cJSON_IsNumber(progress)) { + msg->progress = progress->valueint; + if (msg->progress < 0) msg->progress = 0; + if (msg->progress > 100) msg->progress = 100; + } + cJSON *filePath = cJSON_GetObjectItem(root, "filePath"); + if (filePath && cJSON_IsString(filePath)) { + snprintf(msg->file_path, sizeof(msg->file_path), "%s", filePath->valuestring); + } + cJSON *error = cJSON_GetObjectItem(root, "error"); + if (error && cJSON_IsString(error)) { + snprintf(msg->error_msg, sizeof(msg->error_msg), "%s", error->valuestring); + } + cJSON *md5 = cJSON_GetObjectItem(root, "md5"); + if (md5 && cJSON_IsString(md5)) { + snprintf(msg->md5, sizeof(msg->md5), "%s", md5->valuestring); + } + + cJSON_Delete(root); + return 0; +} + +/* 校验文件MD5是否匹配(不区分大小写),返回1=通过,0=不通过,-1=出错 */ +static int ln_ota_is_md5_ok(const char *file_path, const char *expected_md5) +{ + if (file_path == NULL || expected_md5 == NULL || expected_md5[0] == '\0') + { + printf("[OTA] md5 verify: invalid args\n"); + return -1; + } + char calc[33] = {0}; + if (ln_calc_file_md5_lower_hex(file_path, calc) != 0) + { + printf("[OTA] md5 verify: calc failed for %s\n", file_path); + return -1; + } + /* 规范化期望md5为小写并截断为32字符 */ + char expect_lower[33] = {0}; + size_t elen = strlen(expected_md5); + size_t copy_len = elen > 32 ? 32 : elen; + for (size_t i = 0; i < copy_len; i++) + { + char ch = expected_md5[i]; + if (ch >= 'A' && ch <= 'Z') ch = (char)(ch - 'A' + 'a'); + expect_lower[i] = ch; + } + expect_lower[32] = '\0'; + + int ok = (strcmp(calc, expect_lower) == 0) ? 1 : 0; + printf("[OTA] md5 verify result=%s, calc=%s, expect=%s\n", ok ? "OK" : "FAIL", calc, expect_lower); + return ok; +} + +/* OTA下载消息处理(对接时序图中的后续流程)*/ +static void handle_ota_download_msg(const OtaDownloadMsg *msg) +{ + if (!msg) return; + switch (msg->status) { + case OTA_DOWNLOAD_STATUS_PROGRESS: + printf("[OTA_DOWNLOAD] 下载进度: %d%%\n", msg->progress); + break; + case OTA_DOWNLOAD_STATUS_COMPLETED: + printf("[OTA_DOWNLOAD] 下载完成,文件路径: %s\n", msg->file_path); + if (msg->file_path[0] && msg->md5[0]) { + printf("[OTA_DOWNLOAD] 开始MD5校验: %s\n", msg->md5); + int md5_ok = ln_ota_is_md5_ok(msg->file_path, msg->md5); + if (md5_ok == 1) { + printf("[OTA_DOWNLOAD] MD5校验成功,准备重启升级\n"); + /* 外部可在此触发升级动作 */ + } else if (md5_ok == 0) { + printf("[OTA_DOWNLOAD] MD5校验失败,建议重新下载\n"); + } else { + printf("[OTA_DOWNLOAD] MD5校验出错\n"); + } + } + break; + case OTA_DOWNLOAD_STATUS_FAILED: + printf("[OTA_DOWNLOAD] 下载失败: %s\n", msg->error_msg); + break; + default: + printf("[OTA_DOWNLOAD] 未知状态: %d\n", msg->status); + break; + } +} + +/* ============================ OTA 包下载实现 End ============================ */ + // 发送队列 static void ln_http_server_send_task(void const *arg) { @@ -1220,7 +1639,7 @@ static void ln_http_server_send_task(void const *arg) { if (mail.data != NULL && mail.len == sizeof(ln_device_token_req_t)) { - printf("LN_HTTP_SERVICE_CMD_GET_DEVICE_TOKEN\n"); + printf("LN_HTTP_SERVICE_CMD_GET_DEVICE_TOKEN:获取雷鸟设备Token\n"); ln_device_token_req_t req; memcpy(&req, mail.data, sizeof(req)); ln_get_device_token_http(req.device_id, req.device_iccid, req.device_model, req.device_version); @@ -1239,7 +1658,7 @@ static void ln_http_server_send_task(void const *arg) else if (mail.cmd == LN_HTTP_SERVICE_CMD_STORY_CHOOSE) { if (mail.data && mail.len == sizeof(ln_story_choose_req_t)) { - printf("LN_HTTP_SERVICE_CMD_STORY_CHOOSE\n"); + printf("LN_HTTP_SERVICE_CMD_STORY_CHOOSE:选择故事\n"); ln_story_choose_req_t req; memcpy(&req, mail.data, sizeof(req)); ln_story_choose_http(uapi_ln_get_device_id(), uapi_ln_get_device_token(), req.audio_path); } @@ -1247,36 +1666,41 @@ static void ln_http_server_send_task(void const *arg) else if (mail.cmd == LN_HTTP_SERVICE_CMD_STORY_RANDOM) { if (mail.data && mail.len == sizeof(ln_story_random_req_t)) { - printf("LN_HTTP_SERVICE_CMD_STORY_RANDOM\n"); + printf("LN_HTTP_SERVICE_CMD_STORY_RANDOM:随机故事\n"); ln_story_random_req_t req; memcpy(&req, mail.data, sizeof(req)); ln_story_random_http(uapi_ln_get_device_id(), uapi_ln_get_device_token(), req.story_theme[0] ? req.story_theme : NULL); } } else if (mail.cmd == LN_HTTP_SERVICE_CMD_STORY_EXIT_INQUIRY) { - printf("LN_HTTP_SERVICE_CMD_STORY_EXIT_INQUIRY\n"); + printf("LN_HTTP_SERVICE_CMD_STORY_EXIT_INQUIRY:获取退出哄睡询问\n"); ln_story_exit_inquiry_http(uapi_ln_get_device_id(), uapi_ln_get_device_token()); } else if (mail.cmd == LN_HTTP_SERVICE_CMD_STORY_FINISH) { - printf("LN_HTTP_SERVICE_CMD_STORY_FINISH\n"); + printf("LN_HTTP_SERVICE_CMD_STORY_FINISH:确认结束哄睡\n"); ln_story_finish_http(uapi_ln_get_device_id(), uapi_ln_get_device_token()); } else if (mail.cmd == LN_HTTP_SERVICE_CMD_GET_CHARACTER) { - printf("LN_HTTP_SERVICE_CMD_GET_CHARACTER\n"); + printf("LN_HTTP_SERVICE_CMD_GET_CHARACTER:获取设备角色信息\n"); ln_device_get_character_http(uapi_ln_get_device_id(), uapi_ln_get_device_token()); } else if (mail.cmd == LN_HTTP_SERVICE_CMD_GET_USER_ID) { - printf("LN_HTTP_SERVICE_CMD_GET_USER_ID\n"); + printf("LN_HTTP_SERVICE_CMD_GET_USER_ID:获取设备绑定用户ID\n"); ln_device_get_user_id_http(uapi_ln_get_device_id(), uapi_ln_get_device_token()); } else if (mail.cmd == LN_HTTP_SERVICE_CMD_OTA_GET_TASK) { - printf("LN_HTTP_SERVICE_CMD_OTA_GET_TASK\n"); + printf("LN_HTTP_SERVICE_CMD_OTA_GET_TASK:获取OTA升级任务\n"); ln_ota_get_task_http(uapi_ln_get_device_id(), uapi_ln_get_device_token(), uapi_ln_get_device_version()); } + else if (mail.cmd == LN_HTTP_SERVICE_CMD_OTA_DOWNLOAD) + { + printf("LN_HTTP_SERVICE_CMD_OTA_DOWNLOAD:下载OTA升级包\n"); + ln_ota_download_flow_http(uapi_ln_get_device_id(), uapi_ln_get_device_token(), uapi_ln_get_device_version()); + } if (mail.data != NULL) { printf("free(mail.data)\n"); @@ -1299,38 +1723,58 @@ static void ln_http_server_recv_task(void const *arg) OSAL_MSGQ_WAIT_FOREVER); if (ret != OSAL_SUCCESS) { + printf("[RECV_TASK] 消息读取失败: %d\n", ret); continue; } - if (mail.cmd == LN_HTTP_SERVICE_CMD_SSE_RECV - || mail.cmd == LN_HTTP_RECV_STORY_CHOOSE - || mail.cmd == LN_HTTP_RECV_STORY_RANDOM - || mail.cmd == LN_HTTP_RECV_STORY_EXIT - || mail.cmd == LN_HTTP_RECV_STORY_FINISH - || mail.cmd == LN_HTTP_RECV_DEVICE_CHARACTER - || mail.cmd == LN_HTTP_RECV_DEVICE_USER_ID - || mail.cmd == LN_HTTP_RECV_OTA_TASK) - { - if (mail.data && mail.len) - { - /* 分类打印,便于区分不同来源 */ - const char *tag = (mail.cmd == LN_HTTP_SERVICE_CMD_SSE_RECV) ? "SPEECH_CHAT" : - (mail.cmd == LN_HTTP_RECV_STORY_CHOOSE) ? "STORY_CHOOSE" : - (mail.cmd == LN_HTTP_RECV_STORY_RANDOM) ? "STORY_RANDOM" : - (mail.cmd == LN_HTTP_RECV_STORY_EXIT) ? "STORY_EXIT" : - (mail.cmd == LN_HTTP_RECV_STORY_FINISH) ? "STORY_FINISH" : - (mail.cmd == LN_HTTP_RECV_DEVICE_CHARACTER) ? "DEVICE_CHARACTER" : - (mail.cmd == LN_HTTP_RECV_DEVICE_USER_ID) ? "DEVICE_USER_ID" : - (mail.cmd == LN_HTTP_RECV_OTA_TASK) ? "OTA_TASK" : "UNKNOWN"; - printf("[%s] SSE raw len=%u\n", tag, mail.len); - /* 确保可打印:追加\0 */ - char *buf = (char *)malloc(mail.len + 1); - if (buf) { - memcpy(buf, mail.data, mail.len); - buf[mail.len] = '\0'; - printf("[%s] SSE raw data=%s\n", tag, buf); - free(buf); + + // 消息有效性检查 + if (!mail.data || mail.len <= 0) + { + printf("[RECV_TASK] 无效消息: data=%p, len=%u\n", mail.data, mail.len); + continue; + } + + // 分类处理消息 + const char *tag = "UNKNOWN"; + if (mail.cmd == LN_HTTP_SERVICE_CMD_SSE_RECV) { + tag = "SPEECH_CHAT"; + } else if (mail.cmd == LN_HTTP_RECV_STORY_CHOOSE) { + tag = "STORY_CHOOSE"; + } else if (mail.cmd == LN_HTTP_RECV_STORY_RANDOM) { + tag = "STORY_RANDOM"; + } else if (mail.cmd == LN_HTTP_RECV_STORY_EXIT) { + tag = "STORY_EXIT"; + } else if (mail.cmd == LN_HTTP_RECV_STORY_FINISH) { + tag = "STORY_FINISH"; + } else if (mail.cmd == LN_HTTP_RECV_DEVICE_CHARACTER) { + tag = "DEVICE_CHARACTER"; + } else if (mail.cmd == LN_HTTP_RECV_DEVICE_USER_ID) { + tag = "DEVICE_USER_ID"; + } else if (mail.cmd == LN_HTTP_RECV_OTA_TASK) { + tag = "OTA_TASK"; + } else if (mail.cmd == LN_HTTP_RECV_OTA_DOWNLOAD_MSG) { + tag = "OTA_DOWNLOAD_MSG"; + } + + printf("[%s] 数据长度: %u\n", tag, mail.len); + /* 确保可打印:追加\0 */ + char *buf = (char *)malloc(mail.len + 1); + if (buf) { + memcpy(buf, mail.data, mail.len); + buf[mail.len] = '\0'; + printf("[%s] 原始数据:%s\n", tag, buf); + /* 在收到 OTA_TASK 后,根据升级策略决定是否触发下载 */ + if (mail.cmd == LN_HTTP_RECV_OTA_TASK) { + ln_handle_ota_task_response(buf, (int)mail.len); + } + /* 在收到 OTA_DOWNLOAD_MSG 后,解析状态并回调处理(进度/完成/失败) */ + if (mail.cmd == LN_HTTP_RECV_OTA_DOWNLOAD_MSG) { + OtaDownloadMsg m = {0}; + if (parse_ota_download_msg(buf, &m) == 0) { + handle_ota_download_msg(&m); } } + free(buf); } } } diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h index 6ecfa2a04..07f69aa80 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h @@ -39,6 +39,9 @@ typedef struct #define LN_HTTP_SERVICE_CMD_OTA_GET_TASK 0x50 /* 获取OTA升级任务(JSON, GET,带查询参数)*/ /* OTA 升级相关接收命令 */ #define LN_HTTP_RECV_OTA_TASK 0x60 /* OTA 升级任务 接收 */ +/* OTA 下载命令与回执 */ +#define LN_HTTP_SERVICE_CMD_OTA_DOWNLOAD 0x51 /* 触发 OTA 包下载(根据 getTask 的返回 packageUrl)*/ +#define LN_HTTP_RECV_OTA_DOWNLOAD_MSG 0x61 /* 可选:OTA 下载控制消息(JSON)接收)*/ typedef struct { @@ -86,6 +89,10 @@ char *uapi_ln_get_device_version(void); char *uapi_ln_get_device_token(void); /* 清除设备token */ void uapi_ln_clear_device_token(void); +/* OTA 查询结果:获取最近一次 getTask 的升级类型(-1:未知 0:可选 1:强制)*/ +int uapi_ln_ota_get_update_type(void); +/* OTA 下载保存的最后一个文件路径(无则返回NULL)*/ +const char *uapi_ln_ota_get_saved_path(void); #ifdef __cplusplus } #endif -- 2.17.1