add reliable BLE telemetry transport
This commit is contained in:
@@ -11,7 +11,7 @@ combined by an underlying byte transport.
|
||||
| ---: | ---: | --- |
|
||||
| 0 | 4 | ASCII magic `TRK1` |
|
||||
| 4 | 1 | Wire version (`1`) |
|
||||
| 5 | 1 | Packet type: metadata `1`, samples `2` |
|
||||
| 5 | 1 | Packet type: metadata `1`, samples `2`, status `3` |
|
||||
| 6 | 1 | Header size (`36`) |
|
||||
| 7 | 1 | Record size (`0` or `20`) |
|
||||
| 8 | 1 | Record count (`0` or 1–8) |
|
||||
@@ -51,7 +51,11 @@ timestamp is reconstructed by cumulatively adding its delta. Firmware ends the
|
||||
current packet before a delta exceeds the representable 655.35 ms range, making
|
||||
the next sample the exact base timestamp of a new packet. As a defensive encoder
|
||||
fallback, an unrepresentable delta is stored as `0xFFFF` and sets packet flag bit
|
||||
0. Sample sequence gaps remain detectable independently.
|
||||
0. Because intra-packet deltas are rounded to 10 us while each packet base keeps
|
||||
the exact ESP timer value, an integrity check for an exact 10,000 us interval can
|
||||
report up to +/-5 us at packet boundaries; changing the transport's packet size
|
||||
changes how often that harmless quantization boundary appears. Sample sequence
|
||||
gaps remain detectable independently.
|
||||
|
||||
Mapped raw counts are authoritative. The original sensor-native axes can be
|
||||
reconstructed because the mappings are lossless:
|
||||
@@ -76,6 +80,28 @@ A byte-stream receiver may begin inside an incomplete frame. It discards bytes
|
||||
until a magic/header/CRC combination validates. Host tools report any rejection
|
||||
before that first valid frame separately from CRC failures after synchronization.
|
||||
|
||||
## Status payload (32 bytes)
|
||||
|
||||
Status frames use packet type `3`, record size/count zero, and payload version
|
||||
`1`. They are emitted at startup and approximately every five seconds. Offsets
|
||||
0 and 2 are uint16 payload version and payload size; the remaining fields are
|
||||
cumulative uint32 counters:
|
||||
|
||||
| Offset | Field |
|
||||
| ---: | --- |
|
||||
| 4 | Sensor read failures |
|
||||
| 8 | Sample-queue overflows |
|
||||
| 12 | Initial transport submissions that accepted zero bytes |
|
||||
| 16 | BLE disconnects after a connection was established |
|
||||
| 20 | BLE notification enqueue/send failures |
|
||||
| 24 | BLE frame replays after disconnect, subscription change, or ACK timeout |
|
||||
| 28 | Malformed, stale, premature, or wrong-connection ACK writes |
|
||||
|
||||
The header's cumulative dropped-sample count remains the sum of sensor read
|
||||
failures and queue overflows, preserving version-1 receiver compatibility while
|
||||
the status payload makes the causes independently observable. USB-specific BLE
|
||||
counters remain zero.
|
||||
|
||||
## Buffering
|
||||
|
||||
Acquisition runs in a dedicated higher-priority task and writes complete samples
|
||||
@@ -106,9 +132,9 @@ USB drain is not end-to-end application delivery confirmation. A host process
|
||||
may attach after earlier frames have already left the endpoint, or fail after
|
||||
the endpoint accepts them. CRC and sequence checks make resulting loss visible,
|
||||
but an application acknowledgement and replay window are still required to
|
||||
guarantee receipt. Accordingly, USB `COMPLETE` means endpoint drain, while the
|
||||
planned reliable BLE backend will reserve `COMPLETE` for an application ACK of
|
||||
the exact frame.
|
||||
guarantee receipt. Accordingly, USB `COMPLETE` means endpoint drain, while
|
||||
reliable BLE reserves `COMPLETE` for an application ACK of the exact frame. See
|
||||
`ble-transport-v1.md` for fragmentation, replay, and UUIDs.
|
||||
|
||||
Receivers report bytes left in an incomplete trailing frame when capture ends.
|
||||
Those bytes cannot pass CRC validation and are not silently admitted as samples.
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# 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`, 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. The reference
|
||||
`tools/capture_ble.py` implements this ordering.
|
||||
|
||||
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.
|
||||
Reference in New Issue
Block a user