VoIP Stack 2026.7.0: Home Assistant is a phone now. Yes, a real one. And your ESPs too.

This release changes the category of the project.
It started as an ESPHome full-duplex intercom.
It is now a local SIP/VoIP system built around Home Assistant.
Home Assistant can now act as a SIP softphone, call router, RTP bridge/resampler, local SIP registrar and optional SIP trunk endpoint. ESPHome devices become real SIP/SDP/RTP endpoints too: they can call each other, call Home Assistant, receive calls from Home Assistant, call registered softphones and, with a trunk configured, call or receive calls from external phone numbers.
This is the stable 2026.7.0 SIP/VoIP migration release.
It is a major breaking upgrade from the old intercom call model to SIP/SDP/RTP, so test custom YAMLs on spare hardware before upgrading devices installed in places where temporary audio glitches or call-routing regressions would be a problem.
Why this matters
With a SIP trunk configured, this is no longer just “an intercom card”.
You can build things like:
| Scenario | What happens |
|---|---|
| Your real mobile/landline call reaches Home Assistant through the SIP trunk, and HA can ring the softphone card or route the call to an ESP device | |
| An ESP door station can call Home Assistant, an internal ESP, a registered softphone or an external number routed through HA | |
| The Lovelace card can ring, answer, decline, hang up and start calls as Home Assistant itself | |
| ESPHome devices can call each other by phonebook name or extension | |
| Zoiper, Linphone, baresip or pjsua can register directly to Home Assistant and become internal contacts | |
| Call your SIP trunk number from anywhere and route the inbound call to HA or internal devices | |
| ESP devices can call phonebook contacts that resolve to external trunk numbers |
No Asterisk box required beside Home Assistant for the normal local use case.
External calls: mobile phones, landlines and the outside world
This is the part that makes the project stop looking like a gadget and start looking like a tiny local phone system.
With the optional SIP trunk enabled, Home Assistant can register to a provider or PBX trunk.
That means:
| Direction | Example |
|---|---|
| External → Home Assistant | Call your trunk number from a mobile phone and make the HA softphone card ring |
| External → ESP | Call your trunk number and route the call to a door panel, room phone or wall display |
| ESP → external | Press a button or select a contact on an ESP and call a mobile or landline through HA |
| HA → external | Use the Lovelace softphone card to call a public number |
| Softphone → external | Register Zoiper/Linphone to HA and call out through the trunk |
| External → internal extension | Use DTMF / route hints to reach a local contact such as 101 |
So yes: a doorbell can call your phone, your phone can call home, and Home Assistant can sit in the middle doing the routing.
Provider behavior varies, especially around codecs, DTMF and NAT, so trunk feedback is especially useful.
Component showcase
| Icon | Component | Role |
|---|---|---|
voip_stack Home Assistant integration |
SIP softphone, B2BUA/router, bridge, resampler, phonebook publisher, local registrar and optional trunk client | |
| VoIP Stack Lovelace card | HA softphone UI and ESP mirror/control UI | |
esphome-voip-stack |
ESPHome SIP phone component: SIP/SDP/RTP, call FSM, phonebook, entities, actions and triggers | |
esphome-audio-stack |
Full-duplex ESPHome audio backend with I2S/codec ownership, AEC, AFE and clean mic/speaker surfaces | |
esphome-runtime-controller |
Deterministic reducer for Voice Assistant, media, TTS, timers, VoIP, mute, LEDs and display state | |
| Phonebook / dial plan | Central routing model shared between HA, ESP devices, registered softphones and trunk contacts |
Home Assistant is now a phone
The Home Assistant card has a real softphone mode.
It can:
- ring on incoming calls;
- answer from browser, wall tablet or Companion app;
- decline with a SIP reason;
- hang up active calls;
- place outbound calls;
- dial a phonebook contact, extension, SIP URI or external number;
- bridge calls toward ESPs, registered softphones or the optional trunk.
There are two card modes:
| Mode | Use it for |
|---|---|
ha_softphone |
Home Assistant itself is the phone |
| Hybrid / ESP mirror | The card mirrors and controls a selected ESP phone |
If an ESP calls Home Assistant, the HA softphone card rings.
If Home Assistant calls an ESP, the ESP rings.
If auto-answer is enabled on either side, the call can be accepted automatically.
ESPHome devices are SIP phones now
ESP devices do not register to a PBX.
They are local SIP user agents. Home Assistant learns them through ESPHome, reads their endpoint identity, publishes them into the shared phonebook and routes calls when needed.
Each ESP can be one of four endpoint types:
| Mode | Media path | Example |
|---|---|---|
full_duplex |
Microphone TX + speaker RX | Door intercom, room phone, wall panel |
mic_only |
Microphone TX only | Monitor microphone, outdoor call station |
speaker_only |
Speaker RX only | Paging speaker, announcement target |
control_only |
SIP signaling and state only | LVGL panel, button panel, call-state display |
Mic-only and speaker-only are first-class modes, not broken full-duplex devices.
Phonebook-driven routing
The central phonebook is now the dial plan.
A contact can represent:
- an ESP endpoint;
- Home Assistant itself;
- a registered SIP softphone;
- a manual SIP URI;
- an internal extension;
- an external number through the optional trunk;
- a routed target handled by Home Assistant.
Routing follows the data available for the selected contact:
| Contact data | Route |
|---|---|
| Direct SIP endpoint | Compatible ESPs can call it directly |
| Name or extension only | ESP sends the call to Home Assistant |
| Registered softphone | HA routes to the active SIP registration |
| External number | HA uses the configured SIP trunk |
| No valid route | HA rejects the call with a readable reason |
The phonebook is also pushed automatically to online ESP devices when HA contacts, ESP endpoints or registered softphones change.
Voice Assistant integration
VoIP Stack can optionally expose Assist intents.
That means voice satellites can call, answer, decline or hang up using voice commands.
Examples:
Call Kitchen
Call Garage
Answer
Decline
Hang up
Spoken names are resolved against the live phonebook, so you do not need to hardcode every low-level route in YAML.
Full voice device: media, TTS, wake word and VoIP on the same ESP
One of the goals of this release is keeping the “everything device” viable:
- media playback;
- Music Assistant / Sendspin;
- TTS;
- Micro Wake Word;
- Voice Assistant;
- AEC / AFE;
- ringtone;
- VoIP calls;
- LVGL display state.
The maintained full-experience profiles now use a shared media/source path and the runtime reducer model so callbacks report state instead of fighting over LEDs, displays and audio policy.
On my current tested full voice ESP32-S3 profile, the complete stack can coexist around the 22–23% CPU range, depending on hardware profile and enabled features.
ESP Audio Stack split
The reusable audio backend now lives here:
It provides:
- full-duplex I2S ownership;
- shared codec bus support;
- split RX/TX bus support;
- TDM support;
- hardware codec backends;
- speaker reference handling for AEC;
- software AEC through Espressif ESP-SR;
- full AFE pipeline with noise suppression, VAD, AGC and dual-mic Speech Enhancement;
- ESPHome microphone and speaker platforms on top.
Supported codec families currently include ES7210, ES8311, ES8388, ES8374 and ES8389.
ESPHome VoIP Stack split
The ESP SIP component now lives here:
It provides:
- SIP signaling over UDP or TCP;
- SDP offer/answer;
- RTP media;
- jitter buffer;
- call FSM;
- endpoint identity publishing;
- local phonebook;
- per-direction PCM negotiation;
- ESPHome actions, triggers, conditions and entities.
RTP media is UDP in both SIP transport modes.
Runtime Controller split
The runtime arbitration component now lives here:
It solves the classic “last callback wins” problem on complex ESPHome devices.
Instead of Voice Assistant, media, VoIP, timers, mute and connectivity all writing directly to LEDs or display state, they report facts. The reducer resolves one coherent device state.
That means fewer stuck LEDs, fewer display races, fewer ducking bugs and fewer YAML goblin rituals.
Screenshots and demos
Tested / maintained target families
The maintained YAMLs focus mainly on ESP32-S3 and ESP32-P4 voice hardware.
Examples include:
| Target | Notes |
|---|---|
| Waveshare ESP32-S3 Audio Board | Full voice / VoIP profile target |
| Spotpear Ball v2 | Voice UI, Assistant, media and VoIP testing |
| Waveshare ESP32-P4 Touch | Display-heavy P4 target, still useful for tuning and feedback |
| Generic ESP32-S3 with I2S mic/speaker | Native and audio-stack profiles |
| INMP441 + MAX98357A-style builds | Useful for simple duplex tests |
| XMOS / external DSP front-ends | Can use native ESPHome audio paths when echo cancellation is already handled in hardware |
Installation
Home Assistant
VoIP Stack is available through HACS.
Search for:
VoIP Stack
Then install, restart Home Assistant and add the integration from:
Settings → Devices & services → Add integration → VoIP Stack
Use the custom repository flow only if you intentionally want to test main or development builds directly:
https://github.com/n-IA-hane/esphome-intercom
Default ports:
| Purpose | Default |
|---|---|
| SIP | 5060 |
| RTP base | 40000 |
The integration automatically registers the Lovelace card.
ESPHome: VoIP only
external_components:
- source: github://n-IA-hane/esphome-voip-stack@main
components: [voip_stack]
ESPHome: VoIP + lightweight AEC
external_components:
- source: github://n-IA-hane/esphome-voip-stack@main
components: [voip_stack]
- source: github://n-IA-hane/esphome-audio-stack@main
components: [esp_audio_stack, esp_aec]
ESPHome: VoIP + full AFE
external_components:
- source: github://n-IA-hane/esphome-voip-stack@main
components: [voip_stack]
- source: github://n-IA-hane/esphome-audio-stack@main
components: [esp_audio_stack, esp_afe]
ESPHome: runtime reducer
external_components:
- source: github://n-IA-hane/esphome-runtime-controller@main
components: [runtime_controller]
Or use the ready package for full voice + VoIP profiles:
packages:
runtime_controller: github://n-IA-hane/esphome-runtime-controller/packages/runtime_controller/full_controller.yaml@main
Upgrade notes
This is a breaking migration from the old project-specific call-control model to SIP/SDP/RTP.
Please review custom YAMLs before upgrading.
Important notes:
- ESP devices are local SIP phones, but they do not REGISTER to a provider or PBX.
- Home Assistant owns external trunk registration.
- RTP media is UDP even when SIP signaling uses TCP.
- Audio is negotiated per direction.
- AFE/AEC microphone output remains 16 kHz by design.
- HA bridging is the supported path for incompatible direct media formats.
- UDP high-rate audio is limited by datagram size.
- Use TCP signaling when routed/VPN/remote network behavior is uncertain.
- Do not expose random SIP/RTP ports directly to the public internet unless you enjoy packet goblins chewing your logs.
Migration from the old intercom thread
If you were using the previous intercom releases, this is the new direction of the project.
The old simple use case still works:
ESP button → Home Assistant rings → answer → talk → hang up
The difference is that the foundation is now SIP/VoIP, so the same setup can grow into:
ESP ↔ ESP
ESP ↔ Home Assistant
Home Assistant ↔ registered softphone
ESP ↔ registered softphone through HA
Home Assistant ↔ external phone number through trunk
ESP ↔ external phone number through trunk
Links
- Main repository: VoIP Stack for ESPHome and Home Assistant
- ESPHome VoIP component: esphome-voip-stack
- ESPHome audio backend: esphome-audio-stack
- Runtime reducer: esphome-runtime-controller
- Release notes: 2026.7.0 release notes
- Sponsor: GitHub Sponsors
Feedback wanted
I am especially interested in real-world feedback around:
- ESP ↔ ESP calls;
- ESP → HA softphone calls;
- HA softphone → ESP calls;
- registered softphones;
- SIP trunk inbound and outbound routing;
- DTMF / route-hint behavior;
- VLANs, VPNs, routed networks and weird NAT setups;
- mixed 16 kHz / 48 kHz audio paths;
- Waveshare S3, Spotpear, Waveshare P4 and generic ESP32-S3 profiles.
This started as an intercom.
Now Home Assistant has a local phone system living inside it. ![]()
![]()














