M5Stack Atom Echo - On-device wake word with MicroWakeWord

@BramNH Sadly I have exactly the same problem :frowning: so at least you are not alone

1 Like

I can compile, flash and get all entities in home assistant. But the assistant never seems to go “active” in home assistant. In the logs, I do see that the wakeword is detected, but then the logs stop after setting state to “streaming_microphone”. I can put logs here later.

For now I just want to see if any older versions do work, or that it is just very unstable.

Oh okay then you are getting further than I do haha. Hoep someday there will be a solution. For me it is just stuck at “Preparing installation”.

On what system are you trying to build? I read it could use quite a bit of RAM.

My home assistant is running in a VM on an old laptop, it has 2GB of RAM. As I try to install it using the ESPHome add on I think this is the system you meant isn’t it?

Yes, quote from this site: On device wake word on ESP32-S3 is here - Voice: Chapter 6 - Home Assistant

“Changing the default “okay nabu” wake word will require adjusting your ESPHome configuration and recompiling the firmware, which may take a long time and requires a machine with more than 2GB of RAM.”

Oh okay, thank you. I got it to run through (took over 2000 seconds) and it said successfully installed but later wasn’t able to connect to the ESP:

Linking .pioenvs/m5stack-atom-echo/firmware.elf
/data/cache/platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: missing --end-group; added as last command line option
RAM:   [=         ]  10.6% (used 34684 bytes from 327680 bytes)
Flash: [========  ]  79.8% (used 1463925 bytes from 1835008 bytes)
Building .pioenvs/m5stack-atom-echo/firmware.bin
Creating esp32 image...
Successfully created esp32 image.
esp32_create_combined_bin([".pioenvs/m5stack-atom-echo/firmware.bin"], [".pioenvs/m5stack-atom-echo/firmware.elf"])
Wrote 0x177020 bytes to file /data/build/m5stack-atom-echo/.pioenvs/m5stack-atom-echo/firmware.factory.bin, ready to flash to offset 0x0
esp32_copy_ota_bin([".pioenvs/m5stack-atom-echo/firmware.bin"], [".pioenvs/m5stack-atom-echo/firmware.elf"])
======================== [SUCCESS] Took 2116.20 seconds ========================
INFO Successfully compiled program.
INFO Resolving IP address of m5stack-atom-echo.local
ERROR Error resolving IP address of m5stack-atom-echo.local. Is it connected to WiFi?
ERROR (If this error persists, please set a static IP address: https://esphome.io/components/wifi.html#manual-ips)
ERROR Error resolving IP address: Error resolving address with mDNS: Did not respond. Maybe the device is offline., [Errno -5] No address associated with hostname

Which does not make sense to me as I can see it in my routers network settings.

In my ESPHome the device I created with the configuration is shown as offline but next to it a new m5Stack Atom Echo has been discovered and I can adopt it.

It all looks very unclear to me.

I am also having issues with the domain name. Don’t install the firmware OTA, there should be an option to download manually and flash over usb

Sadly I did install it via USB :(. I will fiddle around a bit more and will hopefully find a solution haha

When I try to do that, I get an error, at the line I moved. could you please provide the complete working code.

---
substitutions:
  name: 
  friendly_name: Atom Echo
  static_ip: 192.xxxxxxx
  micro_wake_word_model: okay_nabu  # alexa, hey_jarvis, hey_mycroft are also supported



esphome:
  name: "${name}"
  friendly_name: "${friendly_name}"
  name_add_mac_suffix: false
  project:
    name: m5stack.atom-echo-voice-assistant
    version: "1.0"
  min_version: 2024.6.0


esp32:
  board: m5stack-atom
  framework:
    type: esp-idf

logger:
api:
  encryption:
    key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

ota:
  - platform: esphome
    id: ota_esphome
  - platform: http_request
    id: ota_http_request

update:
  - platform: http_request
    id: update_http_request
    name: Firmware
    source: https://firmware.esphome.io/wake-word-voice-assistant/m5stack-atom-echo/manifest.json

dashboard_import:
  package_import_url: github://esphome/firmware/voice-assistant/m5stack-atom-echo.yaml@main

http_request:

wifi:
  networks:
  - ssid: !secret wifi_ssid
    password: !secret wifi_password

  manual_ip:
    static_ip: ${static_ip}
    gateway: 192.xxxxxxxxx
    subnet: 255.255.255.0

button:
  - platform: factory_reset
    id: factory_reset_btn
    name: Factory reset
  - platform: restart
    name: "${friendly_name} Restart"


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

speaker:
  - platform: i2s_audio
    id: echo_speaker
    i2s_dout_pin: GPIO22
    dac_type: external
    # mode: mono

voice_assistant:
  id: va
  microphone: echo_microphone
  speaker: echo_speaker
  noise_suppression_level: 2
  auto_gain: 31dBFS
  volume_multiplier: 2.0
  vad_threshold: 3
  on_wake_word_detected:
    - microphone.stop_capture:
    - wait_until:
        not:
          microphone.is_capturing:
    - lambda: id(echo_speaker).play(id(ok_sound), sizeof(id(ok_sound)));
  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:
    - delay: 100ms
    - voice_assistant.stop:
    - wait_until:
        not:
          voice_assistant.is_running:
    - wait_until:
        not:
          switch.is_on: timer_ringing
    - if:
        condition:
          lambda: return id(wake_word_engine_location).state == "On device";
        then:
          - micro_wake_word.start:
          - script.execute: reset_led
        else:
          - voice_assistant.start_continuous:
          - 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
    - if:
        condition:
          lambda: return id(wake_word_engine_location).state == "On device";
        then:
          - micro_wake_word.start:
        else:
          - voice_assistant.start_continuous:
          - script.execute: reset_led
  on_client_disconnected:
    - voice_assistant.stop:
    - micro_wake_word.stop:
  on_timer_finished:
    - voice_assistant.stop:
    - micro_wake_word.stop:
    - switch.turn_on: timer_ringing
    - wait_until:
        not:
          microphone.is_capturing:
    - light.turn_on:
        id: led
        red: 0%
        green: 100%
        blue: 0%
        brightness: 100%
        effect: "Fast Pulse"
    - while:
        condition:
          switch.is_on: timer_ringing
        then:
          - lambda: id(echo_speaker).play(id(timer_finished_wave_file), sizeof(id(timer_finished_wave_file)));
          - delay: 1s
    - wait_until:
        not:
          speaker.is_playing:
    - light.turn_off: led
    - switch.turn_off: timer_ringing
    - if:
        condition:
          lambda: return id(wake_word_engine_location).state == "On device";
        then:
          - micro_wake_word.start:
          - script.execute: reset_led
        else:
          - voice_assistant.start_continuous:
          - script.execute: reset_led

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:
                - if:
                    condition:
                      lambda: return id(wake_word_engine_location).state == "On device";
                    then:
                      - voice_assistant.stop
                      - micro_wake_word.stop:
                      - delay: 1s
                      - script.execute: reset_led
                      - script.wait: reset_led
                      - micro_wake_word.start:
                    else:
                      - if:
                          condition: voice_assistant.is_running
                          then:
                            - voice_assistant.stop:
                            - script.execute: reset_led
                      - voice_assistant.start_continuous:
      - 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
    rmt_channel: 0
    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

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
    internal: true
    restore_mode: ALWAYS_OFF
    on_turn_on:
      - 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

external_components:
  - source: github://pr#5230
    components:
      - esp_adf
    refresh: 0s
  - source: github://jesserockz/esphome-components
    components: [file]
    refresh: 0s

esp_adf:

file:
  - id: ok_sound
    file: wakeword_detected3.raw
  - id: timer_finished_wave_file
    file: https://github.com/esphome/firmware/raw/main/voice-assistant/sounds/timer_finished.wav

micro_wake_word:
  on_wake_word_detected:
    - lambda: id(echo_speaker).play(id(ok_sound), sizeof(id(ok_sound)));
    - voice_assistant.start:
        wake_word: !lambda return wake_word;
  vad:
  models:
    - model: ${micro_wake_word_model}

Edit: I now understand that micro_wake_word differs from open_wake_word.

More details here:

https://github.com/kahrendt/microWakeWord/issues/28#issuecomment-2236199393

Original Post:
I have successfully changed my on-device micro_wake_word to Hey Jarvis.

Has anyone managed to use a custom wake word?

I followed the directions at:

https://www.home-assistant.io/voice_control/create_wake_word/

I have a hey_eddie.tflite and hey_eddie.jason model in my Github repo.

I have successfully uploaded it to Home Assistant as a custom wake word, but using a custom on-device wake word eludes me. Has anyone managed it?

Thanks for your Code.
Now I get this error and don’t know where to get that file:
Could not find file ‘/config/esphome/wakeword_detected3.raw’. Please make sure it exists (full path: /config/esphome/wakeword_detected3.raw).

wakeword_detected3.raw

I share the file @Merc send to me.

Why is on_wake_word_detected repeated?

I am not 100% sure (I copied this code from other threads and adjusted it to make it work) but I think one is for normal voice assistant and the other if you run micro wake word on device.
I cannot tell you if this is double dipping or whether it would work the same way if one is removed.
But it definitely works like that.