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.

I managed to get a custom wake word working. I used this training notebook as a guide. It took a bit of finessing to get it to work, but it resulted in a .tflite file that was significantly smaller, and it fixed the “Failed to allocate tensors for the streaming model” issue.

In the new esphome update, they have expanded media player support and simplified media interaction.
Looks like we need to rebuild the configuration files.


A new version of the code has already been released, but it lacks the wake-up sound. Added broadcast functionality that was introduced earlier this month.

I’m trying this out here, and am getting a message that reads:

INFO ESPHome 2025.2.1
INFO Reading configuration /config/esphome/m5-atom-echo.yaml...
INFO Updating https://github.com/esphome/esphome.git@pull/5230/head
INFO Updating https://github.com/kahrendt/esphome@mww-v2-external-library
Failed config

external_components: [source /config/esphome/m5-atom-echo.yaml:273]
  - source: github://pr#5230
    components: 
      - esp_adf
    refresh: 0s
  - 
    couldn't find remote ref mww-v2-external-library.
    source: 
      type: git
      url: https://github.com/kahrendt/esphome
      ref: mww-v2-external-library
    refresh: 0s
    components: 
      - micro_wake_word

This refers to line 293, the source: github://pr#5230. Any assistance is appreciated.

external_components:
  - source: github://pr#5230
    components:
      - esp_adf
    refresh: 0s
  - source:
      type: git
      url: https://github.com/kahrendt/esphome
      ref:  mww-v2-external-library
    refresh: 0s
    components: [ micro_wake_word ]

What does your config look like. I have no more than:

substitutions:
  name: m5stack-atom-echo-80bfd4
  friendly_name: M5Stack Atom Echo 80bfd4
packages:
  m5stack.atom-echo-voice-assistant: github://esphome/firmware/wake-word-voice-assistant/m5stack-atom-echo.yaml
api:
  encryption:
    key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

And it installs. (I was going to say cleanly… but there are a bunch of warnings, so I won’t!)