1609 lines
54 KiB
YAML
1609 lines
54 KiB
YAML
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'
|
|
# Change this to true in case you ahve a hidden SSID at home.
|
|
hidden_ssid: "false"
|
|
# Substitutions for audio files
|
|
mute_switch_on_sound_file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/mute_switch_on.flac
|
|
mute_switch_off_sound_file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/mute_switch_off.flac
|
|
timer_finished_sound_file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/timer_finished.flac
|
|
wake_word_triggered_sound_file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/wake_word_triggered.flac
|
|
center_button_press_sound_file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/center_button_press.flac
|
|
center_button_double_press_sound_file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/center_button_double_press.flac
|
|
center_button_triple_press_sound_file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/center_button_triple_press.flac
|
|
center_button_long_press_sound_file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/center_button_long_press.flac
|
|
factory_reset_initiated_sound_file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/factory_reset_initiated.mp3
|
|
factory_reset_cancelled_sound_file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/factory_reset_cancelled.mp3
|
|
factory_reset_confirmed_sound_file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/factory_reset_confirmed.mp3
|
|
error_cloud_expired_sound_file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/error_cloud_expired.mp3
|
|
|
|
|
|
esphome:
|
|
project:
|
|
name: formatbce.Respeaker Lite Satellite
|
|
version: 2026.5.0
|
|
min_version: 2026.5.0
|
|
on_boot:
|
|
- priority: 375
|
|
then:
|
|
- sensor.template.publish:
|
|
id: next_timer
|
|
state: -1
|
|
# Run the script to refresh the LED status
|
|
- script.execute: control_leds
|
|
# 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
|
|
- priority: -100
|
|
then:
|
|
- lambda: |-
|
|
auto call = id(alarm_action).make_call();
|
|
call.set_option(id(saved_alarm_action));
|
|
call.perform();
|
|
- lambda: |-
|
|
setenv("TZ", id(saved_time_zone).c_str(), 1);
|
|
tzset();
|
|
on_shutdown:
|
|
then:
|
|
# Prevent loud noise on software restart
|
|
- lambda: id(respeaker).mute_speaker();
|
|
|
|
esp32:
|
|
board: esp32-s3-devkitc-1
|
|
cpu_frequency: 240MHz
|
|
variant: esp32s3
|
|
flash_size: 8MB
|
|
framework:
|
|
type: esp-idf
|
|
version: recommended
|
|
components:
|
|
- espressif/esp-nn==1.1.2 # TODO remove this when MWW stops failing
|
|
sdkconfig_options:
|
|
CONFIG_ESP32S3_DATA_CACHE_64KB: "y"
|
|
CONFIG_ESP32S3_DATA_CACHE_LINE_64B: "y"
|
|
CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB: "y"
|
|
|
|
# Moves instructions and read only data from flash into PSRAM on boot.
|
|
# Both enabled allows instructions to execute while a flash operation is in progress without needing to be placed in IRAM.
|
|
# Considerably speeds up mWW at the cost of using more PSRAM.
|
|
CONFIG_SPIRAM_RODATA: "y"
|
|
CONFIG_SPIRAM_FETCH_INSTRUCTIONS: "y"
|
|
|
|
CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST: "y"
|
|
CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY: "y"
|
|
|
|
CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC: "y"
|
|
CONFIG_MBEDTLS_SSL_PROTO_TLS1_3: "y" # TLS1.3 support isn't enabled by default in IDF 5.1.5
|
|
|
|
wifi:
|
|
id: wifi_id
|
|
fast_connect: ${hidden_ssid}
|
|
on_connect:
|
|
- lambda: id(improv_ble_in_progress) = false;
|
|
- script.execute: control_leds
|
|
on_disconnect:
|
|
- script.execute: control_leds
|
|
|
|
network:
|
|
enable_ipv6: true
|
|
|
|
logger:
|
|
level: debug
|
|
initial_level: debug
|
|
logs:
|
|
sensor: WARN # avoids logging debug sensor updates
|
|
# hardware_uart: uart0 #Uncomment to see serial logs via USB connection. Comment out after debufgging - this line introduces noise on speaker...
|
|
|
|
api:
|
|
id: api_id
|
|
actions:
|
|
- action: start_va
|
|
then:
|
|
- voice_assistant.start
|
|
- action: stop_va
|
|
then:
|
|
- voice_assistant.stop
|
|
- action: set_time_zone
|
|
variables:
|
|
posix_time_zone: string
|
|
then:
|
|
- lambda: |-
|
|
setenv("TZ", posix_time_zone.c_str(), 1);
|
|
tzset();
|
|
id(saved_time_zone) = posix_time_zone;
|
|
id(publish_current_time).execute();
|
|
|
|
on_client_connected:
|
|
- script.execute: control_leds
|
|
on_client_disconnected:
|
|
- script.execute: control_leds
|
|
encryption:
|
|
|
|
ota:
|
|
- platform: esphome
|
|
id: ota_esphome
|
|
password: !secret ota_password
|
|
|
|
i2c:
|
|
- id: internal_i2c
|
|
sda: GPIO5
|
|
scl: GPIO6
|
|
frequency: 400kHz
|
|
|
|
psram:
|
|
mode: octal
|
|
speed: 80MHz
|
|
ignore_not_found: false # The VPE has PSRAM, so this is safe. Allows configuring WiFi driver to use more resources (done automatically by the speaker media player)
|
|
|
|
globals:
|
|
- 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}
|
|
- id: saved_time_zone
|
|
type: std::string
|
|
restore_value: yes
|
|
initial_value: '"UTC0"'
|
|
- id: saved_alarm_action
|
|
type: std::string
|
|
restore_value: yes
|
|
initial_value: '"Play sound"'
|
|
# Global variable storing the first active timer
|
|
- id: first_active_timer
|
|
type: voice_assistant::Timer
|
|
restore_value: no
|
|
# Global variable storing if a timer is active
|
|
- id: is_timer_active
|
|
type: bool
|
|
restore_value: no
|
|
# Global variable storing if a factory reset was requested. If it is set to true, the device will factory reset once the center button is released
|
|
- id: factory_reset_requested
|
|
type: bool
|
|
restore_value: no
|
|
initial_value: 'false'
|
|
|
|
# Time sync from Home Assistant
|
|
time:
|
|
- platform: homeassistant
|
|
id: homeassistant_time
|
|
on_time:
|
|
# Every 1 minute
|
|
- seconds: 0
|
|
then:
|
|
- script.execute: check_alarm
|
|
on_time_sync:
|
|
- script.execute: publish_current_time
|
|
|
|
datetime:
|
|
# schedule the alarm time from Home Assistant
|
|
- platform: template
|
|
icon: mdi:bell-ring
|
|
name: "Alarm time"
|
|
id: alarm_time
|
|
type: time
|
|
initial_value: "00:00:00"
|
|
restore_value: true
|
|
optimistic: true
|
|
internal: false
|
|
set_action:
|
|
then:
|
|
- switch.turn_on: alarm_on
|
|
|
|
|
|
switch:
|
|
# Hardware speaker mute
|
|
- platform: template
|
|
id: speaker_mute_switch
|
|
name: Speaker mute
|
|
icon: mdi:volume-mute
|
|
internal: true
|
|
optimistic: true
|
|
turn_on_action:
|
|
- lambda: id(respeaker).mute_speaker();
|
|
turn_off_action:
|
|
- lambda: id(respeaker).unmute_speaker();
|
|
# stateless momentary mic mute switch
|
|
- platform: gpio
|
|
internal: true
|
|
pin:
|
|
number: GPIO4 # D3
|
|
inverted: true
|
|
id: mute_toggle
|
|
on_turn_on:
|
|
- delay: 300ms
|
|
- switch.turn_off: mute_toggle
|
|
# stateful user facing mic mute switch
|
|
- platform: template
|
|
id: mic_mute_switch
|
|
name: Mic mute
|
|
icon: mdi:microphone-off
|
|
optimistic: false
|
|
lambda: |-
|
|
if (id(mute_state).state) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
on_turn_on:
|
|
- if:
|
|
condition:
|
|
and:
|
|
- lambda: return !id(init_in_progress);
|
|
- switch.is_on: mute_sound
|
|
then:
|
|
- script.execute:
|
|
id: play_sound
|
|
priority: false
|
|
sound_file: "mute_switch_on_sound"
|
|
on_turn_off:
|
|
- if:
|
|
condition:
|
|
and:
|
|
- lambda: return !id(init_in_progress);
|
|
- switch.is_on: mute_sound
|
|
then:
|
|
- script.execute:
|
|
id: play_sound
|
|
priority: false
|
|
sound_file: "mute_switch_off_sound"
|
|
turn_on_action:
|
|
- if:
|
|
condition:
|
|
lambda: return !id(mute_state).state;
|
|
then:
|
|
- switch.turn_on: mute_toggle
|
|
turn_off_action:
|
|
- if:
|
|
condition:
|
|
lambda: return id(mute_state).state;
|
|
then:
|
|
- switch.turn_on: mute_toggle
|
|
# Button click Sounds Switch.
|
|
- platform: template
|
|
id: button_sound
|
|
name: Button click sounds
|
|
icon: "mdi:bullhorn"
|
|
entity_category: config
|
|
optimistic: true
|
|
restore_mode: RESTORE_DEFAULT_ON
|
|
# Mute Sound Switch.
|
|
- platform: template
|
|
id: mute_sound
|
|
name: Mute-unmute sound
|
|
icon: "mdi:bullhorn"
|
|
entity_category: config
|
|
optimistic: true
|
|
restore_mode: RESTORE_DEFAULT_ON
|
|
# 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
|
|
- micro_wake_word.disable_model: stop
|
|
- script.execute: disable_repeat
|
|
# Stop any current announcement (ie: stop the timer ring mid playback)
|
|
- if:
|
|
condition:
|
|
media_player.is_announcing:
|
|
id: external_media_player
|
|
then:
|
|
media_player.stop:
|
|
announcement: true
|
|
id: external_media_player
|
|
# Set back ducking ratio to zero
|
|
- mixer_speaker.apply_ducking:
|
|
id: media_mixing_input
|
|
decibel_reduction: 0
|
|
duration: 1.0s
|
|
# Refresh the LED ring
|
|
- script.execute: control_leds
|
|
on_turn_on:
|
|
# Duck audio
|
|
- mixer_speaker.apply_ducking:
|
|
id: media_mixing_input
|
|
decibel_reduction: 20
|
|
duration: 0.0s
|
|
# Enable stop wake word
|
|
- micro_wake_word.enable_model: stop
|
|
# 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
|
|
# Defines if alarm is active
|
|
- platform: template
|
|
optimistic: true
|
|
restore_mode: RESTORE_DEFAULT_OFF
|
|
id: alarm_on
|
|
icon: mdi:bell-badge
|
|
name: "Alarm on"
|
|
on_turn_on:
|
|
- script.execute: control_leds
|
|
on_turn_off:
|
|
- script.execute: control_leds
|
|
|
|
binary_sensor:
|
|
# User Button. Used for many things (See on_multi_click)
|
|
- platform: gpio
|
|
id: user_button
|
|
pin:
|
|
number: GPIO3 # D2
|
|
inverted: true
|
|
name: "User button"
|
|
on_press:
|
|
- script.execute: control_leds
|
|
on_release:
|
|
- script.execute: control_leds
|
|
# If a factory reset is requested, factory reset on release
|
|
- if:
|
|
condition:
|
|
lambda: return id(factory_reset_requested);
|
|
then:
|
|
- button.press: factory_reset_button
|
|
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);
|
|
then:
|
|
- if:
|
|
condition:
|
|
switch.is_on: timer_ringing
|
|
then:
|
|
- switch.turn_off: timer_ringing
|
|
else:
|
|
- if:
|
|
condition:
|
|
voice_assistant.is_running:
|
|
then:
|
|
- voice_assistant.stop:
|
|
else:
|
|
- if:
|
|
condition:
|
|
media_player.is_announcing:
|
|
id: external_media_player
|
|
then:
|
|
media_player.stop:
|
|
announcement: true
|
|
id: external_media_player
|
|
else:
|
|
- if:
|
|
condition:
|
|
media_player.is_playing:
|
|
id: external_media_player
|
|
then:
|
|
- media_player.pause:
|
|
id: external_media_player
|
|
else:
|
|
- if:
|
|
condition:
|
|
and:
|
|
- switch.is_off: mic_mute_switch
|
|
- not:
|
|
voice_assistant.is_running
|
|
then:
|
|
- if:
|
|
condition:
|
|
switch.is_on: button_sound
|
|
then:
|
|
- script.execute:
|
|
id: play_sound
|
|
priority: true
|
|
sound_file: "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);
|
|
then:
|
|
- if:
|
|
condition:
|
|
switch.is_on: button_sound
|
|
then:
|
|
- script.execute:
|
|
id: play_sound
|
|
priority: false
|
|
sound_file: "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);
|
|
then:
|
|
- if:
|
|
condition:
|
|
switch.is_on: button_sound
|
|
then:
|
|
- script.execute:
|
|
id: play_sound
|
|
priority: false
|
|
sound_file: "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);
|
|
then:
|
|
- if:
|
|
condition:
|
|
switch.is_on: button_sound
|
|
then:
|
|
- script.execute:
|
|
id: play_sound
|
|
priority: false
|
|
sound_file: "center_button_long_press_sound"
|
|
- light.turn_off: led_internal
|
|
- event.trigger:
|
|
id: button_press_event
|
|
event_type: "long_press"
|
|
# Factory Reset Warning
|
|
# . Audible and Visible warning.
|
|
- timing:
|
|
- ON for at least 10s
|
|
then:
|
|
- light.turn_on:
|
|
brightness: 100%
|
|
id: led_internal
|
|
effect: "Factory Reset Coming Up"
|
|
- script.execute:
|
|
id: play_sound
|
|
priority: true
|
|
sound_file: "factory_reset_initiated_sound"
|
|
- wait_until:
|
|
binary_sensor.is_off: user_button
|
|
- if:
|
|
condition:
|
|
lambda: return !id(factory_reset_requested);
|
|
then:
|
|
- light.turn_off: led_internal
|
|
- script.execute:
|
|
id: play_sound
|
|
priority: true
|
|
sound_file: "factory_reset_cancelled_sound"
|
|
# Factory Reset Confirmed.
|
|
# . Audible warning to prompt user to release the button
|
|
# . Set factory_reset_requested to true
|
|
- timing:
|
|
- ON for at least 22s
|
|
then:
|
|
- script.execute:
|
|
id: play_sound
|
|
priority: true
|
|
sound_file: "factory_reset_confirmed_sound"
|
|
- light.turn_on:
|
|
brightness: 100%
|
|
red: 100%
|
|
green: 0%
|
|
blue: 0%
|
|
id: led_internal
|
|
effect: "none"
|
|
- lambda: id(factory_reset_requested) = true;
|
|
|
|
light:
|
|
- platform: esp32_rmt_led_strip
|
|
id: led_internal
|
|
internal: true
|
|
rgb_order: GRB
|
|
pin: GPIO1
|
|
num_leds: 1
|
|
rmt_symbols: 192
|
|
chipset: ws2812
|
|
default_transition_length: 0s
|
|
effects:
|
|
- addressable_lambda:
|
|
name: "Fast Pulse"
|
|
update_interval: 10ms
|
|
lambda: |-
|
|
static float fraction = 0.0;
|
|
static float step = 0.05;
|
|
static bool increasing = true;
|
|
|
|
auto values = id(led_internal)->current_values;
|
|
Color color(values.get_red() * 255, values.get_green() * 255, values.get_blue() * 255);
|
|
it[0].set_rgb(color.red * fraction,
|
|
color.green * fraction,
|
|
color.blue * fraction);
|
|
|
|
fraction += (step * (increasing ? 1 : -1));
|
|
if (fraction > 1.0) {
|
|
fraction = 1.0;
|
|
increasing = !increasing;
|
|
} else if (fraction < 0.0) {
|
|
fraction = 0.0;
|
|
increasing = !increasing;
|
|
}
|
|
|
|
- addressable_lambda:
|
|
name: "Slow Pulse"
|
|
update_interval: 30ms
|
|
lambda: |-
|
|
static float fraction = 0.0;
|
|
static float step = 0.05;
|
|
static bool increasing = true;
|
|
|
|
auto values = id(led_internal)->current_values;
|
|
Color color(values.get_red() * 255, values.get_green() * 255, values.get_blue() * 255);
|
|
it[0].set_rgb(color.red * fraction,
|
|
color.green * fraction,
|
|
color.blue * fraction);
|
|
|
|
fraction += (step * (increasing ? 1 : -1));
|
|
if (fraction > 1.0) {
|
|
fraction = 1.0;
|
|
increasing = !increasing;
|
|
} else if (fraction < 0.0) {
|
|
fraction = 0.0;
|
|
increasing = !increasing;
|
|
}
|
|
|
|
- addressable_lambda:
|
|
name: "Factory Reset Coming Up"
|
|
update_interval: 200ms
|
|
lambda: |-
|
|
static bool on = false;
|
|
on = !on;
|
|
it[0].set_rgb(on ? 255 : 0, 0, 0);
|
|
# User facing LED.
|
|
# Exposed to be used by the user.
|
|
- platform: partition
|
|
id: led_respeaker_onboard
|
|
name: LED Respeaker onboard
|
|
entity_category: config
|
|
icon: "mdi:circle-outline"
|
|
default_transition_length: 0ms
|
|
restore_mode: RESTORE_DEFAULT_OFF
|
|
on_turn_off:
|
|
- script.execute: control_leds
|
|
initial_state:
|
|
color_mode: rgb
|
|
brightness: 100%
|
|
red: 9.4%
|
|
green: 73.3%
|
|
blue: 94.9%
|
|
segments:
|
|
- id: led_internal
|
|
from: 0
|
|
to: 0
|
|
effects:
|
|
- addressable_lambda:
|
|
name: "Fast Pulse"
|
|
update_interval: 10ms
|
|
lambda: |-
|
|
static float fraction = 0.0;
|
|
static float step = 0.05;
|
|
static bool increasing = true;
|
|
|
|
auto values = id(led_respeaker_onboard)->current_values;
|
|
Color color(values.get_red() * 255, values.get_green() * 255, values.get_blue() * 255);
|
|
it[0].set_rgb(color.red * fraction,
|
|
color.green * fraction,
|
|
color.blue * fraction);
|
|
|
|
fraction += (step * (increasing ? 1 : -1));
|
|
if (fraction > 1.0) {
|
|
fraction = 1.0;
|
|
increasing = !increasing;
|
|
} else if (fraction < 0.0) {
|
|
fraction = 0.0;
|
|
increasing = !increasing;
|
|
}
|
|
|
|
- addressable_lambda:
|
|
name: "Slow Pulse"
|
|
update_interval: 30ms
|
|
lambda: |-
|
|
static float fraction = 0.0;
|
|
static float step = 0.05;
|
|
static bool increasing = true;
|
|
|
|
auto values = id(led_respeaker_onboard)->current_values;
|
|
Color color(values.get_red() * 255, values.get_green() * 255, values.get_blue() * 255);
|
|
it[0].set_rgb(color.red * fraction,
|
|
color.green * fraction,
|
|
color.blue * fraction);
|
|
|
|
fraction += (step * (increasing ? 1 : -1));
|
|
if (fraction > 1.0) {
|
|
fraction = 1.0;
|
|
increasing = !increasing;
|
|
} else if (fraction < 0.0) {
|
|
fraction = 0.0;
|
|
increasing = !increasing;
|
|
}
|
|
|
|
sensor:
|
|
- platform: template
|
|
id: next_timer
|
|
name: "Next timer"
|
|
update_interval: never
|
|
disabled_by_default: true
|
|
device_class: duration
|
|
unit_of_measurement: s
|
|
icon: "mdi:timer"
|
|
|
|
text_sensor:
|
|
- platform: template
|
|
id: next_timer_name
|
|
name: "Next timer name"
|
|
icon: "mdi:timer"
|
|
disabled_by_default: true
|
|
- platform: template
|
|
name: "Current device time"
|
|
id: current_time
|
|
icon: mdi:clock
|
|
|
|
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
|
|
|
|
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(user_button).state) {
|
|
id(control_leds_center_button_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(alarm_on).state && !id(led_respeaker_onboard).remote_values.is_on()) {
|
|
id(control_leds_alarm_active).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 slow pulse
|
|
- id: control_leds_improv_ble_state
|
|
then:
|
|
- light.turn_on:
|
|
brightness: !lambda return max( id(led_respeaker_onboard).current_values.get_brightness() , 0.2f );
|
|
red: 100%
|
|
green: 89%
|
|
blue: 71%
|
|
id: led_internal
|
|
effect: "Slow Pulse"
|
|
|
|
# Script executed during initialization
|
|
# Fast Blue pulse if Wifi is connected, Else slow blue pulse
|
|
- id: control_leds_init_state
|
|
then:
|
|
- if:
|
|
condition:
|
|
wifi.connected:
|
|
then:
|
|
- light.turn_on:
|
|
brightness: !lambda return max( id(led_respeaker_onboard).current_values.get_brightness() , 0.2f );
|
|
red: 9%
|
|
green: 73%
|
|
blue: 95%
|
|
id: led_internal
|
|
effect: "Fast Pulse"
|
|
else:
|
|
- light.turn_on:
|
|
brightness: !lambda return max( id(led_respeaker_onboard).current_values.get_brightness() , 0.2f );
|
|
red: 9%
|
|
green: 73%
|
|
blue: 95%
|
|
id: led_internal
|
|
effect: "Slow Pulse"
|
|
# Script executed when the device has no connection to Home Assistant
|
|
# Red slow pulse (This will be visible during HA updates for example)
|
|
- id: control_leds_no_ha_connection_state
|
|
then:
|
|
- light.turn_on:
|
|
brightness: !lambda return max( id(led_respeaker_onboard).current_values.get_brightness() , 0.2f );
|
|
red: 1
|
|
green: 0
|
|
blue: 0
|
|
id: led_internal
|
|
effect: "Slow Pulse"
|
|
|
|
# Script executed when the voice assistant is idle (waiting for a wake word)
|
|
# Nothing
|
|
- id: control_leds_voice_assistant_idle_phase
|
|
then:
|
|
- light.turn_off: led_internal
|
|
- if:
|
|
condition:
|
|
light.is_on: led_respeaker_onboard
|
|
then:
|
|
light.turn_on: led_respeaker_onboard
|
|
|
|
# Script executed when the voice assistant is waiting for a command (After the wake word)
|
|
# Slow purple pulse
|
|
- id: control_leds_voice_assistant_waiting_for_command_phase
|
|
then:
|
|
- light.turn_on:
|
|
brightness: !lambda return max( id(led_respeaker_onboard).current_values.get_brightness() , 0.2f );
|
|
red: 1
|
|
green: 0.2
|
|
blue: 1
|
|
id: led_internal
|
|
effect: "Slow Pulse"
|
|
|
|
# Script executed when the voice assistant is listening to a command
|
|
# Slow purple pulse
|
|
- id: control_leds_voice_assistant_listening_for_command_phase
|
|
then:
|
|
- light.turn_on:
|
|
brightness: !lambda return max( id(led_respeaker_onboard).current_values.get_brightness() , 0.2f );
|
|
red: 1
|
|
green: 0.2
|
|
blue: 1
|
|
id: led_internal
|
|
effect: "Slow Pulse"
|
|
|
|
# Script executed when the voice assistant is thinking to a command
|
|
# Fast purple pulse
|
|
- id: control_leds_voice_assistant_thinking_phase
|
|
then:
|
|
- light.turn_on:
|
|
brightness: !lambda return max( id(led_respeaker_onboard).current_values.get_brightness() , 0.2f );
|
|
red: 1
|
|
green: 0.2
|
|
blue: 1
|
|
id: led_internal
|
|
effect: "Fast Pulse"
|
|
|
|
# Script executed when the voice assistant is replying to a command
|
|
# Slow cyan pulse
|
|
- id: control_leds_voice_assistant_replying_phase
|
|
then:
|
|
- light.turn_on:
|
|
brightness: !lambda return max( id(led_respeaker_onboard).current_values.get_brightness() , 0.2f );
|
|
red: 0.2
|
|
green: 1
|
|
blue: 1
|
|
id: led_internal
|
|
effect: "Slow Pulse"
|
|
|
|
# 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 max( id(led_respeaker_onboard).current_values.get_brightness() , 0.2f );
|
|
red: 1
|
|
green: 0
|
|
blue: 0
|
|
id: led_internal
|
|
effect: "Fast Pulse"
|
|
|
|
# Script executed when the voice assistant is not ready
|
|
- id: control_leds_voice_assistant_not_ready_phase
|
|
then:
|
|
- light.turn_on:
|
|
brightness: !lambda return max( id(led_respeaker_onboard).current_values.get_brightness() , 0.2f );
|
|
red: 1
|
|
green: 0
|
|
blue: 0
|
|
id: led_internal
|
|
effect: "Slow Pulse"
|
|
|
|
# Script executed when the center button is touched
|
|
# The LED turns on blue
|
|
- id: control_leds_center_button_touched
|
|
then:
|
|
- light.turn_on:
|
|
brightness: !lambda return min ( max( id(led_respeaker_onboard).current_values.get_brightness() , 0.2f ) + 0.1f , 1.0f );
|
|
red: 0
|
|
green: 0
|
|
blue: 1
|
|
id: led_internal
|
|
effect: "None"
|
|
|
|
# Script executed when the timer is ringing, to control the LEDs
|
|
# The LED blinks green.
|
|
- id: control_leds_timer_ringing
|
|
then:
|
|
- light.turn_on:
|
|
brightness: !lambda return min ( max( id(led_respeaker_onboard).current_values.get_brightness() , 0.2f ) + 0.1f , 1.0f );
|
|
red: 0
|
|
green: 1
|
|
blue: 0
|
|
id: led_internal
|
|
effect: "Fast Pulse"
|
|
|
|
# Script executed when the timer is ticking, to control the LEDs
|
|
# Slow dim while pulse.
|
|
- id: control_leds_timer_ticking
|
|
then:
|
|
- light.turn_on:
|
|
brightness: !lambda return max( id(led_respeaker_onboard).current_values.get_brightness() , 0.2f );
|
|
red: 0.3
|
|
green: 0.3
|
|
blue: 0.3
|
|
id: led_internal
|
|
effect: "Slow Pulse"
|
|
|
|
# Script executed when the alarm is active
|
|
# The LED turns on dim green
|
|
- id: control_leds_alarm_active
|
|
then:
|
|
- light.turn_on:
|
|
brightness: !lambda return 0.3f;
|
|
red: 0
|
|
green: 1
|
|
blue: 0
|
|
id: led_internal
|
|
effect: "None"
|
|
|
|
|
|
# Script executed when the timer is ringing, to playback sounds.
|
|
- id: ring_timer
|
|
then:
|
|
- script.execute: enable_repeat_one
|
|
- script.execute:
|
|
id: play_sound
|
|
priority: true
|
|
sound_file: "timer_finished_sound"
|
|
|
|
# Script executed when the timer is ringing, to repeat the timer finished sound.
|
|
- id: enable_repeat_one
|
|
then:
|
|
# Turn on the repeat mode and pause for 500 ms between playlist items/repeats
|
|
- media_player.repeat_one:
|
|
id: external_media_player
|
|
announcement: true
|
|
- speaker_source.set_playlist_delay:
|
|
id: external_media_player
|
|
pipeline: announcement
|
|
delay: 500ms
|
|
|
|
# Script execute when the timer is done ringing, to disable repeat mode.
|
|
- id: disable_repeat
|
|
then:
|
|
# Turn off the repeat mode and pause for 0 ms between playlist items/repeats
|
|
- media_player.repeat_off:
|
|
id: external_media_player
|
|
announcement: true
|
|
- speaker_source.set_playlist_delay:
|
|
id: external_media_player
|
|
pipeline: announcement
|
|
delay: 0ms
|
|
|
|
# Script executed when we want to play sounds on the device.
|
|
- id: play_sound
|
|
parameters:
|
|
priority: bool
|
|
# sound_file: "audio::AudioFile*"
|
|
sound_file: string
|
|
then:
|
|
- if:
|
|
condition:
|
|
lambda: return priority;
|
|
then:
|
|
- media_player.stop:
|
|
id: external_media_player
|
|
announcement: true
|
|
- lambda: |-
|
|
if ( (id(external_media_player).state != media_player::MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING ) || priority) {
|
|
id(external_media_player)
|
|
->make_call()
|
|
.set_media_url("audio-file://" + sound_file)
|
|
.set_announcement(true)
|
|
.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();
|
|
for (const auto &timer : timers) {
|
|
if (timer.is_active && timer.seconds_left <= output_timer.seconds_left) {
|
|
output_timer = timer;
|
|
}
|
|
}
|
|
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;
|
|
for (const auto &timer : timers) {
|
|
if (timer.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_once
|
|
then:
|
|
- wait_until:
|
|
condition:
|
|
media_player.is_announcing:
|
|
id: external_media_player
|
|
- delay: 1s
|
|
# Enable stop wake word
|
|
- if:
|
|
condition:
|
|
switch.is_off: timer_ringing
|
|
then:
|
|
- micro_wake_word.enable_model: stop
|
|
- wait_until:
|
|
not:
|
|
media_player.is_announcing:
|
|
id: external_media_player
|
|
- if:
|
|
condition:
|
|
switch.is_off: timer_ringing
|
|
then:
|
|
- micro_wake_word.disable_model: stop
|
|
|
|
- id: check_alarm
|
|
then:
|
|
- lambda: |-
|
|
id(publish_current_time).execute();
|
|
// Check alarm
|
|
if (id(alarm_on).state) {
|
|
auto alarm_dt = id(alarm_time).state_as_esptime();
|
|
auto time_now = id(homeassistant_time).now();
|
|
|
|
if (time_now.hour == alarm_dt.hour && time_now.minute == alarm_dt.minute) {
|
|
auto action = id(alarm_action).current_option();
|
|
if (action == "Play sound") {
|
|
id(timer_ringing).turn_on();
|
|
} else if (action == "Send event") {
|
|
id(send_alarm_event).execute();
|
|
} else if (action == "Sound and event") {
|
|
id(timer_ringing).turn_on();
|
|
id(send_alarm_event).execute();
|
|
}
|
|
}
|
|
}
|
|
|
|
- id: send_wake_word_event
|
|
parameters:
|
|
wake_word: string
|
|
then:
|
|
- homeassistant.event:
|
|
event: esphome.wake_word_detected
|
|
data:
|
|
wake_word: !lambda return wake_word;
|
|
- id: send_alarm_event
|
|
then:
|
|
- homeassistant.event:
|
|
event: esphome.alarm_ringing
|
|
- id: send_tts_uri_event
|
|
parameters:
|
|
tts_uri: string
|
|
then:
|
|
- homeassistant.event:
|
|
event: esphome.tts_uri
|
|
data:
|
|
uri: !lambda return tts_uri;
|
|
- id: send_stt_text_event
|
|
parameters:
|
|
stt_text: string
|
|
then:
|
|
- homeassistant.event:
|
|
event: esphome.stt_text
|
|
data:
|
|
text: !lambda return stt_text;
|
|
- id: publish_current_time
|
|
then:
|
|
- lambda: |-
|
|
// Publish current time
|
|
auto time_now = id(homeassistant_time).now();
|
|
id(current_time).publish_state(time_now.strftime("%H:%M"));
|
|
|
|
i2s_audio:
|
|
- id: i2s_output
|
|
i2s_lrclk_pin:
|
|
number: GPIO7
|
|
allow_other_uses: true
|
|
i2s_bclk_pin:
|
|
number: GPIO8
|
|
allow_other_uses: true
|
|
i2s_mclk_pin:
|
|
number: GPIO9
|
|
allow_other_uses: true
|
|
|
|
- id: i2s_input
|
|
i2s_lrclk_pin:
|
|
number: GPIO7
|
|
allow_other_uses: true
|
|
i2s_bclk_pin:
|
|
number: GPIO8
|
|
allow_other_uses: true
|
|
i2s_mclk_pin:
|
|
number: GPIO9
|
|
allow_other_uses: true
|
|
|
|
microphone:
|
|
- platform: i2s_audio
|
|
id: i2s_mics
|
|
i2s_din_pin: GPIO44
|
|
adc_type: external
|
|
pdm: false
|
|
sample_rate: 48000
|
|
bits_per_sample: 32bit
|
|
i2s_mode: secondary
|
|
i2s_audio_id: i2s_input
|
|
channel: stereo
|
|
|
|
speaker:
|
|
# Hardware speaker output
|
|
- platform: i2s_audio
|
|
id: i2s_audio_speaker
|
|
sample_rate: 48000
|
|
i2s_mode: secondary
|
|
i2s_dout_pin: GPIO43
|
|
bits_per_sample: 32bit
|
|
i2s_audio_id: i2s_output
|
|
dac_type: external
|
|
channel: stereo
|
|
timeout: never
|
|
buffer_duration: 100ms
|
|
audio_dac: aic3204_dac
|
|
|
|
# Virtual speakers to combine the announcement and media streams together into one output
|
|
- platform: mixer
|
|
id: mixing_speaker
|
|
output_speaker: i2s_audio_speaker
|
|
num_channels: 2
|
|
task_stack_in_psram: true
|
|
source_speakers:
|
|
- id: announcement_mixing_input
|
|
timeout: never
|
|
- id: media_mixing_input
|
|
timeout: never
|
|
|
|
# Virtual speakers to resample each pipelines' audio, if necessary, as the mixer speaker requires the same sample rate
|
|
- platform: resampler
|
|
id: announcement_resampling_speaker
|
|
output_speaker: announcement_mixing_input
|
|
sample_rate: 48000
|
|
bits_per_sample: 16
|
|
- platform: resampler
|
|
id: media_resampling_speaker
|
|
output_speaker: media_mixing_input
|
|
sample_rate: 48000
|
|
bits_per_sample: 16
|
|
|
|
sendspin:
|
|
id: sendspin_hub
|
|
task_stack_in_psram: false
|
|
|
|
audio_file:
|
|
- id: center_button_press_sound
|
|
file: ${center_button_press_sound_file}
|
|
- id: center_button_double_press_sound
|
|
file: ${center_button_double_press_sound_file}
|
|
- id: center_button_triple_press_sound
|
|
file: ${center_button_triple_press_sound_file}
|
|
- id: center_button_long_press_sound
|
|
file: ${center_button_long_press_sound_file}
|
|
- id: factory_reset_initiated_sound
|
|
file: ${factory_reset_initiated_sound_file}
|
|
- id: factory_reset_cancelled_sound
|
|
file: ${factory_reset_cancelled_sound_file}
|
|
- id: factory_reset_confirmed_sound
|
|
file: ${factory_reset_confirmed_sound_file}
|
|
- id: mute_switch_on_sound
|
|
file: ${mute_switch_on_sound_file}
|
|
- id: mute_switch_off_sound
|
|
file: ${mute_switch_off_sound_file}
|
|
- id: timer_finished_sound
|
|
file: ${timer_finished_sound_file}
|
|
- id: wake_word_triggered_sound
|
|
file: ${wake_word_triggered_sound_file}
|
|
- id: error_cloud_expired
|
|
file: ${error_cloud_expired_sound_file}
|
|
|
|
media_source:
|
|
- platform: audio_file
|
|
id: audio_file_announcement_source
|
|
- platform: audio_http
|
|
id: http_announcement_source
|
|
buffer_size: 250000
|
|
- platform: audio_http
|
|
id: http_media_source
|
|
buffer_size: 500000
|
|
- platform: sendspin
|
|
id: sendspin_media_source
|
|
fixed_delay: 480 microseconds # The AIC3204 DAC used, as configured, on the VPE delays audio by 480 microseconds
|
|
|
|
media_player:
|
|
- platform: sendspin
|
|
id: sendspin_group_media_player
|
|
- platform: speaker_source
|
|
id: external_media_player
|
|
name: Media Player
|
|
announcement_pipeline:
|
|
format: FLAC # FLAC is the least processor intensive codec
|
|
num_channels: 1 # Stereo audio is unnecessary for announcements
|
|
sample_rate: 48000
|
|
speaker: announcement_resampling_speaker
|
|
sources:
|
|
- audio_file_announcement_source
|
|
- http_announcement_source
|
|
media_pipeline:
|
|
format: FLAC # FLAC is the least processor intensive codec
|
|
num_channels: 2
|
|
sample_rate: 48000
|
|
speaker: media_resampling_speaker
|
|
sources:
|
|
- http_media_source
|
|
- sendspin_media_source
|
|
volume_increment: 0.05
|
|
volume_min: 0.4
|
|
volume_max: 0.85
|
|
on_announcement:
|
|
- mixer_speaker.apply_ducking:
|
|
id: media_mixing_input
|
|
decibel_reduction: 20
|
|
duration: 0.0s
|
|
on_state:
|
|
if:
|
|
condition:
|
|
and:
|
|
- switch.is_off: timer_ringing
|
|
- not:
|
|
voice_assistant.is_running:
|
|
- not:
|
|
media_player.is_announcing: external_media_player
|
|
then:
|
|
- mixer_speaker.apply_ducking:
|
|
id: media_mixing_input
|
|
decibel_reduction: 0
|
|
duration: 1.0s
|
|
|
|
respeaker_lite:
|
|
id: respeaker
|
|
reset_pin: GPIO2
|
|
mute_state:
|
|
internal: true
|
|
id: mute_state
|
|
firmware_version:
|
|
icon: mdi:application-cog
|
|
name: XMOS firmware version
|
|
internal: false
|
|
id: firmware_version
|
|
firmware:
|
|
url: https://github.com/formatBCE/Respeaker-Lite-ESPHome-integration/raw/refs/heads/main/respeaker_lite_i2s_dfu_firmware_48k_v1.1.0.bin
|
|
version: "1.1.0"
|
|
md5: 9297155d1bf3eb21a9d4db52a89ea0c6
|
|
on_begin:
|
|
- light.turn_on:
|
|
brightness: !lambda return max( id(led_respeaker_onboard).current_values.get_brightness() , 0.2f );
|
|
red: 50%
|
|
green: 50%
|
|
blue: 50%
|
|
id: led_internal
|
|
effect: "Slow Pulse"
|
|
on_end:
|
|
- light.turn_on:
|
|
brightness: !lambda return max( id(led_respeaker_onboard).current_values.get_brightness() , 0.2f );
|
|
red: 0%
|
|
green: 100%
|
|
blue: 0%
|
|
id: led_internal
|
|
effect: "Fast Pulse"
|
|
- delay: 3s
|
|
- light.turn_off:
|
|
id: led_internal
|
|
on_error:
|
|
- light.turn_on:
|
|
brightness: !lambda return max( id(led_respeaker_onboard).current_values.get_brightness() , 0.2f );
|
|
red: 100%
|
|
green: 0%
|
|
blue: 0%
|
|
id: led_internal
|
|
effect: "Fast Pulse"
|
|
- delay: 3s
|
|
- light.turn_off:
|
|
id: led_internal
|
|
|
|
external_components:
|
|
- source:
|
|
type: git
|
|
url: https://github.com/formatBCE/esphome
|
|
ref: respeaker_microphone
|
|
components:
|
|
- i2s_audio
|
|
refresh: 0s
|
|
- source:
|
|
type: git
|
|
url: https://github.com/formatBCE/Respeaker-Lite-ESPHome-integration
|
|
ref: main
|
|
components:
|
|
- respeaker_lite
|
|
refresh: 0s
|
|
|
|
audio_dac:
|
|
- platform: aic3204
|
|
id: aic3204_dac
|
|
i2c_id: internal_i2c
|
|
|
|
micro_wake_word:
|
|
id: mww
|
|
microphone:
|
|
microphone: i2s_mics
|
|
channels: 1
|
|
gain_factor: 4
|
|
stop_after_detection: false
|
|
models:
|
|
- model: https://github.com/kahrendt/microWakeWord/releases/download/okay_nabu_20241226.3/okay_nabu.json
|
|
# probability_cutoff: 0.8
|
|
id: okay_nabu
|
|
- model: https://raw.githubusercontent.com/formatBCE/Respeaker-Lite-ESPHome-integration/refs/heads/main/microwakeword/models/v2/kenobi.json
|
|
id: kenobi
|
|
- 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:
|
|
probability_cutoff: 0.05
|
|
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: mic_mute_switch
|
|
then:
|
|
- script.execute:
|
|
id: send_wake_word_event
|
|
wake_word: !lambda return wake_word;
|
|
# 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
|
|
# Stop voice assistant if running
|
|
else:
|
|
- if:
|
|
condition:
|
|
voice_assistant.is_running:
|
|
then:
|
|
voice_assistant.stop:
|
|
# Stop any other media player announcement
|
|
else:
|
|
- if:
|
|
condition:
|
|
media_player.is_announcing:
|
|
id: external_media_player
|
|
then:
|
|
- media_player.stop:
|
|
announcement: true
|
|
id: external_media_player
|
|
# Start the voice assistant and play the wake sound, if enabled
|
|
else:
|
|
- if:
|
|
condition:
|
|
switch.is_on: wake_sound
|
|
then:
|
|
- script.execute:
|
|
id: play_sound
|
|
priority: true
|
|
sound_file: "wake_word_triggered_sound"
|
|
- delay: 300ms
|
|
- voice_assistant.start:
|
|
wake_word: !lambda return wake_word;
|
|
|
|
select:
|
|
- platform: template
|
|
name: "Wake word sensitivity"
|
|
id: wake_word_sensitivity
|
|
optimistic: true
|
|
initial_option: Slightly sensitive
|
|
restore_value: true
|
|
entity_category: config
|
|
options:
|
|
- Slightly sensitive
|
|
- Moderately sensitive
|
|
- Very sensitive
|
|
on_value:
|
|
# Sets specific wake word probabilities computed for each particular model
|
|
# Note probability cutoffs are set as a quantized uint8 value, each comment has the corresponding floating point cutoff
|
|
# False Accepts per Hour values are tested against all units and channels from the Dinner Party Corpus.
|
|
# These cutoffs apply only to the specific models included in the firmware: okay_nabu@20241226.3, hey_jarvis@v2, hey_mycroft@v2
|
|
lambda: |-
|
|
if (x == "Slightly sensitive") {
|
|
id(okay_nabu).set_probability_cutoff(217); // 0.85 -> 0.000 FAPH on DipCo (Manifest's default)
|
|
id(hey_jarvis).set_probability_cutoff(247); // 0.97 -> 0.563 FAPH on DipCo (Manifest's default)
|
|
id(hey_mycroft).set_probability_cutoff(253); // 0.99 -> 0.567 FAPH on DipCo
|
|
} else if (x == "Moderately sensitive") {
|
|
id(okay_nabu).set_probability_cutoff(176); // 0.69 -> 0.376 FAPH on DipCo
|
|
id(hey_jarvis).set_probability_cutoff(235); // 0.92 -> 0.939 FAPH on DipCo
|
|
id(hey_mycroft).set_probability_cutoff(242); // 0.95 -> 1.502 FAPH on DipCo (Manifest's default)
|
|
} else if (x == "Very sensitive") {
|
|
id(okay_nabu).set_probability_cutoff(143); // 0.56 -> 0.751 FAPH on DipCo
|
|
id(hey_jarvis).set_probability_cutoff(212); // 0.83 -> 1.502 FAPH on DipCo
|
|
id(hey_mycroft).set_probability_cutoff(237); // 0.93 -> 1.878 FAPH on DipCo
|
|
}
|
|
- platform: logger
|
|
id: logger_select
|
|
name: Logger Level
|
|
disabled_by_default: true
|
|
- platform: template
|
|
optimistic: true
|
|
name: "Alarm action"
|
|
id: alarm_action
|
|
icon: mdi:bell-plus
|
|
options:
|
|
- "Play sound"
|
|
- "Send event"
|
|
- "Sound and event"
|
|
initial_option: "Play sound"
|
|
on_value:
|
|
then:
|
|
- lambda: |-
|
|
id(saved_alarm_action) = x;
|
|
|
|
voice_assistant:
|
|
id: va
|
|
microphone:
|
|
microphone: i2s_mics
|
|
channels: 0
|
|
media_player: external_media_player
|
|
micro_wake_word: mww
|
|
use_wake_word: false
|
|
noise_suppression_level: 0
|
|
auto_gain: 0 dbfs
|
|
volume_multiplier: 1
|
|
on_client_connected:
|
|
- if:
|
|
condition:
|
|
- lambda: return id(init_in_progress);
|
|
- switch.is_on: mic_mute_switch
|
|
then:
|
|
- switch.turn_off: mic_mute_switch
|
|
- lambda: id(init_in_progress) = false;
|
|
- micro_wake_word.start:
|
|
- 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:
|
|
# Only set the error phase if the error code is different than duplicate_wake_up_detected or stt-no-text-recognized
|
|
# These two are ignored for a better user experience
|
|
- if:
|
|
condition:
|
|
and:
|
|
- lambda: return !id(init_in_progress);
|
|
- lambda: return code != "duplicate_wake_up_detected";
|
|
- lambda: return code != "stt-no-text-recognized";
|
|
then:
|
|
- lambda: id(voice_assistant_phase) = ${voice_assist_error_phase_id};
|
|
- script.execute: control_leds
|
|
# If the error code is cloud-auth-failed, serve a local audio file guiding the user.
|
|
- if:
|
|
condition:
|
|
- lambda: return code == "cloud-auth-failed";
|
|
then:
|
|
- script.execute:
|
|
id: play_sound
|
|
priority: true
|
|
sound_file: "error_cloud_expired"
|
|
# When the voice assistant starts: Play a wake up sound, duck audio.
|
|
on_start:
|
|
- mixer_speaker.apply_ducking:
|
|
id: media_mixing_input
|
|
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 no transition)
|
|
on_listening:
|
|
- 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_intent_progress:
|
|
- if:
|
|
condition:
|
|
# A nonempty x variable means a streaming TTS url was sent to the media player
|
|
lambda: 'return !x.empty();'
|
|
then:
|
|
- 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_once
|
|
on_tts_start:
|
|
- if:
|
|
condition:
|
|
# The intent_progress trigger didn't start the TTS Reponse
|
|
lambda: 'return id(voice_assistant_phase) != ${voice_assist_replying_phase_id};'
|
|
then:
|
|
- 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_once
|
|
on_tts_end:
|
|
- script.execute:
|
|
id: send_tts_uri_event
|
|
tts_uri: !lambda 'return x;'
|
|
on_stt_end:
|
|
- script.execute:
|
|
id: send_stt_text_event
|
|
stt_text: !lambda 'return x;'
|
|
|
|
# When the voice assistant ends ...
|
|
on_end:
|
|
- wait_until:
|
|
not:
|
|
voice_assistant.is_running:
|
|
# Stop ducking audio.
|
|
- mixer_speaker.apply_ducking:
|
|
id: media_mixing_input
|
|
decibel_reduction: 0
|
|
duration: 1.0s
|
|
# If the end happened because of an error, let the error phase on for a second
|
|
- if:
|
|
condition:
|
|
lambda: return id(voice_assistant_phase) == ${voice_assist_error_phase_id};
|
|
then:
|
|
- 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
|
|
on_timer_finished:
|
|
- switch.turn_on: timer_ringing
|
|
- lambda: |
|
|
id(next_timer).publish_state(-1);
|
|
id(next_timer_name).publish_state("-");
|
|
on_timer_started:
|
|
- script.execute: control_leds
|
|
- lambda: |
|
|
id(next_timer).publish_state(id(first_active_timer).seconds_left);
|
|
id(next_timer_name).publish_state(id(first_active_timer).name);
|
|
on_timer_cancelled:
|
|
- script.execute: control_leds
|
|
- lambda: |
|
|
id(next_timer).publish_state(id(first_active_timer).seconds_left);
|
|
id(next_timer_name).publish_state(id(first_active_timer).name);
|
|
on_timer_updated:
|
|
- script.execute: control_leds
|
|
- lambda: |
|
|
id(next_timer).publish_state(id(first_active_timer).seconds_left);
|
|
id(next_timer_name).publish_state(id(first_active_timer).name);
|
|
on_timer_tick:
|
|
- script.execute: control_leds
|
|
- lambda: |
|
|
int seconds_left = id(first_active_timer).seconds_left;
|
|
if (std::abs(seconds_left) % 5 == 0) {
|
|
id(next_timer).publish_state(seconds_left);
|
|
}
|
|
|
|
button:
|
|
- platform: factory_reset
|
|
id: factory_reset_button
|
|
name: "Factory Reset"
|
|
entity_category: diagnostic
|
|
internal: true
|
|
- platform: restart
|
|
id: restart_button
|
|
name: "Restart"
|
|
entity_category: config
|
|
disabled_by_default: true
|
|
icon: "mdi:restart"
|
|
|
|
debug:
|
|
update_interval: 5s
|