Fix/panel off line#135
Merged
cayossarian merged 15 commits intomainfrom Apr 16, 2026
Merged
Conversation
Distinct from SpanPanelConnectionError — used by get_snapshot() to signal that the client is running but data is not currently live.
Widens SpanPanelClientProtocol and adds SpanMqttClient.register_connection_callback so consumers can subscribe to broker connection state transitions. Fan-out behavior is added in the next commit.
…back - Drop redundant bool annotation on _live to match surrounding style - Document that registration is edge-only, no synthetic initial call
_on_connection_change now emits edge-only notifications to callbacks registered via register_connection_callback. Duplicate states are suppressed; exceptions in one subscriber do not break others.
_FakeBridge now inherits from AsyncMqttBridge and _FakeHomie from HomieDeviceConsumer, bypassing __init__ to avoid I/O setup. This makes assignments to client._bridge and client._homie pass strict type checking without type: ignore or cast().
- Comment in _on_connection_change documents that re-subscribe runs on every connected=True event (including duplicates), while callback fan-out is edge-only. - Add regression test locking in the resubscribe-on-duplicate-True behavior. - Tighten the reconnect test's topic assertion to an exact match against WILDCARD_TOPIC_FMT.
… live Replaces the silent stale-cache return with explicit failure. Three distinct messages (client not connected / broker disconnected / homie not ready) so logs self-diagnose. Breaking change: consumers that called get_snapshot() while offline now receive an exception they can catch to drive offline-state logic.
Document that field values are arbitrary and only object identity is asserted, so future additions to the SpanPanelSnapshot dataclass can be handled with zero/empty defaults without touching assertions.
Addresses final-review items: - SpanPanelStaleDataError is now importable from span_panel_api directly, matching the pattern of every other exception in the hierarchy. - close() resets the _live edge tracker so a subsequent connect() on the same instance correctly fires a True edge for the new session.
…or in README - Update SpanPanelClientProtocol row in the protocols table to include register_connection_callback. - Add a Connection State Monitoring section after Streaming Pattern showing how to subscribe to broker connection edges, with a note that get_snapshot() now raises SpanPanelStaleDataError when not live. - Add SpanPanelStaleDataError to the Error Handling table and explain the semantic distinction from SpanPanelConnectionError. Update the usage example to catch it during normal operation.
…n liveness A snapshot-debounce timer scheduled just before a bridge disconnect could fire after the fact and dispatch a snapshot built from the still-ready accumulator — the panel-die case where no \$state=disconnected message is published. Push consumers (e.g. the span HA integration) then saw a spurious live snapshot while the bridge was actually down. Two complementary fixes: - _on_connection_change(False) now cancels any pending _snapshot_timer. - _dispatch_snapshot is now gated by the same liveness predicate as get_snapshot() (bridge connected AND homie ready); stale dispatches bail silently with a debug log. Amends 2.6.0 in place (not yet released).
Pre-existing drop-by surfaced while reviewing the liveness-signal work.
There was a problem hiding this comment.
Pull request overview
Adds an explicit broker-connection edge callback API to the MQTT client and tightens snapshot semantics so consumers can reliably detect panel offline/online transitions (including preventing stale post-disconnect snapshot dispatch).
Changes:
- Add
SpanMqttClient.register_connection_callback()(also added toSpanPanelClientProtocol) and implement edge-only connection event fan-out with duplicate suppression. - Make
get_snapshot()raiseSpanPanelStaleDataErrorwhen the client is not fully live; guard_dispatch_snapshot()with the same liveness predicate and cancel pending debounce timers on disconnect. - Bump version to
2.6.0and update docs/changelog plus comprehensive tests for the new behavior.
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| uv.lock | Updates locked project version to 2.6.0. |
| pyproject.toml | Bumps package version to 2.6.0. |
| src/span_panel_api/mqtt/client.py | Implements connection callback API, liveness-guarded get_snapshot(), and stale snapshot dispatch prevention. |
| src/span_panel_api/protocol.py | Expands SpanPanelClientProtocol to include register_connection_callback. |
| src/span_panel_api/exceptions.py | Adds SpanPanelStaleDataError to the exception hierarchy. |
| src/span_panel_api/init.py | Exports SpanPanelStaleDataError from the top-level package. |
| README.md | Documents connection state monitoring and updated error handling semantics. |
| CHANGELOG.md | Adds 2.6.0 entry documenting the new API, behavioral change, and fix. |
| tests/test_mqtt_homie.py | Adjusts tests for the new get_snapshot() liveness requirement by stubbing a connected bridge. |
| tests/test_mqtt_client_connection.py | New tests for connection callback dispatch semantics, get_snapshot liveness guards, close behavior, and stale dispatch prevention. |
| tests/test_exceptions.py | New tests for SpanPanelStaleDataError inheritance/distinction. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
… docstring Copilot PR review flagged the 'call ping()' wording as misleading since ping() is an async method. Changed to 'await ping()' to make the async invocation explicit.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a broker-connection callback API to SpanMqttClient and makes get_snapshot() raise when the client is not fully live. Integrations (notably Home
Assistant) can now detect panel offline/online transitions with sub-second latency instead of depending on a fallback poll interval. Part of the work
that unblocks the "panel status shows Connected when panel is offline" bug on the integration side.
What's new
synthetic call at registration time. Returns an idempotent unregister function. Added to SpanPanelClientProtocol.
hierarchy.
Breaking change
silently returned a snapshot built from whatever the accumulator happened to hold, which made offline panels indistinguishable from online ones.
Consumers with a broad except SpanPanelError or except Exception branch already handle this correctly.
Bug fix
snapshot from the still-ready() accumulator (panel died without publishing $state=disconnected) and dispatching it to push-streaming subscribers. This
caused integrations to flip panels back to "online" while the bridge was still down. Fixed by (1) cancelling the pending timer in
_on_connection_change(False) and (2) gating _dispatch_snapshot with the same liveness predicate as get_snapshot(). Discovered via live testing after
the callback + exception work landed.
Test plan
resubscribe + callback, resubscribe-on-duplicate-True regression lock
timer
Version
2.6.0 — minor bump (additive API + breaking contract on get_snapshot()). CHANGELOG.md has the full entry.