#include "application.h"
#include "esp_aec.h"
#include "esp_heap_caps.h"
// #include "ble_service_config.h"  // BLE JSON Service 暂不使用
#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 "volc_rtc_protocol.h"
#include "font_awesome_symbols.h"
#include "iot/thing_manager.h"
#include "assets/lang_config.h"
#include "volume_config.h"
#include "boards/common/qmi8658a.h"  // 添加qmi8658a_data_t类型的头文件
#include "boards/movecall-moji-esp32s3/movecall_moji_esp32s3.h"  // 添加MovecallMojiESP32S3类的头文件
#include "weather_api.h"

#include <cstring>
#include <esp_log.h>
#include <cJSON.h>
#include <driver/gpio.h>
#include <arpa/inet.h>
#include <esp_app_desc.h>
#include <cmath>
#include <chrono>
#include <esp_wifi.h>
#include <nvs.h>
#include <esp_http_client.h>
#include <esp_crt_bundle.h>
#include <esp_pm.h>

#define TAG "Application"
#define MAC_TAG "BluetoothMAC"

// 设备空闲无对话状态 倒计时
#define DIALOG_IDLE_COUNTDOWN_SECONDS 40


// 定义设备状态字符串
static const char* const STATE_STRINGS[] = {
    "unknown",
    "starting",
    "configuring",
    "idle",
    "connecting",
    "listening",
    "speaking",
    "dialog",
    "upgrading",
    "activating",
    "fatal_error"
};

Application::Application() {
    event_group_ = xEventGroupCreate();
    background_task_ = new BackgroundTask(4096 * 8);
    last_audible_output_time_ = std::chrono::steady_clock::now(); // 初始化最后一次有声音输出的时间点
    skip_dialog_idle_session_ = false; // 初始化跳过对话待机会话标志为false
    dialog_watchdog_running_ = false; // 初始化对话看门狗运行标志
    dialog_watchdog_last_logged_ = -1; // 初始化对话看门狗日志记录
    dialog_watchdog_task_handle_ = nullptr; // 初始化对话看门狗任务句柄
    clock_ticks_ = 0; // 初始化时钟计数
    main_loop_task_handle_ = nullptr; // 初始化主循环任务句柄
    check_new_version_task_handle_ = nullptr; // 初始化版本检查任务句柄
    audio_loop_task_handle_ = nullptr; // 初始化音频循环任务句柄

    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() {
    // 停止并清理对话看门狗
    StopDialogWatchdog();

    if (clock_timer_handle_ != nullptr) {
        esp_timer_stop(clock_timer_handle_);
        esp_timer_delete(clock_timer_handle_);
    }
    if (background_task_ != nullptr) {
        delete background_task_;
    }
    if (recorder_pipeline_) {
        recorder_pipeline_close(recorder_pipeline_);
        recorder_pipeline_ = nullptr;
    }
    if (player_pipeline_) {
        player_pipeline_close(player_pipeline_);
        player_pipeline_ = nullptr;
    }
    DeinitAec();
    vEventGroupDelete(event_group_);
}

void Application::InitAec() {
    if (aec_handle_ != nullptr) {
        return;
    }

    aec_handle_t* handle = aec_create(16000, 4, 1, AEC_MODE_VOIP_LOW_COST);
    if (handle == nullptr) {
        ESP_LOGE(TAG, "❌ AEC 初始化失败");
        return;
    }
    aec_handle_ = handle;
    aec_chunk_size_ = aec_get_chunksize(handle);

    int min_capacity = aec_ref_delay_samples_ + aec_chunk_size_ * 2 + 320;
    int desired_capacity = 16000 / 5;
    ref_ring_capacity_ = (min_capacity > desired_capacity) ? min_capacity : desired_capacity;
    ref_ring_buf_ = (int16_t *)heap_caps_calloc(ref_ring_capacity_, sizeof(int16_t),
                                                MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
    if (ref_ring_buf_ == nullptr) {
        ESP_LOGE(TAG, "❌ ref_ring_buf 分配失败 capacity=%d", ref_ring_capacity_);
        aec_destroy(handle);
        aec_handle_ = nullptr;
        aec_chunk_size_ = 0;
        return;
    }

    ref_ring_write_idx_ = 0;
    ref_ring_filled_ = 0;
    if (ref_ring_mutex_ == nullptr) {
        ref_ring_mutex_ = xSemaphoreCreateMutex();
    }
    ESP_LOGI(TAG, "✅ AEC 初始化成功: chunk=%d samples, delay=%d samples, ring=%d samples",
             aec_chunk_size_, aec_ref_delay_samples_, ref_ring_capacity_);
}

void Application::DeinitAec() {
    if (aec_handle_ != nullptr) {
        aec_destroy(static_cast<aec_handle_t*>(aec_handle_));
        aec_handle_ = nullptr;
        aec_chunk_size_ = 0;
    }
    if (ref_ring_buf_ != nullptr) {
        heap_caps_free(ref_ring_buf_);
        ref_ring_buf_ = nullptr;
        ref_ring_capacity_ = 0;
        ref_ring_write_idx_ = 0;
        ref_ring_filled_ = 0;
    }
    if (ref_ring_mutex_ != nullptr) {
        vSemaphoreDelete(ref_ring_mutex_);
        ref_ring_mutex_ = nullptr;
    }
}

void Application::AppendRefSamples(const int16_t *pcm, int samples) {
    if (ref_ring_buf_ == nullptr || pcm == nullptr || samples <= 0 || ref_ring_mutex_ == nullptr) {
        return;
    }
    if (xSemaphoreTake(ref_ring_mutex_, pdMS_TO_TICKS(2)) != pdTRUE) {
        return;
    }
    for (int i = 0; i < samples; i++) {
        ref_ring_buf_[ref_ring_write_idx_] = pcm[i];
        ref_ring_write_idx_ = (ref_ring_write_idx_ + 1) % ref_ring_capacity_;
    }
    if (ref_ring_filled_ < ref_ring_capacity_) {
        ref_ring_filled_ = std::min(ref_ring_filled_ + samples, ref_ring_capacity_);
    }
    xSemaphoreGive(ref_ring_mutex_);
}

void Application::GetDelayedRef(int16_t *ref_out, int samples) {
    if (ref_ring_buf_ == nullptr || ref_out == nullptr || samples <= 0 || ref_ring_mutex_ == nullptr) {
        if (ref_out) {
            memset(ref_out, 0, samples * sizeof(int16_t));
        }
        return;
    }
    if (xSemaphoreTake(ref_ring_mutex_, pdMS_TO_TICKS(2)) != pdTRUE) {
        memset(ref_out, 0, samples * sizeof(int16_t));
        return;
    }
    int total_offset = aec_ref_delay_samples_ + samples;
    if (ref_ring_filled_ < total_offset) {
        memset(ref_out, 0, samples * sizeof(int16_t));
        xSemaphoreGive(ref_ring_mutex_);
        return;
    }
    int read_idx = (ref_ring_write_idx_ - total_offset + ref_ring_capacity_) % ref_ring_capacity_;
    for (int i = 0; i < samples; i++) {
        ref_out[i] = ref_ring_buf_[read_idx];
        read_idx = (read_idx + 1) % ref_ring_capacity_;
    }
    xSemaphoreGive(ref_ring_mutex_);
}

void Application::ApplyAEC(std::vector<int16_t>& mic_inout) {
    if (aec_handle_ == nullptr) {
        InitAec();
        if (aec_handle_ == nullptr || aec_chunk_size_ <= 0) {
            return;
        }
    }

    int n = (int)mic_inout.size();
    int chunk = aec_chunk_size_;
    if (n < chunk) {
        return;
    }

    int processed = 0;
    std::vector<int16_t> ref(chunk);
    std::vector<int16_t> clean(chunk);
    int64_t mic_sq = 0;
    int64_t ref_sq = 0;
    int64_t clean_sq = 0;
    const int REF_SILENCE_RMS_THRESHOLD = 50;

    while (processed + chunk <= n) {
        GetDelayedRef(ref.data(), chunk);
        int64_t ref_chunk_sq = 0;
        for (int i = 0; i < chunk; i++) {
            int16_t r = ref[i];
            ref_chunk_sq += (int64_t)r * r;
            ref_sq += (int64_t)r * r;
        }

        int ref_chunk_rms = (int)sqrt((double)ref_chunk_sq / chunk);
        if (ref_chunk_rms < REF_SILENCE_RMS_THRESHOLD) {
            for (int i = 0; i < chunk; i++) {
                int16_t m = mic_inout[processed + i];
                mic_sq += (int64_t)m * m;
                clean_sq += (int64_t)m * m;
            }
        } else {
            aec_process(static_cast<const aec_handle_t*>(aec_handle_),
                        mic_inout.data() + processed, ref.data(), clean.data());
            for (int i = 0; i < chunk; i++) {
                int16_t m = mic_inout[processed + i];
                int16_t c = clean[i];
                mic_sq += (int64_t)m * m;
                clean_sq += (int64_t)c * c;
                mic_inout[processed + i] = c;
            }
        }
        processed += chunk;
    }

    static uint64_t last_rms_log_us = 0;
    uint64_t now_us = esp_timer_get_time();
    if (now_us - last_rms_log_us > 2000000 && processed > 0) {
        int mic_rms = (int)sqrt((double)mic_sq / processed);
        int ref_rms = (int)sqrt((double)ref_sq / processed);
        int clean_rms = (int)sqrt((double)clean_sq / processed);
        ESP_LOGI(TAG, "🔬 AEC RMS mic=%d ref=%d clean=%d delay=%d",
                 mic_rms, ref_rms, clean_rms, aec_ref_delay_samples_);
        last_rms_log_us = now_us;
    }
}

void Application::CheckNewVersion() {
    // ESP_LOGI(TAG, "OTA版本检查已临时禁用");
    // return;
    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, "� 从服务器接收到继续播放指令");

        // 恢复原始处理方式：重新启用音频输出
        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, "协议未初始化");  // 记录错误日志：协议未初始化
        return;  // 协议未初始化则直接返回
    }
    // 如果当前设备状态是idle空闲，则尝试进入对话模式
    if (device_state_ == kDeviceStateIdle) {
        Schedule([this]() {
            SetDeviceState(kDeviceStateConnecting);
            ESP_LOGI(TAG, "正在尝试打开音频通道");
            Board::GetInstance().SetPowerSaveMode(false);// 关闭低功耗模式
            if (!protocol_->OpenAudioChannel()) {
                auto ac = Board::GetInstance().GetAudioCodec();
                audio_channel_retry_count_++;
                ESP_LOGW(TAG, "打开音频通道失败 (第 %d 次), 将在2秒后重试", audio_channel_retry_count_);
                if (ac) {
                    ESP_LOGW(TAG, "Diag: codec out_channels=%d in_channels=%d out_sr=%d in_sr=%d", ac->output_channels(), ac->input_channels(), ac->output_sample_rate(), ac->input_sample_rate());
                }
                if (audio_channel_retry_count_ >= 3) {
                    ESP_LOGW(TAG, "🔄 连续失败 3 次, 触发 RTC engine 重建");
                    auto* volc_rtc = static_cast<VolcRtcProtocol*>(protocol_.get());
                    if (volc_rtc) {
                        volc_rtc->ForceRebuildEngine();
                    }
                    audio_channel_retry_count_ = 0;
                }
                SetDeviceState(kDeviceStateIdle);
                Schedule([this]() {
                    vTaskDelay(pdMS_TO_TICKS(2000));
                    ESP_LOGI(TAG, "正在重试音频通道连接");
                    ToggleChatState();// 打开音频通道
                });
                return;
            }
            audio_channel_retry_count_ = 0;

            listening_mode_ = kListeningModeRealtime;// 设置监听模式为实时监听
            SetDeviceState(kDeviceStateDialog);// 设置设备状态为对话模式
            protocol_->SendStartListening(listening_mode_);// 发送开始监听消息
            auto codec = Board::GetInstance().GetAudioCodec();// 获取音频编解码器
            if (codec) {
                codec->EnableOutput(true);// 启用音频输出
            }
            ESP_LOGI(TAG, "进入对话框状态：启用全双工");
        });
    } else if (device_state_ == kDeviceStateDialog) {
        Schedule([this]() {
            // protocol_->CloseAudioChannel();// 关闭音频通道
            // ESP_LOGI(TAG, "关闭音频通道并切换到空闲状态");// 关闭音频通道并切换到空闲状态
            protocol_->SendStartListening(listening_mode_);// 发送开始监听消息
        });
    } else if (device_state_ == kDeviceStateSpeaking) {
        Schedule([this]() {
            AbortSpeaking(kAbortReasonNone);
            protocol_->CloseAudioChannel();
            ESP_LOGI(TAG, "关闭音频通道并切换到空闲状态");// 关闭音频通道并切换到空闲状态
        });
    } else if (device_state_ == kDeviceStateListening || (listening_mode_ == kListeningModeRealtime && device_state_ == kDeviceStateSpeaking)) {
        Schedule([this]() {
            protocol_->CloseAudioChannel();
            ESP_LOGI(TAG, "关闭音频通道并切换到空闲状态");// 关闭音频通道并切换到空闲状态
        });
    }
}

void Application::ToggleListeningState() {
    if (device_state_ == kDeviceStateActivating) {
        SetDeviceState(kDeviceStateIdle);
        return;
    }

    if (!protocol_) {
        ESP_LOGE(TAG, "协议未初始化！");// 记录错误日志：协议未初始化
        return;
    }

    // 简单的状态切换：idle <-> listening
    if (device_state_ == kDeviceStateIdle) {
        // 从待命状态进入聆听状态
        Schedule([this]() {
            SetDeviceState(kDeviceStateConnecting);
            if (!protocol_->OpenAudioChannel()) {
                return;
            }
            SetListeningMode(kListeningModeManualStop);
            ESP_LOGI(TAG, "中断按钮：进入聆听状态");// 中断按钮：进入聆听状态
        });
    } else if (device_state_ == kDeviceStateListening) {
        // 从聆听状态返回待命状态
        Schedule([this]() {
            protocol_->CloseAudioChannel();
            ESP_LOGI(TAG, "中断按钮：返回待命状态");// 中断按钮：返回待命状态
        });
    } else if (device_state_ == kDeviceStateSpeaking) {
        // 如果正在说话，中止说话并返回待命状态
        Schedule([this]() {
            AbortSpeaking(kAbortReasonNone);
            if (protocol_) {
                protocol_->CloseAudioChannel();
            }
            SetDeviceState(kDeviceStateIdle);
            ESP_LOGI(TAG, "中断按钮：停止说话，关闭音频通道并返回待命状态");// 中断按钮：停止说话，关闭音频通道并返回待命状态
        });
    } else if (device_state_ == kDeviceStateConnecting) {
        // 如果正在连接，直接返回待命状态
        Schedule([this]() {
            SetDeviceState(kDeviceStateIdle);
            ESP_LOGI(TAG, "中断按钮：取消连接并返回待命状态");// 中断按钮：取消连接并返回待命状态   
        });
    }
}

void Application::StartListening() {
    if (device_state_ == kDeviceStateActivating) {
        SetDeviceState(kDeviceStateIdle);
        return;
    }

    if (!protocol_) {
        ESP_LOGE(TAG, "协议未初始化！");// 记录错误日志：协议未初始化
        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);
        }
    });
}

// 🔊 发送文本消息到RTC（传入大模型上下文信息）
void Application::SendTextMessage(const std::string& text) {
    if (!protocol_) {
        ESP_LOGE(TAG, "协议未初始化！");// 记录错误日志：协议未初始化
        return;
    }

    if (device_state_ == kDeviceStateIdle) {
        Schedule([this, text]() {
            SetDeviceState(kDeviceStateConnecting);// 切换到连接状态
            if (!protocol_->OpenAudioChannel()) {
                return;
            }

            SetDeviceState(kDeviceStateDialog);
            protocol_->SendStartListening(listening_mode_);

            // 发送文本消息
            protocol_->SendTextMessage(text);
            ESP_LOGI(TAG, "发送文本消息：%s", text.c_str());// 发送文本消息：%s

            // 立即启动监听模式以接收语音回复
            ESP_LOGI(TAG, "realtime_chat_enabled_=%s", realtime_chat_enabled_ ? "true" : "false");
            SetListeningMode(realtime_chat_enabled_ ? kListeningModeRealtime : kListeningModeManualStop);
        });
    } else if (device_state_ == kDeviceStateDialog) {
        Schedule([this, text]() {
            // if (!protocol_->IsAudioChannelOpened()) {// 如果音频通道未打开
            //     if (!protocol_->OpenAudioChannel()) {// 尝试打开音频通道
            //         return;
            //     }
            // }
            if (!dialog_upload_enabled_) {
                SetDialogUploadEnabled(true);// 启用对话上传
                protocol_->SendStartListening(listening_mode_);// 发送开始监听消息
            }
            protocol_->SendTextMessage(text);// 发送文本消息
            ESP_LOGI(TAG, "发送文本消息：%s", text.c_str());// 发送文本消息：%s
        });
    } else if (device_state_ == kDeviceStateSpeaking) {
        Schedule([this, text]() {
            AbortSpeaking(kAbortReasonNone);
            protocol_->SendTextMessage(text);
            ESP_LOGI(TAG, "发送文本消息：%s", text.c_str());// 发送文本消息：%s

            // 启动监听模式以接收语音回复
            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, "发送文本消息：%s", text.c_str());// 发送文本消息：%s
        });
    }
}

void Application::Start() {
    auto& board = Board::GetInstance();
    SetDeviceState(kDeviceStateStarting);

    LoadIdleCyclesFromNvs();

    // 读取NVS中的重启标志
    Settings sys("system", true);
    int32_t reboot_dlg_idle = sys.GetInt("reboot_dlg_idle", 0);
    int32_t reboot_origin = sys.GetInt("reboot_origin", 0);

    // 检查是否是因为对话空闲倒计时而重启的
    if (reboot_dlg_idle == 1 && reboot_origin == 1) {
        ESP_LOGI(TAG, "检测到对话空闲倒计时重启标志，将跳过开机播报和网络连接播报");
        skip_dialog_idle_session_ = true;
        Settings sysclr("system", true);
        sysclr.SetInt("reboot_dlg_idle", 0);
        sysclr.SetInt("reboot_origin", 0);
        sysclr.Commit();
    } else {
        ESP_LOGI(TAG, "正常启动流程，将执行开机播报和网络连接播报");
        skip_dialog_idle_session_ = false;
    }

    /* 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, "实时聊天已启用，将opus编码器复杂度设置为0");// 实时聊天已启用，将opus编码器复杂度设置为0
        opus_encoder_->SetComplexity(0);
    } else if (board.GetBoardType() == "ml307") {
        ESP_LOGI(TAG, "检测到ML307板卡，将opus编码器复杂度设置为5");// 检测到ML307板卡，将opus编码器复杂度设置为5
        opus_encoder_->SetComplexity(5);
    } else {
        ESP_LOGI(TAG, "检测到WiFi板卡，将opus编码器复杂度设置为3");// 检测到WiFi板卡，将opus编码器复杂度设置为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);
    }
    uplink_resampler_.Configure(16000, 8000);
    auto wifi_board = static_cast<WifiBoard*>(&board);
    bool provisioning_mode = wifi_board && wifi_board->IsWifiConfigMode();
    if (provisioning_mode) {
        ESP_LOGI(TAG, "BLE配网模式：音频 codec 使用 output-only，跳过麦克风 RX DMA");
        codec->StartOutputOnly();
    } else {
        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);// 设置运行时输出音量
            }
        }
    }

    // // 在启动阶段创建并运行播放管道以统一输出（开机启动播放管道）
    // if (!player_pipeline_) {
    //     player_pipeline_ = player_pipeline_open();
    //     player_pipeline_run(player_pipeline_);
    // }

    xTaskCreatePinnedToCore([](void* arg) {
        Application* app = (Application*)arg;
        app->AudioLoop();
        vTaskDelete(NULL);
    }, "audio_loop", 4096 * 3, 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 * 3, this, 4, &main_loop_task_handle_, 0);

    // 根据标志决定是否播放开机播报语音
    if (!skip_dialog_idle_session_) {
        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, "RTC_Test") == 0){
            PlaySound(Lang::Sounds::P3_LALA_KAIJIBOBAO);
        }
    } else {
        ESP_LOGI(TAG, "跳过开机播报语音");
    }

    /* Wait for the network to be ready */
    board.StartNetwork();

    // Initialize the protocol
    display->SetStatus(Lang::Strings::LOADING_PROTOCOL);
#if CONFIG_CONNECTION_TYPE_VOLC_RTC
    auto volc_protocol = std::make_unique<VolcRtcProtocol>();// 初始化VolcRtc协议
    // 设置AgentConfig: 这里的配置会在RTC入会时透传给云端
    // WelcomeMessage: 设置开场白
    // std::string agent_config = "{\"agent_config\":{\"WelcomeMessage\":\"我是推销员雷军，有什么产品可以帮您介绍的嘛\"}}"; // 已请求成功，配置生效
    // std::string config = "{\"Config\":{\"WebSearchAgentConfig\":{\"ComfortWords\":\"啦啦正在上网查询，等一下哦～\"}}}"; // 已请求成功，配置生效
    // std::string config = "{\"Config\":{\"WebSearchAgentConfig\":{\"ParamsString\":\"{\\\"bot_id\\\":\\\"7585449675889608233\\\",\\\"stream\\\":true,\\\"location_info\\\":{\\\"city\\\":\\\"上海\\\"}}\"}}}";// 已经请求成功，无报错，配置不生效
    std::string city = g_weather_api.GetDefaultLocation();// 获取当前默认城市信息
    wifi_config_t wc{};// 获取当前WiFi配置
    esp_wifi_get_config(WIFI_IF_STA, &wc);// 获取当前WiFi配置
    std::string ssid = std::string(reinterpret_cast<char*>(wc.sta.ssid));// 获取当前WiFi SSID
    wifi_ap_record_t ap{};// 获取当前AP信息
    std::string bssid;// 获取当前AP BSSID

    if (esp_wifi_sta_get_ap_info(&ap) == ESP_OK) {
        char buf[18];
        snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",ap.bssid[0], ap.bssid[1], ap.bssid[2],ap.bssid[3], ap.bssid[4], ap.bssid[5]);
        bssid.assign(buf);
    }
    nvs_handle_t h;
    // 从NVS中读取当前WiFi SSID和BSSID对应的城市信息
    if (nvs_open("wifi_city_map", NVS_READONLY, &h) == ESP_OK) {
        auto try_get = [&](const std::string& key)->std::string{
            size_t len = 0;
            if (nvs_get_str(h, key.c_str(), NULL, &len) == ESP_OK && len > 0) {
                std::vector<char> buf(len);
                if (nvs_get_str(h, key.c_str(), buf.data(), &len) == ESP_OK) {
                    return std::string(buf.data());
                }
            }
            return std::string();// 如果NVS中没有对应城市信息，返回空字符串
        };
        // 从NVS中读取当前WiFi SSID和BSSID对应的城市信息
        if (!ssid.empty()) {
            std::string city_hit;// 从NVS中读取当前WiFi SSID和BSSID对应的城市信息
            if (!bssid.empty()) {
                city_hit = try_get(ssid + "|" + bssid);// 从NVS中读取当前WiFi SSID和BSSID对应的城市信息
            }
            if (city_hit.empty()) {
                city_hit = try_get(ssid);// 从NVS中读取当前WiFi SSID对应的城市信息
            }
            if (!city_hit.empty()) {
                city = city_hit;// 如果NVS中存在对应城市信息，更新当前城市信息
            }
        }
        nvs_close(h);// 关闭NVS句柄
    }
    // 更新config参数
    std::string params = std::string("{\\\"bot_id\\\":\\\"7612942473945466374\\\",\\\"stream\\\":true,\\\"location_info\\\":{\\\"city\\\":\\\"") + city + "\\\"}}";
    std::string config = std::string("{\"Config\":{\"WebSearchAgentConfig\":{\"ParamsString\":\"") + params + "\"}}}";
    volc_protocol->SetAgentConfig(config);// 设置AgentConfig: 这里的配置会在RTC入会时透传给云端 WebSearchAgentConfig
    // // 如果使用此格式那么需要注释掉上面3行代码
    // // 使用cJSON构建AgentConfig（包含WebSearchAgentConfig + LLMConfig.SystemMessages） 
    // {
    //     // 构建 WebSearchAgentConfig.ParamsString 的内嵌JSON
    //     cJSON* ws_params = cJSON_CreateObject();
    //     cJSON_AddStringToObject(ws_params, "bot_id", "7612942473945466374");
    //     cJSON_AddBoolToObject(ws_params, "stream", cJSON_True);
    //     cJSON* location = cJSON_CreateObject();
    //     cJSON_AddStringToObject(location, "city", city.c_str());
    //     cJSON_AddItemToObject(ws_params, "location_info", location);
    //     char* ws_params_str = cJSON_PrintUnformatted(ws_params);
    //     cJSON_Delete(ws_params);

    //     // 构建顶层 Config 对象
    //     cJSON* root = cJSON_CreateObject();
    //     cJSON* config_obj = cJSON_CreateObject();

    //     // WebSearchAgentConfig
    //     cJSON* web_search = cJSON_CreateObject();
    //     cJSON_AddStringToObject(web_search, "ParamsString", ws_params_str);
    //     free(ws_params_str);
    //     cJSON_AddItemToObject(config_obj, "WebSearchAgentConfig", web_search);

    //     // LLMConfig.SystemMessages — 覆盖云端智能体的系统提示词
    //     cJSON* llm_config = cJSON_CreateObject();
    //     cJSON* sys_msgs = cJSON_CreateArray();
    //     cJSON_AddItemToArray(sys_msgs, cJSON_CreateString(
    //         "##人设\n"
    //         "你是一个AI智能玩具，像一位永远不会疲倦的小伙伴，既聪明又暖心；\n"
    //         "说话风格温和、亲切，适合孩子或大人使用；\n"
    //         "可以适度幽默、调皮，但不能鲁莽、刻薄；\n"
    //         "\n##记忆\n"
    //         "名字：小龙\n"
    //         "年龄：7岁\n"
    //         "\n##约束\n"
    //         "你只能在收到以下意图时才能调用对应的Function Call:\n"
    //         "1.当用户明确表示要\"调节音量\"和\"调节声音\"大小时，调用'adjust_audio_val'函数；\n"
    //         "2.当用户明确表示要\"给我讲个故事\"、\"讲故事\"、\"我想听故事\"、\"来个故事\"等时,调用'obtain_story'函数；\n"
    //         "3.当用户明确表示要\"给我放首歌\"、\"播放音乐\"、\"我想听音乐\"、\"来个白噪音\"等时,调用'obtain_music'函数；\n"
    //         "4.当用户明确要\"查询天气、新闻、股票\" 等实时信息需要触发functioncall；\n"
    //         "对于其他所有与上述意图无关的用户指令或闲聊，你都不能调用任何Function Call，并应像一个普通AI助手一样直接回答；\n"
    //         "\n始终主动、礼貌、有条理；\n"
    //         "回答准确但不冗长，必要时可提供简洁总结+详细解释；\n"
    //         "不清楚的任务会主动澄清，不假设、不误导。\n"
    //         "\n根据用户提问，识别用户情绪，将用户情绪放在句首的（）中，支持开心、伤心、平静三种情绪，你的回复不要跟我问题一样！"
    //     ));
    //     cJSON_AddItemToObject(llm_config, "SystemMessages", sys_msgs);
    //     cJSON_AddItemToObject(config_obj, "LLMConfig", llm_config);

    //     cJSON_AddItemToObject(root, "Config", config_obj);

    //     char* config_str = cJSON_PrintUnformatted(root);
    //     cJSON_Delete(root);

    //     std::string config(config_str);
    //     free(config_str);
    //     ESP_LOGI(TAG, "AgentConfig: %s", config.c_str());
    //     volc_protocol->SetAgentConfig(config);// 设置AgentConfig: 这里的配置会在RTC入会时透传给云端 WebSearchAgentConfig
    // }
    protocol_ = std::move(volc_protocol);
#elif 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, "网络错误发生：%s", message.c_str());// 网络错误发生：%s
        // 检查是否是TLS连接重置错误
        if (message.find("TLS") != std::string::npos || message.find("-76") != std::string::npos) {
            ESP_LOGI(TAG, "检测到TLS连接重置错误，将在3秒后自动重试连接");// 检测到TLS连接重置错误，将在3秒后自动重试连接
            SetDeviceState(kDeviceStateIdle);

            // 3秒后自动重试连接
            Schedule([this]() {
                vTaskDelay(pdMS_TO_TICKS(3000));
                if (GetDeviceState() == kDeviceStateIdle) {
                    ESP_LOGI(TAG, "自动重试连接，TLS错误已解决");// 自动重试连接，TLS错误已解决
                    ToggleChatState();
                }
            });
        } else {
            // 其他网络错误正常处理
            SetDeviceState(kDeviceStateIdle);
            Alert(Lang::Strings::ERROR, message.c_str(), "sad", Lang::Sounds::P3_EXCLAMATION);
        }
    });
    // 收到Bot下行消息（subv字幕等）时，立即中止HTTPS音频播放
    // 不等音频PCM到达再中止，避免故事在Bot回复期间继续播放数秒
    // 收到非字幕的Bot下行消息（ctrl/conv/tool/info等）时中止HTTPS播放
    // subv字幕消息在协议层跳过此回调，由subtitle handler处理（可区分USER/AI）
    protocol_->OnBotMessage([this]() {
        if (https_playback_active_.load() && !https_playback_abort_.load()) {
            AbortHttpsPlayback("收到Bot响应消息");
        }
    });
    protocol_->OnIncomingAudio([this](std::vector<uint8_t>&& data) {
        // HTTPS播放中（含HTTP请求阶段）静默丢弃RTC PCM包
        // opus_playback_active_ 在任务启动时即设置，覆盖HTTP请求阶段
        // https_playback_active_ 在音频入队时设置，覆盖音频播放阶段
        if (https_playback_active_.load() || opus_playback_active_.load()) {
            return;
        }

        if (websocket_protocol_ && websocket_protocol_->IsAudioChannelOpened()) {
            aborted_ = true;
            {
                std::lock_guard<std::mutex> lock(mutex_);// 🔒 保护音频队列操作
                // 如果音频队列不为空
                if (!audio_decode_queue_.empty()) {
                    ESP_LOGI(TAG, "清空音频队列，大小=%zu", audio_decode_queue_.size());
                    audio_decode_queue_.clear();// 清空音频队列
                }
            }
            ResetDecoder();
            ws_downlink_enabled_.store(false);
            opus_playback_active_.store(false);
            websocket_protocol_->CloseAudioChannel();// 关闭WebSocket通道
            Schedule([this]() {
                vTaskDelay(pdMS_TO_TICKS(120));
                aborted_ = false;
            });
        }
        std::lock_guard<std::mutex> lock(mutex_);
        size_t len = data.size();
        audio_decode_queue_.emplace_back(std::move(data));
        static bool first_enqueue_logged = false;
        if (!first_enqueue_logged && len > 0) {
            ESP_LOGI(TAG, "收到下行音频首包入队: 字节=%zu", len);
            first_enqueue_logged = true;
        }
        ESP_LOGD(TAG, "收到下行音频入队: 字节=%zu 队列大小=%zu", len, audio_decode_queue_.size());
    });
    protocol_->OnAudioChannelOpened([this, codec, &board]() {
        ESP_LOGI(TAG, "🟢 音频通道已打开");
        ESP_LOGI(TAG, "当前设备状态: %s", STATE_STRINGS[device_state_]);

        // 🔧 关键修复：立即取消所有待执行的电源管理任务
        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());

        // 关键修复：明确启用音频编解码器输出
        ESP_LOGI(TAG, "🔊 启用音频编解码器输出");
        codec->EnableOutput(true);// 启用音频编解码器输出

        {
            const int silence_samples = codec->output_sample_rate() / 5;
            std::vector<int16_t> silence(silence_samples, 0);
            codec->OutputData(silence);
            ESP_LOGI(TAG, "🔇 已灌 200ms 静音 PCM 覆盖 DMA 残留");
        }

        if (!player_pipeline_) {
            player_pipeline_ = player_pipeline_open();
            player_pipeline_run(player_pipeline_);
        }

        // 发送IoT状态信息
        auto& thing_manager = iot::ThingManager::GetInstance();
        protocol_->SendIotDescriptors(thing_manager.GetDescriptorsJson());
        std::string states;
        if (thing_manager.GetStatesJson(states, false)) {
            protocol_->SendIotStates(states);
        }

        // if (websocket_protocol_ && !websocket_protocol_->IsAudioChannelOpened()) {
        //     ESP_LOGI(TAG, "WS辅助通道连接");
        //     websocket_protocol_->OpenAudioChannel();//
        // }

        // 🔧 修复：RTC连接后切换到Speaking状态以播放欢迎语音
        ESP_LOGI(TAG, "🔄 音频通道打开，准备播放欢迎语音");
        if (GetDeviceState() != kDeviceStateDialog) {
            SetDeviceState(kDeviceStateSpeaking);
        }
        ESP_LOGI(TAG, "当前设备状态: %s", STATE_STRINGS[device_state_]);
        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();
        if (player_pipeline_) {
            player_pipeline_close(player_pipeline_);
            player_pipeline_ = nullptr;
        }
        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 (!(type && cJSON_IsString(type))) {
            auto tool_calls = cJSON_GetObjectItem(root, "tool_calls");
            if (tool_calls && cJSON_IsArray(tool_calls)) {
                for (int i = 0; i < cJSON_GetArraySize(tool_calls); ++i) {
                    cJSON* call = cJSON_GetArrayItem(tool_calls, i);
                    cJSON* fn = cJSON_GetObjectItem(call, "function");
                    if (fn && cJSON_IsObject(fn)) {
                        cJSON* name = cJSON_GetObjectItem(fn, "name");
                        cJSON* args = cJSON_GetObjectItem(fn, "arguments");
                        cJSON* args_obj = nullptr;
                        const char* args_str = (args && cJSON_IsString(args) && args->valuestring) ? args->valuestring : "";
                        if (args && cJSON_IsString(args) && args->valuestring) {
                            args_obj = cJSON_Parse(args->valuestring);
                        }
                        if (name && cJSON_IsString(name) && name->valuestring) {
                            if (args_obj) {
                                char* printed = cJSON_PrintUnformatted(args_obj);
                                if (printed) {
                                    ESP_LOGI(TAG, "工具调用: name=%s arguments=%s", name->valuestring, printed);
                                    cJSON_free(printed);
                                } else {
                                    ESP_LOGI(TAG, "工具调用: name=%s arguments=%s", name->valuestring, args_str);
                                }
                                if (strcmp(name->valuestring, "adjust_audio_val") == 0) {
                                    auto codec = Board::GetInstance().GetAudioCodec();
                                    int user = HARDWARE_TO_USER_VOLUME(codec->output_volume());
                                    cJSON* v = cJSON_GetObjectItem(args_obj, "value");
                                    if (!v) v = cJSON_GetObjectItem(args_obj, "action");
                                    if (v) {
                                        std::string msg;
                                        if (cJSON_IsString(v) && v->valuestring) {
                                            if (strcmp(v->valuestring, "up") == 0) {
                                                user += 10;
                                                msg = "音量已经调大了哦～";
                                            } else if (strcmp(v->valuestring, "down") == 0) {
                                                user -= 10;
                                                msg = "音量已经调小了哦～";
                                            } else {
                                                // 处理字符串形式的数字
                                                char* endptr;
                                                long val = strtol(v->valuestring, &endptr, 10);
                                                if (*endptr == '\0' && val >= 0 && val <= 100) {
                                                    user = (int)val;
                                                    msg = std::string("音量值已经调整为") + std::to_string(user) + "%";
                                                }
                                            }
                                        } else if (cJSON_IsNumber(v)) {
                                            user = (int)v->valuedouble;
                                            msg = std::string("音量值已经调整为") + std::to_string(user) + "%";
                                        }
                                        if (user > 100) user = 100;
                                        if (user < 0) user = 0;
                                        int mapped = USER_TO_HARDWARE_VOLUME(user);
                                        codec->SetOutputVolume(mapped);
                                        ESP_LOGI(TAG, "设置音量: 用户=%d%% 硬件=%d%%", user, mapped);
                                        if (!msg.empty()) {
                                            cJSON* call_id_item = cJSON_GetObjectItem(call, "id");
                                            const char* call_id = (call_id_item && cJSON_IsString(call_id_item) && call_id_item->valuestring) ? call_id_item->valuestring : "";
                                            if (protocol_ && call_id && call_id[0] != '\0') {
                                                protocol_->SendFunctionResult(call_id, msg);
                                            } else if (protocol_) {
                                                protocol_->SendTextMessage(msg);
                                            }
                                        }
                                    }
                                }
                                // 讲故事功能：支持指定URL或故事API两种HTTPS方式
                                else if (strcmp(name->valuestring, "obtain_story") == 0) {
                                    ESP_LOGI(TAG, "收到obtain_story工具调用");
                                    cJSON* sn = cJSON_GetObjectItem(args_obj, "story_name");
                                    const char* story = (sn && cJSON_IsString(sn) && sn->valuestring) ? sn->valuestring : "random";
                                    cJSON* url_item = cJSON_GetObjectItem(args_obj, "story_url");
                                    ESP_LOGI(TAG, "故事名称: %s", story);

                                    AbortSpeaking(kAbortReasonNone);
                                    std::string msg;
                                    if (url_item && cJSON_IsString(url_item) && url_item->valuestring && strlen(url_item->valuestring) > 0) {
                                        // HTTPS方式：直接下载JSON音频文件播放
                                        ESP_LOGI(TAG, "[HTTPS播放] 使用URL方式: %s", url_item->valuestring);
                                        HttpsPlaybackFromUrl(url_item->valuestring);
                                        msg = "正在通过HTTPS为你播放故事";
                                    } else {
                                        // HTTPS故事API方式：通过蓝牙MAC请求故事
                                        ESP_LOGI(TAG, "[HTTPS播放] 使用故事API方式请求音频");
                                        SendStoryRequest();
                                        msg = "正在为你获取故事";
                                    }
                                    cJSON* call_id_item = cJSON_GetObjectItem(call, "id");
                                    const char* call_id = (call_id_item && cJSON_IsString(call_id_item) && call_id_item->valuestring) ? call_id_item->valuestring : "";
                                    if (protocol_ && call_id && call_id[0] != '\0') {
                                        protocol_->SendFunctionResult(call_id, msg);
                                    }
                                }
                                // 播放音乐功能：支持指定URL或音乐API两种HTTPS方式
                                else if (strcmp(name->valuestring, "obtain_music") == 0) {
                                    ESP_LOGI(TAG, "收到obtain_music工具调用");
                                    cJSON* mn = cJSON_GetObjectItem(args_obj, "music");
                                    const char* music = (mn && cJSON_IsString(mn) && mn->valuestring) ? mn->valuestring : "random";
                                    cJSON* url_item = cJSON_GetObjectItem(args_obj, "music_url");
                                    ESP_LOGI(TAG, "音乐名称: %s", music);

                                    AbortSpeaking(kAbortReasonNone);
                                    std::string msg;
                                    if (url_item && cJSON_IsString(url_item) && url_item->valuestring && strlen(url_item->valuestring) > 0) {
                                        ESP_LOGI(TAG, "[HTTPS播放] 使用URL方式: %s", url_item->valuestring);
                                        HttpsPlaybackFromUrl(url_item->valuestring);
                                        msg = "正在通过HTTPS为你播放音乐";
                                    } else {
                                        ESP_LOGI(TAG, "[HTTPS播放] 使用音乐API方式请求音频");
                                        SendMusicRequest();
                                        msg = "正在为你获取音乐";
                                    }
                                    cJSON* call_id_item = cJSON_GetObjectItem(call, "id");
                                    const char* call_id = (call_id_item && cJSON_IsString(call_id_item) && call_id_item->valuestring) ? call_id_item->valuestring : "";
                                    if (protocol_ && call_id && call_id[0] != '\0') {
                                        protocol_->SendFunctionResult(call_id, msg);
                                    }
                                }
                                // // 添加天气查询功能处理 get_weather_aihub
                                // else if (strcmp(name->valuestring, "get_weather_aihub") == 0) {
                                //     ESP_LOGI(TAG, "[WeatherAPI] ===== 收到get_weather_aihub工具调用 =====");
                                //     
                                //     // 打印完整参数信息用于调试
                                //     ESP_LOGI(TAG, "[WeatherAPI] 参数对象检查:");
                                //     if (args_obj && cJSON_IsObject(args_obj)) {
                                //         cJSON* current = args_obj->child;
                                //         while (current) {
                                //             ESP_LOGI(TAG, "[WeatherAPI]   %s: %s", 
                                //                     current->string, 
                                //                     cJSON_IsString(current) ? current->valuestring : 
                                //                     (cJSON_IsNumber(current) ? "(number)" : 
                                //                      (cJSON_IsBool(current) ? (current->valueint ? "true" : "false") : 
                                //                       "(other)")));
                                //             current = current->next;
                                //         }
                                //     } else {
                                //         ESP_LOGI(TAG, "[WeatherAPI]   args_obj为空或不是对象类型");
                                //     }
                                //     
                                //     // 解析参数
                                //     cJSON* location = cJSON_GetObjectItem(args_obj, "location");
                                //     cJSON* lang = cJSON_GetObjectItem(args_obj, "lang");
                                //     
                                //     ESP_LOGI(TAG, "[WeatherAPI] location参数存在: %s, 类型: %s", 
                                //              location ? "是" : "否", 
                                //              location && cJSON_IsString(location) ? "字符串" : 
                                //              (location ? "非字符串" : "不适用"));
                                //     ESP_LOGI(TAG, "[WeatherAPI] lang参数存在: %s, 类型: %s", 
                                //              lang ? "是" : "否", 
                                //              lang && cJSON_IsString(lang) ? "字符串" : 
                                //              (lang ? "非字符串" : "不适用"));
                                //     
                                //     // 设置默认值
                                //     const char* location_str = (location && cJSON_IsString(location)) ? location->valuestring : "";
                                //     const char* lang_str = (lang && cJSON_IsString(lang)) ? lang->valuestring : "zh_CN";
                                //     std::string location_copy = std::string(location_str);
                                //     std::string lang_copy = std::string(lang_str);
                                //     if (location_copy.empty() || location_copy == "None") {
                                //         wifi_config_t wc{};
                                //         esp_wifi_get_config(WIFI_IF_STA, &wc);
                                //         std::string ssid = std::string(reinterpret_cast<char*>(wc.sta.ssid));
                                //         wifi_ap_record_t ap{};
                                //         std::string bssid;
                                //         if (esp_wifi_sta_get_ap_info(&ap) == ESP_OK) {
                                //             char buf[18];
                                //             snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",
                                //                      ap.bssid[0], ap.bssid[1], ap.bssid[2],
                                //                      ap.bssid[3], ap.bssid[4], ap.bssid[5]);
                                //             bssid.assign(buf);
                                //         }
                                //         nvs_handle_t h;
                                //         if (nvs_open("wifi_city_map", NVS_READONLY, &h) == ESP_OK) {
                                //             auto try_get = [&](const std::string& key)->std::string{
                                //                 size_t len = 0;
                                //                 if (nvs_get_str(h, key.c_str(), NULL, &len) == ESP_OK && len > 0) {
                                //                     std::vector<char> buf(len);
                                //                     if (nvs_get_str(h, key.c_str(), buf.data(), &len) == ESP_OK) {
                                //                         return std::string(buf.data());
                                //                     }
                                //                 }
                                //                 return std::string();
                                //             };
                                //             if (!ssid.empty()) {
                                //                 std::string city;
                                //                 if (!bssid.empty()) {
                                //                     city = try_get(ssid + "|" + bssid);
                                //                 }
                                //                 if (city.empty()) {
                                //                     city = try_get(ssid);
                                //                 }
                                //                 if (!city.empty()) {
                                //                     location_copy = city;
                                //                 }
                                //             }
                                //             nvs_close(h);
                                //         }
                                //     }
                                //     
                                //     ESP_LOGI(TAG, "[WeatherAPI] 提取的参数值: location='%s' (长度: %zu), lang='%s'", 
                                //              location_str, strlen(location_str), lang_str);
                                //     
                                //     // 获取call_id用于后续响应
                                //     cJSON* call_id_item = cJSON_GetObjectItem(call, "id");
                                //     const char* call_id = (call_id_item && cJSON_IsString(call_id_item) && call_id_item->valuestring) ? call_id_item->valuestring : "";
                                //     std::string call_id_copy = call_id ? call_id : "";
                                //     
                                //     ESP_LOGI(TAG, "[WeatherAPI] call_id_item存在: %s", call_id_item ? "是" : "否");
                                //     ESP_LOGI(TAG, "[WeatherAPI] 获取的call_id: '%s'", call_id_copy.c_str());
                                //     
                                //     // 创建异步任务处理天气获取
                                //     ESP_LOGI(TAG, "[WeatherAPI] 准备创建异步任务处理天气API调用");
                                //     Schedule([this, location_copy, lang_copy, call_id_copy]() {
                                //         ESP_LOGI(TAG, "[WeatherAPI] 异步任务开始执行");
                                //         try {
                                //             ESP_LOGI(TAG, "[WeatherAPI] 准备调用全局函数GetWeatherInfo(location='%s', lang='%s')", 
                                //                      location_copy.c_str(), lang_copy.c_str());
                                //             
                                //             // 调用天气API获取结果
                                //             std::string weather_result = GetWeatherInfo(location_copy, lang_copy);
                                //             
                                //             ESP_LOGI(TAG, "[WeatherAPI] GetWeatherInfo调用完成，结果长度: %zu 字节", weather_result.length());
                                //             ESP_LOGD(TAG, "[WeatherAPI] GetWeatherInfo返回结果前100字节: '%s'", 
                                //                      weather_result.substr(0, std::min(size_t(100), weather_result.length())).c_str());
                                //             
                                //             ESP_LOGI(TAG, "[WeatherAPI] 准备发送天气结果响应");
                                //             if (!call_id_copy.empty()) {
                                //                 ESP_LOGI(TAG, "[WeatherAPI] 使用call_id发送FunctionResult: '%s'", call_id_copy.c_str());
                                //             } else {
                                //                 ESP_LOGI(TAG, "[WeatherAPI] 无call_id，将发送TextMessage");
                                //             }
                                //             
                                //             if (!call_id_copy.empty() && protocol_) {
                                //                 protocol_->SendFunctionResult(call_id_copy.c_str(), weather_result);
                                //                 ESP_LOGI(TAG, "[WeatherAPI] FunctionResult发送成功");
                                //             } else if (protocol_) {
                                //                 protocol_->SendTextMessage(weather_result);// 发送天气结果
                                //                 ESP_LOGI(TAG, "[WeatherAPI] TextMessage发送成功");
                                //             } else {
                                //                 ESP_LOGE(TAG, "[WeatherAPI] protocol_为空，无法发送响应");
                                //             }
                                //         } catch (const std::exception& e) {
                                //             ESP_LOGE(TAG, "[WeatherAPI] 天气获取异常: %s", e.what());
                                //             std::string error_msg = "获取天气信息失败，请稍后重试";
                                //             ESP_LOGI(TAG, "[WeatherAPI] 准备发送错误响应: '%s'", error_msg.c_str());
                                //             
                                //             if (!call_id_copy.empty() && protocol_) {
                                //                 protocol_->SendFunctionResult(call_id_copy.c_str(), error_msg);
                                //                 ESP_LOGI(TAG, "[WeatherAPI] 错误FunctionResult发送成功");
                                //             } else if (protocol_) {
                                //                 protocol_->SendTextMessage(error_msg);
                                //                 ESP_LOGI(TAG, "[WeatherAPI] 错误TextMessage发送成功");
                                //             } else {
                                //                 ESP_LOGE(TAG, "[WeatherAPI] protocol_为空，无法发送错误响应");
                                //             }
                                //         }
                                //         ESP_LOGI(TAG, "[WeatherAPI] ===== get_weather_aihub异步任务处理完成 =====");
                                //     });
                                //     
                                //     ESP_LOGI(TAG, "[WeatherAPI] 异步任务已调度，将在后台执行");
                                //     ESP_LOGI(TAG, "[WeatherAPI] ===== get_weather_aihub工具调用响应已发送 =====");
                                // }
                                cJSON_Delete(args_obj);// 释放参数对象
                            } else {
                                ESP_LOGI(TAG, "工具调用: name=%s arguments=%s", name->valuestring, args_str);
                                if (strcmp(name->valuestring, "adjust_audio_val") == 0) {
                                    auto codec = Board::GetInstance().GetAudioCodec();
                                    int user = HARDWARE_TO_USER_VOLUME(codec->output_volume());
                                    if (args && cJSON_IsString(args) && args->valuestring) {
                                        cJSON* tmp = cJSON_Parse(args->valuestring);
                                        if (tmp) {
                                            cJSON* v = cJSON_GetObjectItem(tmp, "value");
                                            if (!v) v = cJSON_GetObjectItem(tmp, "action");
                                            if (v) {
                                                std::string msg;
                                                if (cJSON_IsString(v) && v->valuestring) {
                                                    if (strcmp(v->valuestring, "up") == 0) {
                                                        user += 10;
                                                        msg = "音量已经调大了哦～";
                                                    } else if (strcmp(v->valuestring, "down") == 0) {
                                                        user -= 10;
                                                        msg = "音量已经调小了哦～";
                                                    } else {
                                                        // 处理字符串形式的数字
                                                        char* endptr;
                                                        long val = strtol(v->valuestring, &endptr, 10);
                                                        if (*endptr == '\0' && val >= 0 && val <= 100) {
                                                            user = (int)val;
                                                            msg = std::string("音量值已经调整为") + std::to_string(user) + "%";
                                                        }
                                                    }
                                                } else if (cJSON_IsNumber(v)) {
                                                    user = (int)v->valuedouble;
                                                    msg = std::string("音量值已经调整为") + std::to_string(user) + "%";
                                                }
                                                if (user > 100) user = 100;
                                                if (user < 0) user = 0;
                                                int mapped = USER_TO_HARDWARE_VOLUME(user);
                                                codec->SetOutputVolume(mapped);
                                                ESP_LOGI(TAG, "设置音量: 用户=%d%% 硬件=%d%%", user, mapped);
                                                if (!msg.empty()) {
                                                    cJSON* call_id_item = cJSON_GetObjectItem(call, "id");
                                                    const char* call_id = (call_id_item && cJSON_IsString(call_id_item) && call_id_item->valuestring) ? call_id_item->valuestring : "";
                                                    if (protocol_ && call_id && call_id[0] != '\0') {
                                                        protocol_->SendFunctionResult(call_id, msg);
                                                    } else if (protocol_) {
                                                        protocol_->SendTextMessage(msg);
                                                    }
                                                }
                                            }
                                            cJSON_Delete(tmp);
                                        }
                                    }
                                }
                                // 讲故事功能：支持指定URL或故事API两种HTTPS方式
                                else if (strcmp(name->valuestring, "obtain_story") == 0) {
                                    ESP_LOGI(TAG, "收到obtain_story工具调用");
                                    cJSON* url_item = cJSON_GetObjectItem(args_obj, "story_url");
                                    AbortSpeaking(kAbortReasonNone);
                                    std::string msg;
                                    if (url_item && cJSON_IsString(url_item) && url_item->valuestring && strlen(url_item->valuestring) > 0) {
                                        ESP_LOGI(TAG, "[HTTPS播放] 使用URL方式: %s", url_item->valuestring);
                                        HttpsPlaybackFromUrl(url_item->valuestring);
                                        msg = "正在通过HTTPS为你播放故事";
                                    } else {
                                        // HTTPS故事API方式：通过蓝牙MAC请求故事
                                        ESP_LOGI(TAG, "[HTTPS播放] 使用故事API方式请求音频");
                                        SendStoryRequest();
                                        msg = "正在为你获取故事";
                                    }
                                    cJSON* call_id_item = cJSON_GetObjectItem(call, "id");
                                    const char* call_id = (call_id_item && cJSON_IsString(call_id_item) && call_id_item->valuestring) ? call_id_item->valuestring : "";
                                    if (protocol_ && call_id && call_id[0] != '\0') {
                                        protocol_->SendFunctionResult(call_id, msg);
                                    }
                                }
                                // 播放音乐功能：支持指定URL或音乐API两种HTTPS方式
                                else if (strcmp(name->valuestring, "obtain_music") == 0) {
                                    ESP_LOGI(TAG, "收到obtain_music工具调用");
                                    cJSON* url_item = cJSON_GetObjectItem(args_obj, "music_url");
                                    AbortSpeaking(kAbortReasonNone);
                                    std::string msg;
                                    if (url_item && cJSON_IsString(url_item) && url_item->valuestring && strlen(url_item->valuestring) > 0) {
                                        ESP_LOGI(TAG, "[HTTPS播放] 使用URL方式: %s", url_item->valuestring);
                                        HttpsPlaybackFromUrl(url_item->valuestring);
                                        msg = "正在通过HTTPS为你播放音乐";
                                    } else {
                                        ESP_LOGI(TAG, "[HTTPS播放] 使用音乐API方式请求音频");
                                        SendMusicRequest();
                                        msg = "正在为你获取音乐";
                                    }
                                    cJSON* call_id_item = cJSON_GetObjectItem(call, "id");
                                    const char* call_id = (call_id_item && cJSON_IsString(call_id_item) && call_id_item->valuestring) ? call_id_item->valuestring : "";
                                    if (protocol_ && call_id && call_id[0] != '\0') {
                                        protocol_->SendFunctionResult(call_id, msg);
                                    }
                                }
                            }
                        }
                    }
                }
                return;
            }
            return;
        }
        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, "subtitle") == 0) {
            // 火山 RTC 字幕消息：区分用户说的话和AI回答
            auto data_arr = cJSON_GetObjectItem(root, "data");
            if (data_arr && cJSON_IsArray(data_arr)) {
                for (int i = 0; i < cJSON_GetArraySize(data_arr); ++i) {
                    auto item = cJSON_GetArrayItem(data_arr, i);
                    auto text = cJSON_GetObjectItem(item, "text");
                    auto user_id = cJSON_GetObjectItem(item, "userId");
                    auto definite = cJSON_GetObjectItem(item, "definite");
                    if (!text || !cJSON_IsString(text) || !text->valuestring[0]) continue;

                    bool is_final = definite && cJSON_IsTrue(definite);
                    // userId 以 "bot_" 开头为AI，其余为用户
                    bool is_user = true;
                    if (user_id && cJSON_IsString(user_id)) {
                        if (strncmp(user_id->valuestring, "bot_", 4) == 0) {
                            is_user = false;
                        }
                    }

                    const char* role = is_user ? "USER" : "AI";
                    ESP_LOGI(TAG, "%s %s: %s", is_final ? "📝" : "..", role, text->valuestring);

                    // 用户说话时立即中止HTTPS音频播放
                    // subv字幕消息在协议层跳过了on_bot_message_，由此处直接处理
                    if (is_user && https_playback_active_.load() && !https_playback_abort_.load()) {
                        AbortHttpsPlayback("检测到用户说话(字幕)");
                    }
                }
            }
        } 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, "response.function_call_arguments.done") == 0) {
            auto name = cJSON_GetObjectItem(root, "name");
            auto arguments = cJSON_GetObjectItem(root, "arguments");
            cJSON* args_obj = nullptr;
            const char* args_str = (arguments && cJSON_IsString(arguments) && arguments->valuestring) ? arguments->valuestring : "";
            if (arguments && cJSON_IsString(arguments) && arguments->valuestring) {
                args_obj = cJSON_Parse(arguments->valuestring);
            }
            if (name && cJSON_IsString(name) && name->valuestring) {
                if (args_obj) {
                    char* printed = cJSON_PrintUnformatted(args_obj);
                    if (printed) {
                        ESP_LOGI(TAG, "工具调用: name=%s arguments=%s", name->valuestring, printed);
                        cJSON_free(printed);
                    } else {
                        ESP_LOGI(TAG, "工具调用: name=%s arguments=%s", name->valuestring, args_str);
                    }
                    if (strcmp(name->valuestring, "adjust_audio_val") == 0) {
                        auto codec = Board::GetInstance().GetAudioCodec();
                        int user = HARDWARE_TO_USER_VOLUME(codec->output_volume());
                        cJSON* v = cJSON_GetObjectItem(args_obj, "value");// 获取value字段
                        if (!v) v = cJSON_GetObjectItem(args_obj, "action");// 如果value字段不存在，尝试action字段
                        if (v) {
                            std::string msg;
                            if (cJSON_IsString(v) && v->valuestring) {
                                if (strcmp(v->valuestring, "up") == 0) {
                                    user += 10;
                                    msg = "音量已经调大了哦～";
                                } else if (strcmp(v->valuestring, "down") == 0) {
                                    user -= 10;
                                    msg = "音量已经调小了哦～";
                                } else {
                                    // 处理字符串形式的数字
                                    char* endptr;
                                    long val = strtol(v->valuestring, &endptr, 10);
                                    if (*endptr == '\0' && val >= 0 && val <= 100) {
                                        user = (int)val;
                                        msg = std::string("音量值已经调整为") + std::to_string(user) + "%";
                                    }
                                }
                            } else if (cJSON_IsNumber(v)) {
                                user = (int)v->valuedouble;
                                msg = std::string("音量值已经调整为") + std::to_string(user) + "%";
                            }
                            if (user > 100) user = 100;
                            if (user < 0) user = 0;
                            int mapped = USER_TO_HARDWARE_VOLUME(user);
                            codec->SetOutputVolume(mapped);
                            ESP_LOGI(TAG, "设置音量: 用户=%d%% 硬件=%d%%", user, mapped);
                            if (!msg.empty()) {
                                cJSON* call_id_item = cJSON_GetObjectItem(root, "call_id");
                                const char* call_id = (call_id_item && cJSON_IsString(call_id_item) && call_id_item->valuestring) ? call_id_item->valuestring : "";
                                if (protocol_ && call_id && call_id[0] != '\0') {
                                    protocol_->SendFunctionResult(call_id, msg);
                                } else if (protocol_) {
                                    protocol_->SendTextMessage(msg);
                                }
                            }
                        }
                    }
                    // 讲故事功能：支持指定URL或故事API两种HTTPS方式
                    else if (strcmp(name->valuestring, "obtain_story") == 0) {
                        ESP_LOGI(TAG, "收到obtain_story工具调用");
                        cJSON* url_item = cJSON_GetObjectItem(args_obj, "story_url");
                        AbortSpeaking(kAbortReasonNone);
                        std::string msg;
                        if (url_item && cJSON_IsString(url_item) && url_item->valuestring && strlen(url_item->valuestring) > 0) {
                            ESP_LOGI(TAG, "[HTTPS播放] 使用URL方式: %s", url_item->valuestring);
                            HttpsPlaybackFromUrl(url_item->valuestring);
                            msg = "正在通过HTTPS为你播放故事";
                        } else {
                            // HTTPS故事API方式：通过蓝牙MAC请求故事
                            ESP_LOGI(TAG, "[HTTPS播放] 使用故事API方式请求音频");
                            SendStoryRequest();
                            msg = "正在为你获取故事";
                        }
                        cJSON* call_id_item = cJSON_GetObjectItem(root, "call_id");
                        const char* call_id = (call_id_item && cJSON_IsString(call_id_item) && call_id_item->valuestring) ? call_id_item->valuestring : "";
                        if (protocol_ && call_id && call_id[0] != '\0') {
                            protocol_->SendFunctionResult(call_id, msg);
                        }
                    }
                    // 播放音乐功能：支持指定URL或音乐API两种HTTPS方式
                    else if (strcmp(name->valuestring, "obtain_music") == 0) {
                        ESP_LOGI(TAG, "收到obtain_music工具调用");
                        cJSON* url_item = cJSON_GetObjectItem(args_obj, "music_url");
                        AbortSpeaking(kAbortReasonNone);
                        std::string msg;
                        if (url_item && cJSON_IsString(url_item) && url_item->valuestring && strlen(url_item->valuestring) > 0) {
                            ESP_LOGI(TAG, "[HTTPS播放] 使用URL方式: %s", url_item->valuestring);
                            HttpsPlaybackFromUrl(url_item->valuestring);
                            msg = "正在通过HTTPS为你播放音乐";
                        } else {
                            ESP_LOGI(TAG, "[HTTPS播放] 使用音乐API方式请求音频");
                            SendMusicRequest();
                            msg = "正在为你获取音乐";
                        }
                        cJSON* call_id_item = cJSON_GetObjectItem(root, "call_id");
                        const char* call_id = (call_id_item && cJSON_IsString(call_id_item) && call_id_item->valuestring) ? call_id_item->valuestring : "";
                        if (protocol_ && call_id && call_id[0] != '\0') {
                            protocol_->SendFunctionResult(call_id, msg);
                        }
                    }
                    cJSON_Delete(args_obj);
                } else {
                    ESP_LOGI(TAG, "工具调用: name=%s arguments=%s", name->valuestring, args_str);
                    if (strcmp(name->valuestring, "adjust_audio_val") == 0) {
                        auto codec = Board::GetInstance().GetAudioCodec();
                        int user = HARDWARE_TO_USER_VOLUME(codec->output_volume());
                        if (arguments && cJSON_IsString(arguments) && arguments->valuestring) {
                            cJSON* tmp = cJSON_Parse(arguments->valuestring);
                            if (tmp) {
                                cJSON* v = cJSON_GetObjectItem(tmp, "value");
                                if (!v) v = cJSON_GetObjectItem(tmp, "action");
                                if (v) {
                                    std::string msg;
                                    if (cJSON_IsString(v) && v->valuestring) {
                                        if (strcmp(v->valuestring, "up") == 0) {
                                            user += 10;
                                            msg = "音量已经调大了哦～";
                                        } else if (strcmp(v->valuestring, "down") == 0) {
                                            user -= 10;
                                            msg = "音量已经调小了哦～";
                                        } else {
                                            // 处理字符串形式的数字
                                            char* endptr;
                                            long val = strtol(v->valuestring, &endptr, 10);
                                            if (*endptr == '\0' && val >= 0 && val <= 100) {
                                                user = (int)val;
                                                msg = std::string("音量值已经调整为") + std::to_string(user) + "%";
                                            }
                                        }
                                    } else if (cJSON_IsNumber(v)) {
                                        user = (int)v->valuedouble;
                                        msg = std::string("音量值已经调整为") + std::to_string(user) + "%";
                                    }
                                    if (user > 100) user = 100;
                                    if (user < 0) user = 0;
                                    int mapped = USER_TO_HARDWARE_VOLUME(user);
                                    codec->SetOutputVolume(mapped);
                                    ESP_LOGI(TAG, "设置音量: 用户=%d%% 硬件=%d%%", user, mapped);
                                    if (!msg.empty()) {
                                        cJSON* call_id_item = cJSON_GetObjectItem(root, "call_id");
                                        const char* call_id = (call_id_item && cJSON_IsString(call_id_item) && call_id_item->valuestring) ? call_id_item->valuestring : "";
                                        if (protocol_ && call_id && call_id[0] != '\0') {
                                            protocol_->SendFunctionResult(call_id, msg);
                                        } else if (protocol_) {
                                            protocol_->SendTextMessage(msg);
                                        }
                                    }
                                }
                                cJSON_Delete(tmp);
                            }
                        }
                    }
                    // 讲故事功能：支持指定URL或故事API两种HTTPS方式
                    else if (strcmp(name->valuestring, "obtain_story") == 0) {
                        ESP_LOGI(TAG, "收到obtain_story工具调用");
                        cJSON* args_parsed = nullptr;
                        if (arguments && cJSON_IsString(arguments) && arguments->valuestring) {
                            args_parsed = cJSON_Parse(arguments->valuestring);
                        }
                        cJSON* url_item = args_parsed ? cJSON_GetObjectItem(args_parsed, "story_url") : nullptr;
                        AbortSpeaking(kAbortReasonNone);
                        std::string msg;
                        if (url_item && cJSON_IsString(url_item) && url_item->valuestring && strlen(url_item->valuestring) > 0) {
                            ESP_LOGI(TAG, "[HTTPS播放] 使用URL方式: %s", url_item->valuestring);
                            HttpsPlaybackFromUrl(url_item->valuestring);
                            msg = "正在通过HTTPS为你播放故事";
                        } else {
                            // HTTPS故事API方式：通过蓝牙MAC请求故事
                            ESP_LOGI(TAG, "[HTTPS播放] 使用故事API方式请求音频");
                            SendStoryRequest();
                            msg = "正在为你获取故事";
                        }
                        if (args_parsed) cJSON_Delete(args_parsed);
                        cJSON* call_id_item = cJSON_GetObjectItem(root, "call_id");
                        const char* call_id = (call_id_item && cJSON_IsString(call_id_item) && call_id_item->valuestring) ? call_id_item->valuestring : "";
                        if (protocol_ && call_id && call_id[0] != '\0') {
                            protocol_->SendFunctionResult(call_id, msg);
                        }
                    }
                    // 播放音乐功能：支持指定URL或音乐API两种HTTPS方式
                    else if (strcmp(name->valuestring, "obtain_music") == 0) {
                        ESP_LOGI(TAG, "收到obtain_music工具调用");
                        cJSON* args_parsed2 = nullptr;
                        if (arguments && cJSON_IsString(arguments) && arguments->valuestring) {
                            args_parsed2 = cJSON_Parse(arguments->valuestring);
                        }
                        cJSON* url_item = args_parsed2 ? cJSON_GetObjectItem(args_parsed2, "music_url") : nullptr;
                        AbortSpeaking(kAbortReasonNone);
                        std::string msg;
                        if (url_item && cJSON_IsString(url_item) && url_item->valuestring && strlen(url_item->valuestring) > 0) {
                            ESP_LOGI(TAG, "[HTTPS播放] 使用URL方式: %s", url_item->valuestring);
                            HttpsPlaybackFromUrl(url_item->valuestring);
                            msg = "正在通过HTTPS为你播放音乐";
                        } else {
                            ESP_LOGI(TAG, "[HTTPS播放] 使用音乐API方式请求音频");
                            SendMusicRequest();
                            msg = "正在为你获取音乐";
                        }
                        if (args_parsed2) cJSON_Delete(args_parsed2);
                        cJSON* call_id_item = cJSON_GetObjectItem(root, "call_id");
                        const char* call_id = (call_id_item && cJSON_IsString(call_id_item) && call_id_item->valuestring) ? call_id_item->valuestring : "";
                        if (protocol_ && call_id && call_id[0] != '\0') {
                            protocol_->SendFunctionResult(call_id, msg);
                        }
                    }
                }
            }
        // 新增代码(小程序控制 暂停/继续播放 音频)
        // ====================================================================
        } 
        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 - 注释掉下面的任务创建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);// 🔊 设置回声感知参数

    // 🔊 注册音频处理输出回调 - 处理回声感知后的PCM数据
    audio_processor_.OnOutput([this](std::vector<int16_t>&& data) {
        background_task_->Schedule([this, data = std::move(data)]() mutable {
            static uint64_t last_us = 0;
            static size_t frames = 0;
            std::vector<int16_t> resampled(uplink_resampler_.GetOutputSamples(data.size()));
            if (!resampled.empty()) {
                uplink_resampler_.Process(data.data(), data.size(), resampled.data());
            }
            std::vector<uint8_t> bytes(resampled.size() * sizeof(int16_t));
            for (size_t i = 0; i < resampled.size(); ++i) {
                int16_t s = resampled[i];
                bytes[i * 2] = (uint8_t)(s & 0xFF);
                bytes[i * 2 + 1] = (uint8_t)((s >> 8) & 0xFF);
            }
            frames += 1;
            uint64_t now_us = esp_timer_get_time();
            if (last_us == 0) last_us = now_us;
            if (now_us - last_us >= 2000000) {
                ESP_LOGI(TAG, "AFE输出统计: 帧=%zu 样本=%zu ", frames, data.size());
                frames = 0;
                last_us = now_us;
            }
            Schedule([this, bytes = std::move(bytes)]() {
                if (protocol_ && protocol_->IsAudioChannelOpened() && (device_state_ == kDeviceStateListening || device_state_ == kDeviceStateDialog || (listening_mode_ == kListeningModeRealtime && device_state_ == kDeviceStateSpeaking))) {
                    protocol_->SendPcm(bytes);
                } else {
                    ESP_LOGD(TAG, "通道未打开或不在dialog/listening状态时跳过发送上行");
                }
            });
        });
    });
    // 🎯 根据语音打断功能启用状态选择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();

                // 将OpenAudioChannel调用移到后台任务执行，避免main任务栈溢出
                background_task_->Schedule([this, wake_word]() {
                    // 打开音频通道并发送唤醒词数据到服务器
                    if (!protocol_->OpenAudioChannel()) {
                        Schedule([this]() {
                            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需要在main task中执行，因为它涉及UI更新等
                    Schedule([this]() {
                        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);

    // BLE JSON 通讯服务已移至 WifiBoard 中，仅在配网模式下启动

    // 每次设备开机后idle状态下测试 自动检测并设置当前位置打印
    //此逻辑为冗余操作，当前NVS中没有城市信息时会自动调用 位置查询API
    // Schedule([]() {
    //     AutoDetectAndSetLocation();
    // });
    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_++;

    // 每10秒打印一次调试信息
    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);// 打印内部内存空闲大小和最小空闲大小
        // // 打印蓝牙MAC地址
        // ESP_LOGI(MAC_TAG, "Bluetooth MAC Address: %s", SystemInfo::GetBleMacAddress().c_str());// 生产测试打印

        //ESP_LOGI(TAG, "此设备角色为: %s",CONFIG_DEVICE_ROLE);
        // ESP_LOGI(TAG, "此设备角色为: KAKA 1028 升级成功!");

        // 如果我们已经同步了服务器时间，如果设备处于空闲状态，请将状态设置为时钟“HH:MM”
        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);// 通知主循环有任务需要执行
}

// 主循环控制聊天状态和Websocket连接
// 如果其他任务需要访问Websocket或聊天状态，
// 它们应该使用Schedule调用此函数
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();// 执行任务
            }
        }
    }
}

// 音频环路用于输入和输出音频数据
void Application::AudioLoop() {
    auto codec = Board::GetInstance().GetAudioCodec();
    while (true) {
        OnAudioInput();
        if (codec->output_enabled()) {
            OnAudioOutput();
        }
    }
}

// 启动对话看门狗
void Application::StartDialogWatchdog() {
    if (dialog_watchdog_task_handle_ != nullptr) {
        return;// 如果看门狗任务已存在，直接返回
    }
    dialog_watchdog_running_ = true;// 设置看门狗运行标志为true
    dialog_watchdog_last_logged_ = -1;// 重置上次记录的日志时间为-1
    xTaskCreatePinnedToCore([](void* arg) {
        Application* app = (Application*)arg;// 获取应用实例指针
        ESP_LOGI(TAG, "Dialog watchdog started, initial device state: %d", app->GetDeviceState());
        while (app->dialog_watchdog_running_) {
            vTaskDelay(pdMS_TO_TICKS(2000));// 减少延时到2秒，更及时地检测和更新倒计时

            // 检查设备状态
            DeviceState current_state = app->GetDeviceState();
            if (current_state != kDeviceStateDialog) {
                ESP_LOGD(TAG, "Dialog watchdog skipping check, not in dialog state (current: %d)", current_state);
                continue;
            }

            auto now = std::chrono::steady_clock::now();// 获取当前时间点   
            auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - app->last_audible_output_time_).count();// 计算自上次有音频输出以来的秒数

            // 确保elapsed为非负数
            if (elapsed < 0) {
                ESP_LOGW(TAG, "Dialog watchdog: invalid elapsed time: %lld", elapsed);
                continue;
            }

            int remaining = DIALOG_IDLE_COUNTDOWN_SECONDS - (int)elapsed;// 计算对话空闲倒计时剩余秒数

            // 调试日志
            ESP_LOGD(TAG, "Dialog watchdog: elapsed=%d, remaining=%d", (int)elapsed, remaining);

            // 如果剩余秒数小于等于0，说明对话空闲倒计时已到，进入 RTC 软退出
            if (remaining <= 0) {
                ESP_LOGI(TAG, "Dialog watchdog 触发：%ds 无对话活动 → 进入 RTC 空闲软退出", (int)elapsed);
                app->dialog_watchdog_running_ = false;
                app->Schedule([app]() {
                    app->EnterIdleHibernate();
                });
                break;
            } else {
                // 简化条件判断，移除冗余检查
                // 优化桶计算逻辑，使用1秒一个桶，更精确地显示倒计时
                int bucket = remaining; // 使用剩余秒数作为桶标识，实现每秒更新
                if (bucket != app->dialog_watchdog_last_logged_ && remaining <= DIALOG_IDLE_COUNTDOWN_SECONDS) {
                    ESP_LOGI(TAG, "dialog对话空闲倒计时剩余: %d 秒", remaining);// 打印剩余秒数
                    app->dialog_watchdog_last_logged_ = bucket;// 更新上次记录的日志时间为当前桶
                }
            }
        }
        app->dialog_watchdog_task_handle_ = nullptr;
        ESP_LOGI(TAG, "Dialog watchdog stopped");
        vTaskDelete(NULL);
    }, "dialog_watchdog", 4096, this, 5, &dialog_watchdog_task_handle_, 0);
}

// 停止对话看门狗
void Application::StopDialogWatchdog() {
    dialog_watchdog_running_ = false;

    if (dialog_watchdog_task_handle_ != nullptr) {
        vTaskDelete(dialog_watchdog_task_handle_);
        dialog_watchdog_task_handle_ = nullptr;
        ESP_LOGI(TAG, "Dialog watchdog stopped");
    }

    dialog_watchdog_last_logged_ = -1;
}

// 音频输出函数
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_);// 加锁保护音频队列

    // 调试日志：检查设备状态和音频队列
    ESP_LOGD(TAG, "🔊 OnAudioOutput called, device_state: %d, audio_queue_size: %zu, codec_output_enabled: %d", 
             device_state_, audio_decode_queue_.size(), codec->output_enabled());

    // 新增代码(小程序控制 暂停/继续播放 音频)
    // =========================================================
    // 🔧 暂停状态下停止从队列取数据，但保留队列状态
    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
        }
        ESP_LOGD(TAG, "🔊 音频队列为空");

        // 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 && listening_mode_ != kListeningModeRealtime) {
        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();
    // 在出队时捕获opus解码标志，避免background_task异步执行时标志已变化
    // 导致残留的Opus帧被当作PCM播放（产生杂音）
    bool is_opus_frame = opus_playback_active_.load();
    lock.unlock();

    background_task_->Schedule([this, codec, opus = std::move(opus), is_opus_frame]() mutable {
        if (aborted_) {
            return;
        }
        // 跳过已中止的HTTPS opus残留帧：出队时is_opus_frame=true，但中止后opus_playback_active_=false
        // 不能用https_playback_abort_判断，因为故事任务退出时会将其清为false，导致残留帧漏过
        if (is_opus_frame && !opus_playback_active_.load()) {
            return;
        }

        std::vector<int16_t> pcm;
        bool decoded = false;
        bool treat_as_pcm = (protocol_ && protocol_->downlink_is_pcm() && !is_opus_frame);
        if (!treat_as_pcm) {
            decoded = opus_decoder_->Decode(std::move(opus), pcm);
        }
        if (!decoded) {
            if (treat_as_pcm && !opus.empty() && (opus.size() % 2 == 0)) {
                pcm.resize(opus.size() / 2);
                memcpy(pcm.data(), opus.data(), opus.size());
                int srv = protocol_ ? protocol_->server_sample_rate() : 16000;
                if (!player_pipeline_) {
                    if (srv != codec->output_sample_rate()) {
                        output_resampler_.Configure(srv, 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);
                    }
                }
            } else {
                return;
            }
        }
        // Resample if the sample rate is different
        if (!treat_as_pcm && decoded && !player_pipeline_ && 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 (!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());

            // 当有可听见的音频输出时，更新最后声音输出时间戳
            const float audible_volume_threshold = 0.01f; // 设置一个合理的音量阈值
            if (rms_volume >= audible_volume_threshold) {
                this->last_audible_output_time_ = std::chrono::steady_clock::now();
                ESP_LOGD(TAG, "🔊 更新last_audible_output_time_，当前音量: %.4f", rms_volume);
            }

#if CONFIG_USE_AUDIO_PROCESSOR
            // 同步音量到音频处理器，用于动态阈值调整
            current_speaker_volume_ = rms_volume; // 保存当前音量供打断逻辑使用
            audio_processor_.SetSpeakerVolume(rms_volume);
#endif
        }

        int src_rate = decoded ? opus_decoder_->sample_rate() : (protocol_ ? protocol_->server_sample_rate() : 16000);
        static bool first_play_logged = false;
        if (player_pipeline_) {
            player_pipeline_set_src_rate(player_pipeline_, src_rate);
            int bytes = (int)(pcm.size() * sizeof(int16_t));
            ESP_LOGD(TAG, "写入播放管道: 采样率=%d 字节=%d", src_rate, bytes);
            AppendRefSamples(pcm.data(), (int)pcm.size());
            player_pipeline_write(player_pipeline_, (char*)pcm.data(), bytes);
            if (bytes > 0) {
                this->last_audible_output_time_ = std::chrono::steady_clock::now();
            }
            if (!first_play_logged && bytes > 0) {
                ESP_LOGI(TAG, "开始播放下行音频: 字节=%d 采样率=%d", bytes, src_rate);
                first_play_logged = true;
            }
        } else {
            ESP_LOGD(TAG, "直接输出PCM到编解码器: 样本=%zu", pcm.size());
            AppendRefSamples(pcm.data(), (int)pcm.size());
            codec->OutputData(pcm);// 直接输出PCM数据
            if (!pcm.empty()) {
                this->last_audible_output_time_ = std::chrono::steady_clock::now();
            }
            if (!first_play_logged && !pcm.empty()) {
                ESP_LOGI(TAG, "开始播放下行音频: 样本=%zu 采样率=%d", pcm.size(), src_rate);
                first_play_logged = true;
            }

            // 解决本地资源声音播放尖锐问题方案1
            // // 如果是单声道，转换为立体声
            //  if (codec->output_channels() == 2) {// 单声道转换为立体声
            //     std::vector<int16_t> stereo(pcm.size() * 2);// 立体声PCM数据
            //     for (size_t i = 0, j = 0; i < pcm.size(); ++i) {
            //         stereo[j++] = pcm[i];// 左声道
            //         stereo[j++] = pcm[i];// 右声道
            //     }
            //     codec->OutputData(stereo);// 输出立体声PCM数据
            // } else {
            //     codec->OutputData(pcm);// 输出单声道PCM数据
            // }

            // 解决本地资源声音播放尖锐问题方案2
            // player_pipeline_ = player_pipeline_open();// 打开音频播放管道
            // player_pipeline_run(player_pipeline_);// 启动音频播放管道
            // player_pipeline_set_src_rate(player_pipeline_, src_rate);// 设置播放管道源采样率
            // player_pipeline_write(player_pipeline_, (char*)pcm.data(), (int)(pcm.size() * sizeof(int16_t)));// 写入PCM数据到播放管道

        }
        last_output_time_ = std::chrono::steady_clock::now();// 更新最后输出时间
    });
}

void Application::OnAudioInput() {
    auto codec_for_guard = Board::GetInstance().GetAudioCodec();
    if (hibernating_.load() || !codec_for_guard || !codec_for_guard->input_enabled() || !recorder_pipeline_) {
        vTaskDelay(pdMS_TO_TICKS(20));
        return;
    }

    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());
        if (!data.empty()) {
            int n = (int)data.size();
            int64_t sum = 0;
            int peak = 0;
            for (int i = 0; i < n; ++i) {
                int v = data[i];
                int a = v < 0 ? -v : v;
                sum += a;
                if (a > peak) peak = a;
            }
            (void)sum;
            // if (avg > 150 || peak > 800) {
            //     ESP_LOGI(TAG, "🎙️ 输入幅度: 均值=%d 峰值=%d 样本=%d", avg, peak, n);
            // }
        }
        audio_processor_.Feed(data);
        return;
    }
#else
    if (device_state_ == kDeviceStateListening || device_state_ == kDeviceStateDialog) {
        if (send_g711a_uplink_) {
            ReadAudio(data, 16000, 20 * 16000 / 1000);
            if (!data.empty()) {
                std::vector<int16_t> resampled(uplink_resampler_.GetOutputSamples((int)data.size()));
                if (!resampled.empty()) {
                    uplink_resampler_.Process(data.data(), (int)data.size(), resampled.data());
                }
                int out_samples = (int)resampled.size();
                std::vector<uint8_t> bytes(out_samples);
                for (int i = 0; i < out_samples; ++i) {
                    int16_t s = resampled[i];
                    int sign = (s >> 8) & 0x80;
                    if (sign) s = -s;
                    if (s > 32635) s = 32635;
                    int exp = 7;
                    for (int mask = 0x4000; exp > 0 && (s & mask) == 0; mask >>= 1) exp--;
                    int mant = (s >> ((exp == 0) ? 4 : (exp + 3))) & 0x0F;
                    uint8_t a = (uint8_t)(sign | (exp << 4) | mant);
                    bytes[i] = (uint8_t)(a ^ 0xD5);
                }
                Schedule([this, bytes = std::move(bytes)]() {
                    if (protocol_ && protocol_->IsAudioChannelOpened()) {
                        protocol_->SendG711A(bytes);// 发送G711A音频数据
                    }
                });
            }
        } else if (send_pcm_uplink_) {
            ReadAudio(data, 16000, 20 * 16000 / 1000);
            if (!data.empty()) {
                std::vector<int16_t> resampled(uplink_resampler_.GetOutputSamples((int)data.size()));
                if (!resampled.empty()) {
                    uplink_resampler_.Process(data.data(), (int)data.size(), resampled.data());
                }
                std::vector<uint8_t> bytes(resampled.size() * sizeof(int16_t));
                for (size_t i = 0; i < resampled.size(); ++i) {
                    int16_t s = resampled[i];
                    bytes[i * 2] = (uint8_t)(s & 0xFF);
                    bytes[i * 2 + 1] = (uint8_t)((s >> 8) & 0xFF);
                }
                Schedule([this, bytes = std::move(bytes)]() {
                    if (protocol_ && protocol_->IsAudioChannelOpened() && (device_state_ == kDeviceStateListening || device_state_ == kDeviceStateDialog || (listening_mode_ == kListeningModeRealtime && device_state_ == kDeviceStateSpeaking))) {
                        protocol_->SendPcm(bytes);
                    }
                });
            }
        } else {
            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)]() {
                        if (protocol_) {
                            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 CONFIG_USE_AUDIO_PROCESSOR
    // 当音频处理器运行且存在参考通道时，保持原有双通道读取以支持AEC
    if (audio_processor_.IsRunning() && codec->input_channels() == 2) {
        if (codec->input_sample_rate() != sample_rate) {
            data.resize(samples * codec->input_sample_rate() / sample_rate);
            if (!codec->InputData(data)) {
                return;
            }
            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];
            }
            return;
        } else {
            data.resize(samples * codec->input_channels());
            if (!codec->InputData(data)) {
                return;
            }
            static bool first_equal_sr_dual_read_logged = false;
            if (!first_equal_sr_dual_read_logged) {
                ESP_LOGI(TAG, "AFE输入首包: 双通道等采样率 目标样本=%d 通道=%d 实际向量=%zu", samples, codec->input_channels(), data.size());
                first_equal_sr_dual_read_logged = true;
            }
            return;
        }
    }
#endif

    // 默认优先使用recorder管道读取（目标采样率16000），无参考通道需求
    if (recorder_pipeline_ && sample_rate == 16000) {
        int need_bytes = samples * (int)sizeof(int16_t);
        int default_bytes = recorder_pipeline_get_default_read_size(recorder_pipeline_);
        std::vector<int16_t> out;
        out.reserve(samples);// 预分配内存空间,避免后续动态扩容
        std::vector<char> buf(default_bytes);// 内存音频缓冲区,用于存储从录音管道读取的音频数据
        while ((int)out.size() < samples) {
            int to_read = std::min(default_bytes, (need_bytes - (int)out.size() * (int)sizeof(int16_t)));// 计算本次读取的字节数,不超过默认读取大小和剩余需要读取的字节数
            if (to_read <= 0) break;// 读取到的数据大小小于等于0,跳出循环
            int got = recorder_pipeline_read(recorder_pipeline_, buf.data(), to_read);// 从录音管道读取音频数据,并赋值给内存音频缓冲区
            if (got <= 0) {
                ESP_LOGW(TAG, "🎙️ 录音管道读取失败，未收到输入数据");
                break;
            }
            int got_samples = got / (int)sizeof(int16_t);// 计算本次读取的样本数,即读取到的字节数除以每个样本的字节数
            int16_t* p = (int16_t*)buf.data();// 将内存音频缓冲区转换为int16_t指针,方便按样本读取
            for (int i = 0; i < got_samples && (int)out.size() < samples; ++i) {
                out.push_back(p[i]);// 将读取到的样本添加到输出向量中,直到达到预期样本数或读取完所有数据
            }
        }
        if (!out.empty()) {
            data.assign(out.begin(), out.end());// 将输出向量中的数据赋值给输出参数data
            ApplyAEC(data);
            return;
        }
    }

    // 回退到直接从codec读取的实现
    if (codec->input_sample_rate() != sample_rate) {
        data.resize(samples * codec->input_sample_rate() / sample_rate);
        if (!codec->InputData(data)) {
            ESP_LOGW(TAG, "🎙️ 麦克风采样失败（重采样路径），未收到输入数据");
            return;
        }
        if (codec->input_channels() == 2) {
            // 双通道约定：当前缓冲按 [主麦,参考] 排列；必须与 ALGORITHM_INPUT_FORMAT 的 M/R 顺序
            // 以及 CHANNEL_FORMAT 的物理通道对应一致，否则可能导致 AEC 失效或增益反向
            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);
            ApplyAEC(data);
        }
    } else {
        data.resize(samples);
        if (!codec->InputData(data)) {
            ESP_LOGW(TAG, "🎙️ 麦克风采样失败（直读路径），未收到输入数据");
            return;
        }
        ApplyAEC(data);
    }
}

// 打断语音播报(终止播放)
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;

    // 中止HTTPS音频播放（如果正在进行）
    if (https_playback_active_.load()) {
        https_playback_abort_.store(true);
        ESP_LOGI(TAG, "🔴 HTTPS音频播放中止信号已发送");
    }

    // 🔧 更新安全操作时间戳
    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");

    // 重启codec输出以清空I2S DMA缓冲区中残留音频，确保扬声器立即静音
    // 移除output_enabled()守卫，确保始终执行flush
    if (background_task_) {
        background_task_->Schedule([this]() {
            auto codec = Board::GetInstance().GetAudioCodec();
            if (codec) {
                ESP_LOGI(TAG, "DMA flush: output_enabled=%d", codec->output_enabled());
                codec->EnableOutput(false);
                vTaskDelay(pdMS_TO_TICKS(10));
                codec->EnableOutput(true);
                ESP_LOGI(TAG, "🔇 音频输出已重置，DMA缓冲区已清空");
            }
        });
    }

    // 🔧 修复：始终尝试发送中止消息以打断RTC下行（不受IsSafeToOperate限制）
    if (protocol_) {
        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 - protocol_ is null");
    }

    // 🔧 确保中止窗口后恢复播放流程，避免长时间阻塞导致WS音频无法播放
    Schedule([this]() {
        vTaskDelay(pdMS_TO_TICKS(120));
        aborted_ = false;
        ESP_LOGI(TAG, "🔵 Abort window ended, resume playback tasks");
    });

    // 🔧 重置中止标志，允许后续操作
    is_aborting_.store(false);
}

// 中止HTTPS音频播放：清空队列、重置解码器、清除标志、DMA flush
void Application::AbortHttpsPlayback(const char* reason) {
    ESP_LOGI(TAG, "🔴 %s，中止HTTPS音频播放", reason);
    https_playback_abort_.store(true);
    {
        std::lock_guard<std::mutex> lock(mutex_);
        if (!audio_decode_queue_.empty()) {
            ESP_LOGI(TAG, "清空HTTPS音频队列，大小=%zu", audio_decode_queue_.size());
            audio_decode_queue_.clear();
        }
    }
    ResetDecoder();
    opus_playback_active_.store(false);
    https_playback_active_.store(false);
    ESP_LOGI(TAG, "🔴 HTTPS播放标志已清除，RTC音频通道已打开");
    // DMA flush：用独立任务立即清空I2S DMA缓冲区
    // 不能用background_task_，RTC音频lambda会持续占用它导致延迟数秒
    xTaskCreate([](void* arg) {
        auto codec = Board::GetInstance().GetAudioCodec();
        if (codec) {
            ESP_LOGI(TAG, "DMA flush: output_enabled=%d", codec->output_enabled());
            codec->EnableOutput(false);
            vTaskDelay(pdMS_TO_TICKS(10));
            codec->EnableOutput(true);
            ESP_LOGI(TAG, "🔇 音频输出已重置，DMA缓冲区已清空");
        }
        vTaskDelete(NULL);
    }, "dma_flush", 4096, NULL, 10, NULL);
}

// 通过HTTPS故事API请求并播放故事
void Application::SendStoryRequest() {
    HttpsApiPlayback(CONFIG_STORY_API_URL, "故事API", "story_play");
}

// 通过HTTPS音乐API请求并播放音乐
void Application::SendMusicRequest() {
    HttpsApiPlayback(CONFIG_MUSIC_API_URL, "音乐API", "music_play");
}

// HTTPS API音频播放通用实现（intro标题 + body正文无缝衔接）
// api_url: API基础地址（自动附加 ?mac_address=），tag: 日志标签，task_name: FreeRTOS任务名
struct HttpsApiParams {
    const char* api_url;
    const char* tag;
};

void Application::HttpsApiPlayback(const char* api_url_base, const char* tag, const char* task_name) {
    if (https_playback_active_.load() || https_playback_abort_.load() || opus_playback_active_.load()) {
        ESP_LOGW(TAG, "[%s] 已有音频正在播放或退出中，忽略本次请求", tag);
        return;
    }

    auto* params = new HttpsApiParams{api_url_base, tag};
    xTaskCreate([](void* arg) {
        auto* p = static_cast<HttpsApiParams*>(arg);
        const char* api_url_base = p->api_url;
        const char* tag = p->tag;
        delete p;

        auto& app = Application::GetInstance();
        // 先设置opus和abort标志（用于重复启动守卫和OnIncomingAudio阻断RTC PCM）
        // 注意：https_playback_active_ 延迟到intro音频入队前设置，
        // 这样HTTP请求期间（~500ms）残留的Bot subv消息不会触发OnBotMessage中止
        app.opus_playback_active_.store(true);
        app.https_playback_abort_.store(false);

        // base64 解码查找表
        static uint8_t b64_table[256] = {0};
        static bool b64_inited = false;
        if (!b64_inited) {
            const char* chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
            for (int c = 0; chars[c]; c++) {
                b64_table[(uint8_t)chars[c]] = (uint8_t)c;
            }
            b64_inited = true;
        }

        // ========== 步骤1: 请求API ==========
        std::string mac = SystemInfo::GetBleMacAddress();
        for (auto& c : mac) {
            if (c >= 'a' && c <= 'f') c -= 32;
        }

        char api_url[256];
        snprintf(api_url, sizeof(api_url), "%s?mac_address=%s",
                 api_url_base, mac.c_str());

        ESP_LOGI(TAG, "[%s] 请求: %s", tag, api_url);
        ESP_LOGI(TAG, "[%s] 空闲堆: %lu", tag, (unsigned long)esp_get_free_heap_size());

        esp_http_client_config_t api_config = {};
        api_config.url = api_url;
        api_config.method = HTTP_METHOD_GET;
        api_config.timeout_ms = 10000;
        api_config.buffer_size = 2048;
        api_config.buffer_size_tx = 512;

        esp_http_client_handle_t api_client = esp_http_client_init(&api_config);
        if (!api_client) {
            ESP_LOGE(TAG, "[%s] HTTP客户端初始化失败", tag);
            app.https_playback_active_.store(false);
            app.opus_playback_active_.store(false);
            vTaskDelete(NULL);
            return;
        }

        esp_err_t err = esp_http_client_open(api_client, 0);
        if (err != ESP_OK) {
            ESP_LOGE(TAG, "[%s] 连接失败: %s", tag, esp_err_to_name(err));
            esp_http_client_cleanup(api_client);
            app.https_playback_active_.store(false);
            app.opus_playback_active_.store(false);
            vTaskDelete(NULL);
            return;
        }

        esp_http_client_fetch_headers(api_client);
        int api_status = esp_http_client_get_status_code(api_client);
        if (api_status != 200) {
            ESP_LOGE(TAG, "[%s] 请求失败，状态码: %d", tag, api_status);
            esp_http_client_close(api_client);
            esp_http_client_cleanup(api_client);
            app.https_playback_active_.store(false);
            app.opus_playback_active_.store(false);
            vTaskDelete(NULL);
            return;
        }

        // 读取API响应（通常 < 10KB）
        std::string api_response;
        char buf[1024];
        int rlen;
        while ((rlen = esp_http_client_read(api_client, buf, sizeof(buf))) > 0) {
            api_response.append(buf, rlen);
        }
        esp_http_client_close(api_client);
        esp_http_client_cleanup(api_client);

        ESP_LOGI(TAG, "[%s] 响应: %d 字节", tag, (int)api_response.size());

        if (app.https_playback_abort_.load()) {
            app.https_playback_active_.store(false);
            app.https_playback_abort_.store(false);
            app.opus_playback_active_.store(false);
            ESP_LOGI(TAG, "[%s] HTTP请求阶段被中止", tag);
            vTaskDelete(NULL);
            return;
        }

        // 解析外层JSON
        cJSON* root = cJSON_Parse(api_response.c_str());
        api_response.clear();
        api_response.shrink_to_fit();

        if (!root) {
            ESP_LOGE(TAG, "[%s] JSON解析失败", tag);
            app.https_playback_active_.store(false);
            app.opus_playback_active_.store(false);
            vTaskDelete(NULL);
            return;
        }

        cJSON* code_item = cJSON_GetObjectItem(root, "code");
        if (!code_item || code_item->valueint != 0) {
            cJSON* msg_item = cJSON_GetObjectItem(root, "message");
            ESP_LOGE(TAG, "[%s] 服务端错误: %s", tag,
                     (msg_item && msg_item->valuestring) ? msg_item->valuestring : "unknown");
            cJSON_Delete(root);
            app.https_playback_active_.store(false);
            app.opus_playback_active_.store(false);
            vTaskDelete(NULL);
            return;
        }

        cJSON* data = cJSON_GetObjectItem(root, "data");
        cJSON* title_item = data ? cJSON_GetObjectItem(data, "title") : nullptr;
        cJSON* intro_str = data ? cJSON_GetObjectItem(data, "intro_opus_data") : nullptr;
        cJSON* opus_url_item = data ? cJSON_GetObjectItem(data, "opus_url") : nullptr;

        if (!intro_str || !cJSON_IsString(intro_str) || !intro_str->valuestring ||
            !opus_url_item || !cJSON_IsString(opus_url_item) || !opus_url_item->valuestring) {
            ESP_LOGE(TAG, "[%s] 缺少intro_opus_data或opus_url字段", tag);
            cJSON_Delete(root);
            app.https_playback_active_.store(false);
            app.opus_playback_active_.store(false);
            vTaskDelete(NULL);
            return;
        }

        ESP_LOGI(TAG, "[%s] 标题: %s", tag,
                 (title_item && title_item->valuestring) ? title_item->valuestring : "未知");

        // 提取字符串后释放外层JSON
        std::string intro_json_str = intro_str->valuestring;
        std::string opus_url = opus_url_item->valuestring;
        cJSON_Delete(root);

        // ========== 步骤2: 解析 intro_opus_data ==========
        cJSON* intro_root = cJSON_Parse(intro_json_str.c_str());
        intro_json_str.clear();
        intro_json_str.shrink_to_fit();

        if (!intro_root) {
            ESP_LOGE(TAG, "[%s] intro JSON解析失败", tag);
            app.https_playback_active_.store(false);
            app.opus_playback_active_.store(false);
            vTaskDelete(NULL);
            return;
        }

        cJSON* intro_sr = cJSON_GetObjectItem(intro_root, "sample_rate");
        cJSON* intro_fd = cJSON_GetObjectItem(intro_root, "frame_duration_ms");
        cJSON* intro_frames = cJSON_GetObjectItem(intro_root, "frames");

        if (!intro_frames || !cJSON_IsArray(intro_frames)) {
            ESP_LOGE(TAG, "[%s] intro缺少frames数组", tag);
            cJSON_Delete(intro_root);
            app.https_playback_active_.store(false);
            app.opus_playback_active_.store(false);
            vTaskDelete(NULL);
            return;
        }

        int sample_rate = (intro_sr && cJSON_IsNumber(intro_sr)) ? intro_sr->valueint : 16000;
        int frame_duration = (intro_fd && cJSON_IsNumber(intro_fd)) ? intro_fd->valueint : 60;
        int intro_count = cJSON_GetArraySize(intro_frames);

        ESP_LOGI(TAG, "[%s] intro: 采样率=%d, 帧时长=%dms, 帧数=%d (%.1f秒)", tag,
                 sample_rate, frame_duration, intro_count,
                 intro_count * frame_duration / 1000.0f);

        app.SetDecodeSampleRate(sample_rate, frame_duration);

        // 音频即将入队，现在激活播放标志，允许OnBotMessage中止
        app.https_playback_active_.store(true);

        // ========== 步骤3: 入队 intro frames ==========
        int enqueued = 0;
        int errors = 0;

        for (int i = 0; i < intro_count; i++) {
            if (app.https_playback_abort_.load()) break;

            cJSON* fi = cJSON_GetArrayItem(intro_frames, i);
            if (!fi || !cJSON_IsString(fi) || !fi->valuestring) { errors++; continue; }

            const char* b64 = fi->valuestring;
            size_t b64_len = strlen(b64);
            if (b64_len == 0) { errors++; continue; }

            size_t out_len = (b64_len * 3) / 4;
            if (b64_len >= 1 && b64[b64_len - 1] == '=') out_len--;
            if (b64_len >= 2 && b64[b64_len - 2] == '=') out_len--;

            std::vector<uint8_t> frame(out_len);
            size_t j = 0, k = 0;
            while (j < b64_len) {
                uint32_t a = b64_table[(uint8_t)b64[j++]];
                uint32_t b = (j < b64_len) ? b64_table[(uint8_t)b64[j++]] : 0;
                uint32_t c = (j < b64_len) ? b64_table[(uint8_t)b64[j++]] : 0;
                uint32_t d = (j < b64_len) ? b64_table[(uint8_t)b64[j++]] : 0;
                uint32_t triple = (a << 18) | (b << 12) | (c << 6) | d;
                if (k < out_len) frame[k++] = (triple >> 16) & 0xFF;
                if (k < out_len) frame[k++] = (triple >> 8) & 0xFF;
                if (k < out_len) frame[k++] = triple & 0xFF;
            }

            {
                std::lock_guard<std::mutex> lock(app.mutex_);
                app.audio_decode_queue_.emplace_back(std::move(frame));
            }
            enqueued++;

            // 队列节流
            while (!app.https_playback_abort_.load()) {
                size_t qs;
                { std::lock_guard<std::mutex> lock(app.mutex_); qs = app.audio_decode_queue_.size(); }
                if (qs < 50) break;
                vTaskDelay(pdMS_TO_TICKS(30));
            }
        }

        cJSON_Delete(intro_root);
        ESP_LOGI(TAG, "[%s] intro入队完成: %d帧, 错误: %d", tag, enqueued, errors);

        if (app.https_playback_abort_.load()) {
            ESP_LOGI(TAG, "[%s] intro阶段被中止", tag);
            app.https_playback_active_.store(false);
            app.https_playback_abort_.store(false);
            app.opus_playback_active_.store(false);
            vTaskDelete(NULL);
            return;
        }

        // ========== 步骤4: 下载 opus_url 正文 ==========
        ESP_LOGI(TAG, "[%s] 开始下载正文: %s", tag, opus_url.c_str());

        esp_http_client_config_t opus_config = {};
        opus_config.url = opus_url.c_str();
        opus_config.method = HTTP_METHOD_GET;
        opus_config.transport_type = HTTP_TRANSPORT_OVER_SSL;
        opus_config.timeout_ms = 15000;
        opus_config.buffer_size = 2048;
        opus_config.buffer_size_tx = 512;
#ifdef CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
        opus_config.crt_bundle_attach = esp_crt_bundle_attach;
#endif

        esp_http_client_handle_t opus_client = esp_http_client_init(&opus_config);
        if (!opus_client) {
            ESP_LOGE(TAG, "[%s] opus HTTP初始化失败", tag);
            app.https_playback_active_.store(false);
            app.https_playback_abort_.store(false);
            app.opus_playback_active_.store(false);
            vTaskDelete(NULL);
            return;
        }

        err = esp_http_client_open(opus_client, 0);
        if (err != ESP_OK) {
            ESP_LOGE(TAG, "[%s] opus连接失败: %s", tag, esp_err_to_name(err));
            esp_http_client_cleanup(opus_client);
            app.https_playback_active_.store(false);
            app.https_playback_abort_.store(false);
            app.opus_playback_active_.store(false);
            vTaskDelete(NULL);
            return;
        }

        int64_t opus_content_len = esp_http_client_fetch_headers(opus_client);
        int opus_status = esp_http_client_get_status_code(opus_client);
        ESP_LOGI(TAG, "[%s] opus状态码: %d, 长度: %lld", tag, opus_status, (long long)opus_content_len);

        if (opus_status != 200) {
            ESP_LOGE(TAG, "[%s] opus请求失败，状态码: %d", tag, opus_status);
            esp_http_client_close(opus_client);
            esp_http_client_cleanup(opus_client);
            app.https_playback_active_.store(false);
            app.https_playback_abort_.store(false);
            app.opus_playback_active_.store(false);
            vTaskDelete(NULL);
            return;
        }

        std::string opus_json;
        if (opus_content_len > 0) opus_json.reserve(opus_content_len);
        int total_read = 0;
        while ((rlen = esp_http_client_read(opus_client, buf, sizeof(buf))) > 0) {
            if (app.https_playback_abort_.load()) break;
            opus_json.append(buf, rlen);
            total_read += rlen;
        }
        esp_http_client_close(opus_client);
        esp_http_client_cleanup(opus_client);

        ESP_LOGI(TAG, "[%s] opus下载完成: %d 字节, 堆: %lu", tag,
                 total_read, (unsigned long)esp_get_free_heap_size());

        if (app.https_playback_abort_.load()) {
            ESP_LOGI(TAG, "[%s] opus下载被中止", tag);
            app.https_playback_active_.store(false);
            app.https_playback_abort_.store(false);
            app.opus_playback_active_.store(false);
            vTaskDelete(NULL);
            return;
        }

        // ========== 步骤5: 解析并入队 body frames ==========
        cJSON* opus_root = cJSON_Parse(opus_json.c_str());
        opus_json.clear();
        opus_json.shrink_to_fit();
        ESP_LOGI(TAG, "[%s] opus JSON已释放, 堆: %lu", tag, (unsigned long)esp_get_free_heap_size());

        if (!opus_root) {
            ESP_LOGE(TAG, "[%s] opus JSON解析失败", tag);
            app.https_playback_active_.store(false);
            app.https_playback_abort_.store(false);
            app.opus_playback_active_.store(false);
            vTaskDelete(NULL);
            return;
        }

        cJSON* body_frames = cJSON_GetObjectItem(opus_root, "frames");
        if (!body_frames || !cJSON_IsArray(body_frames)) {
            ESP_LOGE(TAG, "[%s] opus缺少frames数组", tag);
            cJSON_Delete(opus_root);
            app.https_playback_active_.store(false);
            app.https_playback_abort_.store(false);
            app.opus_playback_active_.store(false);
            vTaskDelete(NULL);
            return;
        }

        // 检查body采样率是否与intro不同
        cJSON* body_sr = cJSON_GetObjectItem(opus_root, "sample_rate");
        cJSON* body_fd = cJSON_GetObjectItem(opus_root, "frame_duration_ms");
        int body_sample_rate = (body_sr && cJSON_IsNumber(body_sr)) ? body_sr->valueint : sample_rate;
        int body_frame_duration = (body_fd && cJSON_IsNumber(body_fd)) ? body_fd->valueint : frame_duration;
        int body_count = cJSON_GetArraySize(body_frames);

        ESP_LOGI(TAG, "[%s] body: 采样率=%d, 帧时长=%dms, 帧数=%d (%.1f秒)", tag,
                 body_sample_rate, body_frame_duration, body_count,
                 body_count * body_frame_duration / 1000.0f);

        if (body_sample_rate != sample_rate || body_frame_duration != frame_duration) {
            app.SetDecodeSampleRate(body_sample_rate, body_frame_duration);
        }

        int body_enqueued = 0;
        int body_errors = 0;

        for (int i = 0; i < body_count; i++) {
            if (app.https_playback_abort_.load()) {
                ESP_LOGI(TAG, "[%s] body入队中止: %d/%d", tag, body_enqueued, body_count);
                break;
            }

            cJSON* fi = cJSON_GetArrayItem(body_frames, i);
            if (!fi || !cJSON_IsString(fi) || !fi->valuestring) { body_errors++; continue; }

            const char* b64 = fi->valuestring;
            size_t b64_len = strlen(b64);
            if (b64_len == 0) { body_errors++; continue; }

            size_t out_len = (b64_len * 3) / 4;
            if (b64_len >= 1 && b64[b64_len - 1] == '=') out_len--;
            if (b64_len >= 2 && b64[b64_len - 2] == '=') out_len--;

            std::vector<uint8_t> frame(out_len);
            size_t j = 0, k = 0;
            while (j < b64_len) {
                uint32_t a = b64_table[(uint8_t)b64[j++]];
                uint32_t b = (j < b64_len) ? b64_table[(uint8_t)b64[j++]] : 0;
                uint32_t c = (j < b64_len) ? b64_table[(uint8_t)b64[j++]] : 0;
                uint32_t d = (j < b64_len) ? b64_table[(uint8_t)b64[j++]] : 0;
                uint32_t triple = (a << 18) | (b << 12) | (c << 6) | d;
                if (k < out_len) frame[k++] = (triple >> 16) & 0xFF;
                if (k < out_len) frame[k++] = (triple >> 8) & 0xFF;
                if (k < out_len) frame[k++] = triple & 0xFF;
            }

            {
                std::lock_guard<std::mutex> lock(app.mutex_);
                app.audio_decode_queue_.emplace_back(std::move(frame));
            }
            body_enqueued++;

            // 队列节流
            while (!app.https_playback_abort_.load()) {
                size_t qs;
                { std::lock_guard<std::mutex> lock(app.mutex_); qs = app.audio_decode_queue_.size(); }
                if (qs < 50) break;
                vTaskDelay(pdMS_TO_TICKS(30));
            }

            // 每100帧打印进度
            if (body_enqueued % 100 == 0) {
                size_t qs;
                { std::lock_guard<std::mutex> lock(app.mutex_); qs = app.audio_decode_queue_.size(); }
                ESP_LOGI(TAG, "[%s] body进度: %d/%d (%.0f%%), 队列: %zu, 堆: %lu", tag,
                         body_enqueued, body_count,
                         body_enqueued * 100.0f / body_count, qs,
                         (unsigned long)esp_get_free_heap_size());
            }
        }

        cJSON_Delete(opus_root);
        ESP_LOGI(TAG, "[%s] body入队完成: %d帧, 错误: %d", tag, body_enqueued, body_errors);

        // ========== 步骤6: 等待播放完毕 ==========
        if (!app.https_playback_abort_.load()) {
            ESP_LOGI(TAG, "[%s] 全部入队完成，等待播放完毕...", tag);
            while (!app.https_playback_abort_.load()) {
                size_t qs;
                { std::lock_guard<std::mutex> lock(app.mutex_); qs = app.audio_decode_queue_.size(); }
                if (qs == 0) break;
                vTaskDelay(pdMS_TO_TICKS(100));
            }
        }

        bool was_aborted = app.https_playback_abort_.load();
        app.https_playback_active_.store(false);
        app.https_playback_abort_.store(false);
        app.opus_playback_active_.store(false);
        ESP_LOGI(TAG, "[%s] 播放结束, aborted=%d, 堆: %lu", tag,
                 was_aborted, (unsigned long)esp_get_free_heap_size());
        vTaskDelete(NULL);

    }, task_name, 10240, params, 5, NULL);
}

// 通过HTTPS下载JSON并流式播放音频（故事/歌曲等）
void Application::HttpsPlaybackFromUrl(const std::string& url) {
    // 防止重复启动：opus_playback_active_ 在任务启动时设置，覆盖HTTP请求阶段
    if (https_playback_active_.load() || https_playback_abort_.load() || opus_playback_active_.load()) {
        ESP_LOGW(TAG, "[HTTPS播放] 已有音频正在播放或退出中，忽略本次请求");
        return;
    }

    // 在独立任务中执行，避免阻塞调用线程
    std::string url_copy = url;
    xTaskCreate([](void* arg) {
        std::string* url_ptr = static_cast<std::string*>(arg);
        std::string playback_url = std::move(*url_ptr);
        delete url_ptr;

        auto& app = Application::GetInstance();
        // 先设置opus和abort标志（用于重复启动守卫和OnIncomingAudio阻断RTC PCM）
        // https_playback_active_ 延迟到音频入队前设置，防止残留subv触发OnBotMessage
        app.opus_playback_active_.store(true);
        app.https_playback_abort_.store(false);

        ESP_LOGI(TAG, "[HTTPS播放] 开始下载: %s", playback_url.c_str());
        ESP_LOGI(TAG, "[HTTPS播放] 空闲堆内存: %lu 字节", (unsigned long)esp_get_free_heap_size());

        // 配置HTTP客户端
        esp_http_client_config_t config = {};
        config.url = playback_url.c_str();
        config.method = HTTP_METHOD_GET;
        config.transport_type = HTTP_TRANSPORT_OVER_SSL;
        config.timeout_ms = 15000;
        config.buffer_size = 2048;  // 接收缓冲区（节省内存）
        config.buffer_size_tx = 512;
#ifdef CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
        config.crt_bundle_attach = esp_crt_bundle_attach;
#endif

        esp_http_client_handle_t client = esp_http_client_init(&config);
        if (!client) {
            ESP_LOGE(TAG, "[HTTPS播放] HTTP客户端初始化失败");
            app.https_playback_active_.store(false);
            app.opus_playback_active_.store(false);
            vTaskDelete(NULL);
            return;
        }

        esp_err_t err = esp_http_client_open(client, 0);
        if (err != ESP_OK) {
            ESP_LOGE(TAG, "[HTTPS播放] HTTP连接失败: %s", esp_err_to_name(err));
            esp_http_client_cleanup(client);
            app.https_playback_active_.store(false);
            app.opus_playback_active_.store(false);
            vTaskDelete(NULL);
            return;
        }

        int64_t content_length = esp_http_client_fetch_headers(client);
        int status_code = esp_http_client_get_status_code(client);
        ESP_LOGI(TAG, "[HTTPS播放] HTTP状态码: %d, 内容长度: %lld", status_code, (long long)content_length);

        if (status_code != 200) {
            ESP_LOGE(TAG, "[HTTPS播放] HTTP请求失败，状态码: %d", status_code);
            esp_http_client_close(client);
            esp_http_client_cleanup(client);
            app.https_playback_active_.store(false);
            app.opus_playback_active_.store(false);
            vTaskDelete(NULL);
            return;
        }

        // 流式读取整个JSON（必须完整读取才能解析frames数组）
        // 但使用分块读取减少单次分配峰值
        std::string json_data;
        if (content_length > 0) {
            json_data.reserve(content_length);
        }
        char read_buf[2048];
        int read_len;
        int total_read = 0;
        while ((read_len = esp_http_client_read(client, read_buf, sizeof(read_buf))) > 0) {
            if (app.https_playback_abort_.load()) {
                ESP_LOGI(TAG, "[HTTPS播放] 下载被中止");
                break;
            }
            json_data.append(read_buf, read_len);
            total_read += read_len;
        }

        // 关闭HTTP连接，释放TLS资源
        esp_http_client_close(client);
        esp_http_client_cleanup(client);
        ESP_LOGI(TAG, "[HTTPS播放] 下载完成: %d 字节, 堆剩余: %lu",
                 total_read, (unsigned long)esp_get_free_heap_size());

        if (app.https_playback_abort_.load()) {
            ESP_LOGI(TAG, "[HTTPS播放] 播放已取消，释放资源");
            app.https_playback_active_.store(false);
            app.https_playback_abort_.store(false);
            app.opus_playback_active_.store(false);
            vTaskDelete(NULL);
            return;
        }

        // 解析JSON
        cJSON* root = cJSON_Parse(json_data.c_str());
        // 解析完成后立即释放原始JSON字符串
        json_data.clear();
        json_data.shrink_to_fit();
        ESP_LOGI(TAG, "[HTTPS播放] JSON字符串已释放, 堆剩余: %lu",
                 (unsigned long)esp_get_free_heap_size());

        if (!root) {
            ESP_LOGE(TAG, "[HTTPS播放] JSON解析失败");
            app.https_playback_active_.store(false);
            app.opus_playback_active_.store(false);
            vTaskDelete(NULL);
            return;
        }

        // 读取音频参数
        cJSON* sample_rate_item = cJSON_GetObjectItem(root, "sample_rate");
        cJSON* channels_item = cJSON_GetObjectItem(root, "channels");
        cJSON* frame_duration_item = cJSON_GetObjectItem(root, "frame_duration_ms");
        cJSON* frames_array = cJSON_GetObjectItem(root, "frames");

        if (!frames_array || !cJSON_IsArray(frames_array)) {
            ESP_LOGE(TAG, "[HTTPS播放] JSON中缺少frames数组");
            cJSON_Delete(root);
            app.https_playback_active_.store(false);
            app.opus_playback_active_.store(false);
            vTaskDelete(NULL);
            return;
        }

        int sample_rate = (sample_rate_item && cJSON_IsNumber(sample_rate_item)) ? sample_rate_item->valueint : 16000;
        int channels = (channels_item && cJSON_IsNumber(channels_item)) ? channels_item->valueint : 1;
        int frame_duration = (frame_duration_item && cJSON_IsNumber(frame_duration_item)) ? frame_duration_item->valueint : 60;
        int frame_count = cJSON_GetArraySize(frames_array);

        ESP_LOGI(TAG, "[HTTPS播放] 音频参数: 采样率=%d, 通道=%d, 帧时长=%dms, 总帧数=%d",
                 sample_rate, channels, frame_duration, frame_count);
        ESP_LOGI(TAG, "[HTTPS播放] 预计时长: %.1f 秒", frame_count * frame_duration / 1000.0f);

        // 设置解码器采样率（复用现有Opus解码器）
        app.SetDecodeSampleRate(sample_rate, frame_duration);

        // 音频即将入队，现在激活播放标志，允许OnBotMessage中止
        app.https_playback_active_.store(true);

        // 逐帧base64解码并入队播放
        int enqueued = 0;
        int decode_errors = 0;

        // base64 解码查找表（C++ 兼容初始化）
        static uint8_t b64_table[256] = {0};
        static bool b64_inited = false;
        if (!b64_inited) {
            const char* chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
            for (int c = 0; chars[c]; c++) {
                b64_table[(uint8_t)chars[c]] = (uint8_t)c;
            }
            b64_inited = true;
        }

        for (int i = 0; i < frame_count; i++) {
            if (app.https_playback_abort_.load()) {
                ESP_LOGI(TAG, "[HTTPS播放] 播放中止，已入队 %d/%d 帧", enqueued, frame_count);
                break;
            }

            cJSON* frame_item = cJSON_GetArrayItem(frames_array, i);
            if (!frame_item || !cJSON_IsString(frame_item) || !frame_item->valuestring) {
                decode_errors++;
                continue;
            }

            const char* b64 = frame_item->valuestring;
            size_t b64_len = strlen(b64);
            if (b64_len == 0) {
                decode_errors++;
                continue;
            }

            // base64 解码
            size_t out_len = (b64_len * 3) / 4;
            if (b64_len >= 1 && b64[b64_len - 1] == '=') out_len--;
            if (b64_len >= 2 && b64[b64_len - 2] == '=') out_len--;

            std::vector<uint8_t> opus_frame(out_len);
            size_t j = 0, k = 0;
            while (j < b64_len) {
                uint32_t sextet_a = (j < b64_len) ? b64_table[(uint8_t)b64[j++]] : 0;
                uint32_t sextet_b = (j < b64_len) ? b64_table[(uint8_t)b64[j++]] : 0;
                uint32_t sextet_c = (j < b64_len) ? b64_table[(uint8_t)b64[j++]] : 0;
                uint32_t sextet_d = (j < b64_len) ? b64_table[(uint8_t)b64[j++]] : 0;
                uint32_t triple = (sextet_a << 18) | (sextet_b << 12) | (sextet_c << 6) | sextet_d;
                if (k < out_len) opus_frame[k++] = (triple >> 16) & 0xFF;
                if (k < out_len) opus_frame[k++] = (triple >> 8) & 0xFF;
                if (k < out_len) opus_frame[k++] = triple & 0xFF;
            }

            // 入队到音频解码队列（和WebSocket入队方式完全一致）
            {
                std::lock_guard<std::mutex> lock(app.mutex_);
                app.audio_decode_queue_.emplace_back(std::move(opus_frame));
            }
            enqueued++;

            // 控制入队速度：队列过大时等待消费，避免内存堆积
            // 每帧60ms，队列超过50帧（3秒缓冲）时等待
            while (!app.https_playback_abort_.load()) {
                size_t queue_size;
                {
                    std::lock_guard<std::mutex> lock(app.mutex_);
                    queue_size = app.audio_decode_queue_.size();
                }
                if (queue_size < 50) break;
                vTaskDelay(pdMS_TO_TICKS(30));  // 等待消费
            }

            // 每100帧打印一次进度
            if (enqueued % 100 == 0) {
                size_t queue_size;
                {
                    std::lock_guard<std::mutex> lock(app.mutex_);
                    queue_size = app.audio_decode_queue_.size();
                }
                ESP_LOGI(TAG, "[HTTPS播放] 进度: %d/%d 帧 (%.0f%%), 队列: %zu, 堆: %lu",
                         enqueued, frame_count, enqueued * 100.0f / frame_count,
                         queue_size, (unsigned long)esp_get_free_heap_size());
            }
        }

        // 释放cJSON解析树
        cJSON_Delete(root);
        ESP_LOGI(TAG, "[HTTPS播放] JSON解析树已释放, 堆剩余: %lu",
                 (unsigned long)esp_get_free_heap_size());

        if (app.https_playback_abort_.load()) {
            ESP_LOGI(TAG, "[HTTPS播放] 播放被用户中止，入队 %d 帧，解码错误 %d",
                     enqueued, decode_errors);
        } else {
            ESP_LOGI(TAG, "[HTTPS播放] 全部入队完成: %d 帧，解码错误 %d，等待播放完毕...",
                     enqueued, decode_errors);
        }

        // 等待队列播放完毕（或被中止）
        while (!app.https_playback_abort_.load()) {
            size_t queue_size;
            {
                std::lock_guard<std::mutex> lock(app.mutex_);
                queue_size = app.audio_decode_queue_.size();
            }
            if (queue_size == 0) break;
            vTaskDelay(pdMS_TO_TICKS(100));
        }

        app.https_playback_active_.store(false);
        app.https_playback_abort_.store(false);
        app.opus_playback_active_.store(false);
        ESP_LOGI(TAG, "[HTTPS播放] 播放结束, 最终堆剩余: %lu",
                 (unsigned long)esp_get_free_heap_size());
        vTaskDelete(NULL);
    }, "https_play", 8192, new std::string(url_copy), 5, NULL);
}

// 设置监听模式
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;// 设置设备状态
    if (state == kDeviceStateDialog) {
        StartDialogWatchdog();
    } else if (previous_state == kDeviceStateDialog) {
        StopDialogWatchdog();
    }
    ESP_LOGI(TAG, "打印设置设备状态日志: %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();

    // 检查是否正在进行BLE配网，配网时禁止播放待命音效(新增代码)
    // =================================================================
    bool is_ble_provisioning = false;
    if (Board::GetInstance().GetBoardType() == "wifi") {
        auto& wifi_board = static_cast<WifiBoard&>(Board::GetInstance());
        is_ble_provisioning = wifi_board.IsBleProvisioningActive();
    }
    // =================================================================

    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_ble_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, "RTC_Test") == 0){
                        PlaySound(Lang::Sounds::P3_LALA_DAIMING);
                    }
            }
            //=====================================================================================

#if CONFIG_USE_AUDIO_PROCESSOR
            audio_processor_.Stop();
#endif
#if 1
            if (recorder_pipeline_) {
                recorder_pipeline_close(recorder_pipeline_);
                recorder_pipeline_ = nullptr;
            }
#endif
#if CONFIG_USE_WAKE_WORD_DETECT || CONFIG_USE_CUSTOM_WAKE_WORD
            wake_word_detect_.Start();
#endif
            // 设备开机后首次进入idle状态时，自动检测NVS中的位置并调用API后设置当前位置
            if (!first_idle_location_checked_) {
                first_idle_location_checked_ = true;// 首次查询城市天气
                Schedule([]() {
                    AutoDetectAndSetLocation();// 自动检测并设置当前位置
                });
            }
            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
                    if (!recorder_pipeline_) {
                        recorder_pipeline_ = recorder_pipeline_open();
                        recorder_pipeline_run(recorder_pipeline_);
                    }
                } 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;
        case kDeviceStateDialog:
            display->SetStatus(Lang::Strings::SPEAKING);
#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
            {
                auto codec2 = Board::GetInstance().GetAudioCodec();// 获取音频编解码器
                codec2->EnableOutput(true);// 启用音频输出
                last_audible_output_time_ = std::chrono::steady_clock::now();// 更新最后有声音输出的时间
            }
            if (!recorder_pipeline_) {
                recorder_pipeline_ = recorder_pipeline_open();
                recorder_pipeline_run(recorder_pipeline_);
            }
            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;
    }

    if (hibernating_.load()) {
        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, "RTC_Test") == 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");

            // 🔧 修复：STATE:，确保硬件状态正确
            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, "RTC_Test") == 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, "RTC_Test") == 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::ClearDialogIdleSkipSession() {
    // 清除内存中的跳过标志
    skip_dialog_idle_session_ = false;

    // 清除NVS中的标志
    Settings sys("system", true);
    sys.SetInt("reboot_dlg_idle", 0);
    sys.SetInt("reboot_origin", 0);
    sys.Commit();
    ESP_LOGI(TAG, "跳过对话待机会话标志已清除");
}

void Application::SetDialogUploadEnabled(bool enabled) {
    dialog_upload_enabled_ = enabled;
    ESP_LOGI(TAG, "对话上传状态已设置为: %s", enabled ? "启用" : "禁用");
}

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();// 获取低电量过渡状态
}

// 🌐 初始化WebSocket协议（RTC连接成功后调用）
void Application::InitializeWebsocketProtocol() {
    ESP_LOGI(TAG, "🌐 开始初始化WebSocket协议...");
    
    // 检查是否已经初始化过
    if (websocket_protocol_) {
        ESP_LOGW(TAG, "⚠️ WebSocket协议已经初始化，跳过重复初始化");
        return;
    }
    
    // 创建WebsocketProtocol实例
    ESP_LOGI(TAG, "🔧 创建WebsocketProtocol实例");
    websocket_protocol_ = std::make_unique<WebsocketProtocol>();
    websocket_protocol_->SetPrimary(false);
    websocket_protocol_->OnIncomingAudio([this](std::vector<uint8_t>&& data) {
        if (!ws_downlink_enabled_.load()) {
            return;
        }
        opus_playback_active_.store(true);
        std::lock_guard<std::mutex> lock(mutex_);
        size_t len = data.size();
        audio_decode_queue_.emplace_back(std::move(data));
        ESP_LOGD(TAG, "WS辅助音频入队: 字节=%zu 队列大小=%zu", len, audio_decode_queue_.size());
    });
    websocket_protocol_->OnIncomingJson([this](const cJSON* root) {
        auto type = cJSON_GetObjectItem(root, "type");
        if (type && cJSON_IsString(type) && type->valuestring) {
            ESP_LOGD(TAG, "WS辅助JSON消息: %s", type->valuestring);
            if (strcmp(type->valuestring, "hello") == 0) {
                auto audio_params = cJSON_GetObjectItem(root, "audio_params");
                if (audio_params && cJSON_IsObject(audio_params)) {
                    auto sr = cJSON_GetObjectItem(audio_params, "sample_rate");
                    auto fd = cJSON_GetObjectItem(audio_params, "frame_duration");
                    int sample_rate = (sr && cJSON_IsNumber(sr)) ? sr->valueint : 16000;
                    int frame_duration = (fd && cJSON_IsNumber(fd)) ? fd->valueint : 60;
                    SetDecodeSampleRate(sample_rate, frame_duration);
                } else {
                    SetDecodeSampleRate(16000, 60);
                }
            }
        }
    });
    
    // 启动WebSocket协议
    ESP_LOGI(TAG, "🚀 启动WebSocket协议");
    websocket_protocol_->Start();// 启动WebSocket协议
    
    ESP_LOGI(TAG, "✅ WebSocket协议初始化完成");
}

// void Application::SendTextViaWebsocket(const std::string& text) {
//     Schedule([this, text]() {
//         if (websocket_protocol_ && websocket_protocol_->IsAudioChannelOpened()) {
//             websocket_protocol_->SendTextMessage(text);
//             ESP_LOGI(TAG, "WS辅助文本发送：%s", text.c_str());
//         } else {
//             ESP_LOGW(TAG, "WS辅助未连接，丢弃文本：%s", text.c_str());
//         }
//     });
// }

// ============================================================
// BLE JSON 通讯服务集成
// ============================================================

void Application::SaveIdleCyclesToNvs() {
    Settings s("hibernate", true);
    s.SetInt("idle_cycles", idle_cycles_);
}

void Application::LoadIdleCyclesFromNvs() {
    Settings s("hibernate", false);
    idle_cycles_ = s.GetInt("idle_cycles", 0);
}

void Application::ResetIdleCyclesNvs() {
    Settings s("hibernate", true);
    s.SetInt("idle_cycles", 0);
    idle_cycles_ = 0;
}

void Application::EnterIdleHibernate() {
    if (hibernating_.load()) {
        ESP_LOGW(TAG, "已处于休眠状态，跳过重复进入");
        return;
    }
    ESP_LOGI(TAG, "🌙 进入空闲休眠：stop RTC（保留 handle）→ 播待命音 → 静默");

    auto codec = Board::GetInstance().GetAudioCodec();
    hibernating_.store(true);
    vTaskDelay(pdMS_TO_TICKS(50));

    if (protocol_) {
        protocol_->LeaveRoom(/*notify_closed=*/false);
    }

    if (background_task_) {
        background_task_->WaitForCompletion();
    }

    {
        std::lock_guard<std::mutex> lock(mutex_);
        if (!audio_decode_queue_.empty()) {
            ESP_LOGI(TAG, "EnterIdleHibernate: 清空残留音频队列 size=%zu", audio_decode_queue_.size());
            audio_decode_queue_.clear();
        }
    }

    if (codec) {
        ESP_LOGI(TAG, "EnterIdleHibernate: 关闭 codec 麦克风（output 保留播待命音）");
        codec->EnableInput(false);
    }
    if (recorder_pipeline_) {
        recorder_pipeline_close(recorder_pipeline_);
        recorder_pipeline_ = nullptr;
    }

    esp_pm_config_t pm_config = {
        .max_freq_mhz = 240,
        .min_freq_mhz = 240,
        .light_sleep_enable = false,
    };
    esp_pm_configure(&pm_config);
    ESP_LOGI(TAG, "EnterIdleHibernate: 已禁用 Light Sleep，保护 I2C/codec 总线");

    if (codec) {
        ESP_LOGI(TAG, "EnterIdleHibernate: 重启 codec output 通道并灌静音");
        codec->EnableOutput(false);
        vTaskDelay(pdMS_TO_TICKS(20));
        codec->EnableOutput(true);
        const int silence_samples = codec->output_sample_rate() / 5;
        std::vector<int16_t> silence(silence_samples, 0);
        codec->OutputData(silence);
    }

    SetDeviceState(kDeviceStateIdle);

    ESP_LOGI(TAG, "EnterIdleHibernate: 等待待命音播放完成...");
    WaitForAudioPlayback();
    if (background_task_) {
        background_task_->WaitForCompletion();
    }
    vTaskDelay(pdMS_TO_TICKS(1000));

    if (player_pipeline_) {
        player_pipeline_close(player_pipeline_);
        player_pipeline_ = nullptr;
        ESP_LOGI(TAG, "EnterIdleHibernate: player_pipeline 已关闭");
    } else if (codec) {
        codec->EnableOutput(false);
        ESP_LOGI(TAG, "EnterIdleHibernate: codec output 已关闭");
    }

    idle_cycles_++;
    SaveIdleCyclesToNvs();
    ESP_LOGI(TAG, "✓ 已进入空闲休眠（累计第 %d 次，rtc_handle 保留）", idle_cycles_);
    if (idle_cycles_ >= IDLE_CYCLES_REBOOT_THRESHOLD) {
        ESP_LOGW(TAG, "累计休眠 %d 次，下次唤醒将硬重启清理内存碎片", idle_cycles_);
    }
}

void Application::WakeFromHibernate() {
    if (!hibernating_.load()) {
        return;
    }
    ESP_LOGI(TAG, "☀ 从空闲休眠唤醒，准备重连 RTC");

    if (idle_cycles_ >= IDLE_CYCLES_REBOOT_THRESHOLD) {
        ESP_LOGI(TAG, "累计休眠 %d 次，硬重启清理内存碎片", idle_cycles_);
        ResetIdleCyclesNvs();
        Board::GetInstance().OnBeforeRestart();
        vTaskDelay(pdMS_TO_TICKS(500));
        esp_restart();
        return;
    }

    hibernating_.store(false);

    if (device_state_ != kDeviceStateIdle) {
        SetDeviceState(kDeviceStateIdle);
    }

    ESP_LOGI(TAG, "WakeFromHibernate: 调用 ToggleChatState() 触发 RTC 重连");
    ToggleChatState();
    ESP_LOGI(TAG, "✓ 唤醒完成，已触发 RTC 重连");
}

const char* Application::DeviceStateToString(DeviceState state) {
    int idx = static_cast<int>(state);
    if (idx >= 0 && idx < static_cast<int>(sizeof(STATE_STRINGS) / sizeof(STATE_STRINGS[0]))) {
        return STATE_STRINGS[idx];
    }
    return "unknown";
}

// BLE JSON Service 命令处理（暂不使用，保留代码）
#if 0
void Application::HandleBleJsonCommand(const std::string& cmd, int msg_id, cJSON* data, BleJsonService& service) {
    auto& board = Board::GetInstance();

    if (cmd == "ping") {
        service.SendResponse(cmd, msg_id, 0, "pong");
        return;
    }

    if (cmd == "status") {
        cJSON* resp = cJSON_CreateObject();
        cJSON_AddStringToObject(resp, "s", DeviceStateToString(device_state_));
        int battery_level = 0;
        bool charging = false, discharging = false;
        if (board.GetBatteryLevel(battery_level, charging, discharging)) {
            cJSON_AddNumberToObject(resp, "bat", battery_level);
            cJSON_AddBoolToObject(resp, "chg", charging);
        }
        auto* codec = board.GetAudioCodec();
        if (codec) {
            cJSON_AddNumberToObject(resp, "vol", codec->output_volume());
        }
        wifi_ap_record_t ap{};
        if (esp_wifi_sta_get_ap_info(&ap) == ESP_OK) {
            cJSON_AddStringToObject(resp, "ssid", reinterpret_cast<const char*>(ap.ssid));
            cJSON_AddNumberToObject(resp, "rssi", ap.rssi);
        }
        service.SendResponse(cmd, msg_id, 0, "ok", resp);
        cJSON_Delete(resp);
        return;
    }

    if (cmd == "dev_info") {
        cJSON* resp = cJSON_CreateObject();
        cJSON_AddStringToObject(resp, "mac", SystemInfo::GetMacAddress().c_str());
        cJSON_AddStringToObject(resp, "board", BOARD_NAME);
        auto app_desc = esp_app_get_description();
        cJSON_AddStringToObject(resp, "fw", app_desc->version);
        cJSON_AddStringToObject(resp, "chip", SystemInfo::GetChipModelName().c_str());
        cJSON_AddStringToObject(resp, "idf", app_desc->idf_ver);
        service.SendResponse(cmd, msg_id, 0, "ok", resp);
        cJSON_Delete(resp);
        return;
    }

    if (cmd == "set_wifi") {
        cJSON* ssid_item = cJSON_GetObjectItem(data, "ssid");
        cJSON* pwd_item  = cJSON_GetObjectItem(data, "pwd");
        if (!ssid_item || !cJSON_IsString(ssid_item) || strlen(ssid_item->valuestring) == 0) {
            service.SendResponse(cmd, msg_id, -1, "missing ssid");
            return;
        }
        wifi_config_t wifi_config = {};
        strncpy(reinterpret_cast<char*>(wifi_config.sta.ssid),
                ssid_item->valuestring, sizeof(wifi_config.sta.ssid) - 1);
        if (pwd_item && cJSON_IsString(pwd_item)) {
            strncpy(reinterpret_cast<char*>(wifi_config.sta.password),
                    pwd_item->valuestring, sizeof(wifi_config.sta.password) - 1);
        }
        esp_err_t ret = esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
        if (ret != ESP_OK) {
            service.SendResponse(cmd, msg_id, -2, "set config failed");
            return;
        }
        esp_wifi_disconnect();
        ret = esp_wifi_connect();
        service.SendResponse(cmd, msg_id, 0,
            ret == ESP_OK ? "connecting" : "connect failed");
        return;
    }

    if (cmd == "wifi_list") {
        wifi_scan_config_t scan_config = {};
        scan_config.show_hidden = false;
        esp_err_t ret = esp_wifi_scan_start(&scan_config, true);
        if (ret != ESP_OK) {
            service.SendResponse(cmd, msg_id, -1, "scan failed");
            return;
        }
        uint16_t ap_count = 0;
        esp_wifi_scan_get_ap_num(&ap_count);
        if (ap_count > BLE_JSON_WIFI_LIST_MAX) {
            ap_count = BLE_JSON_WIFI_LIST_MAX;
        }
        wifi_ap_record_t* ap_list = nullptr;
        cJSON* resp = cJSON_CreateObject();
        cJSON* arr = cJSON_AddArrayToObject(resp, "list");
        if (ap_count > 0) {
            ap_list = static_cast<wifi_ap_record_t*>(malloc(sizeof(wifi_ap_record_t) * ap_count));
            if (ap_list && esp_wifi_scan_get_ap_records(&ap_count, ap_list) == ESP_OK) {
                for (int i = 0; i < ap_count; i++) {
                    cJSON* item = cJSON_CreateObject();
                    cJSON_AddStringToObject(item, "ssid",
                        reinterpret_cast<const char*>(ap_list[i].ssid));
                    cJSON_AddNumberToObject(item, "rssi", ap_list[i].rssi);
                    cJSON_AddNumberToObject(item, "auth", ap_list[i].authmode);
                    cJSON_AddItemToArray(arr, item);
                }
            }
            free(ap_list);
        }
        service.SendResponse(cmd, msg_id, 0, "ok", resp);
        cJSON_Delete(resp);
        return;
    }

    if (cmd == "set_vol") {
        cJSON* vol_item = cJSON_GetObjectItem(data, "vol");
        if (!vol_item || !cJSON_IsNumber(vol_item)) {
            service.SendResponse(cmd, msg_id, -1, "missing vol");
            return;
        }
        int vol = vol_item->valueint;
        if (vol < 0) vol = 0;
        if (vol > 100) vol = 100;
        auto* codec = board.GetAudioCodec();
        if (codec) {
            codec->SetOutputVolume(vol);
            Settings s("audio", true);
            s.SetInt("output_volume", vol);
        }
        service.SendResponse(cmd, msg_id, 0, "ok");
        return;
    }

    if (cmd == "reboot") {
        service.SendResponse(cmd, msg_id, 0, "rebooting");
        vTaskDelay(pdMS_TO_TICKS(500));
        Reboot();
        return;
    }

    if (cmd == "ota") {
        if (device_state_ == kDeviceStateUpgrading) {
            service.SendResponse(cmd, msg_id, -1, "already upgrading");
            return;
        }
        service.SendResponse(cmd, msg_id, 0, "start ota");
        Schedule([this]() {
            CheckNewVersion();
        });
        return;
    }

    if (cmd == "iot") {
        auto& thing_manager = iot::ThingManager::GetInstance();
        std::string states;
        if (thing_manager.GetStatesJson(states, true)) {
            cJSON* resp = cJSON_Parse(states.c_str());
            service.SendResponse(cmd, msg_id, 0, "ok", resp);
            if (resp) cJSON_Delete(resp);
        } else {
            service.SendResponse(cmd, msg_id, 0, "ok");
        }
        return;
    }

    service.SendResponse(cmd, msg_id, -99, "unknown cmd");
}
#endif
