3.2 KiB
Reliable BLE Transport — Version 1
BLE carries the unchanged, CRC-protected TRK1 frames defined in
binary-record-v1.md. The default peripheral name is TrikkeSensor.
GATT service
| Purpose | UUID | Properties |
|---|---|---|
| Service | 7d2ea000-f75b-4a9b-8fbe-3d4c2a1e9c10 |
Primary service |
| Data | 7d2ea000-f75b-4a9b-8fbe-3d4c2a1e9c11 |
Notify |
| ACK | 7d2ea000-f75b-4a9b-8fbe-3d4c2a1e9c12 |
Write, write without response |
The firmware prefers a 256-byte ATT MTU, allowing the largest 196-byte TRK1
frame and its eight-byte BLE envelope to fit in one notification. Smaller MTUs
remain protocol-compatible; firmware sends at most eight fragments per bounded
poll, although sustained 100 Hz delivery still depends on the negotiated link.
Data notification envelope
Every notification starts with an eight-byte little-endian envelope:
| Offset | Size | Field |
|---|---|---|
| 0 | 4 | TRK1 packet sequence |
| 4 | 2 | Byte offset within the complete TRK1 frame |
| 6 | 2 | Complete TRK1 frame size |
| 8 | remaining | Consecutive frame bytes at that offset |
Offset zero starts or restarts a frame. A receiver appends only consecutive
offsets for the same sequence and total size, then validates the complete
TRK1 header and CRC. A malformed or missing fragment is not acknowledged.
Application ACK and replay
After validating and persisting a frame, the receiver writes exactly eight bytes
to the ACK characteristic: ASCII ACK1, then the acknowledged packet sequence
as little-endian uint32. Firmware accepts an ACK only for the frame it currently
owns and only from the active subscribed connection.
COMPLETE is not reported to the output task until that ACK arrives. Until then:
- a one-second ACK timeout replays the frame from offset zero;
- disconnect or notification unsubscription preserves the frame;
- the next subscription replays it from offset zero;
- BLE polling returns
PENDING, neverRETRY, after ownership begins.
The ACK itself can be lost after the receiver persisted the frame. Receivers
therefore compare the sequence and raw bytes with their last persisted frame,
avoid writing a duplicate, and ACK the replay again. The reference
tools/capture_ble.py implements this ordering.
The rejected-ACK counter is diagnostic, not a pure corruption count. A valid duplicate ACK can arrive after the output task has already completed that frame and begun the next one; firmware then rejects and counts the now-stale write.
BLE notification success only means the fragment entered the stack. The ACK1
write is the end-to-end boundary. It deliberately confirms application
persistence rather than radio or ATT delivery alone.
Version 1 is an unauthenticated, single-connection prototype service. It does not yet provide pairing, authorization, or confidentiality against a nearby peer; those are separate from the loss/replay guarantees above. A nearby peer can also deny availability by subscribing and never acknowledging: firmware correctly retains and replays the owned frame, but the RAM queue eventually fills while the legitimate receiver remains excluded. Pairing and connection authorization are required before treating this as a hostile-environment logger.