Skip to content

feat(sip_client): accept DTMF from ATAs on unnegotiated PTs and looser INFO bodies - #320

Merged
eigger merged 1 commit into
masterfrom
feat/sip-dtmf-rx-tolerance
Aug 31, 2026
Merged

feat(sip_client): accept DTMF from ATAs on unnegotiated PTs and looser INFO bodies#320
eigger merged 1 commit into
masterfrom
feat/sip-dtmf-rx-tolerance

Conversation

@eigger

@eigger eigger commented Aug 31, 2026

Copy link
Copy Markdown
Owner

Follow-up to #319. Both changes are ports of the hass-sip Python client, which
grew them after field reports from ATA / VoIP-adapter users; the ESPHome
component did not have them.

1. RFC 2833 on an unnegotiated payload type

Some adapters send telephone-event without ever offering it in their SDP, or on
a different dynamic PT than the one negotiated. The receive path only accepted
the negotiated PT, so those keypresses were dropped entirely.

A 4-byte payload on a dynamic PT (96-127) that is not the negotiated audio PT
is the RFC 4733 shape — event, E|R|volume, duration — and never an audio frame,
so it is now decoded as a telephone-event. The acceptance is logged once per
session, and a session that negotiated no telephone-event at all now warns that
inbound DTMF depends on this fallback or SIP INFO.

2. Looser SIP INFO bodies

The parser now takes what gateways actually send:

  • keys d= and dtmf= besides Signal=
  • content type audio/telephone-event besides application/dtmf-relay and
    application/dtmf
  • a body with no Content-Type header at all

Duration=250 is still not read as digit D, and a non-DTMF content type is
still rejected.

Tests

Two new cases in tests/native/sip_sdp/test_dtmf.cpp
(info_alternate_keys_and_types, unnegotiated_telephone_event_pt), the
payload-type decision extracted as is_unnegotiated_telephone_event() in
dtmf.h so it is testable on the host.

  • tests/native/sip_sdp/ — all pass
  • esphome compile tests/components/sip_client/test.esp32-idf.yaml — succeeds

Not merged yet on purpose: this widens the DTMF receive path beyond what #318
asked for, so it is worth holding until the on-device results from #318 land.

🤖 Generated with Claude Code

…r INFO bodies

Ported from the hass-sip Python client, which grew both after field reports
from ATA / VoIP-adapter users.

Some adapters send RFC 2833 without ever offering telephone-event in their
SDP, or on a different dynamic payload type than the one negotiated, so the
keypress was dropped. A 4-byte payload on a dynamic PT (96-127) that is not
the negotiated audio PT is the telephone-event shape and is now decoded as
one, logged once per session. Sessions that negotiated no telephone-event at
all now say so, since inbound DTMF then depends on this fallback or SIP INFO.

The INFO parser also accepts what gateways actually send: the `d=` and `dtmf=`
keys besides `Signal=`, the `audio/telephone-event` content type, and a body
with no Content-Type at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@eigger
eigger merged commit 91959e6 into master Aug 31, 2026
10 checks passed
@eigger
eigger deleted the feat/sip-dtmf-rx-tolerance branch August 31, 2026 23:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant