feat(train): the heart-rate strap, recorded by the service itself (ADR 0030, D25-D27)
The Record sheet gains a Heart rate row: a sheet looks for straps advertising the Heart Rate Service, the nearest first, once nearby devices are allowed, and the one chosen is remembered in the person's folder; it records with every workout unless switched off for one. The recorder service connects to it with Android's own Bluetooth API, no package (D26), parses Heart Rate Measurement in Kotlin (tested on the JVM), writes each rate and the strap's state to the recording file, and asks for a lost strap again for as long as it records. The recording screen shows the heart rate, and "Strap lost" while it is gone; a workout's page shows the heart's average and highest rate, each split's average and its chart. iOS's recording waits for a Mac (D25), after the rower. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
405577050d
commit
de5fd43e06
1 changed files with 1782 additions and 42 deletions
|
|
@ -1,6 +1,6 @@
|
|||
# ADR 0030: Halkyon Train, what it does and where its data comes from
|
||||
|
||||
- **Status:** decided 27–28 Oct 2026, by the developer (D1–D24 below). The shell (D12)
|
||||
- **Status:** decided 27–28 Oct 2026, by the developer (D1–D27 below). The shell (D12)
|
||||
is built; recording (D13–D24) is being built.
|
||||
- **Numbering:** 0025 is held by the federation draft (stashed), 0026–0029 by the .NET
|
||||
rewrite on the local branch `rewrite-dotnet`, abandoned on 27 Oct; Train takes the next
|
||||
|
|
@ -385,6 +385,51 @@ Strava. What was found, 27 Oct:
|
|||
the elevation counts a climb once it passes 3 m above where it last turned, so a
|
||||
steady climb's last metres short of the next 3 are not counted (a 20 m hill reads as
|
||||
18 or 19 m): the simple filter, to be compared with recorded runs.
|
||||
- **D25 iOS recording waits for a Mac** (the developer's; 28 Oct): 5e and 5f come before
|
||||
5d, which moves to the end of step 5. iOS's side (CoreLocation with background
|
||||
location, the pedometer, a Live Activity for the notification) needs Xcode, which runs
|
||||
on macOS alone, and this machine has none; written unbuilt it would look done and not
|
||||
compile, and CI's runner has no Xcode either. The heart-rate strap and the WP 5000 are
|
||||
built and tested on Android here meanwhile.
|
||||
- **D26 The recorder service talks to the strap itself** (recommended, the developer's;
|
||||
28 Oct; over `flutter_reactive_ble` for finding the strap beside it, two stacks touching
|
||||
one strap for one screen's sake, and over D5's Dart alone, whose heart rate would stop
|
||||
where a swipe stops the Flutter engine, D18, while GPS records on): Android's own
|
||||
Bluetooth API in Kotlin, as OpenTracks has it, connects during a recording and writes
|
||||
the `h` lines, so heart rate is recorded as long as positions are; finding and choosing
|
||||
a strap goes through the recorder's channel too, so Train has one Bluetooth stack and
|
||||
no Bluetooth package. This revises D5: its open licence holds, as nothing third-party
|
||||
is left, and the platform's API sits behind `RecorderPlatform` as the library would
|
||||
have behind Train's own interface. The profiles' parsers (Heart Rate Measurement now,
|
||||
FTMS's Rower Data in 5f) are Kotlin, tested on the JVM on recorded frames, and iOS (5d)
|
||||
needs them again in Swift.
|
||||
- **D27 The strap, from choosing it to the workout's page** (the developer's; 28 Oct): the
|
||||
Record sheet's *Heart rate* row names the strap or "No strap" and opens a sheet that
|
||||
looks for devices advertising the Heart Rate Service (0x180D), the nearest first;
|
||||
the one chosen is remembered on the device, in the person's folder (`strap.json`, its
|
||||
address and name, never synced: each phone finds it anew), *Forget strap* letting it
|
||||
go. Nearby devices are asked for as the sheet first looks (D19), declared never to
|
||||
locate (Android 12 on; before, scanning needs location). No bonding: the service
|
||||
connects by address. A remembered strap records with every workout unless switched
|
||||
off on the sheet for one; the recording doesn't wait for it. The recording screen
|
||||
shows the heart rate ("–" while connecting), and "Strap lost" where GPS's state shows
|
||||
while it is gone; the notification stays time and distance, as a rate changing each
|
||||
second would repost it each second. A lost strap is asked for again in the background
|
||||
for as long as the recording runs; the minutes without it stay a gap. Only the rate is
|
||||
kept (`h` lines), not the R-R intervals, until something uses them. The workout's
|
||||
page: the heart's average and highest rate while moving among the totals, each
|
||||
split's average, and its chart over the distance (over the moving time without a
|
||||
track). The track is coloured by heart-rate zones once Train's settings hold the
|
||||
person's maximum rate, as colouring by each workout's own range paints an easy run
|
||||
as red as a race.
|
||||
|
||||
Built so (28 Oct), with choices of mine for the developer to look at: the file gains
|
||||
`strap <time> connecting|connected|lost` lines (the format's version stays 1, as a
|
||||
reader before skipped lines it didn't know); a rate older than 5 s shows as "–"; the
|
||||
first attempt connects directly, falling back on Android's background connection
|
||||
once it fails and after every loss; a sighting stronger than −65 dBm is "close by";
|
||||
the heart-rate chart averages the rates of each stretch (as the pace chart's
|
||||
stretches, or 10 s, at least, over the time). Not yet tried with a real strap.
|
||||
|
||||
## Consequences
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue