ota下载添加range请求
authorluozheng <1732036298@qq.com>
Mon, 8 Sep 2025 01:53:01 +0000 (09:53 +0800)
committerluozheng <1732036298@qq.com>
Mon, 8 Sep 2025 01:53:01 +0000 (09:53 +0800)
code/sdk/zhihui/leiniao/imsl_ln/ln_api.c
code/sdk/zhihui/leiniao/imsl_ln/ln_api.h

index e5ad232..5f5223b 100644 (file)
@@ -31,6 +31,7 @@ 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
+static int g_ln_last_package_size = 0;          // 最近一次查询到的 OTA 包大小(字节)
 
 /* 获取设备id: 设备唯一标识 */
 char *uapi_ln_get_device_id(void)
@@ -95,6 +96,8 @@ const char *uapi_ln_ota_get_saved_path(void)
 #define LN_FULL_URL LN_BASE_URL LN_INTERFACE_PATH
 #define LN_CHAT_PATH "/v1/speechChat/chatStream"
 #define LN_CHAT_URL LN_BASE_URL LN_CHAT_PATH
+/* 提取基础域名用于 Host 头(SNI/Host 一致性)*/
+#define LN_BASE_HOST "ambybox-api.test.leiniao.com"
 /* 故事接口路径 */
 #define LN_STORY_CHOOSE_PATH "/v1/story/chooseStory"
 #define LN_STORY_RANDOM_PATH "/v1/story/randomStory"
@@ -430,6 +433,7 @@ static uint32_t ln_speech_chat_stream_http(const char *device_id,
     int audio_len = 0;
     uint8_t *audio_buf = ln_read_file_all(audio_path, &audio_len);
     if (!audio_buf) { printf("chatstream: read audio failed\n"); return ERRCODE_FAIL; }
+    printf("chatstream: audio length = %d\n", audio_len);
 
     /* boundary */
     char boundary[64];
@@ -437,7 +441,9 @@ static uint32_t ln_speech_chat_stream_http(const char *device_id,
 
     /* 组包 */
     char *body = NULL; int body_len = 0; char content_type[96];
-    if (ln_build_multipart_form(boundary, "audio.mp3", audio_buf, audio_len,
+    const char *fname = audio_path; const char *slash = strrchr(audio_path, '/');
+    if (slash && *(slash + 1)) { fname = slash + 1; }
+    if (ln_build_multipart_form(boundary, fname && *fname ? fname : "audio.mp3", audio_buf, audio_len,
                                 &body, &body_len, content_type, sizeof(content_type)) != 0) {
         free(audio_buf); return ERRCODE_FAIL;
     }
@@ -455,7 +461,7 @@ static uint32_t ln_speech_chat_stream_http(const char *device_id,
     client.isHttp = false; /* https */
     client.remotePort = 443;
 
-    /* 自定义头:deviceId/deviceToken/Accept (Content-Type 由 postContentType 指定,不放在自定义头里) */
+    /* 自定义头:仅保留自定义业务头,Content-Type 交由 postContentType 指定 */
     char header_buf[512];
     int hdr = snprintf(header_buf, sizeof(header_buf),
         "deviceId: %s\r\n"
@@ -465,6 +471,9 @@ static uint32_t ln_speech_chat_stream_http(const char *device_id,
         device_id, device_token);
     if (hdr <= 0 || hdr >= (int)sizeof(header_buf)) { free(body); return ERRCODE_FAIL; }
     HttpClientSetCustomHeader(&client, header_buf);
+    
+    /* 关键调试:打印请求头,便于对比Python行为 */
+    printf("chatstream header_buf: %s\n", header_buf);
 
     /* 准备缓冲区(自适应) */
     HTTPC_RESULT prep = ln_http_prepare_auto(&clientData, 2048, 64 * 1024);
@@ -474,8 +483,41 @@ static uint32_t ln_speech_chat_stream_http(const char *device_id,
     clientData.postBuf = body;
     clientData.postBufLen = body_len;
     clientData.postContentType = content_type;
-
-    HTTPC_RESULT res = HttpClientPostRequest(&client, LN_CHAT_URL, &clientData);
+    /* SSE 场景:响应通常为 Transfer-Encoding: chunked,打开分块标志以避免长度校验问题 */
+    clientData.isChunked = true;
+
+    /* 发送请求:对易抖动错误进行有限次重试 */
+    HTTPC_RESULT res = HTTP_EUNKOWN;
+    for (int attempt = 1; attempt <= 3; attempt++) {
+        res = HttpClientPostRequest(&client, LN_CHAT_URL, &clientData);
+        printf("chatstream: post attempt %d result %d\n", attempt, res);
+        if (res == HTTP_SUCCESS) {
+            break;
+        }
+        if (res == HTTP_ECONN || res == HTTP_ECLSD || res == HTTP_ERECV || res == HTTP_ETIMEOUT) {
+            /* 连接/收发超时类错误,短暂休眠后重建会话重试 */
+            HttpClientUnprepare(&clientData);
+            HttpClientClse(&client);
+            LOS_Msleep(200);
+            client = (HttpClient){0};
+            clientData = (HttpClientData){0};
+            client.isHttp = false;
+            client.remotePort = 443;
+            HttpClientSetCustomHeader(&client, header_buf);
+            HTTPC_RESULT prep2 = ln_http_prepare_auto(&clientData, 2048, 64 * 1024);
+            if (prep2 != HTTP_SUCCESS) {
+                printf("chatstream: prepare failed on retry %d, code %d\n", attempt, prep2);
+                break;
+            }
+            clientData.postBuf = body;
+            clientData.postBufLen = body_len;
+            clientData.postContentType = content_type;
+            clientData.isChunked = true;
+            continue;
+        }
+        /* 其他错误不再重试 */
+        break;
+    }
     if (res != HTTP_SUCCESS) {
         printf("chatstream: post failed %d\n", res);
         HttpClientUnprepare(&clientData);
@@ -487,6 +529,10 @@ static uint32_t ln_speech_chat_stream_http(const char *device_id,
     int code = HttpClientGetResponseCode(&client);
     if (code != 200) {
         printf("chatstream: HTTP %d\n", code);
+        if (clientData.responseBuf && clientData.responseBufLen > 0) {
+            int real_len = clientData.responseContentLen > 0 ? clientData.responseContentLen : clientData.responseBufLen;
+            printf("chatstream: resp body: %.*s\n", real_len, clientData.responseBuf);
+        }
         HttpClientUnprepare(&clientData);
         HttpClientClse(&client);
         free(body);
@@ -611,23 +657,21 @@ static uint32_t ln_story_choose_http(const char *device_id,
     client.isHttp = false; // 使用 HTTPS
     client.remotePort = 443;
 
-    /* 设置请求头(包含Content-Type,保持与Python一致) */
-    {
-        char header_buf[1024];
-        int hdr = snprintf(header_buf, sizeof(header_buf),
-            "deviceId: %s\r\n"
-            "deviceToken: %s\r\n"
-            "Accept: text/event-stream\r\n"
-            "Cache-Control: no-cache\r\n"
-            "Content-Type: %s\r\n",
-            device_id, device_token, content_type);
-        if (hdr <= 0 || hdr >= (int)sizeof(header_buf)) {
-            printf("story choose: build header failed\n");
-            free(body);
-            return ERRCODE_FAIL;
-        }
-        HttpClientSetCustomHeader(&client, header_buf);
+    /* 设置请求头:精简头部,避免重复(Host/Connection 交由库设置) */
+    char header_buf[1024];
+    int hdr = snprintf(header_buf, sizeof(header_buf),
+        "deviceId: %s\r\n"
+        "deviceToken: %s\r\n"
+        "Accept: text/event-stream\r\n"
+        "Cache-Control: no-cache\r\n",
+        device_id, device_token);
+    if (hdr <= 0 || hdr >= (int)sizeof(header_buf)) {
+        printf("story choose: build header failed\n");
+        free(body);
+        return ERRCODE_FAIL;
     }
+    HttpClientSetCustomHeader(&client, header_buf);
+    printf("story choose header_buf: %s\n", header_buf);
 
     /* 自适应准备 HTTP 缓冲区 */
     HTTPC_RESULT prep = ln_http_prepare_auto(&clientData, 2048, 8 * 1024);
@@ -641,33 +685,59 @@ static uint32_t ln_story_choose_http(const char *device_id,
     clientData.postBuf = body;
     clientData.postBufLen = body_len;
     clientData.postContentType = content_type;
-
-    /* 发送 POST 请求 */
-    HTTPC_RESULT res = HttpClientPostRequest(&client, LN_STORY_CHOOSE_URL, &clientData);
-    if (res != HTTP_SUCCESS) {
-        if (res == HTTP_EAGAIN) {
-            /* 驱动状态机直到成功或失败 */
-            while (1) {
-                HTTPC_RESULT rrp = HttpClientRecvResponse(&client, &clientData);
-                if (rrp == HTTP_SUCCESS) { break; }
-                if (rrp == HTTP_EAGAIN) { continue; }
-                printf("story choose post failed %d\n", rrp);
-                free(body);
-                HttpClientUnprepare(&clientData);
-                HttpClientClse(&client);
-                return ERRCODE_FAIL;
-            }
-        } else {
-            printf("story choose: send POST request failed (code: %d)\n", res);
-            free(body);
+    clientData.isChunked = true;
+
+    /* 发送请求:对易抖动错误进行有限次重试 */
+    HTTPC_RESULT res = HTTP_EUNKOWN;
+    for (int attempt = 1; attempt <= 3; attempt++) {
+        res = HttpClientPostRequest(&client, LN_STORY_CHOOSE_URL, &clientData);
+        printf("story choose: post attempt %d result %d\n", attempt, res);
+        if (res == HTTP_SUCCESS) {
+            break;
+        }
+        if (res == HTTP_ECONN || res == HTTP_ECLSD || res == HTTP_ERECV || res == HTTP_ETIMEOUT) {
+            /* 连接/收发超时类错误,短暂休眠后重建会话重试 */
             HttpClientUnprepare(&clientData);
             HttpClientClse(&client);
-            return ERRCODE_FAIL;
+            LOS_Msleep(200);
+            client = (HttpClient){0};
+            clientData = (HttpClientData){0};
+            client.isHttp = false;
+            client.remotePort = 443;
+            HttpClientSetCustomHeader(&client, header_buf);
+            HTTPC_RESULT prep2 = ln_http_prepare_auto(&clientData, 2048, 64 * 1024);
+            if (prep2 != HTTP_SUCCESS) {
+                printf("story choose: prepare failed on retry %d, code %d\n", attempt, prep2);
+                break;
+            }
+            clientData.postBuf = body;
+            clientData.postBufLen = body_len;
+            clientData.postContentType = content_type;
+            clientData.isChunked = true;
+            continue;
+        }
+        /* 其他错误不再重试 */
+        break;
+    }
+    printf("story choose: send POST request result: %d\n", res);
+    if (res != HTTP_SUCCESS) {
+        printf("story choose: send POST request failed (code: %d)\n", res);
+        free(body);
+        HttpClientUnprepare(&clientData);
+        HttpClientClse(&client);
+        return ERRCODE_FAIL;
+    }
+    else
+    {
+        if (client.socket == -1) 
+        {
+            printf("[ln_story_choose_http] 错误:POST请求返回成功,但socket无效\n");
         }
     }
 
     /* 检查 HTTP 响应码 */
     int http_code = HttpClientGetResponseCode(&client);
+    printf("story choose: send POST request result: %d\n", res);
     if (http_code != 200) {
         printf("story choose: invalid HTTP response code (%d)\n", http_code);
         // 新增:打印响应体内容
@@ -686,6 +756,12 @@ static uint32_t ln_story_choose_http(const char *device_id,
         return ERRCODE_FAIL;
     }
 
+    printf("story choose: send POST request success\n");
+    if (clientData.responseBuf && clientData.responseBufLen > 0) {
+        int real_len = clientData.responseContentLen > 0 ? clientData.responseContentLen : clientData.responseBufLen;
+        do_ln_http_server_recv_data(LN_HTTP_RECV_STORY_CHOOSE, clientData.responseBuf, real_len);
+    }
+
     /* 循环接收 SSE 分块数据(带累积处理)*/
     int loop_guard = 0;
     char sse_acc[4096] = {0}; // SSE 数据累积缓冲区
@@ -798,39 +874,21 @@ static uint32_t ln_story_random_http(const char *device_id,
     clientData.postBufLen = json_len;
     clientData.postContentType = (char *)"application/json";
 
-
-    /* 打印发送给服务端的请求数据(关键调试信息)*/
-    printf("story random: request details:\n");
-    printf("  URL:%s\n", LN_STORY_RANDOM_URL); // 从Python的RANDOM_STORY_URL提取
-    printf("  Headers:\n");
-    printf("    deviceId: %s\n", device_id);
-    printf("    deviceToken: %s\n", device_token);
-    printf("    Accept: text/event-stream\n");
-    printf("    Cache-Control: no-cache\n");
-    printf("    Content-Type: application/json\n");
-    printf("  Body: %s (length: %d)\n", json_body, json_len);
-
-
     /* 发送 POST 请求 */
     HTTPC_RESULT res = HttpClientPostRequest(&client, LN_STORY_RANDOM_URL, &clientData);
     printf("story random: send POST request result: %d\n", res);
-    if (res != HTTP_SUCCESS) {
-        if (res == HTTP_EAGAIN) {
-            /* 驱动状态机直到成功或失败 */
-            while (1) {
-                HTTPC_RESULT rrp = HttpClientRecvResponse(&client, &clientData);
-                if (rrp == HTTP_SUCCESS) { break; }
-                if (rrp == HTTP_EAGAIN) { continue; }
-                printf("story random post failed %d\n", rrp);
-                HttpClientUnprepare(&clientData);
-                HttpClientClse(&client);
-                return ERRCODE_FAIL;
-            }
-        } else {
-            printf("story random: send POST request failed (code: %d)\n", res);
-            HttpClientUnprepare(&clientData);
-            HttpClientClse(&client);
-            return ERRCODE_FAIL;
+    if (res != HTTP_SUCCESS) 
+    {
+        printf("story random: send POST request failed (code: %d)\n", res);
+        HttpClientUnprepare(&clientData);
+        HttpClientClse(&client);
+        return ERRCODE_FAIL;
+    }
+    else
+    {
+        if (client.socket == -1) 
+        {
+            printf("[ln_speech_chat_stream_http] 错误:POST请求返回成功,但socket无效\n");
         }
     }
 
@@ -856,6 +914,10 @@ static uint32_t ln_story_random_http(const char *device_id,
     }
 
     printf("story random: HTTP response code (%d)\n", http_code);
+    if (clientData.responseBuf && clientData.responseBufLen > 0) {
+        int real_len = clientData.responseContentLen > 0 ? clientData.responseContentLen : clientData.responseBufLen;
+        do_ln_http_server_recv_data(LN_HTTP_RECV_STORY_RANDOM, clientData.responseBuf, real_len);
+    }
 
     /* 循环接收 SSE 分块数据(带累积处理)*/
     int loop_guard = 0;
@@ -1302,69 +1364,117 @@ static int ln_calc_file_md5_lower_hex(const char *file_path, char out_hex[33])
     return 0;
 }
 
-/* 简单下载器:支持 http/https 全URL,保存到 save_path */
-static int ln_download_file_to_path(const char *full_url, const char *save_path)
+/* 分片下载:使用 Range 头按固定分片大小请求,边下边写并上报进度 */
+static int ln_download_file_to_path_range(const char *full_url, const char *save_path, int total_size)
 {
-    if (!full_url || !save_path)
+    if (!full_url || !save_path || total_size <= 0)
     {
         return -1;
     }
-    printf("[OTA] start download: url=%s -> %s\n", full_url, save_path);
+    printf("[OTA] range download: url=%s -> %s, total=%d\n", full_url, save_path, total_size);
 
     /* 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)
+    FILE *fp = fopen(save_path, "wb");
+    if (!fp)
     {
+        printf("[OTA] open file failed: %s\n", save_path);
         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;
-    }
+    const int chunk_size = 256 * 1024; /* 256KB 分片,兼顾内存与吞吐 */
+    int64_t downloaded = 0;
 
-    int code = HttpClientGetResponseCode(&client);
-    if (code != 200)
+    while (downloaded < total_size)
     {
-        printf("[OTA] download HTTP code=%d\n", code);
-        HttpClientUnprepare(&clientData);
-        HttpClientClse(&client);
-        return -1;
-    }
+        int start = (int)downloaded;
+        int remaining = total_size - start;
+        int this_chunk = remaining > chunk_size ? chunk_size : remaining;
+        int end = start + this_chunk - 1;
+
+        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;
+
+        /* 自定义 Range 头 */
+        char header_buf[256];
+        int hdr = snprintf(header_buf, sizeof(header_buf), "Range: bytes=%d-%d\r\n", start, end);
+        if (hdr <= 0 || hdr >= (int)sizeof(header_buf))
+        {
+            fclose(fp);
+            return -1;
+        }
+        HttpClientSetCustomHeader(&client, header_buf);
+
+        /* 预留足够的响应缓冲区(分片大小+余量)*/
+        HTTPC_RESULT prep = ln_http_prepare_auto(&clientData, 2048, this_chunk + 16 * 1024);
+        if (prep != HTTP_SUCCESS)
+        {
+            fclose(fp);
+            return -1;
+        }
+
+        HTTPC_RESULT res = HttpClientGetRequest(&client, full_url, &clientData);
+        if (res != HTTP_SUCCESS)
+        {
+            printf("[OTA] range request failed: %d\n", res);
+            HttpClientUnprepare(&clientData);
+            HttpClientClse(&client);
+            fclose(fp);
+            return -1;
+        }
+
+        int code = HttpClientGetResponseCode(&client);
+        if (!(code == 206 || code == 200))
+        {
+            printf("[OTA] range HTTP code=%d\n", code);
+            HttpClientUnprepare(&clientData);
+            HttpClientClse(&client);
+            fclose(fp);
+            return -1;
+        }
+
+        if (clientData.responseBuf == NULL || clientData.responseBufLen <= 0)
+        {
+            printf("[OTA] range empty body\n");
+            HttpClientUnprepare(&clientData);
+            HttpClientClse(&client);
+            fclose(fp);
+            return -1;
+        }
+
+        int buf_len = clientData.responseContentLen > 0 ? clientData.responseContentLen : clientData.responseBufLen;
+        size_t written = fwrite(clientData.responseBuf, 1, (size_t)buf_len, fp);
+        if ((int)written != buf_len)
+        {
+            printf("[OTA] range fwrite failed\n");
+            HttpClientUnprepare(&clientData);
+            HttpClientClse(&client);
+            fclose(fp);
+            return -1;
+        }
+
+        downloaded += buf_len;
+        printf("[OTA] range downloaded: %d/%d\n", downloaded, total_size);
+
+        /* 进度上报 */
+        int percent = (int)((downloaded * 100) / total_size);
+        if (percent > 100) percent = 100;
+        char prog[128];
+        int pn = snprintf(prog, sizeof(prog), "{\"status\":%d,\"progress\":%d}", OTA_DOWNLOAD_STATUS_PROGRESS, percent);
+        if (pn > 0) do_ln_http_server_recv_data(LN_HTTP_RECV_OTA_DOWNLOAD_MSG, prog, (uint32_t)pn);
 
-    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;
+    fclose(fp);
+    return 0;
 }
 
 /* 仅下载:根据最近一次 getTask 的记录字段执行下载,不再主动请求任务 */
@@ -1387,7 +1497,11 @@ static uint32_t ln_ota_download_flow_http(const char *device_id,
     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);
+    int dres;
+    if (g_ln_last_package_size > 0)
+    {
+        dres = ln_download_file_to_path_range(g_ln_last_package_url, save_path, g_ln_last_package_size);
+    }
     if (dres != 0)
     {
         char msg[256];
@@ -1448,6 +1562,7 @@ static void ln_handle_ota_task_response(const char *json_buf, int json_len)
     cJSON *updateType = cJSON_GetObjectItem(data, "updateType");
     cJSON *packageUrl  = cJSON_GetObjectItem(data, "packageUrl");
     cJSON *packageMd5  = cJSON_GetObjectItem(data, "packageMd5");
+    cJSON *packageSize = cJSON_GetObjectItem(data, "packageSize");
     if (!(targetVersion && cJSON_IsString(targetVersion) && updateType && (cJSON_IsNumber(updateType) || cJSON_IsString(updateType))))
     {
         printf("[OTA] task fields invalid\n");
@@ -1470,6 +1585,7 @@ static void ln_handle_ota_task_response(const char *json_buf, int json_len)
     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
+    g_ln_last_package_size = 0;
     if (targetVersion && cJSON_IsString(targetVersion)) {
         strncpy(g_ln_last_target_version, targetVersion->valuestring, sizeof(g_ln_last_target_version) - 1);
     }
@@ -1479,6 +1595,9 @@ static void ln_handle_ota_task_response(const char *json_buf, int json_len)
     if (packageMd5 && cJSON_IsString(packageMd5)) {
         strncpy(g_ln_last_package_md5, packageMd5->valuestring, sizeof(g_ln_last_package_md5) - 1);
     }
+    if (packageSize && cJSON_IsNumber(packageSize)) {
+        g_ln_last_package_size = (int)packageSize->valuedouble;
+    }
 
     if (ut == 1)
     {
@@ -1501,22 +1620,6 @@ static void ln_handle_ota_task_response(const char *json_buf, int json_len)
     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)
index 07f69aa..734aea0 100644 (file)
@@ -70,6 +70,23 @@ typedef struct
     char story_theme[64]; /* 为空则随机 */
 } ln_story_random_req_t;
 
+/* 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),仅状态为PROGRESS时有效 */
+    char file_path[256];       /* 文件路径,仅状态为COMPLETED时有效 */
+    char error_msg[128];       /* 错误信息,仅状态为FAILED时有效 */
+    char md5[65];              /* MD5(来自任务),仅状态为COMPLETED时有效 */
+} OtaDownloadMsg;
+
 /* 初始化 LN HTTP 服务 */
 errcode_t uapi_ln_http_server_init(void); 
 /* 通用发送接口:向 LN HTTP 发送队列投递消息 */