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>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-28 16:20:37 +01:00
commit f3d08e4e69
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -26,16 +26,18 @@ class FakeRecorderPlatform({
Activity activity,
AutoPauseBy? autoPause,
String? strap,
String? rower,
})
>[];
/// The straps nearby, seen as soon as a scan starts, and whether
/// Bluetooth is on.
List<FoundStrap> nearby = [];
/// The straps and the rowers nearby, seen as soon as a scan starts, and
/// whether Bluetooth is on.
List<FoundDevice> nearby = [];
List<FoundDevice> nearbyRowers = [];
bool bluetoothOn = true;
/// How many scans were started.
int scans = 0;
/// The scans started, of which kind.
final scanned = <DeviceKind>[];
/// The notices shown, the first one the start's.
final notices = <RecorderNotice>[];
@ -72,12 +74,14 @@ class FakeRecorderPlatform({
required AutoPauseBy? autoPause,
required RecorderNotice notice,
String? strap,
String? rower,
}) async {
started.add((
path: path,
activity: activity,
autoPause: autoPause,
strap: strap,
rower: rower,
));
notices.add(notice);
_file = File(path)
@ -126,12 +130,13 @@ class FakeRecorderPlatform({
}
@override
Stream<FoundStrap> scanStraps() {
scans++;
Stream<FoundDevice> scan(DeviceKind kind) {
scanned.add(kind);
if (!bluetoothOn) {
return Stream.error(BluetoothOff());
}
final found = kind == DeviceKind.strap ? nearby : nearbyRowers;
// Open until cancelled, as a scan is.
return Stream.multi((listener) => nearby.forEach(listener.add));
return Stream.multi((listener) => found.forEach(listener.add));
}
}