feat(train): the Loop's firmware updates, directly or through the instance (ADR 0030, D10, D40)
Train's server relays Polar's firmware service for the people signed in (`firmware`: offered, check, download), a fetched file kept in memory a day per hardware and version, off with LOOP_FIRMWARE_RELAY=off. The app asks on the day's first connection and on the band's screen, from Polar or the instance as the person chose, never Polar in the instance's place; an update runs only after asking, reads and keeps the band first, and sets it up again with its figures and clock after. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
7c5e539df5
commit
388b3b1ae6
4 changed files with 2318 additions and 45 deletions
|
|
@ -11,8 +11,11 @@
|
|||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:async' as _ida;
|
||||
import 'dart:typed_data' as _idt;
|
||||
import 'package:halkyon_base_client/halkyon_base_client.dart' as _ihk4p5t7;
|
||||
import 'package:halkyon_oidc_client/halkyon_oidc_client.dart' as _ibkpwzgl;
|
||||
import 'package:halkyon_train_client/src/protocol/firmware/loop_firmware.dart'
|
||||
as _ikc7xqe5;
|
||||
import 'package:halkyon_train_client/src/protocol/map/map_source.dart'
|
||||
as _iebj1x2g;
|
||||
import 'package:http/http.dart' as _i85jenna;
|
||||
|
|
@ -28,6 +31,53 @@ import 'protocol.dart' as _il2as5qe;
|
|||
import 'sync_tables.dart' as _ii3f3u05;
|
||||
import 'package:halkyon_train_client/migrations/migration_registry.dart';
|
||||
|
||||
/// The Polar Loop's firmware through the instance (ADR 0030, D10, D40), for
|
||||
/// the people signed in: the instance asks Polar in their place, so Polar
|
||||
/// learns its address rather than theirs.
|
||||
/// {@category Endpoint}
|
||||
class EndpointFirmware extends _isc.EndpointRef {
|
||||
EndpointFirmware(_isc.EndpointCaller caller) : super(caller);
|
||||
|
||||
@override
|
||||
String get name => 'firmware';
|
||||
|
||||
/// Whether the instance relays firmware.
|
||||
_ida.Future<bool> offered() => caller.callServerEndpoint<bool>(
|
||||
'firmware',
|
||||
'offered',
|
||||
{},
|
||||
);
|
||||
|
||||
/// The update for the band of [uuid] (Polar's UUID made from its ID) at
|
||||
/// [firmwareVersion] with [hardwareCode]; null where there is none.
|
||||
_ida.Future<_ikc7xqe5.LoopFirmware?> check(
|
||||
String uuid,
|
||||
String firmwareVersion,
|
||||
String hardwareCode,
|
||||
) => caller.callServerEndpoint<_ikc7xqe5.LoopFirmware?>(
|
||||
'firmware',
|
||||
'check',
|
||||
{
|
||||
'uuid': uuid,
|
||||
'firmwareVersion': firmwareVersion,
|
||||
'hardwareCode': hardwareCode,
|
||||
},
|
||||
);
|
||||
|
||||
/// The file of [version] for bands of [hardwareCode], as a check named it.
|
||||
_ida.Future<_idt.ByteData> download(
|
||||
String hardwareCode,
|
||||
String version,
|
||||
) => caller.callServerEndpoint<_idt.ByteData>(
|
||||
'firmware',
|
||||
'download',
|
||||
{
|
||||
'hardwareCode': hardwareCode,
|
||||
'version': version,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// The map workouts are drawn on (ADR 0030, D11, D22), for the people signed
|
||||
/// in: an operator's provider may have its key in the style's address.
|
||||
/// {@category Endpoint}
|
||||
|
|
@ -93,16 +143,22 @@ class Client extends _isc.ServerpodClientShared {
|
|||
disconnectStreamsOnLostInternetConnection,
|
||||
httpClientOverride: httpClientOverride,
|
||||
) {
|
||||
firmware = EndpointFirmware(this);
|
||||
map = EndpointMap(this);
|
||||
modules = Modules(this);
|
||||
}
|
||||
|
||||
late final EndpointFirmware firmware;
|
||||
|
||||
late final EndpointMap map;
|
||||
|
||||
late final Modules modules;
|
||||
|
||||
@override
|
||||
Map<String, _isc.EndpointRef> get endpointRefLookup => {'map': map};
|
||||
Map<String, _isc.EndpointRef> get endpointRefLookup => {
|
||||
'firmware': firmware,
|
||||
'map': map,
|
||||
};
|
||||
|
||||
@override
|
||||
Map<String, _isc.ModuleEndpointCaller> get moduleLookup => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
|
||||
/* To generate run: "serverpod generate" */
|
||||
|
||||
// ignore_for_file: implementation_imports
|
||||
// ignore_for_file: library_private_types_in_public_api
|
||||
// ignore_for_file: non_constant_identifier_names
|
||||
// ignore_for_file: public_member_api_docs
|
||||
// ignore_for_file: type_literal_in_constant_pattern
|
||||
// ignore_for_file: use_super_parameters
|
||||
// ignore_for_file: invalid_use_of_internal_member
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:serverpod_client/serverpod_client.dart' as _isc;
|
||||
|
||||
/// A firmware update for the person's Polar Loop that the instance relays
|
||||
/// from Polar's firmware service (ADR 0030, D10, D40): its version, whose file
|
||||
/// the app then downloads (`firmware.download`).
|
||||
abstract class LoopFirmware
|
||||
implements _isc.SerializableModel, _isc.ProtocolSerialization {
|
||||
LoopFirmware._({required this.version});
|
||||
|
||||
factory LoopFirmware({required String version}) = _LoopFirmwareImpl;
|
||||
|
||||
factory LoopFirmware.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return LoopFirmware(version: jsonSerialization['version'] as String);
|
||||
}
|
||||
|
||||
/// The firmware's version, as Polar names it (`3.1.2`).
|
||||
String version;
|
||||
|
||||
/// Returns a shallow copy of this [LoopFirmware]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
LoopFirmware copyWith({String? version});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'LoopFirmware',
|
||||
'version': version,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'LoopFirmware',
|
||||
'version': version,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _isc.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _LoopFirmwareImpl extends LoopFirmware {
|
||||
_LoopFirmwareImpl({required String version}) : super._(version: version);
|
||||
|
||||
/// Returns a shallow copy of this [LoopFirmware]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
@override
|
||||
LoopFirmware copyWith({String? version}) {
|
||||
return LoopFirmware(version: version ?? this.version);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
|
||||
/* To generate run: "serverpod generate" */
|
||||
|
||||
// ignore_for_file: implementation_imports
|
||||
// ignore_for_file: library_private_types_in_public_api
|
||||
// ignore_for_file: non_constant_identifier_names
|
||||
// ignore_for_file: public_member_api_docs
|
||||
// ignore_for_file: type_literal_in_constant_pattern
|
||||
// ignore_for_file: use_super_parameters
|
||||
// ignore_for_file: invalid_use_of_internal_member
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:serverpod_client/serverpod_client.dart' as _isc;
|
||||
|
||||
/// Why the instance relays no firmware (ADR 0030, D40): `off` where its
|
||||
/// operator turned relaying off, `invalid` for a band's figures it doesn't
|
||||
/// take, `unknown` for a file it wasn't told of (or no longer holds),
|
||||
/// `unreachable` where Polar's service didn't answer.
|
||||
abstract class LoopFirmwareRefused
|
||||
implements
|
||||
_isc.SerializableException,
|
||||
_isc.SerializableModel,
|
||||
_isc.ProtocolSerialization {
|
||||
LoopFirmwareRefused._({required this.reason});
|
||||
|
||||
factory LoopFirmwareRefused({required String reason}) =
|
||||
_LoopFirmwareRefusedImpl;
|
||||
|
||||
factory LoopFirmwareRefused.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return LoopFirmwareRefused(reason: jsonSerialization['reason'] as String);
|
||||
}
|
||||
|
||||
String reason;
|
||||
|
||||
/// Returns a shallow copy of this [LoopFirmwareRefused]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
LoopFirmwareRefused copyWith({String? reason});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'LoopFirmwareRefused',
|
||||
'reason': reason,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'LoopFirmwareRefused',
|
||||
'reason': reason,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LoopFirmwareRefused(reason: $reason)';
|
||||
}
|
||||
}
|
||||
|
||||
class _LoopFirmwareRefusedImpl extends LoopFirmwareRefused {
|
||||
_LoopFirmwareRefusedImpl({required String reason}) : super._(reason: reason);
|
||||
|
||||
/// Returns a shallow copy of this [LoopFirmwareRefused]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
@override
|
||||
LoopFirmwareRefused copyWith({String? reason}) {
|
||||
return LoopFirmwareRefused(reason: reason ?? this.reason);
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,8 @@ import 'package:serverpod_offline_sync_client/serverpod_offline_sync_client.dart
|
|||
import 'day/day.dart' as _ikmv9f3y;
|
||||
import 'day/day_series.dart' as _ir1ofny6;
|
||||
import 'day/night.dart' as _i17cgxdf;
|
||||
import 'firmware/loop_firmware.dart' as _i4qollgq;
|
||||
import 'firmware/loop_firmware_refused.dart' as _ipcwdy0l;
|
||||
import 'map/map_source.dart' as _ikw0qg8x;
|
||||
import 'privacy/content_key.dart' as _imed3x5d;
|
||||
import 'profile/profile.dart' as _ia5j8fch;
|
||||
|
|
@ -32,6 +34,8 @@ import 'workout/workout_series.dart' as _i92mefto;
|
|||
export 'day/day.dart';
|
||||
export 'day/day_series.dart';
|
||||
export 'day/night.dart';
|
||||
export 'firmware/loop_firmware.dart';
|
||||
export 'firmware/loop_firmware_refused.dart';
|
||||
export 'map/map_source.dart';
|
||||
export 'privacy/content_key.dart';
|
||||
export 'profile/profile.dart';
|
||||
|
|
@ -629,6 +633,12 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
if (t == _i17cgxdf.Night) {
|
||||
return _i17cgxdf.Night.fromJson(data) as T;
|
||||
}
|
||||
if (t == _i4qollgq.LoopFirmware) {
|
||||
return _i4qollgq.LoopFirmware.fromJson(data) as T;
|
||||
}
|
||||
if (t == _ipcwdy0l.LoopFirmwareRefused) {
|
||||
return _ipcwdy0l.LoopFirmwareRefused.fromJson(data) as T;
|
||||
}
|
||||
if (t == _ikw0qg8x.MapSource) {
|
||||
return _ikw0qg8x.MapSource.fromJson(data) as T;
|
||||
}
|
||||
|
|
@ -653,6 +663,15 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
if (t == _isc.getType<_i17cgxdf.Night?>()) {
|
||||
return (data != null ? _i17cgxdf.Night.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _isc.getType<_i4qollgq.LoopFirmware?>()) {
|
||||
return (data != null ? _i4qollgq.LoopFirmware.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _isc.getType<_ipcwdy0l.LoopFirmwareRefused?>()) {
|
||||
return (data != null
|
||||
? _ipcwdy0l.LoopFirmwareRefused.fromJson(data)
|
||||
: null)
|
||||
as T;
|
||||
}
|
||||
if (t == _isc.getType<_ikw0qg8x.MapSource?>()) {
|
||||
return (data != null ? _ikw0qg8x.MapSource.fromJson(data) : null) as T;
|
||||
}
|
||||
|
|
@ -692,6 +711,8 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
_ikmv9f3y.Day => 'Day',
|
||||
_ir1ofny6.DaySeries => 'DaySeries',
|
||||
_i17cgxdf.Night => 'Night',
|
||||
_i4qollgq.LoopFirmware => 'LoopFirmware',
|
||||
_ipcwdy0l.LoopFirmwareRefused => 'LoopFirmwareRefused',
|
||||
_ikw0qg8x.MapSource => 'MapSource',
|
||||
_imed3x5d.ContentKey => 'ContentKey',
|
||||
_ia5j8fch.Profile => 'Profile',
|
||||
|
|
@ -720,6 +741,10 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
return 'DaySeries';
|
||||
case _i17cgxdf.Night():
|
||||
return 'Night';
|
||||
case _i4qollgq.LoopFirmware():
|
||||
return 'LoopFirmware';
|
||||
case _ipcwdy0l.LoopFirmwareRefused():
|
||||
return 'LoopFirmwareRefused';
|
||||
case _ikw0qg8x.MapSource():
|
||||
return 'MapSource';
|
||||
case _imed3x5d.ContentKey():
|
||||
|
|
@ -775,6 +800,12 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
if (dataClassName == 'Night') {
|
||||
return deserialize<_i17cgxdf.Night>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'LoopFirmware') {
|
||||
return deserialize<_i4qollgq.LoopFirmware>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'LoopFirmwareRefused') {
|
||||
return deserialize<_ipcwdy0l.LoopFirmwareRefused>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'MapSource') {
|
||||
return deserialize<_ikw0qg8x.MapSource>(data['data']);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue