#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 <deque>
|
#include <memory>
|
#include <functional>
|
#include <atomic>
|
|
#include "protocol.h"
|
#include "ota.h"
|
#include "audio_service.h"
|
#include "device_state.h"
|
#include "device_state_machine.h"
|
|
// Main event bits
|
#define MAIN_EVENT_SCHEDULE (1 << 0)
|
#define MAIN_EVENT_SEND_AUDIO (1 << 1)
|
#define MAIN_EVENT_WAKE_WORD_DETECTED (1 << 2)
|
#define MAIN_EVENT_VAD_CHANGE (1 << 3)
|
#define MAIN_EVENT_ERROR (1 << 4)
|
#define MAIN_EVENT_ACTIVATION_DONE (1 << 5)
|
#define MAIN_EVENT_CLOCK_TICK (1 << 6)
|
#define MAIN_EVENT_NETWORK_CONNECTED (1 << 7)
|
#define MAIN_EVENT_NETWORK_DISCONNECTED (1 << 8)
|
#define MAIN_EVENT_TOGGLE_CHAT (1 << 9)
|
#define MAIN_EVENT_START_LISTENING (1 << 10)
|
#define MAIN_EVENT_STOP_LISTENING (1 << 11)
|
#define MAIN_EVENT_STATE_CHANGED (1 << 12)
|
|
|
enum AecMode {
|
kAecOff,
|
kAecOnDeviceSide,
|
kAecOnServerSide,
|
};
|
|
class Application {
|
public:
|
static Application& GetInstance() {
|
static Application instance;
|
return instance;
|
}
|
// Delete copy constructor and assignment operator
|
Application(const Application&) = delete;
|
Application& operator=(const Application&) = delete;
|
|
/**
|
* Initialize the application
|
* This sets up display, audio, network callbacks, etc.
|
* Network connection starts asynchronously.
|
*/
|
void Initialize();
|
|
/**
|
* Run the main event loop
|
* This function runs in the main task and never returns.
|
* It handles all events including network, state changes, and user interactions.
|
*/
|
void Run();
|
|
DeviceState GetDeviceState() const { return state_machine_.GetState(); }
|
bool IsVoiceDetected() const { return audio_service_.IsVoiceDetected(); }
|
|
/**
|
* Request state transition
|
* Returns true if transition was successful
|
*/
|
bool SetDeviceState(DeviceState state);
|
|
/**
|
* Schedule a callback to be executed in the main task
|
*/
|
void Schedule(std::function<void()>&& callback);
|
|
/**
|
* Alert with status, message, emotion and optional sound
|
*/
|
void Alert(const char* status, const char* message, const char* emotion = "", const std::string_view& sound = "");
|
void DismissAlert();
|
|
void AbortSpeaking(AbortReason reason);
|
|
/**
|
* Toggle chat state (event-based, thread-safe)
|
* Sends MAIN_EVENT_TOGGLE_CHAT to be handled in Run()
|
*/
|
void ToggleChatState();
|
|
/**
|
* Start listening (event-based, thread-safe)
|
* Sends MAIN_EVENT_START_LISTENING to be handled in Run()
|
*/
|
void StartListening();
|
|
/**
|
* Stop listening (event-based, thread-safe)
|
* Sends MAIN_EVENT_STOP_LISTENING to be handled in Run()
|
*/
|
void StopListening();
|
void SendListenDetect(const std::string& text, bool open_if_idle = true);
|
bool SendListenDetectNow(const std::string& text);
|
void HoldIdleForServerPrompt();
|
void ReleaseHoldIdle();
|
void StartListeningAfterPrompt();
|
void ArmPendingDetectRetry(const std::string& text, int retries);
|
bool IsAudioChannelConnected() const;
|
void AnnounceReturnedToChat();
|
void ReconnectAfterLesson(const std::string& detect);
|
|
void Reboot();
|
void WakeWordInvoke(const std::string& wake_word);
|
bool UpgradeFirmware(const std::string& url, const std::string& version = "",
|
std::function<void(int progress, size_t speed)> progress_callback = nullptr,
|
std::function<void(bool success)> complete_callback = nullptr);
|
void EnterStandby();
|
void RequestPowerOffStandby();
|
void EnterPowerOffStandby();
|
void RestoreFromPowerOffStandby(bool network_ready);
|
bool IsPowerOffStandby() const { return power_off_standby_active_.load(); }
|
void AcquireSleepInhibit();
|
void ReleaseSleepInhibit();
|
bool CanEnterSleepMode();
|
void PrepareProtocolForNetworkStandby();
|
void SendMcpMessage(const std::string& payload);
|
void SetAecMode(AecMode mode);
|
AecMode GetAecMode() const { return aec_mode_; }
|
void PlaySound(const std::string_view& sound);
|
AudioService& GetAudioService() { return audio_service_; }
|
void ApplyMp3CommandOnlyMic();
|
bool IsOnMainLoop() const;
|
void ResumeListeningAfterMp3Clip();
|
void CloseShadowingListenWindow();
|
|
/**
|
* Reset protocol resources (thread-safe)
|
* Can be called from any task to release resources allocated after network connected
|
* This includes closing audio channel, resetting protocol and ota objects
|
*/
|
void ResetProtocol();
|
|
private:
|
Application();
|
~Application();
|
|
std::mutex mutex_;
|
std::deque<std::function<void()>> main_tasks_;
|
std::unique_ptr<Protocol> protocol_;
|
EventGroupHandle_t event_group_ = nullptr;
|
esp_timer_handle_t clock_timer_handle_ = nullptr;
|
DeviceStateMachine state_machine_;
|
ListeningMode listening_mode_ = kListeningModeAutoStop;
|
AecMode aec_mode_ = kAecOff;
|
std::string last_error_message_;
|
AudioService audio_service_;
|
std::unique_ptr<Ota> ota_;
|
|
bool has_server_time_ = false;
|
bool aborted_ = false;
|
bool assets_version_checked_ = false;
|
bool local_assets_applied_ = false;
|
bool play_popup_on_listening_ = false; // Flag to play popup sound after state changes to listening
|
bool skip_playback_wait_on_listen_ = false;
|
bool resume_sdcard_mp3_after_tts_ = false;
|
bool resume_sdcard_mp3_after_voice_ = false;
|
bool sdcard_mp3_stop_requested_ = false;
|
bool opening_audio_channel_ = false;
|
bool suppress_wake_until_listen_ = false;
|
bool hold_idle_for_prompt_ = false;
|
bool network_error_suppressed_ = false;
|
std::string pending_detect_text_;
|
int pending_detect_retry_ticks_ = 0;
|
int pending_detect_retries_left_ = 0;
|
int greeting_listen_wait_ticks_ = 0;
|
int boot_wake_restore_ticks_ = 0;
|
bool tts_audio_expected_ = false;
|
bool tts_response_started_ = false;
|
bool tts_audio_received_ = false;
|
bool tts_stop_pending_ = false;
|
bool tts_stop_waiting_ = false;
|
bool tts_idle_waiting_ = false;
|
bool tts_session_end_ = false;
|
bool tts_speaking_state_ready_ = false;
|
uint32_t tts_activity_generation_ = 0;
|
int tts_playback_empty_ticks_ = 0;
|
int tts_stop_wait_ticks_ = 0;
|
int64_t speaking_started_us_ = 0;
|
bool standby_requested_ = false;
|
bool power_off_requested_ = false;
|
std::atomic_bool power_off_standby_active_{false};
|
int sleep_inhibit_count_ = 0;
|
int clock_ticks_ = 0;
|
TaskHandle_t activation_task_handle_ = nullptr;
|
TaskHandle_t main_loop_task_ = nullptr;
|
|
|
// Event handlers
|
void HandleStateChangedEvent();
|
void HandleToggleChatEvent();
|
void HandleStartListeningEvent();
|
void HandleStopListeningEvent();
|
void HandleNetworkConnectedEvent();
|
void HandleNetworkDisconnectedEvent();
|
void HandleActivationDoneEvent();
|
void HandleWakeWordDetectedEvent();
|
void ContinueOpenAudioChannel(ListeningMode mode);
|
void ContinueOpenAudioChannelForDetect(const std::string& detect_text);
|
void ContinueWakeWordInvoke(const std::string& wake_word);
|
bool OpenAudioChannelWithRetry(int attempts);
|
|
// Activation task (runs in background)
|
void ActivationTask();
|
|
// Helper methods
|
void LoadLocalAssetsForWakeWord();
|
void CheckAssetsVersion();
|
void CheckNewVersion();
|
void InitializeProtocol();
|
void ShowActivationCode(const std::string& code, const std::string& message);
|
void FinishTtsResponse();
|
void FinishTtsResponseAfterPlayback();
|
void MaybeFinishTtsAfterPlayback();
|
void ScheduleTtsAudio(std::shared_ptr<AudioStreamPacket> packet);
|
void PauseSdCardMp3ForVoice();
|
void ResumeSdCardMp3AfterVoice();
|
void SetListeningMode(ListeningMode mode);
|
ListeningMode GetDefaultListeningMode() const;
|
|
// State change handler called by state machine
|
void OnStateChanged(DeviceState old_state, DeviceState new_state);
|
};
|
|
|
class TaskPriorityReset {
|
public:
|
TaskPriorityReset(BaseType_t priority) {
|
original_priority_ = uxTaskPriorityGet(NULL);
|
vTaskPrioritySet(NULL, priority);
|
}
|
~TaskPriorityReset() {
|
vTaskPrioritySet(NULL, original_priority_);
|
}
|
|
private:
|
BaseType_t original_priority_;
|
};
|
|
#endif // _APPLICATION_H_
|