Skip to content

fix(android): retry CAN subscribe dropped by GATT-busy race - #200

Closed
dkneeland wants to merge 1 commit into
mainfrom
fix/can-subscribe-gatt-race
Closed

fix(android): retry CAN subscribe dropped by GATT-busy race#200
dkneeland wants to merge 1 commit into
mainfrom
fix/can-subscribe-gatt-race

Conversation

@dkneeland

@dkneeland dkneeland commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

What

The app can silently fail to subscribe to the CAN characteristic, which leaves a connected phone with no CAN stream and can stall onboarding at "Pairing" whenever a bus has no data (bench test, or a car that's partially sleeping).

Root cause

When services are discovered, the app enables the CAN and Tesla-status characteristics back-to-back. Android's GATT client allows only one operation in flight, so the second writeDescriptor() call is refused. The old code ignored writeDescriptor()'s return value and logged "Subscribed" unconditionally, so the dropped CAN subscribe looked like a success and no CAN notifications ever arrived.

This is a latent bug in the existing CAN subscription path — the app-channel/Tesla Phase 4 work didn't touch this code or introduce it.

Fix

In DashKitDataSource:

  • Check the writeDescriptor() return value instead of discarding it.
  • Retry the CAN subscribe (3 attempts, 250 ms apart) when the write isn't queued, so it lands once the other subscribe finishing frees the GATT client.
  • Log only the failure path ("write not queued, retrying") and a missing-CCCD error, so a regression is visible in logcat without adding per-connect noise.

Verification

On the bench: the first attempt is refused (queued=false), the retry queues successfully, the firmware reports "Notifications enabled", and the dashboard renders live data (SOC, odometer).

Why: onboarding/Connected stalled at Pairing because the app never
actually subscribed to the CAN characteristic. CAN and the Tesla status
char enable back-to-back on service discovery, but Android's GATT client
allows one op in flight, so the second write was refused. The old code
ignored writeDescriptor()'s return value, so the dropped CAN subscribe
looked like a success and no CAN frames arrived. Since Connected is gated
on the first CAN frame, the app hung whenever there was no bus data (bench
or an off car).

Fix: check writeDescriptor()'s return value and retry the CAN subscribe
(3 attempts, 250ms apart) when the write isn't queued, so it lands once the
other subscribe finishes. Log only the failure path and a missing-CCCD
error so a regression is visible without per-connect noise.

Verified: first attempt is refused, retry queues successfully, firmware
reports the CAN subscription, and the dashboard renders live data.
@dkneeland

Copy link
Copy Markdown
Collaborator Author

Closing in favor of a single consolidated Phase 4 PR: the CAN subscribe retry here is patch-identical to the copy on the Phase 4 branch, which also extends the fix to the Tesla status subscription via a shared DashKitBleManager.subscribeWithRetry() helper. The fix lands (as its own commit) in that PR - link to follow.

@dkneeland

Copy link
Copy Markdown
Collaborator Author

Superseded by #201, which carries this fix (as its own commit) and extends it to the Tesla status subscription via a shared subscribeWithRetry() helper.

dkneeland added a commit that referenced this pull request Aug 24, 2026
Android's GATT client allows only one operation in flight, and the app
subscribes to two characteristics back-to-back when services become
ready (CAN + Tesla status). The second CCCD write could be refused while
the first was pending, and writeDescriptor()'s return value was ignored,
so the dropped subscribe looked like success: no CAN frames arrived
(Connected is gated on the first CAN frame, so onboarding stalled at
Pairing), or the status tile went stale with no error.

Check writeDescriptor()'s return value and retry (3 attempts, 250 ms
apart) via a shared DashKitBleManager.subscribeWithRetry(), now used by
both the CAN datasource and the Tesla status source.

Supersedes #200.
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