diff --git a/src/androidTest/java/de/dennisguse/opentracks/sensors/BluetoothUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/sensors/BluetoothUtilsTest.java index d225e9036..682af3880 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/sensors/BluetoothUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/sensors/BluetoothUtilsTest.java @@ -11,6 +11,7 @@ import de.dennisguse.opentracks.data.models.Cadence; import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.sensors.sensorData.SensorDataCycling; +import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower; import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning; public class BluetoothUtilsTest { @@ -86,10 +87,25 @@ public class BluetoothUtilsTest { characteristic.setValue(new byte[]{0, 0, 40, 0}); // when - int power_w = BluetoothUtils.parseCyclingPower(characteristic); + SensorDataCyclingPower.Data powerCadence = BluetoothUtils.parseCyclingPower(characteristic); // then - assertEquals(40, power_w); + assertEquals(40, powerCadence.getPower().getW(), 0.01); + } + + @Test + public void parseCyclingPower_power_with_cadence() { + BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_POWER.getServiceUUID(), 0, 0); + characteristic.setValue(new byte[]{0x2C, 0x00, 0x00, 0x00, (byte) 0x9F, 0x00, 0x0C, 0x00, (byte) 0xE5, 0x42}); + + // when + SensorDataCyclingPower.Data powerCadence = BluetoothUtils.parseCyclingPower(characteristic); + + // then + assertEquals(0, powerCadence.getPower().getW(), 0.01); + + assertEquals(12, powerCadence.getCadence().getCrankRevolutionsCount()); + assertEquals(17125, powerCadence.getCadence().getCrankRevolutionsTime()); } @Test diff --git a/src/main/java/de/dennisguse/opentracks/sensors/BluetoothConnectionManager.java b/src/main/java/de/dennisguse/opentracks/sensors/BluetoothConnectionManager.java index bbefe34ab..de245d601 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/BluetoothConnectionManager.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/BluetoothConnectionManager.java @@ -254,9 +254,9 @@ public abstract class BluetoothConnectionManager { @Override protected SensorDataCyclingPower parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) { - Integer cyclingPower = BluetoothUtils.parseCyclingPower(characteristic); + SensorDataCyclingPower.Data cyclingPower = BluetoothUtils.parseCyclingPower(characteristic); - return cyclingPower != null ? new SensorDataCyclingPower(address, sensorName, Power.of(cyclingPower)) : null; + return cyclingPower != null ? new SensorDataCyclingPower(address, sensorName, cyclingPower.getPower()) : null; } } diff --git a/src/main/java/de/dennisguse/opentracks/sensors/BluetoothUtils.java b/src/main/java/de/dennisguse/opentracks/sensors/BluetoothUtils.java index 050974e60..722aafd13 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/BluetoothUtils.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/BluetoothUtils.java @@ -30,8 +30,10 @@ import java.util.UUID; import de.dennisguse.opentracks.data.models.Cadence; import de.dennisguse.opentracks.data.models.Distance; +import de.dennisguse.opentracks.data.models.Power; import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.sensors.sensorData.SensorDataCycling; +import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower; import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning; /** @@ -109,14 +111,45 @@ public class BluetoothUtils { return null; } - public static Integer parseCyclingPower(BluetoothGattCharacteristic characteristic) { + public static SensorDataCyclingPower.Data parseCyclingPower(BluetoothGattCharacteristic characteristic) { // DOCUMENTATION https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.cycling_power_measurement.xml int valueLength = characteristic.getValue().length; - if (valueLength < 4) { + if (valueLength == 0) { return null; } - return characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_SINT16, 2); + int index = 0; + int flags1 = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, index++); + int flags2 = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, index++); + boolean hasPedalPowerBalance = (flags1 & 0x01) > 0; + boolean hasAccumulatedTorque = (flags1 & 0x04) > 0; + boolean hasWheel = (flags1 & 16) > 0; + boolean hasCrank = (flags1 & 32) > 0; + + Integer instantaneousPower = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_SINT16, index); + index += 2; + + if (hasPedalPowerBalance) { + index += 1; + } + if (hasAccumulatedTorque) { + index += 2; + } + if (hasWheel) { + index += 2 + 2; + } + + SensorDataCycling.CyclingCadence cadence = null; + if (hasCrank && valueLength - index >= 4) { + long crankCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); + index += 2; + + int crankTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); // 1/1024s + + cadence = new SensorDataCycling.CyclingCadence("", "", crankCount, crankTime); + } + + return new SensorDataCyclingPower.Data(Power.of(instantaneousPower), cadence); } public static SensorDataCycling.CadenceAndSpeed parseCyclingCrankAndWheel(String address, String sensorName, @NonNull BluetoothGattCharacteristic characteristic) { diff --git a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataCycling.java b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataCycling.java index 1d3ad400b..7827127ca 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataCycling.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataCycling.java @@ -40,13 +40,6 @@ public final class SensorDataCycling { this.crankRevolutionsTime = crankRevolutionsTime; } - /** - * Workaround for Wahoo CADENCE: provides speed instead of cadence - */ - public CyclingCadence(@NonNull DistanceSpeed speed) { - this(speed.getSensorAddress(), speed.getSensorName(), speed.wheelRevolutionsCount, speed.wheelRevolutionsTime); - } - public boolean hasData() { return crankRevolutionsCount != null && crankRevolutionsTime != null; } diff --git a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataCyclingPower.java b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataCyclingPower.java index dae106132..5c962b3a5 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataCyclingPower.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataCyclingPower.java @@ -1,6 +1,7 @@ package de.dennisguse.opentracks.sensors.sensorData; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import de.dennisguse.opentracks.data.models.Power; @@ -17,13 +18,40 @@ public class SensorDataCyclingPower extends SensorData { @NonNull @Override - public String toString() { - return super.toString() + " power=" + value; + protected Power getNoneValue() { + return Power.of(0f); } @NonNull @Override - protected Power getNoneValue() { - return Power.of(0f); + public String toString() { + return super.toString() + " data=" + value; + } + + public static class Data { + private final Power power; + private final SensorDataCycling.CyclingCadence cadence; + + public Data(Power power, @Nullable SensorDataCycling.CyclingCadence cadence) { + this.power = power; + this.cadence = cadence; + } + + public Power getPower() { + return power; + } + + public SensorDataCycling.CyclingCadence getCadence() { + return cadence; + } + + @NonNull + @Override + public String toString() { + return "Data{" + + "power=" + power + + ", cadence=" + cadence + + '}'; + } } }