toy-hardware/main/application.cc

2008 lines
83 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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 <cstring>
#include <esp_log.h>
#include <cJSON.h>
#include <driver/gpio.h>
#include <arpa/inet.h>
#include <esp_app_desc.h>
#include <cmath> // 用于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<std::mutex> 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_sound, 10> 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<std::mutex> 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<std::mutex> lock(mutex_);
if (audio_paused_) {
audio_paused_ = false;// 恢复播放(更新标志位)
ESP_LOGI(TAG, "<EFBFBD> 从服务器接收到继续播放指令");
// 恢复原始处理方式:重新启用音频输出
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<uint8_t> opus;
opus.resize(payload_size);
memcpy(opus.data(), p3->payload, payload_size);
p += payload_size;
std::lock_guard<std::mutex> 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<OpusDecoderWrapper>(codec->output_sample_rate(), 1, OPUS_FRAME_DURATION_MS);
opus_encoder_ = std::make_unique<OpusEncoderWrapper>(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<WebsocketProtocol>();
#else
protocol_ = std::make_unique<MqttProtocol>();
#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<uint8_t>&& data) {
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<int16_t>&& data) {
background_task_->Schedule([this, data = std::move(data)]() mutable {
opus_encoder_->Encode(std::move(data), [this](std::vector<uint8_t>&& 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<std::chrono::milliseconds>(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<std::chrono::milliseconds>(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<uint8_t> 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<void()> callback) {
{
std::lock_guard<std::mutex> 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<std::mutex> lock(mutex_);
std::list<std::function<void()>> 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<std::mutex> 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<std::chrono::seconds>(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<int16_t> 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<int16_t> 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<int16_t> 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<uint8_t>&& opus) {
Schedule([this, opus = std::move(opus)]() {
protocol_->SendAudio(opus);
});
});
});
return;
}
#endif
vTaskDelay(pdMS_TO_TICKS(30));
}
void Application::ReadAudio(std::vector<int16_t>& 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<int16_t>(data.size() / 2);
auto reference_channel = std::vector<int16_t>(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<int16_t>(input_resampler_.GetOutputSamples(mic_channel.size()));
auto resampled_reference = std::vector<int16_t>(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<int16_t>(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<std::mutex> 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<WifiBoard&>(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<std::mutex> 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<OpusDecoderWrapper>(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<std::mutex> 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<std::mutex> lock(mutex_);
return audio_decode_queue_.empty();
}
void Application::ClearAudioQueue() {
std::lock_guard<std::mutex> 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<std::chrono::milliseconds>(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<std::mutex> 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<std::mutex> 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<MovecallMojiESP32S3&>(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<MovecallMojiESP32S3&>(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();// 获取低电量过渡状态
}