checkpoint commit
All checks were successful
Podman DDNS Image / build-and-push-ddns (push) Successful in 1m3s

This commit is contained in:
2026-05-05 06:26:40 -04:00
parent e43c534ceb
commit f2015e2c71
76 changed files with 4265 additions and 235 deletions

View File

@@ -0,0 +1,997 @@
---
substitutions:
name: pyramid1
friendly_name: Pyramid 1
# Casita images
loading_illustration_file: https://github.com/esphome/wake-word-voice-assistants/raw/main/casita/loading_320_240.png
idle_illustration_file: https://github.com/esphome/wake-word-voice-assistants/raw/main/casita/idle_320_240.png
listening_illustration_file: https://github.com/esphome/wake-word-voice-assistants/raw/main/casita/listening_320_240.png
thinking_illustration_file: https://github.com/esphome/wake-word-voice-assistants/raw/main/casita/thinking_320_240.png
replying_illustration_file: https://github.com/esphome/wake-word-voice-assistants/raw/main/casita/replying_320_240.png
error_illustration_file: https://github.com/esphome/wake-word-voice-assistants/raw/main/casita/error_320_240.png
error_no_wifi_illustration_file: https://github.com/esphome/wake-word-voice-assistants/raw/main/error_box_illustrations/error-no-wifi.png
error_no_ha_illustration_file: https://github.com/esphome/wake-word-voice-assistants/raw/main/error_box_illustrations/error-no-ha.png
# Fonts
mdi_webfont_file: https://raw.githubusercontent.com/Templarian/MaterialDesign-Webfont/master/fonts/materialdesignicons-webfont.ttf
# Audio files
wake_word_trigger_sound_file: wake_word_triggered.wav
# timer_finished_sound_file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/timer_finished.flac
# error_cloud_expired_file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/error_cloud_expired.mp3
# Micro wake word models
pick_pig: https://raw.githubusercontent.com/esphome/micro-wake-word-models/refs/heads/main/models/v2/experiments/hey_peppa_pig.json
stop_model_file: https://github.com/kahrendt/microWakeWord/releases/download/stop/stop.json
# Background colors
loading_illustration_background_color: "000000"
idle_illustration_background_color: "000000"
listening_illustration_background_color: "FFFFFF"
thinking_illustration_background_color: "FFFFFF"
replying_illustration_background_color: "FFFFFF"
error_illustration_background_color: "000000"
# 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 listening for a voice command
voice_assist_listening_phase_id: "2"
# The voice assistant is currently processing the command
voice_assist_thinking_phase_id: "3"
# The voice assistant is replying to the command
voice_assist_replying_phase_id: "4"
# 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"
# Muted phase
voice_assist_muted_phase_id: "12"
# Finished timer phase
voice_assist_timer_finished_phase_id: "20"
esphome:
name: pyramid1
friendly_name: Pyramid 1
min_version: 2025.11.3
on_boot:
priority: 600
then:
- delay: 30s
- if:
condition:
lambda: return id(init_in_progress);
then:
- lambda: id(init_in_progress) = false;
esp32:
variant: esp32s3
flash_size: 8MB
cpu_frequency: 240MHz
framework:
type: esp-idf
api:
encryption:
key: "innoIL7I6ZfRekL58F65REjeYNLW1Hp/Q/Kv9SEjnNA="
ota:
- platform: esphome
password: "22de00dcf5c2701a25d2fe719d596123"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: "Echo-Pyramid Fallback Hotspot"
password: "uSTvJjVzweZp"
# Enable logging
logger:
level: INFO
logs:
sensor: WARN
captive_portal:
button:
- platform: factory_reset
id: factory_reset_btn
internal: true
binary_sensor:
- platform: gpio
pin:
number: GPIO41
mode: INPUT_PULLUP
inverted: true
id: user_button
internal: true
on_multi_click:
- timing:
- ON for at least 50ms
- OFF for at least 50ms
then:
- switch.turn_off: timer_ringing
- timing:
- ON for at least 10s
then:
- button.press: factory_reset_btn
external_components:
- source: github://m5stack/esphome-yaml/components
components: [aw87559, si5351, lp5562, pyramidrgb, pyramidtouch]
refresh: 0s
# I2C Bus Configuration
i2c:
- id: bsp_bus
sda: GPIO45
scl: GPIO0
scan: true
- id: ext_bus # used on atomic echo base
sda: GPIO38
scl: GPIO39
# Ehco Base GPIO Expander
pi4ioe5v6408:
- id: pi4ioe5v6408_hub
i2c_id: ext_bus
address: 0x43
aw87559:
id: audio_amp
i2c_id: ext_bus
address: 0x5B
si5351:
id: clock_gen
i2c_id: ext_bus
address: 0x60
# I2S Bus Configuration
i2s_audio:
- id: i2s_audio_bus
i2s_lrclk_pin: GPIO8
i2s_bclk_pin: GPIO6
spi:
clk_pin: GPIO15
mosi_pin: GPIO21
# miso_pin is not used
audio_dac:
- platform: es8311
id: es8311_dac
i2c_id: ext_bus
bits_per_sample: 16bit
sample_rate: 16000
audio_adc:
- platform: es7210
id: es7210_adc
i2c_id: ext_bus
address: 0x40
bits_per_sample: 16bit
sample_rate: 16000
microphone:
- platform: i2s_audio
id: i2s_mic
sample_rate: 16000
i2s_din_pin: GPIO5
bits_per_sample: 16bit
adc_type: external
channel: stereo
speaker:
- platform: i2s_audio
id: i2s_speaker
i2s_dout_pin: GPIO7
dac_type: external
bits_per_sample: 16bit
sample_rate: 16000
channel: mono
audio_dac: es8311_dac
media_player:
- platform: speaker
name: "Echo Pyramid Player"
id: echo_pyramid_player
volume_min: 0.0
volume_max: 1.0
volume_initial: 0.10
buffer_size: 6000
announcement_pipeline:
speaker: i2s_speaker
format: WAV
# sample_rate: 48000
# num_channels: 1
codec_support_enabled: false
files:
- id: wake_word_triggered_sound
file: ${wake_word_trigger_sound_file}
# - id: timer_finished_sound
# file: ${timer_finished_sound_file}
# - id: error_cloud_expired
# file: ${error_cloud_expired_file}
on_state:
- logger.log: "State updated!"
on_play:
- logger.log: "Playback started!"
on_announcement:
- logger.log: "Announcing!"
# Stop the wake word (mWW or VA) if the mic is capturing
- if:
condition:
- microphone.is_capturing:
then:
- script.execute: stop_wake_word
# Ensure VA stops before moving on
- if:
condition:
- lambda: |-
return id(wake_word_engine_location).current_option() == "In Home Assistant";
then:
- wait_until:
- not:
voice_assistant.is_running:
# Since VA isn't running, this is user-intiated media playback. Draw the mute display
- if:
condition:
not:
voice_assistant.is_running:
then:
- lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
- script.execute: draw_display
on_idle:
# Since VA isn't running, this is the end of user-intiated media playback. Restart the wake word.
- if:
condition:
not:
voice_assistant.is_running:
then:
- script.execute: start_wake_word
- script.execute: set_idle_or_mute_phase
- script.execute: draw_display
switch:
# NS4150B
- platform: gpio
name: Speaker Enable
pin:
pi4ioe5v6408: pi4ioe5v6408_hub
number: 0
mode:
output: true
icon: "mdi:volume-high"
restore_mode: RESTORE_DEFAULT_ON
- platform: template
name: Mute Microphone
id: mute
icon: "mdi:microphone-off"
optimistic: true
restore_mode: RESTORE_DEFAULT_OFF
entity_category: config
on_turn_off:
- microphone.unmute:
- lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
on_turn_on:
- microphone.mute:
- lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
- platform: template
id: timer_ringing
optimistic: true
internal: true
restore_mode: ALWAYS_OFF
on_turn_off:
# Turn off the repeat mode and disable the pause between playlist items
- lambda: |-
id(echo_pyramid_player)
->make_call()
.set_command(media_player::MediaPlayerCommand::MEDIA_PLAYER_COMMAND_REPEAT_OFF)
.set_announcement(true)
.perform();
id(echo_pyramid_player)->set_playlist_delay_ms(speaker::AudioPipelineType::ANNOUNCEMENT, 0);
# Stop playing the alarm
- media_player.stop:
announcement: true
- script.execute: start_wake_word
on_turn_on:
- script.execute: stop_wake_word
# Turn on the repeat mode and pause for 1000 ms between playlist items/repeats
- lambda: |-
id(echo_pyramid_player)
->make_call()
.set_command(media_player::MediaPlayerCommand::MEDIA_PLAYER_COMMAND_REPEAT_ONE)
.set_announcement(true)
.perform();
id(echo_pyramid_player)->set_playlist_delay_ms(speaker::AudioPipelineType::ANNOUNCEMENT, 1000);
# - media_player.speaker.play_on_device_media_file:
# media_file: timer_finished_sound
# announcement: true
- delay: 15min
- switch.turn_off: timer_ringing
select:
- platform: template
entity_category: config
name: Wake word engine location
id: wake_word_engine_location
icon: "mdi:account-voice"
optimistic: true
restore_value: true
options:
- In Home Assistant
- On device
initial_option: On device
on_value:
- if:
condition:
lambda: return !id(init_in_progress);
then:
- wait_until:
lambda: return id(voice_assistant_phase) == ${voice_assist_muted_phase_id} || id(voice_assistant_phase) == ${voice_assist_idle_phase_id};
- if:
condition:
lambda: return x == "In Home Assistant";
then:
- micro_wake_word.stop
- delay: 500ms
- if:
condition:
switch.is_off: mute
then:
- lambda: id(va).set_use_wake_word(true);
- voice_assistant.start_continuous:
- if:
condition:
lambda: return x == "On device";
then:
- lambda: id(va).set_use_wake_word(false);
- voice_assistant.stop
- delay: 500ms
- if:
condition:
switch.is_off: mute
then:
- micro_wake_word.start
- platform: template
name: "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
}
micro_wake_word:
id: mww
microphone: i2s_mic
models:
- model: okay_nabu
id: okay_nabu
- model: hey_jarvis
id: hey_jarvis
- model: hey_mycroft
id: hey_mycroft
- model: https://raw.githubusercontent.com/esphome/micro-wake-word-models/refs/heads/main/models/v2/experiments/hey_peppa_pig.json
id: hey_peppa_pig
- model: ${stop_model_file}
id: stop
internal: true
vad:
on_wake_word_detected:
- script.execute:
id: play_sound
priority: true
sound_file: !lambda return id(wake_word_triggered_sound);
- wait_until:
condition:
- media_player.is_announcing:
timeout: 0.5s
# Announcement is finished and the I2S bus is free
- wait_until:
- and:
- not:
media_player.is_announcing:
- not:
speaker.is_playing:
- voice_assistant.start:
wake_word: !lambda return wake_word;
voice_assistant:
id: va
microphone: i2s_mic
media_player: echo_pyramid_player
micro_wake_word: mww
noise_suppression_level: 2
auto_gain: 31dBFS
volume_multiplier: 2.0
on_listening:
- lambda: id(voice_assistant_phase) = ${voice_assist_listening_phase_id};
- script.execute: draw_display
on_stt_vad_end:
- lambda: id(voice_assistant_phase) = ${voice_assist_thinking_phase_id};
- script.execute: draw_display
on_tts_start:
- lambda: id(voice_assistant_phase) = ${voice_assist_replying_phase_id};
- script.execute: draw_display
on_end:
# Wait a short amount of time to see if an announcement starts
- wait_until:
condition:
- media_player.is_announcing:
timeout: 0.5s
# Announcement is finished and the I2S bus is free
- wait_until:
- and:
- not:
media_player.is_announcing:
- not:
speaker.is_playing:
# Restart only mWW if enabled; streaming wake words automatically restart
- if:
condition:
- lambda: |-
return id(wake_word_engine_location).current_option() == "On device";
then:
- lambda: id(va).set_use_wake_word(false);
- micro_wake_word.start:
- script.execute: set_idle_or_mute_phase
- script.execute: draw_display
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: draw_display
- delay: 1s
- if:
condition:
switch.is_off: mute
then:
- lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
else:
- lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
# 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: !lambda return id(error_cloud_expired);
- script.execute: draw_display
on_client_connected:
- lambda: id(init_in_progress) = false;
- script.execute: start_wake_word
- script.execute: set_idle_or_mute_phase
- script.execute: draw_display
on_client_disconnected:
- script.execute: stop_wake_word
- lambda: id(voice_assistant_phase) = ${voice_assist_not_ready_phase_id};
- script.execute: draw_display
on_timer_finished:
- switch.turn_on: timer_ringing
- wait_until:
media_player.is_announcing:
- lambda: id(voice_assistant_phase) = ${voice_assist_timer_finished_phase_id};
globals:
- id: init_in_progress
type: bool
restore_value: false
initial_value: "true"
- id: voice_assistant_phase
type: int
restore_value: false
initial_value: ${voice_assist_not_ready_phase_id}
- id: current_volume
type: float
restore_value: true
initial_value: "0.3"
sensor:
- platform: pyramidtouch
address: 0x1A
i2c_id: ext_bus
update_interval: 50ms
publish_swipe_event: true
swipe_timeout_ms: 500
touch1:
name: "Touch 1"
touch2:
name: "Touch 2"
touch3:
name: "Touch 3"
touch4:
name: "Touch 4"
swipe_event:
name: "Touch Swipe Event"
entity_category: diagnostic
on_value:
then:
- lambda: |-
// Swipe codes:
// 1 = Left Up (volume up)
// 2 = Left Down (volume down)
// 3 = Right Up (brightness up)
// 4 = Right Down (brightness down)
const float volume_step = 0.05f; // 5% volume per gesture
const float brightness_step = 5.0f; // 5% brightness per gesture
const int ev = (int) x;
if (ev == 1 || ev == 2) {
// Left side: control volume (0.0 - 1.0)
float v = id(current_volume);
if (ev == 1) {
v = std::min(1.0f, v + volume_step);
} else {
v = std::max(0.0f, v - volume_step);
}
auto call = id(echo_pyramid_player).make_call();
call.set_volume(v);
call.perform();
id(current_volume) = v;
} else if (ev == 3 || ev == 4) {
// Right side: control RGB brightness (0 - 100)
float b = id(rgb_master_brightness).state;
if (ev == 3) {
b = std::min(100.0f, b + brightness_step);
} else {
b = std::max(0.0f, b - brightness_step);
}
uint8_t b8 = (uint8_t) b;
id(pyramid_rgb1).set_strip_brightness(1, b8);
id(pyramid_rgb2).set_strip_brightness(2, b8);
id(rgb_master_brightness).publish_state(b);
} else {
return;
}
lp5562:
id: lp5562_led
i2c_id: bsp_bus
use_internal_clk: true
# power_save_mode: true
# high_pwm_freq: true
# logarithmic_dimming: true
white_current: 17.5
pyramidrgb:
- id: pyramid_rgb1
i2c_id: ext_bus
address: 0x1A
strip: 1
brightness: 80
- id: pyramid_rgb2
i2c_id: ext_bus
address: 0x1A
strip: 2
brightness: 80
number:
# Master media player volume (0.01.0)
- platform: template
name: "Master Volume"
id: master_volume
icon: "mdi:volume-high"
min_value: 0.0
max_value: 0.4
step: 0.01
restore_value: true
initial_value: 0.3
optimistic: true
set_action:
- lambda: |-
float v = x;
auto call = id(echo_pyramid_player).make_call();
call.set_volume(v);
call.perform();
id(current_volume) = v;
# Master RGB brightness (applies to both strips, 0100%)
- platform: template
name: "RGB Master Brightness"
id: rgb_master_brightness
icon: "mdi:brightness-6"
min_value: 0
max_value: 100
step: 1
restore_value: true
initial_value: 100
optimistic: true
set_action:
- lambda: |-
uint8_t b = (uint8_t) x;
id(pyramid_rgb1).set_strip_brightness(1, b);
id(pyramid_rgb2).set_strip_brightness(2, b);
output:
- platform: lp5562
id: lp5562_white_channel
lp5562_id: lp5562_led
channel: white
- platform: pyramidrgb
id: rgb1_ch0_red
pyramidrgb_id: pyramid_rgb1
channel: 0
color: red
- platform: pyramidrgb
id: rgb1_ch0_green
pyramidrgb_id: pyramid_rgb1
channel: 0
color: green
- platform: pyramidrgb
id: rgb1_ch0_blue
pyramidrgb_id: pyramid_rgb1
channel: 0
color: blue
# Strip 1, Channel 1 (Group 2)
- platform: pyramidrgb
id: rgb1_ch1_red
pyramidrgb_id: pyramid_rgb1
channel: 1
color: red
- platform: pyramidrgb
id: rgb1_ch1_green
pyramidrgb_id: pyramid_rgb1
channel: 1
color: green
- platform: pyramidrgb
id: rgb1_ch1_blue
pyramidrgb_id: pyramid_rgb1
channel: 1
color: blue
# Strip 2, Channel 2 (Group 1)
- platform: pyramidrgb
id: rgb2_ch2_red
pyramidrgb_id: pyramid_rgb2
channel: 2
color: red
- platform: pyramidrgb
id: rgb2_ch2_green
pyramidrgb_id: pyramid_rgb2
channel: 2
color: green
- platform: pyramidrgb
id: rgb2_ch2_blue
pyramidrgb_id: pyramid_rgb2
channel: 2
color: blue
# Strip 2, Channel 3 (Group 2)
- platform: pyramidrgb
id: rgb2_ch3_red
pyramidrgb_id: pyramid_rgb2
channel: 3
color: red
- platform: pyramidrgb
id: rgb2_ch3_green
pyramidrgb_id: pyramid_rgb2
channel: 3
color: green
- platform: pyramidrgb
id: rgb2_ch3_blue
pyramidrgb_id: pyramid_rgb2
channel: 3
color: blue
light:
- platform: monochromatic
name: "LCD Backlight"
output: lp5562_white_channel
icon: "mdi:television"
restore_mode: RESTORE_DEFAULT_ON
- platform: rgb
name: "Strip1 Group1"
red: rgb1_ch0_red
green: rgb1_ch0_green
blue: rgb1_ch0_blue
restore_mode: RESTORE_DEFAULT_ON
- platform: rgb
name: "Strip1 Group2"
red: rgb1_ch1_red
green: rgb1_ch1_green
blue: rgb1_ch1_blue
restore_mode: RESTORE_DEFAULT_ON
- platform: rgb
name: "Strip2 Group1"
red: rgb2_ch2_red
green: rgb2_ch2_green
blue: rgb2_ch2_blue
restore_mode: RESTORE_DEFAULT_ON
- platform: rgb
name: "Strip2 Group2"
red: rgb2_ch3_red
green: rgb2_ch3_green
blue: rgb2_ch3_blue
restore_mode: RESTORE_DEFAULT_ON
display:
- platform: mipi_spi
id: atoms3r_lcd
model: ST7789V
dc_pin: GPIO42
reset_pin: GPIO48
cs_pin: GPIO14
data_rate: 40MHz
dimensions:
height: 128
width: 128
offset_width: 2
offset_height: 1
invert_colors: true
rotation: 180°
pages:
- id: idle_page
lambda: |-
it.fill(id(idle_color));
it.image((it.get_width() / 2), (it.get_height() / 2), id(casita_idle), ImageAlign::CENTER);
- id: listening_page
lambda: |-
it.fill(id(listening_color));
it.image((it.get_width() / 2), (it.get_height() / 2), id(casita_listening), ImageAlign::CENTER);
- id: thinking_page
lambda: |-
it.fill(id(thinking_color));
it.image((it.get_width() / 2), (it.get_height() / 2), id(casita_thinking), ImageAlign::CENTER);
- id: replying_page
lambda: |-
it.fill(id(replying_color));
it.image((it.get_width() / 2), (it.get_height() / 2), id(casita_replying), ImageAlign::CENTER);
- id: error_page
lambda: |-
it.fill(id(error_color));
it.image((it.get_width() / 2), (it.get_height() / 2), id(casita_error), ImageAlign::CENTER);
- id: no_ha_page
lambda: |-
it.image((it.get_width() / 2), (it.get_height() / 2), id(error_no_ha), ImageAlign::CENTER);
- id: no_wifi_page
lambda: |-
it.image((it.get_width() / 2), (it.get_height() / 2), id(error_no_wifi), ImageAlign::CENTER);
- id: initializing_page
lambda: |-
it.fill(id(loading_color));
it.image((it.get_width() / 2), (it.get_height() / 2), id(casita_initializing), ImageAlign::CENTER);
- id: muted_page
lambda: |-
it.fill(Color::BLACK);
it.printf(0, 0, id(mdi_icon_128), Color::WHITE, "%s", "\U000F036D");
script:
# Starts either mWW or the streaming wake word, depending on the configured location
- id: start_wake_word
then:
- if:
condition:
and:
- not:
- voice_assistant.is_running:
- lambda: |-
return id(wake_word_engine_location).current_option() == "On device";
then:
- lambda: id(va).set_use_wake_word(false);
- micro_wake_word.start:
- if:
condition:
and:
- not:
- voice_assistant.is_running:
- lambda: |-
return id(wake_word_engine_location).current_option() == "In Home Assistant";
then:
- lambda: id(va).set_use_wake_word(true);
- voice_assistant.start_continuous:
# Stops either mWW or the streaming wake word, depending on the configured location
- id: stop_wake_word
then:
- if:
condition:
lambda: |-
return id(wake_word_engine_location).current_option() == "In Home Assistant";
then:
- lambda: id(va).set_use_wake_word(false);
- voice_assistant.stop:
- if:
condition:
lambda: |-
return id(wake_word_engine_location).current_option() == "On device";
then:
- micro_wake_word.stop:
# Set the voice assistant phase to idle or muted, depending on if the software mute switch is activated
- id: set_idle_or_mute_phase
then:
- if:
condition:
switch.is_off: mute
then:
- lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
else:
- lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
- id: play_sound
parameters:
priority: bool
sound_file: "audio::AudioFile*"
then:
- lambda: |-
if (priority) {
id(echo_pyramid_player)
->make_call()
.set_command(media_player::MediaPlayerCommand::MEDIA_PLAYER_COMMAND_STOP)
.set_announcement(true)
.perform();
}
if ( (id(echo_pyramid_player).state != media_player::MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING ) || priority) {
id(echo_pyramid_player)
->play_file(sound_file, true, false);
}
- id: draw_display
then:
- if:
condition:
lambda: return !id(init_in_progress);
then:
- if:
condition:
wifi.connected:
then:
- if:
condition:
api.connected:
then:
- lambda: |
switch(id(voice_assistant_phase)) {
case ${voice_assist_listening_phase_id}:
id(atoms3r_lcd).show_page(listening_page);
id(atoms3r_lcd).update();
break;
case ${voice_assist_thinking_phase_id}:
id(atoms3r_lcd).show_page(thinking_page);
id(atoms3r_lcd).update();
break;
case ${voice_assist_replying_phase_id}:
id(atoms3r_lcd).show_page(replying_page);
id(atoms3r_lcd).update();
break;
case ${voice_assist_error_phase_id}:
id(atoms3r_lcd).show_page(error_page);
id(atoms3r_lcd).update();
break;
case ${voice_assist_muted_phase_id}:
id(atoms3r_lcd).show_page(muted_page);
id(atoms3r_lcd).update();
break;
case ${voice_assist_not_ready_phase_id}:
id(atoms3r_lcd).show_page(no_ha_page);
id(atoms3r_lcd).update();
break;
default:
id(atoms3r_lcd).show_page(idle_page);
id(atoms3r_lcd).update();
}
else:
- display.page.show: no_ha_page
- component.update: atoms3r_lcd
else:
- display.page.show: no_wifi_page
- component.update: atoms3r_lcd
else:
- display.page.show: initializing_page
- component.update: atoms3r_lcd
image:
- file: ${error_illustration_file}
id: casita_error
resize: 160x120
type: RGB
transparency: alpha_channel
- file: ${idle_illustration_file}
id: casita_idle
resize: 160x120
type: RGB
transparency: alpha_channel
- file: ${listening_illustration_file}
id: casita_listening
resize: 160x120
type: RGB
transparency: alpha_channel
- file: ${thinking_illustration_file}
id: casita_thinking
resize: 160x120
type: RGB
transparency: alpha_channel
- file: ${replying_illustration_file}
id: casita_replying
resize: 160x120
type: RGB
transparency: alpha_channel
- file: ${loading_illustration_file}
id: casita_initializing
resize: 160x120
type: RGB
transparency: alpha_channel
- file: ${error_no_wifi_illustration_file}
id: error_no_wifi
resize: 160x120
type: RGB
transparency: alpha_channel
- file: ${error_no_ha_illustration_file}
id: error_no_ha
resize: 160x120
type: RGB
transparency: alpha_channel
font:
- file: ${mdi_webfont_file}
id: mdi_icon_128
size: 128
bpp: 4
glyphs:
- "\U000F036D" # mdi:mic-mute
color:
- id: idle_color
hex: ${idle_illustration_background_color}
- id: listening_color
hex: ${listening_illustration_background_color}
- id: thinking_color
hex: ${thinking_illustration_background_color}
- id: replying_color
hex: ${replying_illustration_background_color}
- id: loading_color
hex: ${loading_illustration_background_color}
- id: error_color
hex: ${error_illustration_background_color}