Files
trikkeSensors/docs/ble-transport-v2.md

6.3 KiB

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.