#include "application.h" #include "board.h" #include "wifi_board.h" #include "display.h" #include "system_info.h" #include "ml307_ssl_transport.h" #include "audio_codec.h" #include "settings.h" #include "mqtt_protocol.h" #include "websocket_protocol.h" #include "font_awesome_symbols.h" #include "iot/thing_manager.h" #include "assets/lang_config.h" #include "boards/common/qmi8658a.h" // 添加qmi8658a_data_t类型的头文件 #include "boards/movecall-moji-esp32s3/movecall_moji_esp32s3.h" // 添加MovecallMojiESP32S3类的头文件 #include #include #include #include #include #include #include // 用于sqrt函数 #define TAG "Application" #define MAC_TAG "WiFiMAC" static const char* const STATE_STRINGS[] = { "unknown", "starting", "configuring", "idle", "connecting", "listening", "speaking", "upgrading", "activating", "fatal_error", "invalid_state" }; Application::Application() { event_group_ = xEventGroupCreate(); background_task_ = new BackgroundTask(4096 * 8); esp_timer_create_args_t clock_timer_args = { .callback = [](void* arg) { Application* app = (Application*)arg; app->OnClockTimer(); }, .arg = this, .dispatch_method = ESP_TIMER_TASK, .name = "clock_timer", .skip_unhandled_events = true }; esp_timer_create(&clock_timer_args, &clock_timer_handle_); } Application::~Application() { if (clock_timer_handle_ != nullptr) { esp_timer_stop(clock_timer_handle_); esp_timer_delete(clock_timer_handle_); } if (background_task_ != nullptr) { delete background_task_; } vEventGroupDelete(event_group_); } void Application::CheckNewVersion() { auto& board = Board::GetInstance(); auto display = board.GetDisplay(); // Check if there is a new firmware version available ota_.SetPostData(board.GetJson());// 发送当前设备的JSON数据到OTA服务器,用于检查是否有新的固件版本 包办板载信息 BOARD_TYPE const int MAX_RETRY = 10; int retry_count = 0; while (true) { if (!ota_.CheckVersion()) { retry_count++; if (retry_count >= MAX_RETRY) { ESP_LOGE(TAG, "Too many retries, exit version check"); return; } ESP_LOGW(TAG, "Check new version failed, retry in %d seconds (%d/%d)", 60, retry_count, MAX_RETRY); vTaskDelay(pdMS_TO_TICKS(60000)); continue; } retry_count = 0; if (ota_.HasNewVersion()) { Alert(Lang::Strings::OTA_UPGRADE, Lang::Strings::UPGRADING, "happy", Lang::Sounds::P3_UPGRADE); // Wait for the chat state to be idle do { vTaskDelay(pdMS_TO_TICKS(3000)); } while (GetDeviceState() != kDeviceStateIdle); // Use main task to do the upgrade, not cancelable Schedule([this, display]() { SetDeviceState(kDeviceStateUpgrading); display->SetIcon(FONT_AWESOME_DOWNLOAD); std::string message = std::string(Lang::Strings::NEW_VERSION) + ota_.GetFirmwareVersion(); display->SetChatMessage("system", message.c_str()); auto& board = Board::GetInstance(); board.SetPowerSaveMode(false);// 关闭低功耗模式 #if CONFIG_USE_WAKE_WORD_DETECT || CONFIG_USE_CUSTOM_WAKE_WORD wake_word_detect_.Stop(); #endif // 预先关闭音频输出,避免升级过程有音频操作 auto codec = board.GetAudioCodec(); codec->EnableInput(false); codec->EnableOutput(false); { std::lock_guard lock(mutex_); audio_decode_queue_.clear(); } background_task_->WaitForCompletion(); delete background_task_; background_task_ = nullptr; vTaskDelay(pdMS_TO_TICKS(1000)); ota_.StartUpgrade([display](int progress, size_t speed) { char buffer[64]; snprintf(buffer, sizeof(buffer), "%d%% %zuKB/s", progress, speed / 1024); display->SetChatMessage("system", buffer); }); // If upgrade success, the device will reboot and never reach here display->SetStatus(Lang::Strings::UPGRADE_FAILED); ESP_LOGI(TAG, "Firmware upgrade failed..."); vTaskDelay(pdMS_TO_TICKS(3000)); Reboot(); }); return; } // No new version, mark the current version as valid ota_.MarkCurrentVersionValid(); std::string message = std::string(Lang::Strings::VERSION) + ota_.GetCurrentVersion(); display->ShowNotification(message.c_str()); // 检查是否有设备激活码 // if (ota_.HasActivationCode()) { // // Activation code is valid // SetDeviceState(kDeviceStateActivating);//设置设备状态为激活中 // // ShowActivationCode();//显示设备激活码 // // Check again in 60 seconds or until the device is idle // for (int i = 0; i < 60; ++i) { // if (device_state_ == kDeviceStateIdle) { // break; // } // vTaskDelay(pdMS_TO_TICKS(1000)); // } // continue; // } SetDeviceState(kDeviceStateIdle); display->SetChatMessage("system", ""); ResetDecoder(); PlaySound(Lang::Sounds::P3_SUCCESS); // Exit the loop if upgrade or idle break; } } // 取消设备激活码播报,当前设备绑定使用Wi-Fi的Mac地址进行绑定 // void Application::ShowActivationCode() { // auto& message = ota_.GetActivationMessage(); // auto& code = ota_.GetActivationCode(); // struct digit_sound { // char digit; // const std::string_view& sound; // }; // static const std::array digit_sounds{{ // digit_sound{'0', Lang::Sounds::P3_0}, // digit_sound{'1', Lang::Sounds::P3_1}, // digit_sound{'2', Lang::Sounds::P3_2}, // digit_sound{'3', Lang::Sounds::P3_3}, // digit_sound{'4', Lang::Sounds::P3_4}, // digit_sound{'5', Lang::Sounds::P3_5}, // digit_sound{'6', Lang::Sounds::P3_6}, // digit_sound{'7', Lang::Sounds::P3_7}, // digit_sound{'8', Lang::Sounds::P3_8}, // digit_sound{'9', Lang::Sounds::P3_9} // }}; // // This sentence uses 9KB of SRAM, so we need to wait for it to finish // Alert(Lang::Strings::ACTIVATION, message.c_str(), "happy", Lang::Sounds::P3_ACTIVATION); // vTaskDelay(pdMS_TO_TICKS(1000)); // background_task_->WaitForCompletion(); // for (const auto& digit : code) { // auto it = std::find_if(digit_sounds.begin(), digit_sounds.end(), // [digit](const digit_sound& ds) { return ds.digit == digit; }); // if (it != digit_sounds.end()) { // PlaySound(it->sound); // } // } // } // 新增代码(小程序控制 暂停播放 音频) // ========================================================= void Application::PauseAudioPlayback() { std::unique_lock lock(mutex_); if (!audio_paused_) { audio_paused_ = true;// 暂停播放(更新标志位) ESP_LOGI(TAG, "🔇 从服务器接收到暂停播放指令"); // 恢复原始处理方式:立即停止音频输出 auto codec = Board::GetInstance().GetAudioCodec(); if (codec) { codec->EnableOutput(false);// 暂停时立即停止音频输出 ESP_LOGI(TAG, "⏸️ 音频编解码器输出已禁用,实现立即暂停"); } ESP_LOGI(TAG, "⏸️ 音频播放已暂停"); } } // 新增代码(小程序控制 继续播放 音频) void Application::ResumeAudioPlayback() { std::unique_lock lock(mutex_); if (audio_paused_) { audio_paused_ = false;// 恢复播放(更新标志位) ESP_LOGI(TAG, "� 从服务器接收到继续播放指令"); // 恢复原始处理方式:重新启用音频输出 auto codec = Board::GetInstance().GetAudioCodec(); if (codec) { codec->EnableOutput(true);// 恢复时重新启用音频输出 ESP_LOGI(TAG, "▶️ 音频编解码器输出已启用"); } ESP_LOGI(TAG, "▶️ 音频播放已恢复"); } } // ========================================================= void Application::Alert(const char* status, const char* message, const char* emotion, const std::string_view& sound) { ESP_LOGW(TAG, "Alert %s: %s [%s]", status, message, emotion); auto display = Board::GetInstance().GetDisplay(); display->SetStatus(status); display->SetEmotion(emotion); display->SetChatMessage("system", message); if (!sound.empty()) { ResetDecoder(); PlaySound(sound); } } void Application::DismissAlert() { if (device_state_ == kDeviceStateIdle) { auto display = Board::GetInstance().GetDisplay(); display->SetStatus(Lang::Strings::STANDBY); display->SetEmotion("neutral"); display->SetChatMessage("system", ""); } } void Application::PlaySound(const std::string_view& sound) { // The assets are encoded at 16000Hz, 60ms frame duration SetDecodeSampleRate(16000, 60); const char* data = sound.data(); size_t size = sound.size(); for (const char* p = data; p < data + size; ) { auto p3 = (BinaryProtocol3*)p; p += sizeof(BinaryProtocol3); auto payload_size = ntohs(p3->payload_size); std::vector opus; opus.resize(payload_size); memcpy(opus.data(), p3->payload, payload_size); p += payload_size; std::lock_guard lock(mutex_); audio_decode_queue_.emplace_back(std::move(opus)); } } // 切换聊天状态的函数,用于在不同的设备状态之间进行切换 void Application::ToggleChatState() { // 如果当前设备状态是激活中,则将状态设置为空闲并返回 if (device_state_ == kDeviceStateActivating) { SetDeviceState(kDeviceStateIdle); // 设置设备状态为空闲 return; // 直接返回,不执行后续逻辑 } // 检查协议对象是否已初始化 if (!protocol_) { ESP_LOGE(TAG, "Protocol not initialized"); // 记录错误日志:协议未初始化 return; // 协议未初始化则直接返回 } // 如果当前设备状态是idle空闲,则尝试开始聊天 if (device_state_ == kDeviceStateIdle) { // 使用Schedule函数异步执行以下操作 Schedule([this]() { SetDeviceState(kDeviceStateConnecting); // 设置设备状态为连接中 ESP_LOGI(TAG, "Attempting to open audio channel"); // 记录信息日志:尝试打开音频通道 // 尝试打开音频通道 if (!protocol_->OpenAudioChannel()) { ESP_LOGW(TAG, "Failed to open audio channel, will retry in 2 seconds"); // 记录警告日志:打开音频通道失败 SetDeviceState(kDeviceStateIdle); // 将设备状态重新设置为空闲 // 2秒后自动重试 Schedule([this]() { vTaskDelay(pdMS_TO_TICKS(2000)); // 延迟2000毫秒(2秒) ESP_LOGI(TAG, "Retrying audio channel connection"); // 记录信息日志:重试音频通道连接 ToggleChatState(); // 递归调用自身,重新尝试切换聊天状态 }); return; // 返回,不执行后续的SetListeningMode } // 音频通道打开成功,根据实时聊天是否启用来设置监听模式 SetListeningMode(realtime_chat_enabled_ ? kListeningModeRealtime : kListeningModeAutoStop); }); } else if (device_state_ == kDeviceStateSpeaking) { // 如果当前设备状态是说话中 // 异步执行中止说话操作 Schedule([this]() { AbortSpeaking(kAbortReasonNone); // 中止说话,原因为无特定原因 }); } else if (device_state_ == kDeviceStateListening) { // 如果当前设备状态是监听中 // 异步执行关闭音频通道操作 Schedule([this]() { protocol_->CloseAudioChannel(); // 关闭音频通道 }); } } void Application::ToggleListeningState() { if (device_state_ == kDeviceStateActivating) { SetDeviceState(kDeviceStateIdle); return; } if (!protocol_) { ESP_LOGE(TAG, "Protocol not initialized"); return; } // 简单的状态切换:idle <-> listening if (device_state_ == kDeviceStateIdle) { // 从待命状态进入聆听状态 Schedule([this]() { SetDeviceState(kDeviceStateConnecting); if (!protocol_->OpenAudioChannel()) { return; } SetListeningMode(kListeningModeManualStop); ESP_LOGI(TAG, "Interrupt button: Entering listening state"); }); } else if (device_state_ == kDeviceStateListening) { // 从聆听状态返回待命状态 Schedule([this]() { protocol_->CloseAudioChannel(); ESP_LOGI(TAG, "Interrupt button: Returning to idle state"); }); } else if (device_state_ == kDeviceStateSpeaking) { // 如果正在说话,中止说话并返回待命状态 Schedule([this]() { AbortSpeaking(kAbortReasonNone); ESP_LOGI(TAG, "Interrupt button: Stopping speech and returning to idle state"); }); } else if (device_state_ == kDeviceStateConnecting) { // 如果正在连接,直接返回待命状态 Schedule([this]() { SetDeviceState(kDeviceStateIdle); ESP_LOGI(TAG, "Interrupt button: Canceling connection and returning to idle state"); }); } } void Application::StartListening() { if (device_state_ == kDeviceStateActivating) { SetDeviceState(kDeviceStateIdle); return; } if (!protocol_) { ESP_LOGE(TAG, "Protocol not initialized"); return; } if (device_state_ == kDeviceStateIdle) { Schedule([this]() { if (!protocol_->IsAudioChannelOpened()) { SetDeviceState(kDeviceStateConnecting); if (!protocol_->OpenAudioChannel()) { return; } } SetListeningMode(kListeningModeManualStop); }); } else if (device_state_ == kDeviceStateSpeaking) { Schedule([this]() { AbortSpeaking(kAbortReasonNone); SetListeningMode(kListeningModeManualStop); }); } } void Application::StopListening() { Schedule([this]() { if (device_state_ == kDeviceStateListening) { protocol_->SendStopListening(); SetDeviceState(kDeviceStateIdle); } }); } void Application::SendTextMessage(const std::string& text) { if (!protocol_) { ESP_LOGE(TAG, "Protocol not initialized"); return; } if (device_state_ == kDeviceStateIdle) { Schedule([this, text]() { SetDeviceState(kDeviceStateConnecting); if (!protocol_->OpenAudioChannel()) { return; } // 发送文本消息 protocol_->SendTextMessage(text); ESP_LOGI(TAG, "Sent text message: %s", text.c_str()); // 立即启动监听模式以接收语音回复 ESP_LOGI(TAG, "realtime_chat_enabled_=%s", realtime_chat_enabled_ ? "true" : "false"); SetListeningMode(realtime_chat_enabled_ ? kListeningModeRealtime : kListeningModeManualStop); }); } else if (device_state_ == kDeviceStateSpeaking) { Schedule([this, text]() { AbortSpeaking(kAbortReasonNone); protocol_->SendTextMessage(text); ESP_LOGI(TAG, "Sent text message: %s", text.c_str()); // 启动监听模式以接收语音回复 ESP_LOGI(TAG, "realtime_chat_enabled_=%s", realtime_chat_enabled_ ? "true" : "false"); SetListeningMode(realtime_chat_enabled_ ? kListeningModeRealtime : kListeningModeManualStop); }); } else if (device_state_ == kDeviceStateListening) { Schedule([this, text]() { protocol_->SendTextMessage(text); ESP_LOGI(TAG, "Sent text message: %s", text.c_str()); }); } } void Application::Start() { auto& board = Board::GetInstance(); SetDeviceState(kDeviceStateStarting); /* Setup the display */ auto display = board.GetDisplay(); /* Setup the audio codec */ auto codec = board.GetAudioCodec(); opus_decoder_ = std::make_unique(codec->output_sample_rate(), 1, OPUS_FRAME_DURATION_MS); opus_encoder_ = std::make_unique(16000, 1, OPUS_FRAME_DURATION_MS); if (realtime_chat_enabled_) { ESP_LOGI(TAG, "Realtime chat enabled, setting opus encoder complexity to 0"); opus_encoder_->SetComplexity(0); } else if (board.GetBoardType() == "ml307") { ESP_LOGI(TAG, "ML307 board detected, setting opus encoder complexity to 5"); opus_encoder_->SetComplexity(5); } else { ESP_LOGI(TAG, "WiFi board detected, setting opus encoder complexity to 3"); opus_encoder_->SetComplexity(3); } if (codec->input_sample_rate() != 16000) { input_resampler_.Configure(codec->input_sample_rate(), 16000); reference_resampler_.Configure(codec->input_sample_rate(), 16000); } codec->Start(); { int battery_level = 0; bool charging = false; bool discharging = false; if (board.GetBatteryLevel(battery_level, charging, discharging)) { // 如果电池电量低于25%,则将输出音量设置为0(静音) if (battery_level <= 25) { codec->SetOutputVolumeRuntime(0); } else { Settings s("audio", false); int vol = s.GetInt("output_volume", AudioCodec::default_output_volume()); if (vol <= 0) { vol = AudioCodec::default_output_volume(); } codec->SetOutputVolumeRuntime(vol);// 设置运行时输出音量 } } } xTaskCreatePinnedToCore([](void* arg) { Application* app = (Application*)arg; app->AudioLoop(); vTaskDelete(NULL); }, "audio_loop", 4096 * 2, this, 8, &audio_loop_task_handle_, realtime_chat_enabled_ ? 1 : 0); /* Start the main loop */ xTaskCreatePinnedToCore([](void* arg) { Application* app = (Application*)arg; app->MainLoop(); vTaskDelete(NULL); }, "main_loop", 4096 * 2, this, 4, &main_loop_task_handle_, 0); // 播放开机播报语音 - 在网络连接之前 ESP_LOGI(TAG, "设备启动完成,正在播放开机音效!"); //PlaySound(Lang::Sounds::P3_KAIJIBOBAO); 原有蜡笔小新音色 if(strcmp(CONFIG_DEVICE_ROLE, "KAKA") == 0){ PlaySound(Lang::Sounds::P3_KAKA_KAIJIBOBAO); } else if(strcmp(CONFIG_DEVICE_ROLE, "LALA") == 0){ PlaySound(Lang::Sounds::P3_LALA_KAIJIBOBAO); } /* Wait for the network to be ready */ board.StartNetwork(); // Initialize the protocol display->SetStatus(Lang::Strings::LOADING_PROTOCOL); #ifdef CONFIG_CONNECTION_TYPE_WEBSOCKET protocol_ = std::make_unique(); #else protocol_ = std::make_unique(); #endif protocol_->OnNetworkError([this](const std::string& message) { // SetDeviceState(kDeviceStateIdle); // Alert(Lang::Strings::ERROR, message.c_str(), "sad", Lang::Sounds::P3_EXCLAMATION); ESP_LOGW(TAG, "Network error occurred: %s", message.c_str()); // 检查是否是TLS连接重置错误 if (message.find("TLS") != std::string::npos || message.find("-76") != std::string::npos) { ESP_LOGI(TAG, "TLS connection reset detected, will retry connection"); SetDeviceState(kDeviceStateIdle); // 3秒后自动重试连接 Schedule([this]() { vTaskDelay(pdMS_TO_TICKS(3000)); if (GetDeviceState() == kDeviceStateIdle) { ESP_LOGI(TAG, "Auto-retrying connection after TLS error"); ToggleChatState(); } }); } else { // 其他网络错误正常处理 SetDeviceState(kDeviceStateIdle); Alert(Lang::Strings::ERROR, message.c_str(), "sad", Lang::Sounds::P3_EXCLAMATION); } }); protocol_->OnIncomingAudio([this](std::vector&& data) { std::lock_guard lock(mutex_); audio_decode_queue_.emplace_back(std::move(data)); }); protocol_->OnAudioChannelOpened([this, codec, &board]() { ESP_LOGI(TAG, "🟢 音频通道已打开"); // 🔧 关键修复:立即取消所有待执行的电源管理任务 static TaskHandle_t power_save_task = nullptr; if (power_save_task != nullptr) { vTaskDelete(power_save_task); power_save_task = nullptr; ESP_LOGI(TAG, "🔧 取消了待执行的电源管理任务"); } // 唤醒PowerSaveTimer,从低功耗模式恢复到正常模式 board.WakeUp(); // 立即禁用电源管理,确保连接稳定 ESP_LOGI(TAG, "🔄 禁用电源低功耗管理模式"); board.SetPowerSaveMode(false); if (protocol_->server_sample_rate() != codec->output_sample_rate()) { ESP_LOGW(TAG, "⚠️ 服务器采样率 %d 与设备输出采样率 %d 不匹配,重采样可能导致失真", protocol_->server_sample_rate(), codec->output_sample_rate()); } SetDecodeSampleRate(protocol_->server_sample_rate(), protocol_->server_frame_duration()); // 发送IoT状态信息 auto& thing_manager = iot::ThingManager::GetInstance(); protocol_->SendIotDescriptors(thing_manager.GetDescriptorsJson()); std::string states; if (thing_manager.GetStatesJson(states, false)) { protocol_->SendIotStates(states); } ESP_LOGI(TAG, "🟢 音频通道初始化完成"); }); protocol_->OnAudioChannelClosed([this, &board]() { ESP_LOGI(TAG, "🔴 音频通道关闭,开始清理任务"); // 🔧 关键修复:取消所有待执行的电源管理任务,防止时序冲突 static TaskHandle_t power_save_task = nullptr; if (power_save_task != nullptr) { vTaskDelete(power_save_task); power_save_task = nullptr; ESP_LOGI(TAG, "🔧 取消了之前的电源管理任务,防止时序冲突"); } // 音频处理器已经在WebSocket断开时停止了 // 等待所有后台任务完成 background_task_->WaitForCompletion(); ESP_LOGI(TAG, "🔴 后台任务完成"); // 🔧 方案2:先设置设备状态,再启用电源管理,避免时序问题 Schedule([this, &board]() { ESP_LOGI(TAG, "🔄 设置设备为空闲状态"); auto display = Board::GetInstance().GetDisplay(); display->SetChatMessage("system", ""); SetDeviceState(kDeviceStateIdle); // 状态设置完成后,再启用电源管理 vTaskDelay(pdMS_TO_TICKS(100)); ESP_LOGI(TAG, "🔄 设备已稳定在idle状态,启用电源低功耗管理"); try { board.SetPowerSaveMode(true); } catch (...) { ESP_LOGE(TAG, "❌ 设置电源管理模式失败"); } }); }); protocol_->OnIncomingJson([this, display](const cJSON* root) { // Parse JSON data auto type = cJSON_GetObjectItem(root, "type"); if (strcmp(type->valuestring, "tts") == 0) { auto state = cJSON_GetObjectItem(root, "state"); if (strcmp(state->valuestring, "start") == 0) { Schedule([this]() { aborted_ = false; if (device_state_ == kDeviceStateIdle || device_state_ == kDeviceStateListening) { SetDeviceState(kDeviceStateSpeaking); } }); } else if (strcmp(state->valuestring, "stop") == 0) { Schedule([this]() { background_task_->WaitForCompletion(); if (device_state_ == kDeviceStateSpeaking) { if (listening_mode_ == kListeningModeManualStop) { SetDeviceState(kDeviceStateIdle); } else { SetDeviceState(kDeviceStateListening); } } }); } else if (strcmp(state->valuestring, "sentence_start") == 0) { auto text = cJSON_GetObjectItem(root, "text"); if (text != NULL) { ESP_LOGI(TAG, "<< %s", text->valuestring); Schedule([this, display, message = std::string(text->valuestring)]() { display->SetChatMessage("assistant", message.c_str()); }); } } } else if (strcmp(type->valuestring, "stt") == 0) { auto text = cJSON_GetObjectItem(root, "text"); if (text != NULL) { ESP_LOGI(TAG, ">> %s", text->valuestring); Schedule([this, display, message = std::string(text->valuestring)]() { display->SetChatMessage("user", message.c_str()); }); } } else if (strcmp(type->valuestring, "llm") == 0) { auto emotion = cJSON_GetObjectItem(root, "emotion"); if (emotion != NULL) { Schedule([this, display, emotion_str = std::string(emotion->valuestring)]() { display->SetEmotion(emotion_str.c_str()); }); } } else if (strcmp(type->valuestring, "iot") == 0) { auto commands = cJSON_GetObjectItem(root, "commands"); if (commands != NULL) { auto& thing_manager = iot::ThingManager::GetInstance(); for (int i = 0; i < cJSON_GetArraySize(commands); ++i) { auto command = cJSON_GetArrayItem(commands, i); thing_manager.Invoke(command); } } // 新增代码(小程序控制 暂停/继续播放 音频) // ==================================================================== } else if (strcmp(type->valuestring, "music_control") == 0) { auto action = cJSON_GetObjectItem(root, "action"); if (action && cJSON_IsString(action) && strcmp(action->valuestring, "pause") == 0) { // 只有在speaking状态下才响应暂停指令 if (device_state_ == kDeviceStateSpeaking) { ESP_LOGI(TAG, "🔇 从服务器接收到暂停播放指令 (speaking状态)"); Schedule([this]() { PauseAudioPlayback();// 暂停播放 }); } else { ESP_LOGI(TAG, "🔇 收到暂停指令但设备不在speaking状态,忽略指令 (当前状态: %s)", STATE_STRINGS[device_state_]); } } else if (action && cJSON_IsString(action) && strcmp(action->valuestring, "resume") == 0) { // 只有在speaking状态下才响应恢复播放指令 if (device_state_ == kDeviceStateSpeaking) { ESP_LOGI(TAG, "🔊 从服务器接收到继续播放指令 (speaking状态)"); Schedule([this]() { ResumeAudioPlayback();// 恢复播放 }); } else { ESP_LOGI(TAG, "🔊 收到恢复播放指令但设备不在speaking状态,忽略指令 (当前状态: %s)", STATE_STRINGS[device_state_]); } } else if (action && cJSON_IsString(action) && strcmp(action->valuestring, "play") == 0) { // 处理新故事推送 - 确保在音频暂停状态和播放状态下都能正常播放 ESP_LOGI(TAG, "🎵 从服务器接收到新故事推送指令 (action: play)"); Schedule([this]() { // 参考 AbortSpeakingAndReturnToListening 第1583-1651行的逻辑 // 检查并处理音频暂停状态,确保新故事能正常播放 if (audio_paused_) { ESP_LOGI(TAG, "🔵 检测到音频暂停状态,为新故事推送清除暂停状态"); audio_paused_ = false; ESP_LOGI(TAG, "✅ 音频暂停状态已清除"); // 清空音频播放队列,避免播放暂停时残留的音频 std::unique_lock lock(mutex_); audio_decode_queue_.clear(); lock.unlock(); ESP_LOGI(TAG, "🧹 已清空音频播放队列,避免播放残留音频"); // 重新启用音频编解码器输出 auto& board = Board::GetInstance(); auto codec = board.GetAudioCodec(); if (codec) { codec->EnableOutput(true); ESP_LOGI(TAG, "🔧 为新故事推送重新启用音频编解码器输出"); } } // 如果当前在播放状态,也需要清空队列确保新故事能正常播放 if (device_state_ == kDeviceStateSpeaking) { ESP_LOGI(TAG, "🔵 当前在播放状态,为新故事推送清空音频队列"); std::unique_lock lock(mutex_); audio_decode_queue_.clear(); lock.unlock(); ESP_LOGI(TAG, "🧹 已清空音频播放队列,准备播放新故事"); // 确保音频编解码器输出已启用 auto& board = Board::GetInstance(); auto codec = board.GetAudioCodec(); if (codec) { codec->EnableOutput(true); ESP_LOGI(TAG, "🔧 确保音频编解码器输出已启用"); } } ESP_LOGI(TAG, "🎵 新故事推送处理完成,音频系统已准备就绪"); }); } } // ==================================================================== }); protocol_->Start(); // Check for new firmware version or get the MQTT broker address ota_.SetCheckVersionUrl(CONFIG_OTA_VERSION_URL); ota_.SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str()); ota_.SetHeader("Client-Id", board.GetUuid()); ota_.SetHeader("Accept-Language", Lang::CODE); auto app_desc = esp_app_get_description(); ota_.SetHeader("User-Agent", std::string(BOARD_NAME "/") + app_desc->version); // 禁用自动OTA升级更新 - 注释掉下面的任务创建 xTaskCreate([](void* arg) { Application* app = (Application*)arg; app->CheckNewVersion(); vTaskDelete(NULL); }, "check_new_version", 4096 * 2, this, 2, nullptr); #if CONFIG_USE_AUDIO_PROCESSOR audio_processor_.Initialize(codec, realtime_chat_enabled_); // 🎯 根据语音打断功能启用状态配置VAD参数 EchoAwareVadParams enhanced_params; if (realtime_chat_enabled_) { // 语音打断功能启用:配置增强的回声感知参数 - 基于小智AI官方优化方案 // 🎯 平衡配置 - 防误触发同时保证音频流畅 enhanced_params.snr_threshold = 60.0f; // 平衡基础阈值:足够严格但不过度 enhanced_params.min_silence_ms = 2000; // 平衡静音要求:2秒 enhanced_params.interrupt_cooldown_ms = 10000; // 平衡冷却时间:10秒 enhanced_params.adaptive_threshold = true; // 启用自适应阈值 // 🔊 平衡噪声抑制参数 - 优化性能与效果 enhanced_params.adaptive_noise_suppression = true; // 启用自适应噪声抑制 enhanced_params.noise_suppression_base = 5.0f; // 平衡基础抑制强度 enhanced_params.volume_sensitivity = 3.0f; // 平衡音量敏感度:适度的音量影响 enhanced_params.echo_detection_threshold = 0.15f; // 平衡回声检测阈值 enhanced_params.distance_estimation_factor = 3.0f; // 平衡距离估算因子 ESP_LOGI(TAG, "🎯 Adaptive noise suppression enabled for realtime chat - smart volume/distance adjustment"); } else { // 🔧 语音打断功能禁用:关闭复杂VAD,只使用简单VAD enhanced_params.adaptive_threshold = false; // 禁用自适应阈值 enhanced_params.adaptive_noise_suppression = false; // 禁用自适应噪声抑制 ESP_LOGI(TAG, "🔧 Using simple VAD for basic voice detection - complex echo-aware VAD disabled"); } audio_processor_.SetEchoAwareParams(enhanced_params); audio_processor_.OnOutput([this](std::vector&& data) { background_task_->Schedule([this, data = std::move(data)]() mutable { opus_encoder_->Encode(std::move(data), [this](std::vector&& opus) { Schedule([this, opus = std::move(opus)]() { protocol_->SendAudio(opus); }); }); }); }); // 🎯 根据语音打断功能启用状态选择VAD类型 if (realtime_chat_enabled_) { // 语音打断功能启用:使用复杂的回声感知VAD audio_processor_.OnVadStateChange([this](bool speaking) { ESP_LOGI(TAG, "Complex VAD state change: speaking=%s, device_state=%d, listening_mode=%d", speaking ? "true" : "false", (int)device_state_, (int)listening_mode_); if (device_state_ == kDeviceStateListening) { Schedule([this, speaking]() { if (speaking) { voice_detected_ = true; } else { voice_detected_ = false; } auto led = Board::GetInstance().GetLed(); led->OnStateChanged(); }); } }); } // 🔧 简单VAD:用于普通业务(触摸忽略、LED状态等) audio_processor_.OnSimpleVadStateChange([this](bool speaking) { ESP_LOGI(TAG, "Simple VAD state change: speaking=%s, device_state=%d", speaking ? "true" : "false", (int)device_state_); if (device_state_ == kDeviceStateListening) { Schedule([this, speaking]() { if (speaking) { voice_detected_ = true; } else { voice_detected_ = false; } auto led = Board::GetInstance().GetLed(); led->OnStateChanged(); }); } // 🔊 语音打断逻辑:只在简单VAD中处理,因为复杂VAD可能过于严格 if (device_state_ == kDeviceStateSpeaking && listening_mode_ == kListeningModeRealtime) { Schedule([this, speaking]() { static auto speech_start_time = std::chrono::steady_clock::now(); static bool speech_confirmation_pending = false; auto now = std::chrono::steady_clock::now(); if (speaking) { // 小智AI方案:检测到人声开始,启动确认流程 speech_start_time = now; speech_confirmation_pending = true; ESP_LOGD(TAG, "Human voice detected during playback, starting interrupt evaluation"); } else if (speech_confirmation_pending) { // 小智AI方案:人声结束,评估是否触发打断 auto duration = std::chrono::duration_cast(now - speech_start_time); // 🎯 平衡自适应打断策略:防误触发同时保证响应性 // 基础持续时间:3秒,平衡根据干扰情况调整 int required_duration = 3000; // 基础要求3秒 // 🔊 根据当前音量动态调整持续时间要求(平衡策略) if (current_speaker_volume_ > 0.4f) { required_duration = 5000; // 高音量:5秒 } else if (current_speaker_volume_ > 0.1f) { required_duration = 4000; // 中音量:4秒 } // 低音量或静音:保持3秒 if (duration.count() >= required_duration) { static auto last_interrupt_time = std::chrono::steady_clock::now(); auto interrupt_duration = std::chrono::duration_cast(now - last_interrupt_time); // 🎯 平衡自适应多重保护机制:防误触发同时保证性能 bool volume_protection = (current_speaker_volume_ > 0.01f); // 平衡音量保护:1%阈值 bool cooldown_protection = (interrupt_duration.count() <= 10000); // 平衡冷却:10秒 static int false_positive_count = 0; // 误触发计数器 // 🎯 平衡连续误触发保护:适度学习机制 if (interrupt_duration.count() <= 20000) { // 20秒内的误触发相关 false_positive_count++; } else if (interrupt_duration.count() > 60000) { // 60秒后开始衰减 false_positive_count = std::max(false_positive_count - 1, 0); // 适度衰减 } bool consecutive_protection = (false_positive_count >= 2); // 2次误触发后保护 if (!volume_protection && !cooldown_protection && !consecutive_protection) { // 小智AI核心逻辑:StopPlayback -> SetDeviceState(Listening) ESP_LOGI(TAG, "🎯 Adaptive voice interrupt triggered (duration: %.0fms/%dms, vol: %.3f) - stopping playback", (float)duration.count(), required_duration, current_speaker_volume_); AbortSpeaking(kAbortReasonVoiceInterrupt); SetDeviceState(kDeviceStateListening); last_interrupt_time = now; } else { ESP_LOGI(TAG, "🎯 Adaptive interrupt suppressed - vol_protection: %s (%.3f), cooldown: %.0fms, consecutive: %d", volume_protection ? "true" : "false", current_speaker_volume_, 10000.0f - (float)interrupt_duration.count(), false_positive_count); } } else { ESP_LOGI(TAG, "🎯 Voice too brief (%.0fms), likely echo or noise - adaptive threshold: %dms", (float)duration.count(), required_duration); } speech_confirmation_pending = false; } }); } }); #endif #if CONFIG_USE_WAKE_WORD_DETECT || CONFIG_USE_CUSTOM_WAKE_WORD if (!wake_word_detect_.Initialize(codec)) { ESP_LOGE(TAG, "Failed to initialize wake word detection"); return; } wake_word_detect_.OnWakeWordDetected([this](const std::string& wake_word) { Schedule([this, &wake_word]() { if (device_state_ == kDeviceStateIdle) { SetDeviceState(kDeviceStateConnecting); wake_word_detect_.EncodeWakeWordData(); // 打开音频通道并发送唤醒词数据到服务器 if (!protocol_->OpenAudioChannel()) { wake_word_detect_.Start(); return; } // 编码并发送唤醒词音频数据 std::vector opus; // Encode and send the wake word data to the server while (wake_word_detect_.GetWakeWordOpus(opus)) { protocol_->SendAudio(opus); } // Set the chat state to wake word detected protocol_->SendWakeWordDetected(wake_word); ESP_LOGI(TAG, "Wake word detected: %s", wake_word.c_str()); SetListeningMode(realtime_chat_enabled_ ? kListeningModeRealtime : kListeningModeAutoStop); } else if (device_state_ == kDeviceStateSpeaking) { AbortSpeaking(kAbortReasonWakeWordDetected); } else if (device_state_ == kDeviceStateActivating) { SetDeviceState(kDeviceStateIdle); } }); }); wake_word_detect_.Start(); #endif SetDeviceState(kDeviceStateIdle); esp_timer_start_periodic(clock_timer_handle_, 1000000); #if 0 while (true) { SystemInfo::PrintRealTimeStats(pdMS_TO_TICKS(1000)); vTaskDelay(pdMS_TO_TICKS(10000)); } #endif } // 时钟定时器回调函数 void Application::OnClockTimer() { clock_ticks_++; // Print the debug info every 10 seconds if (clock_ticks_ % 10 == 0) { int free_sram = heap_caps_get_free_size(MALLOC_CAP_INTERNAL); int min_free_sram = heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL); ESP_LOGI(TAG, "Free internal: %u minimal internal: %u", free_sram, min_free_sram); // // 打印Wi-Fi的Mac地址 // ESP_LOGI(MAC_TAG, "Wi-Fi MAC Address: %s", SystemInfo::GetMacAddress().c_str());// 生产测试打印 //ESP_LOGI(TAG, "此设备角色为: %s",CONFIG_DEVICE_ROLE); // ESP_LOGI(TAG, "此设备角色为: KAKA 1028 升级成功!"); // If we have synchronized server time, set the status to clock "HH:MM" if the device is idle if (ota_.HasServerTime()) { if (device_state_ == kDeviceStateIdle) { Schedule([this]() { // Set status to clock "HH:MM" time_t now = time(NULL); char time_str[64]; strftime(time_str, sizeof(time_str), "%H:%M ", localtime(&now)); Board::GetInstance().GetDisplay()->SetStatus(time_str); }); } } } } // 添加任务到主循环 void Application::Schedule(std::function callback) { { std::lock_guard lock(mutex_);// 加锁保护任务队列 main_tasks_.push_back(std::move(callback));// 添加任务到队列 } xEventGroupSetBits(event_group_, SCHEDULE_EVENT);// 通知主循环有任务需要执行 } // The Main Loop controls the chat state and websocket connection // If other tasks need to access the websocket or chat state, // they should use Schedule to call this function void Application::MainLoop() { while (true) { auto bits = xEventGroupWaitBits(event_group_, SCHEDULE_EVENT, pdTRUE, pdFALSE, portMAX_DELAY); if (bits & SCHEDULE_EVENT) { std::unique_lock lock(mutex_); std::list> tasks = std::move(main_tasks_); lock.unlock(); for (auto& task : tasks) { task(); } } } } // The Audio Loop is used to input and output audio data void Application::AudioLoop() { auto codec = Board::GetInstance().GetAudioCodec(); while (true) { OnAudioInput(); if (codec->output_enabled()) { OnAudioOutput(); } } } // 音频输出函数 void Application::OnAudioOutput() { auto now = std::chrono::steady_clock::now(); auto codec = Board::GetInstance().GetAudioCodec(); const int max_silence_seconds = 10; std::unique_lock lock(mutex_); // 新增代码(小程序控制 暂停/继续播放 音频) // ========================================================= // 🔧 暂停状态下停止从队列取数据,但保留队列状态 if (audio_paused_) { // 暂停时重置音量状态,避免误判 if (current_speaker_volume_ > 0.0f) { current_speaker_volume_ = 0.0f; #if CONFIG_USE_AUDIO_PROCESSOR audio_processor_.SetSpeakerVolume(0.0f); #endif } return; } // ========================================================= if (audio_decode_queue_.empty()) { // 重要:没有音频数据时立即重置音量状态 if (current_speaker_volume_ > 0.0f) { current_speaker_volume_ = 0.0f; #if CONFIG_USE_AUDIO_PROCESSOR audio_processor_.SetSpeakerVolume(0.0f); #endif } // Disable the output if there is no audio data for a long time if (device_state_ == kDeviceStateIdle) { auto duration = std::chrono::duration_cast(now - last_output_time_).count(); if (duration > max_silence_seconds) { codec->EnableOutput(false); } } return; } if (device_state_ == kDeviceStateListening) { audio_decode_queue_.clear(); // 重要:清空播放队列时重置音量状态,避免误判 current_speaker_volume_ = 0.0f; #if CONFIG_USE_AUDIO_PROCESSOR audio_processor_.SetSpeakerVolume(0.0f); #endif return; } auto opus = std::move(audio_decode_queue_.front()); audio_decode_queue_.pop_front(); lock.unlock(); background_task_->Schedule([this, codec, opus = std::move(opus)]() mutable { if (aborted_) { return; } std::vector pcm; if (!opus_decoder_->Decode(std::move(opus), pcm)) { return; } // Resample if the sample rate is different if (opus_decoder_->sample_rate() != codec->output_sample_rate()) { int target_size = output_resampler_.GetOutputSamples(pcm.size()); std::vector resampled(target_size); output_resampler_.Process(pcm.data(), pcm.size(), resampled.data()); pcm = std::move(resampled); } // 计算并同步音频输出音量到音频处理器,用于动态VAD阈值调整 if (realtime_chat_enabled_ && !pcm.empty()) { // 计算音频块的RMS音量 (0.0 - 1.0) float sum_squares = 0.0f; for (const auto& sample : pcm) { float normalized = (float)sample / 32768.0f; sum_squares += normalized * normalized; } float rms_volume = std::sqrt(sum_squares / pcm.size()); #if CONFIG_USE_AUDIO_PROCESSOR // 同步音量到音频处理器,用于动态阈值调整 current_speaker_volume_ = rms_volume; // 保存当前音量供打断逻辑使用 audio_processor_.SetSpeakerVolume(rms_volume); #endif } codec->OutputData(pcm); last_output_time_ = std::chrono::steady_clock::now(); }); } void Application::OnAudioInput() { std::vector data; #if CONFIG_USE_WAKE_WORD_DETECT || CONFIG_USE_CUSTOM_WAKE_WORD if (wake_word_detect_.IsRunning()) { ReadAudio(data, 16000, wake_word_detect_.GetFeedSize()); wake_word_detect_.Feed(data);// 将音频数据喂给唤醒词检测器 return; } #endif #if CONFIG_USE_AUDIO_PROCESSOR if (audio_processor_.IsRunning()) { ReadAudio(data, 16000, audio_processor_.GetFeedSize()); audio_processor_.Feed(data); return; } #else if (device_state_ == kDeviceStateListening) { ReadAudio(data, 16000, 30 * 16000 / 1000); background_task_->Schedule([this, data = std::move(data)]() mutable { opus_encoder_->Encode(std::move(data), [this](std::vector&& opus) { Schedule([this, opus = std::move(opus)]() { protocol_->SendAudio(opus); }); }); }); return; } #endif vTaskDelay(pdMS_TO_TICKS(30)); } void Application::ReadAudio(std::vector& data, int sample_rate, int samples) { auto codec = Board::GetInstance().GetAudioCodec(); if (codec->input_sample_rate() != sample_rate) { data.resize(samples * codec->input_sample_rate() / sample_rate); if (!codec->InputData(data)) { return; } if (codec->input_channels() == 2) { auto mic_channel = std::vector(data.size() / 2); auto reference_channel = std::vector(data.size() / 2); for (size_t i = 0, j = 0; i < mic_channel.size(); ++i, j += 2) { mic_channel[i] = data[j]; reference_channel[i] = data[j + 1]; } auto resampled_mic = std::vector(input_resampler_.GetOutputSamples(mic_channel.size())); auto resampled_reference = std::vector(reference_resampler_.GetOutputSamples(reference_channel.size())); input_resampler_.Process(mic_channel.data(), mic_channel.size(), resampled_mic.data()); reference_resampler_.Process(reference_channel.data(), reference_channel.size(), resampled_reference.data()); data.resize(resampled_mic.size() + resampled_reference.size()); for (size_t i = 0, j = 0; i < resampled_mic.size(); ++i, j += 2) { data[j] = resampled_mic[i]; data[j + 1] = resampled_reference[i]; } } else { auto resampled = std::vector(input_resampler_.GetOutputSamples(data.size())); input_resampler_.Process(data.data(), data.size(), resampled.data()); data = std::move(resampled); } } else { data.resize(samples); if (!codec->InputData(data)) { return; } } } // 打断语音播报(终止播放) void Application::AbortSpeaking(AbortReason reason) { // 🔧 防止重复中止操作,避免竞态条件 bool expected = false; if (!is_aborting_.compare_exchange_strong(expected, true)) { ESP_LOGD(TAG, "AbortSpeaking already in progress, ignoring duplicate call"); return; } ESP_LOGI(TAG, "🔴 Abort speaking - immediate stop"); aborted_ = true; // 🔧 更新安全操作时间戳 last_safe_operation_.store(std::chrono::steady_clock::now()); // 🔧 修复:立即清空音频队列,确保音频播放立即停止 { std::lock_guard lock(mutex_); if (!audio_decode_queue_.empty()) { ESP_LOGI(TAG, "🔴 Clearing %zu audio frames from queue", audio_decode_queue_.size()); audio_decode_queue_.clear(); // 重置音量状态 current_speaker_volume_ = 0.0f; #if CONFIG_USE_AUDIO_PROCESSOR audio_processor_.SetSpeakerVolume(0.0f); #endif } } // ⚠️ 移除WaitForCompletion避免死锁,让后台任务通过aborted_标志自然结束 ESP_LOGI(TAG, "🔴 Audio queue cleared, background tasks will stop on next iteration"); // 🔧 修复:安全地发送中止消息,避免WebSocket崩溃 if (protocol_ && device_state_ == kDeviceStateSpeaking && IsSafeToOperate()) { try { // 🔧 额外的连接状态验证 if (protocol_->IsAudioChannelOpened()) { protocol_->SendAbortSpeaking(reason); ESP_LOGI(TAG, "AbortSpeaking message sent successfully"); // 更新安全操作时间戳 last_safe_operation_.store(std::chrono::steady_clock::now()); } else { ESP_LOGW(TAG, "Audio channel not properly opened, skipping AbortSpeaking"); } } catch (const std::exception& e) { ESP_LOGW(TAG, "Failed to send AbortSpeaking message: %s", e.what()); } } else { ESP_LOGD(TAG, "Skipping AbortSpeaking message - conditions not safe"); } // 🔧 重置中止标志,允许后续操作 is_aborting_.store(false); } // 发送讲故事请求 【新增】 void Application::SendStoryRequest() { if (!protocol_) { ESP_LOGE(TAG, "Protocol not initialized"); return; } if (device_state_ == kDeviceStateIdle) { // 设备状态为待机 Schedule([this]() { SetDeviceState(kDeviceStateConnecting); if (!protocol_->OpenAudioChannel()) { return; } protocol_->SendStoryRequest(); // 发送讲故事请求 ESP_LOGI(TAG, "Sent story request"); // 立即启动监听模式以接收语音回复 ESP_LOGI(TAG, "SendStoryRequest: realtime_chat_enabled_ = %s", realtime_chat_enabled_ ? "true" : "false"); SetListeningMode(realtime_chat_enabled_ ? kListeningModeRealtime : kListeningModeManualStop); }); } else if (device_state_ == kDeviceStateSpeaking) { // 设备状态为说话 Schedule([this]() { AbortSpeaking(kAbortReasonNone); protocol_->SendStoryRequest(); // 发送讲故事请求 ESP_LOGI(TAG, "Sent story request"); // 启动监听模式以接收语音回复 ESP_LOGI(TAG, "SendStoryRequest: realtime_chat_enabled_ = %s", realtime_chat_enabled_ ? "true" : "false"); SetListeningMode(realtime_chat_enabled_ ? kListeningModeRealtime : kListeningModeManualStop); }); } else if (device_state_ == kDeviceStateListening) { // 设备状态为监听 Schedule([this]() { protocol_->SendStoryRequest(); // 发送讲故事请求(调用协议层) ESP_LOGI(TAG, "Sent story request"); }); } } void Application::SetListeningMode(ListeningMode mode) { ESP_LOGI(TAG, "Setting listening mode to %d", (int)mode); listening_mode_ = mode; SetDeviceState(kDeviceStateListening); } void Application::SetDeviceState(DeviceState state) { if (device_state_ == state) { return; } clock_ticks_ = 0; auto previous_state = device_state_; device_state_ = state; ESP_LOGI(TAG, "STATE: %s", STATE_STRINGS[device_state_]); // The state is changed, wait for all background tasks to finish background_task_->WaitForCompletion(); auto& board = Board::GetInstance(); auto display = board.GetDisplay(); auto led = board.GetLed(); led->OnStateChanged(); // 检查是否正在进行BluFi配网,配网时禁止播放待命音效(新增代码) // ================================================================= bool is_blufi_provisioning = false; if (Board::GetInstance().GetBoardType() == "wifi") { auto& wifi_board = static_cast(Board::GetInstance()); is_blufi_provisioning = wifi_board.IsBluFiProvisioningActive(); } // ================================================================= switch (state) { case kDeviceStateUnknown: case kDeviceStateIdle: display->SetStatus(Lang::Strings::STANDBY); display->SetEmotion("neutral"); // // 只有从非待命状态进入待命状态时才播放待命音效,避免重复播放(原来的代码) // if (previous_state != kDeviceStateIdle && // previous_state != kDeviceStateUnknown && // previous_state != kDeviceStateWifiConfiguring) { // ESP_LOGI(TAG, "Entering idle state, playing standby sound"); // PlaySound(Lang::Sounds::P3_DAIMING); // } // 开机后 进入待命状态 播报 卡卡正在待命(配网模式下不播报“卡卡正在待命”)-新增代码 //===================================================================================== if (previous_state != kDeviceStateIdle && previous_state != kDeviceStateUnknown && previous_state != kDeviceStateWifiConfiguring && !is_blufi_provisioning && !IsLowBatteryTransition()) { ESP_LOGI(TAG, "Entering idle state, playing standby sound"); // PlaySound(Lang::Sounds::P3_DAIMING); 原有 待命 播报 if(strcmp(CONFIG_DEVICE_ROLE, "KAKA") == 0){ PlaySound(Lang::Sounds::P3_KAKA_DAIMING); } else if(strcmp(CONFIG_DEVICE_ROLE, "LALA") == 0){ PlaySound(Lang::Sounds::P3_LALA_DAIMING); } } //===================================================================================== #if CONFIG_USE_AUDIO_PROCESSOR audio_processor_.Stop(); #endif #if CONFIG_USE_WAKE_WORD_DETECT || CONFIG_USE_CUSTOM_WAKE_WORD wake_word_detect_.Start(); #endif break; case kDeviceStateConnecting: display->SetStatus(Lang::Strings::CONNECTING); display->SetEmotion("neutral"); display->SetChatMessage("system", ""); break; case kDeviceStateListening: display->SetStatus(Lang::Strings::LISTENING); display->SetEmotion("neutral"); // 关键修复:只有在非音效播放状态下才重置音量,避免中断正在播放的音效 // 检查是否有音频正在播放,如果有则延迟重置音量 if (IsAudioQueueEmpty()) { current_speaker_volume_ = 0.0f; #if CONFIG_USE_AUDIO_PROCESSOR audio_processor_.SetSpeakerVolume(0.0f); #endif } else { // 如果有音频正在播放,延迟重置音量 Schedule([this]() { vTaskDelay(pdMS_TO_TICKS(500)); // 等待音效播放完成 current_speaker_volume_ = 0.0f; #if CONFIG_USE_AUDIO_PROCESSOR audio_processor_.SetSpeakerVolume(0.0f); #endif }); } // Update the IoT states before sending the start listening command UpdateIotStates(); // Make sure the audio processor is running #if CONFIG_USE_AUDIO_PROCESSOR if (!audio_processor_.IsRunning()) { #else if (true) { #endif // 🔧 关键修复:检查协议连接状态,防止发送到无效连接 if (protocol_ && protocol_->IsAudioChannelOpened()) { // Send the start listening command protocol_->SendStartListening(listening_mode_); if (listening_mode_ == kListeningModeAutoStop && previous_state == kDeviceStateSpeaking) { // FIXME: Wait for the speaker to empty the buffer vTaskDelay(pdMS_TO_TICKS(120)); } opus_encoder_->ResetState(); #if CONFIG_USE_WAKE_WORD_DETECT || CONFIG_USE_CUSTOM_WAKE_WORD wake_word_detect_.Stop(); #endif #if CONFIG_USE_AUDIO_PROCESSOR audio_processor_.Start(); #endif } else { ESP_LOGW(TAG, "Audio channel not available, skipping SendStartListening"); // 保持在聆听状态,不自动回退到idle状态 ESP_LOGI(TAG, "🔵 Staying in listening state despite audio channel unavailable"); } } break; case kDeviceStateSpeaking: display->SetStatus(Lang::Strings::SPEAKING); if (listening_mode_ != kListeningModeRealtime) { #if CONFIG_USE_AUDIO_PROCESSOR audio_processor_.Stop(); #endif #if CONFIG_USE_WAKE_WORD_DETECT || CONFIG_USE_CUSTOM_WAKE_WORD wake_word_detect_.Start(); #endif } else { // 在实时模式下,保持audio_processor运行以检测语音打断 #if CONFIG_USE_AUDIO_PROCESSOR if (!audio_processor_.IsRunning()) { audio_processor_.Start(); } #endif #if CONFIG_USE_WAKE_WORD_DETECT || CONFIG_USE_CUSTOM_WAKE_WORD wake_word_detect_.Stop(); #endif } ResetDecoder(); break; default: // Do nothing break; } } void Application::ResetDecoder() { std::lock_guard lock(mutex_); opus_decoder_->ResetState(); audio_decode_queue_.clear(); last_output_time_ = std::chrono::steady_clock::now(); auto codec = Board::GetInstance().GetAudioCodec(); codec->EnableOutput(true); } void Application::SetDecodeSampleRate(int sample_rate, int frame_duration) { if (opus_decoder_->sample_rate() == sample_rate && opus_decoder_->duration_ms() == frame_duration) { return; } opus_decoder_.reset(); opus_decoder_ = std::make_unique(sample_rate, 1, frame_duration); auto codec = Board::GetInstance().GetAudioCodec(); if (opus_decoder_->sample_rate() != codec->output_sample_rate()) { ESP_LOGI(TAG, "Resampling audio from %d to %d", opus_decoder_->sample_rate(), codec->output_sample_rate()); output_resampler_.Configure(opus_decoder_->sample_rate(), codec->output_sample_rate()); } } void Application::UpdateIotStates() { auto& thing_manager = iot::ThingManager::GetInstance(); std::string states; if (thing_manager.GetStatesJson(states, true)) { protocol_->SendIotStates(states); } } void Application::Reboot() { ESP_LOGI(TAG, "Rebooting..."); esp_restart(); } // 唤醒词触发函数 void Application::WakeWordInvoke(const std::string& wake_word) { if (device_state_ == kDeviceStateIdle) { ToggleChatState(); Schedule([this, wake_word]() { if (protocol_) { protocol_->SendWakeWordDetected(wake_word); } }); } else if (device_state_ == kDeviceStateSpeaking) { //AbortSpeakingAndReturnToListening();// 使用唤醒词打断时立即切换到聆听状态 Schedule([this]() { AbortSpeaking(kAbortReasonNone); }); } else if (device_state_ == kDeviceStateListening) { Schedule([this]() { if (protocol_) { protocol_->CloseAudioChannel(); } }); } } bool Application::CanEnterSleepMode() { if (device_state_ != kDeviceStateIdle) { return false; } if (protocol_ && protocol_->IsAudioChannelOpened()) { return false; } // Now it is safe to enter sleep mode return true; } void Application::WaitForAudioPlayback() { // 等待 audio_decode_queue_ 清空且音频输出完成 auto codec = Board::GetInstance().GetAudioCodec(); int timeout_count = 0; const int max_timeout = 150; // 3秒超时 (150 * 20ms = 3000ms) while (timeout_count < max_timeout) { { std::lock_guard lock(mutex_); if (audio_decode_queue_.empty()) { // 检查音频输出是否已关闭或静音 if (!codec->output_enabled() || device_state_ != kDeviceStateSpeaking) { ESP_LOGI(TAG, "Audio playback completed"); break; } } } vTaskDelay(pdMS_TO_TICKS(20)); timeout_count++; } if (timeout_count >= max_timeout) { ESP_LOGW(TAG, "WaitForAudioPlayback timeout after 3 seconds"); } } bool Application::IsAudioQueueEmpty() { std::lock_guard lock(mutex_); return audio_decode_queue_.empty(); } void Application::ClearAudioQueue() { std::lock_guard lock(mutex_); audio_decode_queue_.clear(); audio_paused_ = false; // 清除暂停状态 // ESP_LOGI(TAG, "🧹 音频播放队列已清空,暂停状态已清除"); ESP_LOGI(TAG, "🎵 测试模式:音频开始播放,等待播放完成"); // 生产测试打印 // 重新启用音频编解码器输出,确保后续音频能正常播放 auto& board = Board::GetInstance(); auto codec = board.GetAudioCodec(); if (codec) { codec->EnableOutput(true); // ESP_LOGI(TAG, "🔧 音频编解码器输出已重新启用"); ESP_LOGI(TAG, "✅ 测试模式:音频播放完成"); // 生产测试打印 } } // 🔧 检查当前是否可以安全执行操作 bool Application::IsSafeToOperate() { // 检查是否正在执行中止操作 if (is_aborting_.load()) { return false; } // 检查最近是否有操作过于频繁 auto now = std::chrono::steady_clock::now(); auto last_op = last_safe_operation_.load(); auto time_diff = std::chrono::duration_cast(now - last_op); // 如果距离上次操作少于50ms,认为可能存在竞态风险 if (time_diff.count() < 50) { ESP_LOGD(TAG, "Operation too frequent, waiting for safety"); return false; } return true; } void Application::StopAudioProcessor() { #if CONFIG_USE_AUDIO_PROCESSOR audio_processor_.Stop(); #endif } // 🔴 专门处理从说话状态到空闲状态的切换 void Application::AbortSpeakingAndReturnToIdle() { ESP_LOGI(TAG, "🔴 AbortSpeakingAndReturnToIdle: Starting transition from speaking to idle state"); ESP_LOGI(TAG, "📊 当前设备状态: %s", STATE_STRINGS[device_state_]); ESP_LOGI(TAG, "🎯 目标状态: idle (空闲状态)"); // 检查当前状态是否为说话状态 if (device_state_ != kDeviceStateSpeaking) { ESP_LOGW(TAG, "🔴 AbortSpeakingAndReturnToIdle: Device not in speaking state, current state: %s", STATE_STRINGS[device_state_]); return; } ESP_LOGI(TAG, "✅ 状态检查通过,当前处于说话状态"); // 检查操作安全性 if (!IsSafeToOperate()) { ESP_LOGW(TAG, "🔴 AbortSpeakingAndReturnToIdle: Operation not safe, scheduling retry"); Schedule([this]() { vTaskDelay(pdMS_TO_TICKS(100)); AbortSpeakingAndReturnToIdle(); }); return; } // 更新安全操作时间戳 last_safe_operation_.store(std::chrono::steady_clock::now()); ESP_LOGI(TAG, "⏰ 安全操作时间戳已更新"); // 立即停止音频处理 ESP_LOGI(TAG, "🔇 开始停止音频处理"); { std::lock_guard lock(mutex_); if (!audio_decode_queue_.empty()) { ESP_LOGI(TAG, "🗑️ 清空音频队列,当前队列大小: %zu", audio_decode_queue_.size()); audio_decode_queue_.clear(); current_speaker_volume_ = 0.0f; #if CONFIG_USE_AUDIO_PROCESSOR audio_processor_.SetSpeakerVolume(0.0f); #endif ESP_LOGI(TAG, "✅ 音频队列已清空,音量已重置为0"); } else { ESP_LOGI(TAG, "ℹ️ 音频队列已为空,无需清空"); } } ESP_LOGI(TAG, "🔴 AbortSpeakingAndReturnToIdle: Sending abort message to server"); // 发送中止消息给服务器 if (protocol_ && protocol_->IsAudioChannelOpened()) { ESP_LOGI(TAG, "📡 WebSocket连接正常,发送中止消息"); try { protocol_->SendAbortSpeaking(kAbortReasonNone); ESP_LOGI(TAG, "✅ 中止消息发送成功"); } catch (const std::exception& e) { ESP_LOGW(TAG, "❌ 发送中止消息失败: %s", e.what()); } // 延迟100ms后主动关闭连接,确保服务器有时间处理中止消息 Schedule([this]() { vTaskDelay(pdMS_TO_TICKS(100)); ESP_LOGI(TAG, "⏳ 延迟100ms后开始主动关闭WebSocket连接"); ESP_LOGI(TAG, "🔌 执行主动断开WebSocket连接"); if (protocol_) { protocol_->CloseAudioChannel(); ESP_LOGI(TAG, "✅ CloseAudioChannel调用完成"); } else { ESP_LOGW(TAG, "⚠️ protocol_为空,无法关闭音频通道"); } }); } else { ESP_LOGW(TAG, "⚠️ WebSocket连接不可用,强制关闭连接"); if (protocol_) { ESP_LOGI(TAG, "🔌 强制执行WebSocket断开"); protocol_->CloseAudioChannel(); ESP_LOGI(TAG, "✅ 强制断开完成"); } else { ESP_LOGW(TAG, "❌ protocol_为空,无法执行断开操作"); } } ESP_LOGI(TAG, "🎯 主动断开流程已启动,等待OnAudioChannelClosed回调触发状态转换"); ESP_LOGI(TAG, "📋 预期流程: WebSocket断开 → 回调触发 → 转换到idle状态 → 播放待机音"); } // 🔵 专门处理从说话状态到聆听状态的切换 void Application::AbortSpeakingAndReturnToListening() { ESP_LOGI(TAG, "🔵 AbortSpeakingAndReturnToListening: Starting transition from speaking to listening state (断开连接方案)"); // 检查当前状态是否为说话状态或可切换状态 // ========================================================================================= if (device_state_ != kDeviceStateSpeaking && device_state_ != kDeviceStateListening && device_state_ != kDeviceStateIdle) { ESP_LOGW(TAG, "🔵 AbortSpeakingAndReturnToListening: Device not in valid state for transition, current state: %s", STATE_STRINGS[device_state_]); return; } // 如果已经在listening状态,直接返回避免重复切换 if (device_state_ == kDeviceStateListening) { ESP_LOGI(TAG, "🔵 AbortSpeakingAndReturnToListening: Already in listening state, skipping transition"); return; } // 🔧 检查并处理音频播放状态(BOOT按键优化方案) if (!audio_paused_ && device_state_ == kDeviceStateSpeaking) { ESP_LOGI(TAG, "🔵 检测到播放状态,一次按键完成暂停和状态切换"); // 第一步:禁用音频输出(立即停止播放) auto& board = Board::GetInstance();// 获取音频编解码器 auto codec = board.GetAudioCodec();// 获取音频编解码器 if (codec) { codec->EnableOutput(false);// 暂停时禁用音频编解码器输出 ESP_LOGI(TAG, "🔧 暂停时禁用音频编解码器输出"); } // 第二步:切换到暂停状态 audio_paused_ = true; ESP_LOGI(TAG, "✅ 已切换到暂停状态"); // 第三步:立即执行状态切换逻辑(不返回,继续执行下面的代码) ESP_LOGI(TAG, "🔵 继续执行状态切换到聆听状态"); } // 🔧 检查并处理音频暂停状态(BOOT按键优化方案) if (audio_paused_) { ESP_LOGI(TAG, "🔵 检测到音频暂停状态,应用BOOT按键优化方案"); audio_paused_ = false; ESP_LOGI(TAG, "✅ 音频暂停状态已清除"); // 🔧 关键优化:清空音频播放队列,避免播放暂停时残留的音频 std::unique_lock lock(mutex_); audio_decode_queue_.clear(); lock.unlock(); ESP_LOGI(TAG, "🧹 已清空音频播放队列,避免播放残留音频"); // BOOT按键切换时的优化方案:确保音频系统能正常响应状态切换 auto& board = Board::GetInstance(); auto codec = board.GetAudioCodec(); if (codec) { codec->EnableOutput(true); ESP_LOGI(TAG, "🔧 为状态切换重新启用音频编解码器输出");// 重新启用输出,后续可以播放 } // 🔧 关键修复:强制停止音频处理器,确保后续状态切换时能重新启动 #if CONFIG_USE_AUDIO_PROCESSOR if (audio_processor_.IsRunning()) { ESP_LOGI(TAG, "🔧 强制停止音频处理器以确保状态切换成功"); audio_processor_.Stop(); } #endif // 🔧 音频暂停状态下直接切换,避免复杂的异步处理 ESP_LOGI(TAG, "🔵 音频暂停状态下直接执行状态切换"); // 播放提示音 if (codec && codec->output_enabled()) { ESP_LOGI(TAG, "播放提示音:卡卡在呢"); // PlaySound(Lang::Sounds::P3_KAKAZAINNE); 原有蜡笔小新 音色播报 if(strcmp(CONFIG_DEVICE_ROLE, "KAKA") == 0){ PlaySound(Lang::Sounds::P3_KAKA_ZAINNE); } else if(strcmp(CONFIG_DEVICE_ROLE, "LALA") == 0){ PlaySound(Lang::Sounds::P3_LALA_ZAINNE); } // 简化等待逻辑 vTaskDelay(pdMS_TO_TICKS(620)); // 等待音效播放完成 ESP_LOGI(TAG, "音频播放完成"); } // 直接切换到聆听状态 SetDeviceState(kDeviceStateListening); ESP_LOGI(TAG, "🔵 音频暂停状态下状态切换完成"); return; } // ========================================================================================= // 检查操作安全性 if (!IsSafeToOperate()) { ESP_LOGW(TAG, "🔵 AbortSpeakingAndReturnToListening: Operation not safe, scheduling retry"); Schedule([this]() { vTaskDelay(pdMS_TO_TICKS(100)); AbortSpeakingAndReturnToListening(); }); return; } // 更新安全操作时间戳 last_safe_operation_.store(std::chrono::steady_clock::now()); // 立即停止音频处理器和清空音频队列 #if CONFIG_USE_AUDIO_PROCESSOR if (audio_processor_.IsRunning()) { ESP_LOGI(TAG, "🔵 停止音频处理器"); audio_processor_.Stop(); } // 清空音频队列并重置音量 if (!IsAudioQueueEmpty()) { ESP_LOGI(TAG, "🔵 清空音频队列并重置音量"); while (!IsAudioQueueEmpty()) { vTaskDelay(pdMS_TO_TICKS(10)); } current_speaker_volume_ = 0.0f; audio_processor_.SetSpeakerVolume(0.0f); ESP_LOGI(TAG, "✅ 音频队列已清空,音量已重置为0"); } else { ESP_LOGI(TAG, "ℹ️ 音频队列已为空,无需清空"); } #endif ESP_LOGI(TAG, "🔵 AbortSpeakingAndReturnToListening: Sending abort message to server"); // 发送中止消息给服务器 if (protocol_ && protocol_->IsAudioChannelOpened()) { ESP_LOGI(TAG, "📡 WebSocket连接正常,发送中止消息"); try { protocol_->SendAbortSpeaking(kAbortReasonVoiceInterrupt); ESP_LOGI(TAG, "✅ 中止消息发送成功"); } catch (const std::exception& e) { ESP_LOGW(TAG, "❌ 发送中止消息失败: %s", e.what()); } // 延迟100ms后播放音效并直接切换到聆听状态,不关闭WebSocket连接 Schedule([this]() { vTaskDelay(pdMS_TO_TICKS(100)); ESP_LOGI(TAG, "⏳ 延迟100ms后播放音效并切换到聆听状态"); // 先播放"卡卡在呢"音效 ESP_LOGI(TAG, "🔵 AbortSpeakingAndReturnToListening: Playing KAKAZAINNE sound"); // 🔧 修复:强制重新初始化音频输出,确保硬件状态正确 auto& board = Board::GetInstance(); auto audio_codec = board.GetAudioCodec(); ESP_LOGI(TAG, "强制重新初始化音频输出"); audio_codec->EnableOutput(false); // 先关闭音频输出 vTaskDelay(pdMS_TO_TICKS(50)); // 短暂延迟让硬件复位 audio_codec->EnableOutput(true); // 再开启,强制硬件重新初始化 // 🔧 检查音频资源是否可用 if (audio_codec->output_enabled()) { ESP_LOGI(TAG, "播放提示音:卡卡在呢"); ResetDecoder(); // 🔧 关键修复:重置解码器状态,清除残留 // 获取当前系统音量并临时设置以确保音效能播放 float system_volume = audio_codec ? (audio_codec->output_volume() / 100.0f) : 0.7f; // 默认70% current_speaker_volume_ = system_volume; #if CONFIG_USE_AUDIO_PROCESSOR audio_processor_.SetSpeakerVolume(system_volume); ESP_LOGI(TAG, "✅ 音量设置成功: %.2f", system_volume); #endif // PlaySound(Lang::Sounds::P3_KAKAZAINNE); 原有蜡笔小新 音色播报 if(strcmp(CONFIG_DEVICE_ROLE, "KAKA") == 0){ PlaySound(Lang::Sounds::P3_KAKA_ZAINNE); } else if(strcmp(CONFIG_DEVICE_ROLE, "LALA") == 0){ PlaySound(Lang::Sounds::P3_LALA_ZAINNE); } // 🔧 修复:使用改进的等待逻辑,确保音频真正播放完成 ESP_LOGI(TAG, "等待音频播放完成..."); vTaskDelay(pdMS_TO_TICKS(100)); // 给音频足够的时间开始播放 // 等待音频队列清空 + 额外缓冲时间确保I2S硬件完成输出 int timeout_count = 0; const int max_timeout = 150; // 3秒超时 while (timeout_count < max_timeout) { if (IsAudioQueueEmpty()) { // 队列清空后,再等待500ms确保I2S硬件完成输出 ESP_LOGI(TAG, "音频队列已清空,等待硬件输出完成..."); vTaskDelay(pdMS_TO_TICKS(500)); ESP_LOGI(TAG, "音频播放完成"); break; } vTaskDelay(pdMS_TO_TICKS(20)); timeout_count++; } if (timeout_count >= max_timeout) { ESP_LOGW(TAG, "等待音频播放超时,继续状态切换"); } } else { ESP_LOGW(TAG, "音频输出无法启用,跳过提示音播放"); } // 直接切换到聆听状态,音频播放已在上面完成 ESP_LOGI(TAG, "🔵 AbortSpeakingAndReturnToListening: Switching to listening state (保持WebSocket连接)"); SetDeviceState(kDeviceStateListening); }); } else { ESP_LOGW(TAG, "⚠️ WebSocket连接不可用,直接切换状态"); // 直接播放音效并切换状态 Schedule([this]() { vTaskDelay(pdMS_TO_TICKS(100)); ESP_LOGI(TAG, "🔵 AbortSpeakingAndReturnToListening: Playing KAKAZAINNE sound"); // 🔧 修复:强制重新初始化音频输出,确保硬件状态正确 auto& board = Board::GetInstance(); auto audio_codec = board.GetAudioCodec(); ESP_LOGI(TAG, "强制重新初始化音频输出"); audio_codec->EnableOutput(false); // 先关闭音频输出 vTaskDelay(pdMS_TO_TICKS(50)); // 短暂延迟让硬件复位 audio_codec->EnableOutput(true); // 再开启,强制硬件重新初始化 // 🔧 检查音频资源是否可用 if (audio_codec->output_enabled()) { ESP_LOGI(TAG, "播放提示音:卡卡在呢"); ResetDecoder(); // 🔧 关键修复:重置解码器状态,清除残留 // 获取当前系统音量并临时设置以确保音效能播放 float system_volume = audio_codec ? (audio_codec->output_volume() / 100.0f) : 0.7f; // 默认70% current_speaker_volume_ = system_volume; #if CONFIG_USE_AUDIO_PROCESSOR audio_processor_.SetSpeakerVolume(system_volume); ESP_LOGI(TAG, "✅ 音量设置成功: %.2f", system_volume); #endif // PlaySound(Lang::Sounds::P3_KAKAZAINNE); 原有蜡笔小新 音色播报 if(strcmp(CONFIG_DEVICE_ROLE, "KAKA") == 0){ PlaySound(Lang::Sounds::P3_KAKA_ZAINNE); } else if(strcmp(CONFIG_DEVICE_ROLE, "LALA") == 0){ PlaySound(Lang::Sounds::P3_LALA_ZAINNE); } // 🔧 修复:使用改进的等待逻辑,确保音频真正播放完成 ESP_LOGI(TAG, "等待音频播放完成..."); vTaskDelay(pdMS_TO_TICKS(100)); // 给音频足够的时间开始播放 // 等待音频队列清空 + 额外缓冲时间确保I2S硬件完成输出 int timeout_count = 0; const int max_timeout = 150; // 3秒超时 while (timeout_count < max_timeout) { if (IsAudioQueueEmpty()) { // 队列清空后,再等待500ms确保I2S硬件完成输出 ESP_LOGI(TAG, "音频队列已清空,等待硬件输出完成..."); vTaskDelay(pdMS_TO_TICKS(500)); ESP_LOGI(TAG, "音频播放完成"); break; } vTaskDelay(pdMS_TO_TICKS(20)); timeout_count++; } if (timeout_count >= max_timeout) { ESP_LOGW(TAG, "等待音频播放超时,继续状态切换"); } } else { ESP_LOGW(TAG, "音频输出无法启用,跳过提示音播放"); } // 直接切换到聆听状态,音频播放已在上面完成 ESP_LOGI(TAG, "🔵 AbortSpeakingAndReturnToListening: Switching to listening state"); SetDeviceState(kDeviceStateListening); }); } ESP_LOGI(TAG, "🔵 AbortSpeakingAndReturnToListening: Transition initiated - keeping WebSocket connection and switching to listening"); } // 姿态传感器接口实现 bool Application::IsImuSensorAvailable() { auto& board = Board::GetInstance(); if (board.GetBoardType() == "movecall-moji-esp32s3") { auto& moji_board = static_cast(board); return moji_board.IsImuInitialized(); } return false; } bool Application::GetImuData(float* acc_x, float* acc_y, float* acc_z, float* gyro_x, float* gyro_y, float* gyro_z, float* temperature) { auto& board = Board::GetInstance(); if (board.GetBoardType() == "movecall-moji-esp32s3") { auto& moji_board = static_cast(board); qmi8658a_data_t imu_data; if (moji_board.GetImuData(&imu_data)) { if (acc_x) *acc_x = imu_data.acc_x; if (acc_y) *acc_y = imu_data.acc_y; if (acc_z) *acc_z = imu_data.acc_z; if (gyro_x) *gyro_x = imu_data.gyro_x; if (gyro_y) *gyro_y = imu_data.gyro_y; if (gyro_z) *gyro_z = imu_data.gyro_z; if (temperature) *temperature = imu_data.temperature; return true; } } return false; } void Application::OnMotionDetected() { ESP_LOGI(TAG, "Motion detected by IMU sensor"); // 如果设备处于空闲状态,可以触发一些动作 if (device_state_ == kDeviceStateIdle) { // 例如:显示运动检测提示 auto& board = Board::GetInstance(); auto display = board.GetDisplay(); display->SetChatMessage("system", "检测到运动"); // 可以在这里添加更多的运动检测处理逻辑 // 比如:唤醒设备、记录运动数据等 } } void Application::SetLowBatteryTransition(bool value) { is_low_battery_transition_.store(value);// 设置低电量过渡状态 } bool Application::IsLowBatteryTransition() const { return is_low_battery_transition_.load();// 获取低电量过渡状态 }