${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
)
$(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 \
#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命令测试程序"
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);
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();
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[] = {
{ (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)" },
};
/* ================================ 公共接口 ================================ */
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");
*/
typedef struct {
char language[16]; // 语言代码,如"zh-CN"
+ char format[16]; // 音频格式,如"pcm"
bool enable_vad; // 是否启用语音活动检测
bool enable_punctuation; // 是否启用标点符号
bool enable_partial_results; // 是否启用部分结果
/* ================================ 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服务上下文
/* ================================ 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服务上下文
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 全局状态
#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
/* ================================ 基础常量定义 ================================ */
/* 内存优化常量 - 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 // 错误消息最大长度
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;
/* ================================ 服务类型定义 ================================ */
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 {
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; // 相关服务
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; // 旧状态
} 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);
*/
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
*/
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)
/**
* @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;
/**
* @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请求
*/
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
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
+#include "cJSON.h"
#ifndef __TEST_MODE__
#include "wearable_log.h"
/* ================================ 公共接口实现 ================================ */
+/**
+ * @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) {
}
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");
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;
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);
}
}
AI_LOGI("Handling ASR response: %.100s...", json_data);
-
+
/* 解析ASR响应 */
asr_result_t result;
ai_error_t ret = parse_asr_response(json_data, &result);
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)
{
}
/* 构建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");
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");
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) {
/* 在实际实现中,这里应该使用完整的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");
/* ================================ 公共接口实现 ================================ */
+
+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) {
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"
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); */
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)
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);
/* 设置服务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;
}
}
#endif
- /* 关闭WebSocket连接 */
- if (conn->websocket >= 0) {
-#ifndef __TEST_MODE__
- WebSocketClientClose(conn->websocket);
-#endif
- conn->websocket = -1;
- }
+ /* 清理所有WebSocket连接 */
+ cleanup_websocket_connection(conn);
}
#ifndef __TEST_MODE__
/* 重置状态 */
memset(&g_connection_manager, 0, sizeof(g_connection_manager));
+
+ /* 重置连接计数器 */
+ g_active_websocket_count = 0;
+
g_manager_initialized = false;
AI_LOGI("Connection manager destroyed");
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++) {
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__
/* 启动心跳定时器 */
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;
}
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)
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 {
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) {
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 // 使用标准优先级
};
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);
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
{
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);
case AI_SERVICE_COUNT:
/* 不应该到达这里 */
break;
+ case AI_SERVICE_GLOBAL:
+ /* 全局服务不需要更新连接矩阵 */
+ break;
}
update_global_state();
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",
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
/* 测试模式下简单延时 */
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__
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;
}
/* 测试模式下模拟连接成功 */
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;
}
static void websocket_on_message(int client, const char *data, size_t length, bool isFinished)
{
- (void)isFinished; // 暂时不处理分片消息
-
/* 安全检查:确保管理器已初始化 */
if (!g_manager_initialized) {
return;
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)
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) {
case AI_SERVICE_COUNT:
/* 不应该到达这里 */
break;
+ case AI_SERVICE_GLOBAL:
+ /* 全局服务不需要更新连接矩阵 */
+ break;
}
update_global_state();
/* 发送状态变更事件 */
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;
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;
#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)
/* 发送心跳包 */
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));
}
}
}
--- /dev/null
+/**
+ * @file ai_event_manager.c
+ * @brief AI事件管理器实现
+ * @author AI Assistant
+ * @date 2024
+ */
+
+#include "ai_core_types.h"
+#include <stdlib.h>
+#include <string.h>
+
+#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 <stdio.h>
+#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
#include "ai_tts_service.h"
#include "ai_connection_manager.h"
#include "ai_memory_pool.h"
+#include "ai_media.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#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);
/* ================================ 公共接口实现 ================================ */
/* 初始化上下文 */
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) {
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;
}
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));
/* 使用统一会话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)
return AI_ERROR_NOT_INITIALIZED;
}
+ /* 🆕 停止ASR服务前,关闭录音数据发送 */
+ g_asr_recording_enabled = false;
+ AI_LOGI("ASR recording disabled");
+
return ai_asr_service_stop(&context->asr_context);
}
}
}
+/* ================================ 音频服务控制接口 ================================ */
+
+/**
+ * @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)
#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;
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;
/* ================================ 公共接口实现 ================================ */
+/*
+ * @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) {
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)
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)
{
// #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)
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)//登录
if(read_dev_token(NULL)==false)
{
return aut_login();
+ }else
+ {
+ return true;
}
}
// 累加长度数据
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) {
// 检查缓存是否足够
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;
// 检查缓存是否足够
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;
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) {
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);
// 如果解析器已经完成一个帧,重置状态
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 前进
}
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;
// 检查载荷数据是否在缓存范围内
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;
}
// 发送帧头
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) {
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;
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;
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;
}
// 分配发送缓冲区
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;
}
// 发送二进制数据
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);
}
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;
}
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;
}
}
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);
}
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;