ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
voice_assistant.h
Go to the documentation of this file.
1#pragma once
2
4
5#ifdef USE_VOICE_ASSISTANT
6
10
16#ifdef USE_MEDIA_PLAYER
18#endif
19#ifdef USE_MICRO_WAKE_WORD
21#endif
22#ifdef USE_SPEAKER
24#endif
26
27#include <span>
28#include <vector>
29
31
32// Version 1: Initial version
33// Version 2: Adds raw speaker support
34static const uint32_t LEGACY_INITIAL_VERSION = 1;
35static const uint32_t LEGACY_SPEAKER_SUPPORT = 2;
36
46
62
67
68struct Timer {
69 std::string id;
70 std::string name;
74
76 static constexpr size_t TO_STR_BUFFER_SIZE = 128;
78 const char *to_str(std::span<char, TO_STR_BUFFER_SIZE> buffer) const {
79 snprintf(buffer.data(), buffer.size(),
80 "Timer(id=%s, name=%s, total_seconds=%" PRIu32 ", seconds_left=%" PRIu32 ", is_active=%s)",
81 this->id.c_str(), this->name.c_str(), this->total_seconds, this->seconds_left, YESNO(this->is_active));
82 return buffer.data();
83 }
84};
85
86struct WakeWord {
87 std::string id;
88 std::string wake_word;
89 std::vector<std::string> trained_languages;
90};
91
93 std::vector<WakeWord> available_wake_words;
94 std::vector<std::string> active_wake_words;
96};
97
98#ifdef USE_MEDIA_PLAYER
99enum class MediaPlayerResponseState : uint8_t {
100 IDLE,
101 URL_SENT,
102 PLAYING,
103 FINISHED,
104};
105#endif
106
107class VoiceAssistant final : public Component {
108 public:
110
111 void loop() override;
112 void setup() override;
113 float get_setup_priority() const override;
114 void start_streaming();
115 void start_streaming(struct sockaddr_storage *addr, uint16_t port);
116 void failed_to_start();
117
118 void set_microphone_source(microphone::MicrophoneSource *mic_source) { this->mic_source_ = mic_source; }
119 void set_microphone_source2(microphone::MicrophoneSource *mic_source2) { this->mic_source2_ = mic_source2; }
120#ifdef USE_MICRO_WAKE_WORD
122#endif
123#ifdef USE_SPEAKER
125 this->speaker_ = speaker;
126 this->local_output_ = true;
127 }
128#endif
129#ifdef USE_MEDIA_PLAYER
131 this->media_player_ = media_player;
132 this->local_output_ = true;
133 }
134#endif
135
137#ifdef USE_SPEAKER
138 if (this->speaker_ != nullptr) {
139 return LEGACY_SPEAKER_SUPPORT;
140 }
141#endif
142 return LEGACY_INITIAL_VERSION;
143 }
144
146 uint32_t flags = 0;
149 if (this->mic_source2_ != nullptr) {
151 }
152#ifdef USE_SPEAKER
153 if (this->speaker_ != nullptr) {
155 }
156#endif
157
158 if (this->has_timers_) {
160 }
161
162#ifdef USE_MEDIA_PLAYER
163 if (this->media_player_ != nullptr) {
166 }
167#endif
168
169 return flags;
170 }
171
172 void request_start(bool continuous, bool silence_detection);
173 void request_stop();
174
176 void on_audio(const api::VoiceAssistantAudio &msg);
179 void on_set_configuration(const std::vector<std::string> &active_wake_words);
181
182 bool is_running() const { return this->state_ != State::IDLE; }
183 void set_continuous(bool continuous) { this->continuous_ = continuous; }
184 bool is_continuous() const { return this->continuous_; }
186 bool is_running_or_continuous() const { return this->is_running() || this->is_continuous(); }
187
188 void set_use_wake_word(bool use_wake_word) { this->use_wake_word_ = use_wake_word; }
189
190 void set_noise_suppression_level(uint8_t noise_suppression_level) {
191 this->noise_suppression_level_ = noise_suppression_level;
192 }
193 void set_auto_gain(uint8_t auto_gain) { this->auto_gain_ = auto_gain; }
194 void set_volume_multiplier(float volume_multiplier) { this->volume_multiplier_ = volume_multiplier; }
195 void set_conversation_timeout(uint32_t conversation_timeout) { this->conversation_timeout_ = conversation_timeout; }
198
207#ifdef USE_SPEAKER
210#endif
217
220
221 void client_subscription(api::APIConnection *client, bool subscribe);
223
224 void set_wake_word(const std::string &wake_word) { this->wake_word_ = wake_word; }
225
231 void set_has_timers(bool has_timers) { this->has_timers_ = has_timers; }
232 const std::vector<Timer> &get_timers() const { return this->timers_; }
233
234 protected:
235 bool allocate_buffers_();
236 void clear_buffers_();
237 void deallocate_buffers_();
238
239 void set_state_(State state);
240 void set_state_(State state, State desired_state);
244 void cancel_response_();
245 bool start_udp_socket_();
246 void signal_stop_();
248
249 // Drains the exposed microphone audio and sends it to Home Assistant over the API in one loop() pass.
250 void stream_api_audio_();
251 // Handles a pass where at least one configured channel has no audio exposed, timing out a channel that
252 // stalls. See audio_channel_stall_start_.
253 void handle_channel_stall_(size_t available, size_t available2);
254
255 std::unique_ptr<socket::Socket> socket_ = nullptr;
257
265#ifdef USE_SPEAKER
268#endif
276
279
281
282 std::vector<Timer> timers_;
283 void timer_tick_();
289
292#ifdef USE_SPEAKER
293 void write_speaker_();
295 uint8_t *speaker_buffer_{nullptr};
299#endif
300#ifdef USE_MEDIA_PLAYER
302 std::string tts_response_url_;
303#endif
304
305 std::string conversation_id_;
306
307 std::string wake_word_;
308
309 // Zero-copy sources that read directly from each microphone channel's ring buffer internal storage.
310 // Each source owns its ring buffer; the matching ``ring_buffer_``/``ring_buffer2_`` weak_ptr is used by
311 // the microphone callback (a different thread) to write into it.
312 std::unique_ptr<audio::RingBufferAudioSource> audio_source_;
313 std::unique_ptr<audio::RingBufferAudioSource> audio_source2_;
314 std::weak_ptr<ring_buffer::RingBuffer> ring_buffer_;
315 std::weak_ptr<ring_buffer::RingBuffer> ring_buffer2_;
316
317 // When streaming multiple channels, the send loop holds an exposed chunk on one channel until the other
318 // channel also has audio so the channels are always sent together (an empty payload looks like
319 // end-of-stream to Home Assistant). Home Assistant has no stream timeout, so a channel that stops
320 // producing entirely would hang streaming forever. This records when such an imbalance began so a
321 // prolonged one can be detected and stopped; 0 means no imbalance is currently being timed.
323
326 // Keep in sync with TTS_PLAYBACK_START_TIMEOUT_DEFAULT in __init__.py.
328
330
331#ifdef USE_MICRO_WAKE_WORD
333#endif
334
335 // 1 byte members grouped at the end so they share padding instead of each taking a word
339 bool has_timers_{false};
341#ifdef USE_SPEAKER
343 bool stream_ended_{false};
344#endif
345#ifdef USE_MEDIA_PLAYER
348#endif
349 bool local_output_{false};
352 uint8_t auto_gain_;
353 bool continuous_{false};
357};
358
359extern VoiceAssistant *global_voice_assistant; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
360
361} // namespace esphome::voice_assistant
362
363#endif // USE_VOICE_ASSISTANT
std::unique_ptr< socket::Socket > socket_
bool is_running_or_continuous() const
The voice_assistant.is_running condition: a pipeline is running or continuous mode keeps one coming.
void set_conversation_timeout(uint32_t conversation_timeout)
microphone::MicrophoneSource * mic_source2_
Trigger< std::string > * get_stt_end_trigger()
Trigger< std::string > * get_intent_progress_trigger()
void on_timer_event(const api::VoiceAssistantTimerEventResponse &msg)
void cancel_response_()
Stop a playing or queued response and tell Home Assistant when the TTS stream is still open.
void on_audio(const api::VoiceAssistantAudio &msg)
Trigger< std::string > * get_tts_end_trigger()
std::unique_ptr< audio::RingBufferAudioSource > audio_source_
const std::vector< Timer > & get_timers() const
std::weak_ptr< ring_buffer::RingBuffer > ring_buffer2_
std::weak_ptr< ring_buffer::RingBuffer > ring_buffer_
media_player::MediaPlayer * media_player_
void set_media_player(media_player::MediaPlayer *media_player)
void client_subscription(api::APIConnection *client, bool subscribe)
MediaPlayerResponseState media_player_response_state_
void on_event(const api::VoiceAssistantEventResponse &msg)
Trigger< std::string, std::string > error_trigger_
void set_state_after_mic_stop_(State state)
Move to state, or only make it the desired state while the microphone is still stopping.
Trigger< const std::vector< Timer > & > timer_tick_trigger_
Trigger< std::string > intent_progress_trigger_
void set_microphone_source2(microphone::MicrophoneSource *mic_source2)
void on_announce(const api::VoiceAssistantAnnounceRequest &msg)
void request_start(bool continuous, bool silence_detection)
void set_speaker(speaker::Speaker *speaker)
Trigger< std::string > * get_tts_start_trigger()
api::APIConnection * get_api_connection() const
void set_microphone_source(microphone::MicrophoneSource *mic_source)
void set_tts_playback_start_timeout(uint32_t timeout)
void handle_channel_stall_(size_t available, size_t available2)
std::unique_ptr< audio::RingBufferAudioSource > audio_source2_
void set_wake_word(const std::string &wake_word)
void set_micro_wake_word(micro_wake_word::MicroWakeWord *mww)
void set_volume_multiplier(float volume_multiplier)
microphone::MicrophoneSource * mic_source_
micro_wake_word::MicroWakeWord * micro_wake_word_
Trigger< std::string, std::string > * get_error_trigger()
void set_noise_suppression_level(uint8_t noise_suppression_level)
void on_set_configuration(const std::vector< std::string > &active_wake_words)
Trigger< const std::vector< Timer > & > * get_timer_tick_trigger()
uint16_t flags
bool state
Definition fan.h:2
uint32_t buffer
Definition gif.cpp:50
VoiceAssistant * global_voice_assistant
static void uint32_t
std::vector< WakeWord > available_wake_words
std::vector< std::string > active_wake_words
static constexpr size_t TO_STR_BUFFER_SIZE
Buffer size for to_str() - sufficient for typical timer names.
const char * to_str(std::span< char, TO_STR_BUFFER_SIZE > buffer) const
Format to buffer, returns pointer to buffer (may truncate long names)
std::vector< std::string > trained_languages