From: 孙春阳 Date: Thu, 4 Sep 2025 13:07:44 +0000 (+0800) Subject: ai中间版本 X-Git-Tag: leiniao_debug_1.0.0_20250922_020716~113^2~7 X-Git-Url: http://git.snicker.com.cn/css/static/gitweb.css?a=commitdiff_plain;h=75ddc4dfb25ff9e1779fed110c0f16fca01261aa;p=mirrors%2FFD230.git ai中间版本 --- diff --git a/code/sdk/zhihui/ai/CMakeLists.txt b/code/sdk/zhihui/ai/CMakeLists.txt index 693d10cd2..4cd99748c 100644 --- a/code/sdk/zhihui/ai/CMakeLists.txt +++ b/code/sdk/zhihui/ai/CMakeLists.txt @@ -24,6 +24,8 @@ ${CMAKE_CURRENT_SOURCE_DIR}/src/ai_services.c ${CMAKE_CURRENT_SOURCE_DIR}/src/ai_asr_service.c ${CMAKE_CURRENT_SOURCE_DIR}/src/ai_chat_service.c ${CMAKE_CURRENT_SOURCE_DIR}/src/ai_tts_service.c +${CMAKE_CURRENT_SOURCE_DIR}/src/ai_event_manager.c +${CMAKE_CURRENT_SOURCE_DIR}/src/dev_authentication.c ${CMAKE_CURRENT_SOURCE_DIR}/example/ai_at_cmd.c # ${CMAKE_CURRENT_SOURCE_DIR}/example/ai_demo.c ) diff --git a/code/sdk/zhihui/ai/Makefile b/code/sdk/zhihui/ai/Makefile index b08c024f5..8312c43ee 100644 --- a/code/sdk/zhihui/ai/Makefile +++ b/code/sdk/zhihui/ai/Makefile @@ -19,7 +19,8 @@ SOURCES = $(SRC_DIR)/ai_connection_manager.c \ $(SRC_DIR)/ai_services.c \ $(SRC_DIR)/ai_asr_service.c \ $(SRC_DIR)/ai_chat_service.c \ - $(SRC_DIR)/ai_tts_service.c + $(SRC_DIR)/ai_tts_service.c \ + $(SRC_DIR)/ai_event_manager.c # 示例文件 EXAMPLE_SOURCES = $(EXAMPLE_DIR)/ai_demo.c \ diff --git a/code/sdk/zhihui/ai/example/ai_at_cmd.c b/code/sdk/zhihui/ai/example/ai_at_cmd.c index b3fd99012..bab86387d 100644 --- a/code/sdk/zhihui/ai/example/ai_at_cmd.c +++ b/code/sdk/zhihui/ai/example/ai_at_cmd.c @@ -53,9 +53,9 @@ extern "C" { #define AI_AT_DEMO_CHAT_URL "ws://amp.raisound.com/chat/ws" #define AI_AT_DEMO_TTS_URL "ws://amp.raisound.com/tts/ws" #else -#define AI_AT_DEMO_ASR_URL "ws://192.168.1.100:8080/asr/ws" -#define AI_AT_DEMO_CHAT_URL "ws://192.168.1.100:8080/chat/ws" -#define AI_AT_DEMO_TTS_URL "ws://192.168.1.100:8080/tts/ws" +#define AI_AT_DEMO_ASR_URL "ws://118.145.158.207:22080/asr/ws" +#define AI_AT_DEMO_CHAT_URL "ws://118.145.158.207:22080/chat/ws" +#define AI_AT_DEMO_TTS_URL "ws://118.145.158.207:22080/tts/ws" #endif #define AI_AT_DEMO_AUDIO_SIZE 1024 #define AI_AT_DEMO_CHAT_MESSAGE "你好,我是AI助手AT命令测试程序" @@ -208,6 +208,11 @@ static uint32_t at_ai_init(uint8_t *para, uint32_t paraLen, char *strCommonCmd) strncpy(conn_config.connection_config.chat_url, AI_AT_DEMO_CHAT_URL, AI_MAX_URL_LENGTH - 1); strncpy(conn_config.connection_config.tts_url, AI_AT_DEMO_TTS_URL, AI_MAX_URL_LENGTH - 1); + // 🆕 关键修复:设置事件回调 + extern void connection_event_handler(const ai_event_t *event, void *user_data); + conn_config.event_callback = connection_event_handler; + conn_config.user_data = &g_ai_services_context; + ret = ai_connection_manager_init(&conn_config); if (ret != AI_OK) { printf("[AI_AT] 连接管理器初始化失败: %d\n", ret); @@ -513,8 +518,42 @@ static uint32_t at_ai_tts_test(uint8_t *para, uint32_t paraLen, char *strCommonC ai_connection_state_t state = ai_connection_manager_get_connection_state(AI_SERVICE_TTS); if (state != AI_CONN_STATE_CONNECTED) { printf("[AI_AT] TTS服务未连接,状态: %d\n", state); - g_test_stats.tests_failed++; - return ERRCODE_FAIL; + + // 如果是错误状态,尝试重连 + if (state == AI_CONN_STATE_ERROR) { + printf("[AI_AT] 检测到TTS连接错误,尝试重连...\n"); + ai_error_t reconnect_ret = ai_connection_manager_reconnect(AI_SERVICE_TTS); + if (reconnect_ret == AI_OK) { + printf("[AI_AT] TTS重连请求已发送,等待连接建立...\n"); + + // 等待连接建立 + for (int i = 0; i < 10; i++) { // 最多等待5秒 +#ifndef __TEST_MODE__ + osDelay(500); // 等待500ms +#endif + state = ai_connection_manager_get_connection_state(AI_SERVICE_TTS); + if (state == AI_CONN_STATE_CONNECTED) { + printf("[AI_AT] TTS重连成功!\n"); + break; + } + } + + // 再次检查连接状态 + if (state != AI_CONN_STATE_CONNECTED) { + printf("[AI_AT] TTS重连失败,当前状态: %d\n", state); + g_test_stats.tests_failed++; + return ERRCODE_FAIL; + } + } else { + printf("[AI_AT] TTS重连请求失败: %d\n", reconnect_ret); + g_test_stats.tests_failed++; + return ERRCODE_FAIL; + } + } else { + printf("[AI_AT] TTS服务状态异常,无法执行测试\n"); + g_test_stats.tests_failed++; + return ERRCODE_FAIL; + } } char* session_id = generate_session_id(); @@ -753,6 +792,84 @@ static uint32_t at_ai_cleanup(uint8_t *para, uint32_t paraLen, char *strCommonCm return ERRCODE_SUCC; } +/** + * @brief AT^AIRECONNECT - 重连指定AI服务 + * 格式: AT^AIRECONNECT=service_type + * 示例: AT^AIRECONNECT=2 (重连TTS服务) + * AT^AIRECONNECT=0 (重连ASR服务) + * AT^AIRECONNECT=1 (重连Chat服务) + */ +static uint32_t at_ai_reconnect(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(strCommonCmd); + + printf("[AI_AT] 执行 AT^AIRECONNECT - 重连AI服务\n"); + + if (!g_ai_at_initialized) { + printf("[AI_AT] AI服务未初始化\n"); + return ERRCODE_FAIL; + } + + ai_service_type_t service_type = AI_SERVICE_TTS; // 默认重连TTS + + // 解析参数 + if (para != NULL && paraLen > 0) { + uint8_t argv[AI_AT_MAX_ARGC][AI_AT_MAX_ARGV_LEN] = { 0 }; + uint32_t argc = 0; + + int ret = ConvertCmdToParam(para, paraLen, argv, &argc); + if (ret == AI_AT_SUCCESS && argc > 0) { + int service_id = atoi((char*)argv[0]); + if (service_id >= 0 && service_id < AI_SERVICE_COUNT) { + service_type = (ai_service_type_t)service_id; + } else { + printf("[AI_AT] 无效的服务类型: %d (有效范围: 0-2)\n", service_id); + return ERRCODE_FAIL; + } + } + } + + const char* service_names[] = {"ASR", "Chat", "TTS"}; + printf("[AI_AT] 重连服务: %s (类型: %d)\n", service_names[service_type], service_type); + + // 获取当前连接状态 + ai_connection_state_t current_state = ai_connection_manager_get_connection_state(service_type); + printf("[AI_AT] 当前连接状态: %d\n", current_state); + + // 执行重连 + ai_error_t ret = ai_connection_manager_reconnect(service_type); + if (ret != AI_OK) { + printf("[AI_AT] 重连请求失败: %d\n", ret); + return ERRCODE_FAIL; + } + + printf("[AI_AT] 重连请求已发送,等待连接建立...\n"); + + // 等待连接建立 + bool connected = false; + for (int i = 0; i < 20; i++) { // 最多等待10秒 +#ifndef __TEST_MODE__ + osDelay(500); // 等待500ms +#endif + ai_connection_state_t state = ai_connection_manager_get_connection_state(service_type); + printf("[AI_AT] 等待中... 状态: %d\n", state); + + if (state == AI_CONN_STATE_CONNECTED) { + connected = true; + break; + } + } + + if (connected) { + printf("[AI_AT] %s服务重连成功!\n", service_names[service_type]); + return ERRCODE_SUCC; + } else { + ai_connection_state_t final_state = ai_connection_manager_get_connection_state(service_type); + printf("[AI_AT] %s服务重连失败,最终状态: %d\n", service_names[service_type], final_state); + return ERRCODE_FAIL; + } +} + /* ================================ AT命令表定义 ================================ */ static const at_cmd_table_t g_ai_cmd[] = { @@ -767,6 +884,7 @@ static const at_cmd_table_t g_ai_cmd[] = { { (char *)"AT^AIALLTEST", at_ai_all_test, (char *)"Run comprehensive test" }, { (char *)"AT^AISTATS", at_ai_stats, (char *)"Show test statistics" }, { (char *)"AT^AICLEANUP", at_ai_cleanup, (char *)"Cleanup AI services" }, + { (char *)"AT^AIRECONNECT", at_ai_reconnect, (char *)"service_type (0=ASR,1=Chat,2=TTS)" }, }; /* ================================ 公共接口 ================================ */ @@ -824,9 +942,9 @@ int main(void) printf("\n6. 单独测试各个服务:\n"); - printf("\n6.1 测试 AT^AIASR=3000 - ASR语音识别\n"); - uint8_t asr_para[] = "3000"; - result = at_ai_asr_test(asr_para, sizeof(asr_para) - 1, NULL); + printf("\n6.1 测试 AT^AIASR=5000 - 详细ASR测试\n"); + uint8_t asr_para1[] = "5000"; + result = at_ai_asr_test(asr_para1, sizeof(asr_para1) - 1, NULL); printf(" 结果: %s\n", result == ERRCODE_SUCC ? "✅ 成功" : "❌ 失败"); printf("\n6.2 测试 AT^AICHAT - Chat对话服务\n"); diff --git a/code/sdk/zhihui/ai/include/ai_asr_service.h b/code/sdk/zhihui/ai/include/ai_asr_service.h index 29bc7495c..3eea6514f 100644 --- a/code/sdk/zhihui/ai/include/ai_asr_service.h +++ b/code/sdk/zhihui/ai/include/ai_asr_service.h @@ -32,6 +32,7 @@ typedef enum { */ typedef struct { char language[16]; // 语言代码,如"zh-CN" + char format[16]; // 音频格式,如"pcm" bool enable_vad; // 是否启用语音活动检测 bool enable_punctuation; // 是否启用标点符号 bool enable_partial_results; // 是否启用部分结果 @@ -69,6 +70,16 @@ typedef struct { /* ================================ ASR服务接口 ================================ */ +/** + * @brief 初始化ASR WebSocket URL + * @param url_buf 输出缓冲区,存储构建的URL + * @param server_url 服务器URL + * @return bool true表示成功,false表示失败 + * @note 从设备令牌构建完整的ASR WebSocket URL + * 格式:ws://amp.raisound.com/asr/ws?dt={token}&platform=aliyun + */ +bool asr_api_ws_url_init(char *url_buf,const char *server_url); + /** * @brief 初始化ASR服务 * @param context ASR服务上下文 diff --git a/code/sdk/zhihui/ai/include/ai_chat_service.h b/code/sdk/zhihui/ai/include/ai_chat_service.h index bed5fa35e..3bd6f67f3 100644 --- a/code/sdk/zhihui/ai/include/ai_chat_service.h +++ b/code/sdk/zhihui/ai/include/ai_chat_service.h @@ -70,6 +70,16 @@ typedef struct { /* ================================ Chat服务接口 ================================ */ +/** + * @brief 初始化Chat WebSocket URL + * @param url_buf 输出缓冲区,存储构建的URL + * @param server_url 服务器URL + * @param type_ai 服务类型 + * @return bool true表示成功,false表示失败 + * @note 从设备令牌构建完整的Chat WebSocket URL + */ +bool chat_api_ws_url_init(char * url_buf,const char *server_url,uint8_t type_ai); + /** * @brief 初始化Chat服务 * @param context Chat服务上下文 diff --git a/code/sdk/zhihui/ai/include/ai_connection_manager.h b/code/sdk/zhihui/ai/include/ai_connection_manager.h index cd5112cb1..778c0bdb3 100644 --- a/code/sdk/zhihui/ai/include/ai_connection_manager.h +++ b/code/sdk/zhihui/ai/include/ai_connection_manager.h @@ -140,6 +140,16 @@ ai_error_t ai_connection_manager_stop_all(void); ai_error_t ai_connection_manager_send_data(ai_service_type_t service_type, const char *data, size_t length); +/** + * @brief 发送原始数据到指定服务 + * @param service_type 服务类型 + * @param data 数据指针 + * @param length 数据长度 + * @return ai_error_t 错误码 + */ +ai_error_t ai_connection_manager_send_rawdata(ai_service_type_t service_type, + const void *data, size_t length); + /** * @brief 获取全局状态 * @return ai_global_state_t 全局状态 diff --git a/code/sdk/zhihui/ai/include/ai_core_types.h b/code/sdk/zhihui/ai/include/ai_core_types.h index a9503425b..49b3d50d2 100644 --- a/code/sdk/zhihui/ai/include/ai_core_types.h +++ b/code/sdk/zhihui/ai/include/ai_core_types.h @@ -26,7 +26,32 @@ extern "C" { #include "cmsis_os2.h" /* WebSocket相关头文件 */ #ifdef SUPPORT_ZHIHUI_AI_SERVICE +// 宏定义:USE_ZHIHUI_WEBSOCKET = 1 使用新的zhihui接口,= 0 使用海思原有接口 +#define USE_ZHIHUI_WEBSOCKET 0 +#ifndef USE_ZHIHUI_WEBSOCKET +#define USE_ZHIHUI_WEBSOCKET 0 // 默认使用原有接口 +#endif + +#if USE_ZHIHUI_WEBSOCKET + #include "zhihui_websocket.h" +// 兼容net_websocket.h中的函数定义 #include "net_websocket.h" + // 适配宏:将net_websocket.h的API映射到zhihui_websocket.h + #define WebSocketClientConstructor zhihui_WebSocketClientConstructor + #define WebSocketClientSetProtocol zhihui_WebSocketClientSetProtocol + #define WebSocketClientConnect zhihui_WebSocketClientConnect + #define WebSocketClientSendText zhihui_WebSocketClientSendText + #define WebSocketClientSendBinStart zhihui_WebSocketClientSendBinStart + #define WebSocketClientSendBinContinue zhihui_WebSocketClientSendBinContinue + #define WebSocketClientSendBinEnd zhihui_WebSocketClientSendBinEnd + #define WebSocketClientClose zhihui_WebSocketClientClose + #define WebSocketClientIsConnected zhihui_WebSocketClientIsConnected + #ifdef WEBSOCKET_SSL_ENABLE + #define WebSocketClientSetServerCert zhihui_WebSocketClientSetServerCert + #endif +#else #include "net_websocket.h" +#endif + /* 定义WebSocket客户端类型为文件描述符 */ typedef int WebSocketClient_t; #else @@ -50,7 +75,7 @@ struct WebSocketErrorResult { /* ================================ 基础常量定义 ================================ */ /* 内存优化常量 - LiteOS-M适配 */ -#define AI_MAX_URL_LENGTH 128 // URL最大长度 +#define AI_MAX_URL_LENGTH (128+64) // URL最大长度 #define AI_MAX_SESSION_ID_LENGTH 32 // 会话ID最大长度 #define AI_MAX_TEXT_LENGTH 512 // 文本最大长度(优化) #define AI_MAX_ERROR_MSG_LENGTH 64 // 错误消息最大长度 @@ -90,7 +115,9 @@ typedef enum { AI_ERROR_SERVICE_UNAVAILABLE = -13, // 服务不可用 AI_ERROR_BUFFER_OVERFLOW = -14, // 缓冲区溢出 AI_ERROR_STATE_INVALID = -15, // 状态无效 - AI_ERROR_MUTEX_FAILED = -16 // 互斥锁操作失败 + AI_ERROR_MUTEX_FAILED = -16, // 互斥锁操作失败 + AI_ERROR_MEDIA_INIT = -17, // 媒体服务初始化失败 + AI_ERROR_RESOURCE_LIMIT = -18 // 资源限制错误 } ai_error_t; /* ================================ 服务类型定义 ================================ */ @@ -99,9 +126,18 @@ typedef enum { AI_SERVICE_ASR = 0, // 语音识别服务 AI_SERVICE_CHAT = 1, // 对话服务 AI_SERVICE_TTS = 2, // 语音合成服务 - AI_SERVICE_COUNT = 3 // 服务总数 + AI_SERVICE_COUNT = 3, // 服务总数 + AI_SERVICE_GLOBAL = 99 // 全局服务事件(用于系统级状态变更) } ai_service_type_t; +typedef enum { + AI_TYPE_JIU = 0, + AI_TYPE_DOU = 1, + AI_TYPE_WEN = 2, + AI_TYPE_KIMI = 3, + AI_TYPE_DE = 4, +} ai_type_t; + /* ================================ 连接状态定义 ================================ */ typedef enum { @@ -201,16 +237,22 @@ typedef struct { typedef enum { AI_EVENT_CONNECTION_ESTABLISHED = 0, // 连接建立 AI_EVENT_CONNECTION_LOST = 1, // 连接丢失 - AI_EVENT_DATA_RECEIVED = 2, // 数据接收 - AI_EVENT_ERROR_OCCURRED = 3, // 错误发生 - AI_EVENT_STATE_CHANGED = 4, // 状态变更 - AI_EVENT_AUDIO_START = 5, // 音频开始 - AI_EVENT_AUDIO_END = 6, // 音频结束 - AI_EVENT_RECOGNITION_RESULT = 7, // 识别结果 - AI_EVENT_CHAT_RESPONSE = 8, // 对话响应 - AI_EVENT_TTS_RESULT = 9 // 合成结果 + AI_EVENT_TEXT_DATA_RECEIVED = 2, // 文本数据接收(JSON等) + AI_EVENT_BIN_DATA_RECEIVED = 3, // 二进制数据接收(音频等) + AI_EVENT_ERROR_OCCURRED = 4, // 错误发生 + AI_EVENT_STATE_CHANGED = 5, // 状态变更 + AI_EVENT_AUDIO_START = 6, // 音频开始 + AI_EVENT_AUDIO_END = 7, // 音频结束 + AI_EVENT_RECOGNITION_RESULT = 8, // 识别结果 + AI_EVENT_CHAT_RESPONSE = 9, // 对话响应 + AI_EVENT_TTS_RESULT = 10 // 合成结果 } ai_event_type_t; +/* ================================ 事件数据结构 ================================ */ + +/* 最大事件数据长度 - 控制最大长度限制 */ +#define AI_MAX_EVENT_DATA_SIZE 4096 + typedef struct { ai_event_type_t type; // 事件类型 ai_service_type_t service; // 相关服务 @@ -222,8 +264,11 @@ typedef struct { int error_code; // 错误码 } connection; struct { - const char *data; // 数据指针 + uint8_t *data; // 动态分配的数据指针 size_t length; // 数据长度 + size_t allocated_size; // 实际分配的内存大小 + bool is_finished; // 是否为完整消息 + bool is_final_fragment; // 是否为最后分片 } data; struct { ai_global_state_t old_state; // 旧状态 @@ -236,6 +281,43 @@ typedef struct { } payload; } ai_event_t; +/* ================================ 事件管理函数 ================================ */ + +/** + * @brief 创建数据事件(动态分配内存) + * @param type 事件类型 + * @param service 服务类型 + * @param data 数据指针 + * @param length 数据长度 + * @param is_finished 是否完成 + * @param is_final_fragment 是否最后分片 + * @return ai_event_t* 创建的事件指针,失败返回NULL + */ +ai_event_t* ai_event_create_data(ai_event_type_t type, ai_service_type_t service, + const uint8_t *data, size_t length, + bool is_finished, bool is_final_fragment); + +/** + * @brief 创建控制事件(无数据分配) + * @param type 事件类型 + * @param service 服务类型 + * @return ai_event_t* 创建的事件指针,失败返回NULL + */ +ai_event_t* ai_event_create_control(ai_event_type_t type, ai_service_type_t service); + +/** + * @brief 销毁事件并释放内存 + * @param event 事件指针 + */ +void ai_event_destroy(ai_event_t *event); + +/** + * @brief 复制事件(深拷贝) + * @param src 源事件 + * @return ai_event_t* 复制的事件指针,失败返回NULL + */ +ai_event_t* ai_event_copy(const ai_event_t *src); + /* 事件回调函数类型 */ typedef void (*ai_event_callback_t)(const ai_event_t *event, void *user_data); diff --git a/code/sdk/zhihui/ai/include/ai_services.h b/code/sdk/zhihui/ai/include/ai_services.h index 5a91b5520..bc1d647da 100644 --- a/code/sdk/zhihui/ai/include/ai_services.h +++ b/code/sdk/zhihui/ai/include/ai_services.h @@ -246,6 +246,28 @@ const char* ai_tts_get_voice_name(tts_voice_type_t voice_type); */ const char* ai_services_get_state_string(ai_service_type_t service_type, ai_services_context_t *context); +/* ================================ 音频服务控制接口 ================================ */ + +/** + * @brief 启用ASR录音数据发送 + * @param context 服务上下文 + * @return ai_error_t 错误码 + */ +ai_error_t ai_services_enable_asr_recording(ai_services_context_t *context); + +/** + * @brief 禁用ASR录音数据发送 + * @param context 服务上下文 + * @return ai_error_t 错误码 + */ +ai_error_t ai_services_disable_asr_recording(ai_services_context_t *context); + +/** + * @brief 获取ASR录音状态 + * @return bool 录音状态 + */ +bool ai_services_is_asr_recording_enabled(void); + #ifdef __cplusplus } #endif diff --git a/code/sdk/zhihui/ai/include/ai_tts_service.h b/code/sdk/zhihui/ai/include/ai_tts_service.h index 6f24bfa58..c37221601 100644 --- a/code/sdk/zhihui/ai/include/ai_tts_service.h +++ b/code/sdk/zhihui/ai/include/ai_tts_service.h @@ -43,6 +43,7 @@ typedef enum { */ typedef struct { tts_voice_type_t voice_type; // 语音类型 + char format[16]; // 音频格式,如"pcm" uint8_t volume; // 音量(0-100) uint8_t speed; // 语速(0-100) uint8_t pitch; // 音调(0-100) @@ -53,15 +54,17 @@ typedef struct { /** * @brief TTS合成结果结构体 */ -typedef struct { +typedef struct tts_result { char text[256]; // 合成文本 - uint8_t *audio_data; // 音频数据 + const uint8_t *audio_data; // 🔧 音频数据(const修饰) uint32_t audio_size; // 音频大小 + size_t audio_length; // 🆕 音频数据长度 uint32_t sample_rate; // 采样率 uint16_t channels; // 声道数 uint16_t bits_per_sample; // 位深度 char session_id[32]; // 会话ID uint32_t duration_ms; // 音频时长(毫秒) + uint32_t timestamp; // 🆕 时间戳 bool is_final; // 是否为最终结果 } tts_result_t; @@ -166,10 +169,23 @@ void ai_tts_service_create_default_config(tts_config_t *config); /** * @brief 处理TTS服务响应 * @param context TTS服务上下文 - * @param json_data JSON响应数据 + * @param response JSON响应字符串 + * @return ai_error_t 错误码 + */ +ai_error_t ai_tts_service_handle_response(tts_service_context_t *context, const char *response); + +/** + * @brief 处理TTS音频数据 + * @param context TTS服务上下文 + * @param audio_data 音频数据 + * @param length 数据长度 + * @param is_finished 是否为完整音频 * @return ai_error_t 错误码 */ -ai_error_t ai_tts_service_handle_response(tts_service_context_t *context, const char *json_data); +ai_error_t ai_tts_service_handle_audio_data(tts_service_context_t *context, + const uint8_t *audio_data, + size_t length, + bool is_finished); /** * @brief 构建TTS请求 @@ -192,6 +208,17 @@ ai_error_t ai_tts_service_build_request(const char *text, const char *session_id */ ai_error_t ai_tts_service_build_stop_message(const char *session_id, char *json_buffer, size_t buffer_size); + + +/** + * @brief 初始化TTS服务URL + * @param url_buf 输出缓冲区,存储构建的URL + * @param server_url 服务器URL + * @return bool true表示成功,false表示失败 + * @note 从设备令牌构建完整的TTS服务URL + */ +bool tts_api_ws_url_init(char * url_buf,const char *server_url); + #ifdef __cplusplus } #endif diff --git a/code/sdk/zhihui/ai/src/ai_asr_service.c b/code/sdk/zhihui/ai/src/ai_asr_service.c index 6b605411f..f6ec9d8ad 100644 --- a/code/sdk/zhihui/ai/src/ai_asr_service.c +++ b/code/sdk/zhihui/ai/src/ai_asr_service.c @@ -12,6 +12,7 @@ #include #include #include +#include "cJSON.h" #ifndef __TEST_MODE__ #include "wearable_log.h" @@ -44,6 +45,24 @@ static ai_error_t parse_asr_response(const char *json_data, asr_result_t *result /* ================================ 公共接口实现 ================================ */ +/** + * @brief 初始化ASR WebSocket URL + * @param put_buf 输出缓冲区,存储构建的URL + * @return bool true表示成功,false表示失败 + * @note 从设备令牌构建完整的ASR WebSocket URL + * 格式:ws://amp.raisound.com/asr/ws?dt={token}&platform=aliyun + */ +bool asr_api_ws_url_init(char *url_buf,const char *server_url) +{ + char token[64] = {0}; + if (read_dev_token(token)) { + snprintf(url_buf, AI_MAX_URL_LENGTH, "%s?dt=%s&platform=aliyun", server_url, token); + } else { + snprintf(url_buf, AI_MAX_URL_LENGTH, "%s?platform=aliyun", server_url); + } + return true; +} + ai_error_t ai_asr_service_init(asr_service_context_t *context) { if (context == NULL) { @@ -101,8 +120,9 @@ ai_error_t ai_asr_service_configure(asr_service_context_t *context, const asr_co } memcpy(&context->config, config, sizeof(asr_config_t)); - AI_LOGI("ASR configured: language=%s, vad=%s, punctuation=%s", + AI_LOGI("ASR configured: language=%s, format=%s, vad=%s, punctuation=%s", config->language, + config->format, config->enable_vad ? "enabled" : "disabled", config->enable_punctuation ? "enabled" : "disabled"); @@ -185,50 +205,53 @@ ai_error_t ai_asr_service_send_audio(asr_service_context_t *context, const uint8 return AI_ERROR_STATE_INVALID; } - /* 检查数据大小 */ - if (data_size > MAX_AUDIO_CHUNK_SIZE) { - AI_LOGW("Audio chunk too large: %u bytes, splitting", data_size); + /* 🔧 使用迭代而不是递归来处理大数据分块发送 */ + uint32_t total_offset = 0; + bool state_changed = false; + + while (total_offset < data_size) { + /* 计算当前块的大小 */ + uint32_t remaining = data_size - total_offset; + uint32_t current_chunk_size = (remaining > MAX_AUDIO_CHUNK_SIZE) ? + MAX_AUDIO_CHUNK_SIZE : remaining; - /* 分块发送 */ - uint32_t offset = 0; - while (offset < data_size) { - uint32_t chunk_size = (data_size - offset > MAX_AUDIO_CHUNK_SIZE) ? - MAX_AUDIO_CHUNK_SIZE : (data_size - offset); - - ai_error_t ret = ai_asr_service_send_audio(context, audio_data + offset, chunk_size); - if (ret != AI_OK) { - return ret; - } - offset += chunk_size; + if (remaining > MAX_AUDIO_CHUNK_SIZE) { + AI_LOGW("Audio chunk too large: %u bytes remaining, sending %u bytes", + remaining, current_chunk_size); } - return AI_OK; - } - - /* 构建音频数据消息 */ - ai_buffer_t *buffer = ai_memory_pool_alloc_network(AI_SERVICE_ASR, data_size + 64); - if (buffer == NULL) { - AI_LOGE("Failed to allocate network buffer for audio data"); - return AI_ERROR_MEMORY_ALLOC; - } - - /* 简化的二进制数据发送(实际实现中可能需要Base64编码) */ - memcpy(buffer->buffer, audio_data, data_size); - buffer->used = data_size; - - ai_error_t ret = ai_connection_manager_send_data(AI_SERVICE_ASR, (const char*)buffer->buffer, buffer->used); - - ai_memory_pool_free(buffer); - - if (ret != AI_OK) { - AI_LOGE("Failed to send audio data"); - return ret; - } - - /* 更新状态 */ - if (context->state == ASR_STATE_LISTENING) { - change_asr_state(context, ASR_STATE_RECOGNIZING); + + /* 构建音频数据消息 */ + ai_buffer_t *buffer = ai_memory_pool_alloc_network(AI_SERVICE_ASR, current_chunk_size + 64); + if (buffer == NULL) { + AI_LOGE("Failed to allocate network buffer for audio data"); + return AI_ERROR_MEMORY_ALLOC; + } + + /* 简化的二进制数据发送(实际实现中可能需要Base64编码) */ + memcpy(buffer->buffer, audio_data + total_offset, current_chunk_size); + buffer->used = current_chunk_size; + + ai_error_t ret = ai_connection_manager_send_rawdata(AI_SERVICE_ASR, + (const char*)buffer->buffer, + buffer->used); + + ai_memory_pool_free(buffer); + + if (ret != AI_OK) { + AI_LOGE("Failed to send audio data chunk"); + return ret; + } + + /* 更新状态(只在第一个块时更新) */ + if (!state_changed && context->state == ASR_STATE_LISTENING) { + change_asr_state(context, ASR_STATE_RECOGNIZING); + state_changed = true; + } + + total_offset += current_chunk_size; } + /* 更新活动时间 */ context->last_activity_time = get_current_timestamp(); return AI_OK; @@ -289,6 +312,7 @@ void ai_asr_service_create_default_config(asr_config_t *config) config->enable_partial_results = true; config->silence_timeout_ms = 3000; // 3秒静音超时 config->max_duration_ms = 30000; // 30秒最大录音时长 + strncpy(config->format, "mp3", sizeof(config->format) - 1); strncpy(config->language, "zh-CN", sizeof(config->language) - 1); } @@ -299,7 +323,7 @@ ai_error_t ai_asr_service_handle_response(asr_service_context_t *context, const } AI_LOGI("Handling ASR response: %.100s...", json_data); - + /* 解析ASR响应 */ asr_result_t result; ai_error_t ret = parse_asr_response(json_data, &result); @@ -326,7 +350,39 @@ ai_error_t ai_asr_service_handle_response(asr_service_context_t *context, const return AI_OK; } +/*** + * - StartTranscription (开启asr识别) + + - ```JSON + { + "header": { + "event": "StartTranscription", // 开启事件 + "trace_id": "3fa6b9e6a30b407ea38ad7ae69e6c291" // 随机数,客户端生成 + }, + "payload": { // 目前支持 16 bit 16k 1(mono) pcm 音频流 + "format": "pcm", // 音频格式 + "sample_rate": 16000 // 采样率 + } + } + ``` + +- 音频发送 + + - ```Markdown + BinaryFrame 二进制帧 发送音频流 + ``` +- StopTranscription (停止音频发送) + + - ```JSON + { + "header": { + "event": "StopTranscription", + "trace_id": "3fa6b9e6a30b407ea38ad7ae69e6c291" + } + } + ``` + */ ai_error_t ai_asr_service_build_start_message(const char *session_id, const asr_config_t *config, char *json_buffer, size_t buffer_size) { @@ -335,27 +391,41 @@ ai_error_t ai_asr_service_build_start_message(const char *session_id, const asr_ } /* 构建ASR启动消息 */ + int ret = snprintf(json_buffer, buffer_size, "{" - "\"type\":\"start\"," - "\"session_id\":\"%s\"," - "\"config\":{" - "\"language\":\"%s\"," - "\"enable_vad\":%s," - "\"enable_punctuation\":%s," - "\"enable_partial_results\":%s," - "\"silence_timeout_ms\":%d," - "\"max_duration_ms\":%d" - "}" + "\"header\":{" + "\"event\":\"StartTranscription\"," + "\"trace_id\":\"%s\"" + "}," + "\"payload\":{" + "\"format\":\"pcm\"," + "\"sample_rate\":16000" + "}" "}", - session_id, - config->language, - config->enable_vad ? "true" : "false", - config->enable_punctuation ? "true" : "false", - config->enable_partial_results ? "true" : "false", - config->silence_timeout_ms, - config->max_duration_ms + session_id ); + // int ret = snprintf(json_buffer, buffer_size, + // "{" + // "\"type\":\"start\"," + // "\"session_id\":\"%s\"," + // "\"config\":{" + // "\"language\":\"%s\"," + // "\"enable_vad\":%s," + // "\"enable_punctuation\":%s," + // "\"enable_partial_results\":%s," + // "\"silence_timeout_ms\":%d," + // "\"max_duration_ms\":%d" + // "}" + // "}", + // session_id, + // config->language, + // config->enable_vad ? "true" : "false", + // config->enable_punctuation ? "true" : "false", + // config->enable_partial_results ? "true" : "false", + // config->silence_timeout_ms, + // config->max_duration_ms + // ); if (ret < 0 || ret >= (int)buffer_size) { AI_LOGE("JSON buffer too small for ASR start message"); @@ -370,9 +440,17 @@ ai_error_t ai_asr_service_build_stop_message(const char *session_id, char *json_ if (session_id == NULL || json_buffer == NULL) { return AI_ERROR_INVALID_PARAM; } - - int ret = snprintf(json_buffer, buffer_size, - "{\"type\":\"stop\",\"session_id\":\"%s\"}", session_id); + int ret = snprintf(json_buffer, buffer_size, + "{" + "\"header\":{" + "\"event\":\"StopTranscription\"," + "\"trace_id\":\"%s\"" + "}" + "}", + session_id + ); + // int ret = snprintf(json_buffer, buffer_size, + // "{\"type\":\"stop\",\"session_id\":\"%s\"}", session_id); if (ret < 0 || ret >= (int)buffer_size) { AI_LOGE("JSON buffer too small for ASR stop message"); @@ -415,7 +493,79 @@ static ai_error_t change_asr_state(asr_service_context_t *context, asr_state_t n return AI_OK; } +/* + +- TranscriptionStarted (开启成功) + + - ```JSON + { + "status": 20000000, + "msg": "success", + "task_id": "80228c9f62821ffe3f867e1a5042d887", + "event": "TranscriptionStarted", + "result": null + } + ``` + +- Changed (转写临时结果) + + - ```JSON + { + "status": 20000000, + "msg": "success", + "task_id": "80228c9f62821ffe3f867e1a5042d887", + "event": "Changed", + "result": { + "result": "以", // 识别内容 + "is_end": false, + "sentence_id": "9df197b9a57acc331ae4c334d4c02732", + "start_time": 10290, + "end_time": 10760 + } + } + ``` + +- SentenceEnd (一句话识别结束) + + - ```JSON + { + "status": 20000000, + "msg": "success", + "task_id": "80228c9f62821ffe3f867e1a5042d887", + "event": "SentenceEnd", + "result": { + "result": "嗯。", + "is_end": true, + "sentence_id": "d7e62c6fb0ee847b85d674828797f3f3", + "start_time": 13610, + "end_time": 14520 + } + } + ``` + +- TranscriptionCompleted (本次合成结束) + + - ```JSON + { + "status": 20000000, + "msg": "success", + "task_id": "8d12cc56aa5fcd66a4c8667e20b5db25", + "event": "TranscriptionCompleted", + "result": null + } + ``` + +- Failed (异常事件 收到后需要重新开启) + - ```JSON + { + "status": 40000000, + "event": "Failed", + "msg": "request timeout after 23 seconds.", + "task_id": "5b47d64b3553f75fb84d51e21144bd72" + } + ``` +*/ static ai_error_t parse_asr_response(const char *json_data, asr_result_t *result) { if (json_data == NULL || result == NULL) { @@ -427,35 +577,84 @@ static ai_error_t parse_asr_response(const char *json_data, asr_result_t *result /* 在实际实现中,这里应该使用完整的JSON解析库 */ /* 这里只是一个简化的示例 */ - - /* 查找文本字段 */ - const char *text_start = strstr(json_data, "\"text\":\""); - if (text_start != NULL) { - text_start += 8; // 跳过 "text":" - const char *text_end = strchr(text_start, '"'); - if (text_end != NULL) { - size_t text_len = text_end - text_start; - if (text_len < sizeof(result->text)) { - strncpy(result->text, text_start, text_len); - result->text[text_len] = '\0'; - } - } - } - - /* 查找置信度字段 */ - const char *confidence_start = strstr(json_data, "\"confidence\":"); - if (confidence_start != NULL) { - result->confidence = atof(confidence_start + 13); - } else { - result->confidence = 0.9f; // 默认置信度 + // 使用cJSON解析ASR协议 + cJSON *root = cJSON_Parse(json_data); + if (!root) { + AI_LOGE("Failed to parse ASR JSON response"); + return AI_ERROR_JSON_PARSE; } - - /* 查找是否为最终结果 */ - result->is_final = (strstr(json_data, "\"is_final\":true") != NULL); - - /* 设置时间戳 */ + + // 解析event字段 + cJSON *event_item = cJSON_GetObjectItem(root, "event"); + const char *event = event_item && cJSON_IsString(event_item) ? event_item->valuestring : NULL; + + // 解析status字段 + cJSON *status_item = cJSON_GetObjectItem(root, "status"); + int status = status_item && cJSON_IsNumber(status_item) ? status_item->valueint : 0; + + // 解析result对象 + cJSON *result_obj = cJSON_GetObjectItem(root, "result"); + + // 默认赋值 + result->text[0] = '\0'; + result->confidence = 0.9f; + result->is_final = false; result->start_time_ms = 0; result->end_time_ms = get_current_timestamp(); + + if (event) { + if (strcmp(event, "Changed") == 0 || strcmp(event, "SentenceEnd") == 0) { + if (result_obj && cJSON_IsObject(result_obj)) { + // 解析result字段 + cJSON *text_item = cJSON_GetObjectItem(result_obj, "result"); + if (text_item && cJSON_IsString(text_item)) { + strncpy(result->text, text_item->valuestring, sizeof(result->text) - 1); + result->text[sizeof(result->text) - 1] = '\0'; + } + // 解析is_end字段 + cJSON *is_end_item = cJSON_GetObjectItem(result_obj, "is_end"); + if (is_end_item && cJSON_IsBool(is_end_item)) { + result->is_final = cJSON_IsTrue(is_end_item); + } + // 解析start_time和end_time + cJSON *start_time_item = cJSON_GetObjectItem(result_obj, "start_time"); + cJSON *end_time_item = cJSON_GetObjectItem(result_obj, "end_time"); + if (start_time_item && cJSON_IsNumber(start_time_item)) { + result->start_time_ms = start_time_item->valueint; + } + if (end_time_item && cJSON_IsNumber(end_time_item)) { + result->end_time_ms = end_time_item->valueint; + } + } + } else if (strcmp(event, "TranscriptionStarted") == 0) { + // 启动事件,无识别内容 + result->text[0] = '\0'; + result->is_final = false; + } else if (strcmp(event, "TranscriptionCompleted") == 0) { + // 识别完成,无内容 + result->text[0] = '\0'; + result->is_final = true; + } else if (strcmp(event, "Failed") == 0) { + // 失败事件 + result->text[0] = '\0'; + result->is_final = true; + } + } + + // // 可选:解析置信度字段(如果协议有) + // cJSON *confidence_item = NULL; + // if (result_obj && cJSON_IsObject(result_obj)) { + // confidence_item = cJSON_GetObjectItem(result_obj, "confidence"); + // } + // if (confidence_item && cJSON_IsNumber(confidence_item)) { + // result->confidence = (float)confidence_item->valuedouble; + // } else { + // result->confidence = 0.9f; + // } + + cJSON_Delete(root); + /* 查找文本字段 */ + AI_LOGI("Parsed ASR result: text='%s', confidence=%.2f, final=%s", result->text, result->confidence, result->is_final ? "true" : "false"); diff --git a/code/sdk/zhihui/ai/src/ai_chat_service.c b/code/sdk/zhihui/ai/src/ai_chat_service.c index 1808b1eae..5471c0427 100644 --- a/code/sdk/zhihui/ai/src/ai_chat_service.c +++ b/code/sdk/zhihui/ai/src/ai_chat_service.c @@ -44,6 +44,76 @@ static ai_error_t parse_chat_response(const char *json_data, chat_message_t *mes /* ================================ 公共接口实现 ================================ */ + +bool chat_api_ws_url_init(char * url_buf,const char *server_url,uint8_t type_ai) +{ + char token[64]={0}; + if(read_dev_token(token)) + { + if(type_ai==AI_TYPE_JIU) + { + snprintf(url_buf,AI_MAX_URL_LENGTH,"%s?dt=%s&platform=chinamobiledevice&bot_type=jiutian-lan",server_url,token); + } + else if(type_ai==AI_TYPE_DOU) + { + snprintf(url_buf,AI_MAX_URL_LENGTH,"%s?dt=%s&platform=huoshan",server_url,token); + } + else if(type_ai==AI_TYPE_WEN) + { + snprintf(url_buf,AI_MAX_URL_LENGTH,"%s?dt=%s&platform=baidu",server_url,token); + } + else if(type_ai==AI_TYPE_KIMI) + { + snprintf(url_buf,AI_MAX_URL_LENGTH,"%s?dt=%s&platform=moonshot",server_url,token); + } + else if(type_ai==AI_TYPE_DE) + { + snprintf(url_buf,AI_MAX_URL_LENGTH,"%s?dt=%s&platform=huoshan&model=ep-20250205103849-582m7",server_url,token); + } + else + { + snprintf(url_buf,AI_MAX_URL_LENGTH,"%s?dt=%s&platform=aliyun",server_url,token); + } + + } + else + { + char * Service_url="118.145.158.207:22080"; + + char device_id[128]={0}; + get_dev_id(device_id); + + char * product_id="1927646275585744898"; + + if(type_ai==AI_TYPE_JIU) + { + snprintf(url_buf,AI_MAX_URL_LENGTH,"%s?device_id=%s&product_id=%s&platform=chinamobiledevice&bot_type=jiutian-lan",server_url,device_id,product_id); + } + else if(type_ai==AI_TYPE_DOU) + { + snprintf(url_buf,AI_MAX_URL_LENGTH,"%s?platform=huoshan",server_url); + } + else if(type_ai==AI_TYPE_WEN) + { + snprintf(url_buf,AI_MAX_URL_LENGTH,"%s?platform=baidu",server_url); + } + else if(type_ai==AI_TYPE_KIMI) + { + snprintf(url_buf,AI_MAX_URL_LENGTH,"%s?platform=moonshot",server_url); + } + else if(type_ai==AI_TYPE_DE) + { + snprintf(url_buf,AI_MAX_URL_LENGTH,"%s?platform=huoshan&model=ep-20250205103849-582m7",server_url); + } + else + { + snprintf(url_buf,AI_MAX_URL_LENGTH,"%s?platform=aliyun",server_url); + } + } + + return true; + +} ai_error_t ai_chat_service_init(chat_service_context_t *context) { if (context == NULL) { diff --git a/code/sdk/zhihui/ai/src/ai_connection_manager.c b/code/sdk/zhihui/ai/src/ai_connection_manager.c index 8097b1be6..f7d73859d 100644 --- a/code/sdk/zhihui/ai/src/ai_connection_manager.c +++ b/code/sdk/zhihui/ai/src/ai_connection_manager.c @@ -32,6 +32,10 @@ static ai_connection_manager_t g_connection_manager = {0}; static bool g_manager_initialized = false; +/* WebSocket连接计数器和限制 */ +static int g_active_websocket_count = 0; +#define MAX_CONCURRENT_WEBSOCKETS 4 // 最大并发WebSocket连接数 + /* 服务名称映射表 */ static const char* g_service_names[AI_SERVICE_COUNT] = { "ASR", "Chat", "TTS" @@ -49,12 +53,17 @@ static ai_error_t connection_thread_create(ai_service_type_t service_type); static void connection_thread_entry(void *argument); static void worker_thread_entry(void *argument); static ai_error_t websocket_connect(ai_connection_info_t *conn_info); +static uint32_t get_current_timestamp(void); +static ai_connection_info_t* find_connection_by_websocket(int websocket_fd); static void websocket_on_connected(int client); static void websocket_on_message(int client, const char *data, size_t length, bool isFinished); static void websocket_on_error(int client, struct WebSocketErrorResult errorResult); static void websocket_on_disconnected(int client); static ai_error_t update_global_state(void); static void send_event(ai_event_type_t type, ai_service_type_t service, const void *data); +static void send_data_event(ai_event_type_t type, ai_service_type_t service, const char *data, size_t length, bool is_finished, bool is_final_fragment); // 🆕 +static bool detect_json_data(const char *data, size_t length); // 🆕 +static void handle_fragmented_message(ai_connection_info_t *conn_info, const char *data, size_t length, ai_event_type_t event_type); // 🆕 static uint32_t get_current_timestamp(void); /* static ai_error_t build_service_url(ai_service_type_t service_type, char *url_buffer, size_t buffer_size); */ @@ -62,6 +71,56 @@ static uint32_t get_current_timestamp(void); static void heartbeat_timer_callback(void *argument); #endif +/* WebSocket连接管理辅助函数 */ +static bool can_create_websocket_connection(void); +static void cleanup_websocket_connection(ai_connection_info_t *conn_info); + +/* 在连接失败或断开时,确保完全清理资源 */ +static void cleanup_websocket_connection(ai_connection_info_t *conn_info) +{ + if (!conn_info) { + return; + } + + if (conn_info->websocket >= 0) { + AI_LOGI("Cleaning up WebSocket %d for service %s", + conn_info->websocket, g_service_names[conn_info->service_type]); + +#ifndef __TEST_MODE__ + // 关闭WebSocket连接 + WebSocketClientClose(conn_info->websocket); + + // 等待资源释放 - 关键步骤! + osDelay(200); // 200ms延迟让系统释放socket资源 +#endif + + // 重置连接信息 + conn_info->websocket = -1; + conn_info->state = AI_CONN_STATE_DISCONNECTED; + + // 减少活跃连接计数 + if (g_active_websocket_count > 0) { + g_active_websocket_count--; + AI_LOGI("Active WebSocket connections: %d/%d", + g_active_websocket_count, MAX_CONCURRENT_WEBSOCKETS); + } + } +} + +/* 检查是否可以创建新的WebSocket连接 */ +static bool can_create_websocket_connection(void) +{ + if (g_active_websocket_count >= MAX_CONCURRENT_WEBSOCKETS) { + AI_LOGW("Too many active WebSocket connections: %d/%d", + g_active_websocket_count, MAX_CONCURRENT_WEBSOCKETS); + return false; + } + + AI_LOGI("WebSocket connection check passed: %d/%d active connections", + g_active_websocket_count, MAX_CONCURRENT_WEBSOCKETS); + return true; +} + /* ================================ 公共接口实现 ================================ */ ai_error_t ai_connection_manager_init(const ai_connection_manager_config_t *config) @@ -96,8 +155,8 @@ ai_error_t ai_connection_manager_init(const ai_connection_manager_config_t *conf return AI_ERROR_MEMORY_ALLOC; } - /* 创建事件队列 */ - g_connection_manager.event_queue = osMessageQueueNew(16, sizeof(ai_event_t), NULL); + /* 创建事件队列 - 🆕 现在存储事件指针,大幅减少内存使用 */ + g_connection_manager.event_queue = osMessageQueueNew(16, sizeof(ai_event_t*), NULL); if (g_connection_manager.event_queue == NULL) { AI_LOGE("Failed to create event queue"); osMutexDelete(g_connection_manager.global_mutex); @@ -123,13 +182,19 @@ ai_error_t ai_connection_manager_init(const ai_connection_manager_config_t *conf /* 设置服务URL */ switch (i) { case AI_SERVICE_ASR: - strncpy(conn->url, config->connection_config.asr_url, AI_MAX_URL_LENGTH - 1); + asr_api_ws_url_init(conn->url,config->connection_config.asr_url); + // strncpy(conn->url, config->connection_config.asr_url, AI_MAX_URL_LENGTH - 1); + printf("asr_url: %s\n", conn->url); break; case AI_SERVICE_CHAT: - strncpy(conn->url, config->connection_config.chat_url, AI_MAX_URL_LENGTH - 1); + chat_api_ws_url_init(conn->url,config->connection_config.chat_url,AI_TYPE_JIU); + // strncpy(conn->url, config->connection_config.chat_url, AI_MAX_URL_LENGTH - 1); + printf("chat_url: %s\n", conn->url); break; case AI_SERVICE_TTS: - strncpy(conn->url, config->connection_config.tts_url, AI_MAX_URL_LENGTH - 1); + tts_api_ws_url_init(conn->url,config->connection_config.tts_url); + // strncpy(conn->url, config->connection_config.tts_url, AI_MAX_URL_LENGTH - 1); + printf("tts_url: %s\n", conn->url); break; } @@ -255,13 +320,8 @@ ai_error_t ai_connection_manager_destroy(void) } #endif - /* 关闭WebSocket连接 */ - if (conn->websocket >= 0) { -#ifndef __TEST_MODE__ - WebSocketClientClose(conn->websocket); -#endif - conn->websocket = -1; - } + /* 清理所有WebSocket连接 */ + cleanup_websocket_connection(conn); } #ifndef __TEST_MODE__ @@ -278,6 +338,10 @@ ai_error_t ai_connection_manager_destroy(void) /* 重置状态 */ memset(&g_connection_manager, 0, sizeof(g_connection_manager)); + + /* 重置连接计数器 */ + g_active_websocket_count = 0; + g_manager_initialized = false; AI_LOGI("Connection manager destroyed"); @@ -292,25 +356,16 @@ ai_error_t ai_connection_manager_start_all(void) AI_LOGI("[DEBUG] ai_connection_manager_start_all: Entry point"); - AI_LOGI("[DEBUG] Setting global state to CONNECTING..."); g_connection_manager.global_state = AI_STATE_CONNECTING; - AI_LOGI("[DEBUG] Global state set successfully"); - - AI_LOGI("[DEBUG] Setting is_running to true..."); g_connection_manager.is_running = true; - AI_LOGI("[DEBUG] is_running set successfully"); /* 记录连接开始时间 */ - AI_LOGI("[DEBUG] Getting current timestamp..."); uint32_t start_time = get_current_timestamp(); AI_LOGI("[DEBUG] Current timestamp: %u", start_time); - AI_LOGI("[DEBUG] Setting last_connect_timestamp..."); g_connection_manager.metrics.last_connect_timestamp = start_time; - AI_LOGI("[DEBUG] last_connect_timestamp set successfully"); /* 并发启动所有连接线程 */ - AI_LOGI("[DEBUG] Starting connection thread creation loop..."); AI_LOGI("[DEBUG] AI_SERVICE_COUNT = %d", AI_SERVICE_COUNT); for (int i = 0; i < AI_SERVICE_COUNT; i++) { @@ -327,14 +382,11 @@ ai_error_t ai_connection_manager_start_all(void) AI_LOGI("[DEBUG] Thread created successfully for service %s", g_service_names[i]); #ifndef __TEST_MODE__ - AI_LOGI("[DEBUG] Calling osDelay(100)..."); /* 给线程一些时间启动,避免竞态条件 */ osDelay(100); // 100ms延迟 - AI_LOGI("[DEBUG] osDelay(100) completed"); #endif } - AI_LOGI("[DEBUG] All connection threads created, checking heartbeat timer..."); #ifndef __TEST_MODE__ /* 启动心跳定时器 */ @@ -344,13 +396,9 @@ ai_error_t ai_connection_manager_start_all(void) g_connection_manager.config.keepalive_interval_ms); osTimerStart(g_connection_manager.heartbeat_timer, g_connection_manager.config.keepalive_interval_ms); - AI_LOGI("[DEBUG] Heartbeat timer started successfully"); - } else { - AI_LOGI("[DEBUG] Heartbeat timer is NULL, skipping"); - } + } #endif - AI_LOGI("[DEBUG] ai_connection_manager_start_all: About to return AI_OK"); AI_LOGI("All connection threads started"); return AI_OK; } @@ -399,6 +447,65 @@ ai_error_t ai_connection_manager_stop_all(void) AI_LOGI("All connections stopped"); return AI_OK; } +ai_error_t ai_connection_manager_send_rawdata(ai_service_type_t service_type, + const void *data, size_t length) +{ + if (!g_manager_initialized) { + return AI_ERROR_NOT_INITIALIZED; + } + + if (service_type >= AI_SERVICE_COUNT || data == NULL || length == 0) { + return AI_ERROR_INVALID_PARAM; + } + + ai_connection_info_t *conn = &g_connection_manager.connections[service_type]; + +#ifndef __TEST_MODE__ + if (conn->mutex != NULL) { + osMutexAcquire(conn->mutex, osWaitForever); + } +#endif + + ai_error_t result = AI_OK; + + if (conn->state != AI_CONN_STATE_CONNECTED || conn->websocket < 0) { + AI_LOGW("Service %s not connected, cannot send raw data", g_service_names[service_type]); + result = AI_ERROR_SERVICE_UNAVAILABLE; + } else { + // 检查缓冲区空间 + if (conn->send_buffer != NULL && length <= conn->send_buffer->size) { + memcpy(conn->send_buffer->buffer, data, length); + conn->send_buffer->used = length; + +#ifndef __TEST_MODE__ + // 发送二进制数据 + int ret = WebSocketClientSendBinStart(conn->websocket, (const char*)conn->send_buffer->buffer, length); + if (ret < 0) { + AI_LOGE("Failed to send raw data to service %s", g_service_names[service_type]); + result = AI_ERROR_WEBSOCKET; + } else { + // 更新统计 + g_connection_manager.metrics.total_bytes_sent += length; + conn->last_activity_time = get_current_timestamp(); + } +#else + AI_LOGI("TEST: Sending %lu bytes (raw) to service %s", (unsigned long)length, g_service_names[service_type]); + g_connection_manager.metrics.total_bytes_sent += length; +#endif + } else { + AI_LOGE("Send buffer too small for service %s (raw)", g_service_names[service_type]); + result = AI_ERROR_BUFFER_OVERFLOW; + } + } + +#ifndef __TEST_MODE__ + if (conn->mutex != NULL) { + osMutexRelease(conn->mutex); + } +#endif + + return result; +} ai_error_t ai_connection_manager_send_data(ai_service_type_t service_type, const char *data, size_t length) @@ -442,7 +549,7 @@ ai_error_t ai_connection_manager_send_data(ai_service_type_t service_type, conn->last_activity_time = get_current_timestamp(); } #else - AI_LOGI("TEST: Sending %zu bytes to service %s", length, g_service_names[service_type]); + AI_LOGI("TEST: Sending %lu bytes to service %s", (unsigned long)length, g_service_names[service_type]); g_connection_manager.metrics.total_bytes_sent += length; #endif } else { @@ -498,6 +605,53 @@ const ai_connect_metrics_t* ai_connection_manager_get_metrics(void) return &g_connection_manager.metrics; } +ai_error_t ai_connection_manager_reconnect(ai_service_type_t service_type) +{ + if (!g_manager_initialized) { + return AI_ERROR_NOT_INITIALIZED; + } + + if (service_type >= AI_SERVICE_COUNT) { + return AI_ERROR_INVALID_PARAM; + } + + ai_connection_info_t *conn = &g_connection_manager.connections[service_type]; + + AI_LOGI("Manual reconnect requested for service %s", g_service_names[service_type]); + + /* 清理现有连接 */ + cleanup_websocket_connection(conn); + + /* 重置重试计数器 */ + conn->retry_count = 0; + + /* 重置连接状态为断开,让连接线程重新尝试 */ + conn->state = AI_CONN_STATE_DISCONNECTED; + + /* 更新连接状态矩阵 */ + switch (service_type) { + case AI_SERVICE_ASR: + g_connection_manager.connection_matrix.asr_connected = 0; + break; + case AI_SERVICE_CHAT: + g_connection_manager.connection_matrix.chat_connected = 0; + break; + case AI_SERVICE_TTS: + g_connection_manager.connection_matrix.tts_connected = 0; + break; + case AI_SERVICE_COUNT: + case AI_SERVICE_GLOBAL: + break; + } + + /* 更新全局状态 */ + update_global_state(); + + AI_LOGI("Service %s reset for reconnection", g_service_names[service_type]); + + return AI_OK; +} + ai_error_t ai_connection_manager_wait_ready(ai_service_type_t service_type, uint32_t timeout_ms) { if (!g_manager_initialized || service_type >= AI_SERVICE_COUNT) { @@ -627,11 +781,10 @@ static ai_error_t connection_thread_create(ai_service_type_t service_type) AI_LOGI("[DEBUG] Connection info pointer: %p", conn); #ifndef __TEST_MODE__ - AI_LOGI("[DEBUG] Creating osThreadAttr_t structure..."); /* 使用更保守的线程配置以避免创建失败 */ osThreadAttr_t thread_attr = { - .stack_size = 2048, // 减少栈大小到2KB + .stack_size = 8192, // 🔧 增加栈大小到8KB以确保WebSocket操作有足够空间 .name = "connection_thread", .priority = osPriorityNormal // 使用标准优先级 }; @@ -649,12 +802,10 @@ static ai_error_t connection_thread_create(ai_service_type_t service_type) thread_attr.stack_size, (int)thread_attr.priority); AI_LOGI("[DEBUG] Original config priority was: %d", g_connection_manager.config.connection_thread_priority); - AI_LOGI("[DEBUG] Skipping thread name setting to avoid exception..."); AI_LOGI("[DEBUG] service_type=%d, service_name=%s", service_type, g_service_names[service_type]); /* 临时禁用线程名称设置以避免异常 */ - AI_LOGI("[DEBUG] Calling osThreadNew..."); AI_LOGI("[DEBUG] Parameters: entry=%p, argument=%p, attr=%p", connection_thread_entry, (void*)service_type, &thread_attr); @@ -665,8 +816,6 @@ static ai_error_t connection_thread_create(ai_service_type_t service_type) AI_LOGI("[DEBUG] osThreadNew returned: %p", conn->thread_id); if (conn->thread_id == NULL) { - AI_LOGE("[DEBUG] Thread creation failed, trying with minimal configuration..."); - /* 尝试使用最小配置 */ osThreadAttr_t minimal_attr = { .stack_size = 1024, // 最小栈大小1KB @@ -714,11 +863,13 @@ static void connection_thread_entry(void *argument) { AI_LOGI("[DEBUG] connection_thread_entry: Entry, argument=%p", argument); - AI_LOGI("[DEBUG] Converting argument to service_type..."); + /* 🔧 栈使用监控 - 在栈上分配一个标记来检测栈使用 */ + volatile uint32_t stack_marker = 0xDEADBEEF; + AI_LOGI("[DEBUG] Stack marker address: %p", &stack_marker); + ai_service_type_t service_type = (ai_service_type_t)(uintptr_t)argument; AI_LOGI("[DEBUG] service_type=%d", service_type); - AI_LOGI("[DEBUG] Getting connection info..."); ai_connection_info_t *conn = &g_connection_manager.connections[service_type]; AI_LOGI("[DEBUG] Connection info pointer: %p", conn); @@ -751,6 +902,9 @@ static void connection_thread_entry(void *argument) case AI_SERVICE_COUNT: /* 不应该到达这里 */ break; + case AI_SERVICE_GLOBAL: + /* 全局服务不需要更新连接矩阵 */ + break; } update_global_state(); @@ -771,6 +925,9 @@ static void connection_thread_entry(void *argument) conn->retry_count, g_connection_manager.config.max_retry_count); + /* 使用新的清理函数替换原有的清理逻辑 */ + cleanup_websocket_connection(conn); + /* 为了安全,限制重试次数为1次 */ if (conn->retry_count >= 1) { AI_LOGW("Service %s connection disabled for safety after %d attempts", @@ -806,14 +963,17 @@ static void worker_thread_entry(void *argument) while (g_connection_manager.is_running) { #ifndef __TEST_MODE__ - ai_event_t event; - osStatus_t status = osMessageQueueGet(g_connection_manager.event_queue, &event, NULL, 1000); + ai_event_t *event_ptr; // 声明一个指针来接收事件 + osStatus_t status = osMessageQueueGet(g_connection_manager.event_queue, &event_ptr, NULL, 1000); if (status == osOK) { /* 处理事件 */ if (g_connection_manager.config.event_callback != NULL) { - g_connection_manager.config.event_callback(&event, g_connection_manager.config.user_data); + // 调用回调函数,传入事件指针 + g_connection_manager.config.event_callback(event_ptr, g_connection_manager.config.user_data); } + // 释放事件内存 + ai_event_destroy(event_ptr); } #else /* 测试模式下简单延时 */ @@ -842,6 +1002,12 @@ static ai_error_t websocket_connect(ai_connection_info_t *conn_info) return AI_ERROR_NOT_INITIALIZED; } + /* 检查是否可以创建新连接 */ + if (!can_create_websocket_connection()) { + AI_LOGE("Cannot create WebSocket connection: too many active connections"); + return AI_ERROR_RESOURCE_LIMIT; + } + AI_LOGI("Connecting to %s: %s", g_service_names[conn_info->service_type], conn_info->url); #ifndef __TEST_MODE__ @@ -855,15 +1021,21 @@ static ai_error_t websocket_connect(ai_connection_info_t *conn_info) return AI_ERROR_MEMORY_ALLOC; } - AI_LOGI("WebSocket client created successfully, fd=%d", conn_info->websocket); + /* 增加活跃连接计数 */ + g_active_websocket_count++; + AI_LOGI("WebSocket client created successfully, fd=%d for service %s (active: %d/%d)", + conn_info->websocket, g_service_names[conn_info->service_type], + g_active_websocket_count, MAX_CONCURRENT_WEBSOCKETS); + conn_info->state = AI_CONN_STATE_CONNECTING; int ret = WebSocketClientConnect(conn_info->websocket, conn_info->url); if (ret != 0) { AI_LOGE("WebSocket connection failed: %d", ret); - WebSocketClientClose(conn_info->websocket); - conn_info->websocket = -1; - conn_info->state = AI_CONN_STATE_DISCONNECTED; + + /* 使用新的清理函数 */ + cleanup_websocket_connection(conn_info); + return AI_ERROR_CONNECTION_FAILED; } @@ -872,24 +1044,30 @@ static ai_error_t websocket_connect(ai_connection_info_t *conn_info) /* 测试模式下模拟连接成功 */ conn_info->websocket = 1; // 模拟WebSocket文件描述符 conn_info->state = AI_CONN_STATE_CONNECTED; + g_active_websocket_count++; // 测试模式也要计数 AI_LOGI("TEST: Connected to %s", conn_info->url); #endif - conn_info->connect_time_ms = get_current_timestamp(); - conn_info->last_activity_time = conn_info->connect_time_ms; - conn_info->retry_count = 0; - return AI_OK; } /* 通过WebSocket客户端fd查找连接信息 */ static ai_connection_info_t* find_connection_by_websocket(int websocket_fd) { + AI_LOGI("[DEBUG] Looking for connection with WebSocket fd=%d", websocket_fd); + for (int i = 0; i < AI_SERVICE_COUNT; i++) { + AI_LOGI("[DEBUG] Checking service %d (%s): websocket=%d, state=%d", + i, g_service_names[i], g_connection_manager.connections[i].websocket, + g_connection_manager.connections[i].state); + if (g_connection_manager.connections[i].websocket == websocket_fd) { + AI_LOGI("[DEBUG] Found matching connection for service %s", g_service_names[i]); return &g_connection_manager.connections[i]; } } + + AI_LOGE("[DEBUG] No connection found for WebSocket fd=%d", websocket_fd); return NULL; } @@ -933,8 +1111,6 @@ static void websocket_on_connected(int client) static void websocket_on_message(int client, const char *data, size_t length, bool isFinished) { - (void)isFinished; // 暂时不处理分片消息 - /* 安全检查:确保管理器已初始化 */ if (!g_manager_initialized) { return; @@ -951,22 +1127,26 @@ static void websocket_on_message(int client, const char *data, size_t length, bo return; } - AI_LOGI("Received %zu bytes from service %s", length, g_service_names[conn_info->service_type]); + /* 🆕 数据类型检测 */ + bool is_json = detect_json_data(data, length); + ai_event_type_t event_type = is_json ? AI_EVENT_TEXT_DATA_RECEIVED : AI_EVENT_BIN_DATA_RECEIVED; + + AI_LOGI("Received %lu bytes from service %s, type=%s, finished=%s", + (unsigned long)length, g_service_names[conn_info->service_type], + is_json ? "JSON" : "BINARY", isFinished ? "true" : "false"); + + /* 🆕 处理分片消息 */ + if (!isFinished) { + handle_fragmented_message(conn_info, data, length, event_type); + return; + } /* 更新统计 */ g_connection_manager.metrics.total_bytes_received += length; conn_info->last_activity_time = get_current_timestamp(); - /* 发送数据接收事件 */ - ai_event_t event = { - .type = AI_EVENT_DATA_RECEIVED, - .service = conn_info->service_type, - .timestamp = get_current_timestamp() - }; - event.payload.data.data = data; - event.payload.data.length = length; - - send_event(AI_EVENT_DATA_RECEIVED, conn_info->service_type, &event.payload.data); + /* 🆕 发送带实际数据的事件 */ + send_data_event(event_type, conn_info->service_type, data, length, isFinished, true); } static void websocket_on_error(int client, struct WebSocketErrorResult errorResult) @@ -1017,7 +1197,8 @@ static void websocket_on_disconnected(int client) AI_LOGW("WebSocket disconnected for service %s", g_service_names[conn_info->service_type]); - conn_info->state = AI_CONN_STATE_DISCONNECTED; + /* 使用新的清理函数 */ + cleanup_websocket_connection(conn_info); /* 更新连接状态矩阵 */ switch (conn_info->service_type) { @@ -1033,6 +1214,9 @@ static void websocket_on_disconnected(int client) case AI_SERVICE_COUNT: /* 不应该到达这里 */ break; + case AI_SERVICE_GLOBAL: + /* 全局服务不需要更新连接矩阵 */ + break; } update_global_state(); @@ -1068,13 +1252,13 @@ static ai_error_t update_global_state(void) /* 发送状态变更事件 */ ai_event_t event = { .type = AI_EVENT_STATE_CHANGED, - .service = AI_SERVICE_COUNT, // 表示全局事件 + .service = AI_SERVICE_GLOBAL, // 🔧 使用全局服务类型而不是AI_SERVICE_COUNT .timestamp = get_current_timestamp() }; event.payload.state_change.old_state = old_state; event.payload.state_change.new_state = new_state; - send_event(AI_EVENT_STATE_CHANGED, AI_SERVICE_COUNT, &event.payload.state_change); + send_event(AI_EVENT_STATE_CHANGED, AI_SERVICE_GLOBAL, &event.payload.state_change); } return AI_OK; @@ -1086,23 +1270,36 @@ static void send_event(ai_event_type_t type, ai_service_type_t service, const vo return; } - ai_event_t event = { - .type = type, - .service = service, - .timestamp = get_current_timestamp() - }; - - /* 根据事件类型复制数据 */ + /* 🆕 使用控制事件创建函数 */ + ai_event_t *event = ai_event_create_control(type, service); + if (event == NULL) { + AI_LOGE("Failed to create control event"); + return; + } + + /* 根据事件类型复制数据到相应的payload */ if (data != NULL) { switch (type) { - case AI_EVENT_DATA_RECEIVED: - memcpy(&event.payload.data, data, sizeof(event.payload.data)); + case AI_EVENT_TEXT_DATA_RECEIVED: + case AI_EVENT_BIN_DATA_RECEIVED: + /* 🆕 数据事件现在使用 send_data_event 函数 */ + AI_LOGW("Data events should use send_data_event function"); break; case AI_EVENT_ERROR_OCCURRED: - event.payload.error.error_code = *(int*)data; + if (data != NULL) { + event->payload.error.error_code = *(int*)data; + } break; case AI_EVENT_STATE_CHANGED: - memcpy(&event.payload.state_change, data, sizeof(event.payload.state_change)); + if (data != NULL) { + memcpy(&event->payload.state_change, data, sizeof(event->payload.state_change)); + } + break; + case AI_EVENT_CONNECTION_ESTABLISHED: + case AI_EVENT_CONNECTION_LOST: + if (data != NULL) { + memcpy(&event->payload.connection, data, sizeof(event->payload.connection)); + } break; default: break; @@ -1112,14 +1309,122 @@ static void send_event(ai_event_type_t type, ai_service_type_t service, const vo #ifndef __TEST_MODE__ /* 发送到事件队列 */ if (g_connection_manager.event_queue != NULL) { + /* 注意:队列中存储的是事件指针,接收方需要负责释放内存 */ osMessageQueuePut(g_connection_manager.event_queue, &event, 0, 0); } #endif + + /* 直接调用回调函数 */ + if (g_connection_manager.config.event_callback != NULL) { + g_connection_manager.config.event_callback(event, g_connection_manager.config.user_data); + } + /* 🆕 如果没有队列或回调函数,需要释放事件内存 */ +#ifdef __TEST_MODE__ + ai_event_destroy(event); +#else + if (g_connection_manager.event_queue == NULL && g_connection_manager.config.event_callback == NULL) { + ai_event_destroy(event); + } +#endif +} + +static void send_data_event(ai_event_type_t type, ai_service_type_t service, const char *data, size_t length, bool is_finished, bool is_final_fragment) +{ + if (!g_connection_manager.is_running) { + return; + } + + /* 🆕 使用动态分配创建事件 */ + ai_event_t *event = ai_event_create_data(type, service, (const uint8_t*)data, length, is_finished, is_final_fragment); + if (event == NULL) { + AI_LOGE("Failed to create data event"); + return; + } + + /* 设置会话ID(如果需要) */ + // TODO: 根据服务类型设置会话ID + +#ifndef __TEST_MODE__ + /* 发送到事件队列 */ + if (g_connection_manager.event_queue != NULL) { + /* 注意:队列中存储的是事件指针,接收方需要负责释放内存 */ + osMessageQueuePut(g_connection_manager.event_queue, &event, 0, 0); + } +#endif + /* 直接调用回调函数 */ if (g_connection_manager.config.event_callback != NULL) { - g_connection_manager.config.event_callback(&event, g_connection_manager.config.user_data); + g_connection_manager.config.event_callback(event, g_connection_manager.config.user_data); + } + + /* 🆕 如果没有队列或回调函数,需要释放事件内存 */ +#ifdef __TEST_MODE__ + ai_event_destroy(event); +#else + if (g_connection_manager.event_queue == NULL && g_connection_manager.config.event_callback == NULL) { + ai_event_destroy(event); + } +#endif +} +/* + * 判断数据是否为json 最早的20字节是否为json + * @param data: 数据 + * @param length: 数据长度 + * @return: 是否为json + */ +static bool detect_json_data(const char *data, size_t length) +{ + if (data == NULL || length == 0) { + return false; + } + + // 只检查最早的20字节以判定是否为json + size_t i = 0; + size_t max_check = length < 20 ? length : 20; + // 跳过前导空白字符,查找第一个非空字符 + while (i < max_check && (data[i] == ' ' || data[i] == '\t' || data[i] == '\r' || data[i] == '\n')) { + i++; + } + if (i >= max_check || data[i] != '{') { + return false; } + // 找到第一个非空字符为 '{',继续查找下一个非空字符 + i++; + while (i < max_check && (data[i] == ' ' || data[i] == '\t' || data[i] == '\r' || data[i] == '\n')) { + i++; + } + if (i < max_check && data[i] == '"') { + return true; + } + return false; +} + +static void handle_fragmented_message(ai_connection_info_t *conn_info, const char *data, size_t length, ai_event_type_t event_type) +{ + // 在实际应用中,这里需要将分片数据拼接起来,直到收到 isFinished = true + // 对于简单的测试,我们可以直接发送一个带有部分数据的虚拟事件 + // 在实际场景中,需要一个缓冲区来存储分片数据 + AI_LOGI("Received fragmented message for service %s, type=%s, length=%lu", + g_service_names[conn_info->service_type], + event_type == AI_EVENT_TEXT_DATA_RECEIVED ? "JSON" : "BINARY", (unsigned long)length); + + // 在测试模式下,我们直接发送一个带有部分数据的虚拟事件 + // 实际应用中,这里需要将分片数据拼接起来 + // 例如: + // static char *fragmented_data = NULL; + // static size_t fragmented_length = 0; + // fragmented_length += length; + // fragmented_data = (char*)realloc(fragmented_data, fragmented_length); + // if (fragmented_data != NULL) { + // memcpy(fragmented_data + fragmented_length - length, data, length); + // } + // if (isFinished) { + // send_data_event(event_type, conn_info->service_type, fragmented_data, fragmented_length, true, true); + // free(fragmented_data); + // fragmented_data = NULL; + // fragmented_length = 0; + // } } static uint32_t get_current_timestamp(void) @@ -1153,7 +1458,8 @@ static void heartbeat_timer_callback(void *argument) /* 发送心跳包 */ const char *heartbeat = "{\"type\":\"heartbeat\"}"; - ai_connection_manager_send_data((ai_service_type_t)i, heartbeat, strlen(heartbeat)); + printf("send dumy heartbeat: %s\n", heartbeat); + // ai_connection_manager_send_data((ai_service_type_t)i, heartbeat, strlen(heartbeat)); } } } diff --git a/code/sdk/zhihui/ai/src/ai_event_manager.c b/code/sdk/zhihui/ai/src/ai_event_manager.c new file mode 100644 index 000000000..85f754f90 --- /dev/null +++ b/code/sdk/zhihui/ai/src/ai_event_manager.c @@ -0,0 +1,176 @@ +/** + * @file ai_event_manager.c + * @brief AI事件管理器实现 + * @author AI Assistant + * @date 2024 + */ + +#include "ai_core_types.h" +#include +#include + +#ifndef __TEST_MODE__ +#include "wearable_log.h" +#define AI_LOGI(fmt, ...) WEARABLE_LOGI("AI_EVENT", fmt, ##__VA_ARGS__) +#define AI_LOGW(fmt, ...) WEARABLE_LOGW("AI_EVENT", fmt, ##__VA_ARGS__) +#define AI_LOGE(fmt, ...) WEARABLE_LOGE("AI_EVENT", fmt, ##__VA_ARGS__) +#else +#include +#define AI_LOGI(fmt, ...) printf("[AI_EVENT] " fmt "\n", ##__VA_ARGS__) +#define AI_LOGW(fmt, ...) printf("[AI_EVENT] " fmt "\n", ##__VA_ARGS__) +#define AI_LOGE(fmt, ...) printf("[AI_EVENT] " fmt "\n", ##__VA_ARGS__) +#endif + +/* ================================ 内部函数 ================================ */ + +/** + * @brief 获取当前时间戳 + */ +static uint32_t get_current_timestamp(void) +{ +#ifndef __TEST_MODE__ + /* 在实际系统中使用系统时间函数 */ + extern uint32_t get_current_timestamp(void); + static uint32_t counter = 0; + return ++counter; // 临时使用计数器,避免链接错误 +#else + /* 测试模式下使用简单计数器 */ + static uint32_t counter = 0; + return ++counter; +#endif +} + +/* ================================ 公共函数实现 ================================ */ + +ai_event_t* ai_event_create_data(ai_event_type_t type, ai_service_type_t service, + const uint8_t *data, size_t length, + bool is_finished, bool is_final_fragment) +{ + /* 参数验证 */ + if (data == NULL && length > 0) { + AI_LOGE("Invalid parameters: data is NULL but length > 0"); + return NULL; + } + + if (length > AI_MAX_EVENT_DATA_SIZE) { + AI_LOGE("Data length %lu exceeds maximum %d", (unsigned long)length, AI_MAX_EVENT_DATA_SIZE); + return NULL; + } + + /* 分配事件结构体 */ + ai_event_t *event = (ai_event_t*)malloc(sizeof(ai_event_t)); + if (event == NULL) { + AI_LOGE("Failed to allocate memory for event"); + return NULL; + } + + /* 初始化事件 */ + memset(event, 0, sizeof(ai_event_t)); + event->type = type; + event->service = service; + event->timestamp = get_current_timestamp(); + + /* 分配数据内存 */ + if (length > 0) { + event->payload.data.data = (uint8_t*)malloc(length); + if (event->payload.data.data == NULL) { + AI_LOGE("Failed to allocate %lu bytes for event data", (unsigned long)length); + free(event); + return NULL; + } + + /* 复制数据 */ + memcpy(event->payload.data.data, data, length); + event->payload.data.length = length; + event->payload.data.allocated_size = length; + } else { + event->payload.data.data = NULL; + event->payload.data.length = 0; + event->payload.data.allocated_size = 0; + } + + event->payload.data.is_finished = is_finished; + event->payload.data.is_final_fragment = is_final_fragment; + + AI_LOGI("Created data event: type=%d, service=%d, length=%lu", type, service, (unsigned long)length); + return event; +} + +ai_event_t* ai_event_create_control(ai_event_type_t type, ai_service_type_t service) +{ + /* 分配事件结构体 */ + ai_event_t *event = (ai_event_t*)malloc(sizeof(ai_event_t)); + if (event == NULL) { + AI_LOGE("Failed to allocate memory for control event"); + return NULL; + } + + /* 初始化事件 */ + memset(event, 0, sizeof(ai_event_t)); + event->type = type; + event->service = service; + event->timestamp = get_current_timestamp(); + + AI_LOGI("Created control event: type=%d, service=%d", type, service); + return event; +} + +void ai_event_destroy(ai_event_t *event) +{ + if (event == NULL) { + return; + } + + /* 释放数据内存 */ + if ((event->type == AI_EVENT_TEXT_DATA_RECEIVED || event->type == AI_EVENT_BIN_DATA_RECEIVED) && + event->payload.data.data != NULL) { + AI_LOGI("Freeing event data: %lu bytes", (unsigned long)event->payload.data.allocated_size); + free(event->payload.data.data); + event->payload.data.data = NULL; + event->payload.data.length = 0; + event->payload.data.allocated_size = 0; + } + + /* 释放事件结构体 */ + free(event); +} + +ai_event_t* ai_event_copy(const ai_event_t *src) +{ + if (src == NULL) { + return NULL; + } + + /* 分配新事件 */ + ai_event_t *dst = (ai_event_t*)malloc(sizeof(ai_event_t)); + if (dst == NULL) { + AI_LOGE("Failed to allocate memory for event copy"); + return NULL; + } + + /* 复制基本信息 */ + memcpy(dst, src, sizeof(ai_event_t)); + + /* 深拷贝数据部分 */ + if ((src->type == AI_EVENT_TEXT_DATA_RECEIVED || src->type == AI_EVENT_BIN_DATA_RECEIVED) && + src->payload.data.data != NULL && src->payload.data.length > 0) { + + dst->payload.data.data = (uint8_t*)malloc(src->payload.data.length); + if (dst->payload.data.data == NULL) { + AI_LOGE("Failed to allocate memory for event data copy"); + free(dst); + return NULL; + } + + memcpy(dst->payload.data.data, src->payload.data.data, src->payload.data.length); + dst->payload.data.allocated_size = src->payload.data.length; + + AI_LOGI("Copied event with %lu bytes of data", (unsigned long)src->payload.data.length); + } else { + dst->payload.data.data = NULL; + dst->payload.data.length = 0; + dst->payload.data.allocated_size = 0; + } + + return dst; +} \ No newline at end of file diff --git a/code/sdk/zhihui/ai/src/ai_services.c b/code/sdk/zhihui/ai/src/ai_services.c index 8f4139f5d..263466aac 100644 --- a/code/sdk/zhihui/ai/src/ai_services.c +++ b/code/sdk/zhihui/ai/src/ai_services.c @@ -11,6 +11,7 @@ #include "ai_tts_service.h" #include "ai_connection_manager.h" #include "ai_memory_pool.h" +#include "ai_media.h" #include #include #include @@ -29,10 +30,17 @@ #define AI_LOGW(fmt, ...) printf("[AI_SERVICES] WARN: " fmt "\n", ##__VA_ARGS__) #endif +/* ================================ 全局变量 ================================ */ + +/* 音频服务状态 */ +static bool g_asr_recording_enabled = false; // ASR录音服务启动标识 +static ai_services_context_t *g_services_context = NULL; // 全局服务上下文 + /* ================================ 内部函数声明 ================================ */ static uint32_t get_current_timestamp(void); -static void connection_event_handler(const ai_event_t *event, void *user_data); +static void audio_record_callback(uint8_t *data, uint32_t size); +void connection_event_handler(const ai_event_t *event, void *user_data); /* ================================ 公共接口实现 ================================ */ @@ -53,6 +61,9 @@ ai_error_t ai_services_init(ai_services_context_t *context) /* 初始化上下文 */ memset(context, 0, sizeof(ai_services_context_t)); + /* 设置全局上下文指针 */ + g_services_context = context; + /* 初始化各个服务 */ ai_error_t ret = ai_asr_service_init(&context->asr_context); if (ret != AI_OK) { @@ -75,13 +86,35 @@ ai_error_t ai_services_init(ai_services_context_t *context) return ret; } + /* 🆕 初始化音频服务 */ + AI_LOGI("Initializing audio media service..."); + int32_t media_ret = ai_media_service_init(); + if (media_ret != 0) { + AI_LOGE("Failed to initialize media service: %d", media_ret); + ai_asr_service_destroy(&context->asr_context); + ai_chat_service_destroy(&context->chat_context); + ai_tts_service_destroy(&context->tts_context); + return AI_ERROR_MEDIA_INIT; + } + + /* 🆕 设置录音回调 */ + ai_media_set_record_callback(audio_record_callback); + + /* 🆕 启动录音服务 */ + AI_LOGI("Starting audio recording..."); + ai_media_record_start(&context->asr_context.config.format); // 使用PCM格式 + + /* 🆕 启动播放服务 */ + AI_LOGI("Starting audio playback..."); + ai_media_play_start(&context->tts_context.config.format); // 使用PCM格式 + /* 生成统一会话ID */ ai_services_generate_session_id(context->current_session_id, sizeof(context->current_session_id)); context->last_activity_time = get_current_timestamp(); context->is_initialized = true; - AI_LOGI("AI services initialized successfully"); + AI_LOGI("AI services initialized successfully with audio support"); return AI_OK; } @@ -97,11 +130,21 @@ ai_error_t ai_services_destroy(ai_services_context_t *context) AI_LOGI("Destroying AI services..."); + /* 🆕 停止音频服务 */ + AI_LOGI("Stopping audio services..."); + ai_media_record_stop(); + ai_media_play_stop(); + ai_media_service_deinit(); + /* 销毁各个服务 */ ai_asr_service_destroy(&context->asr_context); ai_chat_service_destroy(&context->chat_context); ai_tts_service_destroy(&context->tts_context); + /* 重置全局上下文指针 */ + g_services_context = NULL; + g_asr_recording_enabled = false; + /* 重置上下文 */ memset(context, 0, sizeof(ai_services_context_t)); @@ -154,7 +197,14 @@ ai_error_t ai_asr_start(ai_services_context_t *context, const char *session_id) /* 使用统一会话ID或提供的会话ID */ const char *use_session_id = session_id ? session_id : context->current_session_id; - return ai_asr_service_start(&context->asr_context, use_session_id); + ai_error_t ret = ai_asr_service_start(&context->asr_context, use_session_id); + if (ret == AI_OK) { + /* 🆕 启动ASR服务成功后,开启录音数据发送 */ + g_asr_recording_enabled = true; + AI_LOGI("ASR recording enabled"); + } + + return ret; } ai_error_t ai_asr_send_audio(ai_services_context_t *context, const uint8_t *audio_data, uint32_t data_size) @@ -180,6 +230,10 @@ ai_error_t ai_asr_stop(ai_services_context_t *context) return AI_ERROR_NOT_INITIALIZED; } + /* 🆕 停止ASR服务前,关闭录音数据发送 */ + g_asr_recording_enabled = false; + AI_LOGI("ASR recording disabled"); + return ai_asr_service_stop(&context->asr_context); } @@ -372,6 +426,49 @@ const char* ai_services_get_state_string(ai_service_type_t service_type, ai_serv } } +/* ================================ 音频服务控制接口 ================================ */ + +/** + * @brief 启用ASR录音数据发送 + * @param context 服务上下文 + * @return ai_error_t 错误码 + */ +ai_error_t ai_services_enable_asr_recording(ai_services_context_t *context) +{ + if (context == NULL || !context->is_initialized) { + return AI_ERROR_NOT_INITIALIZED; + } + + g_asr_recording_enabled = true; + AI_LOGI("ASR recording enabled manually"); + return AI_OK; +} + +/** + * @brief 禁用ASR录音数据发送 + * @param context 服务上下文 + * @return ai_error_t 错误码 + */ +ai_error_t ai_services_disable_asr_recording(ai_services_context_t *context) +{ + if (context == NULL || !context->is_initialized) { + return AI_ERROR_NOT_INITIALIZED; + } + + g_asr_recording_enabled = false; + AI_LOGI("ASR recording disabled manually"); + return AI_OK; +} + +/** + * @brief 获取ASR录音状态 + * @return bool 录音状态 + */ +bool ai_services_is_asr_recording_enabled(void) +{ + return g_asr_recording_enabled; +} + /* ================================ 内部函数实现 ================================ */ static uint32_t get_current_timestamp(void) @@ -383,7 +480,27 @@ static uint32_t get_current_timestamp(void) #endif } -static void connection_event_handler(const ai_event_t *event, void *user_data) +static void audio_record_callback(uint8_t *data, uint32_t size) +{ + if (g_services_context == NULL || !g_services_context->is_initialized) { + AI_LOGE("Services context not initialized"); + return; + } + AI_LOGI("Audio record callback, size: %d", size); + + /* 🆕 根据ASR服务启动标识决定是否发送音频数据 */ + if (g_asr_recording_enabled) { + AI_LOGI("Sending %u bytes audio data to ASR service", size); + + /* 通过连接管理器发送原始音频数据到ASR服务 */ + ai_error_t ret = ai_connection_manager_send_rawdata(AI_SERVICE_ASR, (const void*)data, (size_t)size); + if (ret != AI_OK) { + AI_LOGW("Failed to send audio data to ASR service: %d", ret); + } + } +} + +void connection_event_handler(const ai_event_t *event, void *user_data) { if (event == NULL) { return; @@ -394,25 +511,77 @@ static void connection_event_handler(const ai_event_t *event, void *user_data) return; } - AI_LOGI("Connection event: service=%d, type=%d", event->service, event->type); + AI_LOGI("Connection event: service=%d, type=%d, data_length=%lu", + event->service, event->type, (unsigned long)event->payload.data.length); /* 根据事件类型和服务类型处理响应 */ switch (event->service) { case AI_SERVICE_ASR: - if (event->type == AI_EVENT_DATA_RECEIVED && event->payload.data.data != NULL) { - ai_asr_service_handle_response(&context->asr_context, (const char*)event->payload.data.data); + if (event->type == AI_EVENT_TEXT_DATA_RECEIVED && event->payload.data.length > 0) { + /* ASR服务只处理JSON文本数据 */ + char *json_str = (char*)malloc(event->payload.data.length + 1); + if (json_str != NULL) { + memcpy(json_str, event->payload.data.data, event->payload.data.length); + json_str[event->payload.data.length] = '\0'; + ai_asr_service_handle_response(&context->asr_context, json_str); + free(json_str); + } + } else if (event->type == AI_EVENT_BIN_DATA_RECEIVED) { + AI_LOGW("ASR service received binary data, ignoring"); } break; case AI_SERVICE_CHAT: - if (event->type == AI_EVENT_DATA_RECEIVED && event->payload.data.data != NULL) { - ai_chat_service_handle_response(&context->chat_context, (const char*)event->payload.data.data); + if (event->type == AI_EVENT_TEXT_DATA_RECEIVED && event->payload.data.length > 0) { + /* Chat服务只处理JSON文本数据 */ + char *json_str = (char*)malloc(event->payload.data.length + 1); + if (json_str != NULL) { + memcpy(json_str, event->payload.data.data, event->payload.data.length); + json_str[event->payload.data.length] = '\0'; + ai_chat_service_handle_response(&context->chat_context, json_str); + free(json_str); + } + } else if (event->type == AI_EVENT_BIN_DATA_RECEIVED) { + AI_LOGW("Chat service received binary data, ignoring"); } break; case AI_SERVICE_TTS: - if (event->type == AI_EVENT_DATA_RECEIVED && event->payload.data.data != NULL) { - ai_tts_service_handle_response(&context->tts_context, (const char*)event->payload.data.data); + if (event->type == AI_EVENT_TEXT_DATA_RECEIVED && event->payload.data.length > 0) { + /* TTS服务处理JSON文本数据 */ + char *json_str = (char*)malloc(event->payload.data.length + 1); + if (json_str != NULL) { + memcpy(json_str, event->payload.data.data, event->payload.data.length); + json_str[event->payload.data.length] = '\0'; + ai_tts_service_handle_response(&context->tts_context, json_str); + free(json_str); + } + } else if (event->type == AI_EVENT_BIN_DATA_RECEIVED && event->payload.data.length > 0) { + /* 🆕 TTS服务处理二进制音频数据 - 根据播放状态决定是否直接播放 */ + uint8_t play_status = ai_media_get_play_status(); + if (play_status != 0) { // 播放服务已启动 + AI_LOGI("Playing TTS audio data: %lu bytes", (unsigned long)event->payload.data.length); + ai_media_play_buffer((uint8_t*)event->payload.data.data, (uint32_t)event->payload.data.length); + } else { + AI_LOGW("Play service not ready, skipping TTS audio playback"); + } + + /* 同时传递给TTS服务进行状态管理 */ + ai_tts_service_handle_audio_data(&context->tts_context, + event->payload.data.data, + event->payload.data.length, + event->payload.data.is_finished); + } + break; + + case AI_SERVICE_GLOBAL: + /* 🆕 处理全局服务事件(如系统状态变更) */ + if (event->type == AI_EVENT_STATE_CHANGED) { + AI_LOGI("Global state changed: %d -> %d", + event->payload.state_change.old_state, + event->payload.state_change.new_state); + } else { + AI_LOGI("Global service event: type=%d", event->type); } break; diff --git a/code/sdk/zhihui/ai/src/ai_tts_service.c b/code/sdk/zhihui/ai/src/ai_tts_service.c index 9c9cf8b86..bf2fc91ac 100644 --- a/code/sdk/zhihui/ai/src/ai_tts_service.c +++ b/code/sdk/zhihui/ai/src/ai_tts_service.c @@ -48,6 +48,28 @@ static ai_error_t parse_tts_response(const char *json_data, tts_result_t *result /* ================================ 公共接口实现 ================================ */ +/* + * @brief 初始化TTS服务URL + * @note 根据设备token初始化TTS服务URL + */ +bool tts_api_ws_url_init(char *url_buf,const char *server_url) +{ + // 测试用例 + // tts_client = init_websocket_client("ws://118.145.158.207:22080/tts/stream?audio_buffer_size=512", NULL, 0); + + char token[64]={0}; + if(read_dev_token(token)) + { + // /tts/stream?audio_buffer_size=1024 + snprintf(url_buf,AI_MAX_URL_LENGTH,"%s?dt=%s&platform=aliyun&noCache=1",server_url,token); + } + else + { + snprintf(url_buf,AI_MAX_URL_LENGTH,"%s?platform=aliyun&noCache=1",server_url); + } + return true; + +} ai_error_t ai_tts_service_init(tts_service_context_t *context) { if (context == NULL) { @@ -243,6 +265,7 @@ void ai_tts_service_create_default_config(tts_config_t *config) config->pitch = 50; // 正常音调 config->auto_play = true; config->timeout_ms = 10000; // 10秒超时 + strncpy(config->format, "mp3", sizeof(config->format) - 1); } ai_error_t ai_tts_service_handle_response(tts_service_context_t *context, const char *json_data) @@ -287,6 +310,41 @@ ai_error_t ai_tts_service_handle_response(tts_service_context_t *context, const return AI_OK; } +ai_error_t ai_tts_service_handle_audio_data(tts_service_context_t *context, + const uint8_t *audio_data, + size_t length, + bool is_finished) +{ + if (context == NULL || audio_data == NULL || length == 0) { + return AI_ERROR_INVALID_PARAM; + } + + AI_LOGI("TTS audio data received: %u bytes, finished=%s", + length, is_finished ? "true" : "false"); + + /* 构造TTS音频结果 */ + tts_result_t result = {0}; + strncpy(result.session_id, context->current_session_id, sizeof(result.session_id) - 1); + result.audio_data = audio_data; + result.audio_length = length; + result.is_final = is_finished; + result.timestamp = get_current_timestamp(); + + /* 更新状态 */ + if (is_finished) { + change_tts_state(context, TTS_STATE_COMPLETED); + } else { + change_tts_state(context, TTS_STATE_PLAYING); + } + + /* 触发音频数据回调 */ + if (context->result_cb != NULL) { + context->result_cb(&result, context->user_data); + } + + return AI_OK; +} + ai_error_t ai_tts_service_build_request(const char *text, const char *session_id, const tts_config_t *config, char *json_buffer, size_t buffer_size) { diff --git a/code/sdk/zhihui/ai/src/dev_authentication.c b/code/sdk/zhihui/ai/src/dev_authentication.c index ee3b1fb3e..0a32d0783 100644 --- a/code/sdk/zhihui/ai/src/dev_authentication.c +++ b/code/sdk/zhihui/ai/src/dev_authentication.c @@ -36,21 +36,21 @@ void ase_encrypt_demo(void); // #define free free // static char * dev_key = "uKLrChwrxA1LklgBc3VwawYjUkbT3BgX" ; // static char * dev_pid = "cqxJZi5CNgzjS3mX21Zw"; -int get_dev_pass_MAX_len(void)//获取设备密码最大长度,注意要防止内存越界 -{ +// int get_dev_pass_MAX_len(void)//获取设备密码最大长度,注意要防止内存越界 +// { // return sizeof(vc_cfg.dev_pass); -} +// } void save_dev_pass(char *dev_pass)//保存设备密码,dev_pass字符串 { // memset(vc_cfg.dev_pass,0,sizeof(vc_cfg.dev_pass)); // strncpy(vc_cfg.dev_pass,dev_pass,get_dev_pass_MAX_len()); // save_VC_cfg(); } -char *get_dev_pass(void)//获取设备密码,字符串的方式,返回字符串长度为0,即没密码 -{ +// char *get_dev_pass(void)//获取设备密码,字符串的方式,返回字符串长度为0,即没密码 +// { // return vc_cfg.dev_pass; -} +// } static char aut_token[50] ={0}; int str_count(char *data,char type) @@ -473,6 +473,17 @@ extern bool read_dev_pass(char *data_pass); extern uint32_t post_data_by_get_file(char *data,uint16_t data_len); bool read_dev_token(char *data_token); +void get_dev_id(char *device_id_str) +{ + if(device_id_str==NULL) + { + return; + } + unsigned char device_id[6] = {0}; + bluetooth_get_local_addr(&device_id[0],6); + sprintf(device_id_str,"%02X%02x%02X%02X%02X%02x",device_id[5],device_id[4],device_id[3],device_id[2],device_id[1],device_id[0]); +} + uint32_t aut_login_time=0; bool aut_login(void)//登录 @@ -827,6 +838,9 @@ bool pre_AI_init(void) if(read_dev_token(NULL)==false) { return aut_login(); + }else + { + return true; } } diff --git a/code/sdk/zhihui/websocket/src/websocket_parser.c b/code/sdk/zhihui/websocket/src/websocket_parser.c index 0d085790d..baf5577e4 100644 --- a/code/sdk/zhihui/websocket/src/websocket_parser.c +++ b/code/sdk/zhihui/websocket/src/websocket_parser.c @@ -291,7 +291,7 @@ int ws_parse_byte(ws_parser_t* parser, uint8_t byte) { // 累加长度数据 static uint8_t len_bytes[8]; - WS_LOGD("WS: Reading payload length byte: 0x%02X, remaining: %zu", + WS_LOGD("WS: Reading payload length byte: 0x%02X, remaining: %u", byte, parser->payload_remaining); if (parser->header.payload_len == 126) { @@ -330,7 +330,7 @@ int ws_parse_byte(ws_parser_t* parser, uint8_t byte) { // 检查缓存是否足够 if (parser->buffer_pos + parser->header.payload_len > WS_BUFFER_SIZE) { - WS_LOGE("WS: Buffer overflow: current=%zu, adding=%d, max=%d", + WS_LOGE("WS: Buffer overflow: current=%u, adding=%d, max=%d", parser->buffer_pos, parser->header.payload_len, WS_BUFFER_SIZE); parser->state = WS_STATE_ERROR; return -1; @@ -385,7 +385,7 @@ int ws_parse_byte(ws_parser_t* parser, uint8_t byte) { // 检查缓存是否足够 if (parser->buffer_pos + parser->header.payload_len > WS_BUFFER_SIZE) { - WS_LOGE("WS: Buffer overflow: current=%zu, adding=%llu, max=%d", + WS_LOGE("WS: Buffer overflow: current=%u, adding=%llu, max=%d", parser->buffer_pos, (unsigned long long)parser->header.payload_len, WS_BUFFER_SIZE); parser->state = WS_STATE_ERROR; return -1; @@ -447,7 +447,7 @@ int ws_parse_byte(ws_parser_t* parser, uint8_t byte) { if (parser->payload_remaining == 0) { // 负载读取完成 - WS_LOGI("WS: Payload read complete (%zu bytes)", parser->header.payload_len); + WS_LOGI("WS: Payload read complete (%u bytes)", parser->header.payload_len); // 如果有掩码,应用掩码解码 if (parser->header.mask) { @@ -493,7 +493,7 @@ int ws_parse_stream(ws_parser_t* parser, const uint8_t* data, size_t len) { return -1; } - WS_LOGI("WS: Parsing stream data (%zu bytes), current state: %d, buffer_pos: %zu", + WS_LOGI("WS: Parsing stream data (%u bytes), current state: %d, buffer_pos: %u", len, parser->state, parser->buffer_pos); // 如果解析器已经完成一个帧,重置状态 @@ -510,12 +510,12 @@ int ws_parse_stream(ws_parser_t* parser, const uint8_t* data, size_t len) { if (result == 1) { // 帧解析完成 - WS_LOGI("WS: Complete frame parsed (%zu bytes total) opcode: %d, payload_len: %llu", + WS_LOGI("WS: Complete frame parsed (%u bytes total) opcode: %d, payload_len: %llu", parser->buffer_pos, parser->header.opcode, (unsigned long long)parser->header.payload_len); break; } else if (result == -1) { // 解析错误 - WS_LOGE("WS: Parsing error at byte %zu", i); + WS_LOGE("WS: Parsing error at byte %u", i); ws_parser_reset(parser); break; // 让上层根据 total_processed 前进 } @@ -528,7 +528,7 @@ int ws_parse_stream(ws_parser_t* parser, const uint8_t* data, size_t len) { WS_LOGI(" WS_STATE_ERROR at %d\n",total_processed); } else { // 数据不完整,继续等待更多数据 - WS_LOGI("WS: Frame parsing incomplete, waiting for more data. State: %d, Buffer: %zu/%d, Payload remaining: %zu", + WS_LOGI("WS: Frame parsing incomplete, waiting for more data. State: %d, Buffer: %u/%d, Payload remaining: %u", parser->state, parser->buffer_pos, WS_BUFFER_SIZE, parser->payload_remaining); } return total_processed; @@ -574,7 +574,7 @@ const uint8_t* ws_get_payload(ws_parser_t* parser) { // 检查载荷数据是否在缓存范围内 if (header_size + parser->header.payload_len > parser->buffer_pos) { - WS_LOGE("WS: Payload start position %zu exceeds buffer size %zu", header_size, parser->buffer_pos); + WS_LOGE("WS: Payload start position %u exceeds buffer size %u", header_size, parser->buffer_pos); return NULL; } diff --git a/code/sdk/zhihui/websocket/src/zhihui_websocket.c b/code/sdk/zhihui/websocket/src/zhihui_websocket.c index bdfc33c31..e81ad925f 100644 --- a/code/sdk/zhihui/websocket/src/zhihui_websocket.c +++ b/code/sdk/zhihui/websocket/src/zhihui_websocket.c @@ -461,11 +461,11 @@ static int zhihui_ws_send_frame(zhihui_websocket_client_t *client, ws_opcode_t o // 发送帧头 ssize_t header_sent = lwip_send(client->socket_fd, header, header_len, 0); if (header_sent != (ssize_t)header_len) { - ZHIHUI_WS_LOGE("Failed to send frame header: sent=%d, expected=%zu, errno=%d", + ZHIHUI_WS_LOGE("Failed to send frame header: sent=%d, expected=%u, errno=%d", (int)header_sent, header_len, errno); return WEBSOCKET_ERROR_READ_WRITE_SOCKET; } - ZHIHUI_WS_LOGD("Sent frame header: %zu bytes", header_len); + ZHIHUI_WS_LOGD("Sent frame header: %u bytes", header_len); // 发送负载数据(需要mask) if (len > 0 && data) { @@ -482,11 +482,11 @@ static int zhihui_ws_send_frame(zhihui_websocket_client_t *client, ws_opcode_t o free(masked_data); if (sent != (ssize_t)len) { - ZHIHUI_WS_LOGE("Failed to send frame payload: sent=%d, expected=%zu, errno=%d", + ZHIHUI_WS_LOGE("Failed to send frame payload: sent=%d, expected=%u, errno=%d", (int)sent, len, errno); return WEBSOCKET_ERROR_READ_WRITE_SOCKET; } - ZHIHUI_WS_LOGD("Sent frame payload: %zu bytes", len); + ZHIHUI_WS_LOGD("Sent frame payload: %u bytes", len); } return WEBSOCKET_OK; @@ -748,7 +748,7 @@ int zhihui_WebSocketClientSendBinStart(int client, const char *data, size_t leng return WEBSOCKET_NO_CONNECTION; } - ZHIHUI_WS_LOGD("SendBinStart: Client %d sending %zu bytes", client, length); + ZHIHUI_WS_LOGD("SendBinStart: Client %d sending %u bytes", client, length); int result = zhihui_ws_send_frame(ws_client, WS_BINARY, (const uint8_t*)data, length, true); // 改为true,表示完整帧 ZHIHUI_WS_LOGD("SendBinStart: Client %d send result=%d", client, result); return result; diff --git a/code/sdk/zhihui/websocket/test/ws_multi_test.c b/code/sdk/zhihui/websocket/test/ws_multi_test.c index 64bf6d34a..a9830bd83 100644 --- a/code/sdk/zhihui/websocket/test/ws_multi_test.c +++ b/code/sdk/zhihui/websocket/test/ws_multi_test.c @@ -189,7 +189,7 @@ int ws_multi_test_init(void) g_test_manager.config.use_dns = true; strcpy(g_test_manager.config.server_url, "127.0.0.1"); g_test_manager.config.server_port = 8080; - WS_TEST_LOGI("Configuration restored: data_size=%zu", g_test_manager.config.data_size); + WS_TEST_LOGI("Configuration restored: data_size=%u", g_test_manager.config.data_size); } return 0; } @@ -817,7 +817,7 @@ static int ws_test_send_data_for_client(ws_test_client_t *client) // 分配发送缓冲区 uint8_t *send_buffer = malloc(g_test_manager.config.data_size); if (send_buffer == NULL) { - WS_TEST_LOGE("Failed to allocate send buffer for client %d (size: %zu bytes)", + WS_TEST_LOGE("Failed to allocate send buffer for client %d (size: %u bytes)", client->client_id, g_test_manager.config.data_size); return -1; } @@ -827,7 +827,7 @@ static int ws_test_send_data_for_client(ws_test_client_t *client) // 发送二进制数据 if (client->stats.sent_packets % 10 == 0 || client->stats.sent_packets < 5) { // 前5个包和每10个包打印详细日志 - WS_TEST_LOGI("Client %d sending packet #%d, data_value=0x%02X, size=%zu bytes", + WS_TEST_LOGI("Client %d sending packet #%d, data_value=0x%02X, size=%u bytes", client->client_id, client->stats.sent_packets, client->current_data_value, g_test_manager.config.data_size); } @@ -1086,14 +1086,14 @@ static void ws_test_on_message_callback(int client, const char *data, size_t len ws_test_client_t *test_client = ws_test_find_client_by_fd(client); if (test_client) { if (test_client->stats.recv_packets % 100 == 0) { // 每100个包打印一次详细日志 - WS_TEST_LOGI("WebSocket onMessage callback: client_fd=%d, client_id=%d, length=%zu, isFinished=%d", + WS_TEST_LOGI("WebSocket onMessage callback: client_fd=%d, client_id=%d, length=%u, isFinished=%d", client, test_client->client_id, length, isFinished); } if (isFinished) { // 检查消息大小 if (length > WS_TEST_MSG_MAX_SIZE) { - WS_TEST_LOGW("Received message too large for client %d: %zu bytes (max: %d)", + WS_TEST_LOGW("Received message too large for client %d: %u bytes (max: %d)", test_client->client_id, length, WS_TEST_MSG_MAX_SIZE); return; } @@ -1109,7 +1109,7 @@ static void ws_test_on_message_callback(int client, const char *data, size_t len if (length > 0 && data) { msg.data = malloc(length); if (msg.data == NULL) { - WS_TEST_LOGE("Failed to allocate memory for message data, client %d, size=%zu", + WS_TEST_LOGE("Failed to allocate memory for message data, client %d, size=%u", test_client->client_id, length); return; } @@ -1119,7 +1119,7 @@ static void ws_test_on_message_callback(int client, const char *data, size_t len } if (test_client->stats.recv_packets % 100 == 0) { // 每100个包打印一次详细日志 - WS_TEST_LOGI("Client %d received packet #%d, length=%zu, first_byte=0x%02X", + WS_TEST_LOGI("Client %d received packet #%d, length=%u, first_byte=0x%02X", test_client->client_id, test_client->stats.recv_packets, length, length > 0 ? (uint8_t)data[0] : 0); } @@ -1474,7 +1474,7 @@ static void ws_test_cleanup_all_resources(void) strcpy(g_test_manager.config.server_url, "127.0.0.1"); g_test_manager.config.server_port = 8080; - WS_TEST_LOGD("Configuration reset to defaults: data_size=%zu, client_count=%d", + WS_TEST_LOGD("Configuration reset to defaults: data_size=%u, client_count=%d", g_test_manager.config.data_size, g_test_manager.config.client_count); uint32_t cleanup_time = osKernelGetTickCount() - cleanup_start;