šŸ”” ESPHome Full-Duplex Audio Intercom - Because I Was Bored on Vacation

Hi Jakub, thanks for the feedback!

You’re right. The filter_length should only need to cover the actual acoustic echo tail (speaker to mic physical path), not compensate for software buffering
alignment.

The ring buffer reference mode uses a fixed pre-fill delay (currently hardcoded at 80ms) to align speaker samples with mic data. If that delay doesn’t match your
hardware’s actual DMA latency, the AEC filter has to search a wider window, requiring higher filter_length (and more CPU).

We’ve just added a configurable delay on dev: aec_reference_delay_ms so you can tune the pre-fill for your hardware. If you find the right value for your setup,
filter_length: 4 should work.

intercom_api:
id: intercom
mode: full
microphone: mic_component
speaker: spk_component
dc_offset_removal: true
aec_id: aec_processor
aec_reference_delay_ms: 80 # Tune for your hardware (10-200ms). Try lower values with filter_length: 4.
ringing_timeout: 30s

The default is 80ms which works for most setups with separate I2S buses. Try lowering it (40-60ms) and see if filter_length: 4 gives you good cancellation. The right
value depends on your I2S DMA buffer configuration and codec latency.

We’re also exploring a single-bus duplex approach (i2s_audio_duplex) where mic and speaker share the same I2S bus (same BCLK/LRCLK, separate DIN/DOUT), giving
sample-aligned reference without ring buffer delay. Currently tested with codec hardware (ES8311/ES7210), working on a no-codec variant.

Can you share your hardware setup? (which mic, speaker, codec if any, single or dual I2S bus?) That would help us suggest the best configuration.

Thank you for fast response.
Actually i’m on dev branch already. I did some experiments with aec_reference_delay_ms maybe i missed sweet spot i will do some more or maybe try to measure delay somehow.
I’m mostly interested in i2s_audio_duplex and aec components, my goal is to improve voice recognition for custom esphome speaker with voice assistant while music is playing. I’m using inmp441 and max98357 on single i2s. To validate improvement i’m streaming pre aec and post aec microphones data to PC. As long as mic is not extremly overdriven your code does really good job in canceling speaker noise. I will get back to filter_lenght: 4 and experiment further. Thanks for clearing my wrong understanding of speaker ref delay and filter lenght corelation.

Is there a way to use this with just an I2S mic (ICS-43434) and stream the audio over TCP? Maybe even the raw data or a rtsp stream? ATM I push the raw mic data over UDP to a server running go2rtc to make a rtsp stream, but if this could replace part or all of that boilerplate that would be awesome!

udp:
  - id: nuc_udp
    addresses: ["192.168.178.12"]
    port: 4800

microphone:
  - platform: i2s_audio
    id: board_mic
    i2s_audio_id: i2s_in
    i2s_din_pin: GPIO2
    adc_type: external
    pdm: false
    sample_rate: 48000
    bits_per_sample: 16bit
    channel: left
    on_data:
      - udp.write:
        id: nuc_udp
        data: !lambda 'return x;'

Hi, take a look at my repo, you’ll find a UDP component. It was my first experiments on this, it worked exactly as you describe: with go2rtc you could capture the stream, and it was also possible to make the ESPs talk to each other over UDP without Home Assistant. I haven’t updated it in a while, but it should still work. Let me know if you run into any issues.
For TCP I haven’t implemented a server-less mode yet, but I don’t see why not to add it in the future. It’s not the first time I’ve been asked.

I’m finally starting to see the light at the end of the tunnel.

I’ve trimmed, optimized, reorganized and streamlined the code, and managed to make room for AFE.

On dual-mic devices, BSS is working well. For the first time I’m getting a glimpse of voice assistant usage on par with the most popular commercial solutions: my Waveshare S3 has started picking up my voice even from other rooms.

Under the hood

After a thorough analysis of how the firmware runs, observing tasks and resources with the SDK debug tools and via JTAG, the picture is fairly clear:

On the S3 boards everything runs well, but there’s no headroom left for additional features. RAM, even organized as best as possible, is saturated.
On the P4 the story changes: there’s plenty of space.
What’s left before the main release

I need to keep testing on more devices. In particular, I have to rebuild a generic YAML to test boards without a hardware codec, and it won’t be trivial. Adding even just one more buffer, at this point, is simply out of the question.

The test cycle has become long: ESP32 with intercom only, then S3 with intercom + AEC, then S3 full experience, then P4. Lots of combinations, but it’s the price of a project that has grown a lot.

That said, in my daily use the system is proving to be unexpectedly stable and resilient. And that’s what makes me happiest of all.

I literally came across your project 10 minutes ago before seeing it here based on a search for a touch controller for a wave share board on Amazon and just wanted to say thanks. While I don’t know if I personally need an intercom system, your I2S Audio Duplex documentation is outstanding. Especially the very detailed explanations and the section on ā€œwhen to use it and when not to use itā€ part. Most projects would have something like on the main page and that’s it but you really go into detail about how the I2S Audio duplexing code/YAML works and I will be using that for some upcoming P4 projects so thanks!

I see. So it’s not possible to use your intercom_api strictly as an audio source for something like an audio only baby monitor? Guess I’ll have to stick with my boilerplate or your old_intercom_udp component.

Yeah, keep in mind I haven’t updated it for months (the UDP component), but you’re not the first person to tell me it could be useful. These days I have time to work on it, so maybe I can apply the same knowledge I gained from the main project and take that component back in hand. I’ll keep you updated if I make any changes in that direction.
Anyway, with the intercom_api component you can call the ESP and leave the call open through Home Assistant as long as you want. The same goes if you connect two ESPs. You can also mute one of the interlocutors’ microphones so that one device works as mic-only while the other plays only through the speaker.

:warning: Major development update: PBX-lite rewrite / 2026.5.0-dev

Hi everyone,

I am preparing a major development update for ESPHome Intercom.

This is not a small feature release. It is a semantic cleanup of the whole project, and I want to explain it clearly before it lands because it will affect existing
YAMLs.

The short version:

The next release will break existing YAMLs, but it is moving the project toward a much cleaner and more flexible architecture.

The project started as a way to make browser-to-ESP and ESP-to-Home-Assistant full-duplex intercom calls work. Over time, it grew into something much bigger: ESP-to-
ESP calls, touch UIs, Voice Assistant coexistence, AEC/AFE audio processing, TCP and UDP transport, routing, call states, and Home Assistant integration.

At that point the old model was no longer enough.

:telephone_receiver: From PBX-like to PBX-lite

The project is moving from ā€œPBX-like behaviorā€ to a real PBX-lite model.

Every ESP is now treated as an independent extension.

That means an ESP can:

  • originate a call
  • receive a call
  • ring
  • answer
  • decline
  • hang up
  • report why a call ended
  • keep its own call state
  • call another ESP directly when possible

Home Assistant is no longer treated as the hidden owner of every call. It becomes another member of the PBX system: it can call ESPs, receive calls from ESPs, and
optionally bridge calls between devices.

Do not be scared by the telephone terminology.

When I say PBX, extension, softphone, routing, or call state, I am not saying you must build a telephone system. You can still use this exactly like before: one ESP
at the door, one Home Assistant card, auto-answer if you want an intercom, manual answer if you want it to ring first.

The point of the new model is that the simple case becomes just one clean case of the bigger system, instead of being held together by special-case logic.

A one-device setup is simply a PBX-lite setup with one destination in the phonebook: Home Assistant.

:notebook: Unified phonebook

The old split between separate TCP and UDP phonebooks is being replaced by a single protocol-aware phonebook.

The new logical format is:

Name|tcp|ip|tcp_port
Name|udp|ip|udp_audio_port|udp_control_port
Name|ha|ip|tcp_port|udp_audio_port|udp_control_port

Home Assistant publishes one roster:

sensor.intercom_phonebook

The ESP firmware subscribes to that single roster and locally shapes it for its own transport.

This means:

  • TCP firmware keeps TCP peers direct.
  • UDP firmware keeps UDP peers direct.
  • TCP-to-UDP and UDP-to-TCP calls go through Home Assistant.
  • The real destination name is preserved even when HA is used as bridge.

So if a TCP ESP calls a UDP ESP, it does not pretend the destination is Home Assistant. The call still targets the real ESP, but the network endpoint points to HA
because HA must bridge between protocols.

:satellite: Direct ESP-to-ESP calls

Same-protocol devices can call each other directly.

Examples:

  • TCP ESP -> TCP ESP: direct call
  • UDP ESP -> UDP ESP: direct call
  • TCP ESP -> UDP ESP: bridged by Home Assistant
  • UDP ESP -> TCP ESP: bridged by Home Assistant

This is the important architectural change: the ESP devices are real call endpoints, not just Home Assistant-controlled speakers/microphones.

One way or another, the project will also include ESP-side auto-discovery.

The goal is that if multiple intercom ESPs are on the same network, they should be able to discover each other, merge those peers into the local phonebook, and call
each other even before Home Assistant gets involved.

There is already work in progress around a generic ESPHome mdns_browser component for this. I am treating it as infrastructure, not as an intercom-only shortcut:
it should be able to browse arbitrary mDNS service types and let YAML/components decide how to use those discovered services.

For the current dev baseline, the stable source of truth is still the Home Assistant-published phonebook. ESP-side discovery will be enabled in the standard YAMLs
only after it has been validated long enough to avoid boot or reconnect instability.

:house_with_garden: Home Assistant as PBX

Home Assistant can now act in two ways.

First, Home Assistant can be a destination.

If the selected ESP destination is the Home Assistant instance name, for example Home, Office, or whatever you configured in HA settings, the Lovelace card
behaves like a softphone. Pressing Call means Home Assistant calls the ESP, and the ESP sees an incoming call from HA.

Second, Home Assistant can be a bridge.

This is useful when:

  • two ESPs use different transports
  • you want the call path to go through HA
  • you want HA to keep visibility of a call
  • you explicitly enable HA-as-PBX routing

This mode is optional. Direct peer-to-peer is still the cleanest path when both devices can talk directly.

:iphone: Lovelace card behavior

The card now follows a simple rule.

If the selected ESP destination is another ESP, the card mirrors the ESP.

Pressing Call on the card presses Call on the ESP. The ESP originates the call. The card reflects the ESP state: outgoing, ringing, streaming, destination, caller,
and end reason.

If the selected ESP destination is Home Assistant, the card behaves like a softphone.

This makes the card less magical and more predictable. It mirrors the device when the ESP owns the call, and it becomes a HA endpoint only when HA itself is
selected.

There is also a show_protocol option so the card can show TCP/UDP context and distinguish normal ESP-to-ESP calls from inter-protocol calls.

:no_entry_sign: Call reasons and Do Not Disturb

Call end reasons now travel through the protocol.

For example, if a device has Do Not Disturb enabled and another ESP calls it, the callee can send:

DND

The caller receives that reason and can show it on screen. Home Assistant and the Lovelace card preserve the reason instead of replacing it with a generic message.

Free-form reason strings are also supported. The PBX layer should forward the real reason, not invent a new one.

This matters because the ESP remains authoritative when the card is mirroring an ESP-to-ESP call. The card should show what the ESP reports, not reinterpret the call
as if HA owned it.

:sound: Audio stack cleanup

The audio stack is being cleaned up into clearer layers:

  • i2s_audio_duplex: full-duplex I2S, speaker/mic paths, AEC reference capture, FIR decimation, runtime audio controls
  • esp_aec: lightweight echo cancellation path
  • esp_afe: full Espressif Audio Front-End path for advanced boards
  • audio_processor: shared interface used by both AEC and AFE processors

Generic full-experience YAMLs are moving toward the lighter AEC path by default. Full AFE remains for boards where it makes sense and has been validated.

AFE user controls are now aligned with board topology.

Single-mic boards expose:

  • Echo Cancellation
  • Noise Suppression
  • Auto Gain Control
  • Voice Activity Detector

Dual-mic boards expose:

  • Echo Cancellation
  • Speech Enhancement
  • Voice Activity Detector

Codec boards use hardware Master Volume through the codec path. Generic/no-codec boards can use software speaker volume. This avoids confusing media-player volume,
intercom volume, and codec DAC volume.

:microphone: Voice Assistant coexistence

The full-experience YAMLs are still meant to combine intercom, full-duplex audio, media playback, wake word, and Home Assistant Voice Assistant on the same device.

The target is not ā€œeither intercom or voice assistantā€.

The target is one ESP device that can be:

  • an intercom endpoint
  • a Voice Assistant satellite
  • a media playback endpoint
  • a PBX-lite extension

This is why the audio stack had to be cleaned up. Intercom audio, media playback, wake word, AEC/AFE processing, and VA all need to share the same hardware without
stepping on each other.

:gear: TCP and UDP are both first-class

TCP is still the preferred reliable signaling/audio transport for many setups.

UDP remains important for simple low-latency direct ESP use cases and for users who want lightweight ESP-to-ESP behavior.

The point of the new architecture is not to kill one transport. The point is to make both transports fit into the same call model.

That is why the phonebook now carries the protocol, and why Home Assistant can bridge only when needed.

:warning: Breaking changes

The next release will break existing YAMLs.

Main changes:

  • versioning moves to calver: 2026.5.0
  • firmware and HA integration must be updated together
  • the wire protocol changed
  • PBX-lite becomes the default product model
  • the old simple/full split is gone
  • raw_udp remains as an explicit opt-in for raw audio use cases
  • TCP and UDP variants are explicit YAML files
  • the old split TCP/UDP phonebook model is replaced by a unified phonebook
  • UDP control uses port 6055
  • UDP audio and TCP still default to 6054
  • old UDP-specific intercom code is gone; UDP now lives inside intercom_api
  • old Home Assistant phonebook push/service patterns are being replaced by the unified phonebook subscription model
  • some device/entity naming is being cleaned up, so Home Assistant entities and automations may need to be renamed

Please do not update blindly when this lands.

Read the dev branch documentation first.

:books: Documentation to read before upgrading

The most important docs are:

  • README.md
  • docs/INTERCOM_PROTOCOL.md
  • docs/PHONEBOOK_PROTOCOL.md
  • docs/DEPLOYMENT_GUIDE.md
  • component READMEs under esphome/components/

The goal is to make the project easier to reason about long-term.

This may be uncomfortable during migration, but it gives the project a real foundation: independent ESP extensions, Home Assistant as an optional PBX member,
protocol-aware routing, cleaner audio layers, and a phonebook model that can scale beyond the original doorbell use case.

:construction: Current status

This is still active development.

The stable baseline right now is:

  • Home Assistant publishes the unified phonebook.
  • ESPs subscribe to it.
  • ESP-to-ESP calls work when the phonebook gives them a compatible endpoint.
  • Home Assistant bridges when transport/protocol differences require it.
  • ESP-side mDNS discovery is being redesigned before it returns to the standard YAMLs.

So yes, the project is temporarily more disruptive, but the reason is to stop accumulating special cases and move toward a model that can scale.

The old system worked, but it was becoming difficult to explain and maintain.

The new system is stricter, but much easier to reason about.

:rocket: v2026.6.0 - Intercom Native polish and Espressif GMF audio stack

This release is the next major step after the 2026.5.0 PBX-lite migration.

2026.5.x introduced the new call model: ESP devices as independent extensions, Home Assistant as a peer/bridge, unified phonebook, TCP/UDP routing and browser
softphone support.

2026.6.0 keeps that model and rebuilds the audio foundation under it.

:house: Home Assistant / Intercom Native

The Home Assistant side has been cleaned up around the unified PBX-lite event model.

:repeat_button: Unified call event model

The integration and Lovelace card now use the unified intercom_native.call_event event shape for session, bridge and forward updates.

This gives automations and the card a more consistent view of:

  • call scope
  • event type
  • call state
  • hangup / decline / failure reason
  • bridge and forward lifecycle

The older split event behavior is no longer the preferred model.

:no_mobile_phones: Better unavailable-device handling

The card now handles unavailable ESP devices more explicitly instead of showing stale call controls as if the device were still reachable.

This should make dashboard state clearer when an ESP is offline, rebooting, being flashed, or temporarily disconnected from Home Assistant.

:high_voltage: Safer fast hangup / redial behavior

The browser softphone path has been hardened for fast user actions.

If a call is ended and another call starts immediately after, browser audio cleanup no longer tears down the new call's microphone/audio path by mistake.

This fixes a class of "second call has no browser audio" style problems.

:mobile_phone: Mobile notification answer flow

The documented mobile flow now supports real Answer / Decline actions:

  • Answer opens the dashboard view containing intercom-card with ?intercom_answer=1
  • the card requests microphone permission and starts the full-duplex browser/app audio path
  • Decline stays in Home Assistant automation logic and calls intercom_native.decline

This is the supported way to answer an ESP-originated call from the Home Assistant Companion app.

:broom: Versioned card cache behavior

The card is registered with a versioned frontend URL derived from the installed integration version.

After upgrading, hard-refresh the dashboard page or clear the Companion app cache if the card still shows an old version.

:white_check_mark: Minimum Versions

This release requires:

  • ESPHome: 2026.5.x or newer
  • Home Assistant Core: 2026.5.0 or newer

HACS metadata now declares the Home Assistant minimum version accordingly.

:warning: Breaking Changes

Custom YAMLs that still use the old audio component/package layout need to be migrated.

Main migration points:

  • maintained YAMLs now use esp_audio_stack
  • old i2s_audio_duplex packages are no longer the supported path
  • some YAML options were renamed:
    • speaker_volume -> master_volume
    • mic_attenuation -> input_gain
    • frame_buffers_in_psram -> buffers_in_psram
    • audio_stack_in_psram -> audio_task_stack_in_psram
  • Generic full profiles are split into AEC and AFE variants
  • full audio/LVGL profiles include OTA maintenance handling
  • old copied Lovelace card files should be replaced by the bundled card

After upgrading, clear ESPHome build caches once before compiling:

find . -type d -name .esphome -prune -exec rm -rf {} +

:headphone: Audio Stack Migration

The biggest internal change in 2026.6.0 is the migration from the old custom duplex audio path to the new esp_audio_stack backend.

This replaces the maintained i2s_audio_duplex path.

The goal is not just a component rename. The new backend is built around Espressif / ESP-IDF audio components that are designed to work together:

  • esp_driver_i2s for official I2S channel ownership
  • esp_codec_dev for codec-backed devices
  • gmf_io / io_codec_dev for codec IO
  • esp_audio_effects for rate, bit-depth and layout conversion
  • esp-sr for Acoustic Echo Cancellation
  • gmf_ai_audio / esp_gmf_afe_manager for the full Audio Front-End pipeline

This means the project now carries less custom audio infrastructure and relies more directly on the Espressif audio ecosystem.

:light_bulb: Why This Matters

Earlier versions had custom code for a lot of low-level audio work:

  • I2S lifecycle
  • speaker/microphone glue
  • AEC reference routing
  • rate conversion
  • bit-depth conversion
  • channel layout conversion
  • ring buffers
  • processor feed/fetch timing
  • codec-specific assumptions

That worked, but it created too much maintenance pressure and too many board-specific edge cases.

With esp_audio_stack, the project is closer to the native ESP-IDF audio model while still exposing normal ESPHome surfaces above it:

  • microphone
  • speaker
  • media player
  • mixer
  • Voice Assistant
  • Micro Wake Word
  • intercom API
  • Home Assistant entities

:puzzle_piece: Supported Audio Shapes

The maintained profiles now cover these layouts through the new stack:

  • single-bus codec boards
  • single-bus no-codec boards
  • dual-bus MEMS mic + I2S amplifier boards
  • ES8311 stereo playback-reference boards
  • ES7210 + ES8311 TDM reference boards
  • dual-mic AFE boards
  • lightweight AEC-only Generic S3 profiles
  • full AFE profiles for larger flash/RAM layouts

Codec-backed devices use esp_codec_dev.

No-codec devices use official esp_driver_i2s channels directly, avoiding unnecessary codec/GMF IO dependencies on smaller builds.

:studio_microphone: AEC and AFE Profiles

Profiles are now split more clearly.

:feather: esp_aec

Use this for lightweight echo cancellation.

It is the default direction for:

  • intercom-only devices
  • Generic S3 full-experience profiles that need to fit smaller flash layouts
  • users who want Acoustic Echo Cancellation without the full Audio Front-End cost

:brain: esp_afe

Use this for the full Espressif Audio Front-End path.

It adds:

  • Acoustic Echo Cancellation
  • Noise Suppression
  • Automatic Gain Control
  • Voice Activity Detection
  • Speech Enhancement / Blind Source Separation on supported dual-mic boards

It is heavier, but it is the right direction for boards with enough flash/RAM and for full voice-device profiles.

:package: Generic Profile Split

Generic S3 full-experience YAMLs are now split by intended target:

  • generic-s3-full-aec-*

    • lightweight path
    • intended for 4 MB-friendly builds
    • uses standalone esp_aec
    • uses the lighter previous_frame reference
  • generic-s3-full-afe-*

    • full Audio Front-End path
    • intended for larger flash layouts
    • uses esp_afe
    • uses TYPE2-style software reference

This avoids pretending one Generic YAML can fit every board and every flash layout.

:speaker_high_volume: Better AEC Reference Handling

Echo cancellation quality depends heavily on the playback reference.

The new stack handles reference routing per topology:

  • ES8311 boards can use stereo digital feedback
  • ES7210 TDM boards can use a hardware TDM reference slot
  • no-codec Generic AEC profiles can use previous_frame
  • Generic AFE profiles can use TYPE2-style software reference

This is one of the main reasons for the audio migration. AEC quality depends on reference timing, channel layout and conversion path, not only on enabling a library.

:brain: Runtime and Memory Improvements

The migration also cleaned up runtime behavior:

  • large buffers and task stacks are allocated earlier
  • repeated heap churn during call/media transitions has been reduced
  • microphone and speaker wrapper loops wake on real events instead of spinning
  • intercom_api parks its loop when idle
  • intercom TX uses lower-copy reads where possible
  • full profiles place selected buffers/stacks in PSRAM
  • full LVGL/audio profiles enter OTA maintenance mode before flashing

This helps demanding full-experience devices where media playback, Piper TTS, Micro Wake Word, Voice Assistant, AFE/AEC and intercom all coexist.

:compass: Maintained Board Direction

Current maintained baseline:

  • Waveshare ESP32-S3 Audio Board: full AFE, dual mic, TDM reference
  • Spotpear Ball v2: codec-backed AFE/intercom profiles
  • Generic S3 AEC: lightweight 4 MB-friendly full-experience profiles
  • Generic S3 AFE: larger flash full AFE profiles
  • Generic dual-bus: maintained intercom profiles
  • Waveshare P4 Touch: present and improving, still board-specific/experimental

:test_tube: Validation

Before this release, the public YAMLs were switched to remote release mode so users can download only the YAML and let ESPHome fetch packages, assets and external
components from main.

Validation performed:

  • HACS validation passes
  • hassfest validation passes
  • generic-s3-full-afe-tcp.yaml compiles successfully with ESPHome 2026.5.1
  • ESPHome fetches this repository from main
  • Espressif managed components resolve and build correctly

Generic full AFE firmware size from the validation build is about 2.1 MB.

:up_arrow: Upgrade Notes

Recommended upgrade path:

  1. Update the Home Assistant integration through HACS.
  2. Restart Home Assistant.
  3. Hard-refresh the dashboard page containing intercom-card.
  4. Clear ESPHome build cache once.
  5. Recompile from the updated YAMLs.
  6. Flash the ESP firmware.

If you maintain custom YAMLs, start from the closest maintained profile and reapply only your board-specific changes.

This is some amazing work. I have been struggling with half duplex i2s audio on the ESP32-P4. I think your audio stack will fix it.

Have you considered replacing media play with the new Sendspin?

:warning: Heads-up: major breaking changes are coming in the next version.

I know it can be frustrating to keep up with every big architectural change, and before you send me to hell: this one is not just another internal refactor.

The next release makes ESPHome Intercom compatible with SIP and VoIP. Yes, really. This is already working on the dev branch.

What does it mean in practice?

It means that, if you want, you can have a simple Home Assistant / ESPHome-centric PBX without having to carry Asterisk along with it.

  • Your ESP intercom can still stay simple: one button, one destination, call, answer, talk, hang up.
  • Under the hood, ESP devices are becoming real SIP/VoIP phones.
  • ESPs can still call each other directly when direct SIP routing is available.
  • ESPs can still call Home Assistant, and Home Assistant can still call ESPs.
  • Home Assistant / Intercom Native is now also a SIP softphone, router/B2BUA, RTP bridge/resampler, optional local registrar and optional trunk client.
  • You can register a real softphone such as Zoiper, Linphone, baresip or similar directly to Intercom Native.
  • Registered softphones can appear in the central phonebook, so ESPs and Home Assistant can call them by name.
  • Home Assistant itself can receive calls as a VoIP softphone and make its Lovelace card ring.
  • If you configure a SIP trunk, Home Assistant and ESP devices can make and receive external calls without requiring Asterisk beside the integration.
  • You can call landline/mobile numbers from Home Assistant or from an ESP through the trunk.
  • External inbound calls can ring Home Assistant by default.
  • I am also working on DTMF-based routing/IVR: for example, call your home number, press nothing to ring Home Assistant, or dial an optional internal extension such as 200 to ring a specific ESP.
  • Internal extension numbers are optional: contacts can still be resolved by name, SIP URI, direct IP/port, HA routing, softphone registration, or trunk routing depending on your setup.
  • Audio quality is negotiated per direction, so a device can receive the best compatible speaker format while transmitting whatever its microphone path can actually produce.

This is a breaking migration because the old project-specific call-control model is being replaced by SIP/SDP/RTP primitives. The goal is to make the project simpler conceptually and much more interoperable: ESPs, Home Assistant, browser card, softphones and trunks all speak the same VoIP model.

If you want to test early, use the dev branch. If your install is critical, wait for the release notes and migration guide before upgrading.

Current testing status: I am actively testing with Spotpear, Waveshare S3, Home Assistant, Zoiper and baresip. I have not completed testing across every YAML/profile yet, but if you want to start experimenting now, these are the devices and clients I can already say are working!

:telephone: Project Update: ESPHome Intercom Has Evolved Into A Complete VoIP Stack

Hi everyone! :waving_hand:

A quick update for anyone following this older ESPHome Intercom thread.

The project has evolved considerably and is no longer based on the original custom intercom transport.

It is now a complete local SIP/SDP/RTP VoIP stack for Home Assistant and ESPHome.

Home Assistant can now act as:

  • a real SIP softphone;
  • a local SIP registrar;
  • a SIP call router and B2BUA;
  • an RTP bridge and audio resampler;
  • a central phonebook and dial plan;
  • an optional SIP trunk endpoint;
  • a telephone gateway to Home Assistant Assist.

ESPHome devices are now real SIP endpoints and can:

  • call Home Assistant;
  • receive calls from Home Assistant;
  • call other ESP devices;
  • call registered clients such as Zoiper or Linphone;
  • participate in ring groups and conference rooms;
  • call external mobile or landline numbers through an optional trunk;
  • coexist with Voice Assistant, Micro Wake Word, TTS, media playback, Sendspin and AEC/AFE audio processing.

VoIP Stack dashboard in action

:speaking_head: Home Assistant Assist Can Now Be Called By Phone

A Home Assistant Assist pipeline can be exposed as a normal destination in the shared VoIP phonebook.

It can be called:

  • from an ESPHome VoIP device;
  • from Home Assistant’s softphone;
  • from a registered SIP client;
  • by name or numeric extension;
  • from an external telephone through an optional SIP trunk.

The assistant receives the caller identity, can greet the caller first and then continues with a normal multi-turn spoken conversation using the selected native Home
Assistant STT, conversation and TTS pipeline.

:bell: Ring Groups, Conferences And A Shared Dial Plan

The current stack includes:

  • extensions for HA, ESP devices and registered SIP phones;
  • shared phonebook-driven routing;
  • simultaneous ring groups where the first endpoint to answer wins;
  • Home Assistant-hosted SIP conference rooms;
  • DND and Auto Answer;
  • optional external SIP trunk routing;
  • standard DTMF extension selection;
  • experimental in-call DTMF events for Home Assistant automations.

The Lovelace card can operate as:

  • the Home Assistant softphone;
  • an ESP phone mirror and controller;
  • a manual keypad;
  • a shared phonebook view.

The maintained Waveshare P4 Touch profile also includes an on-device Contacts/Keyboard telephone interface.

:speaker_high_volume: The Full-Duplex Audio Work Continues

The original full-duplex ESPHome audio goal is still an important part of the project.

The reusable audio stack now provides:

  • official ESP-IDF I²S ownership;
  • codec-backed and codec-free audio paths;
  • lightweight AEC and full Espressif AFE profiles;
  • single-microphone and dual-microphone processing;
  • speaker-reference echo cancellation;
  • Voice Assistant barge-in;
  • shared media, TTS and VoIP playback;
  • real-time audio paths without arbitrary loop delays.

:pushpin: Follow The New VoIP Stack Thread

This older thread remains useful as project history, but future announcements, release information, new features and VoIP-specific discussion will be published in
the new thread:

:backhand_index_pointing_right: https://community.home-assistant.io/t/voip-stack-for-home-assistant-and-esphome/

Main repository:

:backhand_index_pointing_right: GitHub - n-IA-hane/esphome-intercom: VoIP Stack for ESPHome and Home Assistant - local SIP phones, HA softphone/router, phonebook, softphone accounts and optional trunk calls Ā· GitHub

Current stable release:

:backhand_index_pointing_right: Release 2026.7.1: Assist By Phone, Groups And A Stronger VoIP Stack Ā· n-IA-hane/esphome-intercom Ā· GitHub

If you were using one of the older Intercom Native versions, please refer to the current documentation and maintained YAML profiles before upgrading: the project has
migrated from its original custom call transport to standards-based SIP/SDP/RTP.

What started as an ESPHome full-duplex intercom is now a local telephone system built around Home Assistant. :telephone::house: