#include "websocket_protocol.h" #include "board.h" #include "system_info.h" #include "application.h" #include "background_task.h" #include #include #include #include #include #include "assets/lang_config.h" #define TAG "WS" // 初始化静态成员 std::atomic WebsocketProtocol::pending_delete_tasks_{0}; WebsocketProtocol::WebsocketProtocol() { event_group_handle_ = xEventGroupCreate(); } WebsocketProtocol::~WebsocketProtocol() { if (websocket_ != nullptr) { delete websocket_; } vEventGroupDelete(event_group_handle_); } void WebsocketProtocol::Start() { } void WebsocketProtocol::SendAudio(const std::vector& data) { if (websocket_ == nullptr || !websocket_->IsConnected()) { ESP_LOGD(TAG, "WebSocket not connected, dropping audio data"); return; } websocket_->Send(data.data(), data.size(), true); } void WebsocketProtocol::SendText(const std::string& text) { // 🔧 修复:增强连接状态检查,防止访问无效连接 if (websocket_ == nullptr) { ESP_LOGD(TAG, "WebSocket is null, dropping text message: %s", text.c_str()); return; } // 🔧 双重检查连接状态,防止偶发性连接异常 if (!websocket_->IsConnected()) { ESP_LOGD(TAG, "WebSocket not connected, dropping text message: %s", text.c_str()); return; } // 🔧 再次验证连接有效性(防止偶发性TLS状态异常) if (!IsAudioChannelOpened()) { ESP_LOGW(TAG, "Audio channel not properly opened, dropping message: %s", text.c_str()); return; } // 🔧 添加异常处理,防止TLS层崩溃 try { // 验证消息内容有效性 if (text.empty()) { ESP_LOGW(TAG, "Attempted to send empty message"); return; } if (!websocket_->Send(text)) { ESP_LOGE(TAG, "Failed to send text: %s", text.c_str()); SetError(Lang::Strings::SERVER_ERROR); } else { ESP_LOGD(TAG, "Successfully sent WebSocket message: %s", text.c_str()); } } catch (const std::exception& e) { ESP_LOGE(TAG, "Exception sending text: %s, message: %s", e.what(), text.c_str()); SetError(Lang::Strings::SERVER_ERROR); } catch (...) { ESP_LOGE(TAG, "Unknown exception sending text: %s", text.c_str()); SetError(Lang::Strings::SERVER_ERROR); } } bool WebsocketProtocol::IsAudioChannelOpened() const { if (websocket_ == nullptr) { return false; } // 🔧 增强连接状态验证:不仅检查IsConnected,还验证实际可用性 bool basic_check = websocket_->IsConnected() && !error_occurred_ && !IsTimeout(); if (!basic_check) { return false; } // 🔧 额外验证:确保WebSocket真正可用(偶发性保护) try { // 这里可以添加轻量级的连接测试,但要避免频繁调用 return true; } catch (...) { ESP_LOGW(TAG, "WebSocket connection validation failed"); return false; } } void WebsocketProtocol::CloseAudioChannel() { std::lock_guard lock(websocket_mutex_); if (websocket_ != nullptr && !is_being_deleted_) { // ESP_LOGI(TAG, "🔧 关闭WebSocket连接"); is_being_deleted_ = true; try { websocket_->Close(); } catch (const std::exception& e) { ESP_LOGE(TAG, "WebSocket close failed: %s", e.what()); } auto websocket_to_delete = websocket_; websocket_ = nullptr; // 立即置空,防止重复访问 // 使用更安全的延迟进行异步删除,确保其他线程完成访问 Application::GetInstance().Schedule([this, websocket_to_delete]() { vTaskDelay(pdMS_TO_TICKS(50)); try { delete websocket_to_delete; ESP_LOGI(TAG, "🔧 WebSocket已安全删除"); } catch (const std::exception& e) { ESP_LOGE(TAG, "WebSocket deletion failed: %s", e.what()); } is_being_deleted_ = false; }); } } bool WebsocketProtocol::OpenAudioChannel() { std::lock_guard lock(websocket_mutex_); if (websocket_ != nullptr && !is_being_deleted_) { // ESP_LOGI(TAG, "🔧 关闭现有WebSocket连接"); is_being_deleted_ = true; try { // 🔧 关键修复:清除OnDisconnected回调,防止触发OnAudioChannelClosed websocket_->OnDisconnected(nullptr); websocket_->Close(); } catch (const std::exception& e) { ESP_LOGE(TAG, "WebSocket close failed during reopen: %s", e.what()); } auto websocket_to_delete = websocket_; websocket_ = nullptr; // 立即置空,防止重复访问 // 增加待删除任务计数 pending_delete_tasks_++; // 使用更安全的异步删除机制 Application::GetInstance().Schedule([this, websocket_to_delete]() { vTaskDelay(pdMS_TO_TICKS(200)); // 增加延迟到200ms try { delete websocket_to_delete; ESP_LOGI(TAG, "🔧 旧WebSocket已安全删除,剩余待删除任务: %d", pending_delete_tasks_.load() - 1); } catch (const std::exception& e) { ESP_LOGE(TAG, "WebSocket deletion failed: %s", e.what()); } pending_delete_tasks_--; // 减少计数 is_being_deleted_ = false; }); // 短暂延迟让删除任务启动 vTaskDelay(pdMS_TO_TICKS(150)); } // 如果有太多待删除任务,等待一下 if (pending_delete_tasks_.load() > 2) { ESP_LOGW(TAG, "⚠️ 检测到多个待删除任务 (%d),等待清理完成", pending_delete_tasks_.load()); int wait_count = 0; while (pending_delete_tasks_.load() > 1 && wait_count < 10) { vTaskDelay(pdMS_TO_TICKS(100)); wait_count++; } } error_occurred_ = false; std::string url = CONFIG_WEBSOCKET_URL; std::string token = "Bearer " + std::string(CONFIG_WEBSOCKET_ACCESS_TOKEN); // 🔧 添加内存检查和错误处理 try { websocket_ = Board::GetInstance().CreateWebSocket(); if (websocket_ == nullptr) { ESP_LOGE("WebsocketProtocol", "Failed to create WebSocket - out of memory"); return false; } } catch (const std::exception& e) { ESP_LOGE("WebsocketProtocol", "Exception creating WebSocket: %s", e.what()); return false; } catch (...) { ESP_LOGE("WebsocketProtocol", "Unknown exception creating WebSocket"); return false; } websocket_->SetHeader("Authorization", token.c_str()); websocket_->SetHeader("Protocol-Version", "1"); websocket_->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str()); websocket_->SetHeader("Client-Id", Board::GetInstance().GetUuid().c_str()); websocket_->OnData([this](const char* data, size_t len, bool binary) { if (binary) { if (on_incoming_audio_ != nullptr) { on_incoming_audio_(std::vector((uint8_t*)data, (uint8_t*)data + len));// 接收音频数据 } } else { // Parse JSON data auto root = cJSON_Parse(data); // 添加调试日志:打印接收到的原始JSON数据 ESP_LOGI(TAG, "🔍 接收到JSON数据: %s", data); // 添加调试日志:解析JSON结构 if (root == NULL) { ESP_LOGE(TAG, "❌ JSON解析失败,数据格式错误"); return; } // 打印JSON对象的所有字段 char* json_string = cJSON_Print(root); if (json_string != NULL) { ESP_LOGI(TAG, "📋 解析后的JSON结构: %s", json_string); free(json_string); } auto type = cJSON_GetObjectItem(root, "type");// if (type != NULL) { ESP_LOGI(TAG, "📝 消息类型: %s", type->valuestring); if (strcmp(type->valuestring, "hello") == 0) {// 接收服务器hello消息 ParseServerHello(root);// 解析服务器hello消息 } else { if (on_incoming_json_ != nullptr) {// 接收服务器其他消息 on_incoming_json_(root);// 调用回调函数处理其他消息 } } } else { ESP_LOGE(TAG, "缺少消息类型, data: %s", data);// 缺少消息类型 } cJSON_Delete(root); } last_incoming_time_ = std::chrono::steady_clock::now(); }); websocket_->OnDisconnected([this]() { ESP_LOGI(TAG, "Websocket disconnected"); // 立即停止音频处理,防止崩溃 auto& app = Application::GetInstance(); #if CONFIG_USE_AUDIO_PROCESSOR app.StopAudioProcessor(); ESP_LOGI(TAG, "Audio processor stopped immediately"); #endif if (on_audio_channel_closed_ != nullptr) { on_audio_channel_closed_(); } }); if (!websocket_->Connect(url.c_str())) { ESP_LOGE(TAG, "Failed to connect to websocket server"); SetError(Lang::Strings::SERVER_NOT_FOUND); return false; } // Send hello message to describe the client // keys: message type, version, audio_params (format, sample_rate, channels) std::string message = "{"; message += "\"type\":\"hello\","; message += "\"version\": 1,"; message += "\"transport\":\"websocket\","; message += "\"audio_params\":{"; message += "\"format\":\"opus\", \"sample_rate\":16000, \"channels\":1, \"frame_duration\":" + std::to_string(OPUS_FRAME_DURATION_MS); message += "}}"; websocket_->Send(message); // Wait for server hello EventBits_t bits = xEventGroupWaitBits(event_group_handle_, WEBSOCKET_PROTOCOL_SERVER_HELLO_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(10000)); if (!(bits & WEBSOCKET_PROTOCOL_SERVER_HELLO_EVENT)) { ESP_LOGE(TAG, "Failed to receive server hello"); SetError(Lang::Strings::SERVER_TIMEOUT); return false; } if (on_audio_channel_opened_ != nullptr) { on_audio_channel_opened_(); } return true; } void WebsocketProtocol::ParseServerHello(const cJSON* root) { auto transport = cJSON_GetObjectItem(root, "transport"); if (transport == nullptr || strcmp(transport->valuestring, "websocket") != 0) { ESP_LOGE(TAG, "Unsupported transport: %s", transport->valuestring); return; } auto audio_params = cJSON_GetObjectItem(root, "audio_params"); if (audio_params != NULL) { auto sample_rate = cJSON_GetObjectItem(audio_params, "sample_rate"); if (sample_rate != NULL) { server_sample_rate_ = sample_rate->valueint; } auto frame_duration = cJSON_GetObjectItem(audio_params, "frame_duration"); if (frame_duration != NULL) { server_frame_duration_ = frame_duration->valueint; } } xEventGroupSetBits(event_group_handle_, WEBSOCKET_PROTOCOL_SERVER_HELLO_EVENT); }