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>
This commit is contained in:
parent
de6fe5c15a
commit
cd72cf6ccc
1 changed files with 3485 additions and 99 deletions
|
|
@ -0,0 +1,100 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:halkyon_train_flutter/src/feature/loop/data/loop_platform.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/loop/model/loop_figures.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/recording/data/recorder_platform.dart';
|
||||
|
||||
/// Polar's SDK as tests want it: bands nearby, each in reach or not; what
|
||||
/// was asked of them.
|
||||
class FakeLoopPlatform({
|
||||
var bool bluetooth = true,
|
||||
}) implements LoopPlatform {
|
||||
/// The bands nearby; those [inReach] connect.
|
||||
final nearby = <FoundDevice>[];
|
||||
final inReach = <String>{};
|
||||
|
||||
/// Bands that refuse to be set up.
|
||||
final refusing = <String>{};
|
||||
|
||||
/// What was asked, in order: `connect a`, `setUp a`, `setClock a`, …
|
||||
final calls = <String>[];
|
||||
|
||||
/// The figures each band was set up with.
|
||||
final setUpWith = <String, LoopFigures>{};
|
||||
|
||||
final _events = StreamController<LoopEvent>.broadcast();
|
||||
final _connected = <String>{};
|
||||
|
||||
/// Tells what [event] tells, as a band would.
|
||||
void tell(LoopEvent event) => _events.add(event);
|
||||
|
||||
/// The band [id] comes in reach and connects, if asked to.
|
||||
void comeInReach(String id) {
|
||||
inReach.add(id);
|
||||
if (_connected.contains(id)) {
|
||||
_connectNow(id);
|
||||
}
|
||||
}
|
||||
|
||||
/// The band [id] goes out of reach.
|
||||
void goAway(String id) {
|
||||
inReach.remove(id);
|
||||
_events.add(LoopDisconnected(id));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> askBluetooth() async {
|
||||
calls.add('askBluetooth');
|
||||
return bluetooth;
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<FoundDevice> search() => Stream.fromIterable(nearby);
|
||||
|
||||
@override
|
||||
Stream<LoopEvent> get events => _events.stream;
|
||||
|
||||
@override
|
||||
Future<void> connect(String id) async {
|
||||
calls.add('connect $id');
|
||||
_connected.add(id);
|
||||
if (inReach.contains(id)) {
|
||||
scheduleMicrotask(() => _connectNow(id));
|
||||
}
|
||||
}
|
||||
|
||||
void _connectNow(String id) {
|
||||
_events
|
||||
..add(LoopConnecting(id))
|
||||
..add(LoopConnected(id, name: 'Polar Loop $id'))
|
||||
..add(LoopReady(id));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> disconnect(String id) async {
|
||||
calls.add('disconnect $id');
|
||||
_connected.remove(id);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setUp(String id, LoopFigures figures) async {
|
||||
calls.add('setUp $id');
|
||||
if (refusing.contains(id) || !inReach.contains(id)) {
|
||||
throw const LoopFailed('refused');
|
||||
}
|
||||
setUpWith[id] = figures;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setClock(String id) async {
|
||||
calls.add('setClock $id');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> reset(String id) async {
|
||||
calls.add('reset $id');
|
||||
if (!inReach.contains(id)) {
|
||||
throw const LoopFailed('not in reach');
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue