From: 胡传军 <1224667075@qq.com> Date: Thu, 25 Sep 2025 11:18:46 +0000 (+0000) Subject: Merge #2391 into sdk650pathc0423 from refs/changes/2391/head X-Git-Tag: V1.0.9_20251128_194606_LN~317^2~1 X-Git-Url: http://git.snicker.com.cn/css/static/gitweb.css?a=commitdiff_plain;h=faf071058942441bc670aa4400a06cf0f6f78519;p=mirrors%2FFD230.git Merge #2391 into sdk650pathc0423 from refs/changes/2391/head Code review from 胡传军 * refs/changes/2391/head: (3 commits squashed) - 修复因多线程操作导致的多次播放重复文字问题(增加锁保护 ) - Merge branch 'sdk650pathc0423' of https://codeup.aliyun.com/64191456b9581d62e8f81ac0/FD230 into sdk650pathc0423 - 增加haisi websocket接口 增加ai2.5接口 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/2391 --- diff --git a/code/sdk/application/brandy/brandy_standard/service_init.c b/code/sdk/application/brandy/brandy_standard/service_init.c index c507d3973..9b7fedc2c 100644 --- a/code/sdk/application/brandy/brandy_standard/service_init.c +++ b/code/sdk/application/brandy/brandy_standard/service_init.c @@ -109,6 +109,10 @@ #include "ai_old_at_cmd.h" #endif +#ifdef SUPPORT_ZHIHUI_AI_NEW +#include "ai_V2_5_at_cmd.h" +#endif + static void register_at_service(void) { #ifdef SUPPORT_AT_GRAPHIC @@ -189,6 +193,12 @@ static void register_at_service(void) at_cmd_add_moudle(ai_old_table, ai_old_cmd_count); #endif +#ifdef SUPPORT_ZHIHUI_AI_NEW + uint32_t ai_new_cmd_count; + at_cmd_table_t *ai_new_table = get_ai_2_5_cmd_table(&ai_new_cmd_count); + at_cmd_add_moudle(ai_new_table, ai_new_cmd_count); +#endif + #ifdef SUPPORT_AT_LIS2DOC uint32_t lis2doc_cmd_count; at_cmd_table_t *lis2doc_table = get_lis2doc_cmd_table(&lis2doc_cmd_count); diff --git a/code/sdk/open_source/openharmony/foundation/ace/ace_engine_lite/brandy-native-js-fd230/libwebsocket.a b/code/sdk/open_source/openharmony/foundation/ace/ace_engine_lite/brandy-native-js-fd230/libwebsocket.a index 93b5e6f80..909c556d0 100644 Binary files a/code/sdk/open_source/openharmony/foundation/ace/ace_engine_lite/brandy-native-js-fd230/libwebsocket.a and b/code/sdk/open_source/openharmony/foundation/ace/ace_engine_lite/brandy-native-js-fd230/libwebsocket.a differ diff --git a/code/sdk/open_source/openharmony/foundation/ace/ace_engine_lite/brandy-native-js-fd230_user/libwebsocket.a b/code/sdk/open_source/openharmony/foundation/ace/ace_engine_lite/brandy-native-js-fd230_user/libwebsocket.a index 93b5e6f80..4e97817e7 100644 Binary files a/code/sdk/open_source/openharmony/foundation/ace/ace_engine_lite/brandy-native-js-fd230_user/libwebsocket.a and b/code/sdk/open_source/openharmony/foundation/ace/ace_engine_lite/brandy-native-js-fd230_user/libwebsocket.a differ diff --git a/code/sdk/open_source/openharmony/foundation/ace/ace_engine_lite/brandy-native-js/libwebsocket.a b/code/sdk/open_source/openharmony/foundation/ace/ace_engine_lite/brandy-native-js/libwebsocket.a index 93b5e6f80..4e97817e7 100644 Binary files a/code/sdk/open_source/openharmony/foundation/ace/ace_engine_lite/brandy-native-js/libwebsocket.a and b/code/sdk/open_source/openharmony/foundation/ace/ace_engine_lite/brandy-native-js/libwebsocket.a differ diff --git a/code/sdk/open_source/openharmony/foundation/ace/ace_engine_lite/modules/netstack/websocket/include/net_websocket_type.h b/code/sdk/open_source/openharmony/foundation/ace/ace_engine_lite/modules/netstack/websocket/include/net_websocket_type.h index a1d4fa27a..c9d627801 100644 --- a/code/sdk/open_source/openharmony/foundation/ace/ace_engine_lite/modules/netstack/websocket/include/net_websocket_type.h +++ b/code/sdk/open_source/openharmony/foundation/ace/ace_engine_lite/modules/netstack/websocket/include/net_websocket_type.h @@ -51,10 +51,12 @@ typedef void (*WebSocketOnOpenCallback)(int client); * @param client WebSocket client fd. * @param data Data received by the WebSocket client. * @param length Length of the data received by the WebSocket client. + * @param isFinish data received is finished or not + * @param isBinary data received is binary or not * @since 11 * @version 1.0 */ -typedef void (*WebSocketOnMessageCallback)(int client, const char *data, size_t length, bool isFinished); +typedef void (*WebSocketOnMessageCallback)(int client, const char *data, size_t length, bool isFinished, bool isBinary); /** * @brief Defines the callback function invoked when an error message is received. diff --git a/code/sdk/open_source/openharmony/foundation/ace/ace_engine_lite/modules/websocket/websocket_module.cpp b/code/sdk/open_source/openharmony/foundation/ace/ace_engine_lite/modules/websocket/websocket_module.cpp index 4b2b30160..307e315d1 100644 --- a/code/sdk/open_source/openharmony/foundation/ace/ace_engine_lite/modules/websocket/websocket_module.cpp +++ b/code/sdk/open_source/openharmony/foundation/ace/ace_engine_lite/modules/websocket/websocket_module.cpp @@ -67,7 +67,7 @@ static void OnOpen(int client) JSI::ReleaseValueList(value, err); } -static void OnMessage(int client, const char *data, size_t length, bool isFinished) +static void OnMessage(int client, const char *data, size_t length, bool isFinished,bool isBinary) { auto it = WebsocketModule::eventCallbacks.find(std::string("message")); if (it == WebsocketModule::eventCallbacks.end()) { diff --git a/code/sdk/zhihui/CMakeLists.txt b/code/sdk/zhihui/CMakeLists.txt index e005bdebb..b6c40a308 100644 --- a/code/sdk/zhihui/CMakeLists.txt +++ b/code/sdk/zhihui/CMakeLists.txt @@ -27,3 +27,4 @@ add_subdirectory_if_exist(ai/media) add_subdirectory_if_exist(websocket) add_subdirectory_if_exist(4gTestcpp) add_subdirectory_if_exist(ppp) +add_subdirectory_if_exist(ai_new) diff --git a/code/sdk/zhihui/ai/include/ai_media.h b/code/sdk/zhihui/ai/include/ai_media.h index 89a8db4d2..433d41925 100644 --- a/code/sdk/zhihui/ai/include/ai_media.h +++ b/code/sdk/zhihui/ai/include/ai_media.h @@ -91,7 +91,17 @@ void ai_media_play_buffer(uint8_t *data, uint32_t size); typedef void (*AIVoiceDataCallback)(const uint8_t* data, uint32_t size, void* userData); -int32_t old_Start_AIVoice(AIVoiceDataCallback dataCallback, void* userData); +// AI语音音频参数结构体 +// AI语音音频参数结构体 +typedef struct { + uint32_t audioFormat; // 音频格式 + uint32_t channelCount; // 声道数 + uint32_t sampleRate; // 采样率 +} AIVoiceAudioParams; + + +int32_t old_Start_AIVoice(AIVoiceDataCallback dataCallback, void* userData, + const AIVoiceAudioParams* audioParams); /* * @brief 写入音频数据到输出流 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 1cacf170b..eb821152a 100644 --- a/code/sdk/zhihui/ai/media/ai_media_service_ext.cpp +++ b/code/sdk/zhihui/ai/media/ai_media_service_ext.cpp @@ -46,7 +46,14 @@ const int32_t NUMBER_BASE = 10; const int32_t CHANNEL_COUNT = 2; const int32_t SAMPLERATE_16000 = 16000; const uint32_t AI_VOICE_BUFFER_SIZE = 0x800; // 2KB buffer for AI voice -const uint32_t AI_VOICE_QUEUE_SIZE = 10; +const uint32_t AI_VOICE_QUEUE_SIZE = 128; // 队列大小 + +// 默认音频参数 +const AudioCodecFormat DEFAULT_AUDIO_FORMAT = MP3; +const uint32_t DEFAULT_CHANNEL_COUNT = 1; +const uint32_t DEFAULT_SAMPLE_RATE = SAMPLERATE_16000; + +// AI语音音频参数结构体IVoiceAudioParams; // 定义音频数据回调函数类型 @@ -71,6 +78,10 @@ typedef struct { void* callbackUserData; // 回调函数用户数据 osMessageQueueId_t inputQueue; // 输入播放数据队列 uint32_t queueMsgCount; // 队列消息数量 +// 音频参数 + AudioCodecFormat audioFormat; // 音频格式 + uint32_t channelCount; // 声道数 + uint32_t sampleRate; // 采样率 } AIVoiceContext; static MediaMutexHandle g_aiVoiceContextMutex = nullptr; @@ -236,10 +247,10 @@ static int32_t LocalAIVoiceStart(AIVoiceContext *context) // create downlink AudioStreamIn CapturerInputConfig captureConfig = {}; - captureConfig.audioFormat = MP3; - captureConfig.channelCount = 1; + captureConfig.audioFormat = context->audioFormat; + captureConfig.channelCount = context->channelCount; captureConfig.linkDir = AUDIO_DOWN_LINK; - captureConfig.sampleRate = SAMPLERATE_16000; + captureConfig.sampleRate = context->sampleRate; captureConfig.sessionID = context->sessionId; captureConfig.streamType = AUDIO_STREAM_VOICE_ASSISTANT; @@ -250,10 +261,10 @@ static int32_t LocalAIVoiceStart(AIVoiceContext *context) // create uplink AudioStreamOut AudioRendererConfig renderConfig = {}; - renderConfig.audioFormat = MP3; - renderConfig.channelCount = 1; + renderConfig.audioFormat = context->audioFormat; + renderConfig.channelCount = context->channelCount; renderConfig.linkDir = AUDIO_UP_LINK; - renderConfig.sampleRate = SAMPLERATE_16000; + renderConfig.sampleRate = context->sampleRate; renderConfig.sessionID = context->sessionId; renderConfig.streamType = AUDIO_STREAM_VOICE_ASSISTANT; @@ -327,7 +338,7 @@ static int32_t CreateAIVoiceInputThread(AIVoiceContext *context) task_cfg.create_queue_handle = NULL; task_cfg.attr.name = "AIVoiceInputThread"; - task_cfg.attr.priority = (osPriority_t)16; // 使用合适的优先级 + task_cfg.attr.priority = (osPriority_t)24; // 使用合适的优先级 task_cfg.attr.stack_size = 0x2000; // 8KB栈空间 task_cfg.func = AIVoiceInputThreadFunc; task_cfg.use_mem = USE_EXTEND_MEM; @@ -622,7 +633,8 @@ static void AIVoiceThread(void *priv) // 简单清理,避免复杂的互斥锁操作 printf("AI voice thread cleanup completed\r\n"); } -int32_t old_Start_AIVoice(AIVoiceDataCallback dataCallback, void* userData) +int32_t old_Start_AIVoice(AIVoiceDataCallback dataCallback, void* userData, + const AIVoiceAudioParams* audioParams) { MEDIA_ERR_LOG("StartAIVoice"); app_task_definition_t task_cfg; @@ -653,6 +665,21 @@ int32_t old_Start_AIVoice(AIVoiceDataCallback dataCallback, void* userData) // 设置回调函数 g_aiVoiceContext->dataCallback = dataCallback; g_aiVoiceContext->callbackUserData = userData; + + // 设置音频参数(如果未传入则使用默认值) + if (audioParams != nullptr) { + g_aiVoiceContext->audioFormat = (AudioCodecFormat)audioParams->audioFormat; + g_aiVoiceContext->channelCount = audioParams->channelCount; + g_aiVoiceContext->sampleRate = audioParams->sampleRate; + MEDIA_INFO_LOG("Using custom audio params: format=%d, channels=%u, sampleRate=%u", + audioParams->audioFormat, audioParams->channelCount, audioParams->sampleRate); + } else { + g_aiVoiceContext->audioFormat = DEFAULT_AUDIO_FORMAT; + g_aiVoiceContext->channelCount = DEFAULT_CHANNEL_COUNT; + g_aiVoiceContext->sampleRate = DEFAULT_SAMPLE_RATE; + MEDIA_INFO_LOG("Using default audio params: format=%d, channels=%u, sampleRate=%u", + DEFAULT_AUDIO_FORMAT, DEFAULT_CHANNEL_COUNT, DEFAULT_SAMPLE_RATE); + } MediaMutexUnLock(mutex); MediaThreadJoin(&g_aiVoiceContext->taskHandle); @@ -824,7 +851,16 @@ static void ExampleAIVoiceDataCallback(const uint8_t* data, uint32_t size, void* } // 使用示例: -// int32_t result = old_Start_AIVoice(ExampleAIVoiceDataCallback, nullptr); +// 1. 使用默认参数 +// int32_t result = old_Start_AIVoice(ExampleAIVoiceDataCallback, nullptr, nullptr); +// +// 2. 使用自定义参数 +// AIVoiceAudioParams audioParams = { +// .audioFormat = MP3, +// .channelCount = 1, +// .sampleRate = 16000 +// }; +// int32_t result = old_Start_AIVoice(ExampleAIVoiceDataCallback, nullptr, &audioParams); // // 队列使用示例: // uint8_t audioData[] = {0x01, 0x02, 0x03, 0x04}; @@ -878,6 +914,7 @@ int32_t AI_WriteAudioData(const uint8_t* data, uint32_t size) // MEDIA_INFO_LOG("Successfully wrote %u bytes of audio data to output stream", size); return 0; } + /** * @brief 检查AI语音服务是否正在运行 * @return true 正在运行,false 未运行 diff --git a/code/sdk/zhihui/ai/src/ai_connection_manager.c b/code/sdk/zhihui/ai/src/ai_connection_manager.c index 37a845f76..9ff161b14 100644 --- a/code/sdk/zhihui/ai/src/ai_connection_manager.c +++ b/code/sdk/zhihui/ai/src/ai_connection_manager.c @@ -1075,7 +1075,7 @@ static ai_error_t websocket_connect(ai_connection_info_t *conn_info) AI_LOGI("Creating WebSocket client for service %s", g_service_names[conn_info->service_type]); conn_info->websocket = WebSocketClientConstructor(websocket_on_connected, websocket_on_message, - websocket_on_error, websocket_on_disconnected); + websocket_on_error, websocket_on_disconnected); if (conn_info->websocket < 0) { AI_LOGE("Failed to create WebSocket client: %d", conn_info->websocket); return AI_ERROR_MEMORY_ALLOC; diff --git a/code/sdk/zhihui/ai_new/CMakeLists.txt b/code/sdk/zhihui/ai_new/CMakeLists.txt new file mode 100644 index 000000000..5e4bf76c6 --- /dev/null +++ b/code/sdk/zhihui/ai_new/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory_if_exist(ai_V2.5) \ No newline at end of file diff --git a/code/sdk/zhihui/ai_new/ai_V2.5/CMakeLists.txt b/code/sdk/zhihui/ai_new/ai_V2.5/CMakeLists.txt new file mode 100644 index 000000000..8287dc772 --- /dev/null +++ b/code/sdk/zhihui/ai_new/ai_V2.5/CMakeLists.txt @@ -0,0 +1,60 @@ +#=============================================================================== +# @brief cmake file +# Copyright (c) @CompanyNameMagicTag 2022-2022. All rights reserved. +#=============================================================================== + +set(COMPONENT_NAME "ai_V2.5") + +set(PUBLIC_HEADER +${CMAKE_CURRENT_SOURCE_DIR} +) +set(PRIVATE_HEADER +${ROOT_DIR}/open_source/openharmony/third_party/cJSON +${ROOT_DIR}/open_source/openharmony/foundation/ace/ace_engine_lite/modules/netstack/websocket/include +${ROOT_DIR}/open_source/openharmony/third_party/lwip/lwip_v2.1.3/src/include +${ROOT_DIR}/open_source/openharmony/third_party/lwip/lwip_adapter/liteos/src/include +${ROOT_DIR}/zhihui/websocket/os_ws +${ROOT_DIR}/zhihui/ai/include +) + +set(SOURCES +${CMAKE_CURRENT_SOURCE_DIR}/ai_V2_5_at_cmd.cpp +${CMAKE_CURRENT_SOURCE_DIR}/ai_v2_5_main.cpp +) + +set(COMPONENT_PUBLIC_CCFLAGS +) + +set(COMPONENT_CCFLAGS + -std=c++11 + -Wno-error + -Wno-unused-parameter + -fno-exceptions + -fno-unwind-tables + -fno-asynchronous-unwind-tables + -Wno-undef + -Wno-bitwise-op-parentheses + -Wno-shift-op-parentheses + -nostdlibinc + -nostdinc + -nostdlib + -Wno-c++11-narrowing + -Wno-error=unused-function +) + +set(WHOLE_LINK + true +) + +set(MAIN_COMPONENT + false +) + +set(PUBLIC_DEFINES +SUPPORT_ZHIHUI_AI_NEW +) + +set(PRIVATE_DEFINES +) + +build_component() diff --git a/code/sdk/zhihui/ai_new/ai_V2.5/ai_V2_5_api.h b/code/sdk/zhihui/ai_new/ai_V2.5/ai_V2_5_api.h new file mode 100644 index 000000000..6cb7453ab --- /dev/null +++ b/code/sdk/zhihui/ai_new/ai_V2.5/ai_V2_5_api.h @@ -0,0 +1,95 @@ +/** + * Copyright (c) @CompanyNameMagicTag 2022-2022. All rights reserved. \n + * + * Description: Provides at cmd register func \n + * Author: @CompanyNameTag \n + * History: \n + * 2022-07-20, Create file. \n + */ +#ifndef AI_V2_5_API_H +#define AI_V2_5_API_H + +#include +#include +#include "errcode.h" +#include "soc_osal.h" +#include "at_cmd_api.h" + +/* AI音频参数配置 - 与ai_generate_start_config保持一致 */ +#define AI_AUDIO_FORMAT_PCM 0 // PCM格式 +#define AI_AUDIO_FORMAT_MP3 1 // MP3格式 +#define AI_DEFAULT_AUDIO_FORMAT AI_AUDIO_FORMAT_PCM +#define AI_DEFAULT_SAMPLE_RATE 16000 // 16kHz采样率 +#define AI_DEFAULT_CHANNEL_COUNT 1 // 单声道 +#define AI_DEFAULT_BIT_RATE 16 // 16位比特率 + +/* AI事件类型枚举 */ +typedef enum { + AI_EVENT_AUDIO_CREATE = 0, // 音频创建事件 + AI_EVENT_AUDIO_COMPLETED, // 音频完成事件 + AI_EVENT_CANCEL, // 取消事件 + AI_EVENT_AUDIO_PLAYED, // 音频播放完成事件 + + // ASR事件 + AI_EVENT_ASR_STARTED, // ASR开始事件 + AI_EVENT_ASR_RESULT, // ASR结果事件 + AI_EVENT_ASR_COMPLETED, // ASR完成事件 + + // LLM事件 + AI_EVENT_LLM_CREATED, // LLM创建事件 + AI_EVENT_LLM_DELTA, // LLM增量事件 + AI_EVENT_LLM_COMPLETED, // LLM完成事件 + AI_EVENT_LLM_DONE, // LLM结束事件 + + // TTS事件 + AI_EVENT_TTS_CREATED, // TTS创建事件 + AI_EVENT_TTS_STARTED, // TTS开始事件 + AI_EVENT_TTS_COMPLETED, // TTS完成事件 + + AI_EVENT_MAX // 事件类型总数 +} ai_event_type_t; + +/* AI事件数据结构体 */ +typedef struct { + ai_event_type_t event_type; // 事件类型 + char content[256]; // 事件内容(如ASR结果、LLM内容等) + char dialog_id[128]; // 对话ID + char sid[128]; // 会话ID +} ai_event_data_t; + +#ifdef __cplusplus +#if __cplusplus +extern "C" { +#endif /* __cplusplus */ +#endif /* __cplusplus */ + +int ai_service_start(void); + +/* JSON配置生成函数 */ +char* ai_generate_start_config(const char* device_id, const char* bot_id, + const char* workflow_id, const char* voice_name); +void ai_free_json_string(char* json_string); +void ai_print_start_config(void); +void ai_test_json_generation(void); + +/* AI配置发送函数 */ +int ai_send_start_config(void); +int ai_send_custom_start_config(const char* device_id, const char* bot_id, + const char* workflow_id, const char* voice_name); + +/* AI事件发送函数 */ +int ai_send_event(ai_event_type_t event_type); +const char* ai_get_event_name(ai_event_type_t event_type); + +/* AI事件解析函数 */ +ai_event_type_t ai_parse_event(const char* json_string, ai_event_data_t* event_data); + + + +#ifdef __cplusplus +#if __cplusplus +} +#endif /* __cplusplus */ +#endif /* __cplusplus */ + +#endif diff --git a/code/sdk/zhihui/ai_new/ai_V2.5/ai_V2_5_at_cmd.cpp b/code/sdk/zhihui/ai_new/ai_V2.5/ai_V2_5_at_cmd.cpp new file mode 100644 index 000000000..999f600e2 --- /dev/null +++ b/code/sdk/zhihui/ai_new/ai_V2.5/ai_V2_5_at_cmd.cpp @@ -0,0 +1,160 @@ + +#include "at_cmd_api.h" +#include "pm_control.h" +#include "cat1_server.h" +#include "ai_old_at_cmd.h" +#include "cat1_ctl.h" +#include +#include +#include +#include +#include +#include +#include "ai_V2_5_api.h" + + +// #include "phoneaddressmodel.h" // Commented out due to missing header + +#ifdef __cplusplus +#if __cplusplus +extern "C" { +#endif /* __cplusplus */ +#endif /* __cplusplus */ +#define ARRAY_COUNT(array) (sizeof(array) / sizeof(array[0])) +#define PPG_MAX_ARGC 8 +#define PPG_MAX_ARGV_LEN 64 + +#define PPG_SUCCESS 0 +#define PPG_FAILED (-1) + +#include "app_at_process.h" +#include "zhihui_ws.h" +#include "websocket_test.h" +#include "cmsis_os2.h" +#include "chat_https_servrt.h" + +static int ConvertCmdToParam(uint8_t *para, uint32_t paraLen, + uint8_t argv[PPG_MAX_ARGC][PPG_MAX_ARGV_LEN], uint32_t* argc) +{ + uint32_t pos = 0; + *argc = 0; + + for (uint32_t i = 0; i < paraLen; i++) { + if ((para[i] == ' ' || para[i] == '\n') && pos != 0) { + argv[*argc][pos] = '\0'; + *argc += 1; + pos = 0; + if (*argc >= PPG_MAX_ARGC) { + return PPG_FAILED; + } + } else { + argv[*argc][pos] = para[i]; + pos++; + if (pos >= PPG_MAX_ARGV_LEN) { + return PPG_FAILED; + } + } + } + if (pos != 0) { + *argc += 1; + } + + return PPG_SUCCESS; +} + + +static uint32_t Start(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(strCommonCmd); + uint8_t argv[PPG_MAX_ARGC][PPG_MAX_ARGV_LEN] = { 0 }; + uint32_t argc = 0; + printf("ai_msg\n"); + int ret = ConvertCmdToParam(para, paraLen, argv, &argc); + if (ret != PPG_SUCCESS) { + printf("CAT1TEST ATCMD Convert Command Failed\n"); + return ERRCODE_FAIL; + } + ai_service_start(); + return 0; +} + +static uint32_t SendEvent(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(strCommonCmd); + uint8_t argv[PPG_MAX_ARGC][PPG_MAX_ARGV_LEN] = { 0 }; + uint32_t argc = 0; + + int ret = ConvertCmdToParam(para, paraLen, argv, &argc); + if (ret != PPG_SUCCESS) { + printf("AI Event ATCMD Convert Command Failed\n"); + return ERRCODE_FAIL; + } + + if (argc < 1) { + printf("Usage: AT^SENDEVENT=\n"); + printf("Event types: 0=audio.create, 1=audio.completed, 2=cancel, 3=audio.played\n"); + return ERRCODE_FAIL; + } + + int event_type = atoi((char*)argv[0]); + if (event_type < 0 || event_type >= AI_EVENT_MAX) { + printf("Invalid event type: %d (valid range: 0-%d)\n", event_type, AI_EVENT_MAX - 1); + return ERRCODE_FAIL; + } + + printf("Sending AI event: %s (type=%d)\n", ai_get_event_name((ai_event_type_t)event_type), event_type); + ret = ai_send_event((ai_event_type_t)event_type); + + if (ret == 0) { + printf("Event sent successfully\n"); + return ERRCODE_SUCC; + } else { + printf("Failed to send event, ret=%d\n", ret); + return ERRCODE_FAIL; + } +} + +static uint32_t ListEvents(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(para); + UNUSED(paraLen); + UNUSED(strCommonCmd); + + printf("Available AI Events:\n"); + printf("0 - %s\n", ai_get_event_name(AI_EVENT_AUDIO_CREATE)); + printf("1 - %s\n", ai_get_event_name(AI_EVENT_AUDIO_COMPLETED)); + printf("2 - %s\n", ai_get_event_name(AI_EVENT_CANCEL)); + printf("3 - %s\n", ai_get_event_name(AI_EVENT_AUDIO_PLAYED)); + printf("4 - %s\n", ai_get_event_name(AI_EVENT_ASR_STARTED)); + printf("5 - %s\n", ai_get_event_name(AI_EVENT_ASR_RESULT)); + printf("6 - %s\n", ai_get_event_name(AI_EVENT_ASR_COMPLETED)); + printf("7 - %s\n", ai_get_event_name(AI_EVENT_LLM_CREATED)); + printf("8 - %s\n", ai_get_event_name(AI_EVENT_LLM_DELTA)); + printf("9 - %s\n", ai_get_event_name(AI_EVENT_LLM_COMPLETED)); + printf("10 - %s\n", ai_get_event_name(AI_EVENT_LLM_DONE)); + printf("11 - %s\n", ai_get_event_name(AI_EVENT_TTS_CREATED)); + printf("12 - %s\n", ai_get_event_name(AI_EVENT_TTS_STARTED)); + printf("13 - %s\n", ai_get_event_name(AI_EVENT_TTS_COMPLETED)); + printf("Usage: AT^SENDEVENT=\n"); + + return ERRCODE_SUCC; +} + + + +static const at_cmd_table_t g_ai_25_cmd[] = { + { (char *)"AT^STARTAI", Start, (char *)"1" }, + { (char *)"AT^SENDEVENT", SendEvent, (char *)"1" }, + { (char *)"AT^LISTEVENTS", ListEvents, (char *)"0" }, +}; + +at_cmd_table_t *get_ai_2_5_cmd_table(uint32_t *cmd_count) +{ + *cmd_count = ARRAY_COUNT(g_ai_25_cmd); + return (at_cmd_table_t *)g_ai_25_cmd; +} +#ifdef __cplusplus +#if __cplusplus +} +#endif /* __cplusplus */ +#endif /* __cplusplus */ \ No newline at end of file diff --git a/code/sdk/zhihui/ai_new/ai_V2.5/ai_V2_5_at_cmd.h b/code/sdk/zhihui/ai_new/ai_V2.5/ai_V2_5_at_cmd.h new file mode 100644 index 000000000..746f5063d --- /dev/null +++ b/code/sdk/zhihui/ai_new/ai_V2.5/ai_V2_5_at_cmd.h @@ -0,0 +1,32 @@ +/** + * Copyright (c) @CompanyNameMagicTag 2022-2022. All rights reserved. \n + * + * Description: Provides at cmd register func \n + * Author: @CompanyNameTag \n + * History: \n + * 2022-07-20, Create file. \n + */ +#ifndef AI_NEW_2_5_AT_CMD_H +#define AI_NEW_2_5_AT_CMD_H + +#include +#include +#include "errcode.h" +#include "soc_osal.h" +#include "at_cmd_api.h" + +#ifdef __cplusplus +#if __cplusplus +extern "C" { +#endif /* __cplusplus */ +#endif /* __cplusplus */ + +at_cmd_table_t *get_ai_2_5_cmd_table(uint32_t *cmd_count); + +#ifdef __cplusplus +#if __cplusplus +} +#endif /* __cplusplus */ +#endif /* __cplusplus */ + +#endif diff --git a/code/sdk/zhihui/ai_new/ai_V2.5/ai_v2_5_main.cpp b/code/sdk/zhihui/ai_new/ai_V2.5/ai_v2_5_main.cpp new file mode 100644 index 000000000..132131882 --- /dev/null +++ b/code/sdk/zhihui/ai_new/ai_V2.5/ai_v2_5_main.cpp @@ -0,0 +1,1011 @@ +/* ============================== AI V2.5 主程序 ============================== */ + +#include +#include +#include +#include +#include "../../../../../open_source/openharmony/third_party/cJSON/cJSON.h" +#include "haisi_websocket.h" +#include "ai_media.h" +#include "ai_V2_5_api.h" +#include "audio_base_type.h" + +#ifdef __cplusplus +#if __cplusplus +extern "C" { +#endif /* __cplusplus */ +#endif /* __cplusplus */ + +void ai_print_start_config(void); +char* ai_generate_start_config(const char* device_id, const char* bot_id, + const char* workflow_id, const char* voice_name); + +int ai_send_start_config(void); + +void ai_free_json_string(char* json_string); +/* ============================== 全局变量 ============================== */ + +static int g_ai_running = 0; // AI服务运行状态 + +/* ============================== WebSocket回调函数 ============================== */ + +// {"event":"started","sid":"37efa28576ae4afe8129efcf8677b82e"} + +// { +// "event": "audio.create", +// "payload": {} +// } + +uint8_t asr_is_start=0; + +/** + * @brief 文本消息处理回调函数 + * @param raw_data 原始消息数据 + * @param timestamp 消息时间戳 + */ +static void ai_message_handler(const char *raw_data, uint32_t timestamp) +{ + printf("[AI] Received text message at %u: %s\n", timestamp, raw_data); + + // 解析事件数据 + ai_event_data_t event_data; + ai_event_type_t event_type = ai_parse_event(raw_data, &event_data); + + if (event_type == AI_EVENT_MAX) { + printf("[AI] Failed to parse event or unknown event type\n"); + return; + } + + printf("[AI] Parsed event: %s, content: %s, dialog_id: %s, sid: %s\n", + ai_get_event_name(event_type), event_data.content, event_data.dialog_id, event_data.sid); + + // 根据事件类型进行相应处理 + switch (event_type) { + + case AI_EVENT_ASR_STARTED: + printf("[AI] Handling asr.started event\n"); + // TODO: 处理ASR开始事件 + asr_is_start=1; + break; + + case AI_EVENT_ASR_RESULT: + printf("[AI] Handling asr.result event, content: %s\n", event_data.content); + // TODO: 处理ASR结果事件 + asr_is_start=0; + break; + + case AI_EVENT_ASR_COMPLETED: + printf("[AI] Handling asr.completed event\n"); + // TODO: 处理ASR完成事件 + break; + + case AI_EVENT_LLM_CREATED: + printf("[AI] Handling llm.created event\n"); + // TODO: 处理LLM创建事件 + break; + + case AI_EVENT_LLM_DELTA: + printf("[AI] Handling llm.delta event, content: %s\n", event_data.content); + // TODO: 处理LLM增量事件 + break; + + case AI_EVENT_LLM_COMPLETED: + printf("[AI] Handling llm.completed event, content: %s\n", event_data.content); + // TODO: 处理LLM完成事件 + break; + + case AI_EVENT_LLM_DONE: + printf("[AI] Handling llm.done event\n"); + // TODO: 处理LLM结束事件 + break; + + case AI_EVENT_TTS_CREATED: + printf("[AI] Handling tts.created event\n"); + // TODO: 处理TTS创建事件 + break; + + case AI_EVENT_TTS_STARTED: + printf("[AI] Handling tts.started event\n"); + // TODO: 处理TTS开始事件 + break; + + case AI_EVENT_TTS_COMPLETED: + printf("[AI] Handling tts.completed event\n"); + // TODO: 处理TTS完成事件 + break; + + default: + printf("[AI] Unknown event type: %d\n", event_type); + break; + } +} + +/** + * @brief 二进制消息处理回调函数 + * @param raw_data 原始二进制数据 + * @param timestamp 消息时间戳 + * @param data_len 数据长度 + */ +static void ai_binary_message_handler(const uint8_t *raw_data, uint32_t timestamp, uint16_t data_len) +{ + printf("[AI] Received binary message at %u, length: %d bytes\n", timestamp, data_len); + + // TODO: 处理二进制数据(如音频数据) + // TODO: 根据数据长度和内容进行相应处理 + // TODO: 调用音频处理逻辑 + AI_SendAudioDataToQueue(raw_data,data_len); + + // 打印前几个字节用于调试 + // if (data_len > 0 && raw_data != NULL) { + // printf("[AI] Binary data preview (first 16 bytes): "); + // int preview_len = (data_len > 16) ? 16 : data_len; + // for (int i = 0; i < preview_len; i++) { + // printf("%02X ", raw_data[i]); + // } + // printf("\n"); + // } +} + +/** + * @brief URL构建回调函数 + * @param url URL输出缓冲区 + * @param url_size URL缓冲区大小 + * @param header_buf Header输出缓冲区 + * @param header_size Header缓冲区大小 + */ +static void ai_url_handler(char *url, size_t url_size, char *header_buf, size_t header_size) +{ + printf("[AI] Building WebSocket URL and headers\n"); + snprintf(url, url_size, "ws://118.145.158.207:22080/vc/chat"); + +} + +/** + * @brief 心跳处理回调函数 + * @return 1表示需要发送心跳,0表示不需要 + */ +static int ai_heartbeat_handler(void) +{ + printf("[AI] Heartbeat check\n"); + + // TODO: 实现心跳逻辑 + // TODO: 检查是否需要发送心跳 + // TODO: 发送心跳消息 + + return 0; // 暂时不发送心跳 +} + +/** + * @brief 连接事件回调函数 + * @param event_type 事件类型 + */ +static void ai_event_handler(ws_event_type_t event_type) +{ + switch (event_type) { + case WS_EVENT_CONNECTING: + printf("[AI] WebSocket connecting...\n"); + // TODO: 连接开始处理 + break; + + case WS_EVENT_CONNECTED: + printf("[AI] WebSocket connected successfully!\n"); + // TODO: 连接成功处理 + // TODO: 发送初始化消息 + ai_send_start_config(); + break; + + case WS_EVENT_DISCONNECTED: + printf("[AI] WebSocket disconnected\n"); + asr_is_start=0; + // TODO: 连接断开处理 + // TODO: 清理资源 + break; + } +} + +/* ============================== AI配置发送函数 ============================== */ + +/** + * @brief 发送AI启动配置JSON + * @return 0成功,-1失败 + */ + int ai_send_start_config(void) +{ + printf("[AI] Sending AI start configuration...\n"); + + // 生成AI启动配置JSON + char* config_json = ai_generate_start_config(NULL, NULL, NULL, NULL); + if (config_json == NULL) { + printf("[AI] Failed to generate start configuration JSON\n"); + return -1; + } + + printf("[AI] Generated configuration:\n%s\n", config_json); + + // 通过WebSocket发送配置 + int ret = uapi_ws_send_text(config_json); + if (ret == 0) { + printf("[AI] Start configuration sent successfully\n"); + } else { + printf("[AI] Failed to send start configuration, ret: %d\n", ret); + } + + // 释放JSON字符串内存 + ai_free_json_string(config_json); + + return ret; +} + +/** + * @brief 发送自定义AI启动配置JSON + * @param device_id 设备ID + * @param bot_id Coze机器人ID + * @param workflow_id Coze工作流ID + * @param voice_name 音色名称 + * @return 0成功,-1失败 + */ + int ai_send_custom_start_config(const char* device_id, const char* bot_id, + const char* workflow_id, const char* voice_name) +{ + printf("[AI] Sending custom AI start configuration...\n"); + + // 生成自定义AI启动配置JSON + char* config_json = ai_generate_start_config(device_id, bot_id, workflow_id, voice_name); + if (config_json == NULL) { + printf("[AI] Failed to generate custom start configuration JSON\n"); + return -1; + } + + printf("[AI] Generated custom configuration:\n%s\n", config_json); + + // 通过WebSocket发送配置 + int ret = uapi_ws_send_text(config_json); + if (ret == 0) { + printf("[AI] Custom start configuration sent successfully\n"); + } else { + printf("[AI] Failed to send custom start configuration, ret: %d\n", ret); + } + + // 释放JSON字符串内存 + ai_free_json_string(config_json); + + return ret; +} + +/* ============================== AI服务管理函数 ============================== */ + +/** + * @brief 初始化AI服务 + * @return 0成功,-1失败 + */ +static int ai_service_init(void) +{ + printf("[AI] Initializing AI service...\n"); + + // TODO: 初始化AI相关资源 + // TODO: 加载模型 + // TODO: 初始化其他组件 + + return 0; +} + +static void AIVoiceDataCallback_(const uint8_t* data, uint32_t size, void* userData) +{ + // MEDIA_INFO_LOG("Received audio data: size=%u bytes", size); + // 在这里处理音频数据,例如: + // - 发送到AI语音识别服务 + // - 保存到文件 + // - 进行音频处理等 + // 注意:data指针指向的缓冲区在回调函数返回后可能被释放,如需保存数据请复制 + if(asr_is_start) + { + uapi_ws_send_BIN((uint8_t*)data,size); + } + +} + + +/** + * @brief 启动AI服务 + * @return 0成功,-1失败 + */ +int ai_service_start(void) +{ + printf("[AI] Starting AI service...\n"); + + // 初始化AI服务 + if (ai_service_init() != 0) { + printf("[AI] Failed to initialize AI service\n"); + return -1; + } + + // 设置WebSocket回调 + ws_call_backs_t callbacks = { + .message_callback = ai_message_handler, + .binary_callback = ai_binary_message_handler, + .url_callback = ai_url_handler, + .heartbeat_callback = ai_heartbeat_handler, + .event_callback = ai_event_handler + }; + + // 启动WebSocket服务 + int ret = uapi_ws_start(&callbacks); + if (ret != 0) { + printf("[AI] Failed to start WebSocket service: %d\n", ret); + return -1; + } + + g_ai_running = 1; + printf("[AI] AI service started successfully\n"); + + // 创建与ai_generate_start_config一致的音频参数 + AIVoiceAudioParams audioParams = { + .audioFormat = PCM, + .channelCount = AI_DEFAULT_CHANNEL_COUNT, + .sampleRate = AI_DEFAULT_SAMPLE_RATE + }; + + printf("[AI] Starting AI voice with audio params: format=%d, channels=%u, sampleRate=%u\n", + audioParams.audioFormat, audioParams.channelCount, audioParams.sampleRate); + + old_Start_AIVoice(AIVoiceDataCallback_, nullptr, &audioParams); + + return 0; +} + +/* ============================== 测试函数 ============================== */ + +/** + * @brief 测试JSON配置生成功能 + */ +void ai_test_json_generation(void) +{ + printf("\n=== AI JSON配置生成测试 ===\n"); + + // 测试1: 使用默认参数 + printf("\n1. 使用默认参数生成配置:\n"); + ai_print_start_config(); + + // 测试2: 使用自定义参数 + printf("\n2. 使用自定义参数生成配置:\n"); + char* custom_config = ai_generate_start_config( + "DEVICE_001", + "CUSTOM_BOT_123", + "CUSTOM_WORKFLOW_456", + "zh_male_custom_voice" + ); + + if (custom_config) { + printf("[AI] Custom config:\n%s\n", custom_config); + ai_free_json_string(custom_config); + } else { + printf("[AI] Failed to generate custom config\n"); + } + + printf("=== 测试完成 ===\n\n"); +} + +/* ============================== AI事件发送函数 ============================== */ + +/** + * @brief 获取事件名称 + * @param event_type 事件类型 + * @return 事件名称字符串 + */ +const char* ai_get_event_name(ai_event_type_t event_type) +{ + switch (event_type) { + case AI_EVENT_AUDIO_CREATE: + return "audio.create"; + case AI_EVENT_AUDIO_COMPLETED: + return "audio.completed"; + case AI_EVENT_CANCEL: + return "cancel"; + case AI_EVENT_AUDIO_PLAYED: + return "audio.played"; + + // ASR事件 + case AI_EVENT_ASR_STARTED: + return "asr.started"; + case AI_EVENT_ASR_RESULT: + return "asr.result"; + case AI_EVENT_ASR_COMPLETED: + return "asr.completed"; + + // LLM事件 + case AI_EVENT_LLM_CREATED: + return "llm.created"; + case AI_EVENT_LLM_DELTA: + return "llm.delta"; + case AI_EVENT_LLM_COMPLETED: + return "llm.completed"; + case AI_EVENT_LLM_DONE: + return "llm.done"; + + // TTS事件 + case AI_EVENT_TTS_CREATED: + return "tts.created"; + case AI_EVENT_TTS_STARTED: + return "tts.started"; + case AI_EVENT_TTS_COMPLETED: + return "tts.completed"; + + default: + return "unknown"; + } +} + +/** + * @brief 发送AI事件 + * @param event_type 事件类型 + * @return 0成功,-1失败 + */ +int ai_send_event(ai_event_type_t event_type) +{ + if (event_type >= AI_EVENT_MAX) { + printf("[AI] Invalid event type: %d\n", event_type); + return -1; + } + + printf("[AI] Sending AI event: %s\n", ai_get_event_name(event_type)); + + // 生成事件JSON + cJSON *root = cJSON_CreateObject(); + if (root == NULL) { + printf("[AI] Failed to create event JSON object\n"); + return -1; + } + + // 设置event字段 + cJSON_AddStringToObject(root, "event", ai_get_event_name(event_type)); + + + + // 创建空的payload对象 + cJSON *payload = cJSON_CreateObject(); + if (payload == NULL) { + printf("[AI] Failed to create payload JSON object\n"); + cJSON_Delete(root); + return -1; + } + + cJSON_AddItemToObject(root, "payload", payload); + + // 生成JSON字符串 + char *json_string = cJSON_Print(root); + if (json_string == NULL) { + printf("[AI] Failed to print event JSON to string\n"); + cJSON_Delete(root); + return -1; + } + + printf("[AI] Generated event JSON:\n%s\n", json_string); + + // 通过WebSocket发送事件 + int ret = uapi_ws_send_text(json_string); + if (ret == 0) { + printf("[AI] Event '%s' sent successfully\n", ai_get_event_name(event_type)); + if(AI_EVENT_AUDIO_COMPLETED) + { + asr_is_start=0; + } + } else { + printf("[AI] Failed to send event '%s', ret: %d\n", ai_get_event_name(event_type), ret); + } + + // 清理资源 + free(json_string); + cJSON_Delete(root); + + return ret; +} + +/** + * @brief 解析AI事件JSON字符串 + * @param json_string JSON字符串 + * @param event_data 输出参数:事件数据结构体 + * @return 事件类型枚举,如果解析失败返回AI_EVENT_MAX + */ +ai_event_type_t ai_parse_event(const char* json_string, ai_event_data_t* event_data) +{ + if (json_string == NULL || event_data == NULL) { + printf("[AI] Invalid parameters for event parsing\n"); + return AI_EVENT_MAX; + } + + // 初始化输出参数 + event_data->event_type = AI_EVENT_MAX; + event_data->content[0] = '\0'; + event_data->dialog_id[0] = '\0'; + event_data->sid[0] = '\0'; + + printf("[AI] Parsing event JSON:\n%s\n", json_string); + + // 解析JSON + cJSON *root = cJSON_Parse(json_string); + if (root == NULL) { + printf("[AI] Failed to parse JSON string\n"); + return AI_EVENT_MAX; + } + + // 获取event字段 + cJSON *event_item = cJSON_GetObjectItem(root, "event"); + if (event_item == NULL || !cJSON_IsString(event_item)) { + printf("[AI] Missing or invalid 'event' field\n"); + cJSON_Delete(root); + return AI_EVENT_MAX; + } + + const char *event_name = event_item->valuestring; + printf("[AI] Parsed event: %s\n", event_name); + + // 获取payload字段 + cJSON *payload = cJSON_GetObjectItem(root, "payload"); + if (payload != NULL && cJSON_IsObject(payload)) { + // 获取content字段 + cJSON *content_item = cJSON_GetObjectItem(payload, "content"); + if (content_item != NULL && cJSON_IsString(content_item)) { + strncpy(event_data->content, content_item->valuestring, sizeof(event_data->content) - 1); + event_data->content[sizeof(event_data->content) - 1] = '\0'; + printf("[AI] Parsed content: %s\n", event_data->content); + } + + // 获取dialog_id字段 + cJSON *dialog_id_item = cJSON_GetObjectItem(payload, "dialog_id"); + if (dialog_id_item != NULL && cJSON_IsString(dialog_id_item)) { + strncpy(event_data->dialog_id, dialog_id_item->valuestring, sizeof(event_data->dialog_id) - 1); + event_data->dialog_id[sizeof(event_data->dialog_id) - 1] = '\0'; + printf("[AI] Parsed dialog_id: %s\n", event_data->dialog_id); + } + } + + // 获取sid字段 + cJSON *sid_item = cJSON_GetObjectItem(root, "sid"); + if (sid_item != NULL && cJSON_IsString(sid_item)) { + strncpy(event_data->sid, sid_item->valuestring, sizeof(event_data->sid) - 1); + event_data->sid[sizeof(event_data->sid) - 1] = '\0'; + printf("[AI] Parsed sid: %s\n", event_data->sid); + } + + // 根据事件名称返回对应的枚举类型 + ai_event_type_t event_type = AI_EVENT_MAX; + + if (strcmp(event_name, "audio.create") == 0) { + event_type = AI_EVENT_AUDIO_CREATE; + } else if (strcmp(event_name, "audio.completed") == 0) { + event_type = AI_EVENT_AUDIO_COMPLETED; + } else if (strcmp(event_name, "cancel") == 0) { + event_type = AI_EVENT_CANCEL; + } else if (strcmp(event_name, "audio.played") == 0) { + event_type = AI_EVENT_AUDIO_PLAYED; + } else if (strcmp(event_name, "asr.started") == 0) { + event_type = AI_EVENT_ASR_STARTED; + } else if (strcmp(event_name, "asr.result") == 0) { + event_type = AI_EVENT_ASR_RESULT; + } else if (strcmp(event_name, "asr.completed") == 0) { + event_type = AI_EVENT_ASR_COMPLETED; + } else if (strcmp(event_name, "llm.created") == 0) { + event_type = AI_EVENT_LLM_CREATED; + } else if (strcmp(event_name, "llm.delta") == 0) { + event_type = AI_EVENT_LLM_DELTA; + } else if (strcmp(event_name, "llm.completed") == 0) { + event_type = AI_EVENT_LLM_COMPLETED; + } else if (strcmp(event_name, "llm.done") == 0) { + event_type = AI_EVENT_LLM_DONE; + } else if (strcmp(event_name, "tts.created") == 0) { + event_type = AI_EVENT_TTS_CREATED; + } else if (strcmp(event_name, "tts.started") == 0) { + event_type = AI_EVENT_TTS_STARTED; + } else if (strcmp(event_name, "tts.completed") == 0) { + event_type = AI_EVENT_TTS_COMPLETED; + } + else if (strcmp(event_name, "completed") == 0) { + event_type = AI_EVENT_ASR_COMPLETED; + } + else { + printf("[AI] Unknown event type: %s\n", event_name); + } + + // 设置事件类型到结构体中 + event_data->event_type = event_type; + + cJSON_Delete(root); + + if (event_type != AI_EVENT_MAX) { + printf("[AI] Successfully parsed event: %s (type=%d)\n", event_name, event_type); + } + + return event_type; +} + +/* ============================== JSON配置生成函数 ============================== */ + +/** + * @brief 生成AI服务启动配置JSON + * @param device_id 设备ID + * @param bot_id Coze机器人ID + * @param workflow_id Coze工作流ID + * @param voice_name 音色名称 + * @return 生成的JSON字符串,需要调用者释放内存 + */ +char* ai_generate_start_config(const char* device_id, const char* bot_id, + const char* workflow_id, const char* voice_name) +{ + cJSON *root = NULL; + cJSON *payload = NULL; + cJSON *dialog_attributes = NULL; + cJSON *dialog_cfg = NULL; + cJSON *custom_variables = NULL; + cJSON *greeting = NULL; + cJSON *bot_option = NULL; + cJSON *outbound_attributes = NULL; + cJSON *outbound_cfg = NULL; + cJSON *inbound_attributes = NULL; + cJSON *interrupt_options = NULL; + cJSON *inbound_cfg = NULL; + cJSON *keywords_array = NULL; + char *json_string = NULL; + + // 创建根对象 + root = cJSON_CreateObject(); + if (root == NULL) { + printf("[AI] Failed to create root JSON object\n"); + goto error; + } + + // 设置event字段 + cJSON_AddStringToObject(root, "event", "start"); + + // 创建payload对象 + payload = cJSON_CreateObject(); + if (payload == NULL) { + printf("[AI] Failed to create payload JSON object\n"); + goto error; + } + + // 设置payload基本字段 + cJSON_AddStringToObject(payload, "device_id", device_id ? device_id : "123123123"); + cJSON_AddStringToObject(payload, "user_agent", "AudioClient/1.0"); + cJSON_AddBoolToObject(payload, "simple_response", cJSON_True); + + // 创建dialog_attributes对象 + dialog_attributes = cJSON_CreateObject(); + if (dialog_attributes == NULL) { + printf("[AI] Failed to create dialog_attributes JSON object\n"); + goto error; + } + + cJSON_AddStringToObject(dialog_attributes, "provider", "coze"); + + // 创建dialog_cfg对象 + dialog_cfg = cJSON_CreateObject(); + if (dialog_cfg == NULL) { + printf("[AI] Failed to create dialog_cfg JSON object\n"); + goto error; + } + + cJSON_AddBoolToObject(dialog_cfg, "disable_empty_prompt", cJSON_True); + + // 创建custom_variables对象 + custom_variables = cJSON_CreateObject(); + if (custom_variables == NULL) { + printf("[AI] Failed to create custom_variables JSON object\n"); + goto error; + } + cJSON_AddStringToObject(custom_variables, "name", "小瑞"); + cJSON_AddItemToObject(dialog_cfg, "custom_variables", custom_variables); + + // 创建greeting对象 + greeting = cJSON_CreateObject(); + if (greeting == NULL) { + printf("[AI] Failed to create greeting JSON object\n"); + goto error; + } + cJSON_AddBoolToObject(greeting, "enabled", cJSON_True); + cJSON_AddStringToObject(greeting, "content", ""); + cJSON_AddItemToObject(dialog_cfg, "greeting", greeting); + + // 创建bot_option对象 + bot_option = cJSON_CreateObject(); + if (bot_option == NULL) { + printf("[AI] Failed to create bot_option JSON object\n"); + goto error; + } + cJSON_AddBoolToObject(bot_option, "use_workflow", cJSON_True); + cJSON_AddStringToObject(bot_option, "bot_id", bot_id ? bot_id : "7462617881076351002"); + cJSON_AddStringToObject(bot_option, "workflow_id", workflow_id ? workflow_id : "7462621757327196187"); + cJSON_AddItemToObject(dialog_cfg, "bot_option", bot_option); + + cJSON_AddItemToObject(dialog_attributes, "cfg", dialog_cfg); + cJSON_AddItemToObject(payload, "dialog_attributes", dialog_attributes); + + // 创建outbound_attributes对象 + outbound_attributes = cJSON_CreateObject(); + if (outbound_attributes == NULL) { + printf("[AI] Failed to create outbound_attributes JSON object\n"); + goto error; + } + + cJSON_AddStringToObject(outbound_attributes, "provider", "volcengine"); + + // 创建outbound_cfg对象 + outbound_cfg = cJSON_CreateObject(); + if (outbound_cfg == NULL) { + printf("[AI] Failed to create outbound_cfg JSON object\n"); + goto error; + } + + cJSON_AddStringToObject(outbound_cfg, "format", "pcm"); + cJSON_AddNumberToObject(outbound_cfg, "sample_rate", AI_DEFAULT_SAMPLE_RATE); + cJSON_AddNumberToObject(outbound_cfg, "bit_rate", AI_DEFAULT_BIT_RATE); + cJSON_AddNumberToObject(outbound_cfg, "channel", AI_DEFAULT_CHANNEL_COUNT); + cJSON_AddNumberToObject(outbound_cfg, "volume", 100); + cJSON_AddNumberToObject(outbound_cfg, "speech_rate", 0); + cJSON_AddNumberToObject(outbound_cfg, "chunk_interval_ms", 100); + cJSON_AddStringToObject(outbound_cfg, "voice", voice_name ? voice_name : "zh_female_linjianvhai_moon_bigtts"); + cJSON_AddStringToObject(outbound_cfg, "voice_source", "system"); + + cJSON_AddItemToObject(outbound_attributes, "cfg", outbound_cfg); + cJSON_AddItemToObject(payload, "outbound_attributes", outbound_attributes); + + // 创建inbound_attributes对象 + inbound_attributes = cJSON_CreateObject(); + if (inbound_attributes == NULL) { + printf("[AI] Failed to create inbound_attributes JSON object\n"); + goto error; + } + + cJSON_AddStringToObject(inbound_attributes, "provider", "volcengine"); + cJSON_AddBoolToObject(inbound_attributes, "disable_vad", cJSON_False); + cJSON_AddNumberToObject(inbound_attributes, "vad_silence", 400); + + // 创建interrupt_options对象 + interrupt_options = cJSON_CreateObject(); + if (interrupt_options == NULL) { + printf("[AI] Failed to create interrupt_options JSON object\n"); + goto error; + } + cJSON_AddBoolToObject(interrupt_options, "concatenate_results", cJSON_True); + cJSON_AddBoolToObject(interrupt_options, "thinking_time_only", cJSON_True); + cJSON_AddItemToObject(inbound_attributes, "interrupt_options", interrupt_options); + + // 创建inbound_cfg对象 + inbound_cfg = cJSON_CreateObject(); + if (inbound_cfg == NULL) { + printf("[AI] Failed to create inbound_cfg JSON object\n"); + goto error; + } + + cJSON_AddStringToObject(inbound_cfg, "format", "pcm"); + cJSON_AddNumberToObject(inbound_cfg, "sample_rate", AI_DEFAULT_SAMPLE_RATE); + cJSON_AddNumberToObject(inbound_cfg, "bit_rate", AI_DEFAULT_BIT_RATE); + cJSON_AddNumberToObject(inbound_cfg, "channel", AI_DEFAULT_CHANNEL_COUNT); + cJSON_AddBoolToObject(inbound_cfg, "enable_punctuation", cJSON_True); + cJSON_AddBoolToObject(inbound_cfg, "enable_itn", cJSON_True); + + // 创建keywords数组 + keywords_array = cJSON_CreateArray(); + if (keywords_array == NULL) { + printf("[AI] Failed to create keywords array\n"); + goto error; + } + cJSON_AddItemToArray(keywords_array, cJSON_CreateString("张三")); + cJSON_AddItemToObject(inbound_cfg, "keywords", keywords_array); + + cJSON_AddItemToObject(inbound_attributes, "cfg", inbound_cfg); + cJSON_AddItemToObject(payload, "inbound_attributes", inbound_attributes); + + // 将payload添加到根对象 + cJSON_AddItemToObject(root, "payload", payload); + + // 生成JSON字符串 + json_string = cJSON_Print(root); + if (json_string == NULL) { + printf("[AI] Failed to print JSON to string\n"); + goto error; + } + + // 清理资源 + cJSON_Delete(root); + return json_string; + +error: + if (root) { + cJSON_Delete(root); + } + return NULL; +} + +/** + * @brief 释放JSON字符串内存 + * @param json_string 需要释放的JSON字符串 + */ +void ai_free_json_string(char* json_string) +{ + if (json_string) { + free(json_string); + } +} + +/** + * @brief 打印AI配置JSON(用于测试) + */ +void ai_print_start_config(void) +{ + char* config_json = ai_generate_start_config(NULL, NULL, NULL, NULL); + if (config_json) { + printf("[AI] Generated start config:\n%s\n", config_json); + ai_free_json_string(config_json); + } else { + printf("[AI] Failed to generate start config\n"); + } +} + +// { +// "event": "start", +// "payload": { +// "device_id": "123123123", // 设备ID +// "user_agent": "AudioClient/1.0", // 浏览器 UA +// "simple_response": true, // true 只返回完整的LLM文本 默认false 返回增量LLM文本 +// "dialog_attributes": { // 智能体配置 +// "provider": "coze", // 切换服务驱动 +// "cfg": { +// "disable_empty_prompt": true, // 拦截 asr 识别出来的空文本发送到大模型 +// "custom_variables": { // coze 自定义参数 +// "name": "小瑞" +// }, +// "greeting": { // 问候语 配置 +// "enabled": true, // 是否开启问候语 +// "content": "" // 问候语内容 coze 不填写默认使用 coze 平台设置的 +// }, +// "bot_option": { +// "use_workflow": true, // 使用对话流方式 coze 推荐使用对话流 +// "bot_id":"7462617881076351002", // 使用对话流 bot_id 需要和 workflow_id处于同一个工作空间即可 +// "workflow_id":"7462621757327196187", // +// } +// } +// }, +// "outbound_attributes": { // TTS输出音频配置 +// "provider": "volcengine", // TTS服务驱动 +// "cfg": { +// "format": "pcm", // 音频输出格式 +// "sample_rate": AI_DEFAULT_SAMPLE_RATE, // 音频输出采样率 +// "bit_rate": AI_DEFAULT_BIT_RATE, // 比特率 +// "channel": AI_DEFAULT_CHANNEL_COUNT, // 声道 1 单声道 +// "volume": 100, // 音量 0-100 +// "speech_rate": 0, // 语速 [-50,100] 默认 0 +// "chunk_interval_ms": 100, // 配置每段音频入队间隔时间 100ms +// "voice": "zh_female_linjianvhai_moon_bigtts" // 音色 +// "voice_source": "system", // 音色来源。目前定义 private 复刻音色。 system 使用系统默认音色,不提交这个参数兼容 system +// } +// }, +// "inbound_attributes": { // ASR输入音频配置 +// "provider": "volcengine", // ASR服务驱动 +// "disable_vad": false, // 开启vad 自动断句 (开启后可以有客户端处理回音消除后持续发送音频由服务器判断是否转写和发送LLM) +// "vad_silence": 400, // 设置vad 静音阈值 (毫秒) 连续静音超过 阈值自动切换状态 +// "interrupt_options": { +// "concatenate_results": true, // 是否在打断后拼接前后的ASR结果 +// "thinking_time_only":true // 是否只在思考时间允许打断 思考时间(ASR结束--->第一帧音频合成) +// // "keyword_interrupt": false, // 启用打断词功能 (打断词只有在思考以及之后才能打断) +// // "interrupt_keywords": ["停止", "打断","打断一下", "等一下", "别说了", "打住"] // 配置打断词列表 +// }, +// "cfg": { +// "format": "pcm", // 音频输入格式 +// "sample_rate": AI_DEFAULT_SAMPLE_RATE, // 音频输入采样率 目前只支持 16000 +// "bit_rate": AI_DEFAULT_BIT_RATE, // 比特率 目前只支持 16bit +// "channel": AI_DEFAULT_CHANNEL_COUNT // 声道 目前只支持单声道 +// "enable_punctuation": true, // 开启标点 +// "enable_itn":true, // 开启itn +// "keywords": ["张三"] // 热词 +// } +// }, +// } + +// { +// "event": "audio.create", +// "payload": {} +// } +// { +// "event": "audio.completed", +// "payload": {} +// } +// { +// "event": "cancel", +// "payload": {} +// } +// { +// "event": "audio.played", +// "payload": {} +// } + +// { +// "event": "asr.started", +// "payload": { +// "dialog_id": "f56fd6081f6e4f40bf325fd625f214ea_1744162450468050009" +// }, +// "sid": "f56fd6081f6e4f40bf325fd625f214ea" +// } + +// { +// "event": "asr.result", +// "payload": { +// "content": "你好。", +// "dialog_id": "f56fd6081f6e4f40bf325fd625f214ea_1744162450468050009" +// }, +// "sid": "f56fd6081f6e4f40bf325fd625f214ea" +// } + +// { +// "event": "asr.completed", +// "payload": { +// "dialog_id": "f56fd6081f6e4f40bf325fd625f214ea_1744162450468050009" +// }, +// "sid": "f56fd6081f6e4f40bf325fd625f214ea" +// } + + +// { +// "event": "llm.created", +// "payload": { +// "dialog_id": "f56fd6081f6e4f40bf325fd625f214ea_1744162450468050009" +// }, +// "sid": "f56fd6081f6e4f40bf325fd625f214ea" +// } + +// { +// "event": "llm.delta", +// "payload": { +// "content": "哈喽", +// "dialog_id": "f56fd6081f6e4f40bf325fd625f214ea_1744162450468050009" +// }, +// "sid": "f56fd6081f6e4f40bf325fd625f214ea" +// } + +// { +// "event": "llm.completed", +// "payload": { +// "content": "哈喽呀小a!今天过得咋样?", +// "dialog_id": "f56fd6081f6e4f40bf325fd625f214ea_1744162450468050009" +// }, +// "sid": "f56fd6081f6e4f40bf325fd625f214ea" +// } + +// { +// "event": "llm.done", +// "payload": { +// "dialog_id": "f56fd6081f6e4f40bf325fd625f214ea_1744162450468050009" +// }, +// "sid": "f56fd6081f6e4f40bf325fd625f214ea" +// } + +// { +// "event": "tts.created", +// "payload": { +// "dialog_id": "f56fd6081f6e4f40bf325fd625f214ea_1744162450468050009" +// }, +// "sid": "f56fd6081f6e4f40bf325fd625f214ea" +// } + + +// { +// "event": "tts.started", +// "payload": { +// "dialog_id": "f56fd6081f6e4f40bf325fd625f214ea_1744162450468050009" +// }, +// "sid": "f56fd6081f6e4f40bf325fd625f214ea" +// } + +// { +// "event": "tts.completed", +// "payload": { +// "dialog_id": "f56fd6081f6e4f40bf325fd625f214ea_1744162450468050009" +// }, +// "sid": "f56fd6081f6e4f40bf325fd625f214ea" +// } + +#ifdef __cplusplus +#if __cplusplus +} +#endif /* __cplusplus */ +#endif /* __cplusplus */ + diff --git a/code/sdk/zhihui/ai_old/zhihui_ws.cpp b/code/sdk/zhihui/ai_old/zhihui_ws.cpp index d741fc1cb..6e4c7f66c 100644 --- a/code/sdk/zhihui/ai_old/zhihui_ws.cpp +++ b/code/sdk/zhihui/ai_old/zhihui_ws.cpp @@ -132,16 +132,280 @@ void bk_sleep(uint32_t time) { msleep(time); } + +// DNS 解析相关结构 +typedef struct { + char hostname[256]; + struct in_addr ip_addr; + int resolved; + int error; +} dns_result_t; + +// 异步 DNS 解析回调函数 +static void dns_callback(const char *name, const struct ip_addr *ipaddr, void *callback_arg) { + dns_result_t *result = (dns_result_t *)callback_arg; + // if (ipaddr != NULL) { + // result->ip_addr = *ipaddr; + // result->resolved = 1; + // printf("DNS callback: %s resolved to %s\n", name, inet_ntoa(result->ip_addr)); + // } else { + // result->error = 1; + // printf("DNS callback: Failed to resolve %s\n", name); + // } +} + +// 异步 DNS 解析函数 +static int dns_resolve_async(const char *hostname, struct in_addr *ip_addr, uint32_t timeout_ms) { + dns_result_t result; + memset(&result, 0, sizeof(result)); + strncpy(result.hostname, hostname, sizeof(result.hostname) - 1); + + printf("Starting async DNS resolution for: %s\n", hostname); + + // 启动异步 DNS 解析 + err_t ret = 0;//dns_gethostbyname(hostname, &result.ip_addr, dns_callback, &result); + + if (ret == ERR_OK) { + // 立即解析成功 + *ip_addr = result.ip_addr; + printf("DNS resolved immediately: %s -> %s\n", hostname, inet_ntoa(*ip_addr)); + return 0; + } else if (ret == ERR_INPROGRESS) { + // 异步解析中,等待结果 + printf("DNS resolution in progress, waiting...\n"); + + uint32_t start_time = (uint32_t)osKernelGetTickCount(); + while (!result.resolved && !result.error) { + uint32_t current_time = (uint32_t)osKernelGetTickCount(); + if ((current_time - start_time) >= timeout_ms) { + printf("DNS resolution timeout after %u ms\n", timeout_ms); + return -1; + } + osDelay(10); // 等待 10ms + } + + if (result.resolved) { + *ip_addr = result.ip_addr; + printf("DNS resolved: %s -> %s\n", hostname, inet_ntoa(*ip_addr)); + return 0; + } else { + printf("DNS resolution failed for: %s\n", hostname); + return -1; + } + } else { + printf("DNS resolution error: %d\n", ret); + return -1; + } +} + +// SSL/TLS 支持函数实现 +#ifdef CONFIG_CRYPTO_MBEDTLS +int ws_ssl_init(WebSocketClient *client) { + if (!client || !client->use_ssl) { + return 0; // 不需要SSL + } + + WS_LOG_INFO("Initializing SSL/TLS context"); + + // 分配SSL上下文 + client->ssl_context = malloc(sizeof(mbedtls_ssl_context)); + client->ssl_config = malloc(sizeof(mbedtls_ssl_config)); + client->ctr_drbg = malloc(sizeof(mbedtls_ctr_drbg_context)); + client->entropy = malloc(sizeof(mbedtls_entropy_context)); + client->cacert = malloc(sizeof(mbedtls_x509_crt)); + + if (!client->ssl_context || !client->ssl_config || + !client->ctr_drbg || !client->entropy || !client->cacert) { + WS_LOG_ERROR("Failed to allocate SSL context memory"); + ws_ssl_cleanup(client); + return -1; + } + + // 初始化SSL上下文 + mbedtls_ssl_init((mbedtls_ssl_context*)client->ssl_context); + mbedtls_ssl_config_init((mbedtls_ssl_config*)client->ssl_config); + mbedtls_ctr_drbg_init((mbedtls_ctr_drbg_context*)client->ctr_drbg); + mbedtls_entropy_init((mbedtls_entropy_context*)client->entropy); + mbedtls_x509_crt_init((mbedtls_x509_crt*)client->cacert); + + // 设置随机数生成器 + const char *pers = "websocket_client"; + int ret = mbedtls_ctr_drbg_seed((mbedtls_ctr_drbg_context*)client->ctr_drbg, + mbedtls_entropy_func, + (mbedtls_entropy_context*)client->entropy, + (const unsigned char *) pers, strlen(pers)); + if (ret != 0) { + WS_LOG_ERROR("mbedtls_ctr_drbg_seed failed: %d", ret); + ws_ssl_cleanup(client); + return -1; + } + + // 配置SSL + ret = mbedtls_ssl_config_defaults((mbedtls_ssl_config*)client->ssl_config, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT); + if (ret != 0) { + WS_LOG_ERROR("mbedtls_ssl_config_defaults failed: %d", ret); + ws_ssl_cleanup(client); + return -1; + } + + // 设置RNG + mbedtls_ssl_conf_rng((mbedtls_ssl_config*)client->ssl_config, + mbedtls_ctr_drbg_random, + (mbedtls_ctr_drbg_context*)client->ctr_drbg); + + // 设置验证模式(可选:跳过证书验证用于测试) + mbedtls_ssl_conf_authmode((mbedtls_ssl_config*)client->ssl_config, MBEDTLS_SSL_VERIFY_NONE); + + // 设置SSL上下文 + ret = mbedtls_ssl_setup((mbedtls_ssl_context*)client->ssl_context, + (mbedtls_ssl_config*)client->ssl_config); + if (ret != 0) { + WS_LOG_ERROR("mbedtls_ssl_setup failed: %d", ret); + ws_ssl_cleanup(client); + return -1; + } + + // 设置主机名 + ret = mbedtls_ssl_set_hostname((mbedtls_ssl_context*)client->ssl_context, client->host); + if (ret != 0) { + WS_LOG_ERROR("mbedtls_ssl_set_hostname failed: %d", ret); + ws_ssl_cleanup(client); + return -1; + } + + WS_LOG_INFO("SSL/TLS context initialized successfully"); + return 0; +} + +void ws_ssl_cleanup(WebSocketClient *client) { + if (!client) return; + + if (client->ssl_context) { + mbedtls_ssl_free((mbedtls_ssl_context*)client->ssl_context); + free(client->ssl_context); + client->ssl_context = NULL; + } + + if (client->ssl_config) { + mbedtls_ssl_config_free((mbedtls_ssl_config*)client->ssl_config); + free(client->ssl_config); + client->ssl_config = NULL; + } + + if (client->ctr_drbg) { + mbedtls_ctr_drbg_free((mbedtls_ctr_drbg_context*)client->ctr_drbg); + free(client->ctr_drbg); + client->ctr_drbg = NULL; + } + + if (client->entropy) { + mbedtls_entropy_free((mbedtls_entropy_context*)client->entropy); + free(client->entropy); + client->entropy = NULL; + } + + if (client->cacert) { + mbedtls_x509_crt_free((mbedtls_x509_crt*)client->cacert); + free(client->cacert); + client->cacert = NULL; + } +} + +int ws_ssl_handshake(WebSocketClient *client) { + if (!client || !client->use_ssl || !client->ssl_context) { + return 0; // 不需要SSL握手 + } + + WS_LOG_INFO("Starting SSL/TLS handshake"); + + // 设置socket文件描述符 + mbedtls_ssl_set_bio((mbedtls_ssl_context*)client->ssl_context, + &client->sockfd, + mbedtls_net_send, + mbedtls_net_recv, + NULL); + uint32_t ret=0; + + // 执行SSL握手 + while ((ret = mbedtls_ssl_handshake((mbedtls_ssl_context*)client->ssl_context)) != 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + WS_LOG_ERROR("SSL handshake failed: %x", ret); + return -1; + } + } + + WS_LOG_INFO("SSL/TLS handshake completed successfully"); + return 0; +} + +int ws_ssl_send(WebSocketClient *client, const uint8_t *data, size_t len) { + if (!client || !client->use_ssl || !client->ssl_context) { + // 使用普通socket发送 + return lwip_send(client->sockfd, data, len, 0); + } + + int ret = mbedtls_ssl_write((mbedtls_ssl_context*)client->ssl_context, data, len); + if (ret < 0) { + WS_LOG_ERROR("SSL send failed: %d", ret); + return -1; + } + + return ret; +} + +int ws_ssl_recv(WebSocketClient *client, uint8_t *data, size_t len) { + if (!client || !client->use_ssl || !client->ssl_context) { + // 使用普通socket接收 + return lwip_recv(client->sockfd, data, len, 0); + } + + int ret = mbedtls_ssl_read((mbedtls_ssl_context*)client->ssl_context, data, len); + if (ret < 0) { + if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) { + return 0; // 需要更多数据 + } + WS_LOG_ERROR("SSL recv failed: %d", ret); + return -1; + } + + return ret; +} +#else +// 当没有mbedTLS支持时的空实现 +int ws_ssl_init(WebSocketClient *client) { + WS_LOG_WARNING("SSL/TLS not supported - mbedTLS not available"); + return -1; +} + +void ws_ssl_cleanup(WebSocketClient *client) { + (void)client; +} + +int ws_ssl_handshake(WebSocketClient *client) { + (void)client; + return -1; +} + +int ws_ssl_send(WebSocketClient *client, const uint8_t *data, size_t len) { + if (!client) return -1; + return lwip_send(client->sockfd, data, len, 0); +} + +int ws_ssl_recv(WebSocketClient *client, uint8_t *data, size_t len) { + if (!client) return -1; + return lwip_recv(client->sockfd, data, len, 0); +} +#endif #include #include #include -void * aic_zalloc(unsigned int len) -{ - return malloc(len); -} +// malloc 函数已移除,直接使用 malloc @@ -162,7 +426,7 @@ char* bk_strdup(const char* str) { return NULL; } size_t len = strlen(str) + 1; - char* new_str = (char*)aic_zalloc(len); + char* new_str = (char*)malloc(len); if (new_str == NULL) { return NULL; } @@ -406,11 +670,11 @@ int send_handshake(WebSocketClient *client,const char *headers,char *response,in generate_accept_key(key, accept_key); WS_LOG_INFO("send_handshake2"); - char *request =( char *)aic_zalloc(BUFFER_SIZE); + char *request =( char *)malloc(BUFFER_SIZE); if(request==NULL) { - WS_LOG_ERROR("request aic_zalloc error"); + WS_LOG_ERROR("request malloc error"); return -1; } @@ -449,13 +713,13 @@ int send_handshake(WebSocketClient *client,const char *headers,char *response,in WS_LOG_INFO("发送WebSocket握手请求..."); // 发送握手请求 - int send_ret = lwip_send(client->sockfd, request, strlen(request), 0); + int send_ret = ws_ssl_send(client, (const uint8_t*)request, strlen(request)); if (send_ret < 0) { WS_LOG_ERROR("发送握手请求失败: %d", send_ret); ret = -1; } else { // 接收响应 - ret = lwip_recv(client->sockfd, response, response_len - 1, 0); + ret = ws_ssl_recv(client, (uint8_t*)response, response_len - 1); if (ret > 0) { response[ret] = '\0'; // 确保字符串结束 } @@ -500,10 +764,10 @@ int send_handshake(WebSocketClient *client,const char *headers) { int ret = 0; generate_accept_key(key, accept_key); printf("send_handshake2"); - char *request =( char *)aic_zalloc(BUFFER_SIZE); + char *request =( char *)malloc(BUFFER_SIZE); if(request==NULL) { - printf("request aic_zalloc erro"); + printf("request malloc error"); return -1; } @@ -568,12 +832,12 @@ int send_websocket_message(WebSocketClient *client, uint8_t *message, size_t mes WS_LOG_ERROR("send_websocket_message: WebSocket handshake not completed"); return -1; } - uint8_t* frame=( uint8_t *)aic_zalloc(message_len + 50); + uint8_t* frame=( uint8_t *)malloc(message_len + 50); int offset = 0; int ret = -1; if(!frame) { - WS_LOG_ERROR("send_websocket_message zalloc error"); + WS_LOG_ERROR("send_websocket_message malloc error"); return -1; } // 第一个字节:FIN = 1, 设置 opcode @@ -624,7 +888,7 @@ int send_websocket_message(WebSocketClient *client, uint8_t *message, size_t mes - ret_tcp_send_data = lwip_send(client->sockfd, frame, offset, 0); + ret_tcp_send_data = ws_ssl_send(client, frame, offset); if (ret_tcp_send_data < 0) { WS_LOG_ERROR("send message failed %d", ret_tcp_send_data); ret = -1; @@ -681,6 +945,7 @@ WebSocketClient *init_websocket_client(const char *url, const char *headers,uint int ret=0; parsed_url = parse_url(url);//解析url + bool use_ssl =false; if (parsed_url.error) { printf("init_websocket_client Error: %s", parsed_url.error_message); @@ -691,14 +956,23 @@ WebSocketClient *init_websocket_client(const char *url, const char *headers,uint printf("Port: %d\n", parsed_url.port); printf("Path: %s\n", parsed_url.path); printf("Query: %s\n", parsed_url.query); - + + // 检查是否使用SSL/TLS + use_ssl = false; + if (strcmp(parsed_url.protocol, "wss") == 0 || strcmp(parsed_url.protocol, "https") == 0) { + use_ssl = true; + printf("Using SSL/TLS for secure WebSocket connection\n"); + } } - client = (WebSocketClient *)aic_zalloc(sizeof(WebSocketClient)) ; + client = (WebSocketClient *)malloc(sizeof(WebSocketClient)) ; if(!client) { - printf("init_websocket_client alloc error"); + printf("init_websocket_client malloc error"); goto client_ERROR; } + + // 设置SSL标志 + client->use_ssl = use_ssl; @@ -713,27 +987,50 @@ WebSocketClient *init_websocket_client(const char *url, const char *headers,uint lwip_setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (const void*)&opt, sizeof(opt)); lwip_setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,(const void*)&opt, sizeof(opt)); // printf("init_websocket_client 3\n"); - if(use_dns) - { - // ip_sem.dns_done_sem= osSemaphoreNew(1, 0, NULL); - // if(ip_sem.dns_done_sem==0) - // { - // goto client_ERROR; - // } - - // ret =dns_gethostbyname(parsed_url.host,&ip,dns_found,&ip_sem); - // // if(ERR_OK==ret) - // { - // osSemaphoreAcquire(ip_sem.dns_done_sem, 1000*30); - // } - // osSemaphoreDelete(ip_sem.dns_done_sem); - // printf("dns_gethostbyname %d\r\n",ip_sem.ip.u_addr.ip4); - // memcpy(&server_addr.sin_addr.s_addr, &ip_sem.ip.u_addr.ip4, 4); - - } - else - { - inet_pton(AF_INET, parsed_url.host, &server_addr.sin_addr); + // DNS 解析或直接使用 IP + if (use_dns || !inet_aton(parsed_url.host, &server_addr.sin_addr)) { + printf("Resolving DNS for host: %s\n", parsed_url.host); + + // 首先尝试异步 DNS 解析(推荐方式) + struct in_addr resolved_ip; + if (0) + { + //dns_resolve_async(parsed_url.host, &resolved_ip, 5000) == 0) { + // 异步解析成功 + server_addr.sin_addr = resolved_ip; + printf("Async DNS resolved %s to %s\n", parsed_url.host, inet_ntoa(server_addr.sin_addr)); + } else { + // 异步解析失败,尝试同步解析作为后备 + printf("Async DNS failed, trying sync DNS...\n"); + struct hostent *host_entry = lwip_gethostbyname(parsed_url.host); + if (host_entry == NULL) { + printf("init_websocket_client Error: Both async and sync DNS resolution failed for %s\n", parsed_url.host); + printf("DNS error: %s\n", strerror(errno)); + goto client_ERROR; + } + + // 检查解析结果 + if (host_entry->h_addr_list[0] == NULL) { + printf("init_websocket_client Error: No address found for %s\n", parsed_url.host); + goto client_ERROR; + } + + // 复制解析到的 IP 地址 + memcpy(&server_addr.sin_addr, host_entry->h_addr_list[0], host_entry->h_length); + printf("Sync DNS resolved %s to %s\n", parsed_url.host, inet_ntoa(server_addr.sin_addr)); + + // 打印所有解析到的地址(如果有多个) + if (host_entry->h_addr_list[1] != NULL) { + printf("Additional addresses found:\n"); + for (int i = 1; host_entry->h_addr_list[i] != NULL; i++) { + struct in_addr addr; + memcpy(&addr, host_entry->h_addr_list[i], host_entry->h_length); + printf(" %s\n", inet_ntoa(addr)); + } + } + } + } else { + printf("Using direct IP address: %s\n", parsed_url.host); } @@ -754,6 +1051,24 @@ WebSocketClient *init_websocket_client(const char *url, const char *headers,uint } printf("socket creation succ\r\n"); + + // 如果需要SSL,先进行SSL握手 + if (client->use_ssl) { + printf("Initializing SSL/TLS connection...\n"); + if (ws_ssl_init(client) != 0) { + printf("SSL initialization failed\n"); + lwip_close(sockfd); + goto client_ERROR; + } + + if (ws_ssl_handshake(client) != 0) { + printf("SSL handshake failed\n"); + ws_ssl_cleanup(client); + lwip_close(sockfd); + goto client_ERROR; + } + printf("SSL/TLS connection established\n"); + } strcpy(client->host, parsed_url.host); @@ -764,21 +1079,23 @@ WebSocketClient *init_websocket_client(const char *url, const char *headers,uint if (send_handshake(client,headers) < 0) { //发送头 printf("socket send_handshake client_ERROR"); - + if (client->use_ssl) { + ws_ssl_cleanup(client); + } lwip_close(sockfd); goto client_ERROR; } - buffer=(char *)aic_zalloc(BUFFER_SIZE); + buffer=(char *)malloc(BUFFER_SIZE); if(buffer==NULL) { - printf("aic_zalloc(BUFFER_SIZE);null "); + printf("malloc(BUFFER_SIZE) failed"); goto client_ERROR; } printf("recv:%s\r\n",parsed_url.path); - bytes_received = lwip_recv(sockfd, buffer, BUFFER_SIZE - 1, 0); + bytes_received = ws_ssl_recv(client, (uint8_t*)buffer, BUFFER_SIZE - 1); printf("recv; "); @@ -815,6 +1132,9 @@ client_ERROR: buffer=NULL; } if(client){ + if (client->use_ssl) { + ws_ssl_cleanup(client); + } free(client); client = NULL; } @@ -840,6 +1160,7 @@ WebSocketClient *init_websocket_client(const char *url, const char *headers,uint int error = 0; socklen_t len = sizeof(error); int flags; + bool use_ssl = false; if (!url) { WS_LOG_ERROR("init_websocket_client Error: URL is NULL"); @@ -857,16 +1178,25 @@ WebSocketClient *init_websocket_client(const char *url, const char *headers,uint WS_LOG_INFO("Port: %d", parsed_url.port); WS_LOG_INFO("Path: %s", parsed_url.path); WS_LOG_INFO("Query: %s", parsed_url.query); + + // 检查是否使用SSL/TLS + if (strcmp(parsed_url.protocol, "wss") == 0 || strcmp(parsed_url.protocol, "https") == 0) { + use_ssl = true; + WS_LOG_INFO("Using SSL/TLS for secure WebSocket connection"); + } } - client = (WebSocketClient *)aic_zalloc(sizeof(WebSocketClient)) ; + client = (WebSocketClient *)malloc(sizeof(WebSocketClient)) ; memset_s(client,sizeof(WebSocketClient),0,sizeof(WebSocketClient)); if(!client) { - WS_LOG_ERROR("init_websocket_client alloc error: Failed to allocate memory for WebSocketClient"); + WS_LOG_ERROR("init_websocket_client malloc error: Failed to allocate memory for WebSocketClient"); goto client_ERROR; } + // 设置SSL标志 + client->use_ssl = use_ssl; + // 使用标准 socket 替代 TCP 管理器 printf("lwip_socket init_websocket_client\r\n"); client->sockfd = lwip_socket(AF_INET, SOCK_STREAM, 0); @@ -918,8 +1248,23 @@ WebSocketClient *init_websocket_client(const char *url, const char *headers,uint if (lwip_getsockopt(client->sockfd, SOL_SOCKET, SO_ERROR, &error, &len) == 0 && error == 0) { WS_LOG_INFO("Socket connected successfully"); + // 如果需要SSL,先进行SSL握手 + if (client->use_ssl) { + if (ws_ssl_init(client) != 0) { + WS_LOG_ERROR("SSL initialization failed"); + goto client_ERROR; + } + + if (ws_ssl_handshake(client) != 0) { + WS_LOG_ERROR("SSL handshake failed"); + ws_ssl_cleanup(client); + goto client_ERROR; + } + WS_LOG_INFO("SSL/TLS connection established"); + } + // 发送 WebSocket 握手 - char *buffer = (char *)aic_zalloc(BUFFER_SIZE); + char *buffer = (char *)malloc(BUFFER_SIZE); if (buffer != NULL) { int bytes_received = send_handshake(client, NULL, buffer, BUFFER_SIZE); if (bytes_received >= 0) { @@ -1003,6 +1348,11 @@ void close_websocket_client(WebSocketClient *client) { WS_LOG_INFO("close_websocket_client %s %s", client->host, client->path); + // 先关闭SSL连接 + if (client->use_ssl) { + ws_ssl_cleanup(client); + } + // 先关闭socket连接,避免数据继续流入 if (client->sockfd >= 0) { lwip_close(client->sockfd); @@ -1018,6 +1368,7 @@ void close_websocket_client(WebSocketClient *client) { // 清理客户端状态 client->handshake = 0; client->packet_callback = NULL; + client->use_ssl = false; free(client); } @@ -1292,7 +1643,7 @@ static void* websocket_receive_thread(void* arg) { if (FD_ISSET(client->sockfd, &read_fds)) { // 有数据可读 - int bytes_received = lwip_recv(client->sockfd, buffer, sizeof(buffer), 0); + int bytes_received = ws_ssl_recv(client, buffer, sizeof(buffer)); if (bytes_received <= 0) { WS_LOG_ERROR("websocket_receive_thread: connection closed or error"); break; @@ -1413,7 +1764,7 @@ int receive_websocket_message(WebSocketClient *client, unsigned char *buf,int l return -1; } - bytes_received = lwip_recv_with_timeout(client->sockfd, frame, 1, 0, remaining_time); + bytes_received = ws_ssl_recv(client, frame, 1); if (bytes_received <= 0) { @@ -1466,7 +1817,7 @@ int receive_websocket_message(WebSocketClient *client, unsigned char *buf,int l return -1; } - bytes_received = lwip_recv_with_timeout(client->sockfd, frame+1, 1, 0, remaining_time); + bytes_received = ws_ssl_recv(client, frame+1, 1); if (bytes_received <= 0) { if (bytes_received == 0) { printf("Connection lwip_closed by server.\n"); @@ -1497,7 +1848,7 @@ int receive_websocket_message(WebSocketClient *client, unsigned char *buf,int l } unsigned short extended_payload_len; - int ret = lwip_recv_with_timeout(client->sockfd, frame+offset, 2, 0, remaining_time); + int ret = ws_ssl_recv(client, frame+offset, 2); if (ret <= 0) { printf("receive_websocket_message: failed to receive extended length (126)\n"); return -1; @@ -1522,7 +1873,7 @@ int receive_websocket_message(WebSocketClient *client, unsigned char *buf,int l } uint64_t extended_payload_len; - int ret = lwip_recv_with_timeout(client->sockfd, frame+offset, 8, 0, remaining_time); + int ret = ws_ssl_recv(client, frame+offset, 8); if (ret <= 0) { printf("receive_websocket_message: failed to receive extended length (127)\n"); return -1; @@ -1570,7 +1921,7 @@ int receive_websocket_message(WebSocketClient *client, unsigned char *buf,int l return -1; } - int ret = lwip_recv_with_timeout(client->sockfd, frame+offset, 4, 0, remaining_time); + int ret = ws_ssl_recv(client, frame+offset, 4); if (ret <= 0) { printf("receive_websocket_message: failed to receive masking key\n"); return -1; @@ -1597,7 +1948,7 @@ int receive_websocket_message(WebSocketClient *client, unsigned char *buf,int l } //bk_sleep(recv_len/1000+1); - recv_len = lwip_recv_with_timeout(client->sockfd, buf+bytes_received, payload_len-bytes_received, 0, remaining_time); + recv_len = ws_ssl_recv(client, buf+bytes_received, payload_len-bytes_received); if(recv_len>0) bytes_received += recv_len; else if(recv_len<0) 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 9d05eeb62..45ec9db14 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c @@ -94,7 +94,7 @@ static void ln_ws_on_open(int client) */ // [LN][WS] on_message: {"deviceId":"860132060562402","timestamp":1758275259,"messageId":"0fb3719adca2472c8f0909d38df87666","command":"Pong"} -static void ln_ws_on_message(int client, const char *data, size_t length, bool isFinished) +static void ln_ws_on_message(int client, const char *data, size_t length, bool isFinished,bool isBinary) { (void)client; (void)isFinished; @@ -152,7 +152,10 @@ static void ln_ws_on_close(int client) // int ret = WebSocketClientClose(g_ws_client_fd); } - g_ws_client_fd=-1; + else + { + g_ws_client_fd=-1; + } } /* 断开后增加重连计数,由监督线程在下一次连接时携带 */ if (g_ws_running) { diff --git a/code/sdk/zhihui/websocket/CMakeLists.txt b/code/sdk/zhihui/websocket/CMakeLists.txt index 12e538f93..91fb1cd37 100644 --- a/code/sdk/zhihui/websocket/CMakeLists.txt +++ b/code/sdk/zhihui/websocket/CMakeLists.txt @@ -18,6 +18,7 @@ ${ROOT_DIR}/open_source/openharmony/third_party/lwip/lwip_adapter/liteos/src/inc # ${ROOT_DIR}/open_source/openharmony/base/security/huks/frameworks/huks_standard/main/crypto_engine/mbedtls/include # ${ROOT_DIR}/open_source/openharmony/base/security/huks/frameworks/huks_standard/main/common/include ${ROOT_DIR}/open_source/openharmony/foundation/ace/ace_engine_lite/modules/netstack/websocket/include +${ROOT_DIR}/zhihui/websocket/os_ws ) @@ -26,6 +27,7 @@ set(WEBSOCKET_SOURCES # ${CMAKE_CURRENT_SOURCE_DIR}/websocket/cat1_ai_server.c # ${CMAKE_CURRENT_SOURCE_DIR}/websocket/dev_authentication.c ${CMAKE_CURRENT_SOURCE_DIR}/src/websocket_parser.c +${CMAKE_CURRENT_SOURCE_DIR}/os_ws/haisi_websocket.c # ${CMAKE_CURRENT_SOURCE_DIR}/websocket/websocket_test.c ${CMAKE_CURRENT_SOURCE_DIR}/src/zhihui_websocket.c ${CMAKE_CURRENT_SOURCE_DIR}/test/ws_multi_test.c diff --git a/code/sdk/zhihui/websocket/os_ws/haisi_websocket.c b/code/sdk/zhihui/websocket/os_ws/haisi_websocket.c new file mode 100644 index 000000000..21fe09128 --- /dev/null +++ b/code/sdk/zhihui/websocket/os_ws/haisi_websocket.c @@ -0,0 +1,851 @@ +#include "net_websocket.h" +#include +#include +#include +#include "cJSON.h" // Added for JSON parsing +#include "custom_utils.h" +#include "soc_osal.h" +#include "cmsis_os2.h" +#include "haisi_websocket.h" + +/* + * - g_ws_client_fd 当前 WebSocket 客户端fd + * - g_ws_connected 连接状态:0=未连接,1=连接中,2=连接成功 + * - g_ws_running 心跳线程运行标志 + */ + +/* 消息队列相关定义 */ +#define WS_MSG_QUEUE_COUNT 16 +#define WS_MSG_TYPE_SEND_TEXT 0x01 +#define WS_MSG_TYPE_FORCE_RECONNECT 0x02 +#define WS_MSG_TYPE_STOP 0x03 +#define WS_MSG_TYPE_RAW_MESSAGE 0x04 + +/* 连接状态定义 */ +#define WS_STATE_DISCONNECTED 0 // 未连接 +#define WS_STATE_CONNECTING 1 // 连接中 +#define WS_STATE_CONNECTED 2 // 连接成功 +#define WS_STATE_SLEEP 3 // 休眠状态 + +/* WebSocket关闭状态枚举 */ +typedef enum { + WS_CLOSE_NOT_EXECUTED = 0, // 未执行 + WS_CLOSE_STARTING = 1, // 开始执行 + WS_CLOSE_SUCCESS = 2 // 执行成功 +} ws_close_status_t; + +/* WebSocket命令枚举 */ + + + +typedef struct { + uint8_t msg_type; + uint16_t data_len; + uint32_t timestamp; // 收到消息的时间戳 + bool isBinary; // 消息是否为二进制数据 + char *raw_data; // 动态分配的原始消息数据 +} ws_msg_info_t; + + +static int g_ws_client_fd = -1; +static volatile int g_ws_connected = WS_STATE_DISCONNECTED; +static uint32_t g_ws_main_thread = 0; +static volatile int g_ws_running = 0; +static volatile unsigned int g_ws_reconnect_count = 0; /* 断线后递增,用于URL reconnect参数 */ +static osMessageQueueId_t g_ws_msg_queue = NULL; +static ws_call_backs_t g_ws_callbacks = {0}; /* WebSocket回调函数结构体 */ +static volatile ws_close_status_t g_ws_close_status = WS_CLOSE_NOT_EXECUTED; /* WebSocket关闭状态标志位 */ + +static void uapi_ws_build_url(char *out, size_t out_size); +static void uapi_ws_default_url_handler(char *url, size_t url_size, char *header_buf, size_t header_size); +int uapi_ws_send_ping(void); +static int uapi_ws_close_with_status(int client_fd); +static ws_close_status_t uapi_ws_get_close_status(void); +static void uapi_ws_reset_close_status(void); +const char* uapi_ws_get_connection_state_string(void); + +/* 消息内存管理函数 */ +static ws_msg_info_t* ws_msg_create(uint16_t data_len) +{ + ws_msg_info_t *msg = (ws_msg_info_t*)malloc(sizeof(ws_msg_info_t)); + if (msg == NULL) { + printf("[LN][WS] Failed to allocate memory for ws_msg_info_t\n"); + return NULL; + } + + msg->raw_data = (char*)malloc(data_len + 1); // +1 for null terminator + if (msg->raw_data == NULL) { + printf("[LN][WS] Failed to allocate memory for raw_data (size: %d)\n", data_len); + free(msg); + return NULL; + } + + msg->data_len = data_len; + msg->raw_data[data_len] = '\0'; // Ensure null termination + return msg; +} + +static void ws_msg_free(ws_msg_info_t *msg) +{ + if (msg != NULL) { + if (msg->raw_data != NULL) { + free(msg->raw_data); + } + free(msg); + } +} + +/* 队列创建函数 */ +void uapi_ws_queue_init(void) +{ + printf("[LN][WS] uapi_ws_queue_init\n"); + g_ws_msg_queue = osMessageQueueNew(WS_MSG_QUEUE_COUNT, sizeof(ws_msg_info_t*), NULL); + if (g_ws_msg_queue == NULL) { + printf("[LN][WS] uapi_ws_queue_init failed: failed to create message queue\n"); + } else { + printf("[LN][WS] uapi_ws_queue_init success: queue created\n"); + } +} + +/* 握手成功回调:标记连接就绪 */ +static void uapi_ws_on_open(int client) +{ + (void)client; + printf("WebSocketClientConstructor uapi_ws_on_open\r\n"); + g_ws_connected = WS_STATE_CONNECTED; + uapi_ws_reset_close_status(); // 连接成功时重置关闭状态 + printf("[LN][WS] 连接状态: 连接成功, 关闭状态已重置为 NOT_EXECUTED (%d)\n", g_ws_close_status); +} + +/* + * 收到服务器下发的文本消息 + * - 上层可根据业务定义的 JSON 字段进行再分发 + */ +static void uapi_ws_on_message(int client, const char *data, size_t length, bool isFinished,bool isBinary) +{ + (void)client; + (void)isFinished; + if (data == NULL || length == 0) return; + + /* 数据完整性判断 */ + printf("WebSocketClientConstructor data %d\r\n",length); + if (isFinished) { + // 数据完整,创建消息结构体 + ws_msg_info_t *msg = ws_msg_create(length); + if (msg == NULL) { + printf("[LN][WS] Failed to create message structure\n"); + return; + } + + msg->msg_type = WS_MSG_TYPE_RAW_MESSAGE; + msg->isBinary = isBinary; // 设置二进制标志 + + // 记录收到消息的时间戳 + uapi_get_timestampe_ss(&msg->timestamp); + + // 复制原始数据 + memcpy(msg->raw_data, data, length); + msg->raw_data[length] = '\0'; + + if(isBinary) + { + if (g_ws_callbacks.binary_callback != NULL) { + g_ws_callbacks.binary_callback((const uint8_t*)msg->raw_data, msg->timestamp, msg->data_len); + } else { + printf("[LN][WS] No binary message callback registered\n"); + } + // 二进制消息处理完后释放内存 + ws_msg_free(msg); + } + else + { + osStatus_t ret = osMessageQueuePut(g_ws_msg_queue, &msg, 0, 0); + if (ret == osOK) { + printf("[LN][WS] Raw message queued successfully, length: %d, timestamp: %u\n", msg->data_len, msg->timestamp); + } else { + printf("[LN][WS] Failed to queue raw message: %d\n", ret); + // 队列失败时释放内存 + ws_msg_free(msg); + } + } + + } else { + printf("[LN][WS] Incomplete or invalid message data\n"); + } +} + +/* 出错回调:仅打印,实际重连策略可在上层统一管理 */ +static void uapi_ws_on_error(int client, struct WebSocketErrorResult error) +{ + (void)client; + printf("WebSocketClientConstructor [LN][WS] error: code=%d msg=%s\n", error.errorCode, error.errorMessage); +} + +/* 断开回调:标记为未连接。是否自动重连交由上层决定 */ +static void uapi_ws_on_close(int client) +{ + printf("WebSocketClientConstructor [LN][WS] uapi_ws_on_close %d\r\n",client); + + // 在断开回调触发时,将关闭状态设置为成功 + + printf("[LN][WS] WebSocket close callback triggered, status set to SUCCESS (%d)\n", g_ws_close_status); + + g_ws_connected = WS_STATE_DISCONNECTED; + printf("[LN][WS] 连接状态: 未连接\n"); + + // 触发连接断开事件(注意:这里不能直接调用回调,因为不在主线程中) + // 事件回调将在主线程中处理 + if(g_ws_client_fd>=0) + { + // 这里不再调用 uapi_ws_close_with_status,因为回调已经触发说明关闭成功 + if(g_ws_close_status==WS_CLOSE_NOT_EXECUTED) + { + + } + else + { + g_ws_client_fd=-1; + } + + } + /* 断开后增加重连计数,由监督线程在下一次连接时携带 */ + if (g_ws_running) { + if (g_ws_reconnect_count < 0xFFFFFFFFu) { + g_ws_reconnect_count++; + } + } +} + +/* + * 主线程:负责连接管理、心跳维护和队列消息处理 + * - 监督连接状态,处理重连逻辑 + * - 心跳处理完全由回调函数负责(包括时间判断) + * - 处理队列中的消息 + */ +static void uapi_ws_main_thread(void *arg) +{ + (void)arg; + ws_msg_info_t *msg; + uint32_t msgsize =0; + int last_connection_state = WS_STATE_DISCONNECTED; // 记录上次连接状态 + + while (g_ws_running) { + // 处理队列消息(非阻塞) + if (osMessageQueueGet(g_ws_msg_queue, &msg, NULL, 1000) == osOK) { + printf("[LN][WS] osMessageQueueGet: g_ws_msg_queue\n"); + switch (msg->msg_type) { + case WS_MSG_TYPE_SEND_TEXT: + if (g_ws_connected == WS_STATE_CONNECTED && g_ws_client_fd >= 0) { + // 对于文本消息,我们需要特殊处理,因为现在data是ws_data_msg_t类型 + // 这里需要重新设计文本消息的存储方式 + printf("[LN][WS] Text message type not supported in new structure\n"); + } else { + printf("[LN][WS] Cannot send text, connection state: %d\n", g_ws_connected); + } + ws_msg_free(msg); + break; + + case WS_MSG_TYPE_FORCE_RECONNECT: + printf("[LN][WS] Force reconnect requested\n"); + if (g_ws_client_fd >= 0) { + printf("[LN][WS] WebSocketClientClose\n"); + uapi_ws_close_with_status(g_ws_client_fd); + printf("[LN][WS] WebSocketClientClose end\n"); + } + g_ws_connected = WS_STATE_DISCONNECTED; + printf("[LN][WS] 连接状态: 未连接(强制重连)\n"); + ws_msg_free(msg); + break; + + case WS_MSG_TYPE_STOP: + printf("[LN][WS] Stop requested from queue\n"); + g_ws_running = 0; + ws_msg_free(msg); + break; + + case WS_MSG_TYPE_RAW_MESSAGE: + { + printf("[LN][WS] Received raw message: %s, timestamp: %u, isBinary: %s\n", + msg->raw_data, msg->timestamp, msg->isBinary ? "true" : "false"); + + // 根据消息类型调用不同的回调函数 + if (!msg->isBinary) { + + // 文本消息 + if (g_ws_callbacks.message_callback != NULL) { + g_ws_callbacks.message_callback(msg->raw_data, msg->timestamp); + } else { + printf("[LN][WS] No text message callback registered\n"); + } + } + // 处理完消息后释放内存 + ws_msg_free(msg); + } + break; + + default: + printf("[LN][WS] Unknown message type: %d\n", msg->msg_type); + ws_msg_free(msg); + break; + } + } + else + { + // printf("osMessageQueueGet timeout or failed\r\n"); + } + + // 检测连接状态变化并触发事件回调 + if (g_ws_callbacks.event_callback != NULL && g_ws_connected != last_connection_state) { + if (g_ws_connected == WS_STATE_CONNECTED && last_connection_state != WS_STATE_CONNECTED) { + // 连接成功事件 + g_ws_callbacks.event_callback(WS_EVENT_CONNECTED); + } else if (g_ws_connected == WS_STATE_DISCONNECTED && last_connection_state == WS_STATE_CONNECTED) { + // 连接断开事件 + g_ws_callbacks.event_callback(WS_EVENT_DISCONNECTED); + } + last_connection_state = g_ws_connected; + } + + // 连接管理部分 + if (g_ws_connected == WS_STATE_SLEEP) { + // 休眠状态:暂停所有操作,只打印状态信息 + printf("[LN][WS] Sleep mode: pausing all operations\n"); + usleep(1000 * 1000); // 休眠1秒 + continue; + } + + if (g_ws_connected == WS_STATE_DISCONNECTED) { + if (g_ws_client_fd >= 0) { + { + if(g_ws_close_status== WS_CLOSE_NOT_EXECUTED){ + printf("[LN][WS] WebSocketClientClose WS_CLOSE_NOT_EXECUTED\n"); + uapi_ws_close_with_status(g_ws_client_fd); + g_ws_client_fd=-1; + printf("[LN][WS] WebSocketClientClose WS_CLOSE_NOT_EXECUTED end\n"); + } + } + } + + if (g_ws_client_fd < 0) { + + + printf("[LN][WS] 开始创建WebSocket客户端...\n"); + + // 触发连接开始事件 + if (g_ws_callbacks.event_callback != NULL) { + g_ws_callbacks.event_callback(WS_EVENT_CONNECTING); + } + + g_ws_client_fd = WebSocketClientConstructor(uapi_ws_on_open, uapi_ws_on_message, uapi_ws_on_error, uapi_ws_on_close); + if (g_ws_client_fd < 0) { + printf("[LN][WS] constructor failed: %d\n", g_ws_client_fd); + g_ws_connected = WS_STATE_DISCONNECTED; + printf("[LN][WS] 连接状态: 未连接(创建失败)\n"); + /* 失败则短暂休眠后重试创建 */ + usleep(2000 * 1000); + continue; + } + } + + char url[512] = {0}; + char header_buf[512] = {0}; + + // 使用回调函数处理URL和Header + if (g_ws_callbacks.url_callback != NULL) { + g_ws_callbacks.url_callback(url, sizeof(url), header_buf, sizeof(header_buf)); + } + + printf("[LN][WS] 尝试连接WebSocket: %s\n", url); + printf("[LN][WS] 使用Header: %s\n", header_buf); + int ret=0; + if(strlen(header_buf)==0) + { + ret= WebSocketClientConnect(g_ws_client_fd, url, NULL); + } + else + { + ret= WebSocketClientConnect(g_ws_client_fd, url, header_buf); + } + + // int ret = WebSocketClientConnect(g_ws_client_fd,"ws://118.145.158.207:22080/chat/ws?platform=huoshan"); + + if (ret != 0) { + printf("[LN][WS] connect failed: %d\n", ret); + g_ws_connected = WS_STATE_DISCONNECTED; + printf("[LN][WS] 连接状态: 未连接(连接失败)\n"); + (void)uapi_ws_close_with_status(g_ws_client_fd); + g_ws_client_fd = -1; + /* 指数退避*/ + unsigned int delay = 1u << (g_ws_reconnect_count > 5 ? 5 : g_ws_reconnect_count); + if (delay > 60) delay = 60; + for (unsigned int i = 0; i < delay && g_ws_running; i++) { + usleep(1000 * 1000); + } + continue; + } + else + { + g_ws_connected = WS_STATE_CONNECTING; + } + /* 连接请求已发送,等待握手完成 */ + printf("[LN][WS] 连接请求已发送,等待握手完成...\n"); + } + + // 心跳部分:由回调函数处理时间判断和心跳发送 + if (g_ws_connected == WS_STATE_CONNECTED && g_ws_client_fd >= 0) { + // 使用心跳回调函数处理,包括时间判断 + if (g_ws_callbacks.heartbeat_callback != NULL) { + if (g_ws_callbacks.heartbeat_callback()) { + // 回调函数返回1表示需要发送心跳,这里可以添加额外的处理 + printf("[LN][WS] Heartbeat callback triggered\n"); + } + } + } + else + { + printf("ln ws_state %s\r\n",uapi_ws_get_connection_state_string()); + } + + /* 1秒间隔 */ + usleep(1000 * 1000); + } + + + if (g_ws_client_fd >= 0) { + uapi_ws_close_with_status(g_ws_client_fd); + } + g_ws_connected = WS_STATE_DISCONNECTED; + + // 删除消息队列 + if (g_ws_msg_queue != NULL) { + ws_msg_info_t msg; + while (osMessageQueueGet(g_ws_msg_queue, &msg, NULL, 0) == osOK) { + // 原始消息数据不需要额外释放,因为存储在栈上 + printf("[LN][WS] Clearing queued message: %s\n", msg.raw_data); + } + osMessageQueueDelete(g_ws_msg_queue); + g_ws_msg_queue = NULL; + } +} +/* + * 启动:开启监督线程与心跳机制(心跳线程首次连接成功后会自然启动) + * - 若已在运行则直接返回成功 + * - callback: 消息处理回调函数,可以为NULL使用默认处理 + */ +int uapi_ws_start(const ws_call_backs_t *callbacks) +{ + if (g_ws_running) { + return 0; + } + g_ws_running = 1; + g_ws_reconnect_count = 0; /* 启动时从0开始 */ + g_ws_connected = WS_STATE_DISCONNECTED; /* 启动时重置连接状态 */ + + // 注册回调函数结构体 + if (callbacks != NULL) { + g_ws_callbacks = *callbacks; + printf("[LN][WS] WebSocket callbacks registered successfully\n"); + + if (g_ws_callbacks.message_callback != NULL) { + printf("[LN][WS] Text message callback registered\n"); + } else { + printf("[LN][WS] No text message callback registered\n"); + } + + if (g_ws_callbacks.binary_callback != NULL) { + printf("[LN][WS] Binary message callback registered\n"); + } else { + printf("[LN][WS] No binary message callback registered\n"); + } + + if (g_ws_callbacks.url_callback != NULL) { + printf("[LN][WS] URL callback registered\n"); + } else { + printf("[LN][WS] Using default URL handler\n"); + } + + if (g_ws_callbacks.heartbeat_callback != NULL) { + printf("[LN][WS] Heartbeat callback registered\n"); + } else { + printf("[LN][WS] No heartbeat callback registered\n"); + } + + if (g_ws_callbacks.event_callback != NULL) { + printf("[LN][WS] Event callback registered\n"); + } else { + printf("[LN][WS] No event callback registered\n"); + } + } else { + memset(&g_ws_callbacks, 0, sizeof(ws_call_backs_t)); + printf("[LN][WS] No callbacks provided, using defaults\n"); + } + if (!g_ws_main_thread) { + app_task_definition_ext_t main_task = { + .attr = { + .name = "uapi_ws_main", + .stack_size = 0x2000, + .priority = osPriorityNormal + }, + .func = uapi_ws_main_thread, + .task_handle = &g_ws_main_thread, + .create_queue_handle = uapi_ws_queue_init, + .use_mem = USE_EXTEND_MEM, + .inst_args = NULL + }; + (void)uapi_create_osThread_custom_with_args(&main_task); + } + cat1_api_keep_wakeup(1); + cat1_api_wakeup_cat1(1); + + return 0; +} + +/* 发送文本消息:仅在连接就绪时有效 */ +int uapi_ws_send_text(const char *text) +{ + if (!text) return -1; + if (g_ws_connected != WS_STATE_CONNECTED || g_ws_client_fd < 0) { + printf("[LN][WS] Cannot send text, connection state: %d\n", g_ws_connected); + return -1; + } + return WebSocketClientSendText(g_ws_client_fd, text, strlen(text)); +} + +int uapi_ws_send_BIN(uint8_t *data,int len) +{ + if (!data) return -1; + if (g_ws_connected != WS_STATE_CONNECTED || g_ws_client_fd < 0) { + printf("[LN][WS] Cannot send text, connection state: %d\n", g_ws_connected); + return -1; + } + if(len%3) + { + uint16_t data_mod_len=len%3; + + WebSocketClientSendBinStart(g_ws_client_fd, data, (len/3)); + + WebSocketClientSendBinContinue(g_ws_client_fd, data+ (len/3), (len/3)); + + WebSocketClientSendBinEnd(g_ws_client_fd,data+(len/3)+(len/3),(len/3)+data_mod_len); + + } + else + { + WebSocketClientSendBinStart(g_ws_client_fd, data, len/3); + + WebSocketClientSendBinContinue(g_ws_client_fd,data+len/3,len/3); + + WebSocketClientSendBinEnd(g_ws_client_fd,data+len/3+len/3,len/3); + + } + printf("[LN][WS] uapi_ws_send_BIN,size: %d\n", len); + + return 0; +} + + + +/* 通过队列发送文本消息(异步) */ +int uapi_ws_send_text_async(const char *text) +{ + if (!text || !g_ws_running) return -1; + + // 由于新的数据结构不支持直接文本消息,这里暂时返回错误 + // 如果需要支持文本消息,需要重新设计消息类型 + printf("[LN][WS] Text message not supported in new queue structure\n"); + return -1; +} + +/* 强制重连 */ +int uapi_ws_force_reconnect(void) +{ + if (!g_ws_running) return -1; + + ws_msg_info_t *msg = ws_msg_create(0); + if (msg == NULL) { + printf("[LN][WS] Failed to create force reconnect message\n"); + return -1; + } + + msg->msg_type = WS_MSG_TYPE_FORCE_RECONNECT; + msg->timestamp = 0; // 控制消息不需要时间戳 + msg->isBinary = false; // 控制消息不是二进制数据 + + osStatus_t ret = osMessageQueuePut(g_ws_msg_queue, &msg, 0, 0); + if (ret != osOK) { + ws_msg_free(msg); + return -1; + } + return 0; +} + + +/* 通过队列停止WebSocket服务 */ +int uapi_ws_stop_async(void) +{ + if (!g_ws_running) return -1; + + ws_msg_info_t *msg = ws_msg_create(0); + if (msg == NULL) { + printf("[LN][WS] Failed to create stop message\n"); + return -1; + } + + msg->msg_type = WS_MSG_TYPE_STOP; + msg->timestamp = 0; // 控制消息不需要时间戳 + msg->isBinary = false; // 控制消息不是二进制数据 + + osStatus_t ret = osMessageQueuePut(g_ws_msg_queue, &msg, 0, 0); + if (ret != osOK) { + ws_msg_free(msg); + return -1; + } + return 0; +} + +/* 停止:关闭心跳并关闭连接 */ +int uapi_ws_stop(void) +{ + g_ws_running = 0; + if (g_ws_main_thread) { + osThreadJoin(g_ws_main_thread); + g_ws_main_thread = 0; + } + int ret = 0; + if (g_ws_client_fd >= 0) { + ret = uapi_ws_close_with_status(g_ws_client_fd); + g_ws_client_fd = -1; + } + g_ws_connected = WS_STATE_DISCONNECTED; + + // 删除消息队列 + if (g_ws_msg_queue != NULL) { + osMessageQueueDelete(g_ws_msg_queue); + g_ws_msg_queue = NULL; + } + + return ret; +} + +/* 获取当前连接状态 */ +int uapi_ws_get_connection_state(void) +{ + return g_ws_connected; +} + +/* 获取连接状态字符串 */ +const char* uapi_ws_get_connection_state_string(void) +{ + switch (g_ws_connected) { + case WS_STATE_DISCONNECTED: + return "未连接"; + case WS_STATE_CONNECTING: + return "连接中"; + case WS_STATE_CONNECTED: + return "连接成功"; + case WS_STATE_SLEEP: + return "休眠状态"; + default: + return "未知状态"; + } +} + +/* ============================ 公共回调函数接口 ============================ */ + +/** + * @brief 注册文本消息处理回调函数(公共接口) + * @param callback 回调函数 + */ +void uapi_ws_register_message_callback(ws_message_callback_t callback) +{ + g_ws_callbacks.message_callback = callback; + if (callback != NULL) { + printf("[LN][WS] Text message callback registered successfully\n"); + } else { + printf("[LN][WS] Text message callback unregistered\n"); + } +} + +/** + * @brief 注册二进制消息处理回调函数(公共接口) + * @param callback 回调函数 + */ +void uapi_ws_register_binary_callback(ws_binary_message_callback_t callback) +{ + g_ws_callbacks.binary_callback = callback; + if (callback != NULL) { + printf("[LN][WS] Binary message callback registered successfully\n"); + } else { + printf("[LN][WS] Binary message callback unregistered\n"); + } +} + +/** + * @brief 注册URL处理回调函数(公共接口) + * @param callback 回调函数 + */ +void uapi_ws_register_url_callback(ws_url_callback_t callback) +{ + g_ws_callbacks.url_callback = callback; + if (callback != NULL) { + printf("[LN][WS] URL callback registered successfully\n"); + } else { + printf("[LN][WS] URL callback unregistered\n"); + } +} + +/** + * @brief 注册心跳处理回调函数(公共接口) + * @param callback 回调函数,负责时间判断和心跳发送,返回1表示发送了心跳,返回0表示未发送 + */ +void uapi_ws_register_heartbeat_callback(ws_heartbeat_callback_t callback) +{ + g_ws_callbacks.heartbeat_callback = callback; + if (callback != NULL) { + printf("[LN][WS] Heartbeat callback registered successfully\n"); + } else { + printf("[LN][WS] Heartbeat callback unregistered\n"); + } +} + +/** + * @brief 注册连接事件回调函数(公共接口) + * @param callback 回调函数,在连接状态变化时触发 + */ +void uapi_ws_register_event_callback(ws_event_callback_t callback) +{ + g_ws_callbacks.event_callback = callback; + if (callback != NULL) { + printf("[LN][WS] Event callback registered successfully\n"); + } else { + printf("[LN][WS] Event callback unregistered\n"); + } +} + +/* ============================ 回调函数管理 ============================ */ + + + +/* ============================ WebSocket关闭状态管理 ============================ */ + +/** + * @brief 带状态跟踪的WebSocket关闭函数 + * @param client_fd WebSocket客户端文件描述符 + * @return WebSocketClientClose的返回值 + */ +static int uapi_ws_close_with_status(int client_fd) +{ + if (client_fd < 0) { + printf("[LN][WS] Invalid client_fd for close operation\n"); + return -1; + } + + // 设置状态为开始执行 + g_ws_close_status = WS_CLOSE_STARTING; + printf("[LN][WS] WebSocketClientClose starting, fd=%d, status=%d\n", client_fd, g_ws_close_status); + + // 执行关闭操作 + int ret = WebSocketClientClose(client_fd); + + // 注意:成功状态由 ln_ws_on_close 回调函数设置,这里只处理失败情况 + if (ret != 0) { + g_ws_close_status = WS_CLOSE_NOT_EXECUTED; + printf("[LN][WS] WebSocketClientClose failed, fd=%d, ret=%d, status=%d\n", client_fd, ret, g_ws_close_status); + } else { + printf("[LN][WS] WebSocketClientClose call returned success, waiting for close callback\n"); + } + + return ret; +} + +/** + * @brief 获取当前WebSocket关闭状态 + * @return 当前关闭状态 + */ +static ws_close_status_t uapi_ws_get_close_status(void) +{ + return g_ws_close_status; +} + +/** + * @brief 重置WebSocket关闭状态 + */ +static void uapi_ws_reset_close_status(void) +{ + g_ws_close_status = WS_CLOSE_NOT_EXECUTED; + printf("[LN][WS] WebSocket close status reset to NOT_EXECUTED (%d)\n", g_ws_close_status); +} + +/* ============================ 公共接口函数 ============================ */ + +/** + * @brief 获取WebSocket关闭状态(公共接口) + * @return 当前关闭状态 + */ +int uapi_ws_get_close_status_public(void) +{ + return (int)uapi_ws_get_close_status(); +} + +/** + * @brief 打印WebSocket状态信息(调试用) + */ +void uapi_ws_print_status_info(void) +{ + printf("[LN][WS] === WebSocket Status Info ===\n"); + printf("[LN][WS] Connection State: %s (%d)\n", uapi_ws_get_connection_state_string(), g_ws_connected); + printf("[LN][WS] Close Status: %d (0=NOT_EXECUTED, 1=STARTING, 2=SUCCESS)\n", g_ws_close_status); + printf("[LN][WS] Client FD: %d\n", g_ws_client_fd); + printf("[LN][WS] Running: %d\n", g_ws_running); + printf("[LN][WS] Reconnect Count: %u\n", g_ws_reconnect_count); + printf("[LN][WS] ===============================\n"); +} + +/** + * @brief 进入休眠状态(公共接口) + * @return 0成功,-1失败 + */ +int uapi_ws_enter_sleep(void) +{ + printf("[LN][WS] Entering sleep mode\n"); + + if (!g_ws_running) { + printf("[LN][WS] WebSocket service not running, cannot enter sleep\n"); + return -1; + } + + // 如果当前有连接,先断开 + if (g_ws_client_fd >= 0) { + printf("[LN][WS] Disconnecting before entering sleep mode\n"); + uapi_ws_close_with_status(g_ws_client_fd); + } + + // 设置状态为休眠 + g_ws_connected = WS_STATE_SLEEP; + printf("[LN][WS] Sleep mode activated, all operations paused\n"); + + return 0; +} + +/** + * @brief 退出休眠状态(公共接口) + * @return 0成功,-1失败 + */ +int uapi_ws_exit_sleep(void) +{ + printf("[LN][WS] Exiting sleep mode\n"); + + if (g_ws_connected != WS_STATE_SLEEP) { + printf("[LN][WS] Not in sleep mode, current state: %s\n", uapi_ws_get_connection_state_string()); + return -1; + } + + // 设置状态为断开,让主线程重新开始连接 + g_ws_connected = WS_STATE_DISCONNECTED; + printf("[LN][WS] Sleep mode deactivated, resuming normal operations\n"); + + return 0; +} + + + diff --git a/code/sdk/zhihui/websocket/os_ws/haisi_websocket.h b/code/sdk/zhihui/websocket/os_ws/haisi_websocket.h new file mode 100644 index 000000000..513955ae4 --- /dev/null +++ b/code/sdk/zhihui/websocket/os_ws/haisi_websocket.h @@ -0,0 +1,73 @@ +/* ============================== WebSocket 客户端接口 ============================== */ + +#ifndef __WEBSOCKET_HAISI_H__ +#define __WEBSOCKET_HAISI_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/* 消息处理回调函数类型 */ +typedef void (*ws_message_callback_t)(const char *raw_data, uint32_t timestamp); + +/* 二进制消息处理回调函数类型 */ +typedef void (*ws_binary_message_callback_t)(const uint8_t *raw_data, uint32_t timestamp, uint16_t data_len); + +/* URL构建回调函数类型 */ +typedef void (*ws_url_callback_t)(char *url, size_t url_size, char *header_buf, size_t header_size); + +/* 心跳处理回调函数类型 */ +typedef int (*ws_heartbeat_callback_t)(void); // 返回1表示需要发送心跳,返回0表示不需要 + +/* WebSocket连接事件类型 */ +typedef enum { + WS_EVENT_CONNECTING = 0, /* 开始连接 */ + WS_EVENT_CONNECTED, /* 连接成功 */ + WS_EVENT_DISCONNECTED /* 连接断开 */ +} ws_event_type_t; + +/* 连接事件回调函数类型 */ +typedef void (*ws_event_callback_t)(ws_event_type_t event_type); + +/* WebSocket回调函数结构体 */ +typedef struct { + ws_message_callback_t message_callback; /* 文本消息处理回调函数 */ + ws_binary_message_callback_t binary_callback; /* 二进制消息处理回调函数 */ + ws_url_callback_t url_callback; /* URL构建回调函数 */ + ws_heartbeat_callback_t heartbeat_callback; /* 心跳处理回调函数 */ + ws_event_callback_t event_callback; /* 连接事件回调函数 */ +} ws_call_backs_t; + + +/* WebSocket接口函数 */ +int uapi_ws_start(const ws_call_backs_t *callbacks); +int uapi_ws_send_text(const char *text); +int uapi_ws_stop(void); +int uapi_ws_stop_async(void); +int uapi_ws_force_reconnect(void); +int uapi_ws_enter_sleep(void); +int uapi_ws_exit_sleep(void); +int uapi_ws_send_BIN(uint8_t *data,int len); + +/* WebSocket状态查询接口 */ +int uapi_ws_get_connection_state(void); +const char* uapi_ws_get_connection_state_string(void); +int uapi_ws_get_close_status_public(void); +void uapi_ws_print_status_info(void); + +/* 回调函数注册接口 */ +void uapi_ws_register_message_callback(ws_message_callback_t callback); +void uapi_ws_register_binary_callback(ws_binary_message_callback_t callback); +void uapi_ws_register_url_callback(ws_url_callback_t callback); +void uapi_ws_register_heartbeat_callback(ws_heartbeat_callback_t callback); +void uapi_ws_register_event_callback(ws_event_callback_t callback); + +#ifdef __cplusplus +} +#endif + +#endif // ZHIHUI_WEBSOCKET_H \ No newline at end of file diff --git a/code/sdk/zhihui/websocket/src/zhihui_websocket.c b/code/sdk/zhihui/websocket/src/zhihui_websocket.c index e81ad925f..8b2fdb949 100644 --- a/code/sdk/zhihui/websocket/src/zhihui_websocket.c +++ b/code/sdk/zhihui/websocket/src/zhihui_websocket.c @@ -391,7 +391,7 @@ static void zhihui_ws_handle_received_data(zhihui_websocket_client_t *client, co case WS_TEXT: case WS_BINARY: if (client->on_message) { - client->on_message(client->client_fd, (const char*)payload, payload_len, header->fin); + client->on_message(client->client_fd, (const char*)payload, payload_len, header->fin,true); } break;