M5Stack ATOM Echo voice assistant

Hi,

I have an M5Stack ATOM Echo with firmware for a voice assistant installed via ESPHome (Nabu). Everything works great. However, the built-in speaker is very weak, and I planned to connect the Echo to a better speaker using I2S through an amplifier.

I have the amplifier: MAX98357 I2S mono amplifier 3W

However, I haven’t been able to get it working. I eventually found that I2S only works in Arduino mode, but in Arduino mode, the voice assistant doesn’t work. I’m stuck in a dead end and overwhelmed with information and failed attempts.

Has anyone tried this, or does anyone have a tip on how to connect the Echo to a more powerful speaker?

Thanks a lot!

The MAX98357 works with esp-idf which the echo uses. I have 5 of these connected to esp32’s running esp-idf. You will have to tell us more about how you tried it, connections, and yaml etc. Without that no one will be able to help.

I actually tried many ways because it still didn’t work for me and I had several sources where I got inspiration. I’m new to YAML and HA, so I’m discovering everything gradually and learning as I go.
If possible, it would be most helpful if you could tell me how you did it, if it works for you.
Voice control works great for me - I just need to change the internal speaker to an external one with higher power. Thanks!

To be honest if it was me I would build a new voice setup using and esp32 s3 n16r8, inmp441 and Max 98357. It will in the end be easier than modifying what you have now.

Here is the code I use it has all the connections listed at the top. It may or may not be of use. I have never played with an M5stack echo so have no idea how you would modify one.

#  MIC

#  LRC   GPIO5   WS    
#  BLC   GPIO6   SCK   
#  Din   GPIO4   SD    
#  L/R   GND  

#  Speaker

#  LRC   GPI05
#  BLC   GPIO6
#  Dout  GPIO7    

# LED
  
# GPIO14   

#  5V

#  Speaker
#  led

#  3V

#  Mic

#  GND

#  Mic
#  Speaker

substitutions:
  name: office-assist
  micro_wake_word_model: okay_nabu  # alexa, hey_jarvis, hey_mycroft 

ota:
  - platform: esphome
    password: "xxxxxxxxxxxxxxxxxxxxxxxxxx"

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

api:
  encryption:
    key: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

esphome:
  name: ${name}
  friendly_name: ${name}
  platformio_options:
    board_build.flash_mode: dio
  on_boot:
    - light.turn_on:
        id: led
        blue: 100%
        brightness: 11%
        effect: fast pulse
esp32:
  board: esp32-s3-devkitc-1
  variant: esp32s3
  framework:
    type: esp-idf
    version: 4.4.8
    platform_version: 5.4.0

    sdkconfig_options:
        CONFIG_ESP32_S3_BOX_BOARD: "y"
        CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM: "16"
        CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM: "512"
        CONFIG_TCPIP_RECVMBOX_SIZE: "512"
        CONFIG_TCP_SND_BUF_DEFAULT: "65535"
        CONFIG_TCP_WND_DEFAULT: "512000"
        CONFIG_TCP_RECVMBOX_SIZE: "512"

psram:
  mode: octal
  speed: 80MHz

logger:
 # hardware_uart: UART0

#########################################################
## copy from here down
#########################################################

button:
  - platform: restart
    name: Restart
    id: but_rest

  - platform: factory_reset
    id: factory_reset_btn
    name: Factory reset

#i2s_audio:
#  - id: i2s_in
 #   i2s_lrclk_pin: GPIO5
 #   i2s_bclk_pin: GPIO6
 # - id: i2s_out
#    i2s_lrclk_pin: GPIO11
 #   i2s_bclk_pin: GPIO9

i2s_audio:
  - id: i2s_audio_bus
    i2s_lrclk_pin: GPIO5
    i2s_bclk_pin: GPIO6

microphone:
  - platform: i2s_audio
    id: echo_microphone
    i2s_din_pin: GPIO4
    adc_type: external
    channel: left
    sample_rate: 16000
    bits_per_sample: 32bit
    pdm: false

speaker:
  - platform: i2s_audio
    id: echo_speaker
    i2s_dout_pin: GPIO7
    dac_type: external
    bits_per_sample: 32bit
    channel: right
    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
    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:
            - if:
                condition:
                  lambda: return id(wake_word_engine_location).state == "On device";
                then:
                  - micro_wake_word.stop:
                else:
                  - voice_assistant.stop:
            - script.execute: reset_led
      - light.turn_on:
          id: led
          blue: 0%
          red: 0%
          green: 100%
          brightness: 100%
          effect: none
    on_idle:
      - script.execute: start_wake_word

voice_assistant:
  id: va
  microphone: echo_microphone
  media_player: echo_media_player
  noise_suppression_level: 2
  auto_gain: 31dBFS
  volume_multiplier: 2.0
  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
    - script.execute: start_wake_word
  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:
    - voice_assistant.stop:
    - micro_wake_word.stop:
  on_timer_finished:
    - voice_assistant.stop:
    - micro_wake_word.stop:
    - 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: LED
    disabled_by_default: true
    entity_category: config
    pin: GPIO14
    default_transition_length: 0s
    chipset: ws2812
    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: 0%
                green: 0%
                blue: 100%
                brightness: 11%
                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:
      - wait_until:
          and:
            - media_player.is_idle:
            - speaker.is_stopped:
      - 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:

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: ${micro_wake_word_model}
  - model: okay_nabu
  - model: hey_mycroft
1 Like

Yes, that’s true. I’ve thought about building it from scratch, but I haven’t gathered enough information. Thanks a lot, I’ll look into it!

Do you think the ESP32-S3 N8R8 will be enough? The version with larger memory is currently not available in the Czech Republic.

Thanks for providing your configuration for reference. It helped a lot.

I was trying to get my mixer speaker to work according to the example, to no avail.

I have in the past had a similar setup running on an ESP32-S3 N8R8, but have not tried the latest updates on one, Esphome voice speaker component was updated with the last version. Can you not buy from Aliexpress?

Yes I can, I just didn’t want to wait for delivery from Ali, from the CZ it would be in 2 days. But I’d rather wait for delivery and order from Ali :slight_smile: