2026.9.0 is the stable release of the call-lifecycle, SIP interoperability and ESP32-P4 videophone work developed after 2026.8.0.
This release makes VoIP Stack a much more coherent Home Assistant phone system. Calls from the dashboard, ESPHome phones, registered SIP clients, trunks, forwarding, ring groups and conferences now share the same core rules instead of following separate implementations. It also introduces the first complete ESPHome videophone profile for ESP32-P4.
🏠 Home Assistant: one phone system, not a collection of special cases
One authoritative lifecycle for every call
Every call is now represented by one owned session containing its individual call legs. Answering, bridging, renegotiating media, hanging up and cleaning resources all pass through shared primitives.
In practical terms, the dashboard, an ESPHome phone, a SIP client and a trunk no longer need independent versions of the same call logic. This is the same architectural direction used by mature PBX projects: one model for a call, explicit legs, one final termination decision and a cleanup barrier before the endpoint becomes available again. It should make future features easier to add and greatly reduce stale busy states, ghost calls, leaked RTP ports and immediate-redial races.
SIP trunks now keep registration, identity and routing separate
A trunk can have three different addresses: the public SIP identity used in From, the logical domain used by SIP and Digest authentication, and an outbound proxy used only as the network next hop. VoIP Stack now preserves those roles instead of treating them as one interchangeable server address.
For UDP trunks, an outgoing call also reuses the socket and source port that completed REGISTER. INVITE, authentication, ACK, re-INVITE and BYE therefore remain on the registered NAT flow. This fixes providers that reject or cannot route a call opened from a second random UDP port. A configured UDP proxy remains authoritative even when the logical SIP domain has no directly usable DNS address.
The result is provider-neutral behavior rather than a fix tied to one service. The Swisscom reporter for issue 105 has confirmed that the current release connects and carries the provider announcement.
Audio and video negotiation behave like normal SIP media
Each side of a bridged call negotiates the best media it actually supports. A narrowband trunk is not needlessly presented as wideband, while compatible local clients can retain higher-quality audio. Codec, payload type, direction and packet time are kept with the call leg that negotiated them.
An audio call can add video later with a standard in-dialog re-INVITE, remove it and add it again without restarting the audio call. Initial video calls still start with video immediately. Audio-only ESPHome devices are no longer sent an irrelevant video offer, while video-capable phones retain both initial and delayed video negotiation.
When both legs support the same format, RTP stays direct. When they do not, the bounded media converter can adapt the active direction between the supported H.264, VP8 and RTP/JPEG formats. A rejected video change leaves the existing audio call intact.
Calls remain predictable when several destinations are involved
Forwarding, ring groups and conferences now use the same call owner and leg model as direct calls. The first valid answer can win, losing destinations are cancelled and cleaned, and a failed member does not tear down the members that remain connected.
Busy, decline, cancellation, late answers, remote forks and immediate redial are handled through the same termination and cleanup rules. Public idle is published only when call tasks, media owners, relays, sockets and RTP reservations have actually been released.
DTMF follows the established call
RFC 4733 RTP events and SIP INFO are routed through the active dialog and bridge. This means a digit entered by a dashboard phone, registered SIP client or ESPHome device reaches the endpoint currently participating in that call, including HA-to-HA calls routed through an external PBX.
The phonebook now reflects who can really be called
Registered SIP accounts appear only while they have a valid registrar binding. Explicit de-registration and natural Contact expiry remove them. Offline ESPHome devices follow their availability, while static contacts and browser phones remain independent of SIP registration.
Phonebook delivery is tracked per ESPHome device. Reconnecting one phone restores that phone without republishing an unchanged roster to every other device.
The dashboard phone is more resilient and easier to control
The card now keeps the logical phone alive independently of whether a particular dashboard view is open. Reloading a dashboard or opening multiple cards does not create a second call owner.
Microphone, speaker and camera choices can be selected and remembered per browser phone. They can also be changed during a call without replacing the SIP session. Video processing can run in a worker so JPEG encoding and supported WebCodecs decoding do not block the card interface.
The card also preserves the resolved caller and callee identity, rejects calls cleanly when browser microphone access is unavailable, and prevents frames from an old call appearing in a new one. The microphone anti-alias option reduces high-frequency folding when the browser capture rate must be converted to the negotiated SIP rate.
Better interoperability and localization
- FRITZ!Box registration now uses the correct registrar and account identities, refreshes transaction identifiers after authentication and accepts internal service strings such as
**621. - Valid RTP chunks that do not match the advertised packet size are accumulated and reframed instead of forcing an unnecessary codec change.
- Reliable provisional responses, PRACK, delayed offers, session refresh, REFER/NOTIFY, Digest authentication, RFC 3263 discovery, TLS and IPv6 now share the consolidated SIP lifecycle.
- Home Assistant configuration, entities, repairs, system health, service errors, the card editor and the phonebook now include Brazilian Portuguese and German translations.
- DNS record parsers are loaded outside Home Assistant's event loop, removing blocking-import warnings without changing routing behavior.
Media device selection in the dashboard card
The card Options view now lets each browser or Companion app select and remember its preferred microphone, speaker and camera. It can request media permission, change active devices and switch between available cameras without replacing the SIP call. In future versions we will continue consolidating media-device discovery and selection across browsers and mobile platforms.
🎥 ESPHome: WE HAVE A FUCKING ESPHOME VIDEOPHONE!
Everything started when @Psix-anp shared a working ESP32-P4 camera build based on the ESPHome camera work by @youkorr. What can I say, half a second later I was already experimenting with bidirectional SIP video on ESPHome.
The camera changes developed and validated here are intended to be contributed back to @Psix-anp's project as focused pull requests. Once they are accepted upstream, the maintained videophone YAMLs will point directly to their repository instead of carrying a permanent camera fork.
And we made it. The P4 is not only sending its camera to another phone. It is a complete SIP videophone: it can transmit its own camera, receive the remote RTP video stream, decode it and display the other party on its physical MIPI DSI panel while bidirectional audio remains active.
This opens the door to custom ESPHome videophones and doorbells with echo cancellation, full P4 voice-assistant tablets, and direct room-to-room audio and video calls between P4 devices.
Video sent by the P4
esp_video2.4.1 owns the MIPI-CSI and V4L2 camera pipeline.- The JPEG profile borrows the camera's already encoded JPEG frame for RTP. It does not capture or encode the same image a second time.
- The H.264 profile uses the ESP32-P4 hardware encoder from
esp_h2641.3.6 and optimized I420 conversion fromesp_image_effects1.1.0. - The camera discards its first two CSI buffers after startup, following Espressif practice, so a call starts with a color-stable image instead of exposing a transient ISP startup frame.
Remote video displayed on the P4
- The receive path reorders a bounded RTP packet window and reconstructs complete JPEG or H.264 frames. Damaged or incomplete frames are discarded instead of appearing late.
- JPEG uses the P4 hardware JPEG decoder. H.264 uses Espressif's software decoder. PPA performs scaling, rotation and pixel-format conversion where required.
- Decoded frames are submitted to the physical MIPI DSI display through the maintained display adapter. This is a real panel presentation path, not the ESPHome camera entity and not a result inferred only from RTP counters.
- Presentation follows the video media clock. Bounded queues, persistent workers and reusable buffers prevent slow frames from building an ever-growing delay behind the live call.
- Direct video and LVGL share an explicit display lifecycle. Starting video does not corrupt the controls, and removing video or hanging up restores the idle interface without leaving a stale frame, hidden lower bar or broken navigation state.
A real bidirectional call
Both profiles support calls that offer audio and video immediately, and calls that begin with audio and add video later through a standard re-INVITE. Video can be removed and added again without restarting the established audio path. A rejected video update leaves the audio call active.
JPEG and H.264 remain separate compile-time profiles. Selecting one codec does not silently include the other encoder, decoder, buffers or managed libraries.
The full JPEG profile runs the videophone together with AFE echo cancellation, Micro Wake Word, Voice Assistant, LVGL, TTS, HTTP media and Sendspin. It remains a full voice-assistant and media panel while idle, then becomes a bidirectional audio and video phone during a SIP call. JPEG is the stable full-device baseline. H.264 remains experimental in this release.
Qualification treated SIP/SDP negotiation, RTP and codec processing, and physical-panel presentation as three separate requirements. The hardware scenarios covered initial video, audio-first video activation, video removal, local and remote hangup, immediate redial and repeated calls. The final three-cycle H.264 run presented approximately 10 frames per second on the physical panel while audio remained active.
The maintained profiles are:
waveshare-p4-touch-full-afe-landscape-videophone-jpeg.yaml, the complete AFE, voice, media, LVGL and JPEG videophone;waveshare-p4-touch-videophone-jpeg.yaml, the lighter bidirectional RTP/JPEG videophone;waveshare-p4-touch-videophone-h264.yaml, the experimental bidirectional H.264 videophone.
⌨️ Smaller ESPHome improvements
The compact Spotpear profile has a touch keypad for contact navigation and direct extension dialing.
The Waveshare S3 full AFE profile now keeps both HTTP media ring buffers allocated across playback cycles, matching the qualified Spotpear behavior. Reusing those buffers avoids repeated large allocations and reduces fragmentation during music, TTS and VoIP use.
Existing ESP audio profiles retain their native PCM transport and configured sample rates. This release does not replace the proven audio backend merely to support P4 video.
⚠️ Breaking change
The former P4 full landscape profile is now the canonical full JPEG videophone and has been renamed from:
yamls/full-experience/single-bus/waveshare-p4-touch-full-afe-landscape.yaml
to:
yamls/full-experience/single-bus/waveshare-p4-touch-full-afe-landscape-videophone-jpeg.yaml
Review the complete breaking changes before updating an existing installation.
✅ Qualification of this release
- 1663 Python tests, 4 intentionally deselected tests, 140 parameterized subtests and 88 Home Assistant runtime tests passed.
- A real Wildix-to-Home Assistant video call carried bidirectional PCMA audio and VP8 video with zero RTP sequence loss or duplicates. Manual answer, BYE and final cleanup also passed.
- The real Home Assistant routing matrix covered direct calls, trunks, registered clients, forwarding, busy, decline, cancellation, delayed offers, concurrency, DTMF and final resource cleanup.
- The full JPEG P4 completed repeated bidirectional calls, audio-first video activation, camera requests during a call, hangup, redial and post-call cleanup.
- The H.264 P4 completed three consecutive bidirectional cycles with about 10 presented frames per second, working audio, physical-panel presentation and clean teardown.
- WS3 and Spotpear completed real audio calls and recovery checks with their persistent media buffers enabled.
- The published HACS archive is
voip_stack.zip, SHA256512e1c346c6b6d9ba2f10bfc3c38bf049045a8a338ecc493d686551d197536ca.
⚠️ Known issues
Media-device permission on some browsers and phones
Some browsers and mobile operating systems initially expose only a generic camera or a partial device list. Open the card Options and tap Allow media access to grant permission and display all available microphones, speakers and cameras.
OnePlus high-refresh audio
On a OnePlus Nord 5, received browser audio can develop gaps and increment the playback underrun counter when the display uses a high refresh rate, especially during touch or orientation changes. The same phone is stable at 60 Hz, and the problem has not reproduced on a Samsung S20 or desktop Chromium. The current workaround is to use 60 Hz for Chrome and the Home Assistant Companion app. The evidence currently points to device-side Chromium or OxygenOS scheduling, but the exact cause is not yet proven.
CheapConnect cold-start registration
One CheapConnect installation has shown two unanswered UDP REGISTER attempts after a cold Home Assistant start. Each reached the standard SIP transaction timeout, then a fresh flow registered successfully and calls worked in both directions. A provider-side packet capture is still needed before changing standards-based retry timing or startup ordering.
🔧 Detailed technical changelog
Show implementation details
- Calls use generation-owned
EndpointCallSessioninstances, explicit legs, immutable termination intent, common answer and bridge commits, and a cleanup barrier. - Inbound routing, forwarding, conferences, SIP bridges and browser media claims have focused owners.
CallRegistryremains the observable compatibility index rather than becoming a second lifecycle. - Routing identity and display identity are separate. Stable URI users route calls, quoted display names preserve friendly names and RFC 4916 connected identity reports the resolved callee after answer.
- Completed dialogs retain bounded transaction tombstones, so a delayed retransmission cannot reclaim a phone after BYE.
- REGISTER, INVITE, authenticated retries, ACK, CANCEL, re-INVITE and BYE preserve the correct logical domain, route set, proxy next hop and registered UDP flow.
- Direct and converted audio is paced by negotiated packet time. The shared PCM converter handles valid input chunk boundaries that differ from
ptime. - Media changes are staged and committed atomically across bridge legs. Rejected or failed video changes release staged resources without disturbing established audio.
- Browser worker generations close replaced
VideoFrameandImageBitmapobjects on success, replacement and error paths. - Physical ESPHome phones and browser phones use the same
device_idsource selector but dispatch through their native adapters. An ESP service response is not resolved again as a browser phone. packages/voip/ha_phone.yamlprovides discovery entities, native call controls and the Home Assistant managed phonebook in one include.- Unexpected teardown failures are surfaced instead of silently hidden.
- The local qualification tools cover Home Assistant, Chromium, SIPp, baresip, registered clients, trunks, ESP32-S3 and ESP32-P4 hardware, firmware identity and final quiescence.
📦 Installation and feedback
In HACS, open VoIP Stack, use the three-dot menu, select Redownload and choose 2026.9.0. Restart Home Assistant, then clear the browser or Companion app cache so the new card code is loaded.
When reporting a regression, include the exact test time, call direction, endpoint models, selected codec and sanitized logs from INVITE through final cleanup.
💖 Thanks for the donations
Special thanks to @jjhelin, @kuid87, @jftkcs and @DunklerPhoenix for supporting the project. If VoIP Stack has been useful to you and you would like to support its continued development, you can do so through GitHub Sponsors.



