125 lines
5.4 KiB
Markdown
125 lines
5.4 KiB
Markdown
# Trikke Recorder for Android
|
||
|
||
Prototype v0.2 records the existing reliable BLE transport to an authoritative
|
||
`.trk` file on an Android phone. It deliberately does not filter, fuse, convert,
|
||
or upload telemetry.
|
||
|
||
The permanent Android application ID and Kotlin namespace are
|
||
`com.jsjdesigns.trikkerecorder`.
|
||
|
||
## Platform and behavior
|
||
|
||
- Android 12 (API 31) or newer
|
||
- BLE central connection to the `TrikkeSensor` service
|
||
- User-started connected-device foreground service
|
||
- Partial wake lock during an active recording
|
||
- App-private capture storage with explicit export through Android's document UI
|
||
- A `.session.json` sidecar containing phone wall/monotonic clock anchors and
|
||
final integrity counters
|
||
|
||
Every notification is reassembled using its packet sequence, offset, and total
|
||
size. A complete frame must also pass the TRK1 shape and CRC checks. For a new
|
||
frame the recorder then performs this ordering:
|
||
|
||
1. Append the unchanged frame to the `.trk` file.
|
||
2. Flush the stream and synchronize its file descriptor.
|
||
3. Update in-memory integrity state.
|
||
4. Write the exact `ACK1` packet sequence to the C3.
|
||
|
||
If the ACK is lost, the firmware replays the frame. An exact replay of the last
|
||
persisted packet is not appended again, but it is acknowledged again. The app
|
||
refuses to acknowledge a CRC-invalid frame or the same sequence carrying
|
||
different bytes.
|
||
|
||
At Start, the app generates a random session token and sends it to the Control
|
||
characteristic before subscribing. A new token makes the C3 perform one
|
||
controlled software restart. The app reconnects with the same token, after which
|
||
the C3 starts acquisition with empty queues and zeroed counters. Temporary BLE
|
||
reconnects during that recording reuse the token and therefore preserve queued
|
||
samples instead of resetting the session.
|
||
|
||
The validated phone/C3 pair takes about 10.8 seconds from tapping Start to the
|
||
first persisted frame because a new session deliberately includes a controlled
|
||
C3 restart and two connection passes. A panic, watchdog, brownout, or power-on
|
||
reset during recording invalidates the retained token; recovery adds another
|
||
controlled restart, and the resulting packet/sample sequence reset remains
|
||
visible in the sidecar integrity counters.
|
||
|
||
## Build and install
|
||
|
||
Android Studio is the easiest route: open the `android/` directory, allow the
|
||
Gradle sync, select the phone, and run the `app` configuration.
|
||
|
||
The command-line equivalent on this Mac is:
|
||
|
||
```sh
|
||
cd android
|
||
export JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home"
|
||
export ANDROID_HOME="$HOME/Library/Android/sdk"
|
||
./gradlew testDebugUnitTest lintDebug assembleDebug
|
||
"$ANDROID_HOME/platform-tools/adb" install -r app/build/outputs/apk/debug/app-debug.apk
|
||
```
|
||
|
||
The tests exercise the Android parser against the real
|
||
`ble_mtu_race_4bf00eb.trk` hardware fixture as well as fragment replay, malformed
|
||
ordering, CRC rejection, unsigned sequence wrap, durable append, and replay
|
||
deduplication.
|
||
|
||
## Record and export
|
||
|
||
1. Power the sensor and open **Trikke Recorder**.
|
||
2. Tap **Start recording** and grant Nearby Devices and notification permission.
|
||
3. Wait for `Recording: Connected and subscribed` and confirm that the sample
|
||
count is increasing.
|
||
4. The activity may be left, the screen may be locked, and the phone may be put
|
||
in a pocket. Keep the persistent recording notification active.
|
||
5. Reopen the app and tap **Stop and close safely**.
|
||
6. After the status reaches `Stopped`, tap **Export last .trk** and select a
|
||
destination.
|
||
|
||
The app-private `.session.json` starts with `complete: false`. A normal Stop
|
||
closes and synchronizes the binary file, then rewrites the sidecar with
|
||
`complete: true`, final counts, and first/last phone-to-device clock anchors. If
|
||
Android or the user force-stops the process, every previously acknowledged frame
|
||
remains in the `.trk` file, while the incomplete sidecar makes the abnormal end
|
||
visible.
|
||
|
||
Phone receipt time is only an alignment anchor; BLE delivery latency means it is
|
||
not the sensor's physical sample time. Analysis must continue to use the device
|
||
timestamps carried by each TRK1 frame.
|
||
|
||
## First coordinated acceptance test
|
||
|
||
Do this before a ride:
|
||
|
||
1. Record for two minutes with the enclosure flat and still.
|
||
2. Lock the phone for at least one minute and verify the sample counter resumes
|
||
visibly when the app is reopened.
|
||
3. Cause a roughly three-second outage by switching Bluetooth off, switch it
|
||
back on, and wait for `Recording` again.
|
||
4. Stop and export the `.trk` file.
|
||
5. Decode it from the repository root:
|
||
|
||
```sh
|
||
python3 tools/decode_binary.py ride_YYYYMMDD_HHMMSS.trk ride.csv
|
||
```
|
||
|
||
Acceptance requires a valid complete decode, no unexplained packet or sample
|
||
gaps, and no queue overflow for the short interruption. Duplicate replays and
|
||
the firmware disconnect/replay counters may increase and are expected.
|
||
|
||
After that passes, repeat for 15–30 minutes with the screen locked and include
|
||
several short real-world range/interference interruptions.
|
||
|
||
## Prototype limitations
|
||
|
||
- The BLE service is still unauthenticated and single-connection, as documented
|
||
in `docs/ble-transport-v2.md`.
|
||
- The recorder supports one active session and one known sensor.
|
||
- Only the binary capture is exported from the UI in this pass. The sidecar is
|
||
retained app-private for diagnosis.
|
||
- A phone force-stop cannot run cleanup code. The persisted binary prefix remains
|
||
useful, but the session is intentionally marked incomplete.
|
||
- No GPS, Samsung Health import, CSV rendering, or live motion analysis is in
|
||
this milestone.
|