# Reliable BLE Transport — Version 2 BLE carries the unchanged, CRC-protected `TRK1` frames defined in `binary-record-v1.md`. The default peripheral name is `TrikkeSensor`. Version 2 adds a mandatory, idempotent recording-session handshake; the data envelope and application ACK remain unchanged from version 1. ## 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 | | Control | `7d2ea000-f75b-4a9b-8fbe-3d4c2a1e9c13` | Write | 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. ## Recording-session handshake Every new capture generates a random uint64 session token. The reference receivers first write exactly 12 bytes to Control—ASCII `BGN1`, then that token as little-endian uint64—and enable data notifications after that write succeeds. Firmware also accepts the reverse order: it tracks the raw CCCD state separately from session authorization and treats the connection as subscribed as soon as both conditions are true. Notifications never flow before authorization. When the token differs from the C3's active token, firmware stores it in RTC-retained memory and performs a controlled software restart. Acquisition and output tasks remain stopped after boot. The receiver reconnects and repeats the same `BGN1` write; firmware recognizes the retained token, authorizes that BLE connection, starts acquisition with empty queues and zeroed volatile counters, and accepts the subsequent notification subscription. The Android acceptance capture measured 10.797 seconds from the user's Start action to the first durably persisted frame. That includes the initial control write, controlled restart, advertising and scan latency, GATT reconnection, same-token authorization, notification subscription, and first frame delivery. This startup interval is expected and is not part of the recorded sensor stream. The same token must be written on every reconnect during one recording. That write is idempotent: it authorizes the new connection without restarting or discarding the in-flight frame and sample backlog. A different token is an explicit new-session boundary and deliberately discards all prior volatile state through the controlled restart. Firmware does not start acquisition after boot and does not honor a notification subscription until a valid control write authorizes the connection. This avoids pre-session queue overflow and prevents an older client from bypassing the session boundary. The token provides idempotence, not authentication or secrecy. RTC token recovery is intentionally accepted only after `ESP_RST_SW`. A panic, watchdog, brownout, or power-on reset discards the token and all volatile stream state. When the still-recording receiver reconnects and rewrites its unchanged token, firmware treats it as a new token, performs one additional controlled restart, and authorizes the following same-token reconnect. Packet and sample sequences restart at zero inside the receiver's existing file, where the reset is observable through integrity tracking. This favors a known clean state over silently treating an uncontrolled reset as continuation of the old session. ## 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, after the same-token control write, replays it from offset zero; - BLE polling returns `PENDING`, never `RETRY`, 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. Both reference receivers implement 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. The current firmware reserves a 3072-sample acquisition queue, providing 30.72 seconds of transport-outage tolerance at the nominal 100 Hz rate. This interval includes link-loss detection, scanning, GATT reconnection, session reauthorization, notification subscription, and replay—not merely the time a phone's Bluetooth control is visibly off. Longer interruptions remain bounded and detectable through sample-sequence gaps and the queue-overflow counter. Version 2 remains an unauthenticated, single-connection prototype service. It does not yet provide pairing, authorization, or confidentiality against a nearby peer; those are separate from the session and replay guarantees above. A nearby peer can also deny availability by connecting and withholding the control write, or by subscribing and never acknowledging. Pairing and connection authorization are required before treating this as a hostile-environment logger.