From e2d9a4b62af6c4156ca984438d2de8cc50df780b Mon Sep 17 00:00:00 2001 From: hcj <1224667075@qq.com> Date: Fri, 26 Sep 2025 19:02:50 +0800 Subject: [PATCH] =?utf8?q?ln=20=E5=A2=9E=E5=8A=A0ws=E5=AF=B9=E8=AF=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../zhihui/ai_new/ai_V2.5/ai_v2_5_main.cpp | 3 +- .../zhihui/ai_old/asr_websocket_server.cpp | 2 +- .../zhihui/ai_old/tts_websocket_server.cpp | 2 - .../ai_old/websocket_server_example.cpp | 2 - .../sdk/zhihui/leiniao/imsl_ln/CMakeLists.txt | 2 +- code/sdk/zhihui/leiniao/imsl_ln/ln_api.h | 20 +- .../zhihui/leiniao/imsl_ln/ln_api_websocket.c | 1292 +++++++++++------ 7 files changed, 868 insertions(+), 455 deletions(-) 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 index fa464e2f0..323906ee4 100644 --- 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 @@ -323,9 +323,10 @@ int ai_service_start(void) ws_call_backs_t callbacks = { .message_callback = ai_message_handler, .binary_callback = ai_binary_message_handler, + .incomplete_callback=NULL, .url_callback = ai_url_handler, .heartbeat_callback = ai_heartbeat_handler, - .event_callback = ai_event_handler + .event_callback = ai_event_handler, }; // 启动WebSocket服务 diff --git a/code/sdk/zhihui/ai_old/asr_websocket_server.cpp b/code/sdk/zhihui/ai_old/asr_websocket_server.cpp index df876247e..4f36a6c41 100644 --- a/code/sdk/zhihui/ai_old/asr_websocket_server.cpp +++ b/code/sdk/zhihui/ai_old/asr_websocket_server.cpp @@ -319,7 +319,7 @@ ASR_START: } else if (msg_type == WS_BINARY) { printf("ASR binary data received: %d bytes\n", len); - send_msg_data_into_Ai_msg_queue((uint8_t *)asr_recv_buf, len, 100); + // send_msg_data_into_Ai_msg_queue((uint8_t *)asr_recv_buf, len, 100); } } diff --git a/code/sdk/zhihui/ai_old/tts_websocket_server.cpp b/code/sdk/zhihui/ai_old/tts_websocket_server.cpp index 70c51d012..5cdbe37dd 100644 --- a/code/sdk/zhihui/ai_old/tts_websocket_server.cpp +++ b/code/sdk/zhihui/ai_old/tts_websocket_server.cpp @@ -44,7 +44,6 @@ static uint32_t bk_websocket_tts_tick = 0; extern bool read_dev_token(char *data_token); // extern void uapi_cat1_keep_wakeup(uint8_t state); // Commented out due to missing implementation extern void set_AIStreamend(uint8_t state); -extern int send_msg_data_into_Ai_msg_queue(void *data, uint16_t len, uint16_t type); extern void msleep(uint32_t ms); extern int32_t VoiceCallVolteSample(int32_t argc, const char **argv); @@ -891,7 +890,6 @@ TTS_START: set_AIStreamend(0); bk_websocket_tts_tick = (uint32_t)osKernelGetTickCount(); } - send_msg_data_into_Ai_msg_queue((uint8_t *)tts_recv_buf, len, 200); printf("TTS binary data received: %d bytes\n", len); msleep(1); } else if (msg_type == WS_TEXT) { diff --git a/code/sdk/zhihui/ai_old/websocket_server_example.cpp b/code/sdk/zhihui/ai_old/websocket_server_example.cpp index 1f620a8bf..6cf53aba5 100644 --- a/code/sdk/zhihui/ai_old/websocket_server_example.cpp +++ b/code/sdk/zhihui/ai_old/websocket_server_example.cpp @@ -46,7 +46,6 @@ static void (*g_chat_filter_cb)(char *result) = NULL; /* 不再使用默认BLE回退,未注册时打印日志 */ extern void ble_set_ai_Question_str(char *str); extern void ble_set_ai_Answer_str(char *str, uint8_t type); -extern int send_msg_data_into_Ai_msg_queue(void *data, uint16_t len, uint16_t type); extern void set_AIStreamend(uint8_t state); extern bool uapi_start_tts(void); // extern bool ai_chat_send_txt(uint8_t type, char *msg, WebSocketClient *chat_client); @@ -214,7 +213,6 @@ void handle_asr_data(const ws_event_data_t *event_data) } } else if (event_data->msg_type == WS_BINARY) { printf("ASR binary data: %d bytes\n", event_data->data_len); - send_msg_data_into_Ai_msg_queue(event_data->data, event_data->data_len, 100); } } diff --git a/code/sdk/zhihui/leiniao/imsl_ln/CMakeLists.txt b/code/sdk/zhihui/leiniao/imsl_ln/CMakeLists.txt index 98f893cdd..9863e37fe 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/CMakeLists.txt +++ b/code/sdk/zhihui/leiniao/imsl_ln/CMakeLists.txt @@ -31,7 +31,7 @@ set(PUBLIC_HEADER ${ROOT_DIR}/open_source/openharmony/third_party/lwip/lwip_adapter/liteos/src/include ${ROOT_DIR}/open_source/openharmony/third_party/cJSON/ ${ROOT_DIR}/open_source/openharmony/foundation/ace/ace_engine_lite/modules/netstack/websocket/include - + ${ROOT_DIR}/zhihui/websocket/os_ws ) diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h index e86d0b552..11195b407 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h @@ -332,6 +332,7 @@ typedef enum { WS_CMD_DELETE_EMOJI, /* 删除表情 (DeleteEmoji) */ WS_CMD_PING, /* 心跳 (Ping) */ WS_CMD_PONG, /* 心跳回应 (Pong) */ + WS_CMD_VOICE_CHAT_RESPONSE, /* 语音聊天响应 (VoiceChatResponse) */ WS_CMD_MAX /* 最大命令 */ } ws_command_t; @@ -343,21 +344,30 @@ typedef struct { ws_command_t command; char command_str[32]; char *raw_json; // 原始JSON字符串,用于需要完整数据的场景 + + // VoiceChatResponse 特定字段 + char audio_url[512]; // 音频URL + char text_content[1024]; // 文本内容 + bool finished; // 是否完成 + char chat_id[64]; // 聊天ID } ws_data_msg_t; /* 消息处理回调函数类型 */ -typedef void (*ws_message_callback_t)(const ws_data_msg_t *data_msg, ws_command_t type); +typedef void (*ws_message_callback_t_ln)(const ws_data_msg_t *data_msg, ws_command_t type); /* -------------------------------- 注册进的回调函数 -------------------------------- */ void ln_ws_process_message(const ws_data_msg_t *data_msg, ws_command_t type); /* -------------------------------------------------------------------------- */ /* WebSocket接口函数 */ -int ln_ws_start(ws_message_callback_t callback); +int ln_ws_start(ws_message_callback_t_ln callback); int ln_ws_send_text(const char *text); +int ln_ws_send_audio_data(const uint8_t *audio_data, uint32_t audio_size, bool finished); +int ln_ws_start_audio_chat(void); +int ln_ws_end_audio_chat(void); +const char* ln_ws_get_current_chat_id(void); int ln_ws_stop(void); -int ln_ws_stop_async(void); -int ln_ws_force_reconnect(void); +// int ln_ws_force_reconnect(void); int ln_ws_enter_sleep(void); int ln_ws_exit_sleep(void); @@ -368,7 +378,7 @@ int ln_ws_get_close_status_public(void); void ln_ws_print_status_info(void); /* 回调函数注册接口 */ -void ln_ws_register_message_callback(ws_message_callback_t callback); +void ln_ws_register_message_callback(ws_message_callback_t_ln callback); #ifdef __cplusplus } 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 45ec9db14..efb7c0b00 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c @@ -1,5 +1,6 @@ #include "ln_api.h" #include "net_websocket.h" +#include "haisi_websocket.h" #include #include #include @@ -7,6 +8,7 @@ #include "custom_utils.h" #include "soc_osal.h" #include "cmsis_os2.h" +#include "mbedtls/base64.h" /* * - g_ws_client_fd 当前 WebSocket 客户端fd @@ -14,13 +16,6 @@ * - 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_DATA_MESSAGE 0x04 - /* 连接状态定义 */ #define WS_STATE_DISCONNECTED 0 // 未连接 #define WS_STATE_CONNECTING 1 // 连接中 @@ -34,31 +29,26 @@ typedef enum { WS_CLOSE_SUCCESS = 2 // 执行成功 } ws_close_status_t; -/* WebSocket命令枚举 */ - - -typedef struct { - uint8_t msg_type; - uint16_t data_len; - ws_data_msg_t 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_message_callback_t g_ws_message_callback = NULL; /* 消息处理回调函数 */ static volatile ws_close_status_t g_ws_close_status = WS_CLOSE_NOT_EXECUTED; /* WebSocket关闭状态标志位 */ +/* Base64数据缓存相关变量 */ +static char *g_base64_cache = NULL; /* 缓存的Base64数据 */ +static size_t g_base64_cache_size = 0; /* 缓存大小 */ +static size_t g_base64_cache_capacity = 0; /* 缓存容量 */ +static bool g_base64_in_progress = false; /* 是否正在进行Base64数据接收 */ + +/* 回调函数结构体 */ +static ws_call_backs_t g_ws_callbacks = {0}; + static void ln_ws_build_url(char *out, size_t out_size); int ln_ws_send_ping(void); static ws_command_t ln_ws_parse_command_string(const char *command_str); static int ln_ws_parse_data_message(const char *json_str, ws_data_msg_t *data_msg); -static int ln_ws_queue_data_message(const ws_data_msg_t *data_msg); static void ln_ws_free_data_message(ws_data_msg_t *data_msg); static void ln_ws_default_message_handler(const ws_data_msg_t *data_msg, ws_command_t type); static void ln_ws_process_message_with_callback(const ws_data_msg_t *data_msg); @@ -66,17 +56,15 @@ static int ln_ws_close_with_status(int client_fd); static ws_close_status_t ln_ws_get_close_status(void); static void ln_ws_reset_close_status(void); const char* ln_ws_get_connection_state_string(void); -/* 队列创建函数 */ -void ln_ws_queue_init(void) -{ - printf("[LN][WS] ln_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] ln_ws_queue_init failed: failed to create message queue\n"); - } else { - printf("[LN][WS] ln_ws_queue_init success: queue created\n"); - } -} + +/* Base64缓存管理函数 */ +static void ln_ws_base64_cache_init(void); +static void ln_ws_base64_cache_cleanup(void); +static int ln_ws_base64_cache_append(const char *data, size_t length); +static void ln_ws_base64_cache_reset(void); +static void ln_ws_process_base64_data(const char *base64_data, size_t length); +static void ln_ws_process_incremental_base64_data(const char *new_data, size_t new_length); +static void ln_ws_incomplete_callback(const char *raw_data, size_t length, bool isBinary); /* 握手成功回调:标记连接就绪 */ static void ln_ws_on_open(int client) @@ -84,290 +72,112 @@ static void ln_ws_on_open(int client) (void)client; printf("WebSocketClientConstructor ln_ws_on_open\r\n"); g_ws_connected = WS_STATE_CONNECTED; - ln_ws_reset_close_status(); // 连接成功时重置关闭状态 - printf("[LN][WS] 连接状态: 连接成功, 关闭状态已重置为 NOT_EXECUTED (%d)\n", g_ws_close_status); + printf("[LN][WS] 连接状态: 连接成功\n"); } -/* - * 收到服务器下发的文本消息 - * - 上层可根据业务定义的 JSON 字段进行再分发 - */ - -// [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,bool isBinary) +/* 消息接收回调函数 */ +static void ln_ws_message_callback(const char *raw_data, uint32_t timestamp) { - (void)client; - (void)isFinished; - if (data == NULL || length == 0) return; + (void)timestamp; + if (raw_data == NULL) return; + + size_t length = strlen(raw_data); char *buf = (char *)malloc(length + 1); if (!buf) return; - memcpy(buf, data, length); + memcpy(buf, raw_data, length); buf[length] = '\0'; /* 统一的消息解析处理 */ printf("WebSocketClientConstructor data\r\n"); if (length >= 2 && buf[0] == '{' && buf[length - 1] == '}') { - // 解析消息并写入队列 + // 解析消息并直接处理 ws_data_msg_t data_msg; if (ln_ws_parse_data_message(buf, &data_msg) == 0) { - ln_ws_queue_data_message(&data_msg); - printf("[LN][WS] Message parsed and queued successfully: command=%d (%s)\n", + printf("[LN][WS] Message parsed successfully: command=%d (%s)\n", data_msg.command, data_msg.command_str); + + // 直接使用回调函数处理消息 + ln_ws_process_message_with_callback(&data_msg); + + // 释放消息内存 + ln_ws_free_data_message(&data_msg); } else { printf("[LN][WS] Failed to parse message\n"); } } + else + { + ln_ws_incomplete_callback(raw_data,length,false); + printf("[LN][WS] Invalid message\n"); + } printf("[LN][WS] on_message: %s\n", buf); - /* 透传给接收队列:上层可基于 dispatch_cmd 做进一步处理 */ - //do_ln_http_server_recv_data(dispatch_cmd, buf, (uint32_t)length); - /* 立即在同线程内调上层处理(可选:如担心阻塞可删除此行,仅由接收任务处理) */ - // ln_ws_process_message(dispatch_cmd, buf); free(buf); } -/* 出错回调:仅打印,实际重连策略可在上层统一管理 */ -static void ln_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 ln_ws_on_close(int client) +/* 连接事件回调函数 */ +static void ln_ws_event_callback(ws_event_type_t event_type) { - printf("WebSocketClientConstructor [LN][WS] ln_ws_on_close %d\r\n",client); - - // 在断开回调触发时,将关闭状态设置为成功 - - printf("[LN][WS] WebSocket close callback triggered, status set to SUCCESS (%d)\n", g_ws_close_status); - + switch (event_type) { + case WS_EVENT_CONNECTING: + printf("[LN][WS] Event: Connecting\n"); + g_ws_connected = WS_STATE_CONNECTING; + break; + case WS_EVENT_CONNECTED: + printf("[LN][WS] Event: Connected\n"); + g_ws_connected = WS_STATE_CONNECTED; + break; + case WS_EVENT_DISCONNECTED: + printf("[LN][WS] Event: Disconnected\n"); g_ws_connected = WS_STATE_DISCONNECTED; - printf("[LN][WS] 连接状态: 未连接\n"); - if(g_ws_client_fd>=0) - { - // 这里不再调用 ln_ws_close_with_status,因为回调已经触发说明关闭成功 - if(g_ws_close_status==WS_CLOSE_NOT_EXECUTED) - { - // int ret = WebSocketClientClose(g_ws_client_fd); - - } - else - { - g_ws_client_fd=-1; - } - } /* 断开后增加重连计数,由监督线程在下一次连接时携带 */ if (g_ws_running) { if (g_ws_reconnect_count < 0xFFFFFFFFu) { g_ws_reconnect_count++; } } -} - -/* - * 主线程:负责连接管理、心跳维护和队列消息处理 - * - 监督连接状态,处理重连逻辑 - * - 每30秒发送一次 "ping" 维持链路活性 - * - 处理队列中的消息 - */ -static void ln_ws_main_thread(void *arg) -{ - (void)arg; - int heartbeat_counter = 0; // 心跳计数器,每30秒发送一次ping - ws_msg_info_t msg; - uint32_t msgsize =0; - 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); - } - 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"); - ln_ws_close_with_status(g_ws_client_fd); - printf("[LN][WS] WebSocketClientClose end\n"); - } - g_ws_connected = WS_STATE_DISCONNECTED; - printf("[LN][WS] 连接状态: 未连接(强制重连)\n"); - break; - - case WS_MSG_TYPE_STOP: - printf("[LN][WS] Stop requested from queue\n"); - g_ws_running = 0; - break; - - case WS_MSG_TYPE_DATA_MESSAGE: - { - ws_data_msg_t *data_msg = &msg.data; - printf("[LN][WS] Received message: deviceId=%s, timestamp=%u, messageId=%s, command=%d (%s)\n", - data_msg->device_id, data_msg->timestamp, data_msg->message_id, - data_msg->command, data_msg->command_str); - - // 使用回调函数处理消息 - ln_ws_process_message_with_callback(data_msg); - - // 释放消息内存 - ln_ws_free_data_message(data_msg); - } - break; - - default: - printf("[LN][WS] Unknown message type: %d\n", msg.msg_type); - break; - } - } - else - { - // printf("osMessageQueueGet timeout or failed\r\n"); - } - - // 连接管理部分 - 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"); - ln_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"); - g_ws_client_fd = WebSocketClientConstructor(ln_ws_on_open, ln_ws_on_message, ln_ws_on_error, ln_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}; - ln_ws_build_url(url, sizeof(url)); - - const char *device_id = uapi_ln_get_device_id(); - // f"deviceId: {self.device_id}", - // f"deviceIccid: {self.device_iccid}", - // f"deviceModel: {Constants.DEVICE_MODEL.value}", - // f"deviceVersion: {Constants.DEVICE_VERSION.value}" - - printf("[LN][WS] 尝试连接WebSocket: %s\n", url); - char header_buf[512]={0}; - int hdr = snprintf(header_buf, sizeof(header_buf), - "deviceId: %s\r\n", - device_id); - int 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)ln_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"); - } - - // 心跳部分:每30秒发送一次ping - if (g_ws_connected == WS_STATE_CONNECTED && g_ws_client_fd >= 0) { - heartbeat_counter++; - if (heartbeat_counter >= 15) { // 30秒 = 30次 * 1秒 - (void)ln_ws_send_ping(); - heartbeat_counter = 0; // 重置计数器 - } - } - else - { - printf("ln ws_state %s\r\n",ln_ws_get_connection_state_string()); - } - - /* 1秒间隔 */ - usleep(1000 * 1000); - } - - - if (g_ws_client_fd >= 0) { - ln_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) { - if(msg.data.raw_json) - { - free(msg.data.raw_json); - } - - } - osMessageQueueDelete(g_ws_msg_queue); - g_ws_msg_queue = NULL; + break; + default: + printf("[LN][WS] Event: Unknown event type %d\n", event_type); + break; } } -/* - * 拼接连接URL:环境域名 + 业务必须的查询参数 - * - 认证方式采用“查询参数方式”:至少包含 deviceToken(规范允许) - * - 兼容性增强:同时附带 deviceId 与 reconnect 计数,方便服务端校验与审计 - * - 如需切换环境,仅修改 base 即可 - */ -static void ln_ws_build_url(char *out, size_t out_size) +/* URL构建回调函数 */ +static void ln_ws_url_callback(char *url, size_t url_size, char *header_buf, size_t header_size) { const char *base = "ws://ambybox-api.test.leiniao.com/ambybox-device-api/v1/websocket"; const char *device_id = uapi_ln_get_device_id(); const char *token = uapi_ln_get_device_token(); if (!device_id) device_id = ""; if (!token) token = ""; + /* 仅使用 deviceToken 也可通过;额外带上 deviceId 与 reconnect 便于后端管理 */ - snprintf(out, out_size, "%s?deviceToken=%s&deviceId=%s&reconnect=%u", + snprintf(url, url_size, "%s?deviceToken=%s&deviceId=%s&reconnect=%u", base, token, device_id, g_ws_reconnect_count); + + /* 构建头部信息 */ + snprintf(header_buf, header_size, "deviceId: %s\r\n", device_id); } + +/* 心跳处理回调函数 */ +static int ln_ws_heartbeat_callback(void) +{ + // 每30秒发送一次心跳 + static int heartbeat_counter = 0; + heartbeat_counter++; + if (heartbeat_counter >= 15) { // 30秒 = 30次 * 1秒 + heartbeat_counter = 0; + ln_ws_send_ping(); + return 1; // 需要发送心跳 + } + return 0; // 不需要发送心跳 +} + + int ln_ws_send_ping(void) { - if (g_ws_connected != WS_STATE_CONNECTED || g_ws_client_fd < 0) { + if (g_ws_connected != WS_STATE_CONNECTED) { printf("[LN][WS] Cannot send ping, connection state: %d\n", g_ws_connected); return -1; } @@ -399,9 +209,9 @@ int ln_ws_send_ping(void) } // 发送 JSON 字符串 - int ret = WebSocketClientSendText(g_ws_client_fd, json_string, strlen(json_string)); + int ret = uapi_ws_send_text(json_string); - printf("[LN][WS] Sent ping: %s %d\n", json_string,g_ws_client_fd); + printf("[LN][WS] Sent ping: %s\n", json_string); // 清理内存 free(json_string); @@ -411,12 +221,362 @@ int ln_ws_send_ping(void) } + +uint16_t send_audio=0; + +// 音频聊天相关结构体 +typedef struct { + char chat_id[64]; // 聊天ID + uint32_t message_counter; // 消息计数器 + bool is_active; // 是否活跃 +} audio_chat_context_t; + +// 全局音频聊天上下文 +static audio_chat_context_t g_audio_chat_ctx = {0}; + +// 音频聊天结束标志 +static bool g_audio_chat_ending = false; + +// 函数声明 +static void generate_chat_id(char *chat_id, size_t size); +static void generate_message_id(char *message_id, size_t size, const char *chat_id, uint32_t counter); + +// 生成聊天ID +static void generate_chat_id(char *chat_id, size_t size) +{ + if (!chat_id || size < 16) return; + + uint32_t timestamp; + uapi_get_timestampe_ss(×tamp); + + snprintf(chat_id, size, "chat_%u_%u", timestamp, (uint32_t)rand()); +} + +// 生成消息ID +static void generate_message_id(char *message_id, size_t size, const char *chat_id, uint32_t counter) +{ + if (!message_id || !chat_id || size < 32) return; + + snprintf(message_id, size, "%s_msg_%u", chat_id, counter); +} + +// 发送音频数据到WebSocket +int ln_ws_send_audio_data(const uint8_t *audio_data, uint32_t audio_size, bool finished) +{ + // 检查结束标志位,如果设置了结束标志,则finished必须为true + if (g_audio_chat_ending && !finished) { + printf("[LN][WS] Audio chat is ending, finished must be true\n"); + return -1; + } + + // 对于正常音频数据,必须有有效的数据 + if (!finished && (!audio_data || audio_size == 0)) { + printf("[LN][WS] Invalid audio data parameters\n"); + return -1; + } + + if (g_ws_connected != WS_STATE_CONNECTED) { + printf("[LN][WS] Cannot send audio data, connection state: %d\n", g_ws_connected); + return -1; + } + + // 如果聊天未开始,生成新的聊天ID + if (!g_audio_chat_ctx.is_active) { + generate_chat_id(g_audio_chat_ctx.chat_id, sizeof(g_audio_chat_ctx.chat_id)); + g_audio_chat_ctx.message_counter = 0; + g_audio_chat_ctx.is_active = true; + printf("[LN][WS] New chat started: %s\n", g_audio_chat_ctx.chat_id); + } + + // 增加消息计数器 + g_audio_chat_ctx.message_counter++; + + // 生成消息ID + char message_id[128]; + generate_message_id(message_id, sizeof(message_id), g_audio_chat_ctx.chat_id, g_audio_chat_ctx.message_counter); + + // Base64编码音频数据(如果是结束消息,则使用空字符串) + char *audio_base64 = NULL; + if (audio_size == 0) { + // 结束消息,使用空字符串 + audio_base64 = (char*)malloc(1); + if (!audio_base64) { + printf("[LN][WS] Failed to allocate memory for base64 encoding\n"); + return -1; + } + audio_base64[0] = '\0'; + } else { + // 正常音频数据,进行Base64编码 + size_t base64_size =audio_size*2; //((audio_size + 2) / 3) * 4 + 1; + audio_base64 = (char*)malloc(base64_size); + if (!audio_base64) { + printf("[LN][WS] Failed to allocate memory for base64 encoding\n"); + return -1; + } + + size_t output_len = 0; + int ret_encode = mbedtls_base64_encode((unsigned char*)audio_base64, base64_size - 1, &output_len, + audio_data, audio_size); + if (ret_encode != 0) { + printf("[LN][WS] Base64 encoding failed: %d\n", ret_encode); + free(audio_base64); + return -1; + } + audio_base64[output_len] = '\0'; + } + + // 获取当前时间戳 + uint32_t timestamp; + uapi_get_timestampe_ss(×tamp); + + // 创建JSON消息 + cJSON *json = cJSON_CreateObject(); + if (!json) { + printf("[LN][WS] Failed to create JSON object\n"); + free(audio_base64); + return -1; + } + + // 添加基本字段 + cJSON_AddStringToObject(json, "deviceId", uapi_ln_get_device_id()); + cJSON_AddNumberToObject(json, "timestamp", (double)timestamp); + cJSON_AddStringToObject(json, "messageId", message_id); + cJSON_AddStringToObject(json, "command", "VoiceChatRequest"); + + // 创建data对象 + cJSON *data_obj = cJSON_CreateObject(); + if (!data_obj) { + printf("[LN][WS] Failed to create data object\n"); + cJSON_Delete(json); + free(audio_base64); + return -1; + } + + cJSON_AddStringToObject(data_obj, "audioBase64", audio_base64); + cJSON_AddStringToObject(data_obj, "chatId", g_audio_chat_ctx.chat_id); + cJSON_AddBoolToObject(data_obj, "finished", finished); + + cJSON_AddItemToObject(json, "data", data_obj); + + // 转换为字符串 + char *json_string = cJSON_Print(json); + if (!json_string) { + printf("[LN][WS] Failed to print JSON\n"); + cJSON_Delete(json); + free(audio_base64); + return -1; + } + + // 发送JSON字符串 + int ret = uapi_ws_send_text(json_string); + + printf("[LN][WS] Sent audio data: chatId=%s, messageId=%s, size=%u, finished=%s\n", + g_audio_chat_ctx.chat_id, message_id, audio_size, finished ? "true" : "false"); + + // 清理内存 + free(json_string); + free(audio_base64); + cJSON_Delete(json); + + // 如果音频结束,重置聊天上下文 + if (finished) { + g_audio_chat_ctx.is_active = false; + printf("[LN][WS] Chat ended: %s\n", g_audio_chat_ctx.chat_id); + } + + return ret; +} + +// 开始新的音频聊天 +int ln_ws_start_audio_chat(void) +{ + if (g_audio_chat_ctx.is_active) { + printf("[LN][WS] Audio chat already active: %s\n", g_audio_chat_ctx.chat_id); + return 0; + } + + generate_chat_id(g_audio_chat_ctx.chat_id, sizeof(g_audio_chat_ctx.chat_id)); + g_audio_chat_ctx.message_counter = 0; + g_audio_chat_ctx.is_active = true; + + printf("[LN][WS] Audio chat started: %s\n", g_audio_chat_ctx.chat_id); + return 0; +} + +// 结束当前音频聊天 +int ln_ws_end_audio_chat(void) +{ + if (!g_audio_chat_ctx.is_active) { + printf("[LN][WS] No active audio chat to end\n"); + return -1; + } + printf("[LN][WS] Ending audio chat: %s, total messages: %u\n", + g_audio_chat_ctx.chat_id, g_audio_chat_ctx.message_counter); + // 设置结束标志,等待下次AIVoiceDataCallback_ln调用时发送结束消息 + g_audio_chat_ending = true; + + return 0; +} + +// 获取当前聊天ID +const char* ln_ws_get_current_chat_id(void) +{ + return g_audio_chat_ctx.is_active ? g_audio_chat_ctx.chat_id : NULL; +} + +static void AIVoiceDataCallback_ln(const uint8_t* data, uint32_t size, void* userData) +{ + // MEDIA_INFO_LOG("Received audio data: size=%u bytes", size); + if( g_audio_chat_ctx.is_active ==true) + { + if (g_audio_chat_ending) { + printf("[LN][WS] Sending audio chat end message\n"); + // 发送结束消息 + ln_ws_send_audio_data(data, size, true); + // 重置聊天上下文和结束标志 + g_audio_chat_ctx.is_active = false; + g_audio_chat_ending = false; + } + else + + { + // 使用新的音频发送接口 + ln_ws_send_audio_data(data, size, false); + } + } + + // 检查是否设置了结束标志 + +} + +// "audioBase64": + +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); + + if (!raw_data || data_len == 0) { + printf("[AI] Invalid binary data parameters\n"); + return; + } + + const size_t CHUNK_SIZE = 1024; // 1K 分块大小 + + if (data_len <= CHUNK_SIZE) { + // 数据小于等于1K,直接发送 + printf("[AI] Sending data directly (size: %d bytes)\n", data_len); + AI_SendAudioDataToQueue(raw_data, data_len); + } else { + // 数据大于1K,分块发送 + printf("[AI] Data size %d bytes > %zu bytes, splitting into chunks\n", data_len, CHUNK_SIZE); + + size_t remaining = data_len; + const uint8_t *current_pos = raw_data; + int chunk_count = 0; + + while (remaining > 0) { + size_t current_chunk_size = (remaining > CHUNK_SIZE) ? CHUNK_SIZE : remaining; + + printf("[AI] Sending chunk %d: %zu bytes\n", ++chunk_count, current_chunk_size); + AI_SendAudioDataToQueue(current_pos, current_chunk_size); + + current_pos += current_chunk_size; + remaining -= current_chunk_size; + + // 添加小延迟避免过快发送 + usleep(1000); // 1ms延迟 + } + + printf("[AI] All %d chunks sent successfully\n", chunk_count); + } +} + + +static void ln_ws_incomplete_callback(const char *raw_data, size_t length, bool isBinary) +{ + if (!raw_data || length == 0) { + return; + } + + printf("[LN][WS] Incomplete message received: length=%zu, isBinary=%s\n", + length, isBinary ? "true" : "false"); + + // 检查是否包含 "audioBase64":" 字符串 + const char *audioBase64_key = "\"audioBase64\":\""; + const char *found = strstr(raw_data, audioBase64_key); + + if (found) { + printf("[LN][WS] Found audioBase64 key in incomplete message\n"); + + // 找到 "audioBase64":" 后面的内容 + const char *value_start = found + strlen(audioBase64_key); + + // 找到结束引号 + const char *value_end = strchr(value_start, '"'); + + if (value_end) { + // 找到结束引号,数据完整 + size_t base64_length = value_end - value_start; + printf("[LN][WS] Found complete Base64 data: length=%zu\n", base64_length); + + // 处理完整的Base64数据 + ln_ws_process_base64_data(value_start, base64_length); + + // 重置缓存状态 + ln_ws_base64_cache_reset(); + + } else { + // 没有找到结束引号,数据不完整 + printf("[LN][WS] No closing quote found, data incomplete\n"); + + // 处理不完整的Base64数据 + size_t remaining_length = length - (value_start - raw_data); + if (remaining_length > 0) { + if (!g_base64_in_progress) { + g_base64_in_progress = true; + } + ln_ws_process_incremental_base64_data(value_start, remaining_length); + } + } + } else { + // 没有找到audioBase64关键字 + if (g_base64_in_progress) { + // 如果正在进行Base64数据接收,检查是否包含结束引号 + const char *end_quote = strchr(raw_data, '"'); + if (end_quote) { + // 找到结束引号,处理到引号位置的数据 + size_t data_before_quote = end_quote - raw_data; + if (data_before_quote > 0) { + printf("[LN][WS] Found end quote, processing data before quote: %zu bytes\n", data_before_quote); + ln_ws_process_incremental_base64_data(raw_data, data_before_quote); + } + + // 处理缓存中剩余的数据(如果有的话) + if (g_base64_cache_size > 0) { + printf("[LN][WS] Processing remaining cached data: %zu bytes\n", g_base64_cache_size); + ln_ws_process_base64_data(g_base64_cache, g_base64_cache_size); + ln_ws_base64_cache_reset(); + } + + // 结束Base64数据接收 + g_base64_in_progress = false; + printf("[LN][WS] Base64 data reception completed\n"); + } else { + // 没有结束引号,继续处理增量数据 + printf("[LN][WS] Continuing Base64 data reception, processing incrementally\n"); + ln_ws_process_incremental_base64_data(raw_data, length); + } + } else { + printf("[LN][WS] No audioBase64 key found in message\n"); + } + } +} /* * 启动:开启监督线程与心跳机制(心跳线程首次连接成功后会自然启动) * - 若已在运行则直接返回成功 * - callback: 消息处理回调函数,可以为NULL使用默认处理 */ -int ln_ws_start(ws_message_callback_t callback) +int ln_ws_start(ws_message_callback_t_ln callback) { if (g_ws_running) { return 0; @@ -432,21 +592,24 @@ int ln_ws_start(ws_message_callback_t callback) } else { printf("[LN][WS] Using default message handler\n"); } - if (!g_ws_main_thread) { - app_task_definition_ext_t main_task = { - .attr = { - .name = "ln_ws_main", - .stack_size = 0x2000, - .priority = osPriorityNormal - }, - .func = ln_ws_main_thread, - .task_handle = &g_ws_main_thread, - .create_queue_handle = ln_ws_queue_init, - .use_mem = USE_EXTEND_MEM, - .inst_args = NULL - }; - (void)uapi_create_osThread_custom_with_args(&main_task); + + // 设置回调函数结构体 + g_ws_callbacks.message_callback = ln_ws_message_callback; + g_ws_callbacks.event_callback = ln_ws_event_callback; + g_ws_callbacks.url_callback = ln_ws_url_callback; + g_ws_callbacks.heartbeat_callback = ln_ws_heartbeat_callback; + g_ws_callbacks.binary_callback = ai_binary_message_handler; + g_ws_callbacks.incomplete_callback= ln_ws_incomplete_callback; + // 启动WebSocket服务 + int ret = uapi_ws_start(&g_ws_callbacks); + if (ret != 0) { + printf("[LN][WS] uapi_ws_start failed: %d\n", ret); + g_ws_running = 0; + return ret; } + + old_Start_AIVoice(AIVoiceDataCallback_ln,NULL,NULL,NULL,false); + cat1_api_keep_wakeup(1); cat1_api_wakeup_cat1(1); @@ -457,73 +620,23 @@ int ln_ws_start(ws_message_callback_t callback) int ln_ws_send_text(const char *text) { if (!text) return -1; - if (g_ws_connected != WS_STATE_CONNECTED || g_ws_client_fd < 0) { + if (g_ws_connected != WS_STATE_CONNECTED) { 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 ln_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; + return uapi_ws_send_text(text); } -/* 强制重连 */ -int ln_ws_force_reconnect(void) -{ - if (!g_ws_running) return -1; - - ws_msg_info_t msg; - msg.msg_type = WS_MSG_TYPE_FORCE_RECONNECT; - msg.data_len = 0; - msg.data.raw_json = NULL; - - osStatus_t ret = osMessageQueuePut(g_ws_msg_queue, &msg, 0, 0); - return (ret == osOK) ? 0 : -1; -} - -/* 通过队列停止WebSocket服务 */ -int ln_ws_stop_async(void) -{ - if (!g_ws_running) return -1; - - ws_msg_info_t msg; - msg.msg_type = WS_MSG_TYPE_STOP; - msg.data_len = 0; - msg.data.raw_json = NULL; - // msg.data = NULL; - - osStatus_t ret = osMessageQueuePut(g_ws_msg_queue, &msg, 0, 0); - return (ret == osOK) ? 0 : -1; -} /* 停止:关闭心跳并关闭连接 */ int ln_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 = ln_ws_close_with_status(g_ws_client_fd); - g_ws_client_fd = -1; - } + int ret = uapi_ws_stop(); g_ws_connected = WS_STATE_DISCONNECTED; - // 删除消息队列 - if (g_ws_msg_queue != NULL) { - osMessageQueueDelete(g_ws_msg_queue); - g_ws_msg_queue = NULL; - } + // 清理Base64缓存 + ln_ws_base64_cache_cleanup(); return ret; } @@ -557,7 +670,7 @@ const char* ln_ws_get_connection_state_string(void) * @brief 注册消息处理回调函数(公共接口) * @param callback 回调函数 */ -void ln_ws_register_message_callback(ws_message_callback_t callback) +void ln_ws_register_message_callback(ws_message_callback_t_ln callback) { g_ws_message_callback = callback; if (callback != NULL) { @@ -604,6 +717,8 @@ static ws_command_t ln_ws_parse_command_string(const char *command_str) return WS_CMD_PING; } else if (strcmp(command_str, "Pong") == 0) { return WS_CMD_PONG; + } else if (strcmp(command_str, "VoiceChatResponse") == 0) { + return WS_CMD_VOICE_CHAT_RESPONSE; } else { return WS_CMD_UNKNOWN; } @@ -615,6 +730,13 @@ static ws_command_t ln_ws_parse_command_string(const char *command_str) * @param data_msg 输出的消息结构体 * @return 0成功,-1失败 */ + +// {"deviceId":"DEVICE_PC_TEST_001","timestamp":1758880220, +// "messageId":"d63a0670a3854d709cb95720e1e89f83","command":"VoiceChatResponse","data": +// {"finished":false,"chatId":"chat_1758880210_2087035594", +// "data":{"audioUrl":"http://ambybox-api.test.leiniao.com/ambybox-device-api/file/774317549446_20250926175018_1758880218961.mp3", +// "textContent":"(颔首,夹着烟的手指轻敲桌面)来者何人?"}}} + static int ln_ws_parse_data_message(const char *json_str, ws_data_msg_t *data_msg) { if (!json_str || !data_msg) { @@ -676,6 +798,46 @@ static int ln_ws_parse_data_message(const char *json_str, ws_data_msg_t *data_ms return -1; } + // 解析VoiceChatResponse特定字段 + if (data_msg->command == WS_CMD_VOICE_CHAT_RESPONSE) { + cJSON *data_obj = cJSON_GetObjectItem(root, "data"); + if (data_obj && cJSON_IsObject(data_obj)) { + // 解析finished字段 + cJSON *finished = cJSON_GetObjectItem(data_obj, "finished"); + if (finished && cJSON_IsBool(finished)) { + data_msg->finished = cJSON_IsTrue(finished); + } + + // 解析chatId字段 + cJSON *chat_id = cJSON_GetObjectItem(data_obj, "chatId"); + if (chat_id && cJSON_IsString(chat_id) && chat_id->valuestring) { + strncpy(data_msg->chat_id, chat_id->valuestring, sizeof(data_msg->chat_id) - 1); + data_msg->chat_id[sizeof(data_msg->chat_id) - 1] = '\0'; + } + + // 解析data.data字段 + cJSON *data_data = cJSON_GetObjectItem(data_obj, "data"); + if (data_data && cJSON_IsObject(data_data)) { + // 解析audioUrl字段 + cJSON *audio_url = cJSON_GetObjectItem(data_data, "audioUrl"); + if (audio_url && cJSON_IsString(audio_url) && audio_url->valuestring) { + strncpy(data_msg->audio_url, audio_url->valuestring, sizeof(data_msg->audio_url) - 1); + data_msg->audio_url[sizeof(data_msg->audio_url) - 1] = '\0'; + } + + // 解析textContent字段 + cJSON *text_content = cJSON_GetObjectItem(data_data, "textContent"); + if (text_content && cJSON_IsString(text_content) && text_content->valuestring) { + strncpy(data_msg->text_content, text_content->valuestring, sizeof(data_msg->text_content) - 1); + data_msg->text_content[sizeof(data_msg->text_content) - 1] = '\0'; + } + } + } + + printf("[LN][WS] VoiceChatResponse parsed: finished=%s, chatId=%s, audioUrl=%s, textContent=%s\n", + data_msg->finished ? "true" : "false", data_msg->chat_id, data_msg->audio_url, data_msg->text_content); + } + // 保存原始JSON字符串 data_msg->raw_json = (char*)malloc(strlen(json_str) + 1); if (data_msg->raw_json) { @@ -691,35 +853,6 @@ static int ln_ws_parse_data_message(const char *json_str, ws_data_msg_t *data_ms return 0; } -/** - * @brief 将消息写入队列 - * @param data_msg 消息结构体指针 - * @return 0成功,-1失败 - */ -static int ln_ws_queue_data_message(const ws_data_msg_t *data_msg) -{ - if (!data_msg || !g_ws_running) { - printf("[LN][WS] Invalid parameters or WebSocket not running\n"); - return -1; - } - - ws_msg_info_t msg; - msg.msg_type = WS_MSG_TYPE_DATA_MESSAGE; - msg.data_len = sizeof(ws_data_msg_t); - - // 直接复制数据到结构体中 - memcpy(&msg.data, data_msg, sizeof(ws_data_msg_t)); - - // 写入队列 - osStatus_t ret = osMessageQueuePut(g_ws_msg_queue, &msg, 0, 0); - if (ret != osOK) { - printf("[LN][WS] Failed to put data message to queue: %d\n", ret); - return -1; - } - - printf("[LN][WS] Data message queued successfully count %d\n", osMessageQueueGetCount(g_ws_msg_queue)); - return 0; -} /** * @brief 释放消息结构体内存 @@ -774,6 +907,13 @@ static void ln_ws_default_message_handler(const ws_data_msg_t *data_msg, ws_comm case WS_CMD_SCHEDULE_REMINDER_CFG: printf("[LN][WS] Default: Processing ScheduleReminderConfig message\n"); break; + case WS_CMD_VOICE_CHAT_RESPONSE: + printf("[LN][WS] Default: Processing VoiceChatResponse message\n"); + printf("[LN][WS] VoiceChatResponse details: finished=%s, chatId=%s\n", + data_msg->finished ? "true" : "false", data_msg->chat_id); + printf("[LN][WS] Audio URL: %s\n", data_msg->audio_url); + printf("[LN][WS] Text Content: %s\n", data_msg->text_content); + break; default: printf("[LN][WS] Default: Processing unknown command: %d\n", type); break; @@ -798,126 +938,392 @@ static void ln_ws_process_message_with_callback(const ws_data_msg_t *data_msg) } } -/* ============================ WebSocket关闭状态管理 ============================ */ + +/* ============================ 公共接口函数 ============================ */ + + /** - * @brief 带状态跟踪的WebSocket关闭函数 - * @param client_fd WebSocket客户端文件描述符 - * @return WebSocketClientClose的返回值 + * @brief 进入休眠状态(公共接口) + * @return 0成功,-1失败 */ -static int ln_ws_close_with_status(int client_fd) +int ln_ws_enter_sleep(void) { - 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); + printf("[LN][WS] Entering sleep mode\n"); - // 执行关闭操作 - int ret = WebSocketClientClose(client_fd); + if (!g_ws_running) { + printf("[LN][WS] WebSocket service not running, cannot enter sleep\n"); + return -1; + } - // 注意:成功状态由 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"); + // 使用新的接口进入休眠 + int ret = uapi_ws_enter_sleep(); + if (ret == 0) { + g_ws_connected = WS_STATE_SLEEP; + printf("[LN][WS] Sleep mode activated, all operations paused\n"); } return ret; } /** - * @brief 获取当前WebSocket关闭状态 - * @return 当前关闭状态 + * @brief 退出休眠状态(公共接口) + * @return 0成功,-1失败 */ -static ws_close_status_t ln_ws_get_close_status(void) +int ln_ws_exit_sleep(void) { - return g_ws_close_status; + 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", ln_ws_get_connection_state_string()); + return -1; + } + + // 使用新的接口退出休眠 + int ret = uapi_ws_exit_sleep(); + if (ret == 0) { + g_ws_connected = WS_STATE_DISCONNECTED; + printf("[LN][WS] Sleep mode deactivated, resuming normal operations\n"); + } + + return ret; } +/* ============================ Base64缓存管理函数 ============================ */ + /** - * @brief 重置WebSocket关闭状态 + * @brief 初始化Base64缓存 */ -static void ln_ws_reset_close_status(void) +static void ln_ws_base64_cache_init(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); + if (g_base64_cache == NULL) { + g_base64_cache_capacity = 4096; // 初始容量4KB + g_base64_cache = (char*)malloc(g_base64_cache_capacity); + if (g_base64_cache) { + g_base64_cache_size = 0; + g_base64_in_progress = false; + printf("[LN][WS] Base64 cache initialized with capacity: %zu\n", g_base64_cache_capacity); + } else { + printf("[LN][WS] Failed to initialize Base64 cache\n"); + g_base64_cache_capacity = 0; + } + } } -/* ============================ 公共接口函数 ============================ */ - /** - * @brief 获取WebSocket关闭状态(公共接口) - * @return 当前关闭状态 + * @brief 清理Base64缓存 */ -int ln_ws_get_close_status_public(void) +static void ln_ws_base64_cache_cleanup(void) { - return (int)ln_ws_get_close_status(); + if (g_base64_cache) { + free(g_base64_cache); + g_base64_cache = NULL; + g_base64_cache_size = 0; + g_base64_cache_capacity = 0; + g_base64_in_progress = false; + printf("[LN][WS] Base64 cache cleaned up\n"); + } } /** - * @brief 打印WebSocket状态信息(调试用) + * @brief 重置Base64缓存 */ -void ln_ws_print_status_info(void) +static void ln_ws_base64_cache_reset(void) { - printf("[LN][WS] === WebSocket Status Info ===\n"); - printf("[LN][WS] Connection State: %s (%d)\n", ln_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"); + if (g_base64_cache) { + g_base64_cache_size = 0; + g_base64_in_progress = false; + printf("[LN][WS] Base64 cache reset\n"); + } } /** - * @brief 进入休眠状态(公共接口) + * @brief 向Base64缓存追加数据 + * @param data 要追加的数据 + * @param length 数据长度 * @return 0成功,-1失败 */ -int ln_ws_enter_sleep(void) +static int ln_ws_base64_cache_append(const char *data, size_t length) { - printf("[LN][WS] Entering sleep mode\n"); + if (!data || length == 0) { + return -1; + } - if (!g_ws_running) { - printf("[LN][WS] WebSocket service not running, cannot enter sleep\n"); - return -1; + // 确保缓存已初始化 + if (g_base64_cache == NULL) { + ln_ws_base64_cache_init(); + if (g_base64_cache == NULL) { + return -1; + } } - // 如果当前有连接,先断开 - if (g_ws_client_fd >= 0) { - printf("[LN][WS] Disconnecting before entering sleep mode\n"); - ln_ws_close_with_status(g_ws_client_fd); - } + // 检查是否需要扩容 + if (g_base64_cache_size + length > g_base64_cache_capacity) { + size_t new_capacity = g_base64_cache_capacity * 2; + while (new_capacity < g_base64_cache_size + length) { + new_capacity *= 2; + } + + char *new_cache = (char*)realloc(g_base64_cache, new_capacity); + if (new_cache) { + g_base64_cache = new_cache; + g_base64_cache_capacity = new_capacity; + printf("[LN][WS] Base64 cache expanded to capacity: %zu\n", g_base64_cache_capacity); + } else { + printf("[LN][WS] Failed to expand Base64 cache\n"); + return -1; + } + } - // 设置状态为休眠 - g_ws_connected = WS_STATE_SLEEP; - printf("[LN][WS] Sleep mode activated, all operations paused\n"); + // 追加数据 + memcpy(g_base64_cache + g_base64_cache_size, data, length); + g_base64_cache_size += length; + g_base64_cache[g_base64_cache_size] = '\0'; // 确保字符串结束 + printf("[LN][WS] Base64 cache appended %zu bytes, total size: %zu\n", length, g_base64_cache_size); return 0; } /** - * @brief 退出休眠状态(公共接口) - * @return 0成功,-1失败 + * @brief 处理Base64数据(验证、解码) + * @param base64_data Base64数据 + * @param length 数据长度 */ -int ln_ws_exit_sleep(void) +static void ln_ws_process_base64_data(const char *base64_data, size_t length) { - printf("[LN][WS] Exiting sleep mode\n"); + if (!base64_data || length == 0) { + printf("[LN][WS] Invalid Base64 data parameters\n"); + return; + } - if (g_ws_connected != WS_STATE_SLEEP) { - printf("[LN][WS] Not in sleep mode, current state: %s\n", ln_ws_get_connection_state_string()); - return -1; + printf("[LN][WS] Processing Base64 data: length=%zu\n", length); + + // 检查Base64长度是否有效(必须是4的倍数) + if (length % 4 != 0) { + printf("[LN][WS] Invalid Base64 length: %zu (not multiple of 4)\n", length); + return; } - // 设置状态为断开,让主线程重新开始连接 - g_ws_connected = WS_STATE_DISCONNECTED; - printf("[LN][WS] Sleep mode deactivated, resuming normal operations\n"); + // 检查是否包含有效的Base64字符 + bool valid_base64 = true; + for (size_t i = 0; i < length; i++) { + char c = base64_data[i]; + if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || c == '+' || c == '/' || c == '=')) { + valid_base64 = false; + break; + } + } - return 0; + if (!valid_base64) { + printf("[LN][WS] Invalid Base64 characters detected\n"); + return; + } + + printf("[LN][WS] Valid Base64 format detected, attempting decode\n"); + + // 计算解码后的数据长度 + size_t decoded_length = (length * 3) / 4; + + // 如果Base64字符串以'='结尾,需要调整长度 + if (length >= 2 && base64_data[length - 1] == '=') { + if (base64_data[length - 2] == '=') { + decoded_length -= 2; // 两个填充字符 + } else { + decoded_length -= 1; // 一个填充字符 + } + } + + printf("[LN][WS] Expected decoded length: %zu\n", decoded_length); + + // 分配内存进行解码 + uint8_t *decoded_data = (uint8_t*)malloc(decoded_length); + if (decoded_data) { + size_t actual_decoded_length = 0; + + // 创建临时字符串进行解码 + char *temp_base64 = (char*)malloc(length + 1); + if (temp_base64) { + memcpy(temp_base64, base64_data, length); + temp_base64[length] = '\0'; + + // 执行Base64解码 + int ret = mbedtls_base64_decode(decoded_data, decoded_length, + &actual_decoded_length, + (const unsigned char*)temp_base64, + length); + + if (ret == 0) { + printf("[LN][WS] Base64 decode successful: %zu bytes decoded\n", actual_decoded_length); + + // 这里可以处理解码后的音频数据 + // 例如:调用音频处理回调函数 + printf("[LN][WS] Decoded audio data: first 16 bytes: "); + for (size_t i = 0; i < (actual_decoded_length < 16 ? actual_decoded_length : 16); i++) { + printf("%02X ", decoded_data[i]); + } + printf("\n"); + + // TODO: 在这里可以调用音频数据处理函数 + AI_SendAudioDataToQueue(decoded_data, actual_decoded_length); + + } else { + printf("[LN][WS] Base64 decode failed: %d\n", ret); + } + + free(temp_base64); + } + + free(decoded_data); + } else { + printf("[LN][WS] Failed to allocate memory for decoded data\n"); + } +} + +/** + * @brief 增量处理Base64数据(只缓存多余字符) + * @param new_data 新的Base64数据 + * @param new_length 新数据长度 + */ +static void ln_ws_process_incremental_base64_data(const char *new_data, size_t new_length) +{ + if (!new_data || new_length == 0) { + return; + } + + printf("[LN][WS] Processing incremental Base64 data: length=%zu\n", new_length); + + // 确保缓存已初始化 + if (g_base64_cache == NULL) { + ln_ws_base64_cache_init(); + if (g_base64_cache == NULL) { + return; + } + } + + // 将新数据追加到缓存 + if (ln_ws_base64_cache_append(new_data, new_length) != 0) { + printf("[LN][WS] Failed to append data to cache\n"); + return; + } + + // 计算可以完整解码的数据长度(必须是4的倍数) + size_t decodable_length = (g_base64_cache_size / 4) * 4; + + if (decodable_length > 0) { + printf("[LN][WS] Decodable length: %zu, total cache: %zu\n", decodable_length, g_base64_cache_size); + + // 解码可用的完整数据 + ln_ws_process_base64_data(g_base64_cache, decodable_length); + + // 保留多余的字符(不足4个字符的部分) + size_t remaining_length = g_base64_cache_size - decodable_length; + if (remaining_length > 0) { + printf("[LN][WS] Keeping %zu remaining characters in cache\n", remaining_length); + + // 将剩余字符移到缓存开头 + memmove(g_base64_cache, g_base64_cache + decodable_length, remaining_length); + g_base64_cache_size = remaining_length; + g_base64_cache[g_base64_cache_size] = '\0'; + } else { + // 没有剩余字符,重置缓存 + ln_ws_base64_cache_reset(); + } + } else { + printf("[LN][WS] Not enough data for Base64 decode yet: %zu\n", g_base64_cache_size); + } } +//////////////////////////////////////////////////////////////////////////// + +// int uapi_ln_download_mp3_file(const char *url) +// { +// if (!url) +// { +// WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] url or save_path is NULL"); +// return ERRCODE_FAIL; +// } + +// WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] 开始下载: %s -> %s", url); + +// HttpClient client = {0}; +// HttpClientData client_data = {0}; +// int ret = -1; +// size_t total_written = 0; + + +// HTTPC_RESULT prep = ln_http_prepare_auto(&client_data, 2048, LN_CLIENT_PREPARE_SIZE * 2); +// if (prep != HTTP_SUCCESS) +// { +// WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] prepare failed: %d", prep); + +// return ERRCODE_FAIL; +// } + +// // 建立连接 +// ret = HttpClientConn(&client, url); +// if (ret != 0) +// { +// WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] connection failed, ret=0x%x", ret); + +// HttpClientUnprepare(&client_data); +// return ERRCODE_FAIL; +// } + +// // 发送GET请求 +// ret = HttpClientSend(&client, url, HTTP_GET, &client_data); +// if (ret != 0) +// { +// WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] send request failed, ret=0x%x", ret); + +// HttpClientClse(&client); +// HttpClientUnprepare(&client_data); +// return ERRCODE_FAIL; +// } + +// // 接收响应数据 +// do +// { +// HTTPC_RESULT recv_res = HttpClientRecvResponse(&client, &client_data); +// WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] recv_res = %d", recv_res); +// if (recv_res == HTTP_SUCCESS || recv_res == HTTP_EAGAIN) +// { +// if (client_data.contentBlockLen > 0) +// { +// // size_t written = fwrite(client_data.responseBuf, 1, client_data.contentBlockLen, fd); + +// WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] written = %zu", written); +// if (written != client_data.contentBlockLen) +// { +// WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] write failed, written=%zu, expected=%d", +// written, client_data.contentBlockLen); +// HttpClientClse(&client); +// HttpClientUnprepare(&client_data); +// return ERRCODE_FAIL; +// } +// total_written += written; +// } +// } +// } while (client_data.isMore); +// if (client_data.responseContentLen == 0) +// { +// WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] receive length = 0"); +// } +// WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] responsecode = %d", client.responseCode); +// if (total_written == client_data.responseContentLen && client.responseCode == 200 && total_written > 0) +// { +// ret = ERRCODE_SUCC; +// WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] The file is downloaded successfully and the data is complete!"); +// } +// else +// { +// ret = ERRCODE_FAIL; +// WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] The file data is incomplete!"); +// } +// HttpClientUnprepare(&client_data); +// HttpClientClse(&client); +// WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] download process finished,ret=%d", ret); +// return ret; +// } \ No newline at end of file -- 2.17.1