语音对话接口和选择故事接口试下mp3下载功能
authorcheng <chengyf@zhihuishixun.com>
Thu, 11 Sep 2025 06:16:55 +0000 (14:16 +0800)
committercheng <chengyf@zhihuishixun.com>
Thu, 11 Sep 2025 06:16:55 +0000 (14:16 +0800)
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_speech_chat.c
code/sdk/zhihui/leiniao/imsl_ln/ln_api_story_choose.c

index 5e43cfc..971e3ac 100644 (file)
@@ -106,45 +106,6 @@ HTTPC_RESULT ln_http_prepare_auto(HttpClientData *clientData, int header_size, i
     return ret;
 }
 
-// 累积并解析SSE数据(补充实现)
-void ln_sse_accumulate_and_dispatch(int cmd, const char *data, int data_len,
-                                          char *acc, int *acc_len, int acc_size) {
-    if (!data || data_len <= 0 || !acc || !acc_len || *acc_len >= acc_size) return;
-
-    // 累积数据
-    int copy_len = (acc_size - *acc_len - 1) < data_len ? (acc_size - *acc_len - 1) : data_len;
-    memcpy(acc + *acc_len, data, copy_len);
-    *acc_len += copy_len;
-    acc[*acc_len] = '\0';
-
-    // 分割SSE事件(按"\n\n"分割)
-    char *ptr = acc;
-    while ((ptr = strstr(ptr, "\n\n")) != NULL) {
-        int event_len = ptr - acc;
-        if (event_len > 0) {
-            // 提取data字段(去掉"date: "前缀)
-            char *data_start = strstr(acc, "data: ");
-            if (data_start) {
-                data_start += 6;  // 跳过"data: "
-                int json_len = (acc + event_len) - data_start;
-                char *json = (char *)malloc(json_len + 1);
-                if (json) {
-                    memcpy(json, data_start, json_len);
-                    json[json_len] = '\0';
-                    // 投递解析后的JSON数据
-                    do_ln_http_server_recv_data(cmd, json, json_len);
-                    free(json);
-                }
-            }
-        }
-        // 移动指针处理剩余数据
-        *acc_len -= (ptr - acc + 2);
-        memmove(acc, ptr + 2, *acc_len);
-        acc[*acc_len] = '\0';
-        ptr = acc;
-    }
-}
-
 /* 计算文件MD5(小写hex)*/
 static int ln_calc_file_md5_lower_hex(const char *file_path, char out_hex[33])
 {
@@ -502,14 +463,12 @@ 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.data || mail.len <= 0) 
         { 
-            printf("[RECV_TASK] 无效消息: data=%p, len=%u\n", mail.data, mail.len);
             continue;
         }
 
@@ -668,3 +627,395 @@ void do_ln_http_server_recv_data(int cmd, char *data, uint32_t len)
         if (recv_data.data) free(recv_data.data);
     }
 }
+
+
+/**
+ * 高级MP3下载函数,支持自定义保存目录和文件名
+ * @param audio_url 音频文件URL
+ * @param save_dir 保存目录
+ * @param filename 文件名(不包含路径)
+ * @return ERRCODE_SUCC 成功,ERRCODE_FAIL 失败
+ */
+static uint32_t ln_download_mp3_file_advanced(const char *audio_url, const char *save_dir, const char *filename)
+{
+    if (!audio_url || !save_dir || !filename)
+    {
+        printf("[SSE] download_mp3_advanced: invalid parameters\n");
+        return ERRCODE_FAIL;
+    }
+
+    // 构建完整保存路径
+    char save_path[512];
+    snprintf(save_path, sizeof(save_path), "%s/%s", save_dir, filename);
+
+    // 确保目录存在
+    if (access(save_dir, F_OK) == -1)
+    {
+        uapi_create_folder(save_dir);
+    }
+
+    printf("[SSE] downloading MP3: %s -> %s\n", audio_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);
+    LOS_Msleep(300);
+
+    HttpClient client = {0};
+    HttpClientData client_data = {0};
+    int ret = -1;
+    size_t total_written = 0;
+
+    // 打开文件准备写入
+    FILE *fd = fopen(save_path, "wb");
+    if (!fd)
+    {
+        printf("[SSE] fopen failed for %s\n", save_path);
+        return ERRCODE_FAIL;
+    }
+
+    HTTPC_RESULT prep = ln_http_prepare_auto(&client_data, 2048, LN_CLIENT_PREPARE_SIZE * 2);
+    if (prep != HTTP_SUCCESS)
+    {
+        printf("[SSE] prepare failed: %d\n", prep);
+        fclose(fd);
+        return ERRCODE_FAIL;
+    }
+
+    // 建立连接
+    ret = HttpClientConn(&client, audio_url);
+    if (ret != 0)
+    {
+        printf("[SSE] connection failed, ret=0x%x\n", ret);
+        fclose(fd);
+        HttpClientUnprepare(&client_data);
+        return ERRCODE_FAIL;
+    }
+
+    // 发送GET请求
+    ret = HttpClientSend(&client, audio_url, HTTP_GET, &client_data);
+    if (ret != 0)
+    {
+        printf("[SSE] send request failed, ret=0x%x\n", ret);
+        fclose(fd);
+        HttpClientClse(&client);
+        HttpClientUnprepare(&client_data);
+        return ERRCODE_FAIL;
+    }
+
+    // 接收响应数据
+    do
+    {
+        HTTPC_RESULT recv_res = HttpClientRecvResponse(&client, &client_data);
+        if (recv_res == HTTP_SUCCESS || recv_res == HTTP_EAGAIN)
+        {
+            if (client_data.contentBlockLen > 0)
+            {
+                size_t written = fwrite(client_data.responseBuf, 1, client_data.contentBlockLen, fd);
+                if (written != client_data.contentBlockLen)
+                {
+                    printf("[SSE] write failed, written=%zu, expected=%d\n",
+                           written, client_data.contentBlockLen);
+                    fclose(fd);
+                    HttpClientClse(&client);
+                    HttpClientUnprepare(&client_data);
+                    return ERRCODE_FAIL;
+                }
+                total_written += written;
+            }
+        }
+        else
+        {
+            printf("[SSE] receive ended (code: %d)\n", recv_res);
+            break;
+        }
+    } while (client_data.isMore);
+
+    fclose(fd);
+    HttpClientUnprepare(&client_data);
+    HttpClientClse(&client);
+
+    printf("[SSE] download completed, total bytes: %zu\n", total_written);
+    return ERRCODE_SUCC;
+}
+
+/**
+ * 解析JSON数据,下载MP3并推送消息
+ * @param json_data JSON数据字符串
+ * @param interface_type 接口类型
+ * @param segment_index 片段索引
+ * @return ERRCODE_SUCC 成功,ERRCODE_FAIL 失败
+ */
+uint32_t ln_parse_and_process_json_data(const char *json_data, int interface_type, int segment_index)
+{
+    if (!json_data)
+    {
+        printf("[JSON_PARSE] invalid json_data\n");
+        return ERRCODE_FAIL;
+    }
+
+    printf("[JSON_PARSE] parsing JSON data (interface_type=%d, segment=%d): %s\n", 
+           interface_type, segment_index, json_data);
+
+    // 解析JSON数据
+    cJSON *root = cJSON_Parse(json_data);
+    if (!root)
+    {
+        printf("[JSON_PARSE] JSON parse failed: %s\n", json_data);
+        return ERRCODE_FAIL;
+    }
+
+    // 检查是否有错误
+    cJSON *errorCode = cJSON_GetObjectItem(root, "errorCode");
+    if (errorCode && !cJSON_IsNull(errorCode))
+    {
+        printf("[JSON_PARSE] server error: %s\n", 
+               cJSON_IsString(errorCode) ? errorCode->valuestring : "unknown");
+        cJSON_Delete(root);
+        return ERRCODE_FAIL;
+    }
+
+    // 获取data字段
+    cJSON *data = cJSON_GetObjectItem(root, "data");
+    if (!data || !cJSON_IsObject(data))
+    {
+        printf("[JSON_PARSE] no data field found\n");
+        cJSON_Delete(root);
+        return ERRCODE_FAIL;
+    }
+
+    // 根据接口类型获取音频URL
+    char *audio_url = NULL;
+    if (interface_type == LN_HTTP_SERVICE_CMD_SSE_RECV)
+    {
+        // 语音对话接口:使用 audioUrl 字段
+        cJSON *audioUrl = cJSON_GetObjectItem(data, "audioUrl");
+        if (audioUrl && cJSON_IsString(audioUrl))
+        {
+            audio_url = audioUrl->valuestring;
+        }
+    }
+    else if (interface_type == LN_HTTP_RECV_STORY_CHOOSE || interface_type == LN_HTTP_RECV_STORY_RANDOM)
+    {
+        // 故事接口:使用 storyAudioUrl 字段
+        cJSON *storyAudioUrl = cJSON_GetObjectItem(data, "storyAudioUrl");
+        if (storyAudioUrl && cJSON_IsString(storyAudioUrl))
+        {
+            audio_url = storyAudioUrl->valuestring;
+        }
+    }
+    else
+    {
+        // 默认尝试 audioUrl 字段
+        cJSON *audioUrl = cJSON_GetObjectItem(data, "audioUrl");
+        if (audioUrl && cJSON_IsString(audioUrl))
+        {
+            audio_url = audioUrl->valuestring;
+        }
+    }
+
+    // 获取textContent
+    cJSON *textContent = cJSON_GetObjectItem(data, "textContent");
+    char *text_content = NULL;
+    if (textContent && cJSON_IsString(textContent))
+    {
+        text_content = textContent->valuestring;
+    }
+
+    // 获取intention
+    cJSON *intention = cJSON_GetObjectItem(data, "intention");
+    char *intention_str = NULL;
+    if (intention && cJSON_IsString(intention))
+    {
+        intention_str = intention->valuestring;
+    }
+
+    // 获取storyTheme(故事接口特有)
+    cJSON *storyTheme = cJSON_GetObjectItem(data, "storyTheme");
+    char *story_theme = NULL;
+    if (storyTheme && cJSON_IsString(storyTheme))
+    {
+        story_theme = storyTheme->valuestring;
+    }
+
+    // 获取storyContent(故事接口特有)
+    cJSON *storyContent = cJSON_GetObjectItem(data, "storyContent");
+    char *story_content = NULL;
+    if (storyContent && cJSON_IsString(storyContent))
+    {
+        story_content = storyContent->valuestring;
+    }
+
+    // 创建推送数据
+    cJSON *push_data = cJSON_CreateObject();
+    
+    // 根据接口类型添加相应字段
+    if (text_content && strlen(text_content) > 0)
+    {
+        cJSON_AddStringToObject(push_data, "textContent", text_content);
+    }
+    
+    if (story_content && strlen(story_content) > 0)
+    {
+        cJSON_AddStringToObject(push_data, "storyContent", story_content);
+    }
+    
+    if (intention_str && strlen(intention_str) > 0)
+    {
+        cJSON_AddStringToObject(push_data, "intention", intention_str);
+    }
+    
+    if (story_theme && strlen(story_theme) > 0)
+    {
+        cJSON_AddStringToObject(push_data, "storyTheme", story_theme);
+    }
+
+    // 推送到相应的接收线程
+    char *json_string = cJSON_Print(push_data);
+    if (json_string)
+    {
+        printf("[JSON_PARSE] pushing data to interface_type=%d: %s\n", interface_type, json_string);
+        do_ln_http_server_recv_data(interface_type, json_string, strlen(json_string));
+        free(json_string);
+    }
+    cJSON_Delete(push_data);
+
+    // 下载音频文件
+    if (audio_url && strlen(audio_url) > 0)
+    {
+        char save_dir[256];
+        char filename[128];
+        
+        // 根据接口类型确定保存目录和文件名
+        switch (interface_type)
+        {
+            case LN_HTTP_SERVICE_CMD_SSE_RECV:
+                snprintf(save_dir, sizeof(save_dir), "%s/speech", LN_DATA_PATH);
+                snprintf(filename, sizeof(filename), "speech_audio_%d.mp3", segment_index);
+                break;
+            case LN_HTTP_RECV_STORY_CHOOSE:
+                snprintf(save_dir, sizeof(save_dir), "%s/story/choose", LN_DATA_PATH);
+                snprintf(filename, sizeof(filename), "story_choose_%d.mp3", segment_index);
+                break;
+            case LN_HTTP_RECV_STORY_RANDOM:
+                snprintf(save_dir, sizeof(save_dir), "%s/story/random", LN_DATA_PATH);
+                snprintf(filename, sizeof(filename), "story_random_%d.mp3", segment_index);
+                break;
+            default:
+                snprintf(save_dir, sizeof(save_dir), "%s", LN_DATA_PATH);
+                snprintf(filename, sizeof(filename), "audio_%d.mp3", segment_index);
+                break;
+        }
+
+        printf("[JSON_PARSE] found audio URL (interface_type=%d): %s\n", interface_type, audio_url);
+        printf("[JSON_PARSE] downloading audio to: %s/%s\n", save_dir, filename);
+        
+        uint32_t download_result = ln_download_mp3_file_advanced(audio_url, save_dir, filename);
+        if (download_result == ERRCODE_SUCC)
+        {
+            printf("[JSON_PARSE] audio downloaded successfully: %s/%s\n", save_dir, filename);
+        }
+        else
+        {
+            printf("[JSON_PARSE] audio download failed\n");
+        }
+    }
+    else
+    {
+        printf("[JSON_PARSE] no audio URL found for interface_type=%d, skipping download\n", interface_type);
+    }
+
+    cJSON_Delete(root);
+    return ERRCODE_SUCC;
+}
+
+/**
+ * 从文件中解析data数据,下载MP3并推送消息
+ * @param file_path 文件路径
+ * @param interface_type 接口类型
+ * @return ERRCODE_SUCC 成功,ERRCODE_FAIL 失败
+ */
+uint32_t ln_parse_file_and_process_data(const char *file_path, int interface_type)
+{
+    if (!file_path)
+    {
+        printf("[FILE_PARSE] invalid file path\n");
+        return ERRCODE_FAIL;
+    }
+
+    printf("[FILE_PARSE] processing file: %s (interface_type=%d)\n", file_path, interface_type);
+
+    FILE *fp = fopen(file_path, "r");
+    if (!fp)
+    {
+        printf("[FILE_PARSE] failed to open file: %s\n", file_path);
+        return ERRCODE_FAIL;
+    }
+
+    char line[2048];
+    int line_count = 0;
+    int success_count = 0;
+    int error_count = 0;
+
+    while (fgets(line, sizeof(line), fp))
+    {
+        line_count++;
+        
+        // 去除行尾的换行符
+        size_t len = strlen(line);
+        if (len > 0 && line[len - 1] == '\n')
+        {
+            line[len - 1] = '\0';
+            len--;
+        }
+        if (len > 0 && line[len - 1] == '\r')
+        {
+            line[len - 1] = '\0';
+            len--;
+        }
+
+        // 跳过空行
+        if (len == 0)
+        {
+            continue;
+        }
+
+        // 检查是否是SSE数据格式 (data: {...})
+        if (strncmp(line, "data:", 5) == 0)
+        {
+            char *json_start = line + 5;
+            // 跳过可能的空格
+            while (*json_start == ' ' || *json_start == '\t')
+            {
+                json_start++;
+            }
+
+            printf("[FILE_PARSE] line %d: processing JSON: %s\n", line_count, json_start);
+            
+            uint32_t result = ln_parse_and_process_json_data(json_start, interface_type, line_count);
+            if (result == ERRCODE_SUCC)
+            {
+                success_count++;
+            }
+            else
+            {
+                error_count++;
+            }
+        }
+        else
+        {
+            printf("[FILE_PARSE] line %d: invalid format (not SSE data): %s\n", line_count, line);
+            error_count++;
+        }
+    }
+
+    fclose(fp);
+
+    printf("[FILE_PARSE] processing completed: total lines=%d, success=%d, errors=%d\n", 
+           line_count, success_count, error_count);
+
+    return (error_count == 0) ? ERRCODE_SUCC : ERRCODE_FAIL;
+}
+
index 14966f0..5b927b0 100644 (file)
@@ -10,7 +10,7 @@ extern "C"
 #include "errcode.h"
 /* HTTP types for helper function prototypes */
 #include "netstack/http/include/httpclient.h"
-
+#include <unistd.h>
 #include "wearable_log.h"
 
 typedef struct
@@ -45,8 +45,10 @@ typedef struct
 #define LN_SIGN_SECRET "vDdXVXgPUEUM6_piUjz5AEhMvYTSFzQdPaN-ogKnSus="
 /* OTA 包保存目录(最终文件仍将放在 /user/ 下)*/
 #define LN_OTA_SAVE_DIR "/user"
+/* 用户数据保存目录 */
+#define LN_DATA_PATH          "/user/leiniao/"
 
-#define LN_CLIENT_PREPARE_SIZE 4*1024
+#define LN_CLIENT_PREPARE_SIZE 2*1024
 
 #define LN_HTTP_SERVICE_CMD_GET_DEVICE_TOKEN 0x1 /* 获取设备 token */
 #define LN_HTTP_SERVICE_CMD_SPEECH_CHAT_STREAM 0x2 /* 语音对话流 */
@@ -154,8 +156,6 @@ extern int g_ln_last_package_size;
 
 /* ============================== 公共工具函数(供拆分后的模块复用) ============================== */
 HTTPC_RESULT ln_http_prepare_auto(HttpClientData *clientData, int header_size, int resp_size);
-void ln_sse_accumulate_and_dispatch(int cmd, const char *data, int data_len,
-                                    char *acc, int *acc_len, int acc_size);
 /* ============================== 各接口函数声明 ============================== */
 uint32_t ln_get_device_token_http(const char *device_id,
                                   const char *device_iccid,
@@ -193,6 +193,9 @@ uint32_t ln_ota_get_task_http(const char *device_id,
 uint32_t ln_ota_download_flow_http(const char *device_id,
                                    const char *device_token,
                                    const char *source_version);
+/* 文件解析和数据处理函数 */
+uint32_t ln_parse_file_and_process_data(const char *file_path, int interface_type);
+uint32_t ln_parse_and_process_json_data(const char *json_data, int interface_type, int segment_index);
 #ifdef __cplusplus
 }
 #endif
index 2dbf6f7..acb6e2f 100644 (file)
@@ -1,6 +1,8 @@
 #include "ln_api.h"
 #include <stdio.h>
 
+#define speech_file_path "/user/leiniao/speech/speech.txt"
+
 /* 读取整个文件到内存,返回缓冲区和长度;需调用者 free */
 static uint8_t *ln_read_file_all_speech_chat(const char *path, int *out_len)
 {
@@ -73,6 +75,22 @@ static int ln_build_multipart_form_speech_chat(const char *boundary,
     return 0;
 }
 
+static void ln_parse_speech_file(void)
+{
+    printf("Starting to parse speech file...\n");
+    // 处理语音对话数据
+    uint32_t result = ln_parse_file_and_process_data(speech_file_path, LN_HTTP_SERVICE_CMD_SSE_RECV);
+
+    if (result == ERRCODE_SUCC)
+    {
+        printf("Speech file parsing completed successfully!\n");
+    }
+    else
+    {
+        printf("Speech file parsing failed!\n");
+    }
+}
+
 uint32_t ln_speech_chat_stream_http(const char *device_id,
                                     const char *device_token,
                                     const char *audio_path)
@@ -174,6 +192,24 @@ uint32_t ln_speech_chat_stream_http(const char *device_id,
         return ret;
     }
 
+    // 打开文件准备写入
+    const char *save_path_folder = "/user/leiniao/speech/";
+    // 判断目录是否存在,不存在则创建
+    if (access(save_path_folder, F_OK) == -1)
+    {
+        uapi_create_folder(save_path_folder);
+    }
+    FILE *fd = fopen(speech_file_path, "w+");
+    if (!fd)
+    {
+        printf("chatstream: fopen failed for %s:\n", speech_file_path);
+        free(body);
+        HttpClientClse(&client);
+        HttpClientUnprepare(&clientData);
+        return ERRCODE_FAIL;
+    }
+
+    size_t total_written = 0;
     do
     {
         HTTPC_RESULT recv_res = HttpClientRecvResponse(&client, &clientData);
@@ -183,8 +219,19 @@ uint32_t ln_speech_chat_stream_http(const char *device_id,
             // 处理接收到的数据块
             if (clientData.contentBlockLen > 0)
             {
-                do_ln_http_server_recv_data(LN_HTTP_SERVICE_CMD_SSE_RECV,
-                                            clientData.responseBuf, clientData.contentBlockLen);
+                // 写入到文件(保持原有功能)
+                size_t written = fwrite(clientData.responseBuf, 1, clientData.contentBlockLen, fd);
+                if (written != clientData.contentBlockLen)
+                {
+                    printf("chatstream: write failed, written=%zu, expected=%d\n",
+                           written, clientData.contentBlockLen);
+                    free(body);
+                    HttpClientClse(&client);
+                    HttpClientUnprepare(&clientData);
+                    fclose(fd);
+                    return ERRCODE_FAIL;
+                }
+                total_written += written;
             }
         }
         else
@@ -197,5 +244,12 @@ uint32_t ln_speech_chat_stream_http(const char *device_id,
     HttpClientUnprepare(&clientData);
     HttpClientClse(&client);
     free(body);
+    fclose(fd);
+
+    if (total_written > 0)
+    {
+        ln_parse_speech_file();
+    }
+
     return ERRCODE_SUCC;
 }
index 056d414..b6c364e 100644 (file)
@@ -1,6 +1,8 @@
 #include "ln_api.h"
 #include <stdio.h>
 
+#define story_choose_file_path "/user/leiniao/story/choose.txt"
+
 /* 读取整个文件到内存,返回缓冲区和长度;需调用者 free */
 static uint8_t *ln_read_file_all_story(const char *path, int *out_len)
 {
@@ -73,6 +75,22 @@ static int ln_build_multipart_form_story(const char *boundary,
     return 0;
 }
 
+static void ln_parse_story_file(void)
+{
+    printf("Starting to parse story choose file...\n");
+
+    uint32_t result = ln_parse_file_and_process_data(story_choose_file_path, LN_HTTP_RECV_STORY_CHOOSE);
+
+    if (result == ERRCODE_SUCC)
+    {
+        printf("Story file parsing completed successfully!\n");
+    }
+    else
+    {
+        printf("Story file parsing failed!\n");
+    }
+}
+
 uint32_t ln_story_choose_http(const char *device_id,
                               const char *device_token,
                               const char *audio_path)
@@ -143,7 +161,7 @@ uint32_t ln_story_choose_http(const char *device_id,
     printf("story choose header_buf: %s\n", header_buf);
 
     /* 自适应准备 HTTP 缓冲区 */
-    HTTPC_RESULT prep = ln_http_prepare_auto(&clientData, 2048, LN_CLIENT_PREPARE_SIZE);
+    HTTPC_RESULT prep = ln_http_prepare_auto(&clientData, 2048, LN_CLIENT_PREPARE_SIZE * 2);
     if (prep != HTTP_SUCCESS)
     {
         printf("story choose: prepare HTTP buffer failed (code: %d)\n", prep);
@@ -178,6 +196,24 @@ uint32_t ln_story_choose_http(const char *device_id,
         return ret;
     }
 
+    // 打开文件准备写入
+    const char *save_path_folder = "/user/leiniao/story/";
+    // 判断目录是否存在,不存在则创建
+    if (access(save_path_folder, F_OK) == -1)
+    {
+        uapi_create_folder(save_path_folder);
+    }
+    FILE *fd = fopen(story_choose_file_path, "w+");
+    if (!fd)
+    {
+        printf("story choose: fopen failed for %s:\n", story_choose_file_path);
+        free(body);
+        HttpClientClse(&client);
+        HttpClientUnprepare(&clientData);
+        return ERRCODE_FAIL;
+    }
+
+    size_t total_written = 0;
     do
     {
         HTTPC_RESULT recv_res = HttpClientRecvResponse(&client, &clientData);
@@ -187,8 +223,18 @@ uint32_t ln_story_choose_http(const char *device_id,
             // 处理接收到的数据块
             if (clientData.contentBlockLen > 0)
             {
-                do_ln_http_server_recv_data(LN_HTTP_RECV_STORY_CHOOSE,
-                                            clientData.responseBuf, clientData.contentBlockLen);
+                size_t written = fwrite(clientData.responseBuf, 1, clientData.contentBlockLen, fd);
+                if (written != clientData.contentBlockLen)
+                {
+                    printf("story choose: write failed, written=%zu, expected=%d\n",
+                           written, clientData.contentBlockLen);
+                    free(body);
+                    HttpClientClse(&client);
+                    HttpClientUnprepare(&clientData);
+                    fclose(fd);
+                    return ERRCODE_FAIL;
+                }
+                total_written += written;
             }
         }
         else
@@ -201,5 +247,12 @@ uint32_t ln_story_choose_http(const char *device_id,
     free(body);
     HttpClientUnprepare(&clientData);
     HttpClientClse(&client);
+    fclose(fd);
+
+    if (total_written > 0)
+    {
+        ln_parse_story_file();
+    }
+
     return ERRCODE_SUCC;
 }