wangzhibo
2026-08-26 70481429367ca00144b24f118cbfdfb28b02967d
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);
        }
    });