Skip to content

Commit

Permalink
Sensors: separate data generation and aggregation.
Browse files Browse the repository at this point in the history
Part of #1424.
  • Loading branch information
dennisguse committed Nov 14, 2023
1 parent 15f3ccb commit 0d41d9f
Show file tree
Hide file tree
Showing 17 changed files with 226 additions and 209 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -548,22 +548,23 @@ private void assertMarkers() {
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 = new SensorDataCyclingPower("power", "power", Power.of(power));
sensorDataSet.heartRate = new SensorDataHeartRate("heartRate", "heartRate", 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,22 @@
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() {
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerCyclingDistanceSpeed.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
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
Expand All @@ -29,11 +28,11 @@ public void parseCyclingSpeedCadence_wheelOnly() {
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
Expand All @@ -42,11 +41,11 @@ public void parseCyclingSpeedCadence_crankWheel() {
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());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@

import org.junit.Test;

import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;

public class BluetoothHandlerCyclingPowerTest {

@Test
Expand All @@ -16,10 +14,10 @@ public void parseCyclingPower_power() {
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
Expand All @@ -28,13 +26,13 @@ public void parseCyclingPower_power_with_cadence() {
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());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
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 {

Expand All @@ -19,11 +18,11 @@ public void parseRunningSpeedAndCadence_with_distance() {
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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@
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.SensorDataHeartRate;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
import de.dennisguse.opentracks.sensors.sensorData.Raw;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.stats.TrackStatistics;
Expand Down Expand Up @@ -258,11 +258,11 @@ public void testRecording_blesensor_only_no_distance() {
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);
Expand Down Expand Up @@ -594,12 +594,12 @@ public void testRecording_gpsAndSensor_gpsIdleMoving_sensorMoving() {
// 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";
Expand All @@ -608,12 +608,12 @@ public void testRecording_gpsAndSensor_gpsIdleMoving_sensorMoving() {
// 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";
Expand All @@ -622,7 +622,7 @@ public void testRecording_gpsAndSensor_gpsIdleMoving_sensorMoving() {
// 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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
import java.util.Optional;
import java.util.UUID;

import de.dennisguse.opentracks.sensors.sensorData.SensorData;
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;

/**
Expand Down Expand Up @@ -140,8 +139,7 @@ synchronized void connect(Context context, Handler handler, @NonNull BluetoothDe

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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

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 {
Expand All @@ -30,24 +30,28 @@ public SensorDataCyclingCadence createEmptySensorData(String address) {

@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<>(address, sensorName, 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<>(address, sensorName, 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
) {}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package de.dennisguse.opentracks.sensors;

import android.bluetooth.BluetoothGattCharacteristic;
import android.util.Pair;

import androidx.annotation.NonNull;
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;

Expand All @@ -32,19 +32,15 @@ public SensorDataCyclingDistanceSpeed createEmptySensorData(String address) {

@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<>(address, sensorName, 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) {
Expand All @@ -56,24 +52,31 @@ public static SensorDataCyclingCadenceAndDistanceSpeed parseCyclingCrankAndWheel
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
) {}
}
Loading

0 comments on commit 0d41d9f

Please sign in to comment.