feat: add BLE connection status events - #110
Conversation
Fans genuine BLE session state changes (bleak's disconnected_callback plus a completed connect()) out to registered listen_connection_status callbacks, mirroring BroadcastListeners' listen_* idiom, so consumers get connect/disconnect pushes instead of polling is_connected.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4a6027097d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ) | ||
| await self.client.start_notify(READ_UUID, self._on_notify) | ||
| await self._start_keepalive() | ||
| self._set_connected(True) |
There was a problem hiding this comment.
Reset connection state when a repeated connect fails
When connect() is called while a session is already marked connected and the new establish_connection() or start_notify() attempt fails, the exception path clears self.client and may disconnect the old client but never calls _set_connected(False); the disconnect callback is also ignored because self.client was cleared first. Consequently listeners receive no loss event, and a later successful connection emits no True event because _connected remains stuck at True. Reset the tracked state in the failed-connect cleanup (or avoid replacing an active session).
AGENTS.md reference: AGENTS.md:L135-L135
Useful? React with 👍 / 👎.
Intent
Add a BLE connection-status event to VehicleBluetooth so consumers get connect/disconnect pushes instead of polling is_connected. This is library-first groundwork for the Home Assistant teslemetry BLE stack (D9-1), which will drop its 5-second is_connected watcher and consume this event instead - so the contract must cover what that watcher provided: notification on connection established and on connection lost, including losses detected mid-operation (not just clean disconnects). Implementation mirrors the library's existing BroadcastListeners listen_()/unsubscribe idiom: added listen_connection_status(callback) to VehicleBluetooth (tesla_fleet_api/tesla/vehicle/bluetooth.py), fed by two sources - bleak's own disconnected_callback (wired into establish_connection inside connect(), so it fires on an unexpected drop detected mid-operation, not a timer) and a dispatch after connect() successfully subscribes GATT notifications. A single _set_connected(bool) chokepoint dedupes so listeners only fire on an actual state transition, deliberately preventing double-fires across reconnect loops or redundant connect()/disconnect() calls. Reuses BroadcastListeners' _register/_dispatch_callback helpers rather than duplicating them. Scope is deliberately BLE-layer only - no Router or cloud-transport changes, since the HA integration consuming this is a separate downstream effort. Tests added in tests/test_ble_connection_status.py cover: connection established, clean disconnect, an unexpected drop simulated via the bleak disconnected_callback (mid-operation loss), no double-fire across a reconnect loop or redundant connect(), unsubscribe (including idempotent double-unsubscribe), and callback-failure isolation. Documented the new listener in AGENTS.md alongside the existing broadcast-listener entry. Full test suite (559 tests), pyright strict, and ruff all pass; two pre-existing formatting warnings in unrelated test files were left untouched.
What Changed
VehicleBluetooth.listen_connection_status()to emit deduplicated connect and disconnect transitions, including unexpected transport losses detected by bleak.Risk Assessment
✅ Low: The change is well-bounded, satisfies the connection-event contract, and the active-client identity guard durably prevents stale disconnect callbacks from corrupting current session state.
Testing
Alongside the author-reported broad suite, pyright, and ruff baseline, targeted testing exercised all 12 connection-listener tests and an end-user-style callback flow; all passed, the evidence showed the exact
True, False, True, Falsetransition sequence with deduplication and unsubscribe behavior, and the worktree remained unchanged.Evidence: BLE connection-status consumer transcript
Consumer callback sequence: connected → unexpectedly lost → reconnected → cleanly disconnected. Redundant connect was deduplicated and unsubscribe stopped subsequent updates.Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 1 issue found → auto-fixed ✅
tesla_fleet_api/tesla/vehicle/bluetooth.py:678- The required contract says listeners report genuine state transitions and redundant connect/reconnect activity must not produce false events, but this callback ignores theclientargument and always marks the vehicle disconnected. A concrete reachable sequence is: connect client A, call publicconnect()again soself.clientbecomes client B, then client A's delayed disconnect callback fires; listeners receiveFalsewhile B remains connected. Guard callbacks so only the active client can change connection state (for example,if client is self.client), and add a stale-callback regression case using distinct clients.🔧 Fix: Ignore stale BLE disconnect callbacks
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
git diff ced23f426b18e9181246e18cc7214a1202252986..1af63b62c660ef540e46996aface9f88d77f44aa -- tesla_fleet_api/tesla/vehicle/bluetooth.py AGENTS.mduv run pytest -q tests/test_ble_connection_status.pyAPI-level mocked BLE consumer scenario exercising connect, redundant connect, bleakdisconnected_callback, reconnect, clean disconnect, and unsubscribe; transcript captured under/tmp/no-mistakes-evidence/01KZ3203QGDDN6CA8KPG99HXWAgit status --shortto confirm testing left no transient worktree changes✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.