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