BLE: decode cadence from cycling powermeter.

Part of #1352.
This commit is contained in:
Dennis Guse
2022-08-16 20:33:54 +02:00
parent 8696a15ce7
commit 7d804b603d
5 changed files with 88 additions and 18 deletions
@@ -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
@@ -254,9 +254,9 @@ public abstract class BluetoothConnectionManager<DataType> {
@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;
}
}
@@ -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) {
@@ -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;
}
@@ -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<Power> {
@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 +
'}';
}
}
}