wangzhibo
10 天以前 220e4fab27b7f419b3e89e2f2d28ae789aac09d7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
#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_