BLE: process cycling cadence from powermeter.

Fixes #1352.
This commit is contained in:
Dennis Guse
2022-09-04 19:53:08 +02:00
parent da29766fd0
commit a93424629d
12 changed files with 92 additions and 56 deletions
@@ -87,10 +87,10 @@ public class BluetoothUtilsTest {
characteristic.setValue(new byte[]{0, 0, 40, 0});
// when
SensorDataCyclingPower.Data powerCadence = BluetoothUtils.parseCyclingPower(characteristic);
SensorDataCyclingPower.Data powerCadence = BluetoothUtils.parseCyclingPower("", "", characteristic);
// then
assertEquals(40, powerCadence.getPower().getW(), 0.01);
assertEquals(40, powerCadence.getPower().getValue().getW(), 0.01);
}
@Test
@@ -99,10 +99,10 @@ public class BluetoothUtilsTest {
characteristic.setValue(new byte[]{0x2C, 0x00, 0x00, 0x00, (byte) 0x9F, 0x00, 0x0C, 0x00, (byte) 0xE5, 0x42});
// when
SensorDataCyclingPower.Data powerCadence = BluetoothUtils.parseCyclingPower(characteristic);
SensorDataCyclingPower.Data powerCadence = BluetoothUtils.parseCyclingPower("", "", characteristic);
// then
assertEquals(0, powerCadence.getPower().getW(), 0.01);
assertEquals(0, powerCadence.getPower().getValue().getW(), 0.01);
assertEquals(12, powerCadence.getCadence().getCrankRevolutionsCount());
assertEquals(17125, powerCadence.getCadence().getCrankRevolutionsTime());
@@ -31,10 +31,10 @@ import android.util.Log;
import androidx.annotation.NonNull;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.sensors.sensorData.SensorData;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataHeartRate;
/**
* Manages connection to a Bluetooth LE sensor and subscribes for onChange-notifications.
@@ -111,8 +111,16 @@ public abstract class BluetoothConnectionManager<DataType> {
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic) {
Log.d(TAG, "Received data from " + gatt.getDevice().getAddress());
SensorData<DataType> sensorData = parsePayload(gatt.getDevice().getName(), gatt.getDevice().getAddress(), characteristic);
UUID serviceUUID = characteristic.getService().getUuid();
Log.d(TAG, "Received data from " + gatt.getDevice().getAddress() + " with service " + serviceUUID + " and characteristics " + characteristic.getUuid());
Optional<ServiceMeasurementUUID> serviceMeasurementUUID = serviceMeasurementUUIDs.stream()
.filter(s -> s.getServiceUUID().equals(characteristic.getService().getUuid())).findFirst();
if (serviceMeasurementUUID.isEmpty()) {
Log.e(TAG, "Unknown service UUID; not supported?");
return;
}
SensorData<DataType> sensorData = parsePayload(serviceMeasurementUUID.get(), gatt.getDevice().getName(), gatt.getDevice().getAddress(), characteristic);
if (sensorData != null) {
Log.d(TAG, "Decoded data from " + gatt.getDevice().getAddress() + ": " + sensorData);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -180,26 +188,7 @@ public abstract class BluetoothConnectionManager<DataType> {
/**
* @return null if data could not be parsed.
*/
protected abstract SensorData<DataType> parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic);
public static class HeartRateConnectionManager extends BluetoothConnectionManager<HeartRate> {
HeartRateConnectionManager(@NonNull SensorDataObserver observer) {
super(BluetoothUtils.HEART_RATE_SUPPORTING_DEVICES, observer);
}
@Override
protected SensorDataHeartRate createEmptySensorData(String address) {
return new SensorDataHeartRate(address);
}
@Override
protected SensorDataHeartRate parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) {
Integer heartRate = BluetoothUtils.parseHeartRate(characteristic);
return heartRate != null ? new SensorDataHeartRate(address, sensorName, HeartRate.of(heartRate)) : null;
}
}
protected abstract SensorData<DataType> parsePayload(@NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, @NonNull BluetoothGattCharacteristic characteristic);
interface SensorDataObserver {
@@ -1,15 +1,19 @@
package de.dennisguse.opentracks.sensors;
import android.bluetooth.BluetoothGattCharacteristic;
import android.util.Log;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadence;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadenceAndDistanceSpeed;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
public class BluetoothRemoteSensorManagerCyclingCadence extends BluetoothConnectionManager<Cadence> {
private static final String TAG = BluetoothRemoteSensorManagerCyclingCadence.class.getSimpleName();
BluetoothRemoteSensorManagerCyclingCadence(SensorDataObserver observer) {
super(BluetoothUtils.CYCLING_SPEED_CADENCE, observer);
super(BluetoothUtils.CYCLING_CADENCE, observer);
}
@Override
@@ -18,16 +22,26 @@ public class BluetoothRemoteSensorManagerCyclingCadence extends BluetoothConnect
}
@Override
protected SensorDataCyclingCadence parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) {
SensorDataCyclingCadenceAndDistanceSpeed cadenceAndSpeed = BluetoothUtils.parseCyclingCrankAndWheel(address, sensorName, characteristic);
if (cadenceAndSpeed == null) {
return null;
}
if (cadenceAndSpeed.getCadence() != null) {
return cadenceAndSpeed.getCadence();
protected SensorDataCyclingCadence parsePayload(ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
//TODO Implement to ServiceMeasurement.parse()?
if (serviceMeasurementUUID.equals(BluetoothUtils.CYCLING_POWER)) {
SensorDataCyclingPower.Data data = BluetoothUtils.parseCyclingPower(address, sensorName, characteristic);
if (data!= null) {
return data.getCadence();
}
} else if (serviceMeasurementUUID.equals(BluetoothUtils.CYCLING_SPEED_CADENCE)) {
SensorDataCyclingCadenceAndDistanceSpeed cadenceAndSpeed = BluetoothUtils.parseCyclingCrankAndWheel(address, sensorName, characteristic);
if (cadenceAndSpeed == null) {
return null;
}
if (cadenceAndSpeed.getCadence() != null) {
return cadenceAndSpeed.getCadence();
}
}
Log.e(TAG, "Don't know how to decode this payload.");
return null;
}
}
@@ -17,7 +17,7 @@ public class BluetoothRemoteSensorManagerCyclingDistanceSpeed extends BluetoothC
}
@Override
protected SensorDataCyclingDistanceSpeed parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) {
protected SensorDataCyclingDistanceSpeed parsePayload(ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
SensorDataCyclingCadenceAndDistanceSpeed cadenceAndSpeed = BluetoothUtils.parseCyclingCrankAndWheel(address, sensorName, characteristic);
if (cadenceAndSpeed == null) {
return null;
@@ -19,9 +19,9 @@ public class BluetoothRemoteSensorManagerCyclingPower extends BluetoothConnectio
}
@Override
protected SensorDataCyclingPower parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) {
SensorDataCyclingPower.Data cyclingPower = BluetoothUtils.parseCyclingPower(characteristic);
protected SensorDataCyclingPower parsePayload(@NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
SensorDataCyclingPower.Data cyclingPower = BluetoothUtils.parseCyclingPower(address, sensorName, characteristic);
return cyclingPower != null ? new SensorDataCyclingPower(address, sensorName, cyclingPower.getPower()) : null;
return cyclingPower != null ? cyclingPower.getPower() : null;
}
}
@@ -19,7 +19,7 @@ public class BluetoothRemoteSensorManagerHeartRate extends BluetoothConnectionMa
}
@Override
protected SensorDataHeartRate parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) {
protected SensorDataHeartRate parsePayload(@NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
Integer heartRate = BluetoothUtils.parseHeartRate(characteristic);
return heartRate != null ? new SensorDataHeartRate(address, sensorName, HeartRate.of(heartRate)) : null;
@@ -18,7 +18,7 @@ public class BluetoothRemoteSensorManagerRunningSpeedAndCadence extends Bluetoot
}
@Override
protected SensorDataRunning parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) {
protected SensorDataRunning parsePayload(@NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
return BluetoothUtils.parseRunningSpeedAndCadence(address, sensorName, characteristic);
}
}
@@ -119,7 +119,7 @@ public class BluetoothUtils {
return null;
}
public static SensorDataCyclingPower.Data parseCyclingPower(BluetoothGattCharacteristic characteristic) {
public static SensorDataCyclingPower.Data parseCyclingPower(String address, String sensorName, 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 == 0) {
@@ -154,10 +154,11 @@ public class BluetoothUtils {
int crankTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); // 1/1024s
cadence = new SensorDataCyclingCadence("", "", crankCount, crankTime);
cadence = new SensorDataCyclingCadence(address, sensorName, crankCount, crankTime);
}
return new SensorDataCyclingPower.Data(Power.of(instantaneousPower), cadence);
return new SensorDataCyclingPower.Data(new SensorDataCyclingPower(sensorName, address, Power.of(instantaneousPower)), cadence);
}
public static SensorDataCyclingCadenceAndDistanceSpeed parseCyclingCrankAndWheel(String address, String sensorName, @NonNull BluetoothGattCharacteristic characteristic) {
@@ -29,15 +29,15 @@ public class SensorDataCyclingPower extends SensorData<Power> {
}
public static class Data {
private final Power power;
private final SensorDataCyclingPower power;
private final SensorDataCyclingCadence cadence;
public Data(Power power, @Nullable SensorDataCyclingCadence cadence) {
public Data(SensorDataCyclingPower power, @Nullable SensorDataCyclingCadence cadence) {
this.power = power;
this.cadence = cadence;
}
public Power getPower() {
public SensorDataCyclingPower getPower() {
return power;
}
@@ -0,0 +1,32 @@
package de.dennisguse.opentracks.settings.bluetooth;
import android.content.Context;
import android.util.AttributeSet;
import androidx.preference.PreferenceDialogFragmentCompat;
import de.dennisguse.opentracks.sensors.BluetoothUtils;
public class BluetoothLeCyclingCadencePreference extends BluetoothLeSensorPreference {
public BluetoothLeCyclingCadencePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public BluetoothLeCyclingCadencePreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public BluetoothLeCyclingCadencePreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BluetoothLeCyclingCadencePreference(Context context) {
super(context);
}
@Override
public PreferenceDialogFragmentCompat createInstance() {
return BluetoothLeSensorPreferenceDialog.newInstance(getKey(), BluetoothUtils.CYCLING_CADENCE);
}
}
@@ -7,21 +7,21 @@ import androidx.preference.PreferenceDialogFragmentCompat;
import de.dennisguse.opentracks.sensors.BluetoothUtils;
public class BluetoothLeCyclingCadenceAndSpeedPreference extends BluetoothLeSensorPreference {
public class BluetoothLeCyclingSpeedPreference extends BluetoothLeSensorPreference {
public BluetoothLeCyclingCadenceAndSpeedPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
public BluetoothLeCyclingSpeedPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public BluetoothLeCyclingCadenceAndSpeedPreference(Context context, AttributeSet attrs, int defStyleAttr) {
public BluetoothLeCyclingSpeedPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public BluetoothLeCyclingCadenceAndSpeedPreference(Context context, AttributeSet attrs) {
public BluetoothLeCyclingSpeedPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BluetoothLeCyclingCadenceAndSpeedPreference(Context context) {
public BluetoothLeCyclingSpeedPreference(Context context) {
super(context);
}
+2 -2
View File
@@ -12,7 +12,7 @@
</PreferenceCategory>
<PreferenceCategory android:title="@string/settings_cycling_sensor">
<de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingCadenceAndSpeedPreference
<de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingCadencePreference
android:defaultValue="@string/sensor_type_value_none"
android:key="@string/settings_sensor_bluetooth_cycling_cadence_key"
android:title="@string/settings_sensor_cycling_cadence" />
@@ -20,7 +20,7 @@
android:defaultValue="@string/sensor_type_value_none"
android:key="@string/settings_sensor_bluetooth_cycling_power_key"
android:title="@string/sensor_state_power" />
<de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingCadenceAndSpeedPreference
<de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingSpeedPreference
android:defaultValue="@string/sensor_type_value_none"
android:key="@string/settings_sensor_bluetooth_cycling_speed_key"
android:title="@string/settings_sensor_cycling_distance_speed" />