diff --git a/README.md b/README.md index a9160dc..f45a8ea 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,17 @@ remains the nominal datasheet value. Mapped raw counts are stored directly, and sensor-native counts are reconstructed losslessly from the documented mapping. No software filtering or sensor fusion is performed yet. -The ESP32-C3 polls at exactly 100 Hz in a dedicated acquisition task, but each +The BLE reconnect fixture measured scheduling jitter without drift: among 2,575 +contiguous sample intervals, 1,293 differed from exactly 10 ms; absolute timing +deviation had a 4 us median, 160 us p95, 170 us p99, and 780 us maximum, while +cumulative error was only 152 us over 25.75 seconds. Bluetooth tasks can preempt +the polling task, but every record carries its actual acquisition timestamp. +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 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 diff --git a/docs/binary-record-v1.md b/docs/binary-record-v1.md index 59d37a8..49bc2d1 100644 --- a/docs/binary-record-v1.md +++ b/docs/binary-record-v1.md @@ -100,7 +100,9 @@ cumulative uint32 counters: 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. +counters remain zero. The rejected-ACK total can also include a harmless valid +duplicate that arrives after its frame has already completed, so a nonzero value +does not by itself prove corruption or a hostile receiver. ## Buffering diff --git a/docs/ble-transport-v1.md b/docs/ble-transport-v1.md index f3751fb..e17e926 100644 --- a/docs/ble-transport-v1.md +++ b/docs/ble-transport-v1.md @@ -50,10 +50,19 @@ 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. +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. 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. +peer; those are separate from the loss/replay guarantees above. A nearby peer +can also deny availability by subscribing and never acknowledging: firmware +correctly retains and replays the owned frame, but the RAM queue eventually +fills while the legitimate receiver remains excluded. Pairing and connection +authorization are required before treating this as a hostile-environment +logger. diff --git a/main/trikke_ble_transport.c b/main/trikke_ble_transport.c index 2120135..d1764bb 100644 --- a/main/trikke_ble_transport.c +++ b/main/trikke_ble_transport.c @@ -340,6 +340,12 @@ static trikke_transport_status_t ble_poll_once(void *context) portEXIT_CRITICAL(&ble->lock); const uint16_t mtu = ble_att_mtu(connection_handle); + if (mtu == 0) { + // The connection can disappear after the locked state snapshot above. + // The GAP callback advances the delivery epoch; retain ownership and + // let a later poll restart the frame after resubscription. + return TRIKKE_TRANSPORT_PENDING; + } if (mtu <= 3 + TRIKKE_BLE_FRAGMENT_HEADER_SIZE) { return TRIKKE_TRANSPORT_FATAL; } diff --git a/tests/fixtures/README.md b/tests/fixtures/README.md index 81c9e90..603cfb2 100644 --- a/tests/fixtures/README.md +++ b/tests/fixtures/README.md @@ -63,6 +63,11 @@ removed before production firmware was built and flashed. final status records one disconnect and one deliberate frame replay, with zero notification failures or invalid ACKs. It validates that the 10.24-second queue covered the measured resubscription interval without permanent loss. + Of its 2,575 contiguous sample intervals, 1,293 differ from exactly 10 ms. + Absolute deviation has a 4 us median, 160 us p95, 170 us p99, and 780 us + maximum; cumulative error is 152 us over 25.75 seconds. This records bounded + BLE scheduling jitter without rate drift and requires timestamp-derived `dt` + in future fusion work. `tests/test_trikke_protocol.py` verifies the hashes, parses the captures in fragmented chunks, and asserts these signatures so the hardware evidence remains