diff --git a/active/device_esphome/.gitignore b/active/device_esphome/.gitignore new file mode 100644 index 0000000..d8b4157 --- /dev/null +++ b/active/device_esphome/.gitignore @@ -0,0 +1,5 @@ +# Gitignore settings for ESPHome +# This is an example and may include too much for your use-case. +# You can modify this file to suit your needs. +/.esphome/ +/secrets.yaml diff --git a/active/device_esphome/default-atom-echo.yaml b/active/device_esphome/default-atom-echo.yaml new file mode 100644 index 0000000..3c58093 --- /dev/null +++ b/active/device_esphome/default-atom-echo.yaml @@ -0,0 +1,370 @@ +substitutions: + name: m5stack-atom-echo + friendly_name: M5Stack Atom Echo + +esphome: + name: ${name} + name_add_mac_suffix: true + friendly_name: ${friendly_name} + min_version: 2025.5.0 + +esp32: + board: m5stack-atom + cpu_frequency: 240MHz + framework: + type: esp-idf + +logger: +api: + +ota: + - platform: esphome + id: ota_esphome + +wifi: + ap: + +captive_portal: + +button: + - platform: factory_reset + id: factory_reset_btn + name: Factory reset + +i2s_audio: + - id: i2s_audio_bus + i2s_lrclk_pin: GPIO33 + i2s_bclk_pin: GPIO19 + +microphone: + - platform: i2s_audio + id: echo_microphone + i2s_din_pin: GPIO23 + adc_type: external + pdm: true + sample_rate: 16000 + correct_dc_offset: true + +speaker: + - platform: i2s_audio + id: echo_speaker + i2s_dout_pin: GPIO22 + dac_type: external + bits_per_sample: 16bit + sample_rate: 16000 + channel: stereo # The Echo has poor playback audio quality when using mon audio + buffer_duration: 60ms + +media_player: + - platform: speaker + name: None + id: echo_media_player + announcement_pipeline: + speaker: echo_speaker + format: WAV + codec_support_enabled: false + buffer_size: 6000 + volume_min: 0.4 + files: + - id: timer_finished_wave_file + file: https://github.com/esphome/wake-word-voice-assistants/raw/main/sounds/timer_finished.wav + on_announcement: + - if: + condition: + - microphone.is_capturing: + then: + - script.execute: stop_wake_word + - light.turn_on: + id: led + blue: 100% + red: 0% + green: 0% + brightness: 100% + effect: none + on_idle: + - script.execute: start_wake_word + - script.execute: reset_led + +voice_assistant: + id: va + micro_wake_word: + microphone: + microphone: echo_microphone + channels: 0 + gain_factor: 4 + media_player: echo_media_player + noise_suppression_level: 2 + auto_gain: 31dBFS + on_listening: + - light.turn_on: + id: led + blue: 100% + red: 0% + green: 0% + effect: "Slow Pulse" + on_stt_vad_end: + - light.turn_on: + id: led + blue: 100% + red: 0% + green: 0% + effect: "Fast Pulse" + on_tts_start: + - light.turn_on: + id: led + blue: 100% + red: 0% + green: 0% + brightness: 100% + effect: none + on_end: + # Handle the "nevermind" case where there is no announcement + - wait_until: + condition: + - media_player.is_announcing: + timeout: 0.5s + # Restart only mWW if enabled; streaming wake words automatically restart + - if: + condition: + - lambda: return id(wake_word_engine_location).state == "On device"; + then: + - wait_until: + - and: + - not: + voice_assistant.is_running: + - not: + speaker.is_playing: + - lambda: id(va).set_use_wake_word(false); + - micro_wake_word.start: + - script.execute: reset_led + on_error: + - light.turn_on: + id: led + red: 100% + green: 0% + blue: 0% + brightness: 100% + effect: none + - delay: 2s + - script.execute: reset_led + on_client_connected: + - delay: 2s # Give the api server time to settle + - script.execute: start_wake_word + on_client_disconnected: + - script.execute: stop_wake_word + on_timer_finished: + - script.execute: stop_wake_word + - wait_until: + not: + microphone.is_capturing: + - switch.turn_on: timer_ringing + - light.turn_on: + id: led + red: 0% + green: 100% + blue: 0% + brightness: 100% + effect: "Fast Pulse" + - wait_until: + - switch.is_off: timer_ringing + - light.turn_off: led + - switch.turn_off: timer_ringing + +binary_sensor: + # button does the following: + # short click - stop a timer + # if no timer then restart either microwakeword or voice assistant continuous + - platform: gpio + pin: + number: GPIO39 + inverted: true + name: Button + disabled_by_default: true + entity_category: diagnostic + id: echo_button + on_multi_click: + - timing: + - ON for at least 50ms + - OFF for at least 50ms + then: + - if: + condition: + switch.is_on: timer_ringing + then: + - switch.turn_off: timer_ringing + else: + - script.execute: start_wake_word + - timing: + - ON for at least 10s + then: + - button.press: factory_reset_btn + +light: + - platform: esp32_rmt_led_strip + id: led + name: None + disabled_by_default: true + entity_category: config + pin: GPIO27 + default_transition_length: 0s + chipset: SK6812 + num_leds: 1 + rgb_order: grb + effects: + - pulse: + name: "Slow Pulse" + transition_length: 250ms + update_interval: 250ms + min_brightness: 50% + max_brightness: 100% + - pulse: + name: "Fast Pulse" + transition_length: 100ms + update_interval: 100ms + min_brightness: 50% + max_brightness: 100% + +script: + - id: reset_led + then: + - if: + condition: + - lambda: return id(wake_word_engine_location).state == "On device"; + - switch.is_on: use_listen_light + then: + - light.turn_on: + id: led + red: 100% + green: 89% + blue: 71% + brightness: 60% + effect: none + else: + - if: + condition: + - lambda: return id(wake_word_engine_location).state != "On device"; + - switch.is_on: use_listen_light + then: + - light.turn_on: + id: led + red: 0% + green: 100% + blue: 100% + brightness: 60% + effect: none + else: + - light.turn_off: led + - id: start_wake_word + then: + - if: + condition: + and: + - not: + - voice_assistant.is_running: + - lambda: return id(wake_word_engine_location).state == "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).state == "In Home Assistant"; + then: + - lambda: id(va).set_use_wake_word(true); + - voice_assistant.start_continuous: + - id: stop_wake_word + then: + - if: + condition: + lambda: return id(wake_word_engine_location).state == "In Home Assistant"; + then: + - lambda: id(va).set_use_wake_word(false); + - voice_assistant.stop: + - if: + condition: + lambda: return id(wake_word_engine_location).state == "On device"; + then: + - micro_wake_word.stop: + +switch: + - platform: template + name: Use listen light + id: use_listen_light + optimistic: true + restore_mode: RESTORE_DEFAULT_ON + entity_category: config + on_turn_on: + - script.execute: reset_led + on_turn_off: + - script.execute: reset_led + - platform: template + id: timer_ringing + optimistic: true + restore_mode: ALWAYS_OFF + on_turn_off: + # Turn off the repeat mode and disable the pause between playlist items + - lambda: |- + id(echo_media_player) + ->make_call() + .set_command(media_player::MediaPlayerCommand::MEDIA_PLAYER_COMMAND_REPEAT_OFF) + .set_announcement(true) + .perform(); + id(echo_media_player)->set_playlist_delay_ms(speaker::AudioPipelineType::ANNOUNCEMENT, 0); + # Stop playing the alarm + - media_player.stop: + announcement: true + on_turn_on: + # Turn on the repeat mode and pause for 1000 ms between playlist items/repeats + - lambda: |- + id(echo_media_player) + ->make_call() + .set_command(media_player::MediaPlayerCommand::MEDIA_PLAYER_COMMAND_REPEAT_ONE) + .set_announcement(true) + .perform(); + id(echo_media_player)->set_playlist_delay_ms(speaker::AudioPipelineType::ANNOUNCEMENT, 1000); + - media_player.speaker.play_on_device_media_file: + media_file: timer_finished_wave_file + 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 + optimistic: true + restore_value: true + options: + - In Home Assistant + - On device + initial_option: On device + on_value: + - if: + condition: + lambda: return x == "In Home Assistant"; + then: + - micro_wake_word.stop: + - delay: 500ms + - 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 + - micro_wake_word.start: + +micro_wake_word: + on_wake_word_detected: + - voice_assistant.start: + wake_word: !lambda return wake_word; + vad: + models: + - model: okay_nabu + - model: hey_mycroft + - model: hey_jarvis diff --git a/active/device_esphome/esphome.md b/active/device_esphome/esphome.md new file mode 100644 index 0000000..2d5fc56 --- /dev/null +++ b/active/device_esphome/esphome.md @@ -0,0 +1,179 @@ +# ESP32 + +- [ESP32](#esp32) + - [Install](#install) + - [Devices](#devices) + - [Lilygo tdongle](#lilygo-tdongle) + - [Local Flashing](#local-flashing) + - [Adding a New Device](#adding-a-new-device) + - [Controlling Home Assistant](#controlling-home-assistant) + - [Configuration Sections](#configuration-sections) + - [esphome](#esphome) + - [esp32](#esp32-1) + - [logger](#logger) + - [api](#api) + - [wifi](#wifi) + - [ota](#ota) + - [captive portal](#captive-portal) + - [button](#button) + - [i2s audio](#i2s-audio) + - [microphone](#microphone) + - [speaker](#speaker) + - [media player](#media-player) + - [voice assistant](#voice-assistant) + - [micro wake word](#micro-wake-word) + - [light](#light) + - [binary sensor](#binary-sensor) + - [lambda](#lambda) + +## Install + +```bash +# Check that you have python 3.11 installed +uv python list --only-installed + +# Create the venv (python 3.11 is recommended in the docs) +uv venv --python 3.11 + +# Install esphome +uv pip install esphome wheel pip +source .venv/bin/activate +``` + +## Devices + +### Lilygo tdongle + +Display: 80 X 160 + +## Local Flashing + +Make sure your permissions are set correctly + +```bash +sudo usermod -a -G dialout ducoterra +``` + +Then "run" your config file + +```bash +cd active/device_esp32 +uv venv +uv pip install esphome +source .venv/bin/activate + +esphome run m5stack-atom-echo.yaml +``` + +## Adding a New Device + +1. Create a new yaml configuration file called "my-device-device-type.yaml" + +## Controlling Home Assistant + + + +## Configuration Sections + + + +### esphome + +### esp32 + + + +### logger + + + +### api + + + +### wifi + + + +### ota + + + + + +### captive portal + + + +### button + + + +### i2s audio + + + +### microphone + + + + + +### speaker + + + +### media player + + + +Sometimes you'll need to convert media files to supported encoders. + +```bash +ffmpeg -i input.flac output.wav +``` + +To play media on other devices from home assistant, put the + +```yaml +action: media_player.play_media +target: + entity_id: media_player.kitchen_google_home +data: + media_content_type: "audio/wav" + media_content_id: "media-source://media_source/local/wake_word_triggered.wav" +``` + +### voice assistant + + + +In Home Assistant's configuration.yaml, add the following to listen to +audio recordings of your voice request: + +```bash +assist_pipeline: + debug_recording_dir: /share/assist_pipeline +``` + +### micro wake word + + + +### light + + + +### binary sensor + + + +### lambda + + + +> id(...) is a helper function that makes ESPHome fetch an object with the +> supplied ID (which you defined somewhere else, like top_end_stop ) and lets +> you call any of ESPHome’s many APIs directly. For example, here we’re +> retrieving the current state of the end stop using .state and using it to +> construct our cover state. diff --git a/active/device_esphome/great-room-atom-echo.yaml b/active/device_esphome/great-room-atom-echo.yaml new file mode 100644 index 0000000..96748d6 --- /dev/null +++ b/active/device_esphome/great-room-atom-echo.yaml @@ -0,0 +1,386 @@ +esphome: + name: great-room-atom-echo + friendly_name: Great Room Atom Echo + +esp32: + board: m5stack-atom + framework: + type: esp-idf + +# Enable logging +logger: + level: debug + +# Enable Home Assistant API +api: + encryption: + key: !secret great_room_atom_echo_key + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + domain: .reeselink.com + fast_connect: true + enable_btm: true + on_disconnect: + - light.turn_on: + id: led + blue: 0% + red: 100% + green: 0% + effect: "Slow Pulse" + # Enable fallback hotspot (captive portal) in case wifi connection fails + ap: + ssid: "Great-Room-Atom-Echo" + password: !secret hotspot_password + +ota: + - platform: esphome + password: !secret ota_password + +captive_portal: + +button: + - platform: factory_reset + id: factory_reset_btn + name: Factory reset + +i2s_audio: + - id: i2s_audio_bus + i2s_lrclk_pin: GPIO33 + i2s_bclk_pin: GPIO19 + +microphone: + - platform: i2s_audio + id: echo_microphone + i2s_din_pin: GPIO23 + adc_type: external + pdm: true + sample_rate: 16000 + correct_dc_offset: true + +speaker: + - platform: i2s_audio + id: echo_speaker + i2s_dout_pin: GPIO22 + dac_type: external + bits_per_sample: 16bit + sample_rate: 16000 + channel: stereo # The Echo has poor playback audio quality when using mon audio + buffer_duration: 60ms + +media_player: + - platform: speaker + name: None + id: echo_media_player + announcement_pipeline: + speaker: echo_speaker + format: WAV + codec_support_enabled: false + buffer_size: 6000 + volume_min: 1 + volume_max: 1 + volume_initial: 1 + files: + - id: timer_finished_wave_file + file: https://github.com/esphome/wake-word-voice-assistants/raw/main/sounds/timer_finished.wav + on_announcement: + - if: + condition: + - microphone.is_capturing: + then: + - script.execute: stop_wake_word + - light.turn_on: + id: led + blue: 100% + red: 0% + green: 0% + brightness: 100% + effect: none + on_idle: + - script.execute: start_wake_word + - script.execute: reset_led + +voice_assistant: + id: va + micro_wake_word: + microphone: + microphone: echo_microphone + channels: 0 + gain_factor: 64 + media_player: echo_media_player + noise_suppression_level: 2 + auto_gain: 31dBFS + on_listening: + - light.turn_on: + id: led + blue: 100% + red: 0% + green: 0% + effect: "Slow Pulse" + on_stt_vad_end: + - light.turn_on: + id: led + blue: 100% + red: 0% + green: 0% + effect: "Fast Pulse" + on_tts_start: + - light.turn_on: + id: led + blue: 100% + red: 0% + green: 0% + brightness: 100% + effect: none + on_end: + # Handle the "nevermind" case where there is no announcement + - wait_until: + condition: + - media_player.is_announcing: + timeout: 0.5s + # Restart only mWW if enabled; streaming wake words automatically restart + - if: + condition: + - lambda: return id(wake_word_engine_location).state == "On device"; + then: + - wait_until: + - and: + - not: + voice_assistant.is_running: + - not: + speaker.is_playing: + - lambda: id(va).set_use_wake_word(false); + - micro_wake_word.start: + - script.execute: reset_led + on_error: + - light.turn_on: + id: led + red: 100% + green: 0% + blue: 0% + brightness: 100% + effect: none + - delay: 2s + - script.execute: reset_led + on_client_connected: + - delay: 2s # Give the api server time to settle + - script.execute: start_wake_word + on_client_disconnected: + - script.execute: stop_wake_word + on_timer_finished: + - script.execute: stop_wake_word + - wait_until: + not: + microphone.is_capturing: + - switch.turn_on: timer_ringing + - light.turn_on: + id: led + red: 0% + green: 100% + blue: 0% + brightness: 100% + effect: "Fast Pulse" + - wait_until: + - switch.is_off: timer_ringing + - light.turn_off: led + - switch.turn_off: timer_ringing + +binary_sensor: + # button does the following: + # short click - stop a timer + # if no timer then restart either microwakeword or voice assistant continuous + - platform: gpio + pin: + number: GPIO39 + inverted: true + name: Button + disabled_by_default: true + entity_category: diagnostic + id: echo_button + on_multi_click: + - timing: + - ON for at least 50ms + - OFF for at least 50ms + then: + - if: + condition: + switch.is_on: timer_ringing + then: + - switch.turn_off: timer_ringing + else: + - script.execute: start_wake_word + - timing: + - ON for at least 10s + then: + - button.press: factory_reset_btn + +light: + - platform: esp32_rmt_led_strip + id: led + name: None + disabled_by_default: true + entity_category: config + pin: GPIO27 + default_transition_length: 0s + chipset: SK6812 + num_leds: 1 + rgb_order: grb + effects: + - pulse: + name: "Slow Pulse" + transition_length: 250ms + update_interval: 250ms + min_brightness: 50% + max_brightness: 100% + - pulse: + name: "Fast Pulse" + transition_length: 100ms + update_interval: 100ms + min_brightness: 50% + max_brightness: 100% + +script: + - id: reset_led + then: + - if: + condition: + - lambda: return id(wake_word_engine_location).state == "On device"; + - switch.is_on: use_listen_light + then: + - light.turn_on: + id: led + red: 100% + green: 89% + blue: 71% + brightness: 60% + effect: none + else: + - if: + condition: + - lambda: return id(wake_word_engine_location).state != "On device"; + - switch.is_on: use_listen_light + then: + - light.turn_on: + id: led + red: 0% + green: 100% + blue: 100% + brightness: 60% + effect: none + else: + - light.turn_off: led + - id: start_wake_word + then: + - if: + condition: + and: + - not: + - voice_assistant.is_running: + - lambda: return id(wake_word_engine_location).state == "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).state == "In Home Assistant"; + then: + - lambda: id(va).set_use_wake_word(true); + - voice_assistant.start_continuous: + - id: stop_wake_word + then: + - if: + condition: + lambda: return id(wake_word_engine_location).state == "In Home Assistant"; + then: + - lambda: id(va).set_use_wake_word(false); + - voice_assistant.stop: + - if: + condition: + lambda: return id(wake_word_engine_location).state == "On device"; + then: + - micro_wake_word.stop: + +switch: + - platform: template + name: Use listen light + id: use_listen_light + optimistic: true + restore_mode: RESTORE_DEFAULT_ON + entity_category: config + on_turn_on: + - script.execute: reset_led + on_turn_off: + - script.execute: reset_led + - platform: template + id: timer_ringing + optimistic: true + restore_mode: ALWAYS_OFF + on_turn_off: + # Turn off the repeat mode and disable the pause between playlist items + - lambda: |- + id(echo_media_player) + ->make_call() + .set_command(media_player::MediaPlayerCommand::MEDIA_PLAYER_COMMAND_REPEAT_OFF) + .set_announcement(true) + .perform(); + id(echo_media_player)->set_playlist_delay_ms(speaker::AudioPipelineType::ANNOUNCEMENT, 0); + # Stop playing the alarm + - media_player.stop: + announcement: true + on_turn_on: + # Turn on the repeat mode and pause for 1000 ms between playlist items/repeats + - lambda: |- + id(echo_media_player) + ->make_call() + .set_command(media_player::MediaPlayerCommand::MEDIA_PLAYER_COMMAND_REPEAT_ONE) + .set_announcement(true) + .perform(); + id(echo_media_player)->set_playlist_delay_ms(speaker::AudioPipelineType::ANNOUNCEMENT, 1000); + - media_player.speaker.play_on_device_media_file: + media_file: timer_finished_wave_file + 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 + optimistic: true + restore_value: true + options: + - In Home Assistant + - On device + initial_option: On device + on_value: + - if: + condition: + lambda: return x == "In Home Assistant"; + then: + - micro_wake_word.stop: + - delay: 500ms + - 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 + - micro_wake_word.start: + +micro_wake_word: + on_wake_word_detected: + - voice_assistant.start: + wake_word: !lambda return wake_word; + vad: + models: + - model: okay_nabu + - model: hey_mycroft + - model: hey_jarvis diff --git a/active/device_esphome/lilygo-tdongle.yaml b/active/device_esphome/lilygo-tdongle.yaml new file mode 100644 index 0000000..8e9d77f --- /dev/null +++ b/active/device_esphome/lilygo-tdongle.yaml @@ -0,0 +1,118 @@ +esphome: + name: tdongle + friendly_name: tdongle + +esp32: + board: esp32-s3-devkitc-1 + framework: + type: esp-idf + flash_size: 16MB + +logger: + +# Enable Home Assistant API +api: + encryption: + key: !secret lilygo_tdongle_key + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + domain: .reeselink.com + fast_connect: true + enable_btm: true + id: wifithing + # on_connect: + # - component.update: my_online_image + +ota: + - platform: esphome + password: !secret ota_password + +captive_portal: + +binary_sensor: + - platform: gpio + pin: GPIO0 + name: Button + +spi: + - id: spi_led + clk_pin: GPIO39 + mosi_pin: GPIO40 + - id: spi_lcd + clk_pin: GPIO5 + mosi_pin: GPIO3 + +output: + - platform: ledc + frequency: 2000 + pin: GPIO38 + inverted: True + id: backlight_output + +light: + - platform: monochromatic + output: backlight_output + name: "LCD Backlight" + id: lcd_backlight + restore_mode: ALWAYS_ON +# RGB Led, APA102 on GPIO39/GPIO40 + - platform: spi_led_strip + spi_id: spi_led + num_leds: 1 + name: "FastLED SPI Light" + data_rate: 1MHz # Adjust as needed, APA102 supports up to 20MHz, 1MHz is a safe starting point + +display: + - platform: st7735 + spi_id: spi_lcd + model: "INITR_MINI160X80" + reset_pin: GPIO1 + cs_pin: GPIO4 + dc_pin: GPIO2 + rotation: 270 + device_width: 82 + device_height: 161 + col_start: 0 + row_start: 0 + eight_bit_color: true + invert_colors: true + use_bgr: true + auto_clear_enabled: true + id: my_display + pages: + - id: page1 + lambda: |- + it.print(0, 10, id(font_roboto), "Connecting to"); + it.print(0, 30, id(font_roboto), "Home Assistant..."); + - id: page2 + lambda: |- + it.print(0, 10, id(font_roboto), "Configuring"); + it.print(0, 30, id(font_roboto), "sensors..."); + - id: page3 + lambda: |- + it.print(0, 10, id(font_roboto), "Loading"); + it.print(0, 30, id(font_roboto), "important"); + it.print(0, 50, id(font_roboto), "update..."); + - id: page4 + lambda: |- + it.image(0, 0, id(my_image), COLOR_OFF, COLOR_ON); + +image: + - file: "test_tdongle_image.png" + type: RGB + id: my_image + +http_request: + +font: + - file: "gfonts://Roboto" + id: font_roboto + size: 20 + +interval: + - interval: 5s + then: + - display.page.show_next: my_display + - component.update: my_display \ No newline at end of file diff --git a/active/device_esphome/loft-atom-echo.yaml b/active/device_esphome/loft-atom-echo.yaml new file mode 100644 index 0000000..5edd3c1 --- /dev/null +++ b/active/device_esphome/loft-atom-echo.yaml @@ -0,0 +1,387 @@ +esphome: + name: loft-atom-echo + friendly_name: Loft Atom Echo + +esp32: + board: m5stack-atom + cpu_frequency: 240MHz + framework: + type: esp-idf + +# Enable logging +logger: + level: debug + +# Enable Home Assistant API +api: + encryption: + key: !secret loft_atom_echo_key + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + domain: .reeselink.com + fast_connect: true + enable_btm: true + on_disconnect: + - light.turn_on: + id: led + blue: 0% + red: 100% + green: 0% + effect: "Slow Pulse" + # Enable fallback hotspot (captive portal) in case wifi connection fails + ap: + ssid: "Loft-Atom-Echo" + password: !secret hotspot_password + +ota: + - platform: esphome + password: !secret ota_password + +captive_portal: + +button: + - platform: factory_reset + id: factory_reset_btn + name: Factory reset + +i2s_audio: + - id: i2s_audio_bus + i2s_lrclk_pin: GPIO33 + i2s_bclk_pin: GPIO19 + +microphone: + - platform: i2s_audio + id: echo_microphone + i2s_din_pin: GPIO23 + adc_type: external + pdm: true + sample_rate: 16000 + correct_dc_offset: true + +speaker: + - platform: i2s_audio + id: echo_speaker + i2s_dout_pin: GPIO22 + dac_type: external + bits_per_sample: 16bit + sample_rate: 16000 + channel: stereo # The Echo has poor playback audio quality when using mon audio + buffer_duration: 60ms + +media_player: + - platform: speaker + name: None + id: echo_media_player + announcement_pipeline: + speaker: echo_speaker + format: WAV + codec_support_enabled: false + buffer_size: 6000 + volume_min: 1 + volume_max: 1 + volume_initial: 1 + files: + - id: timer_finished_wave_file + file: https://github.com/esphome/wake-word-voice-assistants/raw/main/sounds/timer_finished.wav + on_announcement: + - if: + condition: + - microphone.is_capturing: + then: + - script.execute: stop_wake_word + - light.turn_on: + id: led + blue: 100% + red: 0% + green: 0% + brightness: 100% + effect: none + on_idle: + - script.execute: start_wake_word + - script.execute: reset_led + +voice_assistant: + id: va + micro_wake_word: + microphone: + microphone: echo_microphone + channels: 0 + gain_factor: 64 + media_player: echo_media_player + noise_suppression_level: 2 + auto_gain: 31dBFS + on_listening: + - light.turn_on: + id: led + blue: 100% + red: 0% + green: 0% + effect: "Slow Pulse" + on_stt_vad_end: + - light.turn_on: + id: led + blue: 100% + red: 0% + green: 0% + effect: "Fast Pulse" + on_tts_start: + - light.turn_on: + id: led + blue: 100% + red: 0% + green: 0% + brightness: 100% + effect: none + on_end: + # Handle the "nevermind" case where there is no announcement + - wait_until: + condition: + - media_player.is_announcing: + timeout: 0.5s + # Restart only mWW if enabled; streaming wake words automatically restart + - if: + condition: + - lambda: return id(wake_word_engine_location).state == "On device"; + then: + - wait_until: + - and: + - not: + voice_assistant.is_running: + - not: + speaker.is_playing: + - lambda: id(va).set_use_wake_word(false); + - micro_wake_word.start: + - script.execute: reset_led + on_error: + - light.turn_on: + id: led + red: 100% + green: 0% + blue: 0% + brightness: 100% + effect: none + - delay: 2s + - script.execute: reset_led + on_client_connected: + - delay: 2s # Give the api server time to settle + - script.execute: start_wake_word + on_client_disconnected: + - script.execute: stop_wake_word + on_timer_finished: + - script.execute: stop_wake_word + - wait_until: + not: + microphone.is_capturing: + - switch.turn_on: timer_ringing + - light.turn_on: + id: led + red: 0% + green: 100% + blue: 0% + brightness: 100% + effect: "Fast Pulse" + - wait_until: + - switch.is_off: timer_ringing + - light.turn_off: led + - switch.turn_off: timer_ringing + +binary_sensor: + # button does the following: + # short click - stop a timer + # if no timer then restart either microwakeword or voice assistant continuous + - platform: gpio + pin: + number: GPIO39 + inverted: true + name: Button + disabled_by_default: true + entity_category: diagnostic + id: echo_button + on_multi_click: + - timing: + - ON for at least 50ms + - OFF for at least 50ms + then: + - if: + condition: + switch.is_on: timer_ringing + then: + - switch.turn_off: timer_ringing + else: + - script.execute: start_wake_word + - timing: + - ON for at least 10s + then: + - button.press: factory_reset_btn + +light: + - platform: esp32_rmt_led_strip + id: led + name: None + disabled_by_default: true + entity_category: config + pin: GPIO27 + default_transition_length: 0s + chipset: SK6812 + num_leds: 1 + rgb_order: grb + effects: + - pulse: + name: "Slow Pulse" + transition_length: 250ms + update_interval: 250ms + min_brightness: 50% + max_brightness: 100% + - pulse: + name: "Fast Pulse" + transition_length: 100ms + update_interval: 100ms + min_brightness: 50% + max_brightness: 100% + +script: + - id: reset_led + then: + - if: + condition: + - lambda: return id(wake_word_engine_location).state == "On device"; + - switch.is_on: use_listen_light + then: + - light.turn_on: + id: led + red: 100% + green: 89% + blue: 71% + brightness: 60% + effect: none + else: + - if: + condition: + - lambda: return id(wake_word_engine_location).state != "On device"; + - switch.is_on: use_listen_light + then: + - light.turn_on: + id: led + red: 0% + green: 100% + blue: 100% + brightness: 60% + effect: none + else: + - light.turn_off: led + - id: start_wake_word + then: + - if: + condition: + and: + - not: + - voice_assistant.is_running: + - lambda: return id(wake_word_engine_location).state == "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).state == "In Home Assistant"; + then: + - lambda: id(va).set_use_wake_word(true); + - voice_assistant.start_continuous: + - id: stop_wake_word + then: + - if: + condition: + lambda: return id(wake_word_engine_location).state == "In Home Assistant"; + then: + - lambda: id(va).set_use_wake_word(false); + - voice_assistant.stop: + - if: + condition: + lambda: return id(wake_word_engine_location).state == "On device"; + then: + - micro_wake_word.stop: + +switch: + - platform: template + name: Use listen light + id: use_listen_light + optimistic: true + restore_mode: RESTORE_DEFAULT_ON + entity_category: config + on_turn_on: + - script.execute: reset_led + on_turn_off: + - script.execute: reset_led + - platform: template + id: timer_ringing + optimistic: true + restore_mode: ALWAYS_OFF + on_turn_off: + # Turn off the repeat mode and disable the pause between playlist items + - lambda: |- + id(echo_media_player) + ->make_call() + .set_command(media_player::MediaPlayerCommand::MEDIA_PLAYER_COMMAND_REPEAT_OFF) + .set_announcement(true) + .perform(); + id(echo_media_player)->set_playlist_delay_ms(speaker::AudioPipelineType::ANNOUNCEMENT, 0); + # Stop playing the alarm + - media_player.stop: + announcement: true + on_turn_on: + # Turn on the repeat mode and pause for 1000 ms between playlist items/repeats + - lambda: |- + id(echo_media_player) + ->make_call() + .set_command(media_player::MediaPlayerCommand::MEDIA_PLAYER_COMMAND_REPEAT_ONE) + .set_announcement(true) + .perform(); + id(echo_media_player)->set_playlist_delay_ms(speaker::AudioPipelineType::ANNOUNCEMENT, 1000); + - media_player.speaker.play_on_device_media_file: + media_file: timer_finished_wave_file + 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 + optimistic: true + restore_value: true + options: + - In Home Assistant + - On device + initial_option: On device + on_value: + - if: + condition: + lambda: return x == "In Home Assistant"; + then: + - micro_wake_word.stop: + - delay: 500ms + - 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 + - micro_wake_word.start: + +micro_wake_word: + on_wake_word_detected: + - voice_assistant.start: + wake_word: !lambda return wake_word; + vad: + models: + - model: okay_nabu + - model: hey_mycroft + - model: hey_jarvis diff --git a/active/device_esphome/test_tdongle_image.png b/active/device_esphome/test_tdongle_image.png new file mode 100644 index 0000000..4cbfeca Binary files /dev/null and b/active/device_esphome/test_tdongle_image.png differ diff --git a/active/device_esphome/test_tdongle_image.xcf b/active/device_esphome/test_tdongle_image.xcf new file mode 100644 index 0000000..e44501a Binary files /dev/null and b/active/device_esphome/test_tdongle_image.xcf differ diff --git a/active/device_esphome/wake_word_triggered.flac b/active/device_esphome/wake_word_triggered.flac new file mode 100644 index 0000000..414e808 Binary files /dev/null and b/active/device_esphome/wake_word_triggered.flac differ diff --git a/active/device_esphome/wake_word_triggered.wav b/active/device_esphome/wake_word_triggered.wav new file mode 100644 index 0000000..ef1a53f Binary files /dev/null and b/active/device_esphome/wake_word_triggered.wav differ