Sensors: separate data generation and aggregation.

Part of #1424.
This commit is contained in:
Dennis Guse
2023-11-12 12:03:03 +01:00
parent 15f3ccbcbd
commit 58d79380cf
25 changed files with 314 additions and 485 deletions
@@ -548,22 +548,28 @@ public class ExportImportTest {
private void mockBLESensorData(TrackPointCreator trackPointCreator, Float speed, Distance distance, float heartRate, float cadence, Float power) {
SensorDataSet sensorDataSet = new SensorDataSet();
sensorDataSet.set(new SensorDataCyclingPower("power", "power", Power.of(power)));
sensorDataSet.set(new SensorDataHeartRate("heartRate", "heartRate", HeartRate.of(heartRate)));
sensorDataSet.cyclingPower = Mockito.mock(SensorDataCyclingPower.class);
Mockito.when( sensorDataSet.cyclingPower.hasValue()).thenReturn(true);
Mockito.when(sensorDataSet.cyclingPower.getValue()).thenReturn(Power.of(power));
sensorDataSet.heartRate = Mockito.mock(SensorDataHeartRate.class);
Mockito.when(sensorDataSet.heartRate.getValue()).thenReturn(HeartRate.of(heartRate));
SensorDataCyclingCadence cyclingCadence = Mockito.mock(SensorDataCyclingCadence.class);
Mockito.when(cyclingCadence.hasValue()).thenReturn(true);
Mockito.when(cyclingCadence.getValue()).thenReturn(Cadence.of(cadence));
sensorDataSet.set(cyclingCadence);
sensorDataSet.cyclingCadence = cyclingCadence;
if (distance != null && speed != null) {
SensorDataCyclingDistanceSpeed.Data distanceSpeedData = Mockito.mock(SensorDataCyclingDistanceSpeed.Data.class);
Mockito.when(distanceSpeedData.getDistanceOverall()).thenReturn(distance);
Mockito.when(distanceSpeedData.getSpeed()).thenReturn(Speed.of(speed));
SensorDataCyclingDistanceSpeed distanceSpeed = Mockito.mock(SensorDataCyclingDistanceSpeed.class);
Mockito.when(distanceSpeed.hasValue()).thenReturn(true);
Mockito.when(distanceSpeed.getValue()).thenReturn(distanceSpeedData);
sensorDataSet.set(distanceSpeed);
sensorDataSet.cyclingDistanceSpeed = distanceSpeed;
}
trackPointCreator.getSensorManager().sensorDataSet = sensorDataSet;
@@ -4,11 +4,10 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import android.bluetooth.BluetoothGattCharacteristic;
import android.util.Pair;
import org.junit.Test;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadenceAndDistanceSpeed;
public class BluetoothHandlerCyclingDistanceSpeedTest {
@Test
public void parseCyclingSpeedCadence_crankOnly() {
@@ -16,11 +15,11 @@ public class BluetoothHandlerCyclingDistanceSpeedTest {
characteristic.setValue(new byte[]{0x02, (byte) 0xC8, 0x00, 0x00, 0x00, 0x06, (byte) 0x99});
// when
SensorDataCyclingCadenceAndDistanceSpeed sensor = BluetoothHandlerCyclingDistanceSpeed.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
Pair<BluetoothHandlerCyclingDistanceSpeed.WheelData, BluetoothHandlerCyclingCadence.CrankData> sensor = BluetoothHandlerCyclingDistanceSpeed.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
// then
assertNull(sensor.getDistanceSpeed());
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
assertNull(sensor.first);
assertEquals(200, sensor.second.crankRevolutionsCount());
}
@Test
@@ -29,11 +28,11 @@ public class BluetoothHandlerCyclingDistanceSpeedTest {
characteristic.setValue(new byte[]{0x01, (byte) 0xFF, (byte) 0xFF, 0, 1, 0x45, (byte) 0x99});
// when
SensorDataCyclingCadenceAndDistanceSpeed sensor = BluetoothHandlerCyclingDistanceSpeed.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
Pair<BluetoothHandlerCyclingDistanceSpeed.WheelData, BluetoothHandlerCyclingCadence.CrankData> sensor = BluetoothHandlerCyclingDistanceSpeed.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
// then
assertEquals(65535 + 16777216, sensor.getDistanceSpeed().getWheelRevolutionsCount());
assertNull(sensor.getCadence());
assertEquals(65535 + 16777216, sensor.first.wheelRevolutionsCount());
assertNull(sensor.second);
}
@Test
@@ -42,11 +41,11 @@ public class BluetoothHandlerCyclingDistanceSpeedTest {
characteristic.setValue(new byte[]{0x03, (byte) 0xC8, 0x00, 0x00, 0x01, 0x06, (byte) 0x99, (byte) 0xE1, 0x00, 0x45, (byte) 0x99});
// when
SensorDataCyclingCadenceAndDistanceSpeed sensor = BluetoothHandlerCyclingDistanceSpeed.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
Pair<BluetoothHandlerCyclingDistanceSpeed.WheelData, BluetoothHandlerCyclingCadence.CrankData> sensor = BluetoothHandlerCyclingDistanceSpeed.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
// then
assertEquals(200 + 16777216, sensor.getDistanceSpeed().getWheelRevolutionsCount());
assertEquals(225, sensor.getCadence().getCrankRevolutionsCount());
assertEquals(200 + 16777216, sensor.first.wheelRevolutionsCount());
assertEquals(225, sensor.second.crankRevolutionsCount());
}
}
@@ -6,8 +6,6 @@ import android.bluetooth.BluetoothGattCharacteristic;
import org.junit.Test;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
public class BluetoothHandlerCyclingPowerTest {
@Test
@@ -16,10 +14,10 @@ public class BluetoothHandlerCyclingPowerTest {
characteristic.setValue(new byte[]{0, 0, 40, 0});
// when
SensorDataCyclingPower.Data powerCadence = BluetoothHandlerManagerCyclingPower.parseCyclingPower("", "", characteristic);
BluetoothHandlerManagerCyclingPower.Data powerCadence = BluetoothHandlerManagerCyclingPower.parseCyclingPower(characteristic);
// then
assertEquals(40, powerCadence.power().getValue().getW(), 0.01);
assertEquals(40, powerCadence.power().getW(), 0.01);
}
@Test
@@ -28,13 +26,13 @@ public class BluetoothHandlerCyclingPowerTest {
characteristic.setValue(new byte[]{0x2C, 0x00, 0x00, 0x00, (byte) 0x9F, 0x00, 0x0C, 0x00, (byte) 0xE5, 0x42});
// when
SensorDataCyclingPower.Data powerCadence = BluetoothHandlerManagerCyclingPower.parseCyclingPower("", "", characteristic);
BluetoothHandlerManagerCyclingPower.Data powerCadence = BluetoothHandlerManagerCyclingPower.parseCyclingPower(characteristic);
// then
assertEquals(0, powerCadence.power().getValue().getW(), 0.01);
assertEquals(0, powerCadence.power().getW(), 0.01);
assertEquals(12, powerCadence.cadence().getCrankRevolutionsCount());
assertEquals(17125, powerCadence.cadence().getCrankRevolutionsTime());
assertEquals(12, powerCadence.crank().crankRevolutionsCount());
assertEquals(17125, powerCadence.crank().crankRevolutionsTime());
}
}
@@ -9,7 +9,6 @@ import org.junit.Test;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
public class BluetoothHandlerRunningSpeedAndCadenceTest {
@@ -19,11 +18,11 @@ public class BluetoothHandlerRunningSpeedAndCadenceTest {
characteristic.setValue(new byte[]{2, 0, 5, 80, (byte) 0xFF, (byte) 0xFF, 0, 1});
// when
SensorDataRunning sensor = BluetoothHandlerRunningSpeedAndCadence.parseRunningSpeedAndCadence("address", "sensorName", characteristic);
BluetoothHandlerRunningSpeedAndCadence.Data sensor = BluetoothHandlerRunningSpeedAndCadence.parseRunningSpeedAndCadence("sensorName", characteristic);
// then
assertEquals(Speed.of(5), sensor.getSpeed());
assertEquals(Cadence.of(80), sensor.getCadence());
assertEquals(Distance.of(6553.5 + 1677721.6), sensor.getTotalDistance());
assertEquals(Speed.of(5), sensor.speed());
assertEquals(Cadence.of(80), sensor.cadence());
assertEquals(Distance.of(6553.5 + 1677721.6), sensor.totalDistance());
}
}
@@ -2,17 +2,17 @@ package de.dennisguse.opentracks.sensors.sensorData;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingCadence;
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingDistanceSpeed;
import de.dennisguse.opentracks.sensors.UintUtils;
@RunWith(AndroidJUnit4.class)
@@ -20,12 +20,11 @@ public class SensorDataCyclingTest {
@Test
public void compute_cadence_1() {
// given
SensorDataCyclingCadence previous = new SensorDataCyclingCadence("sensorAddress", "sensorName", 1, 1024); // 1s
SensorDataCyclingCadence current = new SensorDataCyclingCadence("sensorAddress", "sensorName", 2, 2048); // 2s
SensorDataCyclingCadence current = new SensorDataCyclingCadence("", "");
// when
current.compute(previous);
current.add(new Raw<>(new BluetoothHandlerCyclingCadence.CrankData(1, 1024)));
current.add(new Raw<>(new BluetoothHandlerCyclingCadence.CrankData(2, 2048)));
// then
assertEquals(60, current.getValue().getRPM(), 0.01);
@@ -33,12 +32,11 @@ public class SensorDataCyclingTest {
@Test
public void compute_cadence_2() {
// given
SensorDataCyclingCadence previous = new SensorDataCyclingCadence("sensorAddress", "sensorName", 1, 6184);
SensorDataCyclingCadence current = new SensorDataCyclingCadence("sensorAddress", "sensorName", 2, 8016);
SensorDataCyclingCadence current = new SensorDataCyclingCadence("", "");
// when
current.compute(previous);
current.add(new Raw<>(new BluetoothHandlerCyclingCadence.CrankData(1, 6184)));
current.add(new Raw<>(new BluetoothHandlerCyclingCadence.CrankData(2, 8016)));
// then
assertEquals(33.53, current.getValue().getRPM(), 0.01);
@@ -46,12 +44,11 @@ public class SensorDataCyclingTest {
@Test
public void compute_cadence_sameCount() {
// given
SensorDataCyclingCadence previous = new SensorDataCyclingCadence("sensorAddress", "sensorName", 1, 1024);
SensorDataCyclingCadence current = new SensorDataCyclingCadence("sensorAddress", "sensorName", 1, 2048);
SensorDataCyclingCadence current = new SensorDataCyclingCadence("", "");
// when
current.compute(previous);
current.add(new Raw<>(new BluetoothHandlerCyclingCadence.CrankData(1, 1024)));
current.add(new Raw<>(new BluetoothHandlerCyclingCadence.CrankData(1, 2048)));
// then
assertEquals(Cadence.of(0), current.getValue());
@@ -60,126 +57,72 @@ public class SensorDataCyclingTest {
@Test
public void compute_cadence_sameTime() {
// given
SensorDataCyclingCadence previous = new SensorDataCyclingCadence("sensorAddress", "sensorName", 1, 1024);
SensorDataCyclingCadence current = new SensorDataCyclingCadence("sensorAddress", "sensorName", 2, 1024);
SensorDataCyclingCadence current = new SensorDataCyclingCadence("", "");
// when
current.compute(previous);
current.add(new Raw<>(new BluetoothHandlerCyclingCadence.CrankData(1, 1024)));
current.add(new Raw<>(new BluetoothHandlerCyclingCadence.CrankData(2, 1024)));
// then
assertFalse(current.hasValue());
assertFalse(current.hasValue()); //TODO Cadence should be 0?
}
@Test
public void compute_cadence_rollOverTime() {
// given
SensorDataCyclingCadence previous = new SensorDataCyclingCadence("sensorAddress", "sensorName", 1, UintUtils.UINT16_MAX - 1024);
SensorDataCyclingCadence current = new SensorDataCyclingCadence("sensorAddress", "sensorName", 2, 0);
SensorDataCyclingCadence current = new SensorDataCyclingCadence("", "");
// when
current.compute(previous);
current.add(new Raw<>(new BluetoothHandlerCyclingCadence.CrankData(1, UintUtils.UINT16_MAX - 1024)));
current.add(new Raw<>(new BluetoothHandlerCyclingCadence.CrankData(2, 0)));
// then
assertEquals(60, current.getValue().getRPM(), 0.01);
}
@Ignore("Disabled from #953")
@Test
@Deprecated
public void compute_cadence_rollOverCount() {
// given
SensorDataCyclingCadence previous = new SensorDataCyclingCadence("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
SensorDataCyclingCadence current = new SensorDataCyclingCadence("sensorAddress", "sensorName", 0, 2048);
SensorDataCyclingCadence current = new SensorDataCyclingCadence("", "");
// when
current.compute(previous);
// then
assertEquals(60, current.getValue().getRPM(), 0.01);
}
@Test
public void compute_cadence_overflow() {
// given
SensorDataCyclingCadence previous = new SensorDataCyclingCadence("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
SensorDataCyclingCadence current = new SensorDataCyclingCadence("sensorAddress", "sensorName", 0, 2048);
// when
current.compute(previous);
current.add(new Raw<>(new BluetoothHandlerCyclingCadence.CrankData(UintUtils.UINT32_MAX - 1, 1024)));
current.add(new Raw<>(new BluetoothHandlerCyclingCadence.CrankData(0, 2048)));
// then
// TODO See #953
// assertEquals(60, current.getValue().getRPM(), 0.01);
assertNull(current.getValue());
}
@Test
public void compute_speed() {
// given
SensorDataCyclingDistanceSpeed previous = new SensorDataCyclingDistanceSpeed("sensorAddress", "sensorName", 1, 6184);
SensorDataCyclingDistanceSpeed current = new SensorDataCyclingDistanceSpeed("sensorAddress", "sensorName", 2, 8016);
SensorDataCyclingDistanceSpeed current = new SensorDataCyclingDistanceSpeed("", "");
current.setWheelCircumference(Distance.ofMM(2150));
// when
current.compute(previous, Distance.ofMM(2150));
current.add(new Raw<>(new BluetoothHandlerCyclingDistanceSpeed.WheelData(1, 6184)));
current.add(new Raw<>(new BluetoothHandlerCyclingDistanceSpeed.WheelData(2, 8016)));
// then
assertEquals(2.15, current.getValue().getDistance().toM(), 0.01);
assertEquals(1.20, current.getValue().getSpeed().toMPS(), 0.01);
}
@Ignore("Disabled from #953")
@Test
@Deprecated
public void compute_speed_rollOverCount() {
// given
SensorDataCyclingDistanceSpeed previous = new SensorDataCyclingDistanceSpeed("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
SensorDataCyclingDistanceSpeed current = new SensorDataCyclingDistanceSpeed("sensorAddress", "sensorName", 0, 2048);
SensorDataCyclingDistanceSpeed current = new SensorDataCyclingDistanceSpeed("", "");
current.setWheelCircumference(Distance.ofMM(2000));
// when
current.compute(previous, Distance.ofMM(2000));
// then
assertEquals(2, current.getValue().getDistance().toM(), 0.01);
assertEquals(2, current.getValue().getSpeed().toMPS(), 0.01);
}
@Test
public void compute_speed_overflow() {
// given
SensorDataCyclingDistanceSpeed previous = new SensorDataCyclingDistanceSpeed("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
SensorDataCyclingDistanceSpeed current = new SensorDataCyclingDistanceSpeed("sensorAddress", "sensorName", 0, 2048);
// when
current.compute(previous, Distance.ofMM(2000));
current.add(new Raw<>(new BluetoothHandlerCyclingDistanceSpeed.WheelData(UintUtils.UINT32_MAX - 1, 1024)));
current.add(new Raw<>(new BluetoothHandlerCyclingDistanceSpeed.WheelData(0, 2048)));
// then
// TODO See #953
// assertEquals(2, current.getValue().getDistance().toM(), 0.01);
// assertEquals(2, current.getValue().getSpeed().toMPS(), 0.01);
assertNull(current.getValue());
}
@Test
public void equals_speed_with_no_data() {
// given
SensorDataCyclingDistanceSpeed previous = new SensorDataCyclingDistanceSpeed("sensorAddress");
SensorDataCyclingDistanceSpeed current = new SensorDataCyclingDistanceSpeed("sensorAddress", "sensorName", 0, 2048);
// when
previous.toString();
// then
assertNotEquals(previous, current);
assertNotEquals(previous, previous);
}
@Test
public void equals_cadence_with_no_data() {
// given
SensorDataCyclingCadence previous = new SensorDataCyclingCadence("sensorAddress");
SensorDataCyclingCadence current = new SensorDataCyclingCadence("sensorAddress", "sensorName", 0, 2048);
// when
previous.toString();
// then
assertNotEquals(previous, current);
assertNotEquals(previous, previous);
}
}
@@ -35,7 +35,9 @@ import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.io.file.importer.TrackPointAssert;
import de.dennisguse.opentracks.sensors.AltitudeSumManager;
import de.dennisguse.opentracks.sensors.BluetoothHandlerRunningSpeedAndCadence;
import de.dennisguse.opentracks.sensors.SensorManager;
import de.dennisguse.opentracks.sensors.sensorData.Raw;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataHeartRate;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
@@ -253,16 +255,16 @@ public class TrackRecordingServiceRecordingTest {
Track.Id trackId = service.startNewTrack();
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
SensorManager sensorManager = trackPointCreator.getSensorManager();
sensorManager.sensorDataSet.add(new SensorDataHeartRate("", ""));
// when
String sensor1 = "2020-02-02T02:02:03Z";
trackPointCreator.setClock(sensor1);
sensorManager.onChanged(new SensorDataHeartRate("", "", HeartRate.of(5))); //Should be ignored
sensorManager.onChanged(new Raw<>(HeartRate.of(5))); //Should be ignored
String sensor3 = "2020-02-02T02:02:13Z";
trackPointCreator.setClock(sensor3);
sensorManager.onChanged(new SensorDataHeartRate("", "", HeartRate.of(7)));
sensorManager.onChanged(new Raw<>(HeartRate.of(7)));
String stopTime = "2020-02-02T02:02:15Z";
trackPointCreator.setClock(stopTime);
@@ -590,16 +592,17 @@ public class TrackRecordingServiceRecordingTest {
Track.Id trackId = service.startNewTrack();
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
SensorManager sensorManager = trackPointCreator.getSensorManager();
sensorManager.sensorDataSet.add(new SensorDataRunning("", ""));
// when
String sensor1 = "2020-02-02T02:02:03Z";
trackPointCreator.setClock(sensor1);
sensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(0))); //Should be ignored
sensorManager.onChanged(new Raw<>(new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), null, Distance.of(0)))); //Should be ignored
// when
String sensor2 = "2020-02-02T02:02:04Z";
trackPointCreator.setClock(sensor2);
sensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(2)));
sensorManager.onChanged(new Raw<>(new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), null, Distance.of(2))));
// when
String gps1 = "2020-02-02T02:02:05Z";
@@ -608,12 +611,12 @@ public class TrackRecordingServiceRecordingTest {
// when
String sensor3 = "2020-02-02T02:02:06Z";
trackPointCreator.setClock(sensor3);
sensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(12)));
sensorManager.onChanged(new Raw<>(new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), null, Distance.of(12))));
// when
String sensor4 = "2020-02-02T02:02:07Z";
trackPointCreator.setClock(sensor4);
sensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(14))); //Should be ignored
sensorManager.onChanged(new Raw<>(new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), null, Distance.of(14)))); //Should be ignored
// when
String gps2 = "2020-02-02T02:02:08Z";
@@ -622,7 +625,7 @@ public class TrackRecordingServiceRecordingTest {
// when
String sensor5 = "2020-02-02T02:02:10Z";
trackPointCreator.setClock(sensor5);
sensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(16))); //Should be ignored
sensorManager.onChanged(new Raw<>(new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), null, Distance.of(16)))); //Should be ignored
// when
String gps3 = "2020-02-02T02:02:12Z";
@@ -33,7 +33,6 @@ import androidx.annotation.NonNull;
import java.util.Optional;
import java.util.UUID;
import de.dennisguse.opentracks.sensors.sensorData.SensorData;
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
/**
@@ -140,8 +139,7 @@ public class BluetoothConnectionManager {
bluetoothGatt = device.connectGatt(context, false, connectCallback, BluetoothDevice.TRANSPORT_AUTO, 0, handler);
SensorData<?> sensorData = sensorHandler.createEmptySensorData(bluetoothGatt.getDevice().getAddress());
observer.onChange(sensorData);
observer.onConnect(sensorHandler.createEmptySensorData(bluetoothGatt.getDevice().getAddress()));
}
private synchronized void clearData() {
@@ -22,7 +22,7 @@ public class BluetoothHandlerBarometricPressure implements SensorHandlerInterfac
}
@Override
public SensorData<?> createEmptySensorData(String address) {
public SensorData<?, ?> createEmptySensorData(String address) {
return null; //TODO
}
@@ -2,12 +2,12 @@ package de.dennisguse.opentracks.sensors;
import android.bluetooth.BluetoothGattCharacteristic;
import android.util.Log;
import android.util.Pair;
import java.util.List;
import de.dennisguse.opentracks.sensors.sensorData.Raw;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadence;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadenceAndDistanceSpeed;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
public class BluetoothHandlerCyclingCadence implements SensorHandlerInterface {
@@ -30,24 +30,28 @@ public class BluetoothHandlerCyclingCadence implements SensorHandlerInterface {
@Override
public void handlePayload(SensorManager.SensorDataChangedObserver observer, ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
//TODO Implement to ServiceMeasurement.parse()?
if (serviceMeasurementUUID.equals(BluetoothHandlerManagerCyclingPower.CYCLING_POWER)) {
SensorDataCyclingPower.Data data = BluetoothHandlerManagerCyclingPower.parseCyclingPower(address, sensorName, characteristic);
if (data!= null) {
observer.onChange(data.cadence());
}
} else if (serviceMeasurementUUID.equals(BluetoothHandlerCyclingDistanceSpeed.CYCLING_SPEED_CADENCE)) {
SensorDataCyclingCadenceAndDistanceSpeed cadenceAndSpeed = BluetoothHandlerCyclingDistanceSpeed.parseCyclingCrankAndWheel(address, sensorName, characteristic);
if (cadenceAndSpeed == null) {
return;
BluetoothHandlerManagerCyclingPower.Data data = BluetoothHandlerManagerCyclingPower.parseCyclingPower(characteristic);
if (data != null && data.crank() != null) {
observer.onChange(new Raw<>(data.crank()));
}
return;
}
if (cadenceAndSpeed.getCadence() != null) {
observer.onChange(cadenceAndSpeed.getCadence());
if (serviceMeasurementUUID.equals(BluetoothHandlerCyclingDistanceSpeed.CYCLING_SPEED_CADENCE)) {
Pair<BluetoothHandlerCyclingDistanceSpeed.WheelData, CrankData> data = BluetoothHandlerCyclingDistanceSpeed.parseCyclingCrankAndWheel(address, sensorName, characteristic);
if (data != null && data.second != null) {
observer.onChange(new Raw<>(data.second));
}
return;
}
Log.e(TAG, "Don't know how to decode this payload.");
}
public record CrankData(
long crankRevolutionsCount, // UINT32
int crankRevolutionsTime // UINT16; 1/1024s
) {}
}
@@ -1,6 +1,7 @@
package de.dennisguse.opentracks.sensors;
import android.bluetooth.BluetoothGattCharacteristic;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -8,8 +9,7 @@ import androidx.annotation.VisibleForTesting;
import java.util.List;
import java.util.UUID;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadence;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadenceAndDistanceSpeed;
import de.dennisguse.opentracks.sensors.sensorData.Raw;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingDistanceSpeed;
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
@@ -32,19 +32,15 @@ public class BluetoothHandlerCyclingDistanceSpeed implements SensorHandlerInterf
@Override
public void handlePayload(SensorManager.SensorDataChangedObserver observer, ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
SensorDataCyclingCadenceAndDistanceSpeed cadenceAndSpeed = parseCyclingCrankAndWheel(address, sensorName, characteristic);
if (cadenceAndSpeed == null) {
return;
}
if (cadenceAndSpeed.getDistanceSpeed() != null) {
observer.onChange(cadenceAndSpeed.getDistanceSpeed());
Pair<WheelData, BluetoothHandlerCyclingCadence.CrankData> data = parseCyclingCrankAndWheel(address, sensorName, characteristic);
if (data.first != null) {
observer.onChange(new Raw<>(data.first));
}
}
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public static SensorDataCyclingCadenceAndDistanceSpeed parseCyclingCrankAndWheel(String address, String sensorName, @NonNull BluetoothGattCharacteristic characteristic) {
public static Pair<WheelData, BluetoothHandlerCyclingCadence.CrankData> 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
int valueLength = characteristic.getValue().length;
if (valueLength == 0) {
@@ -56,24 +52,31 @@ public class BluetoothHandlerCyclingDistanceSpeed implements SensorHandlerInterf
boolean hasCrank = (flags & 0x02) > 0;
int index = 1;
SensorDataCyclingDistanceSpeed speed = null;
WheelData wheelData = null;
if (hasWheel && valueLength - index >= 6) {
int wheelTotalRevolutionCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, index);
long wheelTotalRevolutionCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, index);
index += 4;
int wheelTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); // 1/1024s
speed = new SensorDataCyclingDistanceSpeed(address, sensorName, wheelTotalRevolutionCount, wheelTime);
wheelData = new WheelData(wheelTotalRevolutionCount, wheelTime);
index += 2;
}
SensorDataCyclingCadence cadence = null;
BluetoothHandlerCyclingCadence.CrankData crankData = 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);
crankData = new BluetoothHandlerCyclingCadence.CrankData(crankCount, crankTime);
}
return new SensorDataCyclingCadenceAndDistanceSpeed(address, sensorName, cadence, speed);
return new Pair<>(wheelData, crankData);
}
public record WheelData(
long wheelRevolutionsCount, // UINT32
int wheelRevolutionsTime // UINT16; 1/1024s
) {}
}
@@ -9,7 +9,7 @@ import java.util.List;
import java.util.UUID;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadence;
import de.dennisguse.opentracks.sensors.sensorData.Raw;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
@@ -32,16 +32,16 @@ public class BluetoothHandlerManagerCyclingPower implements SensorHandlerInterfa
@Override
public void handlePayload(SensorManager.SensorDataChangedObserver observer, @NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
SensorDataCyclingPower.Data cyclingPower = parseCyclingPower(address, sensorName, characteristic);
Data cyclingPower = parseCyclingPower(characteristic);
if (cyclingPower != null) {
observer.onChange(cyclingPower.power());
observer.onChange(new Raw<>(cyclingPower));
}
}
@VisibleForTesting
public static SensorDataCyclingPower.Data parseCyclingPower(String address, String sensorName, BluetoothGattCharacteristic characteristic) {
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public static Data parseCyclingPower(BluetoothGattCharacteristic characteristic) {
// DOCUMENTATION https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.cycling_power_measurement.xml
int valueLength = characteristic.getValue().length;
if (valueLength == 0) {
@@ -69,18 +69,19 @@ public class BluetoothHandlerManagerCyclingPower implements SensorHandlerInterfa
index += 2 + 2;
}
SensorDataCyclingCadence cadence = null;
BluetoothHandlerCyclingCadence.CrankData crankData = 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);
crankData = new BluetoothHandlerCyclingCadence.CrankData(crankCount, crankTime);
}
return new SensorDataCyclingPower.Data(new SensorDataCyclingPower(sensorName, address, Power.of(instantaneousPower)), cadence);
return new Data(Power.of(instantaneousPower), crankData);
}
public record Data(Power power, BluetoothHandlerCyclingCadence.CrankData crank) {}
}
@@ -9,6 +9,7 @@ import java.util.List;
import java.util.UUID;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.sensors.sensorData.Raw;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataHeartRate;
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
@@ -44,7 +45,7 @@ public class BluetoothHandlerManagerHeartRate implements SensorHandlerInterface
HeartRate heartRate = parseHeartRate(characteristic);
if (heartRate != null) {
observer.onChange(new SensorDataHeartRate(address, sensorName, heartRate));
observer.onChange(new Raw<>(heartRate));
}
}
@@ -11,6 +11,7 @@ import java.util.UUID;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.sensors.sensorData.Raw;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
@@ -34,11 +35,12 @@ public class BluetoothHandlerRunningSpeedAndCadence implements SensorHandlerInte
@Override
public void handlePayload(SensorManager.SensorDataChangedObserver observer, @NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
observer.onChange(parseRunningSpeedAndCadence(address, sensorName, characteristic));
Data data = parseRunningSpeedAndCadence(sensorName, characteristic);
observer.onChange(new Raw<>(data));
}
@VisibleForTesting
public static SensorDataRunning parseRunningSpeedAndCadence(String address, String sensorName, @NonNull BluetoothGattCharacteristic characteristic) {
public static Data parseRunningSpeedAndCadence(String sensorName, @NonNull BluetoothGattCharacteristic characteristic) {
// DOCUMENTATION https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.rsc_measurement.xml
int valueLength = characteristic.getValue().length;
if (valueLength == 0) {
@@ -79,6 +81,8 @@ public class BluetoothHandlerRunningSpeedAndCadence implements SensorHandlerInte
totalDistance = Distance.ofDM(characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, index));
}
return new SensorDataRunning(address, sensorName, speed, cadence, totalDistance);
return new Data(speed, cadence, totalDistance);
}
public record Data(Speed speed, Cadence cadence, Distance totalDistance) {}
}
@@ -50,7 +50,7 @@ public class BluetoothRemoteSensorManager implements SensorConnector, SharedPref
private static final String TAG = BluetoothRemoteSensorManager.class.getSimpleName();
public static final Duration MAX_SENSOR_DATE_SET_AGE = Duration.ofSeconds(5);
public static final Duration MAX_SENSOR_DATE_SET_AGE = Duration.ofSeconds(50); //TODO
private final BluetoothAdapter bluetoothAdapter;
private final Context context;
@@ -9,6 +9,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.sensors.sensorData.Raw;
import de.dennisguse.opentracks.sensors.sensorData.SensorData;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
import de.dennisguse.opentracks.services.handlers.GPSManager;
@@ -27,13 +28,19 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
private final SensorDataChangedObserver listener = new SensorDataChangedObserver() {
@Override
public void onChange(SensorData<?> sensorData) {
sensorDataSet.set(sensorData);
public void onConnect(SensorData<?, ?> sensorData) {
sensorDataSet.add(sensorData);
observer.onChange(new SensorDataSet(sensorDataSet));
}
@Override
public void onDisconnect(SensorData<?> sensorData) {
public void onChange(Raw<?> sensorData) {
sensorDataSet.update(sensorData);
observer.onChange(new SensorDataSet(sensorDataSet));
}
@Override
public void onDisconnect(SensorData<?, ?> sensorData) {
sensorDataSet.remove(sensorData);
observer.onChange(new SensorDataSet(sensorDataSet));
}
@@ -91,7 +98,7 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
@Deprecated
@VisibleForTesting
public void onChanged(SensorData<?> data) {
public void onChanged(Raw<?> data) {
listener.onChange(data);
}
@@ -120,8 +127,10 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
}
public interface SensorDataChangedObserver {
void onChange(SensorData<?> sensorData);
void onDisconnect(SensorData<?> sensorData);
void onConnect(SensorData<?, ?> sensorData);
void onChange(Raw<?> sensorData);
void onDisconnect(SensorData<?, ?> sensorData);
}
}
@@ -0,0 +1,16 @@
package de.dennisguse.opentracks.sensors.sensorData;
import androidx.annotation.NonNull;
import java.time.Instant;
public record Raw<T extends Record>(
@NonNull T value,
@NonNull Instant time
) {
public Raw(@NonNull T value) {
this(value, Instant.now()); //TODO We should be using the MonotonicClock
}
}
@@ -1,48 +1,48 @@
package de.dennisguse.opentracks.sensors.sensorData;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import java.time.Instant;
import de.dennisguse.opentracks.sensors.BluetoothRemoteSensorManager;
public abstract class SensorData<T> {
public abstract class SensorData<Input extends Record, Output> {
protected T value;
protected Raw<Input> previous;
protected Output value;
private final String sensorAddress;
private final String sensorName;
private final Instant time;
SensorData(String sensorAddress) {
this(sensorAddress, null);
}
SensorData(String sensorAddress, String sensorName) {
this(sensorAddress, sensorName, Instant.now());
}
@VisibleForTesting
SensorData(String sensorAddress, String sensorName, Instant time) {
this.sensorAddress = sensorAddress;
this.sensorName = sensorName;
this.time = time;
}
public String getSensorNameOrAddress() {
return sensorName != null ? sensorName : sensorAddress;
}
public final void add(Raw<Input> current) {
computeValue(current);
previous = current;
}
protected abstract void computeValue(Raw<Input> current);
public boolean hasValue() {
return value != null;
}
@NonNull
protected abstract T getNoneValue();
protected abstract Output getNoneValue();
public T getValue() {
public Output getValue() {
if (!hasValue()) {
return null;
}
@@ -55,20 +55,23 @@ public abstract class SensorData<T> {
/**
* Reset long term aggregated values (more than derived from previous SensorData). e.g. overall distance.
*/
public void reset() {
}
public void reset() {};
/**
* Is the data recent considering the current time.
*/
private boolean isRecent() {
if (previous == null) {
return false;
}
return Instant.now()
.isBefore(time.plus(BluetoothRemoteSensorManager.MAX_SENSOR_DATE_SET_AGE));
.isBefore(previous.time().plus(BluetoothRemoteSensorManager.MAX_SENSOR_DATE_SET_AGE));
}
@NonNull
@Override
public String toString() {
return "sensorAddress='" + sensorAddress;
return "sensorAddress=" + sensorAddress + " data=" + value;
}
}
@@ -3,53 +3,29 @@ 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.BluetoothHandlerCyclingCadence;
import de.dennisguse.opentracks.sensors.UintUtils;
public class SensorDataCyclingCadence extends SensorData<Cadence> {
public class SensorDataCyclingCadence extends SensorData<BluetoothHandlerCyclingCadence.CrankData, 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) {
public SensorDataCyclingCadence(String sensorAddress, String sensorName) {
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;
protected void computeValue(Raw<BluetoothHandlerCyclingCadence.CrankData> current) {
if (previous != null) {
float timeDiff_ms = UintUtils.diff(current.value().crankRevolutionsTime(), previous.value().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.");
@@ -58,30 +34,19 @@ public class SensorDataCyclingCadence extends SensorData<Cadence> {
}
// 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) {
if (current.value().crankRevolutionsCount() < previous.value().crankRevolutionsCount()) {
Log.e(TAG, "Crank revolutions count difference is invalid: cannot compute cadence.");
return;
}
long crankDiff = UintUtils.diff(crankRevolutionsCount, previous.crankRevolutionsCount, UintUtils.UINT32_MAX);
long crankDiff = UintUtils.diff(current.value().crankRevolutionsCount(), previous.value().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 comp)) return false;
if (hasData() && comp.hasData() == hasData()) {
return getCrankRevolutionsCount() == comp.getCrankRevolutionsCount() && getCrankRevolutionsTime() == comp.getCrankRevolutionsTime();
} else {
return false;
}
protected Cadence getNoneValue() {
return Cadence.of(0);
}
}
@@ -1,27 +0,0 @@
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);
}
}
@@ -3,58 +3,32 @@ 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.BluetoothHandlerCyclingDistanceSpeed;
import de.dennisguse.opentracks.sensors.UintUtils;
public class SensorDataCyclingDistanceSpeed extends SensorData<SensorDataCyclingDistanceSpeed.Data> {
public class SensorDataCyclingDistanceSpeed extends SensorData<BluetoothHandlerCyclingDistanceSpeed.WheelData, SensorDataCyclingDistanceSpeed.Data> {
private final String TAG = SensorDataCyclingDistanceSpeed.class.getSimpleName();
private final Long wheelRevolutionsCount; // UINT32
private final Integer wheelRevolutionsTime; // UINT16; 1/1024s
private Distance wheelCircumference;
public SensorDataCyclingDistanceSpeed(String sensorAddress) {
super(sensorAddress);
this.wheelRevolutionsCount = null;
this.wheelRevolutionsTime = null;
}
public SensorDataCyclingDistanceSpeed(String sensorAddress, String sensorName, long wheelRevolutionsCount, int wheelRevolutionsTime) {
public SensorDataCyclingDistanceSpeed(String sensorAddress, String sensorName) {
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;
protected void computeValue(Raw<BluetoothHandlerCyclingDistanceSpeed.WheelData> current) {
if (previous != null) {
float timeDiff_ms = UintUtils.diff(current.value().wheelRevolutionsTime(), previous.value().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.");
@@ -62,16 +36,16 @@ public class SensorDataCyclingDistanceSpeed extends SensorData<SensorDataCycling
return;
}
if (wheelRevolutionsCount < previous.wheelRevolutionsCount) {
if (current.value().wheelRevolutionsCount() < previous.value().wheelRevolutionsCount()) {
Log.e(TAG, "Wheel revolutions count difference is invalid: cannot compute speed.");
return;
}
long wheelDiff = UintUtils.diff(wheelRevolutionsCount, previous.wheelRevolutionsCount, UintUtils.UINT32_MAX);
long wheelDiff = UintUtils.diff(current.value().wheelRevolutionsCount(), previous.value().wheelRevolutionsCount(), UintUtils.UINT32_MAX);
Distance distance = wheelCircumference.multipliedBy(wheelDiff);
Distance distanceOverall = distance;
if (previous.hasValue()) {
distanceOverall = distance.plus(previous.getValue().distanceOverall);
if (value != null) {
distanceOverall = distance.plus(value.distanceOverall);
}
Speed speed_mps = Speed.of(distance, timeDiff);
value = new Data(distance, distanceOverall, speed_mps);
@@ -87,21 +61,19 @@ public class SensorDataCyclingDistanceSpeed extends SensorData<SensorDataCycling
@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 comp)) return false;
if (!(hasData() && comp.hasData())) {
return false;
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());
}
return getWheelRevolutionsCount() == comp.getWheelRevolutionsCount() && getWheelRevolutionsTime() == comp.getWheelRevolutionsTime();
}
public void setWheelCircumference(Distance wheelCircumference) {
this.wheelCircumference = wheelCircumference;
}
// TODO Make record
public static class Data {
private final Distance distance;
private final Distance distanceOverall;
@@ -1,19 +1,22 @@
package de.dennisguse.opentracks.sensors.sensorData;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import de.dennisguse.opentracks.data.models.Power;
public class SensorDataCyclingPower extends SensorData<Power> {
public class SensorDataCyclingPower extends SensorData<Power, Power> {
public SensorDataCyclingPower(String address) {
super(address);
}
public SensorDataCyclingPower(String name, String address, Power power) {
public SensorDataCyclingPower(String name, String address) {
super(name, address);
this.value = power;
}
@Override
public void computeValue(Raw<Power> current) {
this.value = current.value();
}
@NonNull
@@ -21,26 +24,4 @@ public class SensorDataCyclingPower extends SensorData<Power> {
protected Power getNoneValue() {
return Power.of(0f);
}
@NonNull
@Override
public String toString() {
return super.toString() + " data=" + value;
}
public record Data(SensorDataCyclingPower power, SensorDataCyclingCadence cadence) {
public Data(SensorDataCyclingPower power, @Nullable SensorDataCyclingCadence cadence) {
this.power = power;
this.cadence = cadence;
}
@NonNull
@Override
public String toString() {
return "Data{" +
"power=" + power +
", cadence=" + cadence +
'}';
}
}
}
@@ -4,21 +4,19 @@ import androidx.annotation.NonNull;
import de.dennisguse.opentracks.data.models.HeartRate;
public class SensorDataHeartRate extends SensorData<HeartRate> {
public class SensorDataHeartRate extends SensorData<HeartRate, HeartRate> {
public SensorDataHeartRate(String address) {
super(address);
}
public SensorDataHeartRate(String name, String address, @NonNull HeartRate heartRate) {
public SensorDataHeartRate(String name, String address) {
super(name, address);
this.value = heartRate;
}
@NonNull
@Override
public String toString() {
return super.toString() + " heart=" + value;
protected void computeValue(Raw<HeartRate> current) {
this.value = current.value();
}
@NonNull
@@ -1,56 +1,48 @@
package de.dennisguse.opentracks.sensors.sensorData;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
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.BluetoothHandlerRunningSpeedAndCadence;
/**
* Provides cadence in rpm and speed in milliseconds from Bluetooth LE Running Speed and Cadence sensors.
*/
public final class SensorDataRunning extends SensorData<SensorDataRunning.Data> {
public final class SensorDataRunning extends SensorData<BluetoothHandlerRunningSpeedAndCadence.Data, SensorDataRunning.Data> {
private static final String TAG = SensorDataRunning.class.getSimpleName();
private final Speed speed;
private final Cadence cadence;
private final Distance totalDistance;
public SensorDataRunning(String sensorAddress) {
super(sensorAddress);
this.speed = null;
this.cadence = null;
this.totalDistance = null;
}
public SensorDataRunning(String sensorAddress, String sensorName, Speed speed, Cadence cadence, Distance totalDistance) {
public SensorDataRunning(String sensorAddress, String sensorName) {
super(sensorAddress, sensorName);
this.speed = speed;
this.cadence = cadence;
this.totalDistance = totalDistance;
}
private boolean hasTotalDistance() {
return totalDistance != null;
@Override
public void computeValue(Raw<BluetoothHandlerRunningSpeedAndCadence.Data> current) {
if (previous != null) {
Distance distance = null;
if (previous.value().totalDistance() != null && current.value().totalDistance() != null) {
distance = current.value().totalDistance().minus(previous.value().totalDistance());
if (value != null) {
distance = distance.plus(value.distance);
}
}
value = new Data(current.value().speed(), current.value().cadence(), distance);
}
}
public Cadence getCadence() {
return cadence;
}
public Speed getSpeed() {
return speed;
}
@VisibleForTesting
public Distance getTotalDistance() {
return totalDistance;
@Override
public void reset() {
if (value != null) {
value = new Data(value.speed, value.cadence, Distance.of(0));
}
}
@NonNull
@@ -63,27 +55,7 @@ public final class SensorDataRunning extends SensorData<SensorDataRunning.Data>
}
}
public void compute(SensorDataRunning previous) {
if (speed != null && hasTotalDistance()) {
Distance overallDistance = null;
if (previous != null && previous.hasTotalDistance()) {
overallDistance = this.totalDistance.minus(previous.totalDistance);
if (previous.hasValue() && previous.getValue().distance() != null) {
overallDistance = overallDistance.plus(previous.getValue().distance());
}
}
value = new Data(speed, cadence, overallDistance);
}
public record Data(Speed speed, Cadence cadence, @NonNull Distance distance) {
}
@Override
public void reset() {
if (value != null) {
value = new Data(value.speed, value.cadence, Distance.of(0));
}
}
public record Data(Speed speed, Cadence cadence, @Nullable Distance distance) {}
}
@@ -1,31 +1,40 @@
package de.dennisguse.opentracks.sensors.sensorData;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingCadence;
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingDistanceSpeed;
import de.dennisguse.opentracks.sensors.BluetoothHandlerManagerCyclingPower;
import de.dennisguse.opentracks.sensors.BluetoothHandlerRunningSpeedAndCadence;
import de.dennisguse.opentracks.settings.PreferencesUtils;
public final class SensorDataSet {
private static final String TAG = SensorDataSet.class.getSimpleName();
private SensorDataHeartRate heartRate;
@VisibleForTesting
public SensorDataHeartRate heartRate;
private SensorDataCyclingCadence cyclingCadence;
@VisibleForTesting
public SensorDataCyclingCadence cyclingCadence;
private SensorDataCyclingDistanceSpeed cyclingDistanceSpeed;
@VisibleForTesting
public SensorDataCyclingDistanceSpeed cyclingDistanceSpeed;
private SensorDataCyclingPower cyclingPower;
@VisibleForTesting
public SensorDataCyclingPower cyclingPower;
private SensorDataRunning runningDistanceSpeedCadence;
@VisibleForTesting
public SensorDataRunning runningDistanceSpeedCadence;
public SensorDataSet() {
}
@@ -51,8 +60,8 @@ public final class SensorDataSet {
return new Pair<>(cyclingCadence.getValue(), cyclingCadence.getSensorNameOrAddress());
}
if (runningDistanceSpeedCadence != null) {
return new Pair<>(runningDistanceSpeedCadence.getCadence(), runningDistanceSpeedCadence.getSensorNameOrAddress());
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasValue() && runningDistanceSpeedCadence.value.cadence() != null) {
return new Pair<>(runningDistanceSpeedCadence.value.cadence(), runningDistanceSpeedCadence.getSensorNameOrAddress());
}
return null;
@@ -64,33 +73,51 @@ public final class SensorDataSet {
}
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasValue() && runningDistanceSpeedCadence.getValue().speed() != null) {
return new Pair<>(runningDistanceSpeedCadence.getSpeed(), runningDistanceSpeedCadence.getSensorNameOrAddress());
return new Pair<>(runningDistanceSpeedCadence.value.speed(), runningDistanceSpeedCadence.getSensorNameOrAddress());
}
return null;
}
public SensorDataCyclingCadence getCyclingCadence() {
return cyclingCadence;
}
public SensorDataCyclingDistanceSpeed getCyclingDistanceSpeed() {
return cyclingDistanceSpeed;
}
public SensorDataCyclingPower getCyclingPower() {
return cyclingPower;
}
public SensorDataRunning getRunningDistanceSpeedCadence() {
return runningDistanceSpeedCadence;
}
public void set(SensorData<?> data) {
public void add(@NonNull SensorData<?, ?> data) {
set(data, data);
}
public void remove(SensorData<?> type) {
public void update(@NonNull Raw<?> data) {
Record value = data.value();
if (value instanceof HeartRate) {
this.heartRate.add((Raw<HeartRate>) data);
return;
}
if (value instanceof BluetoothHandlerCyclingCadence.CrankData) {
this.cyclingCadence.add((Raw<BluetoothHandlerCyclingCadence.CrankData>) data);
return;
}
if (value instanceof BluetoothHandlerCyclingDistanceSpeed.WheelData ) {
this.cyclingDistanceSpeed.setWheelCircumference(PreferencesUtils.getWheelCircumference()); //TODO Fetch once and then listen for changes.
this.cyclingDistanceSpeed.add((Raw<BluetoothHandlerCyclingDistanceSpeed.WheelData>) data);
return;
}
if (value instanceof BluetoothHandlerRunningSpeedAndCadence.Data) {
this.runningDistanceSpeedCadence.add((Raw<BluetoothHandlerRunningSpeedAndCadence.Data>) data);
return;
}
if (value instanceof BluetoothHandlerManagerCyclingPower.Data) {
this.cyclingPower.add((Raw<Power>) data);
return;
}
throw new UnsupportedOperationException(data.getClass().getCanonicalName());
}
public void remove(@NonNull SensorData<?, ?> type) {
set(type, null);
}
@@ -136,71 +163,25 @@ public final class SensorDataSet {
if (runningDistanceSpeedCadence != null) runningDistanceSpeedCadence.reset();
}
@NonNull
@Override
public String toString() {
return (heartRate != null ? "" + heartRate : "")
+ (cyclingCadence != null ? " " + cyclingCadence : "")
+ (cyclingDistanceSpeed != null ? " " + cyclingDistanceSpeed : "")
+ (cyclingPower != null ? " " + cyclingPower : "")
+ (runningDistanceSpeedCadence != null ? " " + runningDistanceSpeedCadence : "");
}
private void set(@NonNull SensorData<?> type, @Nullable SensorData<?> sensorData) {
if (type instanceof SensorDataHeartRate) {
this.heartRate = (SensorDataHeartRate) sensorData;
private void set(@NonNull SensorData<?, ?> type, @Nullable SensorData<?, ?> sensorData) {
if (type instanceof SensorDataHeartRate hr) {
heartRate = hr;
return;
}
if (type instanceof SensorDataCyclingCadence) {
SensorDataCyclingCadence previous = getCyclingCadence();
Log.d(TAG, "Previous: " + previous + "; current: " + sensorData);
if (sensorData != null && sensorData.equals(previous)) {
Log.d(TAG, "onChanged: cadence data repeated.");
return;
}
this.cyclingCadence = (SensorDataCyclingCadence) sensorData;
if (this.cyclingCadence != null) {
this.cyclingCadence.compute(previous);
}
if (type instanceof SensorDataCyclingCadence cc) {
cyclingCadence = cc;
return;
}
if (type instanceof SensorDataCyclingDistanceSpeed) {
SensorDataCyclingDistanceSpeed previous = getCyclingDistanceSpeed();
Log.d(TAG, "Previous: " + previous + "; Current" + sensorData);
if (sensorData != null && sensorData.equals(previous)) {
Log.d(TAG, "onChanged: cycling speed data repeated.");
return;
}
Distance preferenceWheelCircumference = PreferencesUtils.getWheelCircumference(); //TODO Fetch once and then listen for changes.
this.cyclingDistanceSpeed = (SensorDataCyclingDistanceSpeed) sensorData;
if (this.cyclingDistanceSpeed != null) {
this.cyclingDistanceSpeed.compute(previous, preferenceWheelCircumference);
}
if (type instanceof SensorDataCyclingDistanceSpeed ds) {
cyclingDistanceSpeed = ds;
return;
}
if (type instanceof SensorDataCyclingPower) {
this.cyclingPower = (SensorDataCyclingPower) sensorData;
if (type instanceof SensorDataCyclingPower cp) {
cyclingPower = cp;
return;
}
if (type instanceof SensorDataRunning) {
SensorDataRunning previous = getRunningDistanceSpeedCadence();
Log.d(TAG, "Previous: " + previous + "; Current" + sensorData);
if (sensorData != null && sensorData.equals(previous)) {
Log.d(TAG, "onChanged: running speed data repeated.");
return;
}
this.runningDistanceSpeedCadence = (SensorDataRunning) sensorData;
if (this.runningDistanceSpeedCadence != null) {
this.runningDistanceSpeedCadence.compute(previous);
}
if (type instanceof SensorDataRunning rr) {
runningDistanceSpeedCadence = rr;
return;
}
@@ -11,7 +11,7 @@ public interface SensorHandlerInterface {
List<ServiceMeasurementUUID> getServices();
SensorData<?> createEmptySensorData(String address);
SensorData<?, ?> createEmptySensorData(String address);
void handlePayload(SensorManager.SensorDataChangedObserver observer, ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic);
}