microWakeWork stop working when adding bluetooth proxy line

Below code was working fine until I added the bluetooth_proxy and esp32_ble_tracker lines.

BTW, below code was generated by Claude AI by adapting official ESPHome code for the M5Stack Atom Echo to allow to send output sound to another external player.

Any idea why adding these two lines breaks everything?
(And by that I really mean that code compiles, is flashed correctly and then breaks MWW because of those two lines)

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:
  encryption:
    key: ${api_key}

ota:
  - platform: esphome
    id: ota_esphome
    password: ${ota_password}

wifi:
  ssid: !secret wifi_IoT_ssid
  password: !secret wifi_IoT_password
  min_auth_mode: WPA2

esp32_ble_tracker:
  scan_parameters:
    active: true

bluetooth_proxy:
  active: true

# True when the current TTS response must be routed to the external speaker.
globals:
  - id: tts_to_external
    type: bool
    restore_value: false
    initial_value: "false"

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
    buffer_duration: 60ms

media_player:
  - platform: speaker
    name: None
    id: echo_media_player
    announcement_pipeline:
      speaker: echo_speaker
      format: WAV
    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:
      # Original behaviour: stop the wake word if the microphone is capturing
      - 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
      # -----------------------------------------------------------------------
      # EXTERNAL SPEAKER ROUTING
      #
      # on_announcement fires exactly when echo_media_player enters the
      # ANNOUNCING state โ€” before the I2S DMA has had time to emit any
      # significant audio (a few ms depending on buffer size).
      #
      # We immediately stop the internal announcement and set media_player to
      # null so the voice_assistant stops feeding the pipeline.
      # HA sees the ANNOUNCING โ†’ IDLE transition and releases the
      # assist_satellite from the "responding" state.
      # The TTS is played via tts.speak on the external speaker (launched in
      # on_tts_start).
      #
      # on_tts_end restores the media_player pointer so the voice_assistant
      # can finish its pipeline normally (on_end + wake word restart).
      # -----------------------------------------------------------------------
      - if:
          condition:
            lambda: return id(tts_to_external);
          then:
            # Detach first to block further TTS data, then stop the pipeline
            - lambda: |-
                id(tts_to_external) = false;
                id(va)->set_media_player(nullptr);
            - media_player.stop:
                id: echo_media_player
                announcement: true
    on_idle:
      - script.execute: start_wake_word
      - script.execute: reset_led

voice_assistant:
  id: va
  microphone:
    microphone: echo_microphone
    channels: 0
    gain_factor: 4
  micro_wake_word:
  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
    - if:
        condition:
          lambda: return !id(external_speaker_entity).state.empty();
        then:
          - lambda: id(tts_to_external) = true;
          - homeassistant.service:
              service: tts.speak
              data_template:
                entity_id: "{{ engine }}"
                message: "{{ message }}"
                media_player_entity_id: "{{ player }}"
              variables:
                engine: !lambda return id(tts_engine_entity).state;
                message: !lambda return x;
                player: !lambda return id(external_speaker_entity).state;
        else:
          - lambda: id(tts_to_external) = false;
  on_tts_end:
    # Restore the media_player before C++ processes TTS_END, so that
    # signal_end_() is called normally and on_end fires as expected.
    - lambda: id(va)->set_media_player(id(echo_media_player));
  on_end:
    # Original logic unchanged.
    - wait_until:
        condition:
          - media_player.is_announcing:
        timeout: 0.5s
    - if:
        condition:
          - lambda: |-
              return id(wake_word_engine_location).current_option() == "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
    - lambda: |-
        id(tts_to_external) = false;
        id(va)->set_media_player(id(echo_media_player));
  on_client_connected:
    - delay: 2s
    - script.execute: start_wake_word
  on_client_disconnected:
    - script.execute: stop_wake_word
    - lambda: |-
        id(tts_to_external) = false;
        id(va)->set_media_player(id(echo_media_player));
  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:
  - 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%

text:
  # HA entity ID of the media_player to use for voice assistant responses.
  # Example: media_player.lenovo_smart_clock
  # Leave empty to use the M5Stack Atom Echo's internal speaker.
  - platform: template
    name: "External Speaker Entity ID"
    id: external_speaker_entity
    optimistic: true
    restore_value: true
    initial_value: ""
    mode: text
    entity_category: config
    icon: mdi:speaker-wireless

  # Entity ID of the TTS engine used for the external speaker.
  # Common examples: tts.home_assistant (Piper), tts.cloud (Nabu Casa)
  # To find yours: HA โ†’ Settings โ†’ Devices & services โ†’ Entities,
  # filter by domain "tts".
  - platform: template
    name: "TTS Engine Entity ID"
    id: tts_engine_entity
    optimistic: true
    restore_value: true
    initial_value: "tts.home_assistant_cloud"
    mode: text
    entity_category: config
    icon: mdi:text-to-speech

script:
  - id: reset_led
    then:
      - if:
          condition:
            - lambda: |-
                return id(wake_word_engine_location).current_option() == "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).current_option() == "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).current_option() == "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).current_option() == "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).current_option() == "In Home Assistant";
          then:
            - lambda: id(va).set_use_wake_word(false);
            - voice_assistant.stop:
      - if:
          condition:
            lambda: |-
              return id(wake_word_engine_location).current_option() == "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:
      - 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);
      - media_player.stop:
          announcement: true
    on_turn_on:
      - 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

Probably run out of RAM.

Here are some logs:

[23:30:24.725][D][micro_wake_word:365]: Starting wake word detection
[23:30:24.730][D][micro_wake_word:383]: State changed from STOPPED to STARTING
[23:30:24.753][D][micro_wake_word:267]: Inference task has started, attempting to allocate memory for buffers
[23:30:24.755][D][micro_wake_word:272]: Inference task is running
[23:30:24.755][D][micro_wake_word:383]: State changed from STARTING to DETECTING_WAKE_WORD
[23:30:24.760][D][ring_buffer:035][mww]: Created ring buffer with size 3840
[23:30:24.809][E][micro_wake_word:257]: Encountered an error while performing an inference
[23:30:24.809][D][micro_wake_word:279]: Inference task is stopping, deallocating buffers
[23:30:24.809][D][micro_wake_word:284]: Inference task is finished, freeing task resources
[23:30:24.827][D][micro_wake_word:383]: State changed from DETECTING_WAKE_WORD to STOPPED
[23:30:24.827][E][micro_wake_word:062][mww]: Could not allocate the streaming model's tensor arena.
[23:30:54.190][I][safe_mode:142]: Boot seems successful; resetting boot loop counter
[23:30:59.208][D][preferences:148]: Writing 1 items: 0 cached, 1 written, 0 failed

Does that confirm the out of RAM theory ?
If so, is there any device with an enclosure and more memory that is as small (I need to hide it) and has ready to use esphome yaml code (even that Claude AI journey was a hassle) ?

Well the error happened right after the ring buffer was created, so very probably.

Ble uses a lot of resources, you will need a device with PSRAM if you want this to work, the atom is right on the limit just running microwakeword.

Bummer.

@Arh Do you happen to know any similar device to the Atom Echo, so that is small with an enclosure and a microphone, has PSRAM in addition to more memory in general, and without also forgetting that has a ready-to-use esphome yaml file ?

Thanks

Not off the top of my head all my devices are homemade.

But you could spend some time looking through here. One of these devices will almost certainly be what you need.

Thanks for the link.
The same guy also has this repo: GitHub - RealDeco/VoiceSensor: VoiceSensor, VoiceEar and VoiceScreen ยท GitHub
So I might just like you build my own homemade devices using the "VoiceEar" from that repo as I don't need a screen nor speaker.
Next step would then be to find where to 3D print in my city and learn how to solder...

If you want to go home made, I use an esp32 S3 N16R8, INMP441 and max98357, with success.

see here