From 527d7c30c2df50cb027e1b1a81f8e67f3e5bddea Mon Sep 17 00:00:00 2001 From: =?utf8?q?=E8=83=A1=E4=BC=A0=E5=86=9B?= <1224667075@qq.com> Date: Mon, 29 Sep 2025 07:31:22 +0000 Subject: [PATCH] Merge #2426 into sdk650pathc0423 from refs/changes/2426/head MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Code review from 胡传军 * refs/changes/2426/head: (6 commits squashed) - 增加缓存文件清理 - Merge branch 'sdk650pathc0423' of https://codeup.aliyun.com/64191456b9581d62e8f81ac0/FD230 into sdk650pathc0423 - 增加文字开始故事接口 - Merge branch 'sdk650pathc0423' of https://codeup.aliyun.com/64191456b9581d62e8f81ac0/FD230 into sdk650pathc0423 - ln 兼容未知命令解析 - Merge branch 'sdk650pathc0423' of https://codeup.aliyun.com/64191456b9581d62e8f81ac0/FD230 into sdk650pathc0423 Signed-off-by: aliyun8003852482 <1224667075@qq.com> Reviewed-by: snickersun@163.com Merged-by: snickersun@163.com CR-link: https://codeup.aliyun.com/64191456b9581d62e8f81ac0/FD230/change/2426 --- .../zhihui/ai/media/ai_media_service_ext.cpp | 3 + .../zhihui/leiniao/imsl_ln/leiniao_api_at.c | 30 +++ code/sdk/zhihui/leiniao/imsl_ln/ln_api.h | 2 + .../zhihui/leiniao/imsl_ln/ln_api_websocket.c | 220 ++++++++++++++++-- 4 files changed, 239 insertions(+), 16 deletions(-) diff --git a/code/sdk/zhihui/ai/media/ai_media_service_ext.cpp b/code/sdk/zhihui/ai/media/ai_media_service_ext.cpp index bd77bbd4e..dfda41791 100644 --- a/code/sdk/zhihui/ai/media/ai_media_service_ext.cpp +++ b/code/sdk/zhihui/ai/media/ai_media_service_ext.cpp @@ -1906,6 +1906,9 @@ int32_t AI_ForceInterrupt(void) // 立即清空队列 ClearAIVoiceQueue(g_aiVoiceContext); + // 清理文件缓存 + AI_ClearFileCache(); + // 立即暂停和清空输出流 PauseAudioStreams(); ClearAudioStreams(); 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 3cbb8da4c..e7ab7d3ff 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c @@ -838,6 +838,35 @@ static uint32_t ln_ws_end_audio_chat_at(uint8_t *para, uint32_t paraLen, char *s return ERRCODE_SUCC; } +/* AT^LN_WS_SEND_RANDOM_STORY - 发送随机故事请求 */ +static uint32_t ln_ws_send_random_story_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + VAD_UNUSED(strCommonCmd); + + const char *story_theme = "冒险"; // 默认主题 + + // 解析参数 + if (para != NULL && paraLen > 0) { + uint8_t argv[LN_MAX_ARGC][LN_MAX_ARGV_LEN] = {0}; + uint32_t argc = 0; + + if (ConvertCmdToParam(para, paraLen, argv, &argc) == VAD_SUCCESS && argc >= 1) { + story_theme = (const char *)argv[0]; + } + } + + printf("[LN][WS_SEND_RANDOM_STORY] Sending random story request with theme: %s\n", story_theme); + + int ret = ln_ws_send_random_story_request(story_theme); + if (ret != 0) + { + printf("[LN][WS_SEND_RANDOM_STORY] failed: %d\n", ret); + return ERRCODE_FAIL; + } + printf("[LN][WS_SEND_RANDOM_STORY] success\n"); + return ERRCODE_SUCC; +} + /* AT^LN_WS_SLEEP */ static uint32_t ln_ws_sleep_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { @@ -1817,6 +1846,7 @@ static const at_cmd_table_t g_ln_api_cmd[] = { {(char *)"AT^LN_WS_CLOSE_STATUS", ln_ws_close_status_at, (char *)"查询WebSocket关闭状态: AT^LN_WS_CLOSE_STATUS"}, {(char *)"AT^LN_WS_START_AUDIO_CHAT", ln_ws_start_audio_chat_at, (char *)"开始音频聊天: AT^LN_WS_START_AUDIO_CHAT[=0/1]"}, {(char *)"AT^LN_WS_END_AUDIO_CHAT", ln_ws_end_audio_chat_at, (char *)"结束音频聊天: AT^LN_WS_END_AUDIO_CHAT"}, + {(char *)"AT^LN_WS_SEND_RANDOM_STORY", ln_ws_send_random_story_at, (char *)"发送随机故事请求: AT^LN_WS_SEND_RANDOM_STORY[=storyTheme]"}, /* 音频播放 */ {(char *)"AT^LN_STORY_PLAY_AT", ln_audio_play_at, (char *)"播放音频: AT^LN_STORY_PLAY_AT"}, diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h index 51aed1d89..2323e1ccc 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h @@ -311,6 +311,7 @@ typedef enum { WS_CMD_PONG, /* 心跳回应 (Pong) */ WS_CMD_VOICE_CHAT_RESPONSE, /* 语音聊天响应 (VoiceChatResponse) */ WS_CMD_CHOOSE_STORY_RESPONSE, /* 选择故事响应 (ChooseStoryResponse) */ + WS_CMD_RANDOM_STORY_RESPONSE, /* 随机故事响应 (RandomStoryResponse) */ WS_CMD_CONNECTED, /* WebSocket连接成功 */ WS_CMD_DISCONNECTED, /* WebSocket断连 */ WS_CMD_MAX /* 最大命令 */ @@ -349,6 +350,7 @@ typedef enum { int ln_ws_start(ws_message_callback_t_ln callback); int ln_ws_send_text(const char *text); int ln_ws_send_audio_data(const uint8_t *audio_data, uint32_t audio_size, bool finished); +int ln_ws_send_random_story_request(const char *story_theme); int ln_ws_start_audio_chat(ln_audio_chat_command_t command_type); int ln_ws_end_audio_chat(void); const char* ln_ws_get_current_chat_id(void); diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c index 3bb39a8b9..063a5bd87 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c @@ -137,19 +137,19 @@ static void ln_ws_event_callback(ws_event_type_t event_type) break; case WS_EVENT_DISCONNECTED: printf("[LN][WS] Event: Disconnected\n"); - g_ws_connected = WS_STATE_DISCONNECTED; + g_ws_connected = WS_STATE_DISCONNECTED; // 触发断连事件 if (g_ws_message_callback != NULL) { g_ws_message_callback(NULL, WS_CMD_DISCONNECTED); } - /* 断开后增加重连计数,由监督线程在下一次连接时携带 */ - if (g_ws_running) { - if (g_ws_reconnect_count < 0xFFFFFFFFu) { - g_ws_reconnect_count++; - } - } + /* 断开后增加重连计数,由监督线程在下一次连接时携带 */ + if (g_ws_running) { + if (g_ws_reconnect_count < 0xFFFFFFFFu) { + g_ws_reconnect_count++; + } + } break; default: printf("[LN][WS] Event: Unknown event type %d\n", event_type); @@ -408,10 +408,17 @@ int ln_ws_send_audio_data(const uint8_t *audio_data, uint32_t audio_size, bool f // 开始新的音频聊天 int ln_ws_start_audio_chat(ln_audio_chat_command_t command_type) { + if(g_ws_connected != WS_STATE_CONNECTED) + { + printf("[LN][WS] g_ws_connected!=WS_STATE_CONNECTED"); + return 0; + } + if (g_audio_chat_ctx.is_active) { printf("[LN][WS] Audio chat already active: %s\n", g_audio_chat_ctx.chat_id); return 0; } + // AI_ForceInterrupt(); // 设置命令类型 g_audio_chat_command_type = command_type; @@ -650,6 +657,78 @@ int ln_ws_send_text(const char *text) return uapi_ws_send_text(text); } +/* 发送随机故事请求 */ +int ln_ws_send_random_story_request(const char *story_theme) +{ + if (!story_theme) { + printf("[LN][WS] Invalid story theme parameter\n"); + return -1; + } + + if (g_ws_connected != WS_STATE_CONNECTED) { + printf("[LN][WS] Cannot send random story request, connection state: %d\n", g_ws_connected); + return -1; + } + + // 生成聊天ID(使用当前对话ID) + char chat_id[128]; + generate_chat_id(chat_id, sizeof(chat_id)); + + // 生成消息ID + char message_id[128]; + generate_message_id(message_id, sizeof(message_id), chat_id, 1); + + // 构建JSON消息 + cJSON *json = cJSON_CreateObject(); + if (!json) { + printf("[LN][WS] Failed to create JSON object\n"); + return -1; + } + + // 添加基本字段 + cJSON_AddStringToObject(json, "deviceId", uapi_ln_get_device_id()); + cJSON_AddNumberToObject(json, "timestamp", (double)time(NULL)); + cJSON_AddStringToObject(json, "messageId", message_id); + cJSON_AddStringToObject(json, "command", "RandomStoryRequest"); + + // 添加data字段 + cJSON *data = cJSON_CreateObject(); + if (!data) { + printf("[LN][WS] Failed to create data JSON object\n"); + cJSON_Delete(json); + return -1; + } + + cJSON_AddStringToObject(data, "chatId", chat_id); + cJSON_AddStringToObject(data, "storyTheme", story_theme); + cJSON_AddItemToObject(json, "data", data); + + // 转换为字符串 + char *json_string = cJSON_Print(json); + if (!json_string) { + printf("[LN][WS] Failed to convert JSON to string\n"); + cJSON_Delete(json); + return -1; + } + + printf("[LN][WS] Sending RandomStoryRequest: %s\n", json_string); + + // 发送消息 + int ret = uapi_ws_send_text(json_string); + + // 清理资源 + free(json_string); + cJSON_Delete(json); + + if (ret == 0) { + printf("[LN][WS] RandomStoryRequest sent successfully\n"); + } else { + printf("[LN][WS] Failed to send RandomStoryRequest\n"); + } + + return ret; +} + /* 停止:关闭心跳并关闭连接 */ int ln_ws_stop(void) @@ -744,6 +823,8 @@ static ws_command_t ln_ws_parse_command_string(const char *command_str) return WS_CMD_VOICE_CHAT_RESPONSE; } else if (strcmp(command_str, "ChooseStoryResponse") == 0) { return WS_CMD_CHOOSE_STORY_RESPONSE; + } else if (strcmp(command_str, "RandomStoryResponse") == 0) { + return WS_CMD_RANDOM_STORY_RESPONSE; } else { return WS_CMD_UNKNOWN; } @@ -778,6 +859,24 @@ static ws_command_t ln_ws_parse_command_string(const char *command_str) // "data":{"audioUrl":"http://ambybox-api.test.leiniao.com/ambybox-device-api/file/774317549446_20250926175018_1758880218961.mp3", // "textContent":"(颔首,夹着烟的手指轻敲桌面)来者何人?"}}} + +// { +// "deviceId": "DEVICE_001", +// "timestamp": 1640995201, +// "messageId": "random_story_response_001", +// "command": "RandomStoryResponse", +// "data": { +// "chatId": "random_story_12345", +// "finished": false, +// "data": { +// "storyTheme": "冒险故事", +// "storyContent": "在一个阳光明媚的早晨", +// "storyAudioUrl": "https://example.com/story/audio/chunk_001.mp3", +// "storyDuration": null +// }, +// "errorCode": null +// } +// } static int ln_ws_parse_data_message(const char *json_str, ws_data_msg_t *data_msg) { if (!json_str || !data_msg) { @@ -833,6 +932,11 @@ static int ln_ws_parse_data_message(const char *json_str, ws_data_msg_t *data_ms strncpy(data_msg->command_str, command->valuestring, sizeof(data_msg->command_str) - 1); data_msg->command_str[sizeof(data_msg->command_str) - 1] = '\0'; data_msg->command = ln_ws_parse_command_string(command->valuestring); + + // 对于未知命令,记录日志但不返回错误 + if (data_msg->command == WS_CMD_UNKNOWN) { + printf("[LN][WS] Unknown command received: %s\n", command->valuestring); + } } else { printf("[LN][WS] Missing or invalid command in message\n"); cJSON_Delete(root); @@ -919,6 +1023,57 @@ static int ln_ws_parse_data_message(const char *json_str, ws_data_msg_t *data_ms data_msg->finished ? "true" : "false", data_msg->chat_id, data_msg->text_content, data_msg->audio_url); } + // 解析RandomStoryResponse特定字段 + if (data_msg->command == WS_CMD_RANDOM_STORY_RESPONSE) { + cJSON *data_obj = cJSON_GetObjectItem(root, "data"); + if (data_obj && cJSON_IsObject(data_obj)) { + // 解析finished字段 + cJSON *finished = cJSON_GetObjectItem(data_obj, "finished"); + if (finished && cJSON_IsBool(finished)) { + data_msg->finished = cJSON_IsTrue(finished); + } + + // 解析chatId字段 + cJSON *chat_id = cJSON_GetObjectItem(data_obj, "chatId"); + if (chat_id && cJSON_IsString(chat_id) && chat_id->valuestring) { + strncpy(data_msg->chat_id, chat_id->valuestring, sizeof(data_msg->chat_id) - 1); + data_msg->chat_id[sizeof(data_msg->chat_id) - 1] = '\0'; + } + + // 解析data.data字段 + cJSON *data_data = cJSON_GetObjectItem(data_obj, "data"); + if (data_data && cJSON_IsObject(data_data)) { + // 解析storyTheme字段,存储到text_content中 + cJSON *story_theme = cJSON_GetObjectItem(data_data, "storyTheme"); + if (story_theme && cJSON_IsString(story_theme) && story_theme->valuestring) { + strncpy(data_msg->text_content, story_theme->valuestring, sizeof(data_msg->text_content) - 1); + data_msg->text_content[sizeof(data_msg->text_content) - 1] = '\0'; + } + + // 解析storyContent字段(RandomStoryResponse特有) + cJSON *story_content = cJSON_GetObjectItem(data_data, "storyContent"); + if (story_content && cJSON_IsString(story_content) && story_content->valuestring) { + // 如果text_content为空,使用storyContent填充 + if (strlen(data_msg->text_content) == 0) { + strncpy(data_msg->text_content, story_content->valuestring, sizeof(data_msg->text_content) - 1); + data_msg->text_content[sizeof(data_msg->text_content) - 1] = '\0'; + } + } + + // 解析storyAudioUrl字段,存储到audio_url中 + cJSON *story_audio_url = cJSON_GetObjectItem(data_data, "storyAudioUrl"); + if (story_audio_url && cJSON_IsString(story_audio_url) && story_audio_url->valuestring) { + strncpy(data_msg->audio_url, story_audio_url->valuestring, sizeof(data_msg->audio_url) - 1); + data_msg->audio_url[sizeof(data_msg->audio_url) - 1] = '\0'; + } + } + } + + printf("[LN][WS] RandomStoryResponse parsed: finished=%s, chatId=%s, storyTheme=%s, storyContent=%s, storyAudioUrl=%s\n", + data_msg->finished ? "true" : "false", data_msg->chat_id, data_msg->text_content, + data_msg->text_content, data_msg->audio_url); + } + // 保存原始JSON字符串 data_msg->raw_json = (char*)malloc(strlen(json_str) + 1); if (data_msg->raw_json) { @@ -969,43 +1124,43 @@ static void ln_ws_default_message_handler(const ws_data_msg_t *data_msg, ws_comm break; case WS_CMD_PONG: if (data_msg) { - printf("[LN][WS] Default: Processing Pong message\n"); - // 更新心跳状态、记录响应时间等 + printf("[LN][WS] Default: Processing Pong message\n"); + // 更新心跳状态、记录响应时间等 } break; case WS_CMD_CHARACTER_CHANGE: if (data_msg) { - printf("[LN][WS] Default: Processing CharacterChange message\n"); + printf("[LN][WS] Default: Processing CharacterChange message\n"); } break; case WS_CMD_SLEEP_MODE_START: if (data_msg) { - printf("[LN][WS] Default: Processing SleepModeStart message\n"); + printf("[LN][WS] Default: Processing SleepModeStart message\n"); } break; case WS_CMD_SLEEP_MODE_END: if (data_msg) { - printf("[LN][WS] Default: Processing SleepModeEnd message\n"); + printf("[LN][WS] Default: Processing SleepModeEnd message\n"); } break; case WS_CMD_DEVICE_BIND: if (data_msg) { - printf("[LN][WS] Default: Processing DeviceBind message\n"); + printf("[LN][WS] Default: Processing DeviceBind message\n"); } break; case WS_CMD_DEVICE_UNBIND: if (data_msg) { - printf("[LN][WS] Default: Processing DeviceUnbind message\n"); + printf("[LN][WS] Default: Processing DeviceUnbind message\n"); } break; case WS_CMD_SCHEDULE_REMINDER: if (data_msg) { - printf("[LN][WS] Default: Processing ScheduleReminder message\n"); + printf("[LN][WS] Default: Processing ScheduleReminder message\n"); } break; case WS_CMD_SCHEDULE_REMINDER_CFG: if (data_msg) { - printf("[LN][WS] Default: Processing ScheduleReminderConfig message\n"); + printf("[LN][WS] Default: Processing ScheduleReminderConfig message\n"); } break; case WS_CMD_VOICE_CHAT_RESPONSE: @@ -1026,6 +1181,25 @@ static void ln_ws_default_message_handler(const ws_data_msg_t *data_msg, ws_comm printf("[LN][WS] Story Audio URL: %s\n", data_msg->audio_url); } break; + case WS_CMD_RANDOM_STORY_RESPONSE: + if (data_msg) { + printf("[LN][WS] Default: Processing RandomStoryResponse message\n"); + printf("[LN][WS] RandomStoryResponse details: finished=%s, chatId=%s\n", + data_msg->finished ? "true" : "false", data_msg->chat_id); + printf("[LN][WS] Story Theme: %s\n", data_msg->text_content); + printf("[LN][WS] Story Audio URL: %s\n", data_msg->audio_url); + } + break; + case WS_CMD_UNKNOWN: + if (data_msg) { + printf("[LN][WS] Default: Processing unknown command message\n"); + printf("[LN][WS] Unknown command details: command=%s, deviceId=%s, messageId=%s\n", + data_msg->command_str, data_msg->device_id, data_msg->message_id); + if (data_msg->raw_json) { + printf("[LN][WS] Raw JSON: %s\n", data_msg->raw_json); + } + } + break; default: printf("[LN][WS] Default: Processing unknown command: %d\n", type); break; @@ -1345,6 +1519,20 @@ static void ln_ws_process_incremental_base64_data(const char *new_data, size_t n printf("[LN][WS] Not enough data for Base64 decode yet: %zu\n", g_base64_cache_size); } } + + +// { +// "deviceId": "DEVICE_001", +// "timestamp": 1640995200, +// "messageId": "random_story_001", +// "command": "RandomStoryRequest", +// "data": { +// "chatId": "random_story_12345", +// "storyTheme": "冒险" +// } +// } + + //////////////////////////////////////////////////////////////////////////// // int uapi_ln_download_mp3_file(const char *url) -- 2.17.1