Skip to content

feat: add BLE connection status events - #110

Merged
Bre77 merged 4 commits into
mainfrom
fm/tfa-ble-connection-event
Aug 3, 2026
Merged

feat: add BLE connection status events#110
Bre77 merged 4 commits into
mainfrom
fm/tfa-ble-connection-event

Conversation

@Bre77

@Bre77 Bre77 commented Aug 3, 2026

Copy link
Copy Markdown
Member

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

  • Add VehicleBluetooth.listen_connection_status() to emit deduplicated connect and disconnect transitions, including unexpected transport losses detected by bleak.
  • Ignore stale disconnect callbacks from superseded BLE clients and support persistent, failure-isolated listeners with idempotent unsubscribe.
  • Document the connection-event contract and add regression coverage for connection, loss, reconnection, deduplication, unsubscription, and callback isolation.

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, False transition 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.

{
  "consumer_callback_events": [
    true,
    false,
    true,
    false
  ],
  "meaning": [
    "connected",
    "unexpectedly_lost",
    "reconnected",
    "cleanly_disconnected"
  ],
  "redundant_connect_was_deduped": true,
  "unsubscribe_stopped_updates": true
}

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 the client argument and always marks the vehicle disconnected. A concrete reachable sequence is: connect client A, call public connect() again so self.client becomes client B, then client A's delayed disconnect callback fires; listeners receive False while 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.md
  • uv run pytest -q tests/test_ble_connection_status.py
  • API-level mocked BLE consumer scenario exercising connect, redundant connect, bleak disconnected_callback, reconnect, clean disconnect, and unsubscribe; transcript captured under /tmp/no-mistakes-evidence/01KZ3203QGDDN6CA8KPG99HXWA
  • git status --short to confirm testing left no transient worktree changes
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

firstmate crewmate added 4 commits August 3, 2026 15:35
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.
@Bre77 Bre77 added the fm Opened by a Firstmate crewmate label Aug 3, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@Bre77
Bre77 merged commit 695b5aa into main Aug 3, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fm Opened by a Firstmate crewmate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant