mirror of
https://github.com/esphome/esphome.git
synced 2025-10-30 06:33:51 +00:00
[voice_assistant] Timers (#6821)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
This commit is contained in:
@@ -44,6 +44,12 @@ CONF_VOLUME_MULTIPLIER = "volume_multiplier"
|
||||
|
||||
CONF_WAKE_WORD = "wake_word"
|
||||
|
||||
CONF_ON_TIMER_STARTED = "on_timer_started"
|
||||
CONF_ON_TIMER_UPDATED = "on_timer_updated"
|
||||
CONF_ON_TIMER_CANCELLED = "on_timer_cancelled"
|
||||
CONF_ON_TIMER_FINISHED = "on_timer_finished"
|
||||
CONF_ON_TIMER_TICK = "on_timer_tick"
|
||||
|
||||
|
||||
voice_assistant_ns = cg.esphome_ns.namespace("voice_assistant")
|
||||
VoiceAssistant = voice_assistant_ns.class_("VoiceAssistant", cg.Component)
|
||||
@@ -64,6 +70,8 @@ ConnectedCondition = voice_assistant_ns.class_(
|
||||
"ConnectedCondition", automation.Condition, cg.Parented.template(VoiceAssistant)
|
||||
)
|
||||
|
||||
Timer = voice_assistant_ns.struct("Timer")
|
||||
|
||||
|
||||
def tts_stream_validate(config):
|
||||
if CONF_SPEAKER not in config and (
|
||||
@@ -131,6 +139,21 @@ CONFIG_SCHEMA = cv.All(
|
||||
single=True
|
||||
),
|
||||
cv.Optional(CONF_ON_IDLE): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_ON_TIMER_STARTED): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
cv.Optional(CONF_ON_TIMER_UPDATED): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
cv.Optional(CONF_ON_TIMER_CANCELLED): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
cv.Optional(CONF_ON_TIMER_FINISHED): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
cv.Optional(CONF_ON_TIMER_TICK): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
tts_stream_validate,
|
||||
@@ -270,6 +293,49 @@ async def to_code(config):
|
||||
config[CONF_ON_IDLE],
|
||||
)
|
||||
|
||||
has_timers = False
|
||||
if on_timer_started := config.get(CONF_ON_TIMER_STARTED):
|
||||
await automation.build_automation(
|
||||
var.get_timer_started_trigger(),
|
||||
[(Timer, "timer")],
|
||||
on_timer_started,
|
||||
)
|
||||
has_timers = True
|
||||
|
||||
if on_timer_updated := config.get(CONF_ON_TIMER_UPDATED):
|
||||
await automation.build_automation(
|
||||
var.get_timer_updated_trigger(),
|
||||
[(Timer, "timer")],
|
||||
on_timer_updated,
|
||||
)
|
||||
has_timers = True
|
||||
|
||||
if on_timer_cancelled := config.get(CONF_ON_TIMER_CANCELLED):
|
||||
await automation.build_automation(
|
||||
var.get_timer_cancelled_trigger(),
|
||||
[(Timer, "timer")],
|
||||
on_timer_cancelled,
|
||||
)
|
||||
has_timers = True
|
||||
|
||||
if on_timer_finished := config.get(CONF_ON_TIMER_FINISHED):
|
||||
await automation.build_automation(
|
||||
var.get_timer_finished_trigger(),
|
||||
[(Timer, "timer")],
|
||||
on_timer_finished,
|
||||
)
|
||||
has_timers = True
|
||||
|
||||
if on_timer_tick := config.get(CONF_ON_TIMER_TICK):
|
||||
await automation.build_automation(
|
||||
var.get_timer_tick_trigger(),
|
||||
[(cg.std_vector.template(Timer), "timers")],
|
||||
on_timer_tick,
|
||||
)
|
||||
has_timers = True
|
||||
|
||||
cg.add(var.set_has_timers(has_timers))
|
||||
|
||||
cg.add_define("USE_VOICE_ASSISTANT")
|
||||
|
||||
|
||||
|
||||
@@ -798,12 +798,65 @@ void VoiceAssistant::on_audio(const api::VoiceAssistantAudio &msg) {
|
||||
this->speaker_buffer_index_ += msg.data.length();
|
||||
this->speaker_buffer_size_ += msg.data.length();
|
||||
this->speaker_bytes_received_ += msg.data.length();
|
||||
ESP_LOGD(TAG, "Received audio: %d bytes from API", msg.data.length());
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Cannot receive audio, buffer is full");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void VoiceAssistant::on_timer_event(const api::VoiceAssistantTimerEventResponse &msg) {
|
||||
Timer timer = {
|
||||
.id = msg.timer_id,
|
||||
.name = msg.name,
|
||||
.total_seconds = msg.total_seconds,
|
||||
.seconds_left = msg.seconds_left,
|
||||
.is_active = msg.is_active,
|
||||
};
|
||||
this->timers_[timer.id] = timer;
|
||||
ESP_LOGD(TAG, "Timer Event");
|
||||
ESP_LOGD(TAG, " Type: %d", msg.event_type);
|
||||
ESP_LOGD(TAG, " %s", timer.to_string().c_str());
|
||||
|
||||
switch (msg.event_type) {
|
||||
case api::enums::VOICE_ASSISTANT_TIMER_STARTED:
|
||||
this->timer_started_trigger_->trigger(timer);
|
||||
break;
|
||||
case api::enums::VOICE_ASSISTANT_TIMER_UPDATED:
|
||||
this->timer_updated_trigger_->trigger(timer);
|
||||
break;
|
||||
case api::enums::VOICE_ASSISTANT_TIMER_CANCELLED:
|
||||
this->timer_cancelled_trigger_->trigger(timer);
|
||||
this->timers_.erase(timer.id);
|
||||
break;
|
||||
case api::enums::VOICE_ASSISTANT_TIMER_FINISHED:
|
||||
this->timer_finished_trigger_->trigger(timer);
|
||||
this->timers_.erase(timer.id);
|
||||
break;
|
||||
}
|
||||
|
||||
if (this->timers_.empty()) {
|
||||
this->cancel_interval("timer-event");
|
||||
this->timer_tick_running_ = false;
|
||||
} else if (!this->timer_tick_running_) {
|
||||
this->set_interval("timer-event", 1000, [this]() { this->timer_tick_(); });
|
||||
this->timer_tick_running_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void VoiceAssistant::timer_tick_() {
|
||||
std::vector<Timer> res;
|
||||
res.reserve(this->timers_.size());
|
||||
for (auto &pair : this->timers_) {
|
||||
auto &timer = pair.second;
|
||||
if (timer.is_active && timer.seconds_left > 0) {
|
||||
timer.seconds_left--;
|
||||
}
|
||||
res.push_back(timer);
|
||||
}
|
||||
this->timer_tick_trigger_->trigger(res);
|
||||
}
|
||||
|
||||
VoiceAssistant *global_voice_assistant = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace voice_assistant
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
#include <esp_vad.h>
|
||||
#endif
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace voice_assistant {
|
||||
|
||||
@@ -36,6 +39,7 @@ enum VoiceAssistantFeature : uint32_t {
|
||||
FEATURE_VOICE_ASSISTANT = 1 << 0,
|
||||
FEATURE_SPEAKER = 1 << 1,
|
||||
FEATURE_API_AUDIO = 1 << 2,
|
||||
FEATURE_TIMERS = 1 << 3,
|
||||
};
|
||||
|
||||
enum class State {
|
||||
@@ -59,6 +63,20 @@ enum AudioMode : uint8_t {
|
||||
AUDIO_MODE_API,
|
||||
};
|
||||
|
||||
struct Timer {
|
||||
std::string id;
|
||||
std::string name;
|
||||
uint32_t total_seconds;
|
||||
uint32_t seconds_left;
|
||||
bool is_active;
|
||||
|
||||
std::string to_string() const {
|
||||
return str_sprintf("Timer(id=%s, name=%s, total_seconds=%" PRIu32 ", seconds_left=%" PRIu32 ", is_active=%s)",
|
||||
this->id.c_str(), this->name.c_str(), this->total_seconds, this->seconds_left,
|
||||
YESNO(this->is_active));
|
||||
}
|
||||
};
|
||||
|
||||
class VoiceAssistant : public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
@@ -100,6 +118,11 @@ class VoiceAssistant : public Component {
|
||||
flags |= VoiceAssistantFeature::FEATURE_SPEAKER;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (this->has_timers_) {
|
||||
flags |= VoiceAssistantFeature::FEATURE_TIMERS;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
@@ -108,6 +131,7 @@ class VoiceAssistant : public Component {
|
||||
|
||||
void on_event(const api::VoiceAssistantEventResponse &msg);
|
||||
void on_audio(const api::VoiceAssistantAudio &msg);
|
||||
void on_timer_event(const api::VoiceAssistantTimerEventResponse &msg);
|
||||
|
||||
bool is_running() const { return this->state_ != State::IDLE; }
|
||||
void set_continuous(bool continuous) { this->continuous_ = continuous; }
|
||||
@@ -150,6 +174,14 @@ class VoiceAssistant : public Component {
|
||||
|
||||
void set_wake_word(const std::string &wake_word) { this->wake_word_ = wake_word; }
|
||||
|
||||
Trigger<Timer> *get_timer_started_trigger() const { return this->timer_started_trigger_; }
|
||||
Trigger<Timer> *get_timer_updated_trigger() const { return this->timer_updated_trigger_; }
|
||||
Trigger<Timer> *get_timer_cancelled_trigger() const { return this->timer_cancelled_trigger_; }
|
||||
Trigger<Timer> *get_timer_finished_trigger() const { return this->timer_finished_trigger_; }
|
||||
Trigger<std::vector<Timer>> *get_timer_tick_trigger() const { return this->timer_tick_trigger_; }
|
||||
void set_has_timers(bool has_timers) { this->has_timers_ = has_timers; }
|
||||
const std::unordered_map<std::string, Timer> &get_timers() const { return this->timers_; }
|
||||
|
||||
protected:
|
||||
bool allocate_buffers_();
|
||||
void clear_buffers_();
|
||||
@@ -186,6 +218,16 @@ class VoiceAssistant : public Component {
|
||||
|
||||
api::APIConnection *api_client_{nullptr};
|
||||
|
||||
std::unordered_map<std::string, Timer> timers_;
|
||||
void timer_tick_();
|
||||
Trigger<Timer> *timer_started_trigger_ = new Trigger<Timer>();
|
||||
Trigger<Timer> *timer_finished_trigger_ = new Trigger<Timer>();
|
||||
Trigger<Timer> *timer_updated_trigger_ = new Trigger<Timer>();
|
||||
Trigger<Timer> *timer_cancelled_trigger_ = new Trigger<Timer>();
|
||||
Trigger<std::vector<Timer>> *timer_tick_trigger_ = new Trigger<std::vector<Timer>>();
|
||||
bool has_timers_{false};
|
||||
bool timer_tick_running_{false};
|
||||
|
||||
microphone::Microphone *mic_{nullptr};
|
||||
#ifdef USE_SPEAKER
|
||||
void write_speaker_();
|
||||
|
||||
Reference in New Issue
Block a user