#include <iostream>
#include <unistd.h>
#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <queue>
#include "securec.h"
#include "audio_manager.h"
const uint32_t DEFAULT_SAMPLE_RATE = SAMPLERATE_16000;
static MediaMutexHandle GetGlobalMutex(void);
+int32_t AI_GetQueueStatus(uint32_t* queueSize, uint32_t* queueCapacity);
// AI语音音频参数结构体IVoiceAudioParams;
static MediaMutexHandle g_aiVoiceContextMutex = nullptr;
static AIVoiceContext *g_aiVoiceContext = nullptr;
+// 文件缓存相关变量
+static const char* AI_AUDIO_CACHE_FILE = "/user/ai_audio_cache.dat";
+static const size_t AI_AUDIO_CHUNK_SIZE = 1024; // 固定音频块大小:1KB
+static MediaMutexHandle g_fileCacheMutex = nullptr;
+static long g_fileCacheOffset = 0; // 文件读取偏移量
+
// OGG相关内存管理函数
static void MediaMemFree(void **src)
{
return tmp;
}
+// 文件缓存操作辅助函数
+/**
+ * @brief 初始化文件缓存互斥锁
+ */
+static void AI_InitFileCacheMutex()
+{
+ if (g_fileCacheMutex == nullptr) {
+ g_fileCacheMutex = MediaMutexCreate(nullptr);
+ if (g_fileCacheMutex == nullptr) {
+ MEDIA_ERR_LOG("Failed to create file cache mutex");
+ }
+ }
+}
+
+/**
+ * @brief 将音频数据写入文件缓存
+ * @param msg 音频消息
+ * @return 0 成功,-1 失败
+ */
+static int32_t AI_WriteToFileCache(const AIVoiceQueueMessage& msg)
+{
+ AI_InitFileCacheMutex();
+ if (g_fileCacheMutex == nullptr) {
+ return -1;
+ }
+
+ MediaMutexLock(g_fileCacheMutex);
+
+ // 写入文件
+ FILE *file = fopen(AI_AUDIO_CACHE_FILE, "ab");
+ if (file != NULL) {
+ // 写入音频数据
+ size_t written = fwrite(msg.data, 1, msg.size, file);
+ if (written != msg.size) {
+ MEDIA_ERR_LOG("Failed to write audio data to file cache");
+ fclose(file);
+ MediaMutexUnLock(g_fileCacheMutex);
+ return -1;
+ }
+
+ fclose(file);
+ MEDIA_INFO_LOG("Successfully wrote %u bytes to file cache", msg.size);
+ MediaMutexUnLock(g_fileCacheMutex);
+ return 0;
+ } else {
+ MEDIA_ERR_LOG("Failed to open file cache for writing");
+ MediaMutexUnLock(g_fileCacheMutex);
+ return -1;
+ }
+}
+
+/**
+ * @brief 从文件缓存读取音频数据
+ * @param msg 输出的音频消息
+ * @return 0 成功,-1 失败
+ */
+static int32_t AI_ReadFromFileCache(AIVoiceQueueMessage& msg)
+{
+ AI_InitFileCacheMutex();
+ if (g_fileCacheMutex == nullptr) {
+ return -1;
+ }
+
+ MediaMutexLock(g_fileCacheMutex);
+
+ // 从文件读取
+ FILE *file = fopen(AI_AUDIO_CACHE_FILE, "rb");
+ if (file != NULL) {
+ // 设置文件读取位置到当前偏移量
+ if (fseek(file, g_fileCacheOffset, SEEK_SET) != 0) {
+ MEDIA_ERR_LOG("Failed to seek to offset %ld in file cache", g_fileCacheOffset);
+ fclose(file);
+ MediaMutexUnLock(g_fileCacheMutex);
+ return -1;
+ }
+
+ // 分配固定大小的内存
+ msg.data = static_cast<uint8_t*>(malloc(AI_AUDIO_CHUNK_SIZE));
+ if (msg.data == nullptr) {
+ MEDIA_ERR_LOG("Failed to allocate memory for cached audio data");
+ fclose(file);
+ MediaMutexUnLock(g_fileCacheMutex);
+ return -1;
+ }
+
+ // 读取固定大小的音频数据
+ size_t read_count = fread(msg.data, 1, AI_AUDIO_CHUNK_SIZE, file);
+ if (read_count == 0) {
+ // 没有数据可读,检查是否到达文件末尾
+ fseek(file, 0, SEEK_END);
+ long file_size = ftell(file);
+
+ if (g_fileCacheOffset >= file_size) {
+ // 已到达文件末尾,清理文件内容并重置偏移量
+ MEDIA_INFO_LOG("Reached end of file cache (offset: %ld, file size: %ld), clearing cache",
+ g_fileCacheOffset, file_size);
+ fclose(file);
+
+ // 清理文件内容
+ if (remove(AI_AUDIO_CACHE_FILE) == 0) {
+ MEDIA_INFO_LOG("File cache cleared successfully");
+ } else {
+ MEDIA_ERR_LOG("Failed to clear file cache");
+ }
+
+ // 重置偏移量
+ g_fileCacheOffset = 0;
+
+ free(msg.data);
+ msg.data = nullptr;
+ MediaMutexUnLock(g_fileCacheMutex);
+ return -1;
+ } else {
+ MEDIA_ERR_LOG("No data available in file cache at offset %ld", g_fileCacheOffset);
+ free(msg.data);
+ msg.data = nullptr;
+ fclose(file);
+ MediaMutexUnLock(g_fileCacheMutex);
+ return -1;
+ }
+ }
+
+ // 更新偏移量
+ g_fileCacheOffset += read_count;
+
+ // 设置实际读取的数据大小
+ msg.size = read_count;
+
+ fclose(file);
+ MEDIA_INFO_LOG("Read %u bytes from file cache at offset %ld", msg.size, g_fileCacheOffset - read_count);
+ MediaMutexUnLock(g_fileCacheMutex);
+ return 0;
+ } else {
+ MEDIA_ERR_LOG("Failed to open file cache for reading");
+ MediaMutexUnLock(g_fileCacheMutex);
+ return -1;
+ }
+}
+
+/**
+ * @brief 检查文件缓存是否有数据
+ * @return true 有数据,false 无数据
+ */
+static bool AI_HasFileCacheData()
+{
+ AI_InitFileCacheMutex();
+ if (g_fileCacheMutex == nullptr) {
+ return false;
+ }
+
+ MediaMutexLock(g_fileCacheMutex);
+
+ // 检查文件是否存在且不为空
+ FILE *file = fopen(AI_AUDIO_CACHE_FILE, "rb");
+ bool hasData = false;
+ if (file != NULL) {
+ fseek(file, 0, SEEK_END);
+ long fileSize = ftell(file);
+
+ // 检查文件大小是否大于0,且偏移量小于文件大小(还有数据可读)
+ hasData = (fileSize > 0) && (g_fileCacheOffset < fileSize);
+
+ fclose(file);
+ }
+
+ MediaMutexUnLock(g_fileCacheMutex);
+ return hasData;
+}
+
+/**
+ * @brief 清理文件缓存
+ */
+static void AI_ClearFileCache()
+{
+ AI_InitFileCacheMutex();
+ if (g_fileCacheMutex == nullptr) {
+ return;
+ }
+
+ MediaMutexLock(g_fileCacheMutex);
+
+ // 重置文件偏移量
+ g_fileCacheOffset = 0;
+
+ // 删除文件
+ if (remove(AI_AUDIO_CACHE_FILE) == 0) {
+ MEDIA_INFO_LOG("File cache cleared successfully");
+ }
+
+ MediaMutexUnLock(g_fileCacheMutex);
+}
+
// OGG头部初始化函数
static int32_t AudioCapturerOggHeaderInit(CaptureOggContext *oggCtx, uint32_t channelCount, uint32_t sampleRate)
{
if (context->queueMsgCount > 0) {
context->queueMsgCount--;
}
+
+ // 检查并处理文件缓存中的数据(每次只处理一个)
+ if (AI_HasFileCacheData()) {
+ AIVoiceQueueMessage cachedMsg;
+ if (AI_ReadFromFileCache(cachedMsg) == 0) {
+ MEDIA_INFO_LOG("RunAIVoice: restoring cached data to queue: size=%u, timestamp=%u",
+ cachedMsg.size, cachedMsg.timestamp);
+
+ // 将缓存的数据重新写入队列
+ if (cachedMsg.data != nullptr && cachedMsg.size > 0) {
+ osStatus_t queueStatus = osMessageQueuePut(context->inputQueue, &cachedMsg, 0, 0);
+ if (queueStatus == osOK) {
+ context->queueMsgCount++;
+ MEDIA_INFO_LOG("Successfully restored %u bytes from cache to queue", cachedMsg.size);
+ } else {
+ MEDIA_ERR_LOG("Failed to restore cached data to queue: %d", queueStatus);
+ // 如果队列写入失败,释放内存
+ free(cachedMsg.data);
+ }
+ }
+ } else {
+ MEDIA_ERR_LOG("Failed to read from file cache");
+ }
+ }
} else if (status == osErrorTimeout) {
// 超时,继续循环检查停止标志
continue;
task_cfg.create_queue_handle = CreateAIVoiceQueue; // 设置队列创建函数
task_cfg.attr.name = "AIVoiceThread";
- task_cfg.attr.priority = (osPriority_t)16; // 使用合适的优先级
+ task_cfg.attr.priority = (osPriority_t)24; // 使用合适的优先级
task_cfg.attr.stack_size = 0x2000; // 8KB栈空间
task_cfg.func = AIVoiceThread;
task_cfg.use_mem = USE_EXTEND_MEM;
g_aiVoiceContext->threadName[sizeof(g_aiVoiceContext->threadName) - 1] = '\0'; // 确保字符串结束
MEDIA_INFO_LOG("AI voice thread created successfully with name: %s", g_aiVoiceContext->threadName);
+
+ AI_ClearFileCache();
+
return 0;
EXIT:
// 清理已分配的资源
// 首先清理AI语音资源(包括队列)
DeinitAIVoiceResources(context);
+ // 清理文件缓存
+ AI_ClearFileCache();
+
// 清理线程同步资源
if (context->cond != nullptr) {
(void)MediaThreadCondDestroy(&context->cond);
MEDIA_ERR_LOG("Invalid audio data parameters");
return -1;
}
+ uint32_t queueSize, queueCapacity;
+ AI_GetQueueStatus(&queueSize, &queueCapacity);
+ // 检查是否有文件缓存数据需要优先处理
+ bool hasFileCacheData = AI_HasFileCacheData();
+
+ if(queueSize>=queueCapacity)
+ {
+ MEDIA_INFO_LOG("Queue is full, writing to file cache instead");
+
+ // 分配内存并复制数据
+ uint8_t *dataCopy = (uint8_t*)malloc(size);
+ if (dataCopy == nullptr) {
+ MEDIA_ERR_LOG("Failed to allocate memory for audio data");
+ return -1;
+ }
+
+ memcpy(dataCopy, data, size);
+
+ // 创建队列消息
+ AIVoiceQueueMessage msg;
+ msg.data = dataCopy;
+ msg.size = size;
+ msg.timestamp = (uint32_t)time(NULL);
+
+ // 写入文件缓存
+ int32_t result = AI_WriteToFileCache(msg);
+ if (result == 0) {
+ MEDIA_INFO_LOG("Successfully wrote %u bytes to file cache", size);
+ return 0;
+ } else {
+ MEDIA_ERR_LOG("Failed to write to file cache");
+ free(dataCopy);
+ return -1;
+ }
+ }
+
+ // 如果队列有空闲但存在文件缓存数据,优先处理缓存数据
+ if (hasFileCacheData) {
+ MEDIA_INFO_LOG("Queue has space but file cache exists, writing new data to cache to maintain order");
+
+ // 分配内存并复制数据
+ uint8_t *dataCopy = (uint8_t*)malloc(size);
+ if (dataCopy == nullptr) {
+ MEDIA_ERR_LOG("Failed to allocate memory for audio data");
+ return -1;
+ }
+
+ memcpy(dataCopy, data, size);
+
+ // 创建队列消息
+ AIVoiceQueueMessage msg;
+ msg.data = dataCopy;
+ msg.size = size;
+ msg.timestamp = (uint32_t)time(NULL);
+
+ // 写入文件缓存以保持顺序
+ int32_t result = AI_WriteToFileCache(msg);
+ if (result == 0) {
+ MEDIA_INFO_LOG("Successfully wrote %u bytes to file cache to maintain order", size);
+ return 0;
+ } else {
+ MEDIA_ERR_LOG("Failed to write to file cache for order maintenance");
+ free(dataCopy);
+ return -1;
+ }
+ }
+
+ MEDIA_ERR_LOG("AI_GetQueueStatus %d,%d\r\n",queueSize,queueCapacity);
MediaMutexHandle mutex = GetGlobalMutex();
if (mutex == nullptr) {
/* AT^LN_WS_START_AUDIO_CHAT - 开始音频聊天 */
static uint32_t ln_ws_start_audio_chat_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
{
- VAD_UNUSED(para);
- VAD_UNUSED(paraLen);
VAD_UNUSED(strCommonCmd);
- printf("[LN][WS_START_AUDIO_CHAT]\n");
- int ret = ln_ws_start_audio_chat();
+
+ ln_audio_chat_command_t command_type = LN_AUDIO_CHAT_VOICE_CHAT_REQUEST; // 默认值
+
+ // 解析参数
+ if (para != NULL && paraLen > 0) {
+ uint8_t argv[LN_MAX_ARGC][LN_MAX_ARGV_LEN] = {0};
+ uint32_t argc = 0;
+
+ if (ConvertCmdToParam(para, paraLen, argv, &argc) == VAD_SUCCESS && argc >= 1) {
+ const char *param = (const char *)argv[0];
+
+ if (strcmp(param, "0") == 0 || strcmp(param, "VoiceChatRequest") == 0) {
+ command_type = LN_AUDIO_CHAT_VOICE_CHAT_REQUEST;
+ } else if (strcmp(param, "1") == 0 || strcmp(param, "ChooseStoryRequest") == 0) {
+ command_type = LN_AUDIO_CHAT_CHOOSE_STORY_REQUEST;
+ } else {
+ printf("[LN][WS_START_AUDIO_CHAT] Invalid parameter: %s\n", param);
+ printf("Usage: AT^LN_WS_START_AUDIO_CHAT[=0/1 or VoiceChatRequest/ChooseStoryRequest]\n");
+ printf(" 0 or VoiceChatRequest: 语音聊天请求 (默认)\n");
+ printf(" 1 or ChooseStoryRequest: 选择故事请求\n");
+ return ERRCODE_FAIL;
+ }
+ }
+ }
+
+ const char *command_name = (command_type == LN_AUDIO_CHAT_VOICE_CHAT_REQUEST) ?
+ "VoiceChatRequest" : "ChooseStoryRequest";
+
+ printf("[LN][WS_START_AUDIO_CHAT] Starting with command: %s\n", command_name);
+
+ int ret = ln_ws_start_audio_chat(command_type);
if (ret != 0)
{
printf("[LN][WS_START_AUDIO_CHAT] failed: %d\n", ret);
{(char *)"AT^LN_WS_SLEEP", ln_ws_sleep_at, (char *)"进入休眠状态: AT^LN_WS_SLEEP"},
{(char *)"AT^LN_WS_WAKEUP", ln_ws_wakeup_at, (char *)"退出休眠状态: AT^LN_WS_WAKEUP"},
{(char *)"AT^LN_WS_CLOSE_STATUS", ln_ws_close_status_at, (char *)"查询WebSocket关闭状态: AT^LN_WS_CLOSE_STATUS"},
- {(char *)"AT^LN_WS_START_AUDIO_CHAT", ln_ws_start_audio_chat_at, (char *)"开始音频聊天: AT^LN_WS_START_AUDIO_CHAT"},
+ {(char *)"AT^LN_WS_START_AUDIO_CHAT", ln_ws_start_audio_chat_at, (char *)"开始音频聊天: AT^LN_WS_START_AUDIO_CHAT[=0/1]"},
{(char *)"AT^LN_WS_END_AUDIO_CHAT", ln_ws_end_audio_chat_at, (char *)"结束音频聊天: AT^LN_WS_END_AUDIO_CHAT"},
/* 音频播放 */
/* 获取设备id: 优先读本地缓存文件 /user/userdata/leiniao/device_id.bin*/
char *uapi_ln_get_device_id(void)
{
+ return "DEVICE_PC_TEST_001";
+
if (s_cached_device_id && s_cached_device_id[0]) {
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "device_id cached: %s", s_cached_device_id);
return s_cached_device_id;
/* 获取设备token: 优先读本地缓存,判断过期,否则刷新 */
char *uapi_ln_get_device_token(void)
{
+ return "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJXWVNaIiwiZGV2aWNlTW9kZWwiOiJhYm15Ym94XzEiLCJkZXZpY2VWZXJzaW9uIjoiMS4wLjAiLCJkZXZpY2VJZCI6IkRFVklDRV9QQ19URVNUXzAwMSIsImV4cCI6MTc2MTQ2MjQ5Mn0.Oeyu8BDopDTvtAcdNpzz46U2w1UwzEzqcvPwD3Dp08Y";
/* 若内存已有且非占位,直接返回 */
if (g_device_token && strcmp(g_device_token, "<null>") != 0) {
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"uapi_ln_get_device_token: cached token=%s", g_device_token);
{
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[LN_HTTP_SERVER_SEND_TASK] 获取雷鸟设备Token成功\n");
set_is_ln_get_device_token_success(1);
- ret = ln_ws_start(ln_ws_process_message);
+ // ret = ln_ws_start(ln_ws_process_message);
if (ret != 0)
{
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[LN_HTTP_SERVER_SEND_TASK] 启动WebSocket失败: %d\n", ret);
WS_CMD_PING, /* 心跳 (Ping) */
WS_CMD_PONG, /* 心跳回应 (Pong) */
WS_CMD_VOICE_CHAT_RESPONSE, /* 语音聊天响应 (VoiceChatResponse) */
+ WS_CMD_CHOOSE_STORY_RESPONSE, /* 选择故事响应 (ChooseStoryResponse) */
+ WS_CMD_CONNECTED, /* WebSocket连接成功 */
+ WS_CMD_DISCONNECTED, /* WebSocket断连 */
WS_CMD_MAX /* 最大命令 */
} ws_command_t;
// VoiceChatResponse 特定字段
char audio_url[512]; // 音频URL
- char text_content[1024]; // 文本内容
+ char text_content[512]; // 文本内容
bool finished; // 是否完成
char chat_id[64]; // 聊天ID
} ws_data_msg_t;
void ln_ws_process_message(const ws_data_msg_t *data_msg, ws_command_t type);
/* -------------------------------------------------------------------------- */
+/* 音频聊天命令类型枚举 */
+typedef enum {
+ LN_AUDIO_CHAT_VOICE_CHAT_REQUEST = 0, /* 语音聊天请求 */
+ LN_AUDIO_CHAT_CHOOSE_STORY_REQUEST = 1 /* 选择故事请求 */
+} ln_audio_chat_command_t;
+
/* WebSocket接口函数 */
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_start_audio_chat(ln_audio_chat_command_t command_type);
int ln_ws_end_audio_chat(void);
const char* ln_ws_get_current_chat_id(void);
int ln_ws_stop(void);
static size_t g_base64_cache_capacity = 0; /* 缓存容量 */
static bool g_base64_in_progress = false; /* 是否正在进行Base64数据接收 */
+/* 音频聊天命令类型 */
+static ln_audio_chat_command_t g_audio_chat_command_type = LN_AUDIO_CHAT_VOICE_CHAT_REQUEST;
+
/* 回调函数结构体 */
static ws_call_backs_t g_ws_callbacks = {0};
case WS_EVENT_CONNECTED:
printf("[LN][WS] Event: Connected\n");
g_ws_connected = WS_STATE_CONNECTED;
+
+ // 触发连接成功事件
+ if (g_ws_message_callback != NULL) {
+ g_ws_message_callback(NULL, WS_CMD_CONNECTED);
+ }
break;
case WS_EVENT_DISCONNECTED:
printf("[LN][WS] Event: Disconnected\n");
- g_ws_connected = WS_STATE_DISCONNECTED;
- /* 断开后增加重连计数,由监督线程在下一次连接时携带 */
- if (g_ws_running) {
- if (g_ws_reconnect_count < 0xFFFFFFFFu) {
- g_ws_reconnect_count++;
- }
- }
+ g_ws_connected = WS_STATE_DISCONNECTED;
+
+ // 触发断连事件
+ if (g_ws_message_callback != NULL) {
+ g_ws_message_callback(NULL, WS_CMD_DISCONNECTED);
+ }
+
+ /* 断开后增加重连计数,由监督线程在下一次连接时携带 */
+ if (g_ws_running) {
+ if (g_ws_reconnect_count < 0xFFFFFFFFu) {
+ g_ws_reconnect_count++;
+ }
+ }
break;
default:
printf("[LN][WS] Event: Unknown event type %d\n", event_type);
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");
+
+ // 根据命令类型设置不同的command
+ const char *command_str = (g_audio_chat_command_type == LN_AUDIO_CHAT_VOICE_CHAT_REQUEST) ?
+ "VoiceChatRequest" : "ChooseStoryRequest";
+ cJSON_AddStringToObject(json, "command", command_str);
// 创建data对象
cJSON *data_obj = cJSON_CreateObject();
}
// 开始新的音频聊天
-int ln_ws_start_audio_chat(void)
+int ln_ws_start_audio_chat(ln_audio_chat_command_t command_type)
{
if (g_audio_chat_ctx.is_active) {
printf("[LN][WS] Audio chat already active: %s\n", g_audio_chat_ctx.chat_id);
return 0;
}
+ // 设置命令类型
+ g_audio_chat_command_type = command_type;
+
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);
+ const char *command_name = (command_type == LN_AUDIO_CHAT_VOICE_CHAT_REQUEST) ?
+ "VoiceChatRequest" : "ChooseStoryRequest";
+
+ printf("[LN][WS] Audio chat started: %s (command: %s)\n",
+ g_audio_chat_ctx.chat_id, command_name);
return 0;
}
// "audioBase64":
-static void ai_binary_message_handler(const uint8_t *raw_data, uint32_t timestamp, uint16_t data_len)
+static void ai_binary_message_handler(const uint8_t *raw_data, uint32_t timestamp, uint32_t data_len)
{
printf("[AI] Received binary message at %u, length: %d bytes\n", timestamp, data_len);
current_pos += current_chunk_size;
remaining -= current_chunk_size;
- // 添加小延迟避免过快发送
- usleep(1000); // 1ms延迟
}
printf("[AI] All %d chunks sent successfully\n", chunk_count);
return WS_CMD_PONG;
} else if (strcmp(command_str, "VoiceChatResponse") == 0) {
return WS_CMD_VOICE_CHAT_RESPONSE;
+ } else if (strcmp(command_str, "ChooseStoryResponse") == 0) {
+ return WS_CMD_CHOOSE_STORY_RESPONSE;
} else {
return WS_CMD_UNKNOWN;
}
* @param data_msg 输出的消息结构体
* @return 0成功,-1失败
*/
-
+// {
+// "deviceId": "DEVICE_001",
+// "timestamp": 1640995203,
+// "messageId": "story_response_001",
+// "command": "ChooseStoryResponse",
+// "data": {
+// "chatId": "story_12345",
+// "finished": false,
+// "data": {
+// "storyTheme": "童话故事",
+// "storyContent": "从前有一个可爱的小女孩",
+// "storyAudioUrl": "https://example.com/story/audio/chunk_001.mp3",
+// "storyDuration": null
+// },
+// "errorCode": null
+// }
+// }
// {"deviceId":"DEVICE_PC_TEST_001","timestamp":1758880220,
// "messageId":"d63a0670a3854d709cb95720e1e89f83","command":"VoiceChatResponse","data":
// {"finished":false,"chatId":"chat_1758880210_2087035594",
data_msg->finished ? "true" : "false", data_msg->chat_id, data_msg->audio_url, data_msg->text_content);
}
+ // 解析ChooseStoryResponse特定字段
+ if (data_msg->command == WS_CMD_CHOOSE_STORY_RESPONSE) {
+ cJSON *data_obj = cJSON_GetObjectItem(root, "data");
+ if (data_obj && cJSON_IsObject(data_obj)) {
+ // 解析finished字段
+ cJSON *finished = cJSON_GetObjectItem(data_obj, "finished");
+ if (finished && cJSON_IsBool(finished)) {
+ data_msg->finished = cJSON_IsTrue(finished);
+ }
+
+ // 解析chatId字段
+ cJSON *chat_id = cJSON_GetObjectItem(data_obj, "chatId");
+ if (chat_id && cJSON_IsString(chat_id) && chat_id->valuestring) {
+ strncpy(data_msg->chat_id, chat_id->valuestring, sizeof(data_msg->chat_id) - 1);
+ data_msg->chat_id[sizeof(data_msg->chat_id) - 1] = '\0';
+ }
+
+ // 解析data.data字段
+ cJSON *data_data = cJSON_GetObjectItem(data_obj, "data");
+ if (data_data && cJSON_IsObject(data_data)) {
+ // 解析storyTheme字段,存储到text_content中
+ cJSON *story_theme = cJSON_GetObjectItem(data_data, "storyTheme");
+ if (story_theme && cJSON_IsString(story_theme) && story_theme->valuestring) {
+ strncpy(data_msg->text_content, story_theme->valuestring, sizeof(data_msg->text_content) - 1);
+ data_msg->text_content[sizeof(data_msg->text_content) - 1] = '\0';
+ }
+
+ // 解析storyAudioUrl字段,存储到audio_url中
+ cJSON *story_audio_url = cJSON_GetObjectItem(data_data, "storyAudioUrl");
+ if (story_audio_url && cJSON_IsString(story_audio_url) && story_audio_url->valuestring) {
+ strncpy(data_msg->audio_url, story_audio_url->valuestring, sizeof(data_msg->audio_url) - 1);
+ data_msg->audio_url[sizeof(data_msg->audio_url) - 1] = '\0';
+ }
+ }
+ }
+
+ printf("[LN][WS] ChooseStoryResponse parsed: finished=%s, chatId=%s, storyTheme=%s, storyAudioUrl=%s\n",
+ data_msg->finished ? "true" : "false", data_msg->chat_id, data_msg->text_content, data_msg->audio_url);
+ }
+
// 保存原始JSON字符串
data_msg->raw_json = (char*)malloc(strlen(json_str) + 1);
if (data_msg->raw_json) {
*/
static void ln_ws_default_message_handler(const ws_data_msg_t *data_msg, ws_command_t type)
{
- if (!data_msg) return;
-
- printf("[LN][WS] Default handler: Processing %s message (type: %d)\n", data_msg->command_str, type);
+ printf("[LN][WS] Default handler: Processing message (type: %d)\n", type);
switch (type) {
+ case WS_CMD_CONNECTED:
+ printf("[LN][WS] Default: Processing WebSocket Connected event\n");
+ break;
+ case WS_CMD_DISCONNECTED:
+ printf("[LN][WS] Default: Processing WebSocket Disconnected event\n");
+ break;
case WS_CMD_PONG:
- printf("[LN][WS] Default: Processing Pong message\n");
- // 更新心跳状态、记录响应时间等
+ if (data_msg) {
+ printf("[LN][WS] Default: Processing Pong message\n");
+ // 更新心跳状态、记录响应时间等
+ }
break;
case WS_CMD_CHARACTER_CHANGE:
- printf("[LN][WS] Default: Processing CharacterChange message\n");
+ if (data_msg) {
+ printf("[LN][WS] Default: Processing CharacterChange message\n");
+ }
break;
case WS_CMD_SLEEP_MODE_START:
- printf("[LN][WS] Default: Processing SleepModeStart message\n");
+ if (data_msg) {
+ printf("[LN][WS] Default: Processing SleepModeStart message\n");
+ }
break;
case WS_CMD_SLEEP_MODE_END:
- printf("[LN][WS] Default: Processing SleepModeEnd message\n");
+ if (data_msg) {
+ printf("[LN][WS] Default: Processing SleepModeEnd message\n");
+ }
break;
case WS_CMD_DEVICE_BIND:
- printf("[LN][WS] Default: Processing DeviceBind message\n");
+ if (data_msg) {
+ printf("[LN][WS] Default: Processing DeviceBind message\n");
+ }
break;
case WS_CMD_DEVICE_UNBIND:
- printf("[LN][WS] Default: Processing DeviceUnbind message\n");
+ if (data_msg) {
+ printf("[LN][WS] Default: Processing DeviceUnbind message\n");
+ }
break;
case WS_CMD_SCHEDULE_REMINDER:
- printf("[LN][WS] Default: Processing ScheduleReminder message\n");
+ if (data_msg) {
+ printf("[LN][WS] Default: Processing ScheduleReminder message\n");
+ }
break;
case WS_CMD_SCHEDULE_REMINDER_CFG:
- printf("[LN][WS] Default: Processing ScheduleReminderConfig message\n");
+ if (data_msg) {
+ 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);
+ if (data_msg) {
+ 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;
+ case WS_CMD_CHOOSE_STORY_RESPONSE:
+ if (data_msg) {
+ printf("[LN][WS] Default: Processing ChooseStoryResponse message\n");
+ printf("[LN][WS] ChooseStoryResponse details: finished=%s, chatId=%s\n",
+ data_msg->finished ? "true" : "false", data_msg->chat_id);
+ printf("[LN][WS] Story Theme: %s\n", data_msg->text_content);
+ printf("[LN][WS] Story Audio URL: %s\n", data_msg->audio_url);
+ }
break;
default:
printf("[LN][WS] Default: Processing unknown command: %d\n", type);
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
+#include <time.h>
#include "cJSON.h" // Added for JSON parsing
#include "custom_utils.h"
#include "soc_osal.h"
typedef struct {
uint8_t msg_type;
- uint16_t data_len;
+ uint32_t data_len;
uint32_t timestamp; // 收到消息的时间戳
bool isBinary; // 消息是否为二进制数据
char *raw_data; // 动态分配的原始消息数据
static ws_call_backs_t g_ws_callbacks = {0}; /* WebSocket回调函数结构体 */
static volatile ws_close_status_t g_ws_close_status = WS_CLOSE_NOT_EXECUTED; /* WebSocket关闭状态标志位 */
+/* 连接超时检测相关变量 */
+static time_t g_ws_connect_start_time = 0; /* 连接开始时间 */
+static volatile int g_ws_connect_timeout_occurred = 0; /* 超时标志 */
+
static void uapi_ws_build_url(char *out, size_t out_size);
static void uapi_ws_default_url_handler(char *url, size_t url_size, char *header_buf, size_t header_size);
int uapi_ws_send_ping(void);
static void uapi_ws_reset_close_status(void);
const char* uapi_ws_get_connection_state_string(void);
+/* 连接超时检测相关函数 */
+static void uapi_ws_start_connect_timer(void);
+static int uapi_ws_is_connect_timeout(void);
+static void uapi_ws_reset_connect_timer(void);
+
/* 消息内存管理函数 */
static ws_msg_info_t* ws_msg_create(uint16_t data_len)
{
printf("WebSocketClientConstructor uapi_ws_on_open\r\n");
g_ws_connected = WS_STATE_CONNECTED;
uapi_ws_reset_close_status(); // 连接成功时重置关闭状态
+ uapi_ws_reset_connect_timer(); // 连接成功时重置超时计时器
printf("[LN][WS] 连接状态: 连接成功, 关闭状态已重置为 NOT_EXECUTED (%d)\n", g_ws_close_status);
}
if (isFinished) {
// 数据完整,创建消息结构体
- ws_msg_info_t *msg = ws_msg_create(length);
- if (msg == NULL) {
- printf("[LN][WS] Failed to create message structure\n");
- return;
- }
-
- msg->msg_type = WS_MSG_TYPE_RAW_MESSAGE;
- msg->isBinary = isBinary; // 设置二进制标志
-
- // 记录收到消息的时间戳
- uapi_get_timestampe_ss(&msg->timestamp);
-
- // 复制原始数据
- memcpy(msg->raw_data, data, length);
- msg->raw_data[length] = '\0';
+
if(isBinary)
{
+ uint32_t timestamp;
+ uapi_get_timestampe_ss(×tamp);
if (g_ws_callbacks.binary_callback != NULL) {
- g_ws_callbacks.binary_callback((const uint8_t*)msg->raw_data, msg->timestamp, msg->data_len);
+ g_ws_callbacks.binary_callback(data, timestamp, length);
} else {
printf("[LN][WS] No binary message callback registered\n");
}
// 二进制消息处理完后释放内存
- ws_msg_free(msg);
}
else
{
+ ws_msg_info_t *msg = ws_msg_create(length);
+ if (msg == NULL) {
+ printf("[LN][WS] Failed to create message structure\n");
+ return;
+ }
+
+ msg->msg_type = WS_MSG_TYPE_RAW_MESSAGE;
+ msg->isBinary = isBinary; // 设置二进制标志
+
+ // 记录收到消息的时间戳
+ uapi_get_timestampe_ss(&msg->timestamp);
+
+ // 复制原始数据
+ memcpy(msg->raw_data, data, length);
+ msg->raw_data[length] = '\0';
+
osStatus_t ret = osMessageQueuePut(g_ws_msg_queue, &msg, 0, 0);
if (ret == osOK) {
printf("[LN][WS] Raw message queued successfully, length: %d, timestamp: %u\n", msg->data_len, msg->timestamp);
if (g_ws_client_fd >= 0) {
printf("[LN][WS] WebSocketClientClose\n");
uapi_ws_close_with_status(g_ws_client_fd);
+ g_ws_client_fd=-1;
printf("[LN][WS] WebSocketClientClose end\n");
}
g_ws_connected = WS_STATE_DISCONNECTED;
g_ws_callbacks.event_callback(WS_EVENT_CONNECTING);
}
+ // 启动连接超时计时器
+ uapi_ws_start_connect_timer();
+
g_ws_client_fd = WebSocketClientConstructor(uapi_ws_on_open, uapi_ws_on_message, uapi_ws_on_error, uapi_ws_on_close);
if (g_ws_client_fd < 0) {
printf("[LN][WS] constructor failed: %d\n", g_ws_client_fd);
printf("[LN][WS] connect failed: %d\n", ret);
g_ws_connected = WS_STATE_DISCONNECTED;
printf("[LN][WS] 连接状态: 未连接(连接失败)\n");
+ uapi_ws_reset_connect_timer(); // 连接失败时重置超时计时器
(void)uapi_ws_close_with_status(g_ws_client_fd);
g_ws_client_fd = -1;
/* 指数退避*/
printf("[LN][WS] 连接请求已发送,等待握手完成...\n");
}
+ // 连接超时检测:在连接中状态时检查是否超时
+ if (g_ws_connected == WS_STATE_CONNECTING) {
+ if (uapi_ws_is_connect_timeout()) {
+ printf("[LN][WS] Connection timeout detected, forcing disconnect and reconnect\n");
+
+ // 强制关闭连接
+ if (g_ws_client_fd >= 0) {
+ uapi_ws_close_with_status(g_ws_client_fd);
+ g_ws_close_status = WS_CLOSE_NOT_EXECUTED;
+ g_ws_client_fd = -1;
+ }
+
+ // 重置连接状态
+ g_ws_connected = WS_STATE_DISCONNECTED;
+ uapi_ws_reset_connect_timer();
+
+ // 增加重连计数
+ if (g_ws_reconnect_count < 0xFFFFFFFFu) {
+ g_ws_reconnect_count++;
+ }
+
+ printf("[LN][WS] Connection timeout handled, will retry connection\n");
+ }
+ }
+
// 心跳部分:由回调函数处理时间判断和心跳发送
if (g_ws_connected == WS_STATE_CONNECTED && g_ws_client_fd >= 0) {
// 使用心跳回调函数处理,包括时间判断
return 0;
}
+/* ============================== 连接超时检测函数实现 ============================== */
+
+/**
+ * @brief 开始连接超时计时器
+ */
+static void uapi_ws_start_connect_timer(void)
+{
+ g_ws_connect_start_time = time(NULL);
+ g_ws_connect_timeout_occurred = 0;
+ printf("[LN][WS] Connect timer started, timeout: %d seconds\n", WS_CONNECT_TIMEOUT_SECONDS);
+}
+/**
+ * @brief 检查连接是否超时
+ * @return 1表示超时,0表示未超时
+ */
+static int uapi_ws_is_connect_timeout(void)
+{
+#if WS_CONNECT_TIMEOUT_ENABLED
+ if (g_ws_connect_start_time == 0) {
+ return 0; /* 计时器未启动 */
+ }
+
+ time_t current_time = time(NULL);
+ time_t elapsed = current_time - g_ws_connect_start_time;
+
+ if (elapsed >= WS_CONNECT_TIMEOUT_SECONDS) {
+ if (!g_ws_connect_timeout_occurred) {
+ g_ws_connect_timeout_occurred = 1;
+ printf("[LN][WS] Connect timeout after %ld seconds (limit: %d seconds)\n",
+ elapsed, WS_CONNECT_TIMEOUT_SECONDS);
+ }
+ return 1;
+ }
+
+ return 0;
+#else
+ return 0; /* 超时检测被禁用 */
+#endif
+}
+
+/**
+ * @brief 重置连接超时计时器
+ */
+static void uapi_ws_reset_connect_timer(void)
+{
+ g_ws_connect_start_time = 0;
+ g_ws_connect_timeout_occurred = 0;
+ printf("[LN][WS] Connect timer reset\n");
+}
#include <stdint.h>
#include <stddef.h>
+/* WebSocket连接超时配置 */
+#ifndef WS_CONNECT_TIMEOUT_SECONDS
+#define WS_CONNECT_TIMEOUT_SECONDS 30 /* 连接超时时间(秒),默认30秒 */
+#endif
+
+#ifndef WS_CONNECT_TIMEOUT_ENABLED
+#define WS_CONNECT_TIMEOUT_ENABLED 1 /* 是否启用连接超时检测,默认启用 */
+#endif
+
#ifdef __cplusplus
extern "C" {
#endif
typedef void (*ws_message_callback_t)(const char *raw_data, uint32_t timestamp);
/* 二进制消息处理回调函数类型 */
-typedef void (*ws_binary_message_callback_t)(const uint8_t *raw_data, uint32_t timestamp, uint16_t data_len);
+typedef void (*ws_binary_message_callback_t)(const uint8_t *raw_data, uint32_t timestamp, uint32_t data_len);
/* 数据不完整消息处理回调函数类型 */
typedef void (*ws_incomplete_message_callback_t)(const char *raw_data, size_t length, bool isBinary);