toy-hardware/main/application.h
Rdzleo bc14e60836 Create v1.7.6 聆听空闲超时自动退出 + Bug修复
一、新增功能
1. 聆听状态空闲超时自动退出
   - 聆听状态下无用户交互(无语音对话、无按键操作、无音频播放)超过60秒后,
     设备自动关闭音频通道回到idle待命状态,行为等同于手动按下BOOT按键退出
   - 超时时间通过Kconfig CONFIG_LISTENING_IDLE_TIMEOUT_SECONDS可配置(范围30~300秒,默认60秒)
   - speaking状态期间暂停计时,回到listening后从0重新倒计时,确保用户有完整的思考时间

2. 聆听空闲计时器外部重置接口
   - 新增ResetListeningIdleTimer()公开方法,供板级按键/触摸回调调用
   - 重置触发点:触摸按键(摸脑袋等)、BOOT按键、故事按键、收到服务端stt/tts/music_control/story消息

二、Bug修复
3. 修复超时退出后待命音效无声
   - 原因:超时退出路径中audio_processor_.Stop()关闭了功放,之后才播放待命音效
   - 修复:在SetDeviceState(kDeviceStateIdle)播放待命音效前调用codec->EnableOutput(true)确保功放开启

4. 修复WebSocket断开与tts:stop竞态导致崩溃重启
   - 原因:tts:stop和WebSocket断开同时发生时,设备切换到listening触发SendStartListening失败,
     竞态导致WakeWordDetect堆损坏(StoreProhibited崩溃)
   - 修复:tts:stop处理中先检查IsAudioChannelOpened(),音频通道不可用时直接回退到idle

5. 修复listening状态音频通道不可用时逻辑错误
   - 原因:音频通道不可用时"保持在listening状态"导致后续状态混乱
   - 修复:改为直接回退到idle状态

三、优化调整
6. 版本号从1.7.5升级到1.7.6
7. ADC电量采样间隔从10ms缩短为5ms,提高采样效率
8. 日志配置调整:恢复日志输出能力用于调试
9. WiFi组件代码注释补充
10. 新增.gitignore,忽略build目录和.vscode/settings.json

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 11:17:37 +08:00

172 lines
6.9 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

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

#ifndef _APPLICATION_H_
#define _APPLICATION_H_
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include <freertos/task.h>
#include <esp_timer.h>
#include <string>
#include <mutex>
#include <list>
#include <atomic>
#include <opus_encoder.h>
#include <opus_decoder.h>
#include <opus_resampler.h>
#include "protocol.h"
#include "ota.h"
#include "background_task.h"
#if CONFIG_USE_WAKE_WORD_DETECT
#include "wake_word_detect.h"
#elif CONFIG_USE_CUSTOM_WAKE_WORD
#include "custom_wake_word.h"
#endif
#if CONFIG_USE_AUDIO_PROCESSOR
#include "audio_processor.h"
#endif
#define SCHEDULE_EVENT (1 << 0)
#define AUDIO_INPUT_READY_EVENT (1 << 1)
#define AUDIO_OUTPUT_READY_EVENT (1 << 2)
// 未知状态、启动中、WiFi配网模式、空闲待命、连接服务器、语音监听中、语音播报中、固件升级中、设备激活中、致命错误
enum DeviceState {
kDeviceStateUnknown,
kDeviceStateStarting,
kDeviceStateWifiConfiguring,
kDeviceStateIdle,
kDeviceStateConnecting,
kDeviceStateListening,
kDeviceStateSpeaking,
kDeviceStateUpgrading,
kDeviceStateActivating,
kDeviceStateFatalError
};
// OPUS音频帧时长60ms
#define OPUS_FRAME_DURATION_MS 60
// 应用程序主类(单例模式)
class Application {
public:
static Application& GetInstance() {
static Application instance;
return instance;
}
// 删除拷贝构造函数和赋值运算符
Application(const Application&) = delete;
Application& operator=(const Application&) = delete;
void Start(); // 启动应用程序
DeviceState GetDeviceState() const { return device_state_; } // 获取当前状态
bool IsVoiceDetected() const { return voice_detected_; } // 语音检测状态
void Schedule(std::function<void()> callback); // 任务调度
void SetDeviceState(DeviceState state); // 状态变更
void Alert(const char* status, const char* message, const char* emotion = "", const std::string_view& sound = "");// 警报管理 状态、消息、情感、声音
void DismissAlert();// 关闭警报
void AbortSpeaking(AbortReason reason);// 打断语音播报
void SendStoryRequest(); // 发送讲故事 请求
void ToggleChatState();// 切换聊天状态
void ToggleListeningState();// 切换监听状态
void StartListening();// 开始监听
void StopListening();// 停止监听
void SendTextMessage(const std::string& text);// 发送文本消息
void UpdateIotStates();// 更新IOT设备状态
void Reboot();// 系统重启
void WakeWordInvoke(const std::string& wake_word);// 唤醒词回调
void PlaySound(const std::string_view& sound);// 播放声音
void WaitForAudioPlayback();// 等待音频播报完成
bool IsAudioQueueEmpty(); // 检查音频队列是否为空
void ClearAudioQueue(); // 清空音频播放队列
bool CanEnterSleepMode();// 检查是否可以进入睡眠模式
void StopAudioProcessor();// 停止音频处理器
void ResetDecoder();// 重置解码器状态(用于修复音频播放问题)
bool IsSafeToOperate(); // 🔧 检查当前是否可以安全执行操作
void AbortSpeakingAndReturnToIdle(); // 🔴 专门处理从说话状态到空闲状态的切换
void AbortSpeakingAndReturnToListening(); // 🔵 专门处理从说话状态到聆听状态的切换
void PauseAudioPlayback(); // ⏸️ 暂停音频播放
void ResumeAudioPlayback(); // ▶️ 恢复音频播放
void SuppressNextIdleSound();
void SetLowBatteryTransition(bool value);
bool IsLowBatteryTransition() const;
void ResetListeningIdleTimer(); // 重置聆听空闲计时器(外部按键/触摸调用)
// 姿态传感器接口
bool IsImuSensorAvailable(); // 检查IMU传感器是否可用
bool GetImuData(float* acc_x, float* acc_y, float* acc_z,
float* gyro_x, float* gyro_y, float* gyro_z,
float* temperature); // 获取IMU传感器数据
void OnMotionDetected(); // 运动检测事件处理
bool IsAudioPaused() const { return audio_paused_; } // 检查音频是否暂停
private:
Application();// 构造函数
~Application();// 析构函数
// 配置使用唤醒词检测
#if CONFIG_USE_WAKE_WORD_DETECT
WakeWordDetect wake_word_detect_;
#elif CONFIG_USE_CUSTOM_WAKE_WORD
CustomWakeWord wake_word_detect_;
#endif
// 音频处理器
#if CONFIG_USE_AUDIO_PROCESSOR
AudioProcessor audio_processor_;
#endif
Ota ota_;
std::mutex mutex_;
std::list<std::function<void()>> main_tasks_;
std::unique_ptr<Protocol> protocol_;
EventGroupHandle_t event_group_ = nullptr;
esp_timer_handle_t clock_timer_handle_ = nullptr;
volatile DeviceState device_state_ = kDeviceStateUnknown;
std::atomic<bool> is_aborting_{false}; // 🔧 原子标志:防止重复中止操作
std::atomic<std::chrono::steady_clock::time_point> last_safe_operation_; // 🔧 最后安全操作时间戳
std::atomic<bool> is_switching_to_listening_{false}; // 🔵 标志:正在主动切换到聆听状态
std::atomic<bool> is_low_battery_transition_{false};
ListeningMode listening_mode_ = kListeningModeAutoStop;
#if CONFIG_USE_REALTIME_CHAT
bool realtime_chat_enabled_ = true;
#else
bool realtime_chat_enabled_ = false;
#endif
bool aborted_ = false;
bool voice_detected_ = false;
bool audio_paused_ = false; // 音频暂停状态标志
float current_speaker_volume_ = 0.0f; // 当前扬声器音量,用于语音打断判断
std::chrono::time_point<std::chrono::steady_clock> last_audio_input_time_;
int clock_ticks_ = 0;
int listening_idle_ticks_ = 0; // 聆听状态空闲计时器(秒)
TaskHandle_t main_loop_task_handle_ = nullptr;
TaskHandle_t check_new_version_task_handle_ = nullptr;
// Audio encode / decode
TaskHandle_t audio_loop_task_handle_ = nullptr;
BackgroundTask* background_task_ = nullptr;
std::chrono::steady_clock::time_point last_output_time_;
std::list<std::vector<uint8_t>> audio_decode_queue_;
std::unique_ptr<OpusEncoderWrapper> opus_encoder_;
std::unique_ptr<OpusDecoderWrapper> opus_decoder_;
OpusResampler input_resampler_;
OpusResampler reference_resampler_;
OpusResampler output_resampler_;
void MainLoop();// 主事件循环
void OnAudioInput();// 音频输入回调
void OnAudioOutput();// 音频输出回调
void ReadAudio(std::vector<int16_t>& data, int sample_rate, int samples);// 读取音频数据
void SetDecodeSampleRate(int sample_rate, int frame_duration);// 设置解码采样率
void CheckNewVersion();// 检查新固件版本
void ShowActivationCode();// 显示激活码
void OnClockTimer();// 时钟定时器回调
void SetListeningMode(ListeningMode mode);// 设置监听模式
void AudioLoop();// 音频处理循环
bool suppress_next_idle_sound_ = false;// 标志:是否抑制下一个空闲状态的声音播放
};
#endif // _APPLICATION_H_