forked from upstream-mirrors/OpenTracks
Merge pull request #1372 from OpenTracksApp/cadence#1352
Cycling cadence from powermeter
This commit is contained in:
+5
-4
@@ -61,7 +61,8 @@ import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
|||||||
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
||||||
import de.dennisguse.opentracks.sensors.AltitudeSumManager;
|
import de.dennisguse.opentracks.sensors.AltitudeSumManager;
|
||||||
import de.dennisguse.opentracks.sensors.BluetoothRemoteSensorManager;
|
import de.dennisguse.opentracks.sensors.BluetoothRemoteSensorManager;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCycling;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadence;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingDistanceSpeed;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataHeartRate;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataHeartRate;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||||
@@ -504,16 +505,16 @@ public class ExportImportTest {
|
|||||||
sensorDataSet.set(new SensorDataCyclingPower("power", "power", Power.of(power)));
|
sensorDataSet.set(new SensorDataCyclingPower("power", "power", Power.of(power)));
|
||||||
sensorDataSet.set(new SensorDataHeartRate("heartRate", "heartRate", HeartRate.of(heartRate)));
|
sensorDataSet.set(new SensorDataHeartRate("heartRate", "heartRate", HeartRate.of(heartRate)));
|
||||||
|
|
||||||
SensorDataCycling.CyclingCadence cyclingCadence = Mockito.mock(SensorDataCycling.CyclingCadence.class);
|
SensorDataCyclingCadence cyclingCadence = Mockito.mock(SensorDataCyclingCadence.class);
|
||||||
Mockito.when(cyclingCadence.hasValue()).thenReturn(true);
|
Mockito.when(cyclingCadence.hasValue()).thenReturn(true);
|
||||||
Mockito.when(cyclingCadence.getValue()).thenReturn(Cadence.of(cadence));
|
Mockito.when(cyclingCadence.getValue()).thenReturn(Cadence.of(cadence));
|
||||||
sensorDataSet.set(cyclingCadence);
|
sensorDataSet.set(cyclingCadence);
|
||||||
|
|
||||||
if (distance != null && speed != null) {
|
if (distance != null && speed != null) {
|
||||||
SensorDataCycling.DistanceSpeed.Data distanceSpeedData = Mockito.mock(SensorDataCycling.DistanceSpeed.Data.class);
|
SensorDataCyclingDistanceSpeed.Data distanceSpeedData = Mockito.mock(SensorDataCyclingDistanceSpeed.Data.class);
|
||||||
Mockito.when(distanceSpeedData.getDistanceOverall()).thenReturn(distance);
|
Mockito.when(distanceSpeedData.getDistanceOverall()).thenReturn(distance);
|
||||||
Mockito.when(distanceSpeedData.getSpeed()).thenReturn(Speed.of(speed));
|
Mockito.when(distanceSpeedData.getSpeed()).thenReturn(Speed.of(speed));
|
||||||
SensorDataCycling.DistanceSpeed distanceSpeed = Mockito.mock(SensorDataCycling.DistanceSpeed.class);
|
SensorDataCyclingDistanceSpeed distanceSpeed = Mockito.mock(SensorDataCyclingDistanceSpeed.class);
|
||||||
Mockito.when(distanceSpeed.hasValue()).thenReturn(true);
|
Mockito.when(distanceSpeed.hasValue()).thenReturn(true);
|
||||||
Mockito.when(distanceSpeed.getValue()).thenReturn(distanceSpeedData);
|
Mockito.when(distanceSpeed.getValue()).thenReturn(distanceSpeedData);
|
||||||
sensorDataSet.set(distanceSpeed);
|
sensorDataSet.set(distanceSpeed);
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ import org.junit.Test;
|
|||||||
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.Speed;
|
import de.dennisguse.opentracks.data.models.Speed;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCycling;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadenceAndDistanceSpeed;
|
||||||
|
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 {
|
||||||
@@ -18,7 +19,7 @@ public class BluetoothUtilsTest {
|
|||||||
@Test
|
@Test
|
||||||
public void parseHeartRate_uint8() {
|
public void parseHeartRate_uint8() {
|
||||||
// given
|
// given
|
||||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.HEART_RATE_SERVICE_UUID, 0, 0);
|
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.HEARTRATE.getServiceUUID(), 0, 0);
|
||||||
characteristic.setValue(new byte[]{0x02, 0x3C});
|
characteristic.setValue(new byte[]{0x02, 0x3C});
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -31,7 +32,7 @@ public class BluetoothUtilsTest {
|
|||||||
@Test
|
@Test
|
||||||
public void parseHeartRate_uint16() {
|
public void parseHeartRate_uint16() {
|
||||||
// given
|
// given
|
||||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.HEART_RATE_SERVICE_UUID, 0, 0);
|
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.HEARTRATE.getServiceUUID(), 0, 0);
|
||||||
characteristic.setValue(new byte[]{0x01, 0x01, 0x01});
|
characteristic.setValue(new byte[]{0x01, 0x01, 0x01});
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -43,11 +44,11 @@ public class BluetoothUtilsTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void parseCyclingSpeedCadence_crankOnly() {
|
public void parseCyclingSpeedCadence_crankOnly() {
|
||||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, 0, 0);
|
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE.getServiceUUID(), 0, 0);
|
||||||
characteristic.setValue(new byte[]{0x02, (byte) 0xC8, 0x00, 0x00, 0x00, 0x06, (byte) 0x99});
|
characteristic.setValue(new byte[]{0x02, (byte) 0xC8, 0x00, 0x00, 0x00, 0x06, (byte) 0x99});
|
||||||
|
|
||||||
// when
|
// when
|
||||||
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
SensorDataCyclingCadenceAndDistanceSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertNull(sensor.getDistanceSpeed());
|
assertNull(sensor.getDistanceSpeed());
|
||||||
@@ -56,11 +57,11 @@ public class BluetoothUtilsTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void parseCyclingSpeedCadence_wheelOnly() {
|
public void parseCyclingSpeedCadence_wheelOnly() {
|
||||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, 0, 0);
|
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE.getServiceUUID(), 0, 0);
|
||||||
characteristic.setValue(new byte[]{0x01, (byte) 0xFF, (byte) 0xFF, 0, 1, 0x45, (byte) 0x99});
|
characteristic.setValue(new byte[]{0x01, (byte) 0xFF, (byte) 0xFF, 0, 1, 0x45, (byte) 0x99});
|
||||||
|
|
||||||
// when
|
// when
|
||||||
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
SensorDataCyclingCadenceAndDistanceSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(65535 + 16777216, sensor.getDistanceSpeed().getWheelRevolutionsCount());
|
assertEquals(65535 + 16777216, sensor.getDistanceSpeed().getWheelRevolutionsCount());
|
||||||
@@ -69,11 +70,11 @@ public class BluetoothUtilsTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void parseCyclingSpeedCadence_crankWheel() {
|
public void parseCyclingSpeedCadence_crankWheel() {
|
||||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, 0, 0);
|
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE.getServiceUUID(), 0, 0);
|
||||||
characteristic.setValue(new byte[]{0x03, (byte) 0xC8, 0x00, 0x00, 0x01, 0x06, (byte) 0x99, (byte) 0xE1, 0x00, 0x45, (byte) 0x99});
|
characteristic.setValue(new byte[]{0x03, (byte) 0xC8, 0x00, 0x00, 0x01, 0x06, (byte) 0x99, (byte) 0xE1, 0x00, 0x45, (byte) 0x99});
|
||||||
|
|
||||||
// when
|
// when
|
||||||
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
SensorDataCyclingCadenceAndDistanceSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(200 + 16777216, sensor.getDistanceSpeed().getWheelRevolutionsCount());
|
assertEquals(200 + 16777216, sensor.getDistanceSpeed().getWheelRevolutionsCount());
|
||||||
@@ -82,19 +83,34 @@ public class BluetoothUtilsTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void parseCyclingPower_power() {
|
public void parseCyclingPower_power() {
|
||||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_POWER_MEASUREMENT_CHAR_UUID, 0, 0);
|
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_POWER.getServiceUUID(), 0, 0);
|
||||||
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().getValue().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().getValue().getW(), 0.01);
|
||||||
|
|
||||||
|
assertEquals(12, powerCadence.getCadence().getCrankRevolutionsCount());
|
||||||
|
assertEquals(17125, powerCadence.getCadence().getCrankRevolutionsTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void parseRunningSpeedAndCadence_with_distance() {
|
public void parseRunningSpeedAndCadence_with_distance() {
|
||||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.RUNNING_RUNNING_SPEED_CADENCE_CHAR_UUID, 0, 0);
|
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.RUNNING_SPEED_CADENCE.getServiceUUID(), 0, 0);
|
||||||
characteristic.setValue(new byte[]{2, 0, 5, 80, (byte) 0xFF, (byte) 0xFF, 0, 1});
|
characteristic.setValue(new byte[]{2, 0, 5, 80, (byte) 0xFF, (byte) 0xFF, 0, 1});
|
||||||
|
|
||||||
// when
|
// when
|
||||||
|
|||||||
+24
-24
@@ -21,8 +21,8 @@ public class SensorDataCyclingTest {
|
|||||||
@Test
|
@Test
|
||||||
public void compute_cadence_1() {
|
public void compute_cadence_1() {
|
||||||
// given
|
// given
|
||||||
SensorDataCycling.CyclingCadence previous = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 1, 1024); // 1s
|
SensorDataCyclingCadence previous = new SensorDataCyclingCadence("sensorAddress", "sensorName", 1, 1024); // 1s
|
||||||
SensorDataCycling.CyclingCadence current = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 2, 2048); // 2s
|
SensorDataCyclingCadence current = new SensorDataCyclingCadence("sensorAddress", "sensorName", 2, 2048); // 2s
|
||||||
|
|
||||||
// when
|
// when
|
||||||
current.compute(previous);
|
current.compute(previous);
|
||||||
@@ -34,8 +34,8 @@ public class SensorDataCyclingTest {
|
|||||||
@Test
|
@Test
|
||||||
public void compute_cadence_2() {
|
public void compute_cadence_2() {
|
||||||
// given
|
// given
|
||||||
SensorDataCycling.CyclingCadence previous = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 1, 6184);
|
SensorDataCyclingCadence previous = new SensorDataCyclingCadence("sensorAddress", "sensorName", 1, 6184);
|
||||||
SensorDataCycling.CyclingCadence current = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 2, 8016);
|
SensorDataCyclingCadence current = new SensorDataCyclingCadence("sensorAddress", "sensorName", 2, 8016);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
current.compute(previous);
|
current.compute(previous);
|
||||||
@@ -47,8 +47,8 @@ public class SensorDataCyclingTest {
|
|||||||
@Test
|
@Test
|
||||||
public void compute_cadence_sameCount() {
|
public void compute_cadence_sameCount() {
|
||||||
// given
|
// given
|
||||||
SensorDataCycling.CyclingCadence previous = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 1, 1024);
|
SensorDataCyclingCadence previous = new SensorDataCyclingCadence("sensorAddress", "sensorName", 1, 1024);
|
||||||
SensorDataCycling.CyclingCadence current = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 1, 2048);
|
SensorDataCyclingCadence current = new SensorDataCyclingCadence("sensorAddress", "sensorName", 1, 2048);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
current.compute(previous);
|
current.compute(previous);
|
||||||
@@ -61,8 +61,8 @@ public class SensorDataCyclingTest {
|
|||||||
@Test
|
@Test
|
||||||
public void compute_cadence_sameTime() {
|
public void compute_cadence_sameTime() {
|
||||||
// given
|
// given
|
||||||
SensorDataCycling.CyclingCadence previous = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 1, 1024);
|
SensorDataCyclingCadence previous = new SensorDataCyclingCadence("sensorAddress", "sensorName", 1, 1024);
|
||||||
SensorDataCycling.CyclingCadence current = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 2, 1024);
|
SensorDataCyclingCadence current = new SensorDataCyclingCadence("sensorAddress", "sensorName", 2, 1024);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
current.compute(previous);
|
current.compute(previous);
|
||||||
@@ -74,8 +74,8 @@ public class SensorDataCyclingTest {
|
|||||||
@Test
|
@Test
|
||||||
public void compute_cadence_rollOverTime() {
|
public void compute_cadence_rollOverTime() {
|
||||||
// given
|
// given
|
||||||
SensorDataCycling.CyclingCadence previous = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 1, UintUtils.UINT16_MAX - 1024);
|
SensorDataCyclingCadence previous = new SensorDataCyclingCadence("sensorAddress", "sensorName", 1, UintUtils.UINT16_MAX - 1024);
|
||||||
SensorDataCycling.CyclingCadence current = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 2, 0);
|
SensorDataCyclingCadence current = new SensorDataCyclingCadence("sensorAddress", "sensorName", 2, 0);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
current.compute(previous);
|
current.compute(previous);
|
||||||
@@ -89,8 +89,8 @@ public class SensorDataCyclingTest {
|
|||||||
@Deprecated
|
@Deprecated
|
||||||
public void compute_cadence_rollOverCount() {
|
public void compute_cadence_rollOverCount() {
|
||||||
// given
|
// given
|
||||||
SensorDataCycling.CyclingCadence previous = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
|
SensorDataCyclingCadence previous = new SensorDataCyclingCadence("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
|
||||||
SensorDataCycling.CyclingCadence current = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 0, 2048);
|
SensorDataCyclingCadence current = new SensorDataCyclingCadence("sensorAddress", "sensorName", 0, 2048);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
current.compute(previous);
|
current.compute(previous);
|
||||||
@@ -102,8 +102,8 @@ public class SensorDataCyclingTest {
|
|||||||
@Test
|
@Test
|
||||||
public void compute_cadence_overflow() {
|
public void compute_cadence_overflow() {
|
||||||
// given
|
// given
|
||||||
SensorDataCycling.CyclingCadence previous = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
|
SensorDataCyclingCadence previous = new SensorDataCyclingCadence("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
|
||||||
SensorDataCycling.CyclingCadence current = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 0, 2048);
|
SensorDataCyclingCadence current = new SensorDataCyclingCadence("sensorAddress", "sensorName", 0, 2048);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
current.compute(previous);
|
current.compute(previous);
|
||||||
@@ -115,8 +115,8 @@ public class SensorDataCyclingTest {
|
|||||||
@Test
|
@Test
|
||||||
public void compute_speed() {
|
public void compute_speed() {
|
||||||
// given
|
// given
|
||||||
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 1, 6184);
|
SensorDataCyclingDistanceSpeed previous = new SensorDataCyclingDistanceSpeed("sensorAddress", "sensorName", 1, 6184);
|
||||||
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 2, 8016);
|
SensorDataCyclingDistanceSpeed current = new SensorDataCyclingDistanceSpeed("sensorAddress", "sensorName", 2, 8016);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
current.compute(previous, Distance.ofMM(2150));
|
current.compute(previous, Distance.ofMM(2150));
|
||||||
@@ -131,8 +131,8 @@ public class SensorDataCyclingTest {
|
|||||||
@Deprecated
|
@Deprecated
|
||||||
public void compute_speed_rollOverCount() {
|
public void compute_speed_rollOverCount() {
|
||||||
// given
|
// given
|
||||||
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
|
SensorDataCyclingDistanceSpeed previous = new SensorDataCyclingDistanceSpeed("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
|
||||||
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
|
SensorDataCyclingDistanceSpeed current = new SensorDataCyclingDistanceSpeed("sensorAddress", "sensorName", 0, 2048);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
current.compute(previous, Distance.ofMM(2000));
|
current.compute(previous, Distance.ofMM(2000));
|
||||||
@@ -145,8 +145,8 @@ public class SensorDataCyclingTest {
|
|||||||
@Test
|
@Test
|
||||||
public void compute_speed_overflow() {
|
public void compute_speed_overflow() {
|
||||||
// given
|
// given
|
||||||
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
|
SensorDataCyclingDistanceSpeed previous = new SensorDataCyclingDistanceSpeed("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
|
||||||
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
|
SensorDataCyclingDistanceSpeed current = new SensorDataCyclingDistanceSpeed("sensorAddress", "sensorName", 0, 2048);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
current.compute(previous, Distance.ofMM(2000));
|
current.compute(previous, Distance.ofMM(2000));
|
||||||
@@ -158,8 +158,8 @@ public class SensorDataCyclingTest {
|
|||||||
@Test
|
@Test
|
||||||
public void equals_speed_with_no_data() {
|
public void equals_speed_with_no_data() {
|
||||||
// given
|
// given
|
||||||
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress");
|
SensorDataCyclingDistanceSpeed previous = new SensorDataCyclingDistanceSpeed("sensorAddress");
|
||||||
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
|
SensorDataCyclingDistanceSpeed current = new SensorDataCyclingDistanceSpeed("sensorAddress", "sensorName", 0, 2048);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
previous.toString();
|
previous.toString();
|
||||||
@@ -172,8 +172,8 @@ public class SensorDataCyclingTest {
|
|||||||
@Test
|
@Test
|
||||||
public void equals_cadence_with_no_data() {
|
public void equals_cadence_with_no_data() {
|
||||||
// given
|
// given
|
||||||
SensorDataCycling.CyclingCadence previous = new SensorDataCycling.CyclingCadence("sensorAddress");
|
SensorDataCyclingCadence previous = new SensorDataCyclingCadence("sensorAddress");
|
||||||
SensorDataCycling.CyclingCadence current = new SensorDataCycling.CyclingCadence("sensorAddress", "sensorName", 0, 2048);
|
SensorDataCyclingCadence current = new SensorDataCyclingCadence("sensorAddress", "sensorName", 0, 2048);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
previous.toString();
|
previous.toString();
|
||||||
|
|||||||
@@ -30,16 +30,11 @@ import android.util.Log;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.Cadence;
|
|
||||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
|
||||||
import de.dennisguse.opentracks.data.models.Power;
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorData;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorData;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCycling;
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataHeartRate;
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages connection to a Bluetooth LE sensor and subscribes for onChange-notifications.
|
* Manages connection to a Bluetooth LE sensor and subscribes for onChange-notifications.
|
||||||
@@ -51,8 +46,7 @@ public abstract class BluetoothConnectionManager<DataType> {
|
|||||||
|
|
||||||
private final SensorDataObserver observer;
|
private final SensorDataObserver observer;
|
||||||
|
|
||||||
private final UUID serviceUUUID;
|
private final List<ServiceMeasurementUUID> serviceMeasurementUUIDs;
|
||||||
private final UUID measurementUUID;
|
|
||||||
private BluetoothGatt bluetoothGatt;
|
private BluetoothGatt bluetoothGatt;
|
||||||
|
|
||||||
private final BluetoothGattCallback connectCallback = new BluetoothGattCallback() {
|
private final BluetoothGattCallback connectCallback = new BluetoothGattCallback() {
|
||||||
@@ -81,15 +75,24 @@ public abstract class BluetoothConnectionManager<DataType> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onServicesDiscovered(@NonNull BluetoothGatt gatt, int status) {
|
public void onServicesDiscovered(@NonNull BluetoothGatt gatt, int status) {
|
||||||
BluetoothGattService service = gatt.getService(serviceUUUID);
|
BluetoothGattService gattService = null;
|
||||||
if (service == null) {
|
ServiceMeasurementUUID serviceMeasurement = null;
|
||||||
Log.e(TAG, "Could not get service for address=" + gatt.getDevice().getAddress() + " serviceUUID=" + serviceUUUID);
|
for (ServiceMeasurementUUID s : serviceMeasurementUUIDs) {
|
||||||
|
gattService = gatt.getService(s.getServiceUUID());
|
||||||
|
if (gattService != null) {
|
||||||
|
serviceMeasurement = s;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gattService == null) {
|
||||||
|
Log.e(TAG, "Could not get gattService for address=" + gatt.getDevice().getAddress() + " serviceUUID=" + serviceMeasurement);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BluetoothGattCharacteristic characteristic = service.getCharacteristic(measurementUUID);
|
BluetoothGattCharacteristic characteristic = gattService.getCharacteristic(serviceMeasurement.getMeasurementUUID());
|
||||||
if (characteristic == null) {
|
if (characteristic == null) {
|
||||||
Log.e(TAG, "Could not get BluetoothCharacteristic for address=" + gatt.getDevice().getAddress() + " serviceUUID=" + serviceUUUID + " characteristicUUID=" + measurementUUID);
|
Log.e(TAG, "Could not get BluetoothCharacteristic for address=" + gatt.getDevice().getAddress() + " serviceUUID=" + serviceMeasurement.getServiceUUID() + " characteristicUUID=" + serviceMeasurement.getMeasurementUUID());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
gatt.setCharacteristicNotification(characteristic, true);
|
gatt.setCharacteristicNotification(characteristic, true);
|
||||||
@@ -108,8 +111,16 @@ public abstract class BluetoothConnectionManager<DataType> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCharacteristicChanged(BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic) {
|
public void onCharacteristicChanged(BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic) {
|
||||||
Log.d(TAG, "Received data from " + gatt.getDevice().getAddress());
|
UUID serviceUUID = characteristic.getService().getUuid();
|
||||||
SensorData<DataType> sensorData = parsePayload(gatt.getDevice().getName(), gatt.getDevice().getAddress(), characteristic);
|
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) {
|
if (sensorData != null) {
|
||||||
Log.d(TAG, "Decoded data from " + gatt.getDevice().getAddress() + ": " + sensorData);
|
Log.d(TAG, "Decoded data from " + gatt.getDevice().getAddress() + ": " + sensorData);
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
@@ -122,15 +133,20 @@ public abstract class BluetoothConnectionManager<DataType> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
BluetoothConnectionManager(UUID serviceUUUID, UUID measurementUUID, SensorDataObserver observer) {
|
BluetoothConnectionManager(ServiceMeasurementUUID serviceUUUID, SensorDataObserver observer) {
|
||||||
this.serviceUUUID = serviceUUUID;
|
this.serviceMeasurementUUIDs = List.of(serviceUUUID);
|
||||||
this.measurementUUID = measurementUUID;
|
this.observer = observer;
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothConnectionManager(List<ServiceMeasurementUUID> serviceUUUID, SensorDataObserver observer) {
|
||||||
|
this.serviceMeasurementUUIDs = serviceUUUID;
|
||||||
this.observer = observer;
|
this.observer = observer;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void connect(Context context, @NonNull BluetoothDevice device) {
|
synchronized void connect(Context context, @NonNull BluetoothDevice device) {
|
||||||
if (bluetoothGatt != null) {
|
if (bluetoothGatt != null) {
|
||||||
Log.w(TAG, "Already connected; ignoring.");
|
Log.w(TAG, "Already connected; ignoring.");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "Connecting to: " + device);
|
Log.d(TAG, "Connecting to: " + device);
|
||||||
@@ -172,114 +188,7 @@ public abstract class BluetoothConnectionManager<DataType> {
|
|||||||
/**
|
/**
|
||||||
* @return null if data could not be parsed.
|
* @return null if data could not be parsed.
|
||||||
*/
|
*/
|
||||||
protected abstract SensorData<DataType> parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic);
|
protected abstract SensorData<DataType> parsePayload(@NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, @NonNull BluetoothGattCharacteristic characteristic);
|
||||||
|
|
||||||
public static class HeartRateConnectionManager extends BluetoothConnectionManager<HeartRate> {
|
|
||||||
|
|
||||||
HeartRateConnectionManager(@NonNull SensorDataObserver observer) {
|
|
||||||
super(BluetoothUtils.HEART_RATE_SERVICE_UUID, BluetoothUtils.HEART_RATE_MEASUREMENT_CHAR_UUID, 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class CyclingCadence extends BluetoothConnectionManager<Cadence> {
|
|
||||||
|
|
||||||
CyclingCadence(SensorDataObserver observer) {
|
|
||||||
super(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, BluetoothUtils.CYCLING_SPEED_CADENCE_MEASUREMENT_CHAR_UUID, observer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SensorDataCycling.CyclingCadence createEmptySensorData(String address) {
|
|
||||||
return new SensorDataCycling.CyclingCadence(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SensorDataCycling.CyclingCadence parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
|
||||||
SensorDataCycling.CadenceAndSpeed cadenceAndSpeed = BluetoothUtils.parseCyclingCrankAndWheel(address, sensorName, characteristic);
|
|
||||||
if (cadenceAndSpeed == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cadenceAndSpeed.getCadence() != null) {
|
|
||||||
return cadenceAndSpeed.getCadence();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class CyclingDistanceSpeed extends BluetoothConnectionManager<SensorDataCycling.DistanceSpeed.Data> {
|
|
||||||
|
|
||||||
CyclingDistanceSpeed(SensorDataObserver observer) {
|
|
||||||
super(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, BluetoothUtils.CYCLING_SPEED_CADENCE_MEASUREMENT_CHAR_UUID, observer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SensorDataCycling.DistanceSpeed createEmptySensorData(String address) {
|
|
||||||
return new SensorDataCycling.DistanceSpeed(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SensorDataCycling.DistanceSpeed parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
|
||||||
SensorDataCycling.CadenceAndSpeed cadenceAndSpeed = BluetoothUtils.parseCyclingCrankAndWheel(address, sensorName, characteristic);
|
|
||||||
if (cadenceAndSpeed == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cadenceAndSpeed.getDistanceSpeed() != null) {
|
|
||||||
return cadenceAndSpeed.getDistanceSpeed();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class CyclingPower extends BluetoothConnectionManager<Power> {
|
|
||||||
|
|
||||||
CyclingPower(@NonNull SensorDataObserver observer) {
|
|
||||||
super(BluetoothUtils.CYCLING_POWER_UUID, BluetoothUtils.CYCLING_POWER_MEASUREMENT_CHAR_UUID, observer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SensorDataCyclingPower createEmptySensorData(String address) {
|
|
||||||
return new SensorDataCyclingPower(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SensorDataCyclingPower parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
|
||||||
Integer cyclingPower = BluetoothUtils.parseCyclingPower(characteristic);
|
|
||||||
|
|
||||||
return cyclingPower != null ? new SensorDataCyclingPower(address, sensorName, Power.of(cyclingPower)) : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class RunningSpeedAndCadence extends BluetoothConnectionManager<SensorDataRunning.Data> {
|
|
||||||
|
|
||||||
RunningSpeedAndCadence(@NonNull SensorDataObserver observer) {
|
|
||||||
super(BluetoothUtils.RUNNING_RUNNING_SPEED_CADENCE_UUID, BluetoothUtils.RUNNING_RUNNING_SPEED_CADENCE_CHAR_UUID, observer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SensorDataRunning createEmptySensorData(String address) {
|
|
||||||
return new SensorDataRunning(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SensorDataRunning parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
|
||||||
return BluetoothUtils.parseRunningSpeedAndCadence(address, sensorName, characteristic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SensorDataObserver {
|
interface SensorDataObserver {
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ import de.dennisguse.opentracks.R;
|
|||||||
import de.dennisguse.opentracks.data.models.Distance;
|
import de.dennisguse.opentracks.data.models.Distance;
|
||||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorData;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorData;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCycling;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadence;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingDistanceSpeed;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
@@ -64,11 +65,11 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
|
|||||||
|
|
||||||
private Distance preferenceWheelCircumference;
|
private Distance preferenceWheelCircumference;
|
||||||
|
|
||||||
private final BluetoothConnectionManager.HeartRateConnectionManager heartRate = new BluetoothConnectionManager.HeartRateConnectionManager(this);
|
private final BluetoothRemoteSensorManagerHeartRate heartRate = new BluetoothRemoteSensorManagerHeartRate(this);
|
||||||
private final BluetoothConnectionManager.CyclingCadence cyclingCadence = new BluetoothConnectionManager.CyclingCadence(this);
|
private final BluetoothRemoteSensorManagerCyclingCadence cyclingCadence = new BluetoothRemoteSensorManagerCyclingCadence(this);
|
||||||
private final BluetoothConnectionManager.CyclingDistanceSpeed cyclingSpeed = new BluetoothConnectionManager.CyclingDistanceSpeed(this);
|
private final BluetoothRemoteSensorManagerCyclingDistanceSpeed cyclingSpeed = new BluetoothRemoteSensorManagerCyclingDistanceSpeed(this);
|
||||||
private final BluetoothConnectionManager.CyclingPower cyclingPower = new BluetoothConnectionManager.CyclingPower(this);
|
private final BluetoothRemoteSensorManagerCyclingPower cyclingPower = new BluetoothRemoteSensorManagerCyclingPower(this);
|
||||||
private final BluetoothConnectionManager.RunningSpeedAndCadence runningSpeedAndCadence = new BluetoothConnectionManager.RunningSpeedAndCadence(this);
|
private final BluetoothRemoteSensorManagerRunningSpeedAndCadence runningSpeedAndCadence = new BluetoothRemoteSensorManagerRunningSpeedAndCadence(this);
|
||||||
|
|
||||||
private final SensorDataSet sensorDataSet = new SensorDataSet();
|
private final SensorDataSet sensorDataSet = new SensorDataSet();
|
||||||
|
|
||||||
@@ -186,24 +187,24 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void onChanged(SensorData<?> sensorData) {
|
public synchronized void onChanged(SensorData<?> sensorData) {
|
||||||
if (sensorData instanceof SensorDataCycling.CyclingCadence) {
|
if (sensorData instanceof SensorDataCyclingCadence) {
|
||||||
SensorDataCycling.CyclingCadence previous = sensorDataSet.getCyclingCadence();
|
SensorDataCyclingCadence previous = sensorDataSet.getCyclingCadence();
|
||||||
Log.d(TAG, "Previous: " + previous + "; current: " + sensorData);
|
Log.d(TAG, "Previous: " + previous + "; current: " + sensorData);
|
||||||
|
|
||||||
if (sensorData.equals(previous)) {
|
if (sensorData.equals(previous)) {
|
||||||
Log.d(TAG, "onChanged: cadence data repeated.");
|
Log.d(TAG, "onChanged: cadence data repeated.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
((SensorDataCycling.CyclingCadence) sensorData).compute(previous);
|
((SensorDataCyclingCadence) sensorData).compute(previous);
|
||||||
}
|
}
|
||||||
if (sensorData instanceof SensorDataCycling.DistanceSpeed) {
|
if (sensorData instanceof SensorDataCyclingDistanceSpeed) {
|
||||||
SensorDataCycling.DistanceSpeed previous = sensorDataSet.getCyclingDistanceSpeed();
|
SensorDataCyclingDistanceSpeed previous = sensorDataSet.getCyclingDistanceSpeed();
|
||||||
Log.d(TAG, "Previous: " + previous + "; Current" + sensorData);
|
Log.d(TAG, "Previous: " + previous + "; Current" + sensorData);
|
||||||
if (sensorData.equals(previous)) {
|
if (sensorData.equals(previous)) {
|
||||||
Log.d(TAG, "onChanged: cycling speed data repeated.");
|
Log.d(TAG, "onChanged: cycling speed data repeated.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
((SensorDataCycling.DistanceSpeed) sensorData).compute(previous, preferenceWheelCircumference);
|
((SensorDataCyclingDistanceSpeed) sensorData).compute(previous, preferenceWheelCircumference);
|
||||||
}
|
}
|
||||||
if (sensorData instanceof SensorDataRunning) {
|
if (sensorData instanceof SensorDataRunning) {
|
||||||
SensorDataRunning previous = sensorDataSet.getRunningDistanceSpeedCadence();
|
SensorDataRunning previous = sensorDataSet.getRunningDistanceSpeedCadence();
|
||||||
|
|||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
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_CADENCE, observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SensorDataCyclingCadence createEmptySensorData(String address) {
|
||||||
|
return new SensorDataCyclingCadence(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
package de.dennisguse.opentracks.sensors;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadenceAndDistanceSpeed;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingDistanceSpeed;
|
||||||
|
|
||||||
|
public class BluetoothRemoteSensorManagerCyclingDistanceSpeed extends BluetoothConnectionManager<SensorDataCyclingDistanceSpeed.Data> {
|
||||||
|
|
||||||
|
BluetoothRemoteSensorManagerCyclingDistanceSpeed(SensorDataObserver observer) {
|
||||||
|
super(BluetoothUtils.CYCLING_SPEED_CADENCE, observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SensorDataCyclingDistanceSpeed createEmptySensorData(String address) {
|
||||||
|
return new SensorDataCyclingDistanceSpeed(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SensorDataCyclingDistanceSpeed parsePayload(ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
||||||
|
SensorDataCyclingCadenceAndDistanceSpeed cadenceAndSpeed = BluetoothUtils.parseCyclingCrankAndWheel(address, sensorName, characteristic);
|
||||||
|
if (cadenceAndSpeed == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cadenceAndSpeed.getDistanceSpeed() != null) {
|
||||||
|
return cadenceAndSpeed.getDistanceSpeed();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
package de.dennisguse.opentracks.sensors;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.data.models.Power;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
|
||||||
|
|
||||||
|
public class BluetoothRemoteSensorManagerCyclingPower extends BluetoothConnectionManager<Power> {
|
||||||
|
|
||||||
|
BluetoothRemoteSensorManagerCyclingPower(@NonNull SensorDataObserver observer) {
|
||||||
|
super(BluetoothUtils.CYCLING_POWER, observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SensorDataCyclingPower createEmptySensorData(String address) {
|
||||||
|
return new SensorDataCyclingPower(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SensorDataCyclingPower parsePayload(@NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
||||||
|
SensorDataCyclingPower.Data cyclingPower = BluetoothUtils.parseCyclingPower(address, sensorName, characteristic);
|
||||||
|
|
||||||
|
return cyclingPower != null ? cyclingPower.getPower() : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
package de.dennisguse.opentracks.sensors;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataHeartRate;
|
||||||
|
|
||||||
|
public class BluetoothRemoteSensorManagerHeartRate extends BluetoothConnectionManager<HeartRate> {
|
||||||
|
|
||||||
|
BluetoothRemoteSensorManagerHeartRate(@NonNull SensorDataObserver observer) {
|
||||||
|
super(BluetoothUtils.HEARTRATE, observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SensorDataHeartRate createEmptySensorData(String address) {
|
||||||
|
return new SensorDataHeartRate(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
package de.dennisguse.opentracks.sensors;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
|
||||||
|
|
||||||
|
public class BluetoothRemoteSensorManagerRunningSpeedAndCadence extends BluetoothConnectionManager<SensorDataRunning.Data> {
|
||||||
|
|
||||||
|
BluetoothRemoteSensorManagerRunningSpeedAndCadence(@NonNull SensorDataObserver observer) {
|
||||||
|
super(BluetoothUtils.RUNNING_SPEED_CADENCE, observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SensorDataRunning createEmptySensorData(String address) {
|
||||||
|
return new SensorDataRunning(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SensorDataRunning parsePayload(@NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
||||||
|
return BluetoothUtils.parseRunningSpeedAndCadence(address, sensorName, characteristic);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,8 +30,12 @@ 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.SensorDataCyclingCadence;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadenceAndDistanceSpeed;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingDistanceSpeed;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,23 +47,40 @@ public class BluetoothUtils {
|
|||||||
|
|
||||||
public static final UUID CLIENT_CHARACTERISTIC_CONFIG_UUID = new UUID(0x290200001000L, 0x800000805f9b34fbL);
|
public static final UUID CLIENT_CHARACTERISTIC_CONFIG_UUID = new UUID(0x290200001000L, 0x800000805f9b34fbL);
|
||||||
|
|
||||||
public static final UUID HEART_RATE_SERVICE_UUID = new UUID(0x180D00001000L, 0x800000805f9b34fbL);
|
public static final ServiceMeasurementUUID HEARTRATE = new ServiceMeasurementUUID(
|
||||||
public static final UUID HEART_RATE_MEASUREMENT_CHAR_UUID = new UUID(0x2A3700001000L, 0x800000805f9b34fbL);
|
new UUID(0x180D00001000L, 0x800000805f9b34fbL),
|
||||||
|
new UUID(0x2A3700001000L, 0x800000805f9b34fbL)
|
||||||
|
);
|
||||||
|
|
||||||
public static final List<UUID> HEART_RATE_SUPPORTING_DEVICES = Collections.unmodifiableList(Arrays.asList(
|
// Used for device discovery in preferences
|
||||||
BluetoothUtils.HEART_RATE_SERVICE_UUID,
|
public static final List<ServiceMeasurementUUID> HEART_RATE_SUPPORTING_DEVICES = Collections.unmodifiableList(Arrays.asList(
|
||||||
|
HEARTRATE,
|
||||||
//Devices that support HEART_RATE_SERVICE_UUID, but do not announce HEART_RATE_SERVICE_UUID in there BLE announcement messages (during device discovery).
|
//Devices that support HEART_RATE_SERVICE_UUID, but do not announce HEART_RATE_SERVICE_UUID in there BLE announcement messages (during device discovery).
|
||||||
UUID.fromString("0000fee0-0000-1000-8000-00805f9b34fb") //Miband3
|
new ServiceMeasurementUUID(
|
||||||
));
|
UUID.fromString("0000fee0-0000-1000-8000-00805f9b34fb"), //Miband3
|
||||||
|
HEARTRATE.getMeasurementUUID()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
public static final UUID CYCLING_POWER_UUID = new UUID(0x181800001000L, 0x800000805f9b34fbL);
|
public static final ServiceMeasurementUUID CYCLING_POWER = new ServiceMeasurementUUID(
|
||||||
public static final UUID CYCLING_POWER_MEASUREMENT_CHAR_UUID = new UUID(0x2A6300001000L, 0x800000805f9b34fbL);
|
new UUID(0x181800001000L, 0x800000805f9b34fbL),
|
||||||
|
new UUID(0x2A6300001000L, 0x800000805f9b34fbL)
|
||||||
|
);
|
||||||
|
|
||||||
public static final UUID CYCLING_SPEED_CADENCE_SERVICE_UUID = new UUID(0x181600001000L, 0x800000805f9b34fbL);
|
public static final ServiceMeasurementUUID CYCLING_SPEED_CADENCE = new ServiceMeasurementUUID(
|
||||||
public static final UUID CYCLING_SPEED_CADENCE_MEASUREMENT_CHAR_UUID = new UUID(0x2A5B00001000L, 0x800000805f9b34fbL);
|
new UUID(0x181600001000L, 0x800000805f9b34fbL),
|
||||||
|
new UUID(0x2A5B00001000L, 0x800000805f9b34fbL)
|
||||||
|
);
|
||||||
|
|
||||||
public static final UUID RUNNING_RUNNING_SPEED_CADENCE_UUID = new UUID(0x181400001000L, 0x800000805f9b34fbL);
|
public static final List<ServiceMeasurementUUID> CYCLING_CADENCE = List.of(
|
||||||
public static final UUID RUNNING_RUNNING_SPEED_CADENCE_CHAR_UUID = new UUID(0x2A5300001000L, 0x800000805f9b34fbL);
|
CYCLING_POWER,
|
||||||
|
CYCLING_SPEED_CADENCE
|
||||||
|
);
|
||||||
|
|
||||||
|
public static final ServiceMeasurementUUID RUNNING_SPEED_CADENCE = new ServiceMeasurementUUID(
|
||||||
|
new UUID(0x181400001000L, 0x800000805f9b34fbL),
|
||||||
|
new UUID(0x2A5300001000L, 0x800000805f9b34fbL)
|
||||||
|
);
|
||||||
|
|
||||||
private static final String TAG = BluetoothUtils.class.getSimpleName();
|
private static final String TAG = BluetoothUtils.class.getSimpleName();
|
||||||
|
|
||||||
@@ -98,17 +119,49 @@ public class BluetoothUtils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Integer 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
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
SensorDataCyclingCadence 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 SensorDataCyclingCadence(address, sensorName, crankCount, crankTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return new SensorDataCyclingPower.Data(new SensorDataCyclingPower(sensorName, address, Power.of(instantaneousPower)), cadence);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SensorDataCycling.CadenceAndSpeed parseCyclingCrankAndWheel(String address, String sensorName, @NonNull BluetoothGattCharacteristic characteristic) {
|
public static SensorDataCyclingCadenceAndDistanceSpeed parseCyclingCrankAndWheel(String address, String sensorName, @NonNull BluetoothGattCharacteristic characteristic) {
|
||||||
// DOCUMENTATION https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.csc_measurement.xml
|
// DOCUMENTATION https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.csc_measurement.xml
|
||||||
int valueLength = characteristic.getValue().length;
|
int valueLength = characteristic.getValue().length;
|
||||||
if (valueLength == 0) {
|
if (valueLength == 0) {
|
||||||
@@ -120,25 +173,25 @@ public class BluetoothUtils {
|
|||||||
boolean hasCrank = (flags & 0x02) > 0;
|
boolean hasCrank = (flags & 0x02) > 0;
|
||||||
|
|
||||||
int index = 1;
|
int index = 1;
|
||||||
SensorDataCycling.DistanceSpeed speed = null;
|
SensorDataCyclingDistanceSpeed speed = null;
|
||||||
if (hasWheel && valueLength - index >= 6) {
|
if (hasWheel && valueLength - index >= 6) {
|
||||||
int wheelTotalRevolutionCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, index);
|
int wheelTotalRevolutionCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, index);
|
||||||
index += 4;
|
index += 4;
|
||||||
int wheelTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); // 1/1024s
|
int wheelTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); // 1/1024s
|
||||||
speed = new SensorDataCycling.DistanceSpeed(address, sensorName, wheelTotalRevolutionCount, wheelTime);
|
speed = new SensorDataCyclingDistanceSpeed(address, sensorName, wheelTotalRevolutionCount, wheelTime);
|
||||||
index += 2;
|
index += 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
SensorDataCycling.CyclingCadence cadence = null;
|
SensorDataCyclingCadence cadence = null;
|
||||||
if (hasCrank && valueLength - index >= 4) {
|
if (hasCrank && valueLength - index >= 4) {
|
||||||
long crankCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index);
|
long crankCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index);
|
||||||
index += 2;
|
index += 2;
|
||||||
|
|
||||||
int crankTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); // 1/1024s
|
int crankTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); // 1/1024s
|
||||||
cadence = new SensorDataCycling.CyclingCadence(address, sensorName, crankCount, crankTime);
|
cadence = new SensorDataCyclingCadence(address, sensorName, crankCount, crankTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SensorDataCycling.CadenceAndSpeed(address, sensorName, cadence, speed);
|
return new SensorDataCyclingCadenceAndDistanceSpeed(address, sensorName, cadence, speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SensorDataRunning parseRunningSpeedAndCadence(String address, String sensorName, @NonNull BluetoothGattCharacteristic characteristic) {
|
public static SensorDataRunning parseRunningSpeedAndCadence(String address, String sensorName, @NonNull BluetoothGattCharacteristic characteristic) {
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package de.dennisguse.opentracks.sensors;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class ServiceMeasurementUUID {
|
||||||
|
|
||||||
|
private final UUID serviceUUID;
|
||||||
|
private final UUID measurementUUID;
|
||||||
|
|
||||||
|
public ServiceMeasurementUUID(UUID serviceUUID, UUID measurementUUID) {
|
||||||
|
this.serviceUUID = serviceUUID;
|
||||||
|
this.measurementUUID = measurementUUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getServiceUUID() {
|
||||||
|
return serviceUUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getMeasurementUUID() {
|
||||||
|
return measurementUUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ServiceMeasurementUUID{" +
|
||||||
|
"serviceUUID=" + serviceUUID +
|
||||||
|
", measurementUUID=" + measurementUUID +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
ServiceMeasurementUUID that = (ServiceMeasurementUUID) o;
|
||||||
|
return Objects.equals(serviceUUID, that.serviceUUID) && Objects.equals(measurementUUID, that.measurementUUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(serviceUUID, measurementUUID);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,255 +0,0 @@
|
|||||||
package de.dennisguse.opentracks.sensors.sensorData;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.core.util.Pair;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
|
|
||||||
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.UintUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides cadence in rpm and speed in milliseconds from Bluetooth LE Cycling Cadence and Speed sensors.
|
|
||||||
*/
|
|
||||||
public final class SensorDataCycling {
|
|
||||||
|
|
||||||
private static final String TAG = SensorDataCycling.class.getSimpleName();
|
|
||||||
|
|
||||||
private SensorDataCycling() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class CyclingCadence extends SensorData<Cadence> {
|
|
||||||
|
|
||||||
private final Long crankRevolutionsCount; // UINT32
|
|
||||||
private final Integer crankRevolutionsTime; // UINT16; 1/1024s
|
|
||||||
|
|
||||||
public CyclingCadence(String sensorAddress) {
|
|
||||||
super(sensorAddress);
|
|
||||||
this.crankRevolutionsCount = null;
|
|
||||||
this.crankRevolutionsTime = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CyclingCadence(String sensorAddress, String sensorName, long crankRevolutionsCount, int crankRevolutionsTime) {
|
|
||||||
super(sensorAddress, sensorName);
|
|
||||||
this.crankRevolutionsCount = crankRevolutionsCount;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getCrankRevolutionsCount() {
|
|
||||||
return crankRevolutionsCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCrankRevolutionsTime() {
|
|
||||||
return crankRevolutionsTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
protected Cadence getNoneValue() {
|
|
||||||
return Cadence.of(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void compute(CyclingCadence previous) {
|
|
||||||
if (hasData() && previous != null && previous.hasData()) {
|
|
||||||
float timeDiff_ms = UintUtils.diff(crankRevolutionsTime, previous.crankRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * 1000;
|
|
||||||
Duration timeDiff = Duration.ofMillis((long) timeDiff_ms);
|
|
||||||
if (timeDiff.isZero() || timeDiff.isNegative()) {
|
|
||||||
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
|
|
||||||
value = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO We have to treat with overflow according to the documentation: read https://github.com/OpenTracksApp/OpenTracks/pull/953#discussion_r711625268
|
|
||||||
if (crankRevolutionsCount < previous.crankRevolutionsCount) {
|
|
||||||
Log.e(TAG, "Crank revolutions count difference is invalid: cannot compute cadence.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
long crankDiff = UintUtils.diff(crankRevolutionsCount, previous.crankRevolutionsCount, UintUtils.UINT32_MAX);
|
|
||||||
value = Cadence.of(crankDiff, timeDiff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return super.toString() + " cadence=" + value + " time=" + crankRevolutionsTime + " count=" + crankRevolutionsCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(@Nullable Object obj) {
|
|
||||||
if (!(obj instanceof CyclingCadence)) return false;
|
|
||||||
|
|
||||||
CyclingCadence comp = (CyclingCadence) obj;
|
|
||||||
if (hasData() && comp.hasData() == hasData()) {
|
|
||||||
return getCrankRevolutionsCount() == comp.getCrankRevolutionsCount() && getCrankRevolutionsTime() == comp.getCrankRevolutionsTime();
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class DistanceSpeed extends SensorData<DistanceSpeed.Data> {
|
|
||||||
|
|
||||||
private final Long wheelRevolutionsCount; // UINT32
|
|
||||||
private final Integer wheelRevolutionsTime; // UINT16; 1/1024s
|
|
||||||
|
|
||||||
public DistanceSpeed(String sensorAddress) {
|
|
||||||
super(sensorAddress);
|
|
||||||
this.wheelRevolutionsCount = null;
|
|
||||||
this.wheelRevolutionsTime = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DistanceSpeed(String sensorAddress, String sensorName, long wheelRevolutionsCount, int wheelRevolutionsTime) {
|
|
||||||
super(sensorAddress, sensorName);
|
|
||||||
this.wheelRevolutionsCount = wheelRevolutionsCount;
|
|
||||||
this.wheelRevolutionsTime = wheelRevolutionsTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasData() {
|
|
||||||
return wheelRevolutionsCount != null && wheelRevolutionsTime != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getWheelRevolutionsCount() {
|
|
||||||
return wheelRevolutionsCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getWheelRevolutionsTime() {
|
|
||||||
return wheelRevolutionsTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
protected Data getNoneValue() {
|
|
||||||
if (value != null) {
|
|
||||||
return new Data(value.distance, value.distanceOverall, Speed.zero());
|
|
||||||
} else {
|
|
||||||
return new Data(Distance.of(0), Distance.of(0), Speed.zero());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void compute(DistanceSpeed previous, Distance wheelCircumference) {
|
|
||||||
if (hasData() && previous != null && previous.hasData()) {
|
|
||||||
float timeDiff_ms = UintUtils.diff(wheelRevolutionsTime, previous.wheelRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * 1000;
|
|
||||||
Duration timeDiff = Duration.ofMillis((long) timeDiff_ms);
|
|
||||||
if (timeDiff.isZero() || timeDiff.isNegative()) {
|
|
||||||
Log.e(TAG, "Timestamps difference is invalid: cannot compute speed.");
|
|
||||||
value = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wheelRevolutionsCount < previous.wheelRevolutionsCount) {
|
|
||||||
Log.e(TAG, "Wheel revolutions count difference is invalid: cannot compute speed.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
long wheelDiff = UintUtils.diff(wheelRevolutionsCount, previous.wheelRevolutionsCount, UintUtils.UINT32_MAX);
|
|
||||||
|
|
||||||
Distance distance = wheelCircumference.multipliedBy(wheelDiff);
|
|
||||||
Distance distanceOverall = distance;
|
|
||||||
if (previous.hasValue()) {
|
|
||||||
distanceOverall = distance.plus(previous.getValue().distanceOverall);
|
|
||||||
}
|
|
||||||
Speed speed_mps = Speed.of(distance, timeDiff);
|
|
||||||
value = new Data(distance, distanceOverall, speed_mps);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reset() {
|
|
||||||
if (value != null) {
|
|
||||||
value = new Data(value.distance, Distance.of(0), value.speed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return super.toString() + " data=" + value + " time=" + wheelRevolutionsTime + " count=" + wheelRevolutionsCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(@Nullable Object obj) {
|
|
||||||
if (!(obj instanceof DistanceSpeed)) return false;
|
|
||||||
|
|
||||||
DistanceSpeed comp = (DistanceSpeed) obj;
|
|
||||||
if (!(hasData() && comp.hasData())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return getWheelRevolutionsCount() == comp.getWheelRevolutionsCount() && getWheelRevolutionsTime() == comp.getWheelRevolutionsTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Data {
|
|
||||||
private final Distance distance;
|
|
||||||
private final Distance distanceOverall;
|
|
||||||
private final Speed speed;
|
|
||||||
|
|
||||||
private Data(Distance distance, Distance distanceOverall, Speed speed) {
|
|
||||||
this.distance = distance;
|
|
||||||
this.distanceOverall = distanceOverall;
|
|
||||||
this.speed = speed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Distance getDistance() {
|
|
||||||
return distance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Distance getDistanceOverall() {
|
|
||||||
return distanceOverall;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Speed getSpeed() {
|
|
||||||
return speed;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "Data{" +
|
|
||||||
"distance=" + getDistance() +
|
|
||||||
", distance_overall=" + getDistanceOverall() +
|
|
||||||
", speed=" + getSpeed() +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class CadenceAndSpeed extends SensorData<Pair<CyclingCadence, DistanceSpeed>> {
|
|
||||||
|
|
||||||
public CadenceAndSpeed(String sensorAddress, String sensorName, @Nullable CyclingCadence cadence, @Nullable DistanceSpeed distanceSpeed) {
|
|
||||||
super(sensorAddress, sensorName);
|
|
||||||
this.value = new Pair<>(cadence, distanceSpeed);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CyclingCadence getCadence() {
|
|
||||||
return this.value != null ? this.value.first : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DistanceSpeed getDistanceSpeed() {
|
|
||||||
return this.value != null ? this.value.second : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
protected Pair<CyclingCadence, DistanceSpeed> getNoneValue() {
|
|
||||||
return new Pair<>(null, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
package de.dennisguse.opentracks.sensors.sensorData;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.data.models.Cadence;
|
||||||
|
import de.dennisguse.opentracks.sensors.UintUtils;
|
||||||
|
|
||||||
|
public class SensorDataCyclingCadence extends SensorData<Cadence> {
|
||||||
|
|
||||||
|
private final String TAG = SensorDataCyclingCadence.class.getSimpleName();
|
||||||
|
|
||||||
|
private final Long crankRevolutionsCount; // UINT32
|
||||||
|
private final Integer crankRevolutionsTime; // UINT16; 1/1024s
|
||||||
|
|
||||||
|
public SensorDataCyclingCadence(String sensorAddress) {
|
||||||
|
super(sensorAddress);
|
||||||
|
this.crankRevolutionsCount = null;
|
||||||
|
this.crankRevolutionsTime = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SensorDataCyclingCadence(String sensorAddress, String sensorName, long crankRevolutionsCount, int crankRevolutionsTime) {
|
||||||
|
super(sensorAddress, sensorName);
|
||||||
|
this.crankRevolutionsCount = crankRevolutionsCount;
|
||||||
|
this.crankRevolutionsTime = crankRevolutionsTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasData() {
|
||||||
|
return crankRevolutionsCount != null && crankRevolutionsTime != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCrankRevolutionsCount() {
|
||||||
|
return crankRevolutionsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCrankRevolutionsTime() {
|
||||||
|
return crankRevolutionsTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Cadence getNoneValue() {
|
||||||
|
return Cadence.of(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void compute(SensorDataCyclingCadence previous) {
|
||||||
|
if (hasData() && previous != null && previous.hasData()) {
|
||||||
|
float timeDiff_ms = UintUtils.diff(crankRevolutionsTime, previous.crankRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * 1000;
|
||||||
|
Duration timeDiff = Duration.ofMillis((long) timeDiff_ms);
|
||||||
|
if (timeDiff.isZero() || timeDiff.isNegative()) {
|
||||||
|
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
|
||||||
|
value = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO We have to treat with overflow according to the documentation: read https://github.com/OpenTracksApp/OpenTracks/pull/953#discussion_r711625268
|
||||||
|
if (crankRevolutionsCount < previous.crankRevolutionsCount) {
|
||||||
|
Log.e(TAG, "Crank revolutions count difference is invalid: cannot compute cadence.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long crankDiff = UintUtils.diff(crankRevolutionsCount, previous.crankRevolutionsCount, UintUtils.UINT32_MAX);
|
||||||
|
value = Cadence.of(crankDiff, timeDiff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return super.toString() + " cadence=" + value + " time=" + crankRevolutionsTime + " count=" + crankRevolutionsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object obj) {
|
||||||
|
if (!(obj instanceof SensorDataCyclingCadence)) return false;
|
||||||
|
|
||||||
|
SensorDataCyclingCadence comp = (SensorDataCyclingCadence) obj;
|
||||||
|
if (hasData() && comp.hasData() == hasData()) {
|
||||||
|
return getCrankRevolutionsCount() == comp.getCrankRevolutionsCount() && getCrankRevolutionsTime() == comp.getCrankRevolutionsTime();
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
package de.dennisguse.opentracks.sensors.sensorData;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.util.Pair;
|
||||||
|
|
||||||
|
public class SensorDataCyclingCadenceAndDistanceSpeed extends SensorData<Pair<SensorDataCyclingCadence, SensorDataCyclingDistanceSpeed>> {
|
||||||
|
|
||||||
|
public SensorDataCyclingCadenceAndDistanceSpeed(String sensorAddress, String sensorName, @Nullable SensorDataCyclingCadence cadence, @Nullable SensorDataCyclingDistanceSpeed distanceSpeed) {
|
||||||
|
super(sensorAddress, sensorName);
|
||||||
|
this.value = new Pair<>(cadence, distanceSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SensorDataCyclingCadence getCadence() {
|
||||||
|
return this.value != null ? this.value.first : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SensorDataCyclingDistanceSpeed getDistanceSpeed() {
|
||||||
|
return this.value != null ? this.value.second : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Pair<SensorDataCyclingCadence, SensorDataCyclingDistanceSpeed> getNoneValue() {
|
||||||
|
return new Pair<>(null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
+139
@@ -0,0 +1,139 @@
|
|||||||
|
package de.dennisguse.opentracks.sensors.sensorData;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.data.models.Distance;
|
||||||
|
import de.dennisguse.opentracks.data.models.Speed;
|
||||||
|
import de.dennisguse.opentracks.sensors.UintUtils;
|
||||||
|
|
||||||
|
public class SensorDataCyclingDistanceSpeed extends SensorData<SensorDataCyclingDistanceSpeed.Data> {
|
||||||
|
|
||||||
|
private final String TAG = SensorDataCyclingDistanceSpeed.class.getSimpleName();
|
||||||
|
|
||||||
|
private final Long wheelRevolutionsCount; // UINT32
|
||||||
|
private final Integer wheelRevolutionsTime; // UINT16; 1/1024s
|
||||||
|
|
||||||
|
public SensorDataCyclingDistanceSpeed(String sensorAddress) {
|
||||||
|
super(sensorAddress);
|
||||||
|
this.wheelRevolutionsCount = null;
|
||||||
|
this.wheelRevolutionsTime = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SensorDataCyclingDistanceSpeed(String sensorAddress, String sensorName, long wheelRevolutionsCount, int wheelRevolutionsTime) {
|
||||||
|
super(sensorAddress, sensorName);
|
||||||
|
this.wheelRevolutionsCount = wheelRevolutionsCount;
|
||||||
|
this.wheelRevolutionsTime = wheelRevolutionsTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasData() {
|
||||||
|
return wheelRevolutionsCount != null && wheelRevolutionsTime != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getWheelRevolutionsCount() {
|
||||||
|
return wheelRevolutionsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWheelRevolutionsTime() {
|
||||||
|
return wheelRevolutionsTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Data getNoneValue() {
|
||||||
|
if (value != null) {
|
||||||
|
return new Data(value.distance, value.distanceOverall, Speed.zero());
|
||||||
|
} else {
|
||||||
|
return new Data(Distance.of(0), Distance.of(0), Speed.zero());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void compute(SensorDataCyclingDistanceSpeed previous, Distance wheelCircumference) {
|
||||||
|
if (hasData() && previous != null && previous.hasData()) {
|
||||||
|
float timeDiff_ms = UintUtils.diff(wheelRevolutionsTime, previous.wheelRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * 1000;
|
||||||
|
Duration timeDiff = Duration.ofMillis((long) timeDiff_ms);
|
||||||
|
if (timeDiff.isZero() || timeDiff.isNegative()) {
|
||||||
|
Log.e(TAG, "Timestamps difference is invalid: cannot compute speed.");
|
||||||
|
value = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wheelRevolutionsCount < previous.wheelRevolutionsCount) {
|
||||||
|
Log.e(TAG, "Wheel revolutions count difference is invalid: cannot compute speed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long wheelDiff = UintUtils.diff(wheelRevolutionsCount, previous.wheelRevolutionsCount, UintUtils.UINT32_MAX);
|
||||||
|
|
||||||
|
Distance distance = wheelCircumference.multipliedBy(wheelDiff);
|
||||||
|
Distance distanceOverall = distance;
|
||||||
|
if (previous.hasValue()) {
|
||||||
|
distanceOverall = distance.plus(previous.getValue().distanceOverall);
|
||||||
|
}
|
||||||
|
Speed speed_mps = Speed.of(distance, timeDiff);
|
||||||
|
value = new Data(distance, distanceOverall, speed_mps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
if (value != null) {
|
||||||
|
value = new Data(value.distance, Distance.of(0), value.speed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return super.toString() + " data=" + value + " time=" + wheelRevolutionsTime + " count=" + wheelRevolutionsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object obj) {
|
||||||
|
if (!(obj instanceof SensorDataCyclingDistanceSpeed)) return false;
|
||||||
|
|
||||||
|
SensorDataCyclingDistanceSpeed comp = (SensorDataCyclingDistanceSpeed) obj;
|
||||||
|
if (!(hasData() && comp.hasData())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getWheelRevolutionsCount() == comp.getWheelRevolutionsCount() && getWheelRevolutionsTime() == comp.getWheelRevolutionsTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Data {
|
||||||
|
private final Distance distance;
|
||||||
|
private final Distance distanceOverall;
|
||||||
|
private final Speed speed;
|
||||||
|
|
||||||
|
private Data(Distance distance, Distance distanceOverall, Speed speed) {
|
||||||
|
this.distance = distance;
|
||||||
|
this.distanceOverall = distanceOverall;
|
||||||
|
this.speed = speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Distance getDistance() {
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Distance getDistanceOverall() {
|
||||||
|
return distanceOverall;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Speed getSpeed() {
|
||||||
|
return speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Data{" +
|
||||||
|
"distance=" + getDistance() +
|
||||||
|
", distance_overall=" + getDistanceOverall() +
|
||||||
|
", speed=" + getSpeed() +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+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 SensorDataCyclingPower power;
|
||||||
|
private final SensorDataCyclingCadence cadence;
|
||||||
|
|
||||||
|
public Data(SensorDataCyclingPower power, @Nullable SensorDataCyclingCadence cadence) {
|
||||||
|
this.power = power;
|
||||||
|
this.cadence = cadence;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SensorDataCyclingPower getPower() {
|
||||||
|
return power;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SensorDataCyclingCadence getCadence() {
|
||||||
|
return cadence;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Data{" +
|
||||||
|
"power=" + power +
|
||||||
|
", cadence=" + cadence +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ public final class SensorDataSet {
|
|||||||
|
|
||||||
private SensorDataHeartRate heartRate;
|
private SensorDataHeartRate heartRate;
|
||||||
|
|
||||||
private SensorDataCycling.CyclingCadence cyclingCadence;
|
private SensorDataCyclingCadence cyclingCadence;
|
||||||
|
|
||||||
private SensorDataCycling.DistanceSpeed cyclingDistanceSpeed;
|
private SensorDataCyclingDistanceSpeed cyclingDistanceSpeed;
|
||||||
|
|
||||||
private SensorDataCyclingPower cyclingPower;
|
private SensorDataCyclingPower cyclingPower;
|
||||||
|
|
||||||
@@ -64,11 +64,11 @@ public final class SensorDataSet {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SensorDataCycling.CyclingCadence getCyclingCadence() {
|
public SensorDataCyclingCadence getCyclingCadence() {
|
||||||
return cyclingCadence;
|
return cyclingCadence;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SensorDataCycling.DistanceSpeed getCyclingDistanceSpeed() {
|
public SensorDataCyclingDistanceSpeed getCyclingDistanceSpeed() {
|
||||||
return cyclingDistanceSpeed;
|
return cyclingDistanceSpeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,12 +146,12 @@ public final class SensorDataSet {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type instanceof SensorDataCycling.CyclingCadence) {
|
if (type instanceof SensorDataCyclingCadence) {
|
||||||
this.cyclingCadence = (SensorDataCycling.CyclingCadence) data;
|
this.cyclingCadence = (SensorDataCyclingCadence) data;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (type instanceof SensorDataCycling.DistanceSpeed) {
|
if (type instanceof SensorDataCyclingDistanceSpeed) {
|
||||||
this.cyclingDistanceSpeed = (SensorDataCycling.DistanceSpeed) data;
|
this.cyclingDistanceSpeed = (SensorDataCyclingDistanceSpeed) data;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,6 @@ import androidx.preference.Preference;
|
|||||||
import androidx.preference.PreferenceFragmentCompat;
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.sensors.BluetoothUtils;
|
|
||||||
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingCadenceAndSpeedPreference;
|
|
||||||
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingPowerPreference;
|
|
||||||
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeHeartRatePreference;
|
|
||||||
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeRunningSpeedAndCadencePreference;
|
|
||||||
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeSensorPreference;
|
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeSensorPreference;
|
||||||
|
|
||||||
public class SensorsSettingsFragment extends PreferenceFragmentCompat {
|
public class SensorsSettingsFragment extends PreferenceFragmentCompat {
|
||||||
@@ -34,18 +29,8 @@ public class SensorsSettingsFragment extends PreferenceFragmentCompat {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisplayPreferenceDialog(Preference preference) {
|
public void onDisplayPreferenceDialog(Preference preference) {
|
||||||
DialogFragment dialogFragment = null;
|
if (preference instanceof BluetoothLeSensorPreference) {
|
||||||
if (preference instanceof BluetoothLeHeartRatePreference) {
|
DialogFragment dialogFragment = ((BluetoothLeSensorPreference) preference).createInstance();
|
||||||
dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.HEART_RATE_SUPPORTING_DEVICES);
|
|
||||||
} else if (preference instanceof BluetoothLeCyclingCadenceAndSpeedPreference) {
|
|
||||||
dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID);
|
|
||||||
} else if (preference instanceof BluetoothLeCyclingPowerPreference) {
|
|
||||||
dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.CYCLING_POWER_UUID);
|
|
||||||
} else if (preference instanceof BluetoothLeRunningSpeedAndCadencePreference) {
|
|
||||||
dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.RUNNING_RUNNING_SPEED_CADENCE_UUID);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dialogFragment != null) {
|
|
||||||
dialogFragment.setTargetFragment(this, 0);
|
dialogFragment.setTargetFragment(this, 0);
|
||||||
dialogFragment.show(getParentFragmentManager(), getClass().getSimpleName());
|
dialogFragment.show(getParentFragmentManager(), getClass().getSimpleName());
|
||||||
return;
|
return;
|
||||||
|
|||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
package de.dennisguse.opentracks.settings.bluetooth;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
|
|
||||||
public class BluetoothLeCyclingCadenceAndSpeedPreference extends BluetoothLeSensorPreference {
|
|
||||||
|
|
||||||
public BluetoothLeCyclingCadenceAndSpeedPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BluetoothLeCyclingCadenceAndSpeedPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
||||||
super(context, attrs, defStyleAttr);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BluetoothLeCyclingCadenceAndSpeedPreference(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BluetoothLeCyclingCadenceAndSpeedPreference(Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+32
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
@@ -3,6 +3,10 @@ package de.dennisguse.opentracks.settings.bluetooth;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.sensors.BluetoothUtils;
|
||||||
|
|
||||||
public class BluetoothLeCyclingPowerPreference extends BluetoothLeSensorPreference {
|
public class BluetoothLeCyclingPowerPreference extends BluetoothLeSensorPreference {
|
||||||
|
|
||||||
public BluetoothLeCyclingPowerPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
public BluetoothLeCyclingPowerPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
@@ -20,4 +24,9 @@ public class BluetoothLeCyclingPowerPreference extends BluetoothLeSensorPreferen
|
|||||||
public BluetoothLeCyclingPowerPreference(Context context) {
|
public BluetoothLeCyclingPowerPreference(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PreferenceDialogFragmentCompat createInstance() {
|
||||||
|
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), BluetoothUtils.CYCLING_POWER);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+32
@@ -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 BluetoothLeCyclingSpeedPreference extends BluetoothLeSensorPreference {
|
||||||
|
|
||||||
|
public BluetoothLeCyclingSpeedPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BluetoothLeCyclingSpeedPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BluetoothLeCyclingSpeedPreference(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BluetoothLeCyclingSpeedPreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PreferenceDialogFragmentCompat createInstance() {
|
||||||
|
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), BluetoothUtils.CYCLING_SPEED_CADENCE);
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
@@ -3,6 +3,10 @@ package de.dennisguse.opentracks.settings.bluetooth;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.sensors.BluetoothUtils;
|
||||||
|
|
||||||
public class BluetoothLeHeartRatePreference extends BluetoothLeSensorPreference {
|
public class BluetoothLeHeartRatePreference extends BluetoothLeSensorPreference {
|
||||||
|
|
||||||
public BluetoothLeHeartRatePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
public BluetoothLeHeartRatePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
@@ -20,4 +24,9 @@ public class BluetoothLeHeartRatePreference extends BluetoothLeSensorPreference
|
|||||||
public BluetoothLeHeartRatePreference(Context context) {
|
public BluetoothLeHeartRatePreference(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PreferenceDialogFragmentCompat createInstance() {
|
||||||
|
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), BluetoothUtils.HEART_RATE_SUPPORTING_DEVICES);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+9
@@ -3,6 +3,10 @@ package de.dennisguse.opentracks.settings.bluetooth;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.sensors.BluetoothUtils;
|
||||||
|
|
||||||
public class BluetoothLeRunningSpeedAndCadencePreference extends BluetoothLeSensorPreference {
|
public class BluetoothLeRunningSpeedAndCadencePreference extends BluetoothLeSensorPreference {
|
||||||
|
|
||||||
public BluetoothLeRunningSpeedAndCadencePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
public BluetoothLeRunningSpeedAndCadencePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
@@ -20,4 +24,9 @@ public class BluetoothLeRunningSpeedAndCadencePreference extends BluetoothLeSens
|
|||||||
public BluetoothLeRunningSpeedAndCadencePreference(Context context) {
|
public BluetoothLeRunningSpeedAndCadencePreference(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PreferenceDialogFragmentCompat createInstance() {
|
||||||
|
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), BluetoothUtils.RUNNING_SPEED_CADENCE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-4
@@ -23,11 +23,11 @@ import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.sensors.BluetoothUtils;
|
import de.dennisguse.opentracks.sensors.BluetoothUtils;
|
||||||
|
import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
|
||||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.PermissionRequester;
|
import de.dennisguse.opentracks.util.PermissionRequester;
|
||||||
|
|
||||||
@@ -92,6 +92,8 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
|||||||
return getValue();
|
return getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract PreferenceDialogFragmentCompat createInstance();
|
||||||
|
|
||||||
public static class BluetoothLeSensorPreferenceDialog extends PreferenceDialogFragmentCompat {
|
public static class BluetoothLeSensorPreferenceDialog extends PreferenceDialogFragmentCompat {
|
||||||
|
|
||||||
private AnimatedVectorDrawableCompat bluetoothIcon;
|
private AnimatedVectorDrawableCompat bluetoothIcon;
|
||||||
@@ -123,15 +125,18 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public static BluetoothLeSensorPreferenceDialog newInstance(String preferenceKey, UUID sensorUUID) {
|
public static BluetoothLeSensorPreferenceDialog newInstance(String preferenceKey, ServiceMeasurementUUID sensorUUID) {
|
||||||
return newInstance(preferenceKey, Collections.singletonList(sensorUUID));
|
return newInstance(preferenceKey, Collections.singletonList(sensorUUID));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BluetoothLeSensorPreferenceDialog newInstance(String preferenceKey, List<UUID> sensorUUIDs) {
|
public static BluetoothLeSensorPreferenceDialog newInstance(String preferenceKey, List<ServiceMeasurementUUID> sensorUUIDs) {
|
||||||
final BluetoothLeSensorPreferenceDialog fragment = new BluetoothLeSensorPreferenceDialog();
|
final BluetoothLeSensorPreferenceDialog fragment = new BluetoothLeSensorPreferenceDialog();
|
||||||
final Bundle b = new Bundle(1);
|
final Bundle b = new Bundle(1);
|
||||||
b.putString(ARG_KEY, preferenceKey);
|
b.putString(ARG_KEY, preferenceKey);
|
||||||
b.putParcelableArrayList(ARG_BLE_SERVICE_UUIDS, new ArrayList<>(sensorUUIDs.stream().map(ParcelUuid::new).collect(Collectors.toList())));
|
b.putParcelableArrayList(ARG_BLE_SERVICE_UUIDS, new ArrayList<>(sensorUUIDs.stream()
|
||||||
|
.map(ServiceMeasurementUUID::getServiceUUID)
|
||||||
|
.map(ParcelUuid::new)
|
||||||
|
.collect(Collectors.toList())));
|
||||||
fragment.setArguments(b);
|
fragment.setArguments(b);
|
||||||
return fragment;
|
return fragment;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/settings_cycling_sensor">
|
<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:defaultValue="@string/sensor_type_value_none"
|
||||||
android:key="@string/settings_sensor_bluetooth_cycling_cadence_key"
|
android:key="@string/settings_sensor_bluetooth_cycling_cadence_key"
|
||||||
android:title="@string/settings_sensor_cycling_cadence" />
|
android:title="@string/settings_sensor_cycling_cadence" />
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
android:defaultValue="@string/sensor_type_value_none"
|
android:defaultValue="@string/sensor_type_value_none"
|
||||||
android:key="@string/settings_sensor_bluetooth_cycling_power_key"
|
android:key="@string/settings_sensor_bluetooth_cycling_power_key"
|
||||||
android:title="@string/sensor_state_power" />
|
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:defaultValue="@string/sensor_type_value_none"
|
||||||
android:key="@string/settings_sensor_bluetooth_cycling_speed_key"
|
android:key="@string/settings_sensor_bluetooth_cycling_speed_key"
|
||||||
android:title="@string/settings_sensor_cycling_distance_speed" />
|
android:title="@string/settings_sensor_cycling_distance_speed" />
|
||||||
|
|||||||
Reference in New Issue
Block a user