From 796fa216f6c66944e6b08af8f7aef8ec5a7fa44d Mon Sep 17 00:00:00 2001 From: Kilian Lackhove Date: Sun, 19 Apr 2026 10:34:42 +0200 Subject: [PATCH] added bosch cadence sensor --- README_TESTED_SENSORS.md | 3 +- .../sensorData/SensorDataCyclingTest.java | 19 ++++++++++++ .../sensors/CyclingCadenceHandler.java | 2 +- .../driver/CyclingCadenceBluetooth.java | 30 +++++++++++++++---- .../sensorData/AggregatorCyclingCadence.java | 28 +++++++++++++---- .../BluetoothLeCyclingCadencePreference.java | 2 +- 6 files changed, 69 insertions(+), 15 deletions(-) diff --git a/README_TESTED_SENSORS.md b/README_TESTED_SENSORS.md index 358ef53fd..629d73c20 100644 --- a/README_TESTED_SENSORS.md +++ b/README_TESTED_SENSORS.md @@ -123,4 +123,5 @@ Tested with: Currently supported measurements: -* Human power \ No newline at end of file +* Human power +* Cadence diff --git a/src/androidTest/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataCyclingTest.java b/src/androidTest/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataCyclingTest.java index f7f06e13e..ab3c64583 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataCyclingTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataCyclingTest.java @@ -96,6 +96,25 @@ public class SensorDataCyclingTest { assertNull(current.getAggregatedValue(Instant.MIN)); } + @Test + public void compute_cadence_directCadence_withoutPrevious() { + AggregatorCyclingCadence current = new AggregatorCyclingCadence("", ""); + + current.add(Instant.MIN, new CyclingCadenceBluetooth.DirectCadenceData(Cadence.of(92))); + + assertEquals(92, current.getAggregatedValue(Instant.MIN).getRPM(), 0.01); + } + + @Test + public void compute_cadence_directCadence_winsOverCrankData() { + AggregatorCyclingCadence current = new AggregatorCyclingCadence("", ""); + + current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(1L, 1024)); + current.add(Instant.MIN, new CyclingCadenceBluetooth.DirectCadenceData(Cadence.of(92))); + + assertEquals(92, current.getAggregatedValue(Instant.MIN).getRPM(), 0.01); + } + @Test public void compute_speed() { AggregatorCyclingDistanceSpeed current = new AggregatorCyclingDistanceSpeed("", ""); diff --git a/src/main/java/de/dennisguse/opentracks/sensors/CyclingCadenceHandler.java b/src/main/java/de/dennisguse/opentracks/sensors/CyclingCadenceHandler.java index f3a072d53..e08e4f475 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/CyclingCadenceHandler.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/CyclingCadenceHandler.java @@ -9,7 +9,7 @@ import de.dennisguse.opentracks.sensors.driver.CyclingCadenceBluetooth; import de.dennisguse.opentracks.sensors.driver.Driver; import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingCadence; -public class CyclingCadenceHandler extends SensorHandler { +public class CyclingCadenceHandler extends SensorHandler { protected CyclingCadenceHandler(SensorManager sensorManager) { super(sensorManager); diff --git a/src/main/java/de/dennisguse/opentracks/sensors/driver/CyclingCadenceBluetooth.java b/src/main/java/de/dennisguse/opentracks/sensors/driver/CyclingCadenceBluetooth.java index 6177f131b..ec1aa850e 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/driver/CyclingCadenceBluetooth.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/driver/CyclingCadenceBluetooth.java @@ -6,23 +6,25 @@ import android.util.Pair; import java.util.List; +import de.dennisguse.opentracks.data.models.Cadence; import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID; -public class CyclingCadenceBluetooth implements BluetoothDriver.BluetoothParser { +public class CyclingCadenceBluetooth implements BluetoothDriver.BluetoothParser { private static final String TAG = CyclingCadenceBluetooth.class.getSimpleName(); - public static final List CYCLING_CADENCE = List.of( + public static final List SUPPORTED_SERVICES = List.of( CyclingPowerBluetooth.CYCLING_POWER, - CyclingDistanceSpeedBluetooth.CYCLING_SPEED_CADENCE + CyclingDistanceSpeedBluetooth.CYCLING_SPEED_CADENCE, + BoschEbikeParser.BOSCH_EBIKE ); @Override public List getServices() { - return CYCLING_CADENCE; + return SUPPORTED_SERVICES; } @Override - public CrankData parsePayload(ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, BluetoothGattCharacteristic characteristic) { + public CyclingCadenceMeasurement parsePayload(ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, BluetoothGattCharacteristic characteristic) { if (serviceMeasurementUUID.equals(CyclingPowerBluetooth.CYCLING_POWER)) { CyclingPowerBluetooth.Data data = CyclingPowerBluetooth.parseCyclingPower(characteristic); if (data != null && data.crank() != null) { @@ -38,12 +40,28 @@ public class CyclingCadenceBluetooth implements BluetoothDriver.BluetoothParser< } } + if (serviceMeasurementUUID.equals(BoschEbikeParser.BOSCH_EBIKE)) { + BoschEbikeParser.Data boschData = BoschEbikeParser.parse(characteristic.getValue()); + if (boschData != null && boschData.cadence() != null) { + return new DirectCadenceData(boschData.cadence()); + } + } + Log.e(TAG, "Don't know how to decode this payload."); return null; } + public sealed interface CyclingCadenceMeasurement permits CrankData, DirectCadenceData { + } + public record CrankData( long crankRevolutionsCount, // UINT32 int crankRevolutionsTime // UINT16; 1/1024s - ) {} + ) implements CyclingCadenceMeasurement { + } + + public record DirectCadenceData( + Cadence cadence + ) implements CyclingCadenceMeasurement { + } } diff --git a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorCyclingCadence.java b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorCyclingCadence.java index c7fce1e6e..f10fcace9 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorCyclingCadence.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorCyclingCadence.java @@ -10,7 +10,7 @@ import de.dennisguse.opentracks.data.models.Cadence; import de.dennisguse.opentracks.sensors.UintUtils; import de.dennisguse.opentracks.sensors.driver.CyclingCadenceBluetooth; -public class AggregatorCyclingCadence extends Aggregator { +public class AggregatorCyclingCadence extends Aggregator { private final String TAG = AggregatorCyclingCadence.class.getSimpleName(); @@ -19,12 +19,20 @@ public class AggregatorCyclingCadence extends Aggregator current) { - if (previous == null) { + protected void computeValue(@NonNull Raw current) { + if (current.value() instanceof CyclingCadenceBluetooth.DirectCadenceData directCadenceData) { + output = directCadenceData.cadence(); return; } - float timeDiff_ms = UintUtils.diff(current.value().crankRevolutionsTime(), previous.value().crankRevolutionsTime(), UintUtils.UINT16_MAX) / 1024f * 1000; + computeRawCadence((CyclingCadenceBluetooth.CrankData) current.value()); + } + + private void computeRawCadence(@NonNull CyclingCadenceBluetooth.CrankData currentRaw) { + CyclingCadenceBluetooth.CrankData previousRaw = getPreviousRaw(); + if (previousRaw == null) return; + + float timeDiff_ms = UintUtils.diff(currentRaw.crankRevolutionsTime(), previousRaw.crankRevolutionsTime(), UintUtils.UINT16_MAX) / 1024f * 1000; Duration timeDiff = Duration.ofMillis((long) timeDiff_ms); if (timeDiff.isZero()) { @@ -37,13 +45,21 @@ public class AggregatorCyclingCadence extends Aggregator