Setting up bluetooth proxy with M5 Atom Echo voice assistant

I’m just getting started with HA Green. I have already set up an M5 Atom Echo as a voice assistant, and would like to try to also use it as a bluetooth proxy, as described in this post:
https://community.home-assistant.io/t/m5-atom-echo-voice-assistant-with-bluetooth-proxy/617596

Based on the comment marked as the solution for that post, it sounds like this could be possible, but I’m having trouble pinning down exactly how/where to modify the YAML or where to go from there.

I took a stab at this by adding the configuration lines that worked for OP (mentioned here) to my /homeassistant/configuration.yaml.

After saving those changes, nothing seems to be breaking, so maybe that’s good? But uh, yeah, I don’t know wth I’m doing. Like is that even the right place to make those changes – or if it is the right place, what do I do next so I can use it…

Any advice would be appreciated.

1 Like

The provided configuration doesn’t go into the HA configuration.yaml…it goes into the configuration YAML file for the ESP device inside the ESPhome add-on (or local ESPhome if you have it setup somewhere else).

If you used the blog guide, it looks like that is using the ESPhome web interface to flash a pre-compiled firmware. To do what you want, you’ll need to go the “advanced users” route. At a high level:

  1. Install the ESPhome add-on (this is different than the integration)
  2. Click “+ new device”
  3. Hit continue
  4. Give it a name
  5. Select “ESP32” from the list
  6. Click “Skip”
  7. Click “Edit” in the box for the new device
  8. Copy/paste the code “for advanced users” that’s in Github: https://raw.githubusercontent.com/esphome/firmware/main/voice-assistant/m5stack-atom-echo.yaml
  9. Scroll to the bottom and add these two lines:
bluetooth_proxy:
  active: true
  1. Click on “Save”
  2. Click on “Install” and select the “Manual Download” option. Wait for the firmware to compile.
  3. When prompted, select the “modern format”. This should download a *.bin file. Chrome keeps blocking these for me so you might have to check downloads and click the “keep” option.
  4. Plug the Atom into the computer.
  5. Go to https://web.esphome.io/
  6. Click Connect and select the port like you did when you initially set it up.
  7. Click on “Install” and select the file you just downloaded.
  8. Wait for it to finish.
2 Likes

i tried doing this just now and followed all the steps successfully, but what do i need to do after that?

I didn’t see any new devices discovered nor what i would expect to add a BT proxy.

i do see a device to adopt in ESP home though? but trying to do so fails

There’s nothing to add for the BT proxy feature. If it picks up a supported device, you’ll get a prompt to add it.

If a device is seen but failing to add, I’d presume the signal is weak.

how can i check if the device is online? i had previously configured this device using the normal config, so would using the steps above have reset that? do i need to delete and re-add it?

Just check the ESPhome integration. It may (probably) have picked it back up as the same device. Are any of the entities showing available? Deleting and re-adding it will not hurt anything.

I know this post is old but wanted to share an update on this for whoever comes across it like me. I found the instructions by @FriedCheese to be really helpful but there is now a bug that has been introduced to the firmware file for the Atom Echo. When trying to download a project it would always fail. There is a bug ticket on Github that can be found here https://github.com/esphome/firmware/issues/286. I found that to fix this issue I had to add this to the esp32 framework part:

esp32:
  framework:
    type: esp-idf
    version: 4.4.8
    platform_version: 5.4.0
4 Likes

Hi,

I am begginer in esphome too. I too am trying to add the bluetooth proxy feature to my atom echo satellite. My configuration is the following:

substitutions:
  name: m5stack-atom-echo-0f1130
  friendly_name: AtomEchoLit
  micro_wake_word_model: okay_nabu  # alexa, hey_jarvis, hey_mycroft are also supported

esphome:
  min_version: 2025.2.0
  name: ${name}
  name_add_mac_suffix: false
  friendly_name: ${friendly_name}

esp32:
  board: m5stack-atom
  framework:
    type: esp-idf
    version: 4.4.8
    platform_version: 5.4.0

logger:

ota:
  - platform: esphome
    id: ota_esphome

wifi:
  ap:
  ssid: !secret wifi_ssid
  password: !secret wifi_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

speaker:
  - platform: i2s_audio
    id: echo_speaker
    i2s_dout_pin: GPIO22
    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: 100%
          red: 0%
          green: 0%
          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: 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
  - 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}

bluetooth_proxy:
  active: true

Without bluetooth_proxy: active: true, it compiles without issue. But with it, the total size of the program is greater than the maximum available storage on the device.

Is there features in my configuration that I can remove to save some space without compromising too much on features ?

Thanks in advance for any answer, have a nice day

1 Like

Same problem here, have you find something?

1 Like

The Atom Echo will not work as a voice assistant and bluetooth proxy at the same time. The problem isn’t flash space (you can play some tricks to get it to compile and install). It turns out the problem is RAM, it doesn’t have enough to do both. There is actually a warning about this in the docs, but its easy to miss because it is on the BLE tracker hub page and not the bluetooth proxy page.

1 Like

There seems to be a newer model now, the M5Stack Atom Echo S3R. Is it possible that this newer model might be able to support voice assistant and bluetooth proxy at the same time?