mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-11 21:43:05 +02:00
@@ -11,6 +11,7 @@ import de.dennisguse.opentracks.data.models.Cadence;
|
|||||||
import de.dennisguse.opentracks.data.models.Distance;
|
import de.dennisguse.opentracks.data.models.Distance;
|
||||||
import de.dennisguse.opentracks.data.models.Speed;
|
import de.dennisguse.opentracks.data.models.Speed;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCycling;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCycling;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
|
||||||
|
|
||||||
public class BluetoothUtilsTest {
|
public class BluetoothUtilsTest {
|
||||||
@@ -86,10 +87,25 @@ public class BluetoothUtilsTest {
|
|||||||
characteristic.setValue(new byte[]{0, 0, 40, 0});
|
characteristic.setValue(new byte[]{0, 0, 40, 0});
|
||||||
|
|
||||||
// when
|
// when
|
||||||
int power_w = BluetoothUtils.parseCyclingPower(characteristic);
|
SensorDataCyclingPower.Data powerCadence = BluetoothUtils.parseCyclingPower(characteristic);
|
||||||
|
|
||||||
// then
|
// 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
|
@Test
|
||||||
|
|||||||
@@ -254,9 +254,9 @@ public abstract class BluetoothConnectionManager<DataType> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected SensorDataCyclingPower parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
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.Cadence;
|
||||||
import de.dennisguse.opentracks.data.models.Distance;
|
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.data.models.Speed;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCycling;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCycling;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -109,14 +111,45 @@ public class BluetoothUtils {
|
|||||||
return null;
|
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
|
// 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;
|
int valueLength = characteristic.getValue().length;
|
||||||
if (valueLength < 4) {
|
if (valueLength == 0) {
|
||||||
return null;
|
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) {
|
public static SensorDataCycling.CadenceAndSpeed parseCyclingCrankAndWheel(String address, String sensorName, @NonNull BluetoothGattCharacteristic characteristic) {
|
||||||
|
|||||||
@@ -40,13 +40,6 @@ public final class SensorDataCycling {
|
|||||||
this.crankRevolutionsTime = crankRevolutionsTime;
|
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() {
|
public boolean hasData() {
|
||||||
return crankRevolutionsCount != null && crankRevolutionsTime != null;
|
return crankRevolutionsCount != null && crankRevolutionsTime != null;
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-4
@@ -1,6 +1,7 @@
|
|||||||
package de.dennisguse.opentracks.sensors.sensorData;
|
package de.dennisguse.opentracks.sensors.sensorData;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.Power;
|
import de.dennisguse.opentracks.data.models.Power;
|
||||||
|
|
||||||
@@ -17,13 +18,40 @@ public class SensorDataCyclingPower extends SensorData<Power> {
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
protected Power getNoneValue() {
|
||||||
return super.toString() + " power=" + value;
|
return Power.of(0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
protected Power getNoneValue() {
|
public String toString() {
|
||||||
return Power.of(0f);
|
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 +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user