feat(train): a ride reads the bike's speed and cadence sensor and power meter (ADR 0030, D49)

11d, its sensors: a ride's Record sheet offers the CSC sensor (0x1816)
and the power meter (0x1818), found nearby and remembered as the strap
is; the person's devices are now one map of DeviceKind to DeviceStore.
The service parses CSC and Cycling Power measurements (JVM-tested,
counters' rollover, coasting), writes the wheel's revolutions, the
crank's cadence and the watts to the file and to series of their own.
The recording screen shows cadence and watts; the page the power's
chart, average, highest and normalised power and the cadence's chart and
average; GPX, TCX and FIT carry them. A plan's watts target reads the
power meter too.

The wheel's speed and distance follow. Not tried with a real sensor.

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-29 15:11:20 +01:00
commit 5c13e6f3c5
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
5 changed files with 1209 additions and 62 deletions

View file

@ -519,5 +519,26 @@
"workoutExportTitle": "Training exportieren",
"workoutExportGpxHint": "Die Strecke, für Karten und andere Apps",
"workoutExportTcxHint": "Runden, Puls und Kadenz, für die meisten Trainingsseiten",
"workoutExportFitHint": "Was Strava, Garmin und intervals.icu am besten lesen"
"workoutExportFitHint": "Was Strava, Garmin und intervals.icu am besten lesen",
"recordSpeedSensor": "Geschwindigkeits- und Trittfrequenzsensor",
"recordNoSpeedSensor": "Keiner gewählt",
"recordPowerMeter": "Leistungsmesser",
"recordNoPowerMeter": "Keiner gewählt",
"speedSensorSheetTitle": "Geschwindigkeits- und Trittfrequenzsensor",
"speedSensorSearching": "Suche Sensoren in der Nähe …",
"speedSensorSearchingHint": "Dreh das Rad oder die Kurbel, um ihn zu wecken. Einer, den eine andere App hält, ist nicht zu finden: Schließ diese App zuerst.",
"speedSensorUnnamed": "Sensor ohne Namen",
"speedSensorForget": "Sensor vergessen",
"speedSensorRefused": "Train braucht Geräte in der Nähe, um deinen Sensor zu finden. Du kannst das in den Systemeinstellungen erlauben.",
"powerMeterSheetTitle": "Leistungsmesser",
"powerMeterSearching": "Suche Leistungsmesser in der Nähe …",
"powerMeterSearchingHint": "Dreh die Kurbel, um ihn zu wecken. Einer, den eine andere App hält, ist nicht zu finden: Schließ diese App zuerst.",
"powerMeterUnnamed": "Leistungsmesser ohne Namen",
"powerMeterForget": "Leistungsmesser vergessen",
"powerMeterRefused": "Train braucht Geräte in der Nähe, um deinen Leistungsmesser zu finden. Du kannst das in den Systemeinstellungen erlauben.",
"recordingCadence": "Umdrehungen pro Minute",
"workoutPedalling": "Trittfrequenz",
"workoutAverageCadence": "durchschnittliche Trittfrequenz",
"workoutNormalisedPower": "normalisierte Leistung",
"workoutRpmValue": "{rpm} U/min"
}

View file

@ -658,5 +658,27 @@
"workoutExportTitle": "Export the workout",
"workoutExportGpxHint": "The track, for maps and other apps",
"workoutExportTcxHint": "Laps, heart rate and cadence, for most training sites",
"workoutExportFitHint": "What Strava, Garmin and intervals.icu read best"
"workoutExportFitHint": "What Strava, Garmin and intervals.icu read best",
"recordSpeedSensor": "Speed and cadence sensor",
"recordNoSpeedSensor": "None chosen",
"recordPowerMeter": "Power meter",
"recordNoPowerMeter": "None chosen",
"speedSensorSheetTitle": "Speed and cadence sensor",
"speedSensorSearching": "Looking for speed and cadence sensors nearby…",
"speedSensorSearchingHint": "Turn the wheel or the crank to wake it. One another app holds can't be found: close that app first.",
"speedSensorUnnamed": "Unnamed sensor",
"speedSensorForget": "Forget sensor",
"speedSensorRefused": "Train needs nearby devices to find your sensor. You can allow it in the system's settings.",
"powerMeterSheetTitle": "Power meter",
"powerMeterSearching": "Looking for power meters nearby…",
"powerMeterSearchingHint": "Turn the crank to wake it. One another app holds can't be found: close that app first.",
"powerMeterUnnamed": "Unnamed power meter",
"powerMeterForget": "Forget power meter",
"powerMeterRefused": "Train needs nearby devices to find your power meter. You can allow it in the system's settings.",
"recordingCadence": "turns a minute",
"workoutPedalling": "Cadence",
"workoutAverageCadence": "average cadence",
"workoutNormalisedPower": "normalised power",
"workoutRpmValue": "{rpm} rpm",
"@workoutRpmValue": {"placeholders": {"rpm": {"type": "int"}}}
}

View file

@ -3141,6 +3141,132 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'What Strava, Garmin and intervals.icu read best'**
String get workoutExportFitHint;
/// No description provided for @recordSpeedSensor.
///
/// In en, this message translates to:
/// **'Speed and cadence sensor'**
String get recordSpeedSensor;
/// No description provided for @recordNoSpeedSensor.
///
/// In en, this message translates to:
/// **'None chosen'**
String get recordNoSpeedSensor;
/// No description provided for @recordPowerMeter.
///
/// In en, this message translates to:
/// **'Power meter'**
String get recordPowerMeter;
/// No description provided for @recordNoPowerMeter.
///
/// In en, this message translates to:
/// **'None chosen'**
String get recordNoPowerMeter;
/// No description provided for @speedSensorSheetTitle.
///
/// In en, this message translates to:
/// **'Speed and cadence sensor'**
String get speedSensorSheetTitle;
/// No description provided for @speedSensorSearching.
///
/// In en, this message translates to:
/// **'Looking for speed and cadence sensors nearby…'**
String get speedSensorSearching;
/// No description provided for @speedSensorSearchingHint.
///
/// In en, this message translates to:
/// **'Turn the wheel or the crank to wake it. One another app holds can\'t be found: close that app first.'**
String get speedSensorSearchingHint;
/// No description provided for @speedSensorUnnamed.
///
/// In en, this message translates to:
/// **'Unnamed sensor'**
String get speedSensorUnnamed;
/// No description provided for @speedSensorForget.
///
/// In en, this message translates to:
/// **'Forget sensor'**
String get speedSensorForget;
/// No description provided for @speedSensorRefused.
///
/// In en, this message translates to:
/// **'Train needs nearby devices to find your sensor. You can allow it in the system\'s settings.'**
String get speedSensorRefused;
/// No description provided for @powerMeterSheetTitle.
///
/// In en, this message translates to:
/// **'Power meter'**
String get powerMeterSheetTitle;
/// No description provided for @powerMeterSearching.
///
/// In en, this message translates to:
/// **'Looking for power meters nearby…'**
String get powerMeterSearching;
/// No description provided for @powerMeterSearchingHint.
///
/// In en, this message translates to:
/// **'Turn the crank to wake it. One another app holds can\'t be found: close that app first.'**
String get powerMeterSearchingHint;
/// No description provided for @powerMeterUnnamed.
///
/// In en, this message translates to:
/// **'Unnamed power meter'**
String get powerMeterUnnamed;
/// No description provided for @powerMeterForget.
///
/// In en, this message translates to:
/// **'Forget power meter'**
String get powerMeterForget;
/// No description provided for @powerMeterRefused.
///
/// In en, this message translates to:
/// **'Train needs nearby devices to find your power meter. You can allow it in the system\'s settings.'**
String get powerMeterRefused;
/// No description provided for @recordingCadence.
///
/// In en, this message translates to:
/// **'turns a minute'**
String get recordingCadence;
/// No description provided for @workoutPedalling.
///
/// In en, this message translates to:
/// **'Cadence'**
String get workoutPedalling;
/// No description provided for @workoutAverageCadence.
///
/// In en, this message translates to:
/// **'average cadence'**
String get workoutAverageCadence;
/// No description provided for @workoutNormalisedPower.
///
/// In en, this message translates to:
/// **'normalised power'**
String get workoutNormalisedPower;
/// No description provided for @workoutRpmValue.
///
/// In en, this message translates to:
/// **'{rpm} rpm'**
String workoutRpmValue(int rpm);
}
class _AppLocalizationsDelegate

View file

@ -1889,4 +1889,74 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get workoutExportFitHint =>
'Was Strava, Garmin und intervals.icu am besten lesen';
@override
String get recordSpeedSensor => 'Geschwindigkeits- und Trittfrequenzsensor';
@override
String get recordNoSpeedSensor => 'Keiner gewählt';
@override
String get recordPowerMeter => 'Leistungsmesser';
@override
String get recordNoPowerMeter => 'Keiner gewählt';
@override
String get speedSensorSheetTitle =>
'Geschwindigkeits- und Trittfrequenzsensor';
@override
String get speedSensorSearching => 'Suche Sensoren in der Nähe …';
@override
String get speedSensorSearchingHint =>
'Dreh das Rad oder die Kurbel, um ihn zu wecken. Einer, den eine andere App hält, ist nicht zu finden: Schließ diese App zuerst.';
@override
String get speedSensorUnnamed => 'Sensor ohne Namen';
@override
String get speedSensorForget => 'Sensor vergessen';
@override
String get speedSensorRefused =>
'Train braucht Geräte in der Nähe, um deinen Sensor zu finden. Du kannst das in den Systemeinstellungen erlauben.';
@override
String get powerMeterSheetTitle => 'Leistungsmesser';
@override
String get powerMeterSearching => 'Suche Leistungsmesser in der Nähe …';
@override
String get powerMeterSearchingHint =>
'Dreh die Kurbel, um ihn zu wecken. Einer, den eine andere App hält, ist nicht zu finden: Schließ diese App zuerst.';
@override
String get powerMeterUnnamed => 'Leistungsmesser ohne Namen';
@override
String get powerMeterForget => 'Leistungsmesser vergessen';
@override
String get powerMeterRefused =>
'Train braucht Geräte in der Nähe, um deinen Leistungsmesser zu finden. Du kannst das in den Systemeinstellungen erlauben.';
@override
String get recordingCadence => 'Umdrehungen pro Minute';
@override
String get workoutPedalling => 'Trittfrequenz';
@override
String get workoutAverageCadence => 'durchschnittliche Trittfrequenz';
@override
String get workoutNormalisedPower => 'normalisierte Leistung';
@override
String workoutRpmValue(int rpm) {
return '$rpm U/min';
}
}

View file

@ -1877,6 +1877,76 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get workoutExportFitHint =>
'What Strava, Garmin and intervals.icu read best';
@override
String get recordSpeedSensor => 'Speed and cadence sensor';
@override
String get recordNoSpeedSensor => 'None chosen';
@override
String get recordPowerMeter => 'Power meter';
@override
String get recordNoPowerMeter => 'None chosen';
@override
String get speedSensorSheetTitle => 'Speed and cadence sensor';
@override
String get speedSensorSearching =>
'Looking for speed and cadence sensors nearby…';
@override
String get speedSensorSearchingHint =>
'Turn the wheel or the crank to wake it. One another app holds can\'t be found: close that app first.';
@override
String get speedSensorUnnamed => 'Unnamed sensor';
@override
String get speedSensorForget => 'Forget sensor';
@override
String get speedSensorRefused =>
'Train needs nearby devices to find your sensor. You can allow it in the system\'s settings.';
@override
String get powerMeterSheetTitle => 'Power meter';
@override
String get powerMeterSearching => 'Looking for power meters nearby…';
@override
String get powerMeterSearchingHint =>
'Turn the crank to wake it. One another app holds can\'t be found: close that app first.';
@override
String get powerMeterUnnamed => 'Unnamed power meter';
@override
String get powerMeterForget => 'Forget power meter';
@override
String get powerMeterRefused =>
'Train needs nearby devices to find your power meter. You can allow it in the system\'s settings.';
@override
String get recordingCadence => 'turns a minute';
@override
String get workoutPedalling => 'Cadence';
@override
String get workoutAverageCadence => 'average cadence';
@override
String get workoutNormalisedPower => 'normalised power';
@override
String workoutRpmValue(int rpm) {
return '$rpm rpm';
}
}
/// The translations for English, as used in the United Kingdom (`en_GB`).