Add reliable Android BLE ride recorder
This commit is contained in:
@@ -12,9 +12,10 @@ This milestone does four things:
|
||||
4. Maps both sensors into a shared enclosure frame and carries the metadata
|
||||
needed to derive calibrated readings without replacing raw data.
|
||||
|
||||
The wired sensor path is proven. This milestone adds the first reliable BLE
|
||||
transport and a macOS-compatible reference capture client; phone-side storage
|
||||
remains the next consumer implementation.
|
||||
The wired sensor path and reliable BLE transport are proven. The repository now
|
||||
also contains an Android 12+ prototype recorder that stores the authoritative
|
||||
binary stream durably before acknowledging each frame. The macOS reference
|
||||
client remains available for transport diagnosis.
|
||||
|
||||
## Wiring
|
||||
|
||||
@@ -70,33 +71,59 @@ Future fusion, integration, and filtering must derive each `dt` from those
|
||||
timestamps rather than assume a uniform 10 ms interval. Sensor DRDY/FIFO
|
||||
acquisition remains the later refinement for reducing the jitter itself.
|
||||
|
||||
The ESP32-C3 schedules nominal 100 Hz polling in a dedicated acquisition task,
|
||||
but each
|
||||
sensor has an independent internal
|
||||
The 200-second Android acceptance capture provides the current loaded-system
|
||||
characterization. Of 19,983 contiguous intervals, 8,745 (43.8%) were off-grid.
|
||||
Across all intervals, absolute deviation was 0 us median, 40 us p95, 240 us p99,
|
||||
and 1,630 us maximum; among off-grid intervals it was 10 us median, 60 us p95,
|
||||
and 750 us p99. Cumulative error was -726 us. These timestamp statistics are a
|
||||
lower bound on acquisition disturbance because firmware timestamps the poll
|
||||
before performing both I2C reads. Six ADXL345 overrun flags show that preemption
|
||||
between the timestamp and the physical read occasionally crossed a sensor sample
|
||||
boundary even though the recorded timestamp delta did not expose the full delay.
|
||||
The loss is rare (0.03%), explicitly flagged, and reinforces the planned
|
||||
data-ready/FIFO acquisition refinement.
|
||||
|
||||
After the BLE session handshake, the ESP32-C3 schedules nominal 100 Hz polling
|
||||
in a dedicated acquisition task, but each sensor has an independent internal
|
||||
sample clock. The status registers are read immediately before each XYZ read so a
|
||||
consumer can distinguish a fresh sample from a repeated poll and identify gyro
|
||||
overruns. Hardware data-ready interrupts and FIFO acquisition are deferred to the
|
||||
later sensor-side acquisition refinement.
|
||||
|
||||
Completed samples enter a 1024-record RAM queue, providing 10.24 seconds of
|
||||
transport-outage tolerance at 100 Hz when the transport reports backpressure or
|
||||
failure accurately. A failed write retains and retries its packet while this
|
||||
queue accumulates the backlog. A lower-priority output task batches up to eight
|
||||
records into versioned `TRK1` frames, isolating acquisition from brief transport
|
||||
stalls. CRC, packet and sample sequences, timestamps, and cumulative
|
||||
loss/overrun counters make permanent loss detectable by the receiver.
|
||||
Completed samples enter a statically reserved 3072-record RAM queue, providing
|
||||
30.72 seconds of transport-outage tolerance at 100 Hz when the transport reports
|
||||
backpressure or failure accurately. A failed write retains and retries its
|
||||
packet while this queue accumulates the backlog. A lower-priority output task
|
||||
batches up to eight records into versioned `TRK1` frames, isolating acquisition
|
||||
from brief transport stalls. CRC, packet and sample sequences, timestamps, and
|
||||
cumulative loss/overrun counters make permanent loss detectable by the receiver.
|
||||
|
||||
The 30.72-second depth is based on end-to-end phone behavior rather than the
|
||||
visible Bluetooth-off interval alone. A five-second Android Bluetooth outage
|
||||
produced roughly 12.5 seconds of effective transport interruption and overflowed
|
||||
the prior 10.24-second queue by 223 samples. Repeating the same test with the
|
||||
3,072-record queue delivered all 19,984 samples contiguously; one replay was
|
||||
durably deduplicated and all loss counters remained zero. The capture and its
|
||||
completed Android sidecar are preserved under `tests/fixtures/`.
|
||||
|
||||
The static queue occupies 96 KiB. The BLE build leaves 132,389 bytes of DRAM for
|
||||
task stacks, NimBLE runtime allocation, and future features; the 200-second
|
||||
acceptance capture proves the present configuration, but another large buffer or
|
||||
memory-heavy feature requires a fresh runtime and build-time memory review.
|
||||
|
||||
The default build exposes a custom NimBLE GATT service named `TrikkeSensor`.
|
||||
Each unchanged `TRK1` frame is fragmented as needed, persisted by the receiver,
|
||||
and then acknowledged by exact packet sequence. A missing ACK causes a replay;
|
||||
disconnect or subscription loss retains the same frame and restarts it from byte
|
||||
zero after reconnection. The receiver deduplicates these deliberate replays.
|
||||
Acquisition remains stopped until a valid session-token handshake establishes a
|
||||
clean capture boundary. Each unchanged `TRK1` frame is then fragmented as needed,
|
||||
persisted by the receiver, and acknowledged by exact packet sequence. A missing
|
||||
ACK causes a replay; disconnect or subscription loss retains the same frame and
|
||||
restarts it from byte zero after a same-token reconnection. The receiver
|
||||
deduplicates these deliberate replays.
|
||||
|
||||
The preserved USB telemetry option uses ESP-IDF's interrupt-driven USB
|
||||
Serial/JTAG driver behind
|
||||
a transport-neutral state machine. A complete frame is submitted atomically to
|
||||
the driver ring and remains pending across bounded drain timeouts; firmware does
|
||||
not resubmit it ambiguously or dequeue another frame. The 1024-sample queue
|
||||
not resubmit it ambiguously or dequeue another frame. The 3072-sample queue
|
||||
therefore also protects a connected endpoint that temporarily stops draining.
|
||||
|
||||
USB drain confirms that bytes left the device endpoint, not that the capture
|
||||
@@ -141,7 +168,29 @@ python3 tools/capture_ble.py
|
||||
|
||||
The client scans for `TrikkeSensor`, stores only complete CRC-valid `TRK1`
|
||||
frames, flushes the binary and CSV outputs, and only then writes the application
|
||||
ACK. See [the BLE transport specification](docs/ble-transport-v1.md).
|
||||
ACK. See [the BLE transport specification](docs/ble-transport-v2.md).
|
||||
|
||||
## Android recorder
|
||||
|
||||
The `android/` project is the prototype ride recorder. Each new recording sends
|
||||
an idempotent session token that gives the C3 a repeatable software session
|
||||
boundary with empty queues, reset counters, and acquisition starting only after
|
||||
the phone is ready. Reconnects reuse that token and preserve the outage backlog.
|
||||
The app reassembles and validates complete frames, appends each new frame to
|
||||
app-private storage, calls `fsync`, and only then sends `ACK1`. Exact replays are
|
||||
acknowledged without being appended twice. Recording runs in a connected-device
|
||||
foreground service with a partial wake lock so it can continue while the screen
|
||||
is off.
|
||||
|
||||
Build its debug APK with:
|
||||
|
||||
```sh
|
||||
cd android
|
||||
./gradlew testDebugUnitTest lintDebug assembleDebug
|
||||
```
|
||||
|
||||
See [the Android recorder guide](android/README.md) for installation, capture,
|
||||
export, and the first coordinated phone test.
|
||||
|
||||
## TRK1 output and USB validation
|
||||
|
||||
|
||||
Reference in New Issue
Block a user