Skip to content

Expose the mDNS PTR record TTL on the reachability snapshot#1682

Merged
bdraco merged 1 commit into
mainfrom
mdns-expiry-countdown
Jun 24, 2026
Merged

Expose the mDNS PTR record TTL on the reachability snapshot#1682
bdraco merged 1 commit into
mainfrom
mdns-expiry-countdown

Conversation

@bdraco

@bdraco bdraco commented Jun 24, 2026

Copy link
Copy Markdown
Member

What does this implement/fix?

People keep filing "offline device still shows online" reports because mDNS is passive and that isn't obvious. An ESPHome device's online state comes from its zeroconf PTR record; when a device powers off without a goodbye the record only ages out after its TTL (commonly around 75 minutes). We deliberately don't actively probe every device on a short interval, since that would flood the network with mDNS traffic.

This is the backend half of surfacing that in the UI. It adds mdns_ptr_ttl_remaining_seconds to the reachability snapshot (MdnsCacheInfo and the reachability_state event); it is the PTR record's own remaining TTL, the countdown to the zeroconf Removed event that flips an mDNS owned device offline, kept distinct from the existing freshest-record mdns_ttl_remaining_seconds (which tracks the ~120s A record the refresh loop renews). It also adds a one shot devices/get_reachability command so the open drawer can re-read the live value; a pure PTR TTL refresh is deduped by zeroconf and never pushed, so polling is the only way to catch a healthy device re-announcing. The read is scoped to a single device, not fleet wide.

The frontend renders the countdown and does the polling; this PR only exposes the data.

Related issue or feature (if applicable):

Types of changes

  • Bugfix (non-breaking change which fixes an issue) — bugfix
  • New feature (non-breaking change which adds functionality) — new-feature
  • Enhancement to an existing feature — enhancement
  • Breaking change (fix or feature that would cause existing functionality to not work as expected) — breaking-change
  • Refactor (no behaviour change) — refactor
  • Documentation only — docs
  • Maintenance / chore — maintenance
  • CI / workflow change — ci
  • Dependencies bump — dependencies

Frontend coordination

Checklist

  • The code change is tested and works locally.
  • Pre-commit hooks pass (ruff, codespell, yaml/json/python checks).
  • Tests have been added or updated under tests/ where applicable.
  • components.index.json / definitions/components/*.json have not been hand-edited (regenerate via script/sync_components.py if a sync is needed).
  • Architecture-level changes are reflected in docs/ARCHITECTURE.md and/or docs/API.md.

@codspeed-hq

codspeed-hq Bot commented Jun 24, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 27 untouched benchmarks


Comparing mdns-expiry-countdown (d52807f) with main (c6dafd1)

Open in CodSpeed

@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.54%. Comparing base (98ced27) to head (d52807f).
⚠️ Report is 6 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main    #1682   +/-   ##
=======================================
  Coverage   99.54%   99.54%           
=======================================
  Files         227      227           
  Lines       18129    18142   +13     
=======================================
+ Hits        18047    18060   +13     
  Misses         82       82           
Flag Coverage Δ
py3.12 99.52% <100.00%> (+<0.01%) ⬆️
py3.14 99.54% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
..._builder/controllers/_device_state_monitor/mdns.py 99.36% <100.00%> (+<0.01%) ⬆️
...evice_builder/controllers/_reachability_tracker.py 100.00% <100.00%> (ø)
...e_device_builder/controllers/devices/controller.py 99.73% <100.00%> (+<0.01%) ⬆️
esphome_device_builder/models/devices.py 100.00% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the per-device reachability snapshot to expose the mDNS PTR record’s remaining TTL so the UI can display an “offline in N seconds” horizon (distinct from the existing freshest-record TTL), and adds a one-shot command to fetch the current snapshot on demand.

Changes:

  • Add mdns_ptr_ttl_remaining_seconds to DeviceReachabilityData and propagate it through ReachabilityTracker.snapshot.
  • Extend the mDNS cache reader (get_mdns_cache_info) to compute and return the PTR record’s remaining TTL separately from the freshest-record TTL.
  • Add devices/get_reachability WS command and update tests + API docs accordingly.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
esphome_device_builder/controllers/_device_state_monitor/mdns.py Computes PTR TTL remaining and returns it in MdnsCacheInfo.
esphome_device_builder/controllers/_reachability_tracker.py Adds PTR TTL field to MdnsCacheInfo and includes it in the reachability snapshot wire shape.
esphome_device_builder/controllers/devices/controller.py Adds new devices/get_reachability command returning the current snapshot.
esphome_device_builder/models/devices.py Extends DeviceReachabilityData TypedDict with mdns_ptr_ttl_remaining_seconds.
docs/API.md Documents the new field on reachability events and the new one-shot command.
tests/test_state_monitor_reachability.py Adds coverage for PTR TTL calculation via get_mdns_cache_info.
tests/test_reachability_tracker.py Verifies snapshot includes mdns_ptr_ttl_remaining_seconds.
tests/controllers/devices/test_subscribe_reachability.py Adds tests for devices/get_reachability behavior and NOT_FOUND.

Comment thread esphome_device_builder/controllers/devices/controller.py
Comment thread esphome_device_builder/controllers/devices/controller.py Outdated
Comment thread docs/API.md Outdated
@esphbot

esphbot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@esphbot esphbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

@bdraco bdraco force-pushed the mdns-expiry-countdown branch from 503d9cd to 0b90c5c Compare June 24, 2026 16:53
@bdraco bdraco marked this pull request as ready for review June 24, 2026 20:34
Adds mdns_ptr_ttl_remaining_seconds to MdnsCacheInfo and the
reachability_state payload; it is the PTR record's own remaining TTL,
the countdown to the zeroconf Removed event that flips an mDNS owned
device offline, distinct from the freshest-record mdns_ttl_remaining_seconds.

Adds a one shot devices/get_reachability command so the open drawer can
re-read the live value; a pure PTR TTL refresh is deduped by zeroconf and
never pushed, so polling is the only way to catch a healthy device
re-announcing.
@bdraco bdraco force-pushed the mdns-expiry-countdown branch from 0b90c5c to d52807f Compare June 24, 2026 20:47
@esphbot

esphbot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

PR Review — Expose the mDNS PTR record TTL on the reachability snapshot

Clean, additive data-exposure change. Merge-ready.

The PR surfaces exactly one new nullable field and one one-shot read command, with no behavior change to existing paths. Specific strengths:

  • The subtle part — PTR-TTL vs freshest-record union TTL — is implemented correctly: ptr is already resolved at mdns.py:200 and reused for the new remaining-TTL read, and pinned by a dedicated assertion in test_get_mdns_cache_info_picks_latest_across_record_types.

  • MdnsCacheInfo.ptr_ttl_remaining_seconds defaults to None and sits before the default_factory field — valid dataclass ordering — with the None-when-no-PTR branch covered.

  • Empty-snapshot, populated, and None-cache tracker paths were all updated, so the new wire field is exercised everywhere a snapshot is built (100% patch coverage).

  • devices/get_reachability is a justified carve-out from snapshot-plus-events: a deduped PTR re-announce fires no bus event, so polling is the only signal. Validation (empty→INVALID_MESSAGE, unknown→NOT_FOUND) exactly mirrors subscribe_reachability; both paths tested; docs/API.md updated for both commands.

  • Only note: minor validation duplication between get_reachability and reachability.subscribe — optional DRY-up, non-blocking.

The prior Copilot/esphbot comments about polling-cadence text and missing INVALID_MESSAGE reference an earlier revision; the current docstring is already trimmed and validation is already present.



Checklist

  • Input validation at boundaries (empty→INVALID_MESSAGE, unknown→NOT_FOUND)
  • New wire field covered in all snapshot paths
  • Backward-compatible (additive nullable field + additive command)
  • Dataclass field ordering valid
  • Docs (API.md) reflect new command/field
  • No duplicated validation logic

Automated review by Kōan (Claude) HEAD=d52807f 2 min 1s

@esphbot esphbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

@bdraco bdraco enabled auto-merge (squash) June 24, 2026 20:51
@bdraco bdraco merged commit 584d722 into main Jun 24, 2026
21 checks passed
@bdraco bdraco deleted the mdns-expiry-countdown branch June 24, 2026 20:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Improvement to an existing feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants