ESPHomeESP32Home Assistant

ESP32-S3 voice satellite

An ESP32-S3 with an INMP441 microphone and a MAX98357A speaker runs a local wake word in ESPHome, streams speech to Home Assistant Assist, plays the reply, and can keep listening for a follow-up.

Difficulty · IntermediateSetup · 60–120 minutesHA · current stable (Assist pipeline)ESPHome · current stable
Get the pack · from $19Have us set it up

Some hardware links on this page are affiliate links. As an Amazon Associate I earn from qualifying purchases. We may earn a commission if you buy through them, at no extra cost to you. See the affiliate disclosure.

A phone is a poor intercom. This pack turns an ESP32-S3, a digital microphone, and a small amplifier into a Home Assistant Assist satellite: the wake word runs on the board, audio streams to the Assist pipeline you already configured, and the spoken reply comes back out the speaker. An optional continued-listening switch opens another listen window after the reply, so a follow-up does not need the wake word again.

GPIO numbers and the friendly name are substitutions. Wi-Fi credentials stay in secrets.yaml. Do not publish a live device name.

Pairs with voice “find my phone” once a satellite exists, and with the ESPHome AirPlay receiver if you want music on a different board. This pack is speech in and speech out, not a music streamer.

What problem it solves

You want a wake word and a spoken reply in a room without a commercial satellite, and you want the wake word decided on the ESP32-S3 instead of streaming audio all day.

How it works

  1. Two I2S buses — the INMP441 and the MAX98357A do not share bit clock and word select. One bus is the microphone, the other is the speaker. Sharing one bus is the usual reason a build hears itself or records silence.
  2. Local wake word — micro_wake_word listens to the microphone with an on-device model (okay_nabu in the pack default). A voice-activity model sits in front so a thud is less likely to count. Detection starts the Assist session and passes the phrase that fired.
  3. Assist session — voice_assistant streams the microphone to Home Assistant and plays the reply on the I2S speaker. Speech-to-text, the conversation step, and the reply voice are configured in the Assist pipeline, not in this YAML.
  4. Continued listening — when the reply finishes, if the continued-listening switch is on, the node starts another listen for a few seconds. Silence or a stop phrase such as “never mind” ends the session. The switch is how you turn follow-ups off in a noisy room.
  5. Wake word comes back — stop_after_detection defaults to true, so the pack restarts micro_wake_word when the session returns to idle. Without that, the second wake word never hears.
  6. A status LED is optional — one GPIO follows listening versus idle. The pack does not require a ring of pixels.

This is one satellite per board. Echo cancellation is out of scope: put the microphone off-axis from the speaker, or the reply will wake the next listen.

Hardware notes

  • Board. ESP32-S3 with PSRAM (N8R8 or N16R8). The pack YAML uses esp32-s3-devkitc-1 and ESP-IDF. A board without PSRAM will not hold the wake-word model.
  • Microphone. INMP441: VDD to 3.3 V (not 5 V), GND, BCLK, WS, and SD. Tie L/R to GND so the mic is the left channel, and set channel: left. Use a short lead. A 3.3 V pin next to the antenna is a common source of hiss; power the mic from a quieter 3.3 V pin.
  • Amplifier. MAX98357A VIN to 5 V, GND, BCLK, LRC, and DIN. The shutdown pin floats enabled on most breakouts and mixes left and right into mono, which is what you want. Do not feed the speaker pins into another amp.
  • Speaker. 4 Ω to 8 Ω, a few watts. The outputs are bridge-tied: both speaker wires go to the amp, neither goes to ground.
  • Pins. Substitutions. Avoid the USB pins and the strapping pins on your module. The pack’s example uses one trio for the mic and another for the amp; change them to match the silkscreen.

Snippet

Trimmed from the pack YAML. Pins are substitutions. The follow-up switch is a global in the pack. Credentials stay in secrets.yaml.

substitutions:
  friendly_name: Room satellite
  mic_bclk: GPIO4
  mic_lrclk: GPIO5
  mic_din: GPIO6
  spk_bclk: GPIO15
  spk_lrclk: GPIO16
  spk_dout: GPIO17

esp32:
  board: esp32-s3-devkitc-1
  framework:
    type: esp-idf

psram:

i2s_audio:
  - id: i2s_mic_bus
    i2s_bclk_pin: ${mic_bclk}
    i2s_lrclk_pin: ${mic_lrclk}
  - id: i2s_spk_bus
    i2s_bclk_pin: ${spk_bclk}
    i2s_lrclk_pin: ${spk_lrclk}

microphone:
  - platform: i2s_audio
    id: mic_raw
    i2s_audio_id: i2s_mic_bus
    i2s_din_pin: ${mic_din}
    adc_type: external
    pdm: false
    channel: left
    sample_rate: 16000

speaker:
  - platform: i2s_audio
    id: amp_speaker
    i2s_audio_id: i2s_spk_bus
    i2s_dout_pin: ${spk_dout}
    dac_type: external
    channel: mono

micro_wake_word:
  id: room_wake
  microphone:
    microphone: mic_raw
  models:
    - model: okay_nabu
  vad:
  on_wake_word_detected:
    - voice_assistant.start:
        wake_word: !lambda return wake_word;

voice_assistant:
  microphone: mic_raw
  speaker: amp_speaker
  micro_wake_word: room_wake
  on_end:
    - if:
        condition:
          lambda: 'return id(follow_up);'
        then:
          - voice_assistant.start:
    - micro_wake_word.start:

follow_up is a global the pack sets from a dashboard switch. The status LED is in the full pack YAML. A follow-up ends on Assist’s own end-of-speech timeout, and “never mind” is Home Assistant’s built-in cancel phrase; neither needs extra YAML.

Setup outline

  1. In Home Assistant, open Settings → Voice assistants and confirm a pipeline has speech-to-text and a reply voice. A satellite with nowhere to send audio will look “wired” and do nothing useful.
  2. Flash a speaker-only config and play a short file from Home Assistant. Fix pins and power before you add the microphone.
  3. Add the microphone bus, confirm a recording in the Assist audio debug, then add micro_wake_word and voice_assistant.
  4. Adopt the node. Say the wake word, ask for the time, and confirm the reply comes from the speaker rather than a phone.
  5. Turn continued listening on, ask a follow-up without the wake word, then say “never mind” and confirm the session ends.

Troubleshooting

  • Wake word never fires. PSRAM is off, the model name is wrong, or the mic channel does not match L/R. Record a clip first; if the clip is silent, the wake word cannot hear either.
  • Hiss or a constant false wake. The mic is on a noisy 3.3 V pin or shares a ground loop with the amp. Shorten the mic leads and power it away from the antenna. Raise the model probability cutoff before you chase YAML.
  • Reply plays, then the board is deaf. micro_wake_word stopped after the first detection and was not started again on idle. The pack restarts it in on_end.
  • The reply wakes another listen. The microphone faces the speaker and there is no echo cancellation in this pack. Aim the mic away, lower the amp gain, or turn continued listening off.
  • Assist connects, no reply audio. The pipeline has no text-to-speech, or the speaker bus pins are the microphone pins. The speaker-only test in step 2 is the check.
  • Brown-outs when the reply starts. The amp is on a data-only USB port. Feed 5 V that can supply the speaker.

Credits

Continued listening after a reply follows the pattern in mitrokun/esp32s3-voice_assistant. Wake word and the Assist session use ESPHome’s micro wake word and voice assistant components. This write-up is an original overview; the pack YAML is a generic two-bus satellite, not a copy of that firmware.

Changelog

v1.0 · Oct 22, 2026: First publish.