From 70481429367ca00144b24f118cbfdfb28b02967d Mon Sep 17 00:00:00 2001
From: wangzhibo <wangzhibo@shsening.com>
Date: 星期三, 26 八月 2026 15:54:31 +0800
Subject: [PATCH] add sening functions
---
xiaozhi-esp32-2.2.6/main/application.cc | 573 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 540 insertions(+), 33 deletions(-)
diff --git a/xiaozhi-esp32-2.2.6/main/application.cc b/xiaozhi-esp32-2.2.6/main/application.cc
index 0a809fa..e6e0d7d 100644
--- a/xiaozhi-esp32-2.2.6/main/application.cc
+++ b/xiaozhi-esp32-2.2.6/main/application.cc
@@ -5,12 +5,15 @@
#include "audio_codec.h"
#include "mqtt_protocol.h"
#include "websocket_protocol.h"
+#include "boards/sening-xiaozhi-s3/mqtt.h"
#include "assets/lang_config.h"
#include "mcp_server.h"
#include "assets.h"
#include "settings.h"
+#include "boards/sening-xiaozhi-s3/system.h"
#include <cstring>
+#include <memory>
#include <esp_log.h>
#include <cJSON.h>
#include <driver/gpio.h>
@@ -19,6 +22,59 @@
#define TAG "Application"
+extern "C" void SDCard_Pause_Play(void) __attribute__((weak));
+extern "C" void SDCard_Resume_Play(void) __attribute__((weak));
+extern "C" void SDCard_Stop_Play(void) __attribute__((weak));
+extern "C" bool SDCard_Is_Playing(void) __attribute__((weak));
+extern "C" bool SDCard_Is_Paused(void) __attribute__((weak));
+
+static bool IsSdCardMp3Playing() {
+ return SDCard_Is_Playing && SDCard_Is_Playing();
+}
+
+static bool IsSdCardMp3Paused() {
+ return SDCard_Is_Paused && SDCard_Is_Paused();
+}
+
+static void PauseSdCardMp3() {
+ if (SDCard_Pause_Play) {
+ SDCard_Pause_Play();
+ }
+}
+
+static void ResumeSdCardMp3() {
+ if (SDCard_Resume_Play) {
+ SDCard_Resume_Play();
+ }
+}
+
+static void StopSdCardMp3() {
+ if (SDCard_Stop_Play) {
+ SDCard_Stop_Play();
+ }
+}
+
+static bool IsInternalToolSpeech(const char* text) {
+ if (!text) {
+ return false;
+ }
+ while (*text == ' ' || *text == '\t') {
+ ++text;
+ }
+ if (strncmp(text, "% self.", 7) == 0 || strncmp(text, "%self.", 6) == 0) {
+ return true;
+ }
+ return (strstr(text, "\"arguments\"") && strstr(text, "\"trigger_time\"")) || strstr(text, "\"label\"");
+}
+
+static bool IsStandbyRequestText(const char* text) {
+ if (!text) {
+ return false;
+ }
+ return strstr(text, "寰呭懡") || strstr(text, "杩涘叆寰呮満") || strstr(text, "杩涘叆寰呮満鐘舵��") ||
+ strstr(text, "杩涘叆浼戠湢") || strstr(text, "浼戠湢鐘舵��") || strstr(text, "鍋滄鐩戝惉") ||
+ strstr(text, "鍒惉浜�") || strstr(text, "涓嶈鍚簡");
+}
Application::Application() {
event_group_ = xEventGroupCreate();
@@ -60,11 +116,15 @@
void Application::Initialize() {
auto& board = Board::GetInstance();
+ ota_ = std::make_unique<Ota>();
+ ota_->MarkCurrentVersionValid();
+
SetDeviceState(kDeviceStateStarting);
// Setup the display
auto display = board.GetDisplay();
display->SetupUI();
+ ESP_LOGI(TAG, "Application firmware version: %s", APP_FIRMWARE_VERSION);
// Print board name/version info
display->SetChatMessage("system", SystemInfo::GetUserAgent().c_str());
@@ -78,7 +138,14 @@
xEventGroupSetBits(event_group_, MAIN_EVENT_SEND_AUDIO);
};
callbacks.on_wake_word_detected = [this](const std::string& wake_word) {
- xEventGroupSetBits(event_group_, MAIN_EVENT_WAKE_WORD_DETECTED);
+ if (!power_off_standby_active_.load()) {
+ xEventGroupSetBits(event_group_, MAIN_EVENT_WAKE_WORD_DETECTED);
+ }
+ };
+ callbacks.on_offline_command_detected = [this](const std::string& command) {
+ if (!power_off_standby_active_.load()) {
+ Board_Handle_Offline_Command(command.c_str());
+ }
};
callbacks.on_vad_change = [this](bool speaking) {
xEventGroupSetBits(event_group_, MAIN_EVENT_VAD_CHANGE);
@@ -130,6 +197,8 @@
case NetworkEvent::Disconnected:
xEventGroupSetBits(event_group_, MAIN_EVENT_NETWORK_DISCONNECTED);
break;
+ case NetworkEvent::ConnectionFailed:
+ break;
case NetworkEvent::WifiConfigModeEnter:
// WiFi config mode enter is handled by WifiBoard internally
break;
@@ -154,6 +223,8 @@
break;
}
});
+
+ LoadLocalAssetsForWakeWord();
// Start network asynchronously
board.StartNetwork();
@@ -284,6 +355,11 @@
}
void Application::HandleNetworkDisconnectedEvent() {
+ auto& board = Board::GetInstance();
+ if (board.IsNetworkReady()) {
+ return;
+ }
+
// Close current conversation when network disconnected
auto state = GetDeviceState();
if (state == kDeviceStateConnecting || state == kDeviceStateListening || state == kDeviceStateSpeaking) {
@@ -292,7 +368,7 @@
}
// Update the status bar immediately to show the network state
- auto display = Board::GetInstance().GetDisplay();
+ auto display = board.GetDisplay();
display->UpdateStatusBar(true);
}
@@ -320,9 +396,19 @@
});
}
+void Application::LoadLocalAssetsForWakeWord() {
+ auto& assets = Assets::GetInstance();
+ if (!assets.partition_valid() || local_assets_applied_) {
+ return;
+ }
+
+ local_assets_applied_ = assets.Apply(false);
+}
+
void Application::ActivationTask() {
- // Create OTA object for activation process
- ota_ = std::make_unique<Ota>();
+ if (!ota_) {
+ ota_ = std::make_unique<Ota>();
+ }
// Check for new assets version
CheckAssetsVersion();
@@ -372,7 +458,7 @@
bool success = assets.Download(download_url, [this, display](int progress, size_t speed) -> void {
char buffer[32];
- snprintf(buffer, sizeof(buffer), "%d%% %uKB/s", progress, speed / 1024);
+ snprintf(buffer, sizeof(buffer), "%d%% %uKB/s", progress, static_cast<unsigned>(speed / 1024));
Schedule([display, message = std::string(buffer)]() {
display->SetChatMessage("system", message.c_str());
});
@@ -387,10 +473,13 @@
SetDeviceState(kDeviceStateActivating);
return;
}
+ local_assets_applied_ = false;
}
// Apply assets
- assets.Apply();
+ if (!local_assets_applied_) {
+ local_assets_applied_ = assets.Apply();
+ }
display->SetChatMessage("system", "");
display->SetEmotion("microchip_ai");
}
@@ -496,9 +585,20 @@
});
protocol_->OnIncomingAudio([this](std::unique_ptr<AudioStreamPacket> packet) {
- if (GetDeviceState() == kDeviceStateSpeaking) {
- audio_service_.PushPacketToDecodeQueue(std::move(packet));
- }
+ auto shared_packet = std::shared_ptr<AudioStreamPacket>(packet.release());
+ Schedule([this, shared_packet]() {
+ auto state = GetDeviceState();
+ if (state != kDeviceStateSpeaking && !tts_audio_expected_) {
+ return;
+ }
+
+ tts_response_started_ = true;
+
+ ScheduleTtsAudio(shared_packet);
+ if (state == kDeviceStateListening) {
+ SetDeviceState(kDeviceStateSpeaking);
+ }
+ });
});
protocol_->OnAudioChannelOpened([this, codec, &board]() {
@@ -513,8 +613,34 @@
board.SetPowerSaveLevel(PowerSaveLevel::LOW_POWER);
Schedule([this]() {
auto display = Board::GetInstance().GetDisplay();
+ opening_audio_channel_ = false;
+ tts_audio_expected_ = false;
+ tts_response_started_ = false;
+ tts_audio_received_ = false;
+ tts_stop_pending_ = false;
+ tts_stop_waiting_ = false;
+ tts_idle_waiting_ = false;
+ tts_speaking_state_ready_ = false;
+ tts_activity_generation_++;
+ audio_service_.ResetDecoder();
display->SetChatMessage("system", "");
SetDeviceState(kDeviceStateIdle);
+ if (power_off_requested_) {
+ power_off_requested_ = false;
+ resume_sdcard_mp3_after_tts_ = false;
+ resume_sdcard_mp3_after_voice_ = false;
+ Board_Enter_Power_Off();
+ return;
+ }
+ if (standby_requested_) {
+ standby_requested_ = false;
+ }
+ if (resume_sdcard_mp3_after_tts_ && !sdcard_mp3_stop_requested_) {
+ resume_sdcard_mp3_after_tts_ = false;
+ ResumeSdCardMp3();
+ } else {
+ resume_sdcard_mp3_after_tts_ = false;
+ }
});
});
@@ -526,23 +652,44 @@
if (strcmp(state->valuestring, "start") == 0) {
Schedule([this]() {
aborted_ = false;
- SetDeviceState(kDeviceStateSpeaking);
+ tts_audio_expected_ = true;
+ tts_response_started_ = false;
+ tts_audio_received_ = false;
+ tts_activity_generation_++;
});
} else if (strcmp(state->valuestring, "stop") == 0) {
Schedule([this]() {
- if (GetDeviceState() == kDeviceStateSpeaking) {
- if (listening_mode_ == kListeningModeManualStop) {
- SetDeviceState(kDeviceStateIdle);
- } else {
- SetDeviceState(kDeviceStateListening);
- }
+ if (!tts_response_started_ && !standby_requested_ && !power_off_requested_) {
+ tts_stop_pending_ = true;
+ return;
}
+ tts_stop_pending_ = true;
+ FinishTtsResponseAfterPlayback();
});
} else if (strcmp(state->valuestring, "sentence_start") == 0) {
auto text = cJSON_GetObjectItem(root, "text");
if (cJSON_IsString(text)) {
ESP_LOGI(TAG, "<< %s", text->valuestring);
- Schedule([display, message = std::string(text->valuestring)]() {
+ if (IsInternalToolSpeech(text->valuestring)) {
+ ESP_LOGW(TAG, "Ignore internal tool speech: %s", text->valuestring);
+ Schedule([this]() {
+ tts_audio_expected_ = false;
+ tts_response_started_ = false;
+ });
+ return;
+ }
+ Schedule([this, display, message = std::string(text->valuestring)]() {
+ tts_audio_expected_ = true;
+ tts_response_started_ = true;
+ tts_activity_generation_++;
+ if (IsSdCardMp3Playing() && !IsSdCardMp3Paused()) {
+ PauseSdCardMp3();
+ resume_sdcard_mp3_after_tts_ = true;
+ }
+ if (GetDeviceState() == kDeviceStateListening) {
+ SetDeviceState(kDeviceStateSpeaking);
+ }
+ ScheduleTtsIdleFallback();
display->SetChatMessage("assistant", message.c_str());
});
}
@@ -550,7 +697,15 @@
} else if (strcmp(type->valuestring, "stt") == 0) {
auto text = cJSON_GetObjectItem(root, "text");
if (cJSON_IsString(text)) {
+ if (IsSdCardMp3Playing() && !IsSdCardMp3Paused()) {
+ PauseSdCardMp3();
+ resume_sdcard_mp3_after_tts_ = true;
+ }
ESP_LOGI(TAG, ">> %s", text->valuestring);
+ BoardMqtt_PublishSttEvent(text->valuestring);
+ if (IsStandbyRequestText(text->valuestring)) {
+ EnterStandby();
+ }
Schedule([display, message = std::string(text->valuestring)]() {
display->SetChatMessage("user", message.c_str());
});
@@ -565,6 +720,23 @@
} else if (strcmp(type->valuestring, "mcp") == 0) {
auto payload = cJSON_GetObjectItem(root, "payload");
if (cJSON_IsObject(payload)) {
+ auto method = cJSON_GetObjectItem(payload, "method");
+ auto params = cJSON_GetObjectItem(payload, "params");
+ auto name = cJSON_IsObject(params) ? cJSON_GetObjectItem(params, "name") : nullptr;
+ if (cJSON_IsString(method) && strcmp(method->valuestring, "tools/call") == 0 && cJSON_IsString(name)) {
+ if (strcmp(name->valuestring, "self.mp3.stop") == 0) {
+ sdcard_mp3_stop_requested_ = true;
+ BoardMqtt_StopPlaylistPlayback();
+ resume_sdcard_mp3_after_tts_ = false;
+ resume_sdcard_mp3_after_voice_ = false;
+ } else if (strcmp(name->valuestring, "self.mp3.play") == 0) {
+ sdcard_mp3_stop_requested_ = false;
+ if (GetDeviceState() == kDeviceStateSpeaking) {
+ PauseSdCardMp3();
+ resume_sdcard_mp3_after_tts_ = true;
+ }
+ }
+ }
McpServer::GetInstance().ParseMessage(payload);
}
} else if (strcmp(type->valuestring, "system") == 0) {
@@ -672,6 +844,11 @@
}
void Application::HandleToggleChatEvent() {
+ if (power_off_standby_active_.load()) {
+ ESP_LOGI(TAG, "Ignore toggle chat while power-off standby is active");
+ return;
+ }
+
auto state = GetDeviceState();
if (state == kDeviceStateActivating) {
@@ -711,21 +888,59 @@
}
void Application::ContinueOpenAudioChannel(ListeningMode mode) {
+ if (power_off_standby_active_.load()) {
+ ESP_LOGI(TAG, "Ignore open audio channel while power-off standby is active");
+ return;
+ }
+
// Check state again in case it was changed during scheduling
if (GetDeviceState() != kDeviceStateConnecting) {
return;
}
+ if (opening_audio_channel_) {
+ return;
+ }
+ opening_audio_channel_ = true;
if (!protocol_->IsAudioChannelOpened()) {
+ if (!Board::GetInstance().EnsureNetworkReadyForWake(30000)) {
+ opening_audio_channel_ = false;
+ SetDeviceState(kDeviceStateIdle);
+ return;
+ }
if (!protocol_->OpenAudioChannel()) {
+ opening_audio_channel_ = false;
+ SetDeviceState(kDeviceStateIdle);
return;
}
}
+ opening_audio_channel_ = false;
SetListeningMode(mode);
}
+void Application::PauseSdCardMp3ForVoice() {
+ if (IsSdCardMp3Playing() && !IsSdCardMp3Paused()) {
+ PauseSdCardMp3();
+ resume_sdcard_mp3_after_voice_ = true;
+ }
+}
+
+void Application::ResumeSdCardMp3AfterVoice() {
+ if (resume_sdcard_mp3_after_voice_ && !sdcard_mp3_stop_requested_) {
+ resume_sdcard_mp3_after_voice_ = false;
+ ResumeSdCardMp3();
+ } else {
+ resume_sdcard_mp3_after_voice_ = false;
+ }
+}
+
void Application::HandleStartListeningEvent() {
+ if (power_off_standby_active_.load()) {
+ ESP_LOGI(TAG, "Ignore start listening while power-off standby is active");
+ return;
+ }
+
auto state = GetDeviceState();
if (state == kDeviceStateActivating) {
@@ -744,6 +959,7 @@
if (state == kDeviceStateIdle) {
if (!protocol_->IsAudioChannelOpened()) {
+ PauseSdCardMp3ForVoice();
SetDeviceState(kDeviceStateConnecting);
// Schedule to let the state change be processed first (UI update)
Schedule([this]() {
@@ -751,6 +967,7 @@
});
return;
}
+ PauseSdCardMp3ForVoice();
SetListeningMode(kListeningModeManualStop);
} else if (state == kDeviceStateSpeaking) {
AbortSpeaking(kAbortReasonNone);
@@ -774,6 +991,11 @@
}
void Application::HandleWakeWordDetectedEvent() {
+ if (power_off_standby_active_.load()) {
+ ESP_LOGI(TAG, "Ignore wake word while power-off standby is active");
+ return;
+ }
+
if (!protocol_) {
return;
}
@@ -783,6 +1005,7 @@
ESP_LOGI(TAG, "Wake word detected: %s (state: %d)", wake_word.c_str(), (int)state);
if (state == kDeviceStateIdle) {
+ PauseSdCardMp3ForVoice();
audio_service_.EncodeWakeWord();
auto wake_word = audio_service_.GetLastWakeWord();
@@ -795,8 +1018,9 @@
});
return;
}
- // Channel already opened, continue directly
- ContinueWakeWordInvoke(wake_word);
+ Schedule([this, wake_word]() {
+ ContinueWakeWordInvoke(wake_word);
+ });
} else if (state == kDeviceStateSpeaking || state == kDeviceStateListening) {
AbortSpeaking(kAbortReasonWakeWordDetected);
// Clear send queue to avoid sending residues to server
@@ -820,23 +1044,44 @@
}
void Application::ContinueWakeWordInvoke(const std::string& wake_word) {
- // Check state again in case it was changed during scheduling
- if (GetDeviceState() != kDeviceStateConnecting) {
+ if (power_off_standby_active_.load()) {
+ ESP_LOGI(TAG, "Ignore wake word invoke while power-off standby is active");
return;
}
+ // Check state again in case it was changed during scheduling
+ auto state = GetDeviceState();
+ if (state != kDeviceStateConnecting && state != kDeviceStateIdle) {
+ return;
+ }
+ if (opening_audio_channel_) {
+ return;
+ }
+
+ opening_audio_channel_ = true;
+ if (!Board::GetInstance().EnsureNetworkReadyForWake(30000)) {
+ opening_audio_channel_ = false;
+ SetDeviceState(kDeviceStateIdle);
+ audio_service_.EnableWakeWordDetection(true);
+ return;
+ }
if (!protocol_->IsAudioChannelOpened()) {
if (!protocol_->OpenAudioChannel()) {
+ opening_audio_channel_ = false;
+ ResumeSdCardMp3AfterVoice();
audio_service_.EnableWakeWordDetection(true);
return;
}
}
+ opening_audio_channel_ = false;
ESP_LOGI(TAG, "Wake word detected: %s", wake_word.c_str());
#if CONFIG_SEND_WAKE_WORD_DATA
// Encode and send the wake word data to the server
while (auto packet = audio_service_.PopWakeWordPacket()) {
- protocol_->SendAudio(std::move(packet));
+ if (!protocol_->SendAudio(std::move(packet))) {
+ break;
+ }
}
// Set the chat state to wake word detected
protocol_->SendWakeWordDetected(wake_word);
@@ -857,10 +1102,29 @@
auto display = board.GetDisplay();
auto led = board.GetLed();
led->OnStateChanged();
-
+
+ if (power_off_standby_active_.load()) {
+ opening_audio_channel_ = false;
+ play_popup_on_listening_ = false;
+ audio_service_.EnableVoiceProcessing(false);
+ audio_service_.EnableWakeWordDetection(false);
+ audio_service_.ResetDecoder();
+ return;
+ }
+
switch (new_state) {
case kDeviceStateUnknown:
case kDeviceStateIdle:
+ opening_audio_channel_ = false;
+ tts_audio_expected_ = false;
+ tts_response_started_ = false;
+ tts_audio_received_ = false;
+ tts_stop_pending_ = false;
+ tts_stop_waiting_ = false;
+ tts_idle_waiting_ = false;
+ tts_speaking_state_ready_ = false;
+ tts_activity_generation_++;
+ ResumeSdCardMp3AfterVoice();
display->SetStatus(Lang::Strings::STANDBY);
display->ClearChatMessages(); // Clear messages first
display->SetEmotion("neutral"); // Then set emotion (wechat mode checks child count)
@@ -873,6 +1137,7 @@
display->SetChatMessage("system", "");
break;
case kDeviceStateListening:
+ tts_speaking_state_ready_ = false;
display->SetStatus(Lang::Strings::LISTENING);
display->SetEmotion("neutral");
@@ -904,6 +1169,7 @@
}
break;
case kDeviceStateSpeaking:
+ tts_speaking_state_ready_ = false;
display->SetStatus(Lang::Strings::SPEAKING);
if (listening_mode_ != kListeningModeRealtime) {
@@ -912,10 +1178,11 @@
audio_service_.EnableWakeWordDetection(audio_service_.IsAfeWakeWord());
}
audio_service_.ResetDecoder();
+ tts_speaking_state_ready_ = true;
break;
case kDeviceStateWifiConfiguring:
audio_service_.EnableVoiceProcessing(false);
- audio_service_.EnableWakeWordDetection(false);
+ audio_service_.EnableWakeWordDetection(true);
break;
default:
// Do nothing
@@ -961,7 +1228,9 @@
esp_restart();
}
-bool Application::UpgradeFirmware(const std::string& url, const std::string& version) {
+bool Application::UpgradeFirmware(const std::string& url, const std::string& version,
+ std::function<void(int progress, size_t speed)> progress_callback,
+ std::function<void(bool success)> complete_callback) {
auto& board = Board::GetInstance();
auto display = board.GetDisplay();
@@ -978,6 +1247,7 @@
Alert(Lang::Strings::OTA_UPGRADE, Lang::Strings::UPGRADING, "download", Lang::Sounds::OGG_UPGRADE);
vTaskDelay(pdMS_TO_TICKS(3000));
+ AcquireSleepInhibit();
SetDeviceState(kDeviceStateUpgrading);
std::string message = std::string(Lang::Strings::NEW_VERSION) + version_info;
@@ -987,12 +1257,15 @@
audio_service_.Stop();
vTaskDelay(pdMS_TO_TICKS(1000));
- bool upgrade_success = Ota::Upgrade(upgrade_url, [this, display](int progress, size_t speed) {
+ bool upgrade_success = Ota::Upgrade(upgrade_url, [this, display, progress_callback](int progress, size_t speed) {
char buffer[32];
- snprintf(buffer, sizeof(buffer), "%d%% %uKB/s", progress, speed / 1024);
+ snprintf(buffer, sizeof(buffer), "%d%% %uKB/s", progress, static_cast<unsigned>(speed / 1024));
Schedule([display, message = std::string(buffer)]() {
display->SetChatMessage("system", message.c_str());
});
+ if (progress_callback) {
+ progress_callback(progress, speed);
+ }
});
if (!upgrade_success) {
@@ -1000,6 +1273,10 @@
ESP_LOGE(TAG, "Firmware upgrade failed, restarting audio service and continuing operation...");
audio_service_.Start(); // Restart audio service
board.SetPowerSaveLevel(PowerSaveLevel::LOW_POWER); // Restore power save level
+ ReleaseSleepInhibit();
+ if (complete_callback) {
+ complete_callback(false);
+ }
Alert(Lang::Strings::ERROR, Lang::Strings::UPGRADE_FAILED, "circle_xmark", Lang::Sounds::OGG_EXCLAMATION);
vTaskDelay(pdMS_TO_TICKS(3000));
return false;
@@ -1008,12 +1285,21 @@
ESP_LOGI(TAG, "Firmware upgrade successful, rebooting...");
display->SetChatMessage("system", "Upgrade successful, rebooting...");
vTaskDelay(pdMS_TO_TICKS(1000)); // Brief pause to show message
+ ReleaseSleepInhibit();
+ if (complete_callback) {
+ complete_callback(true);
+ }
Reboot();
return true;
}
}
void Application::WakeWordInvoke(const std::string& wake_word) {
+ if (power_off_standby_active_.load()) {
+ ESP_LOGI(TAG, "Ignore wake word invoke while power-off standby is active");
+ return;
+ }
+
if (!protocol_) {
return;
}
@@ -1031,8 +1317,9 @@
});
return;
}
- // Channel already opened, continue directly
- ContinueWakeWordInvoke(wake_word);
+ Schedule([this, wake_word]() {
+ ContinueWakeWordInvoke(wake_word);
+ });
} else if (state == kDeviceStateSpeaking) {
Schedule([this]() {
AbortSpeaking(kAbortReasonNone);
@@ -1046,12 +1333,226 @@
}
}
+void Application::EnterStandby() {
+ Schedule([this]() {
+ standby_requested_ = true;
+ auto state = GetDeviceState();
+ if (state == kDeviceStateIdle) {
+ standby_requested_ = false;
+ } else if (state == kDeviceStateSpeaking && tts_stop_pending_) {
+ FinishTtsResponseAfterPlayback();
+ }
+ });
+}
+
+void Application::RequestPowerOffStandby() {
+ Schedule([this]() {
+ power_off_requested_ = true;
+ auto state = GetDeviceState();
+ if (state == kDeviceStateIdle) {
+ power_off_requested_ = false;
+ Board_Enter_Power_Off();
+ } else if (state == kDeviceStateSpeaking && tts_stop_pending_) {
+ FinishTtsResponseAfterPlayback();
+ }
+ });
+}
+
+void Application::EnterPowerOffStandby() {
+ power_off_standby_active_.store(true);
+ Schedule([this]() {
+ ESP_LOGI(TAG, "Entering power-off standby");
+
+ standby_requested_ = false;
+ power_off_requested_ = false;
+ opening_audio_channel_ = false;
+ play_popup_on_listening_ = false;
+ resume_sdcard_mp3_after_tts_ = false;
+ resume_sdcard_mp3_after_voice_ = false;
+ sdcard_mp3_stop_requested_ = false;
+ tts_audio_expected_ = false;
+ tts_response_started_ = false;
+ tts_audio_received_ = false;
+ tts_stop_pending_ = false;
+ tts_stop_waiting_ = false;
+ tts_idle_waiting_ = false;
+ tts_speaking_state_ready_ = false;
+ tts_activity_generation_++;
+
+ if (GetDeviceState() == kDeviceStateSpeaking) {
+ AbortSpeaking(kAbortReasonNone);
+ }
+ if (protocol_ && protocol_->IsAudioChannelOpened()) {
+ protocol_->CloseAudioChannel();
+ }
+
+ audio_service_.EnableVoiceProcessing(false);
+ audio_service_.EnableWakeWordDetection(false);
+ audio_service_.ResetDecoder();
+ while (audio_service_.PopPacketFromSendQueue()) {
+ }
+ BoardMqtt_StopPlaylistPlayback();
+
+ auto state = GetDeviceState();
+ if (state == kDeviceStateConnecting || state == kDeviceStateListening || state == kDeviceStateSpeaking) {
+ SetDeviceState(kDeviceStateIdle);
+ }
+ });
+}
+
+void Application::RestoreFromPowerOffStandby(bool network_ready) {
+ Schedule([this, network_ready]() {
+ ESP_LOGI(TAG, "Restoring from power-off standby, network_ready=%d", network_ready);
+
+ power_off_standby_active_.store(false);
+ standby_requested_ = false;
+ power_off_requested_ = false;
+ opening_audio_channel_ = false;
+ play_popup_on_listening_ = false;
+ tts_activity_generation_++;
+
+ audio_service_.EnableVoiceProcessing(false);
+ audio_service_.ResetDecoder();
+ if (GetDeviceState() != kDeviceStateIdle) {
+ SetDeviceState(kDeviceStateIdle);
+ }
+ audio_service_.EnableWakeWordDetection(true);
+ BoardMqtt_ResumePlaylistIfUnfinished();
+ });
+}
+
+void Application::AcquireSleepInhibit() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ sleep_inhibit_count_++;
+}
+
+void Application::ReleaseSleepInhibit() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ if (sleep_inhibit_count_ > 0) {
+ sleep_inhibit_count_--;
+ }
+}
+
+void Application::FinishTtsResponse() {
+ tts_audio_expected_ = false;
+ tts_response_started_ = false;
+ tts_audio_received_ = false;
+ tts_stop_pending_ = false;
+ tts_stop_waiting_ = false;
+ tts_idle_waiting_ = false;
+ tts_speaking_state_ready_ = false;
+ if (GetDeviceState() == kDeviceStateSpeaking) {
+ if (listening_mode_ == kListeningModeManualStop) {
+ SetDeviceState(kDeviceStateIdle);
+ } else {
+ SetDeviceState(kDeviceStateListening);
+ }
+ }
+ ResumeSdCardMp3AfterVoice();
+ if (resume_sdcard_mp3_after_tts_ && !sdcard_mp3_stop_requested_) {
+ resume_sdcard_mp3_after_tts_ = false;
+ ResumeSdCardMp3();
+ } else {
+ resume_sdcard_mp3_after_tts_ = false;
+ }
+ if (power_off_requested_) {
+ power_off_requested_ = false;
+ Board_Enter_Power_Off();
+ return;
+ }
+ if (standby_requested_) {
+ standby_requested_ = false;
+ SetDeviceState(kDeviceStateIdle);
+ }
+}
+
+void Application::ScheduleTtsAudio(std::shared_ptr<AudioStreamPacket> packet) {
+ Schedule([this, packet]() {
+ if (GetDeviceState() != kDeviceStateSpeaking || !tts_audio_expected_) {
+ return;
+ }
+ if (!tts_speaking_state_ready_) {
+ ScheduleTtsAudio(packet);
+ return;
+ }
+ tts_audio_received_ = true;
+ tts_activity_generation_++;
+ auto audio_packet = std::make_unique<AudioStreamPacket>(std::move(*packet));
+ audio_service_.PushPacketToDecodeQueue(std::move(audio_packet));
+ ScheduleTtsIdleFallback();
+ if (tts_stop_pending_) {
+ FinishTtsResponseAfterPlayback();
+ }
+ });
+}
+
+void Application::FinishTtsResponseAfterPlayback() {
+ if (tts_stop_waiting_) {
+ return;
+ }
+ if (tts_audio_expected_ && !tts_audio_received_) {
+ ScheduleTtsIdleFallback();
+ return;
+ }
+ tts_stop_waiting_ = true;
+ xTaskCreate([](void* arg) {
+ auto* app = static_cast<Application*>(arg);
+ uint32_t generation = app->tts_activity_generation_;
+ app->audio_service_.WaitForPlaybackQueueEmpty();
+ vTaskDelay(pdMS_TO_TICKS(800));
+ app->Schedule([app, generation]() {
+ if (app->tts_activity_generation_ == generation) {
+ app->FinishTtsResponse();
+ } else {
+ app->tts_stop_waiting_ = false;
+ if (app->GetDeviceState() == kDeviceStateSpeaking) {
+ if (app->tts_stop_pending_) {
+ app->FinishTtsResponseAfterPlayback();
+ } else {
+ app->ScheduleTtsIdleFallback();
+ }
+ }
+ }
+ });
+ vTaskDelete(NULL);
+ }, "tts_finish", 4096, this, 2, NULL);
+}
+
+void Application::ScheduleTtsIdleFallback() {
+ if (tts_idle_waiting_ || tts_stop_waiting_) {
+ return;
+ }
+ tts_idle_waiting_ = true;
+ xTaskCreate([](void* arg) {
+ auto* app = static_cast<Application*>(arg);
+ uint32_t generation = app->tts_activity_generation_;
+ vTaskDelay(pdMS_TO_TICKS(1500));
+ app->Schedule([app, generation]() {
+ app->tts_idle_waiting_ = false;
+ if (app->tts_activity_generation_ != generation || app->GetDeviceState() != kDeviceStateSpeaking) {
+ return;
+ }
+ if (app->tts_audio_received_) {
+ app->FinishTtsResponseAfterPlayback();
+ return;
+ }
+ if (app->tts_stop_pending_ || app->tts_audio_expected_) {
+ app->FinishTtsResponse();
+ }
+ });
+ vTaskDelete(NULL);
+ }, "tts_idle", 4096, this, 2, NULL);
+}
+
bool Application::CanEnterSleepMode() {
- if (GetDeviceState() != kDeviceStateIdle) {
- return false;
+ {
+ std::lock_guard<std::mutex> lock(mutex_);
+ if (sleep_inhibit_count_ > 0) {
+ return false;
+ }
}
- if (protocol_ && protocol_->IsAudioChannelOpened()) {
+ if (GetDeviceState() != kDeviceStateIdle) {
return false;
}
@@ -1063,10 +1564,16 @@
return true;
}
+void Application::PrepareProtocolForNetworkStandby() {
+ if (protocol_) {
+ protocol_->PrepareForNetworkStandby();
+ }
+}
+
void Application::SendMcpMessage(const std::string& payload) {
// Always schedule to run in main task for thread safety
Schedule([this, payload = std::move(payload)]() {
- if (protocol_) {
+ if (protocol_ && !power_off_standby_active_.load()) {
protocol_->SendMcpMessage(payload);
}
});
--
Gitblit v1.9.1