Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save jlpouffier/41351187e2f6f94e797382a658702433 to your computer and use it in GitHub Desktop.
Save jlpouffier/41351187e2f6f94e797382a658702433 to your computer and use it in GitHub Desktop.
Voice Preview Edition Experimental Streaming Wake Word Firmware
substitutions:
# Phases of the Voice Assistant
# The voice assistant is ready to be triggered by a wake word
voice_assist_idle_phase_id: '1'
# The voice assistant is waiting for a voice command (after being triggered by the wake word)
voice_assist_waiting_for_command_phase_id: '2'
# The voice assistant is listening for a voice command
voice_assist_listening_for_command_phase_id: '3'
# The voice assistant is currently processing the command
voice_assist_thinking_phase_id: '4'
# The voice assistant is replying to the command
voice_assist_replying_phase_id: '5'
# The voice assistant is not ready
voice_assist_not_ready_phase_id: '10'
# The voice assistant encountered an error
voice_assist_error_phase_id: '11'
esphome:
name: home-assistant-voice
friendly_name: Home Assistant Voice
name_add_mac_suffix: true
min_version: 2024.9.0
platformio_options:
board_build.flash_mode: dio
on_boot:
priority: 375
then:
# Run the script to refresh the LED status
- script.execute: control_leds
- delay: 1s
- switch.turn_on: internal_speaker_amp
# If the hardware switch is ON, force the software switch to be ON too. This covers the case where the Mute hardware switch is operated when the device is turned off
- if:
condition:
binary_sensor.is_on: hardware_mute_switch
then:
- switch.template.publish:
id: master_mute_switch
state: ON
# If after 10 minutes, the device is still initializing (It did not yet connect to Home Assistant), turn off the init_in_progress variable and run the script to refresh the LED status
- delay: 10min
- if:
condition:
lambda: return id(init_in_progress);
then:
- lambda: id(init_in_progress) = false;
- script.execute: control_leds
esp32:
board: esp32-s3-devkitc-1
variant: esp32s3
flash_size: 16MB
framework:
type: esp-idf
version: recommended
sdkconfig_options:
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: "y"
CONFIG_ESP32S3_DATA_CACHE_64KB: "y"
CONFIG_ESP32S3_DATA_CACHE_LINE_64B: "y"
CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB: "y"
CONFIG_ESP32_S3_BOX_BOARD: "y"
CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY: "y"
CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP: "y"
# Settings based on https://github.com/espressif/esp-adf/issues/297#issuecomment-783811702
CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM: "16"
CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM: "512"
CONFIG_ESP32_WIFI_STATIC_TX_BUFFER: "y"
CONFIG_ESP32_WIFI_TX_BUFFER_TYPE: "0"
CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM: "8"
CONFIG_ESP32_WIFI_CACHE_TX_BUFFER_NUM: "32"
CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED: "y"
CONFIG_ESP32_WIFI_TX_BA_WIN: "16"
CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED: "y"
CONFIG_ESP32_WIFI_RX_BA_WIN: "32"
CONFIG_LWIP_MAX_ACTIVE_TCP: "16"
CONFIG_LWIP_MAX_LISTENING_TCP: "16"
CONFIG_TCP_MAXRTX: "12"
CONFIG_TCP_SYNMAXRTX: "6"
CONFIG_TCP_MSS: "1436"
CONFIG_TCP_MSL: "60000"
CONFIG_TCP_SND_BUF_DEFAULT: "65535"
CONFIG_TCP_WND_DEFAULT: "65535" # Adjusted from linked settings to avoid compilation error
CONFIG_TCP_RECVMBOX_SIZE: "512"
CONFIG_TCP_QUEUE_OOSEQ: "y"
CONFIG_TCP_OVERSIZE_MSS: "y"
CONFIG_LWIP_WND_SCALE: "y"
CONFIG_TCP_RCV_SCALE: "3"
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE: "512"
CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST: "y"
CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY: "y"
wifi:
id: wifi_id
on_connect:
- lambda: id(improv_ble_in_progress) = false;
- script.execute: control_leds
on_disconnect:
- script.execute: control_leds
logger:
level: DEBUG
logs:
sensor: WARN # avoids logging debug sensor updates
api:
id: api_id
on_client_connected:
- script.execute: control_leds
on_client_disconnected:
- script.execute: control_leds
ota:
- platform: esphome
id: ota_esphome
i2c:
sda: GPIO5
scl: GPIO6
frequency: 400kHz
psram:
mode: octal
speed: 80MHz
globals:
# Global index for our LEDs. So that switching between different animation does not lead to unwanted effects.
- id: global_led_animation_index
type: int
restore_value: no
initial_value: '0'
# Global initialization variable. Initialized to true and set to false once everything is connected. Only used to have a smooth "plugging" experience
- id: init_in_progress
type: bool
restore_value: no
initial_value: 'true'
# Global variable storing the state of ImprovBLE. Used to draw different LED animations
- id: improv_ble_in_progress
type: bool
restore_value: no
initial_value: 'false'
# Global variable tracking the phase of the voice assistant (defined above). Initialized to not_ready
- id: voice_assistant_phase
type: int
restore_value: no
initial_value: ${voice_assist_not_ready_phase_id}
# Global variable tracking if the dial was recently touched.
- id: dial_touched
type: bool
restore_value: no
initial_value: 'false'
# Global variable tracking if the LED color was recently changed.
- id: color_changed
type: bool
restore_value: no
initial_value: 'false'
# Global variable tracking if the jack has been plugged touched.
- id: jack_plugged_recently
type: bool
restore_value: no
initial_value: 'false'
# Global variable tracking if the jack has been unplugged touched.
- id: jack_unplugged_recently
type: bool
restore_value: no
initial_value: 'false'
# Global variable storing the first active timer
- id: first_active_timer
type: voice_assistant::Timer
restore_value: false
# Global variable storing if a timer is active
- id: is_timer_active
type: bool
restore_value: false
switch:
# This is the master mute switch. It is exposed to Home Assistant. The user can only turn it on and off if the hardware switch is off. (The hardware switch overrides the software one)
- platform: template
id: master_mute_switch
restore_mode: RESTORE_DEFAULT_OFF
icon: "mdi:microphone-off"
name: Mute
entity_category: config
turn_on_action:
- if:
condition:
binary_sensor.is_off: hardware_mute_switch
then:
- switch.template.publish:
id: master_mute_switch
state: ON
- voice_assistant.stop:
turn_off_action:
- if:
condition:
binary_sensor.is_off: hardware_mute_switch
then:
- switch.template.publish:
id: master_mute_switch
state: OFF
- voice_assistant.start_continuous:
on_turn_on:
- script.execute: control_leds
on_turn_off:
- script.execute: control_leds
# Wake Word Sound Switch.
- platform: template
id: wake_sound
name: Wake sound
icon: "mdi:bullhorn"
entity_category: config
optimistic: true
restore_mode: RESTORE_DEFAULT_ON
# Internal switch to track when a timer is ringing on the device.
- platform: template
id: timer_ringing
optimistic: true
internal: true
restore_mode: ALWAYS_OFF
on_turn_off:
# Disable stop wake word
- lambda: id(stop).disable();
# Stop any current annoucement (ie: stop the timer ring mid playback)
- if:
condition:
lambda: return id(nabu_media_player)->state == media_player::MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING;
then:
lambda: |-
id(nabu_media_player)
->make_call()
.set_command(media_player::MediaPlayerCommand::MEDIA_PLAYER_COMMAND_STOP)
.set_announcement(true)
.perform();
# Set back ducking ratio to zero
- nabu.set_ducking:
decibel_reduction: 0
duration: 1.0s
# Refresh the LED ring
- script.execute: control_leds
on_turn_on:
# Duck audio
- nabu.set_ducking:
decibel_reduction: 20
duration: 0.0s
# Enable stop wake word
- lambda: id(stop).enable();
# Ring timer
- script.execute: ring_timer
# Refresh LED
- script.execute: control_leds
# If 15 minutes have passed and the timer is still ringing, stop it.
- delay: 15min
- switch.turn_off: timer_ringing
- platform: gpio
pin: GPIO47
id: internal_speaker_amp
name: "Internal speaker amp"
entity_category: config
restore_mode: ALWAYS_OFF
internal: true
binary_sensor:
# Center Button. Used for many things (See on_multi_click)
- platform: gpio
id: center_button
pin:
number: GPIO0
inverted: true
on_press:
- script.execute: control_leds
on_release:
- script.execute: control_leds
on_multi_click:
# Simple Click:
# - Abort "things" in order
# - Timer
# - Announcements
# - Voice Assistant Pipeline run
# - Music
# - Starts the voice assistant if it is not yet running and if the device is not muted.
- timing:
- ON for at most 1s
- OFF for at least 0.25s
then:
- if:
condition:
lambda: return !id(init_in_progress) && !id(color_changed);
then:
- if:
condition:
switch.is_on: timer_ringing
then:
- switch.turn_off: timer_ringing
else:
- if:
condition:
lambda: return id(nabu_media_player)->state == media_player::MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING;
then:
- lambda: |
id(nabu_media_player)
->make_call()
.set_command(media_player::MediaPlayerCommand::MEDIA_PLAYER_COMMAND_STOP)
.set_announcement(true)
.perform();
else:
# - if:
# condition:
# voice_assistant.is_running:
# then:
# - voice_assistant.stop:
# else:
- if:
condition:
media_player.is_playing:
then:
- media_player.pause:
# else:
# - if:
# condition:
# and:
# - switch.is_off: master_mute_switch
# - not:
# voice_assistant.is_running
# then:
# - script.execute:
# id: play_sound
# priority: true
# sound_file: !lambda return id(center_button_press_sound);
# - delay: 300ms
# - voice_assistant.start:
# Double Click
# . Exposed as an event entity. To be used in automations inside Home Assistant
- timing:
- ON for at most 1s
- OFF for at most 0.25s
- ON for at most 1s
- OFF for at least 0.25s
then:
- if:
condition:
lambda: return !id(init_in_progress) && !id(color_changed);
then:
- script.execute:
id: play_sound
priority: false
sound_file: !lambda return id(center_button_double_press_sound);
- event.trigger:
id: button_press_event
event_type: "double_press"
# Triple Click
# . Exposed as an event entity. To be used in automations inside Home Assistant
- timing:
- ON for at most 1s
- OFF for at most 0.25s
- ON for at most 1s
- OFF for at most 0.25s
- ON for at most 1s
- OFF for at least 0.25s
then:
- if:
condition:
lambda: return !id(init_in_progress) && !id(color_changed);
then:
- script.execute:
id: play_sound
priority: false
sound_file: !lambda return id(center_button_triple_press_sound);
- event.trigger:
id: button_press_event
event_type: "triple_press"
# Long Press
# . Exposed as an event entity. To be used in automations inside Home Assistant
- timing:
- ON for at least 1s
then:
- if:
condition:
lambda: return !id(init_in_progress) && !id(color_changed);
then:
- script.execute:
id: play_sound
priority: false
sound_file: !lambda return id(center_button_long_press_sound);
- light.turn_off: voice_assistant_leds
- event.trigger:
id: button_press_event
event_type: "long_press"
# Very important do not remove. Trust me :D
- timing:
# H ....
- ON for at most 0.2s
- OFF for 0s to 2s
- ON for at most 0.2s
- OFF for 0s to 2s
- ON for at most 0.2s
- OFF for 0s to 2s
- ON for at most 0.2s
- OFF for 0.5s to 2s
# A ._
- ON for at most 0.2s
- OFF for 0s to 2s
- ON for 0.2s to 2s
then:
- if:
condition:
lambda: return !id(init_in_progress);
then:
- light.turn_on:
brightness: 100%
id: voice_assistant_leds
effect: "Tick"
- script.execute:
id: play_sound
priority: true
sound_file: !lambda return id(easter_egg_tick_sound);
- delay: 4s
- light.turn_off: voice_assistant_leds
- script.execute:
id: play_sound
priority: true
sound_file: !lambda return id(easter_egg_tada_sound);
- light.turn_on:
brightness: 100%
id: voice_assistant_leds
effect: "Rainbow"
- event.trigger:
id: button_press_event
event_type: "easter_egg_press"
# Factory Reset Warning
# . Audible and Visible warning.
- timing:
- ON for at least 10s
then:
- if:
condition:
lambda: return !id(dial_touched);
then:
- light.turn_on:
brightness: 100%
id: voice_assistant_leds
effect: "Factory Reset Coming Up"
- script.execute:
id: play_sound
priority: true
sound_file: !lambda return id(factory_reset_initiated_sound);
- wait_until:
binary_sensor.is_off: center_button
- light.turn_off: voice_assistant_leds
- script.execute:
id: play_sound
priority: true
sound_file: !lambda return id(factory_reset_cancelled_sound);
# Factory Reset
- timing:
- ON for at least 22s
then:
- if:
condition:
lambda: return !id(dial_touched);
then:
- button.press: factory_reset_button
# Hardware mute switch (Side of the device)
- platform: gpio
id: hardware_mute_switch
internal: true
pin: GPIO3
on_press:
- script.execute:
id: play_sound
priority: false
sound_file: !lambda return id(mute_switch_on_sound);
- switch.template.publish:
id: master_mute_switch
state: ON
on_release:
- script.execute:
id: play_sound
priority: false
sound_file: !lambda return id(mute_switch_off_sound);
- switch.template.publish:
id: master_mute_switch
state: OFF
# Audio Jack PLugged sensor
- platform: gpio
id: jack_plugged
# Debouncing it a bit because it can be activated back and forth as you plug the audio jack
filters:
- delayed_on: 200ms
- delayed_off: 200ms
pin:
number: GPIO17
# When the jack is plugged in:
# - LED animation
# - Sound played
on_press:
- lambda: id(jack_plugged_recently) = true;
- script.execute: control_leds
- delay: 200ms
- script.execute:
id: play_sound
priority: false
sound_file: !lambda return id(jack_connected_sound);
- delay: 800ms
- lambda: id(jack_plugged_recently) = false;
- script.execute: control_leds
# When the jack is unplugged:
# - LED animation
# - Sound played
on_release:
- lambda: id(jack_unplugged_recently) = true;
- script.execute: control_leds
- delay: 200ms
- script.execute:
id: play_sound
priority: false
sound_file: !lambda return id(jack_disconnected_sound);
- delay: 800ms
- lambda: id(jack_unplugged_recently) = false;
- script.execute: control_leds
light:
# Hardware LED ring. Not used because remapping needed
- platform: esp32_rmt_led_strip
id: leds_internal
pin: GPIO21
rmt_channel: 1
num_leds: 12
rgb_order: GRB
chipset: WS2812
default_transition_length: 0ms
power_supply: led_power
# Voice Assistant LED ring. Remapping of the internal LED.
# This light is not exposed. The device controls it
- platform: partition
id: voice_assistant_leds
internal: true
default_transition_length: 0ms
segments:
- id: leds_internal
from: 7
to: 11
- id: leds_internal
from: 0
to: 6
effects:
- addressable_lambda:
name: "Waiting for Command"
update_interval: 100ms
lambda: |-
auto light_color = id(led_ring).current_values;
Color color(light_color.get_red() * 255, light_color.get_green() * 255,
light_color.get_blue() * 255);
for (int i = 0; i < 12; i++) {
if (i == id(global_led_animation_index) % 12) {
it[i] = color;
} else if (i == (id(global_led_animation_index) + 11) % 12) {
it[i] = color * 192;
} else if (i == (id(global_led_animation_index) + 10) % 12) {
it[i] = color * 128;
} else if (i == (id(global_led_animation_index) + 6) % 12) {
it[i] = color;
} else if (i == (id(global_led_animation_index) + 5) % 12) {
it[i] = color * 192;
} else if (i == (id(global_led_animation_index) + 4) % 12) {
it[i] = color * 128;
} else {
it[i] = Color::BLACK;
}
}
id(global_led_animation_index) = (id(global_led_animation_index) + 1) % 12;
- addressable_lambda:
name: "Listening For Command"
update_interval: 50ms
lambda: |-
auto light_color = id(led_ring).current_values;
Color color(light_color.get_red() * 255, light_color.get_green() * 255,
light_color.get_blue() * 255);
for (int i = 0; i < 12; i++) {
if (i == id(global_led_animation_index) % 12) {
it[i] = color;
} else if (i == (id(global_led_animation_index) + 11) % 12) {
it[i] = color * 192;
} else if (i == (id(global_led_animation_index) + 10) % 12) {
it[i] = color * 128;
} else if (i == (id(global_led_animation_index) + 6) % 12) {
it[i] = color;
} else if (i == (id(global_led_animation_index) + 5) % 12) {
it[i] = color * 192;
} else if (i == (id(global_led_animation_index) + 4) % 12) {
it[i] = color * 128;
} else {
it[i] = Color::BLACK;
}
}
id(global_led_animation_index) = (id(global_led_animation_index) + 1) % 12;
- addressable_lambda:
name: "Thinking"
update_interval: 10ms
lambda: |-
static uint8_t brightness_step = 0;
static bool brightness_decreasing = true;
static uint8_t brightness_step_number = 10;
if (initial_run) {
brightness_step = 0;
brightness_decreasing = true;
}
auto light_color = id(led_ring).current_values;
Color color(light_color.get_red() * 255, light_color.get_green() * 255,
light_color.get_blue() * 255);
for (int i = 0; i < 12; i++) {
if (i == id(global_led_animation_index) % 12) {
it[i] = color * uint8_t(255/brightness_step_number*(brightness_step_number-brightness_step));
} else if (i == (id(global_led_animation_index) + 6) % 12) {
it[i] = color * uint8_t(255/brightness_step_number*(brightness_step_number-brightness_step));
} else {
it[i] = Color::BLACK;
}
}
if (brightness_decreasing) {
brightness_step++;
} else {
brightness_step--;
}
if (brightness_step == 0 || brightness_step == brightness_step_number) {
brightness_decreasing = !brightness_decreasing;
}
- addressable_lambda:
name: "Replying"
update_interval: 50ms
lambda: |-
id(global_led_animation_index) = (12 + id(global_led_animation_index) - 1) % 12;
auto light_color = id(led_ring).current_values;
Color color(light_color.get_red() * 255, light_color.get_green() * 255,
light_color.get_blue() * 255);
for (int i = 0; i < 12; i++) {
if (i == (id(global_led_animation_index)) % 12) {
it[i] = color;
} else if (i == ( id(global_led_animation_index) + 1) % 12) {
it[i] = color * 192;
} else if (i == ( id(global_led_animation_index) + 2) % 12) {
it[i] = color * 128;
} else if (i == ( id(global_led_animation_index) + 6) % 12) {
it[i] = color;
} else if (i == ( id(global_led_animation_index) + 7) % 12) {
it[i] = color * 192;
} else if (i == ( id(global_led_animation_index) + 8) % 12) {
it[i] = color * 128;
} else {
it[i] = Color::BLACK;
}
}
- addressable_lambda:
name: "Muted or Silent"
update_interval: 16ms
lambda: |-
static int8_t index = 0;
Color muted_color(255, 0, 0);
auto light_color = id(led_ring).current_values;
Color color(light_color.get_red() * 255, light_color.get_green() * 255,
light_color.get_blue() * 255);
for (int i = 0; i < 12; i++) {
if ( light_color.get_state() ) {
it[i] = color;
} else {
it[i] = Color::BLACK;
}
}
if ( id(master_mute_switch).state ) {
it[2] = Color::BLACK;
it[3] = muted_color;
it[4] = Color::BLACK;
it[8] = Color::BLACK;
it[9] = muted_color;
it[10] = Color::BLACK;
}
if ( id(nabu_media_player).volume == 0.0f || id(nabu_media_player).is_muted() ) {
it[5] = Color::BLACK;
it[6] = muted_color;
it[7] = Color::BLACK;
}
- addressable_lambda:
name: "Volume Display"
update_interval: 50ms
lambda: |-
auto light_color = id(led_ring).current_values;
Color color(light_color.get_red() * 255, light_color.get_green() * 255,
light_color.get_blue() * 255);
Color silenced_color(255, 0, 0);
auto volume_ratio = 12.0f * id(nabu_media_player).volume;
for (int i = 0; i < 12; i++) {
if (i <= volume_ratio) {
it[(6+i)%12] = color * min( 255.0f * (volume_ratio - i) , 255.0f ) ;
} else {
it[(6+i)%12] = Color::BLACK;
}
}
if (id(nabu_media_player).volume == 0.0f) {
it[6] = silenced_color;
}
- addressable_lambda:
name: "Center Button Touched"
update_interval: 16ms
lambda: |-
if (initial_run) {
// set voice_assistant_leds light to colors based on led_ring
auto led_ring_cv = id(led_ring).current_values;
auto va_leds_call = id(voice_assistant_leds).make_call();
va_leds_call.from_light_color_values(led_ring_cv);
va_leds_call.set_brightness( min ( max( id(led_ring).current_values.get_brightness() , 0.2f ) + 0.1f , 1.0f ) );
va_leds_call.set_state(true);
va_leds_call.perform();
}
auto light_color = id(voice_assistant_leds).current_values;
Color color(light_color.get_red() * 255, light_color.get_green() * 255,
light_color.get_blue() * 255);
for (int i = 0; i < 12; i++) {
it[i] = color;
}
- addressable_twinkle:
name: "Twinkle"
twinkle_probability: 50%
- addressable_lambda:
name: "Error"
update_interval: 10ms
lambda: |-
static uint8_t brightness_step = 0;
static bool brightness_decreasing = true;
static uint8_t brightness_step_number = 10;
if (initial_run) {
brightness_step = 0;
brightness_decreasing = true;
}
Color error_color(255, 0, 0);
for (int i = 0; i < 12; i++) {
it[i] = error_color * uint8_t(255/brightness_step_number*(brightness_step_number-brightness_step));
}
if (brightness_decreasing) {
brightness_step++;
} else {
brightness_step--;
}
if (brightness_step == 0 || brightness_step == brightness_step_number) {
brightness_decreasing = !brightness_decreasing;
}
- addressable_lambda:
name: "Timer Ring"
update_interval: 10ms
lambda: |-
static uint8_t brightness_step = 0;
static bool brightness_decreasing = true;
static uint8_t brightness_step_number = 10;
if (initial_run) {
brightness_step = 0;
brightness_decreasing = true;
}
auto light_color = id(led_ring).current_values;
Color color(light_color.get_red() * 255, light_color.get_green() * 255,
light_color.get_blue() * 255);
Color muted_color(255, 0, 0);
for (int i = 0; i < 12; i++) {
it[i] = color * uint8_t(255/brightness_step_number*(brightness_step_number-brightness_step));
}
if ( id(master_mute_switch).state ) {
it[3] = muted_color;
it[9] = muted_color;
}
if (brightness_decreasing) {
brightness_step++;
} else {
brightness_step--;
}
if (brightness_step == 0 || brightness_step == brightness_step_number) {
brightness_decreasing = !brightness_decreasing;
}
- addressable_lambda:
name: "Timer Tick"
update_interval: 100ms
lambda: |-
auto light_color = id(led_ring).current_values;
Color color(light_color.get_red() * 255, light_color.get_green() * 255,
light_color.get_blue() * 255);
Color muted_color(255, 0, 0);
auto timer_ratio = 12.0f * id(first_active_timer).seconds_left / max(id(first_active_timer).total_seconds , static_cast<uint32_t>(1));
uint8_t last_led_on = static_cast<uint8_t>(ceil(timer_ratio)) - 1;
for (int i = 0; i < 12; i++) {
float brightness_dip = ( i == id(global_led_animation_index) % 12 && i != last_led_on ) ? 0.9f : 1.0f ;
if (i <= timer_ratio) {
it[i] = color * min(255.0f * brightness_dip * (timer_ratio - i) , 255.0f * brightness_dip) ;
} else {
it[i] = Color::BLACK;
}
}
if (id(master_mute_switch).state) {
it[2] = Color::BLACK;
it[3] = muted_color;
it[4] = Color::BLACK;
it[8] = Color::BLACK;
it[9] = muted_color;
it[10] = Color::BLACK;
}
id(global_led_animation_index) = (12 + id(global_led_animation_index) - 1) % 12;
- addressable_rainbow:
name: "Rainbow"
width: 12
- addressable_lambda:
name: "Tick"
update_interval: 333ms
lambda: |-
static uint8_t index = 0;
Color color(255, 0, 0);
if (initial_run) {
index = 0;
}
for (int i = 0; i < 12; i++) {
if (i <= index ) {
it[i] = Color::BLACK;
} else {
it[i] = color;
}
}
index = (index + 1) % 12;
- addressable_lambda:
name: "Factory Reset Coming Up"
update_interval: 1s
lambda: |-
static uint8_t index = 0;
Color color(255, 0, 0);
if (initial_run) {
index = 0;
}
for (int i = 0; i < 12; i++) {
if (i <= index ) {
it[i] = color;
} else {
it[i] = Color::BLACK;
}
}
index = (index + 1) % 12;
- addressable_lambda:
name: "Jack Plugged"
update_interval: 40ms
lambda: |-
static uint8_t index = 0;
if (initial_run) {
index = 0;
}
auto light_color = id(led_ring).current_values;
Color color(light_color.get_red() * 255, light_color.get_green() * 255,
light_color.get_blue() * 255);
if (index <= 6) {
for (int i = 0; i < 12; i++) {
if (i == index) {
it[i] = color;
} else if (i == (12 - index) % 12) {
it[i] = color;
} else {
it[i] = Color::BLACK;
}
}
}
index = (index + 1);
- addressable_lambda:
name: "Jack Unplugged"
update_interval: 40ms
lambda: |-
static uint8_t index = 0;
if (initial_run) {
index = 0;
}
auto light_color = id(led_ring).current_values;
Color color(light_color.get_red() * 255, light_color.get_green() * 255,
light_color.get_blue() * 255);
if (index <= 6) {
for (int i = 0; i < 12; i++) {
if (i == 6 - index) {
it[i] = color;
} else if (i == (6 + index) % 12) {
it[i] = color;
} else {
it[i] = Color::BLACK;
}
}
}
index = (index + 1);
# User facing LED ring. Remapping of the internal LEDs.
# Exposed to be used by the user.
- platform: partition
id: led_ring
name: LED Ring
entity_category: config
icon: "mdi:circle-outline"
default_transition_length: 0ms
restore_mode: RESTORE_DEFAULT_OFF
initial_state:
color_mode: rgb
brightness: 66%
red: 9.4%
green: 73.3%
blue: 94.9%
segments:
- id: leds_internal
from: 7
to: 11
- id: leds_internal
from: 0
to: 6
power_supply:
- id: led_power
pin: GPIO45
sensor:
# The dial. Used to control volume and Hue of the LED ring.
- platform: rotary_encoder
id: dial
pin_a: GPIO16
pin_b: GPIO18
resolution: 2
on_clockwise:
- lambda: id(dial_touched) = true;
- if:
condition:
binary_sensor.is_off: center_button
then:
- script.execute:
id: control_volume
increase_volume: true
else:
- script.execute:
id: control_hue
increase_hue: true
on_anticlockwise:
- lambda: id(dial_touched) = true;
- if:
condition:
binary_sensor.is_off: center_button
then:
- script.execute:
id: control_volume
increase_volume: false
else:
- script.execute:
id: control_hue
increase_hue: false
event:
# Event entity exposed to the user to automate on complex center button presses.
# The simple press is not exposed as it is used to control the device itself.
- platform: template
id: button_press_event
name: "Button press"
icon: mdi:button-pointer
device_class: button
event_types:
- double_press
- triple_press
- long_press
- easter_egg_press
script:
# Master script controlling the LEDs, based on different conditions : initialization in progress, wifi and api connected and voice assistant phase.
# For the sake of simplicity and re-usability, the script calls child scripts defined below.
# This script will be called every time one of these conditions is changing.
- id: control_leds
then:
- lambda: |
id(check_if_timers_active).execute();
if (id(is_timer_active)){
id(fetch_first_active_timer).execute();
}
if (id(improv_ble_in_progress)) {
id(control_leds_improv_ble_state).execute();
} else if (id(init_in_progress)) {
id(control_leds_init_state).execute();
} else if (!id(wifi_id).is_connected() || !id(api_id).is_connected()){
id(control_leds_no_ha_connection_state).execute();
} else if (id(center_button).state) {
id(control_leds_center_button_touched).execute();
} else if (id(jack_plugged_recently)) {
id(control_leds_jack_plugged_recently).execute();
} else if (id(jack_unplugged_recently)) {
id(control_leds_jack_unplugged_recently).execute();
} else if (id(dial_touched)) {
id(control_leds_dial_touched).execute();
} else if (id(timer_ringing).state) {
id(control_leds_timer_ringing).execute();
} else if (id(voice_assistant_phase) == ${voice_assist_waiting_for_command_phase_id}) {
id(control_leds_voice_assistant_waiting_for_command_phase).execute();
} else if (id(voice_assistant_phase) == ${voice_assist_listening_for_command_phase_id}) {
id(control_leds_voice_assistant_listening_for_command_phase).execute();
} else if (id(voice_assistant_phase) == ${voice_assist_thinking_phase_id}) {
id(control_leds_voice_assistant_thinking_phase).execute();
} else if (id(voice_assistant_phase) == ${voice_assist_replying_phase_id}) {
id(control_leds_voice_assistant_replying_phase).execute();
} else if (id(voice_assistant_phase) == ${voice_assist_error_phase_id}) {
id(control_leds_voice_assistant_error_phase).execute();
} else if (id(voice_assistant_phase) == ${voice_assist_not_ready_phase_id}) {
id(control_leds_voice_assistant_not_ready_phase).execute();
} else if (id(is_timer_active)) {
id(control_leds_timer_ticking).execute();
} else if (id(master_mute_switch).state) {
id(control_leds_muted_or_silent).execute();
} else if (id(nabu_media_player).volume == 0.0f || id(nabu_media_player).is_muted()) {
id(control_leds_muted_or_silent).execute();
} else if (id(voice_assistant_phase) == ${voice_assist_idle_phase_id}) {
id(control_leds_voice_assistant_idle_phase).execute();
}
# Script executed during Improv BLE
# Warm White Twinkle
- id: control_leds_improv_ble_state
then:
- light.turn_on:
brightness: 66%
red: 100%
green: 89%
blue: 71%
id: voice_assistant_leds
effect: "Twinkle"
# Script executed during initialization
# Blue Twinkle if Wifi is connected, Else solid warm white
- id: control_leds_init_state
then:
- if:
condition:
wifi.connected:
then:
- light.turn_on:
brightness: 66%
red: 9.4%
green: 73.3%
blue: 94.9%
id: voice_assistant_leds
effect: "Twinkle"
else:
- light.turn_on:
brightness: 66%
red: 100%
green: 89%
blue: 71%
id: voice_assistant_leds
effect: "none"
# Script executed when the device has no connection to Home Assistant
# Red Twinkle (This will be visible during HA updates for example)
- id: control_leds_no_ha_connection_state
then:
- light.turn_on:
brightness: 66%
red: 1
green: 0
blue: 0
id: voice_assistant_leds
effect: "Twinkle"
# Script executed when the voice assistant is idle (waiting for a wake word)
# Nothing (Either LED ring off or LED ring on if the user decided to turn the user facing LED ring on)
- id: control_leds_voice_assistant_idle_phase
then:
- light.turn_off: voice_assistant_leds
- if:
condition:
light.is_on: led_ring
then:
light.turn_on: led_ring
# Script executed when the voice assistant is waiting for a command (After the wake word)
# Slow clockwise spin of the LED ring.
- id: control_leds_voice_assistant_waiting_for_command_phase
then:
- light.turn_on:
brightness: !lambda return max( id(led_ring).current_values.get_brightness() , 0.2f );
id: voice_assistant_leds
effect: "Waiting for Command"
# Script executed when the voice assistant is listening to a command
# Fast clockwise spin of the LED ring.
- id: control_leds_voice_assistant_listening_for_command_phase
then:
- light.turn_on:
brightness: !lambda return max( id(led_ring).current_values.get_brightness() , 0.2f );
id: voice_assistant_leds
effect: "Listening For Command"
# Script executed when the voice assistant is thinking to a command
# The spin stops and the 2 LEDs that are currently on and blinking indicating the commend is being processed.
- id: control_leds_voice_assistant_thinking_phase
then:
- light.turn_on:
brightness: !lambda return max( id(led_ring).current_values.get_brightness() , 0.2f );
id: voice_assistant_leds
effect: "Thinking"
# Script executed when the voice assistant is thinking to a command
# Fast anticlockwise spin of the LED ring.
- id: control_leds_voice_assistant_replying_phase
then:
- light.turn_on:
brightness: !lambda return max( id(led_ring).current_values.get_brightness() , 0.2f );
id: voice_assistant_leds
effect: "Replying"
# Script executed when the voice assistant is in error
# Fast Red Pulse
- id: control_leds_voice_assistant_error_phase
then:
- light.turn_on:
brightness: !lambda return min ( max( id(led_ring).current_values.get_brightness() , 0.2f ) + 0.1f , 1.0f );
red: 1
green: 0
blue: 0
id: voice_assistant_leds
effect: "Error"
# Script executed when the voice assistant is muted or silent
# The LED next to the 2 microphones turn red / one red LED next to the speaker grill
- id: control_leds_muted_or_silent
then:
- light.turn_on:
brightness: !lambda return max( id(led_ring).current_values.get_brightness() , 0.2f );
id: voice_assistant_leds
effect: "Muted or Silent"
# Script executed when the voice assistant is not ready
- id: control_leds_voice_assistant_not_ready_phase
then:
- light.turn_on:
brightness: 66%
red: 1
green: 0
blue: 0
id: voice_assistant_leds
effect: "Twinkle"
# Script executed when the dial is touched
# A number of LEDs turn on indicating a visual representation of the volume of the media player entity.
- id: control_leds_dial_touched
then:
- light.turn_on:
brightness: !lambda return max( id(led_ring).current_values.get_brightness() , 0.2f );
id: voice_assistant_leds
effect: "Volume Display"
# Script executed when the jack has just been unplugged
# A ripple effect
- id: control_leds_jack_unplugged_recently
then:
- light.turn_on:
brightness: !lambda return max( id(led_ring).current_values.get_brightness() , 0.2f );
id: voice_assistant_leds
effect: "Jack Unplugged"
# Script executed when the jack has just been plugged
# A ripple effect
- id: control_leds_jack_plugged_recently
then:
- light.turn_on:
brightness: !lambda return max( id(led_ring).current_values.get_brightness() , 0.2f );
id: voice_assistant_leds
effect: "Jack Plugged"
# Script executed when the center button is touched
# The complete LED ring turns on
- id: control_leds_center_button_touched
then:
- light.turn_on:
brightness: !lambda return min ( max( id(led_ring).current_values.get_brightness() , 0.2f ) + 0.1f , 1.0f );
id: voice_assistant_leds
effect: "Center Button Touched"
# Script executed when the timer is ringing, to control the LEDs
# The LED ring blinks.
- id: control_leds_timer_ringing
then:
- light.turn_on:
brightness: !lambda return min ( max( id(led_ring).current_values.get_brightness() , 0.2f ) + 0.1f , 1.0f );
id: voice_assistant_leds
effect: "Timer Ring"
# Script executed when the timer is ticking, to control the LEDs
# The LEDs shows the remaining time as a fraction of the full ring.
- id: control_leds_timer_ticking
then:
- light.turn_on:
brightness: !lambda return max( id(led_ring).current_values.get_brightness() , 0.2f );
id: voice_assistant_leds
effect: "Timer tick"
# Script executed when the volume is increased/decreased from the dial
- id: control_volume
mode: restart
parameters:
increase_volume: bool # True: Increase volume / False: Decrease volume.
then:
- delay: 16ms
- if:
condition:
lambda: return increase_volume;
then:
- media_player.volume_up:
else:
- media_player.volume_down:
- script.execute: control_leds
- delay: 1s
- lambda: id(dial_touched) = false;
- sensor.rotary_encoder.set_value:
id: dial
value: 0
- script.execute: control_leds
# Script executed when the hue is increased/decreased from the dial
- id: control_hue
mode: restart
parameters:
increase_hue: bool # True: Increase hue / False: Decrease hue.
then:
- delay: 16ms
- if:
condition:
lambda: return(abs(int(id(dial).state)) > 3 || id(color_changed));
then:
- lambda: |
id(color_changed) = true;
auto light_color = id(voice_assistant_leds).current_values;
int hue = 0;
float saturation = 0;
float value = 0;
rgb_to_hsv( light_color.get_red(),
light_color.get_green(),
light_color.get_blue(),
hue,
saturation,
value);
if (increase_hue) {
hue = (hue + 10) % 360;
} else {
hue = (hue + 350) % 360;
}
if (saturation < 0.05) {
saturation = 1;
}
float red = 0;
float green = 0;
float blue = 0;
hsv_to_rgb( hue,
saturation,
value,
red,
green,
blue);
id(voice_assistant_leds).make_call().set_rgb(red, green, blue).perform();
- wait_until:
binary_sensor.is_off: center_button
- lambda: |
id(dial_touched) = false;
// now we "save" the new LED color/state to led_ring, maintaining its brightness and state
auto led_ring_call = id(led_ring).make_call();
auto va_leds_cv = id(voice_assistant_leds).current_values;
led_ring_call.from_light_color_values(va_leds_cv);
led_ring_call.set_brightness(id(led_ring).current_values.get_brightness());
led_ring_call.set_state(id(led_ring).current_values.is_on());
led_ring_call.perform();
- sensor.rotary_encoder.set_value:
id: dial
value: 0
- script.execute: control_leds
- delay: 500ms
- lambda: id(color_changed) = false;
# Script executed when the timer is ringing, to playback sounds.
- id: ring_timer
then:
- while:
condition:
switch.is_on: timer_ringing
then:
- script.execute:
id: play_sound
priority: true
sound_file: !lambda return id(timer_finished_sound);
- wait_until:
lambda: |-
return id(nabu_media_player)->state == media_player::MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING;
- wait_until:
not:
lambda: |-
return id(nabu_media_player)->state == media_player::MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING;
# Script executed when we want to play sounds on the device.
- id: play_sound
parameters:
priority: bool
sound_file: "media_player::MediaFile*"
then:
- lambda: |-
if (priority) {
id(nabu_media_player)
->make_call()
.set_command(media_player::MediaPlayerCommand::MEDIA_PLAYER_COMMAND_STOP)
.set_announcement(true)
.perform();
}
if ( (id(nabu_media_player).state != media_player::MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING ) || priority) {
id(nabu_media_player)
->make_call()
.set_announcement(true)
.set_local_media_file(sound_file)
.perform();
}
# Script used to fetch the first active timer (Stored in global first_active_timer)
- id: fetch_first_active_timer
then:
- lambda: |
const auto timers = id(va).get_timers();
auto output_timer = timers.begin()->second;
for (auto &iterable_timer : timers) {
if (iterable_timer.second.is_active && iterable_timer.second.seconds_left <= output_timer.seconds_left) {
output_timer = iterable_timer.second;
}
}
id(first_active_timer) = output_timer;
# Script used to check if a timer is active (Stored in global is_timer_active)
- id: check_if_timers_active
then:
- lambda: |
const auto timers = id(va).get_timers();
bool output = false;
if (timers.size() > 0) {
for (auto &iterable_timer : timers) {
if(iterable_timer.second.is_active) {
output = true;
}
}
}
id(is_timer_active) = output;
# Script used activate the stop word if the TTS step is long.
# Why is this wrapped on a script?
# Becasue we want to stop the sequence if the TTS step is faster than that.
# This allows us to prevent having the deactivation of the stop word before its own activation.
- id: activate_stop_word_if_tts_step_is_long
then:
- delay: 1s
# Enable stop wake word
- lambda: id(stop).enable();
# Script to handle the end of the streaming wake word voice assistant.
# Should we restart it? Shall we keep it stopped?
- id: handle_voice_assistant_end
then:
- if:
condition:
switch.is_off: master_mute_switch
then:
- voice_assistant.start_continuous:
else:
- voice_assistant.stop:
i2s_audio:
- id: i2s_output
# i2s_output data pin is gpio10
i2s_lrclk_pin:
number: GPIO7
i2s_bclk_pin:
number: GPIO8
- id: i2s_input
# data line is GPIO15
i2s_lrclk_pin:
number: GPIO14
i2s_bclk_pin:
number: GPIO13
microphone:
- platform: nabu_microphone
i2s_din_pin: GPIO15
adc_type: external
pdm: false
sample_rate: 16000
bits_per_sample: 32bit
i2s_mode: secondary
i2s_audio_id: i2s_input
channel_0:
id: asr_mic
amplify_shift: 0
channel_1:
id: comm_mic
amplify_shift: 2
speaker:
- platform: i2s_audio
sample_rate: 48000
i2s_mode: secondary
i2s_dout_pin: GPIO10
bits_per_sample: 32bit
i2s_audio_id: i2s_output
dac_type: external
channel: stereo
timeout: never
buffer_duration: 100ms
media_player:
- platform: nabu
id: nabu_media_player
name: Media Player
internal: false
audio_dac:
speaker:
sample_rate: 48000
volume_increment: 0.05
volume_min: 0.4
volume_max: 0.85
on_mute:
- script.execute: control_leds
on_unmute:
- script.execute: control_leds
on_volume:
- script.execute: control_leds
on_announcement:
- nabu.set_ducking:
decibel_reduction: 20
duration: 0.0s
on_state:
if:
condition:
and:
- switch.is_off: timer_ringing
- not:
voice_assistant.is_running:
- not:
lambda: return id(nabu_media_player)->state == media_player::MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING;
then:
- nabu.set_ducking:
decibel_reduction: 0
duration: 1.0s
files:
- id: center_button_press_sound
file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/center_button_press.flac
- id: center_button_double_press_sound
file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/center_button_double_press.flac
- id: center_button_triple_press_sound
file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/center_button_triple_press.flac
- id: center_button_long_press_sound
file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/center_button_long_press.flac
- id: factory_reset_initiated_sound
file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/factory_reset_initiated.mp3
- id: factory_reset_cancelled_sound
file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/factory_reset_cancelled.mp3
- id: jack_connected_sound
file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/jack_connected.flac
- id: jack_disconnected_sound
file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/jack_disconnected.flac
- id: mute_switch_on_sound
file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/mute_switch_on.flac
- id: mute_switch_off_sound
file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/mute_switch_off.flac
- id: timer_finished_sound
file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/timer_finished.flac
- id: wake_word_triggered_sound
file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/wake_word_triggered.flac
- id: easter_egg_tick_sound
file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/easter_egg_tick.mp3
- id: easter_egg_tada_sound
file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/easter_egg_tada.mp3
voice_kit:
reset_pin: GPIO4
firmware:
url: https://github.com/esphome/voice-kit-xmos-firmware/releases/download/v1.3.1/ffva_v1.3.1_upgrade.bin
version: "1.3.1"
md5: 964635c5bf125529dab14a2472a15401
external_components:
- source:
type: git
url: https://github.com/esphome/voice-kit
ref: dev
components:
- aic3204
- audio_dac
- media_player
- micro_wake_word
- microphone
- nabu
- nabu_microphone
- voice_assistant
- voice_kit
refresh: 0s
audio_dac:
- platform: aic3204
micro_wake_word:
id: mww
models:
# - model: okay_nabu
# id: okay_nabu
# - model: hey_jarvis
# id: hey_jarvis
# - model: hey_mycroft
# id: hey_mycroft
- model: https://github.com/kahrendt/microWakeWord/releases/download/stop/stop.json
id: stop
internal: true
vad:
microphone: comm_mic
on_wake_word_detected:
# If the wake word is detected when the device is muted (Possible with the software mute switch): Do nothing
- if:
condition:
switch.is_off: master_mute_switch
then:
# If a timer is ringing: Stop it, do not start the voice assistant (We can stop timer from voice!)
- if:
condition:
switch.is_on: timer_ringing
then:
- switch.turn_off: timer_ringing
# Start voice assistant, stop current announcement.
else:
- if:
condition:
lambda: return id(nabu_media_player)->state == media_player::MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING;
then:
lambda: |-
id(nabu_media_player)
->make_call()
.set_command(media_player::MediaPlayerCommand::MEDIA_PLAYER_COMMAND_STOP)
.set_announcement(true)
.perform();
# else:
# - if:
# condition:
# switch.is_on: wake_sound
# then:
# - script.execute:
# id: play_sound
# priority: true
# sound_file: !lambda return id(wake_word_triggered_sound);
# - delay: 300ms
# - voice_assistant.start:
# wake_word: !lambda return wake_word;
voice_assistant:
id: va
microphone: asr_mic
media_player: nabu_media_player
# micro_wake_word: mww
# use_wake_word: false
use_wake_word: true
noise_suppression_level: 0
auto_gain: 0 dbfs
volume_multiplier: 1
on_client_connected:
- lambda: id(init_in_progress) = false;
- micro_wake_word.start:
- voice_assistant.start_continuous:
- lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
- script.execute: control_leds
on_client_disconnected:
- voice_assistant.stop:
- lambda: id(voice_assistant_phase) = ${voice_assist_not_ready_phase_id};
- script.execute: control_leds
on_error:
- if:
condition:
and:
- lambda: return !id(init_in_progress);
- lambda: return code != "duplicate_wake_up_detected";
then:
- lambda: id(voice_assistant_phase) = ${voice_assist_error_phase_id};
- script.execute: control_leds
- delay: 1s
- lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
- script.execute: control_leds
on_listening:
- if:
condition:
switch.is_on: wake_sound
then:
- script.execute:
id: play_sound
priority: true
sound_file: !lambda return id(wake_word_triggered_sound);
# When the voice assistant starts: Play a wake up sound, duck audio.
- nabu.set_ducking:
decibel_reduction: 20 # Number of dB quieter; higher implies more quiet, 0 implies full volume
duration: 0.0s # The duration of the transition (default is 0)
- lambda: id(voice_assistant_phase) = ${voice_assist_waiting_for_command_phase_id};
- script.execute: control_leds
on_stt_vad_start:
- lambda: id(voice_assistant_phase) = ${voice_assist_listening_for_command_phase_id};
- script.execute: control_leds
on_stt_vad_end:
- lambda: id(voice_assistant_phase) = ${voice_assist_thinking_phase_id};
- script.execute: control_leds
on_tts_start:
- lambda: id(voice_assistant_phase) = ${voice_assist_replying_phase_id};
- script.execute: control_leds
# Start a script that would potentially enable the stop word if the response is longer than a second
- script.execute: activate_stop_word_if_tts_step_is_long
- wait_until:
lambda: |-
return id(nabu_media_player)->state == media_player::MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING;
- wait_until:
not:
lambda: |-
return id(nabu_media_player)->state == media_player::MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING;
- lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
- script.execute: control_leds
# When the voice assistant ends ...
on_end:
- wait_until:
not:
voice_assistant.is_running:
# Stop ducking audio.
- nabu.set_ducking:
decibel_reduction: 0 # 0 dB means no reduction
duration: 1.0s
# Stop the script that would potentially enable the stop word if the response is longer than a second
- script.stop: activate_stop_word_if_tts_step_is_long
# Disable the stop word (If the tiemr is not ringing)
- if:
condition:
switch.is_off: timer_ringing
then:
- lambda: id(stop).disable();
- delay: 1s
# Reset the voice assistant phase id and reset the LED animations.
- lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
- script.execute: control_leds
- script.execute: handle_voice_assistant_end
on_timer_finished:
- switch.turn_on: timer_ringing
on_timer_started:
- script.execute: control_leds
on_timer_cancelled:
- script.execute: control_leds
on_timer_updated:
- script.execute: control_leds
on_timer_tick:
- script.execute: control_leds
button:
- platform: factory_reset
id: factory_reset_button
name: "Factory Reset"
entity_category: diagnostic
internal: true
debug:
update_interval: 5s
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment