120 lines
6.3 KiB
Markdown
120 lines
6.3 KiB
Markdown
# 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.
|