Skip to content

feat(android): assemble vehicle VIN from DashKit CAN stream (1/3) - #206

Closed
dkneeland wants to merge 2 commits into
mainfrom
feat/vin-pipeline
Closed

feat(android): assemble vehicle VIN from DashKit CAN stream (1/3)#206
dkneeland wants to merge 2 commits into
mainfrom
feat/vin-pipeline

Conversation

@dkneeland

@dkneeland dkneeland commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Part 1 of 3 of a stacked series that supersedes #201 (closed; work redistributed across this stack).

What & why

Reads the vehicle VIN directly from DashKit's existing BLE CAN notification stream so Tesla enrollment can stop asking the user to type it.

  • VehicleVinAssembler — pure-Kotlin assembler for the muxed VIN frames (bus 1, CAN id 0x405, mux 0x10/0x11/0x12). Accepts segments in any order, tolerates duplicates, rejects short frames / unknown mux / non-ASCII / illegal VIN chars, emits only when all 17 chars validate, resets on teardown. Raw ASCII bytes are joined directly — no 56-bit DBC Double round-trip.
  • CanPacket — extracted raw (bus, address, data) tuple parsing shared by dashboard decoding and the assembler, with the firmware build_ble_packet wire format documented alongside the parser.
  • DashKitDataSource feeds the assembler per frame; exposes vinState.
  • ConnectionViewModel.vehicleVin — dedicated replaying flow; deliberately not added to CarState (stays out of the dash-app data contract); reset during connection teardown.
  • DashKitBleManager.subscribeWithRetry() — generic CCCD-write retry helper shared by the CAN and Tesla status subscriptions (GATT-busy race fix, moved here from the closed PR since the data source needs it).

No user-visible behavior change; additive and inert until Part 3 consumes the flow.

Tests

Assembler unit tests (ordering, duplicates, special mux-A offsets, malformed input) and data-source integration tests (single/multi-notification assembly, batching, truncated packets, unchanged CarState decoding). Full testDebugUnitTest green at carve time against main @ b506147.

Stack & merge order

Order PR Base
1 this PR main
2 Tesla BLE core branch of this PR
3 VIN-driven enrollment UX branch of Part 2

Squash-merge this first. The next PR's base will retarget once this branch is deleted — click Update branch there so its diff collapses to just its own changes (repo uses squash merges).

@dkneeland

Copy link
Copy Markdown
Collaborator Author

ℹ️ Reading the DashKitDataSource.kt diff

The per-file stats (+27/−51) make this file look like logic was removed. It wasn't — most of those deletions are relocations and one deliberate swap, which git bills entirely to this file:

Deleted lines What actually happened
~20 lines — the inline CAN wire-format parsing loop Moved verbatim to parseCanPacket() in the new CanPacket.kt (needed as a shared seam so each raw frame now feeds two consumers: the dashboard decoder and the VIN assembler). Byte-for-byte same bounds checks, offsets, and LE decode.
~13 lines — direct CCCD descriptor write in onServicesReady() Replaced by manager.subscribeWithRetry(...) (DashKitBleManager.kt, +41 in this PR). The old write ignored failures; Android silently refuses CCCD writes that race another GATT operation, which caused intermittent dropped CAN subscriptions. The helper retries. This is the only behavioral change in the file, and it's a bug fix.
~8 lines — imports + stale CCCD_UUID / hardware-filtering comment Follow the two moves above; the comment described code that no longer exists here.

What the additions (+27) actually are: VIN wiring only — vinState flow, assembler feed inside the (unchanged) frame loop, resetVin() on session teardown boundaries, and the retry-helper call site.

Net effect on existing functionality: none. Frame extraction is line-identical logic, decodeFrame runs in the same order on the same data, emission cadence (sample(40), single tryEmit per notification) is untouched, and VIN stays out of CarState. The relocated parser is unit-tested directly in CanPacketVinTest (ordering, batching, truncated packets).

Suggested review approach: skim this file for the VIN wiring, then review CanPacket.kt as if the deleted block had been pasted there (it essentially was).

Android's GATT client allows one operation in flight; a CCCD write
that races another subscribe is silently refused, leaving CAN
notifications dead until reconnect. Replace the fire-and-forget
descriptor write in DashKitDataSource with DashKitBleManager
.subscribeWithRetry(), which verifies the write was queued and retries
(up to 3x, 250ms apart). The helper is shared and will also back the
Tesla status subscription.
Add VehicleVinAssembler, a pure-Kotlin assembler for the muxed VIN
frames (bus 1, CAN id 0x405) carried by the existing DashKit BLE CAN
notification stream. Extract CanPacket parsing so raw (bus, address,
data) tuples feed both dashboard decoding and the assembler, with the
firmware build_ble_packet wire format documented alongside the parser;
subscription moves to subscribeWithRetry to survive GATT-busy CCCD
races.

Expose ConnectionViewModel.vehicleVin as a dedicated replaying state
flow; keep VIN out of CarState and reset it during connection teardown.

Includes parser and assembler unit tests (ordering, duplicates,
batching, malformed packets).
@dkneeland

Copy link
Copy Markdown
Collaborator Author

Superseded by the re-layered 4-PR stack - the GATT-subscribe-retry fix was extracted into its own standalone bottom layer so it can ship independently of feature review:

Merge order: 210 -> 211 -> 212 -> 213. Trees of layers 2-4 are byte-identical to this PR's reviewed content (only ancestry changed); layer diffs verified per-layer.

@dkneeland dkneeland closed this Aug 25, 2026
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