Commit graph halkyon/docs/adr/0030-halkyon-train.md
Author SHA1 Message Date
7c5e539df5
docs(train): how the Loop's firmware updates go (ADR 0030, D40)
D10's details: Train checks once a day and when asked; the instance
relays Polar's firmware service for its people, the files a day in
memory, unless its operator sets LOOP_FIRMWARE_RELAY=off; after an
update the band is set up again at once with the figures it had.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 15:52:58 +01:00
2c5467a282
feat(train): the Loop's training sessions as workouts (ADR 0030, D39)
Each read of the band lists the sessions it holds, reads those from the
read's first day on (`LoopDays.kt`, one line per exercise: its start
with the band's offset, length, Polar's sport ID, figures and heart
rate at its interval), and deletes those up to the day kept, as the
days. The app keeps each as a workout of origin Polar Loop with its
heart rate (`takeLoopSessions`), Polar's running and cycling as
Train's and every other sport as the new `Activity.other`, which a
workout's edit sheet now changes; a session overlapping a workout Train
recorded gives it its heart rate where it has none instead. The
sessions taken are remembered in `loop.json`, so one deleted in Train
isn't brought back. Health Connect writes the Loop's workouts as
Train's own.

The SDK gives sessions as its protobuf messages: protobuf-javalite
3.20.0, the SDK's own, is named for compiling only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 15:52:58 +01:00
06cc39025b
docs(train): the Loop's training sessions are workouts (ADR 0030, D39)
The sessions the band detects become sealed workouts with their heart
rate, origin Polar Loop; Train gains an activity Other workout; one
overlapping a recorded workout gives it its heart rate; deleted on the
band once sealed, as the days are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 15:52:57 +01:00
5250f32329
feat(train): Days, the Loop's days and nights kept sealed (ADR 0030, D37, D38)
What the band read brings, from the bridge or the background job's
file (`LoopLines`), is kept by `DayRepository`: one sealed `day` and
`night` a date and the day's heart rate in chunks of three hours, every
ID made from the person and the date, a later read replacing what it
brought and keeping the rest, nothing rewritten unchanged; versioned,
gzipped JSON (`day_payload.dart`). `PolarLoop` reads the band as each
connection is ready (every half hour at most, or when asked), takes in
the background file as the app opens, and remembers up to which day
the days are sealed, which the next read then deletes on the band;
it keeps the background job on the band and from the right day, and
`Let the band go` stops it.

Home gains a Days tab, each day with its steps, active time, sleep and
Nightly Recharge, pulled to read the band; a day's page shows its
figures, its time in each class, its heart rate, the night's phases
and rating, Nightly Recharge and skin temperature.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 15:52:57 +01:00
24de3c0a4e
docs(train): what Train keeps is deleted on the band (ADR 0030, D38)
Polar's sync guideline asks apps to remove what they synced, so D38
revises D37's third point: once the app has sealed a day (yesterday or
earlier), the next read deletes it on the band type by type; the
background job deletes only what the app confirmed kept; today never.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 15:52:56 +01:00
ab627efe2c
docs(train): the Loop's days and nights (ADR 0030, D37)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 15:52:56 +01:00
cd72cf6ccc
feat(train): setting up and keeping the Polar Loop (ADR 0030, D34–D36)
On Android, the Loop through Polar's BLE SDK 8.3.0 (from JitPack, asked
for Polar's group alone, its files pinned by SHA-256 in Gradle's
verification metadata): LoopBridge.kt over app.halkyon.train/loop, the
first-time use's figures mapped and checked in LoopSetup.kt, tested on
the JVM. PolarLoop per person remembers the band in loop.json, connects
while the app is open, sets its clock at each connection, keeps what it
tells (battery, charging, firmware), offers a changed profile and sends
it only when asked (D35: first-time use writes a new user identifier
each time), and lets the band go by a factory reset after asking. The
setup sheet finds Loops nearest first and asks only for what the
profile lacks; Polar's defaults (60, 220 less the age, a VO2max from
Polar's fitness tables) go to the band alone.

Polar's SDK is not free software (D36, correcting D32): the app's AGPL
gains a section 7 permission for linking it, LICENSE.md names it, and
its licence and bundled notices join the licence list on Android.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 15:52:55 +01:00
2acc9fc6b6
docs(train): setting up and keeping the Loop (ADR 0030, D34)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 15:52:55 +01:00
5ace60c50b
docs(train): the body's figures in a sealed, synced profile (ADR 0030, D33)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 15:52:54 +01:00
e62cf7695f
docs(train): the Loop's own days first (ADR 0030, D32)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 15:52:54 +01:00
8fb2dbf35e
feat(train): other apps' workouts read from Health Connect (ADR 0030, D31)
Read from other apps, off until turned on, asks for reading with the
history; the apps that wrote sessions are listed, each off. One turned
on brings its sessions, earlier ones at once and later ones through the
changes token as the app opens and comes to the front: each a workout
of Train's own store, sealed and synced, its origin in its payload, its
heart rate, pauses and a row's power as series, named by its app in the
list and on its page. A double of a workout Train has brings at most its
heart rate; one deleted in Train stays out; a track comes only when
asked for on the page, Health Connect asking for consent. What came from
Health Connect is never written back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 15:52:53 +01:00
09f61c3b0a
feat(train): workouts written to Health Connect (ADR 0030, D31)
A Health Connect row in the account sheet opens its screen: Write my
workouts, off until turned on, Health Connect asking as it is. Then
every workout this phone holds is written there (its session with its
pauses and route, heart rate, speed and the rower's watts as samples,
distance and elevation gained), and written again or deleted as it
changes, here or through a sync, and as the app opens. Client IDs come
from the workout's ID, a version counts up with each change; what was
written is kept in the person's health_connect.json, never synced. A
permission taken back in Health Connect turns writing off and says so;
Health Connect's link to Train's rationale opens a screen of its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 15:52:53 +01:00
d1dc833aa6
docs(train): Health Connect, written and read (ADR 0030, D31)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 15:52:52 +01:00
2fae7e4f3b
docs(train): Health Connect through Kotlin of our own (ADR 0030, D30)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 15:52:52 +01:00
0dbe81b336
docs(train): Health Connect before the Loop (ADR 0030, D29)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 15:52:52 +01:00
f3d08e4e69
feat(train): rowing on the WP 5000 over FTMS (ADR 0030, D28)
Rowing indoors is offered again, with the rower chosen on the Record
sheet: a sheet looks for Fitness Machine Service rowers, and the one
chosen is remembered in the person's folder. The recorder service
connects to it as to the strap (one NotifyLink for both), parses
FTMS's Rower Data in Kotlin (tested on the JVM on frames written from
the specification and D8's), counts its totals from its first frame,
pauses 5 s after the last stroke, and gets the connectedDevice type. A
row's screen shows its metres, split per 500 m, stroke rate and watts;
its page its average split, stroke rate and power, the highest power,
its strokes, splits per 500 m and charts of the split, stroke rate and
power. Not yet tried on the WP 5000.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 15:52:51 +01:00
de5fd43e06
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>
2026-10-30 15:52:51 +01:00
405577050d
feat(train): the workouts listed, and each one's page with its map (ADR 0030, D22-D24)
Home lists the person's workouts newest first under their months, each
with its activity's icon, its name (else the time of day's and the
activity's), when it started, its distance, moving time and pace or
speed; those this device can't open come last, locked. A workout's page
shows its track on the instance's map source (flutter_map with
vector_map_tiles, pinned at 9.0.0-beta.13), in pieces between its
pauses, start and end marked; its totals with the elevation gained;
its splits per kilometre on foot, per 5 km cycling; pace or speed and
elevation over the distance, drawn by the app's own painter; and what
the map's source sees. Its name and notes are edited there; deleting
asks first and can be undone for a moment from home (halkyon_app's
DelayedDeletions, TrainPerson.deletions).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 15:52:50 +01:00
dd778cb2cd
feat(train): the instance names its map source (ADR 0030, D11, D22)
Train's first endpoint of its own, map.source, names the map workouts
are drawn on, for people signed in only, as a provider's key may be in
its style's address: OpenFreeMap's Liberty style where the operator sets
nothing, else MAP_STYLE_URL (HTTPS only) with MAP_SOURCE_NAME,
MAP_ATTRIBUTION and MAP_ABOUT_URL. A model without a table: no
migration. D22 is recorded in ADR 0030.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 15:52:49 +01:00
7df1cb1740
feat(train): recording a workout on Android (ADR 0030, D13, D16, D19, D21)
RecorderService, a foreground service of the types location and health
started from the open app, takes fixes from LocationManager's GPS and
steps from the step detector, pauses by itself (AutoPause, tested on the
JVM) and appends each sample to the person's recording file, flushed
every five seconds. MainActivity asks for location, notifications and,
for auto-pause on foot, physical activity, never background location
(D19), and bridges the service to the app.

The app follows the file's lines with RecordingBloc and shows the moving
time, the distance, the pace or speed and GPS's state; the notification
shows the time and the distance with Pause or Resume. Stop is held down;
a stopped recording is saved as a workout and its series, or discarded
after asking, and one cut off or left unsaved is offered as the app
opens. Home has the Record button where the device records.

Three choices of the implementation's are recorded under D21 for review:
rowing waits for 5f, auto-pause on foot without steps goes by GPS speed
(below 2 km/h, on above 3 km/h), and Stop's hint shows above the button.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 15:52:49 +01:00
aab25bf932
feat(train): a recording's file read and saved as a workout (ADR 0030, D18, D20)
The recorder's file is text, one sample a line (D18): positions with
their accuracy, heart rates, steps, pauses and the end. Reading it drops
a last line cut short and skips any line it can't read; a file without
its end is a recording cut off, which ends at its last sample.

A recording becomes its workout and series: every fix kept with its
accuracy (D20), the distance through those within 20 m and never across
a pause, the moving time without the pauses (D16), each kind of sample
in chunks of ten minutes (D14). D18 to D20 are recorded in ADR 0030.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 15:52:49 +01:00
ff91f62303
docs(adr): ADR 0030's D13–D17, how Train records
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 15:52:46 +01:00
2fd39352fc
docs(adr): ADR 0030 (Halkyon Train) and ADR 0031 (the apps' icons)
ADR 0030 records what Train does and where its data comes from: running,
cycling and rowing, in the order recording, intervals, routes, music; the
Polar Loop taken over through Polar's BLE SDK; music from Jellyfin;
devices on phones through flutter_reactive_ble; Health Connect both ways
through the health package; every row sealed; the WP 5000 an FTMS rower,
as scanned; no WaterRower USB driver for now; the Loop's firmware from
Polar directly or through the instance; flutter_map, the instance's map
source, BRouter as a sidecar, offline use and working with CoMaps; and
Train's shell after Todo's.

ADR 0031 gives the apps' icons one family: Lucide glyphs fading from
cobalt through a colour between into each app's own, Learn's graduation
cap, Todo's list, Train's bike, Meditate's circle-dot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 15:52:44 +01:00