diff --git a/src/androidTest/java/de/dennisguse/opentracks/sensors/AltitudeSumManagerTest.java b/src/androidTest/java/de/dennisguse/opentracks/sensors/AltitudeSumManagerTest.java index c194f7db7..fb8160c52 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/sensors/AltitudeSumManagerTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/sensors/AltitudeSumManagerTest.java @@ -4,6 +4,8 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import de.dennisguse.opentracks.data.models.AtmosphericPressure; + /** * NOTE: Test data is completely artificial. */ @@ -13,7 +15,7 @@ public class AltitudeSumManagerTest { private static void addSensorValue(AltitudeSumManager altitudeSumManager, float[] values) { for (float f : values) { - altitudeSumManager.onSensorValueChanged(f); + altitudeSumManager.onSensorValueChanged(AtmosphericPressure.ofHPA(f)); } } @@ -28,11 +30,11 @@ public class AltitudeSumManagerTest { subject.setConnected(true); // then - addSensorValue(subject, new float[]{1015f, 1015.01f, 1015.02f, 1015.03f, 1015.04f, 1015.05f, 1015.06f, 1015.07f, 1015.08f, 1015.09f, 1015.10f, 1015.11f, 1015.12f, 1015.13f, 1015.14f, 1015.15f}); + addSensorValue(subject, new float[]{1015f, 1015.01f, 1015.02f, 1015.03f, 1015.04f, 1015.05f, 1015.06f, 1015.07f, 1015.08f, 1015.09f, 1015.10f, 1015.11f, 1015.12f, 1015.13f, 1018f, 1018.1f, 1018.1f, 1018.1f, 1018.1f}); // then Assert.assertEquals(0f, subject.getAltitudeGain_m(), 0.01); - Assert.assertEquals(48.0, subject.getAltitudeLoss_m(), 0.01); + Assert.assertEquals(15f, subject.getAltitudeLoss_m(), 0.01); } @Test @@ -41,7 +43,7 @@ public class AltitudeSumManagerTest { subject.setConnected(false); // then - subject.onSensorValueChanged(999f); + subject.onSensorValueChanged(AtmosphericPressure.ofHPA(999f)); // then Assert.assertNull(subject.getAltitudeGain_m()); diff --git a/src/androidTest/java/de/dennisguse/opentracks/sensors/BluetoothUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/sensors/BluetoothUtilsTest.java index 8654a4658..dd595458f 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/sensors/BluetoothUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/sensors/BluetoothUtilsTest.java @@ -7,6 +7,7 @@ import android.bluetooth.BluetoothGattCharacteristic; import org.junit.Test; +import de.dennisguse.opentracks.data.models.AtmosphericPressure; import de.dennisguse.opentracks.data.models.Cadence; import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.HeartRate; @@ -44,17 +45,16 @@ public class BluetoothUtilsTest { } @Test - public void parsePressure_Pa() { + public void parseEnvironmentalSensing_Pa() { // given - BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.PRESSURE.getServiceUUID(), 0, 0); + BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.BAROMETRIC_PRESSURE.getServiceUUID(), 0, 0); characteristic.setValue(new byte[]{(byte) 0xB2, (byte) 0x48, (byte) 0x0F, (byte) 0x00}); - // when - int pressure = BluetoothUtils.parsePressure(characteristic); + AtmosphericPressure pressure = BluetoothUtils.parseEnvironmentalSensing(characteristic); // then - assertEquals(1001650, pressure); + assertEquals(AtmosphericPressure.ofPA(100165), pressure); } @Test diff --git a/src/androidTest/java/de/dennisguse/opentracks/sensors/PressureSensorUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/sensors/PressureSensorUtilsTest.java index feae236d7..d1ef901ec 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/sensors/PressureSensorUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/sensors/PressureSensorUtilsTest.java @@ -5,6 +5,8 @@ import static org.junit.Assert.assertNull; import org.junit.Test; +import de.dennisguse.opentracks.data.models.AtmosphericPressure; + public class PressureSensorUtilsTest { // real data at 500Hz stationary @@ -12,11 +14,11 @@ public class PressureSensorUtilsTest { public void altitudeChanges_none() { // given float[] sensorValues_hPa = new float[]{1015.6876f, 1015.699f, 1015.70905f, 1015.71075f, 1015.7224f, 1015.72f, 1015.7373f, 1015.7481f, 1015.74133f, 1015.73553f, 1015.7462f, 1015.74896f, 1015.752f, 1015.754f, 1015.7456f, 1015.73303f, 1015.7288f, 1015.7239f, 1015.7298f, 1015.7387f, 1015.72534f, 1015.72577f, 1015.72797f, 1015.71796f, 1015.7113f, 1015.7097f, 1015.7096f, 1015.7013f, 1015.7072f, 1015.71063f, 1015.7088f, 1015.7115f, 1015.713f, 1015.7121f, 1015.7062f, 1015.7062f, 1015.6994f, 1015.69617f, 1015.68945f, 1015.698f, 1015.6913f, 1015.69696f, 1015.69293f, 1015.6955f, 1015.6928f, 1015.6939f, 1015.68787f, 1015.6881f, 1015.68536f, 1015.68726f, 1015.6879f, 1015.6904f, 1015.6937f, 1015.69476f, 1015.7046f, 1015.7019f, 1015.7079f, 1015.71124f, 1015.71216f, 1015.71436f, 1015.7171f, 1015.7186f, 1015.7186f, 1015.7246f, 1015.72046f, 1015.7211f, 1015.72375f, 1015.7194f, 1015.72125f, 1015.7161f, 1015.71185f, 1015.7188f, 1015.721f, 1015.7252f, 1015.72266f, 1015.7236f, 1015.72614f, 1015.7266f, 1015.7344f, 1015.7376f, 1015.7337f, 1015.7253f, 1015.71875f, 1015.7219f, 1015.7219f, 1015.7317f, 1015.7284f, 1015.73444f, 1015.73584f, 1015.7369f, 1015.73254f, 1015.7369f, 1015.7336f, 1015.73254f, 1015.73676f, 1015.74176f, 1015.74506f, 1015.7478f, 1015.755f, 1015.7515f, 1015.7543f, 1015.7459f, 1015.7468f, 1015.7492f, 1015.74585f, 1015.74756f, 1015.74756f, 1015.75085f, 1015.754f, 1015.75336f, 1015.7568f, 1015.75165f, 1015.761f, 1015.7527f, 1015.75684f, 1015.7635f, 1015.7585f, 1015.7552f, 1015.7475f, 1015.74506f, 1015.7542f, 1015.75586f, 1015.74176f, 1015.74414f, 1015.7501f, 1015.751f, 1015.7478f, 1015.755f, 1015.75836f, 1015.7592f, 1015.75256f, 1015.7559f, 1015.76654f, 1015.76154f, 1015.766f, 1015.77014f, 1015.76904f, 1015.76013f, 1015.7674f, 1015.76514f, 1015.7674f, 1015.7635f, 1015.76404f, 1015.7607f, 1015.754f, 1015.7574f, 1015.7607f, 1015.7607f, 1015.7607f, 1015.754f, 1015.7567f, 1015.7616f, 1015.7643f, 1015.7666f, 1015.7609f, 1015.766f, 1015.76764f, 1015.755f, 1015.75757f, 1015.74927f, 1015.74927f, 1015.7426f, 1015.745f, 1015.745f, 1015.7409f, 1015.7376f, 1015.7326f, 1015.74164f, 1015.74664f, 1015.745f, 1015.7459f, 1015.7502f, 1015.7493f, 1015.7476f, 1015.7493f, 1015.751f, 1015.7601f, 1015.76434f, 1015.766f, 1015.756f, 1015.7617f, 1015.7623f, 1015.76404f, 1015.76733f, 1015.7607f, 1015.7623f, 1015.76337f, 1015.7584f, 1015.7573f, 1015.7502f, 1015.7534f, 1015.7534f, 1015.7601f, 1015.75684f, 1015.7518f, 1015.75574f, 1015.7524f, 1015.7507f, 1015.7484f, 1015.7502f, 1015.7534f, 1015.7623f, 1015.75903f, 1015.75574f, 1015.75574f, 1015.7573f, 1015.7524f, 1015.75574f, 1015.75574f, 1015.7607f, 1015.7573f, 1015.754f, 1015.74677f, 1015.754f, 1015.75574f, 1015.75903f, 1015.7573f, 1015.7551f, 1015.7573f, 1015.754f, 1015.7507f, 1015.7374f, 1015.739f, 1015.73566f, 1015.73566f, 1015.7367f, 1015.7417f, 1015.7417f, 1015.739f, 1015.74835f, 1015.74567f, 1015.7517f, 1015.74066f, 1015.7374f, 1015.739f, 1015.7417f, 1015.73065f, 1015.72894f, 1015.73505f, 1015.73334f, 1015.74005f, 1015.74005f, 1015.7367f, 1015.7434f, 1015.744f, 1015.7423f, 1015.7384f, 1015.739f, 1015.7374f, 1015.7374f, 1015.7374f, 1015.7341f, 1015.73505f, 1015.7341f, 1015.7267f, 1015.72833f, 1015.7234f, 1015.7167f, 1015.725f, 1015.7217f, 1015.7234f, 1015.73004f, 1015.7324f, 1015.7423f, 1015.74005f, 1015.73505f, 1015.73004f, 1015.739f, 1015.7417f, 1015.74567f, 1015.74066f, 1015.74146f, 1015.74146f, 1015.73816f, 1015.7274f, 1015.72906f, 1015.7274f, 1015.7341f, 1015.739f, 1015.744f, 1015.7417f, 1015.7374f, 1015.7324f, 1015.73816f, 1015.74146f, 1015.7374f, 1015.7341f, 1015.7365f, 1015.7365f, 1015.74146f, 1015.74414f, 1015.7475f, 1015.7448f, 1015.74146f, 1015.74585f, 1015.7475f, 1015.75146f, 1015.75146f, 1015.7591f, 1015.75476f, 1015.7614f, 1015.75476f, 1015.7492f, 1015.74817f, 1015.7498f, 1015.7515f, 1015.75586f, 1015.7509f, 1015.7448f, 1015.7525f, 1015.7581f, 1015.7592f}; - float firstSensorValue = sensorValues_hPa[0]; + AtmosphericPressure firstSensorValue = AtmosphericPressure.ofHPA(sensorValues_hPa[0]); // when // then for (float v : sensorValues_hPa) { - assertNull(PressureSensorUtils.computeChanges(firstSensorValue, v)); + assertNull(PressureSensorUtils.computeChanges(firstSensorValue, AtmosphericPressure.ofHPA(v))); } } @@ -25,25 +27,25 @@ public class PressureSensorUtilsTest { @Test public void computeChanges_downhill() { float[] sensorValues_hPa = new float[]{1015f, 1015.01f, 1015.02f, 1015.03f, 1015.04f, 1015.05f, 1015.06f, 1015.07f, 1015.08f, 1015.09f, 1015.10f, 1015.11f, 1015.12f, 1015.13f, 1018f, 1018.1f, 1018.1f, 1018.1f, 1018.1f}; - float firstSensorValue = sensorValues_hPa[0]; + AtmosphericPressure firstSensorValue = AtmosphericPressure.ofHPA(sensorValues_hPa[0]); // when float altitudeGain_m = 0; float altitudeLoss = 0; - float lastUsedPressureValue_hPa = firstSensorValue; + AtmosphericPressure lastUsedPressureValue = firstSensorValue; for (float v : sensorValues_hPa) { - PressureSensorUtils.AltitudeChange altitudeChange = PressureSensorUtils.computeChanges(lastUsedPressureValue_hPa, v); + PressureSensorUtils.AltitudeChange altitudeChange = PressureSensorUtils.computeChanges(lastUsedPressureValue, AtmosphericPressure.ofHPA(v)); if (altitudeChange != null) { altitudeGain_m += altitudeChange.getAltitudeGain_m(); altitudeLoss += altitudeChange.getAltitudeLoss_m(); - lastUsedPressureValue_hPa = altitudeChange.getCurrentSensorValue_hPa(); + lastUsedPressureValue = altitudeChange.getCurrentSensorValue(); } } // then assertEquals(0f, altitudeGain_m, 0.01); assertEquals(15.0f, altitudeLoss, 0.01); - assertEquals(1016.80f, lastUsedPressureValue_hPa, 0.01); //Expect exponential smoothing + assertEquals(1016.80f, lastUsedPressureValue.getHPA(), 0.01); //Expect exponential smoothing } } \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/data/models/AtmosphericPressure.java b/src/main/java/de/dennisguse/opentracks/data/models/AtmosphericPressure.java new file mode 100644 index 000000000..c32d5d6fb --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/data/models/AtmosphericPressure.java @@ -0,0 +1,48 @@ +package de.dennisguse.opentracks.data.models; + +import java.util.Objects; + +public class AtmosphericPressure { + + public static AtmosphericPressure ofPA(float value_Pa) { + return new AtmosphericPressure(value_Pa * 100); + } + + public static AtmosphericPressure ofHPA(float value_hPa) { + return new AtmosphericPressure(value_hPa); + } + + private final float value; + + private AtmosphericPressure(float value) { + this.value = value; + } + + public float getPA() { + return value * 100; + } + + public float getHPA() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AtmosphericPressure that = (AtmosphericPressure) o; + return Float.compare(that.value, value) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return "AtmosphericPressure{" + + "value=" + value + + '}'; + } +} diff --git a/src/main/java/de/dennisguse/opentracks/sensors/AltitudeSumManager.java b/src/main/java/de/dennisguse/opentracks/sensors/AltitudeSumManager.java index fbd7837aa..c44522405 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/AltitudeSumManager.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/AltitudeSumManager.java @@ -14,6 +14,7 @@ import androidx.annotation.VisibleForTesting; import java.util.concurrent.TimeUnit; +import de.dennisguse.opentracks.data.models.AtmosphericPressure; import de.dennisguse.opentracks.data.models.TrackPoint; /** @@ -25,9 +26,9 @@ public class AltitudeSumManager implements SensorConnector, SensorEventListener private boolean isConnected = false; - private float lastAcceptedPressureValue_hPa; + private AtmosphericPressure lastAcceptedSensorValue; - private float lastSeenSensorValue_hPa; + private AtmosphericPressure lastSeenSensorValue; private Float altitudeGain_m; private Float altitudeLoss_m; @@ -43,7 +44,7 @@ public class AltitudeSumManager implements SensorConnector, SensorEventListener isConnected = sensorManager.registerListener(this, pressureSensor, (int) TimeUnit.SECONDS.toMicros(5), handler); } - lastAcceptedPressureValue_hPa = Float.NaN; + lastAcceptedSensorValue = null; reset(); } @@ -116,30 +117,30 @@ public class AltitudeSumManager implements SensorConnector, SensorEventListener Log.w(TAG, "Not connected to sensor, cannot process data."); return; } - onSensorValueChanged(event.values[0]); + onSensorValueChanged(AtmosphericPressure.ofHPA(event.values[0])); } @VisibleForTesting - void onSensorValueChanged(float value_hPa) { - if (Float.isNaN(lastAcceptedPressureValue_hPa)) { - lastAcceptedPressureValue_hPa = value_hPa; - lastSeenSensorValue_hPa = value_hPa; + void onSensorValueChanged(AtmosphericPressure currentSensorValue) { + if (lastAcceptedSensorValue == null) { + lastAcceptedSensorValue = currentSensorValue; + lastSeenSensorValue = currentSensorValue; return; } altitudeGain_m = altitudeGain_m != null ? altitudeGain_m : 0; altitudeLoss_m = altitudeLoss_m != null ? altitudeLoss_m : 0; - PressureSensorUtils.AltitudeChange altitudeChange = PressureSensorUtils.computeChangesWithSmoothing_m(lastAcceptedPressureValue_hPa, lastSeenSensorValue_hPa, value_hPa); + PressureSensorUtils.AltitudeChange altitudeChange = PressureSensorUtils.computeChangesWithSmoothing_m(lastAcceptedSensorValue, lastSeenSensorValue, currentSensorValue); if (altitudeChange != null) { altitudeGain_m += altitudeChange.getAltitudeGain_m(); altitudeLoss_m += altitudeChange.getAltitudeLoss_m(); - lastAcceptedPressureValue_hPa = altitudeChange.getCurrentSensorValue_hPa(); + lastAcceptedSensorValue = altitudeChange.getCurrentSensorValue(); } - lastSeenSensorValue_hPa = value_hPa; + lastSeenSensorValue = currentSensorValue; Log.v(TAG, "altitude gain: " + altitudeGain_m + ", altitude loss: " + altitudeLoss_m); } diff --git a/src/main/java/de/dennisguse/opentracks/sensors/BluetoothUtils.java b/src/main/java/de/dennisguse/opentracks/sensors/BluetoothUtils.java index 23e099d81..028dff1e1 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/BluetoothUtils.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/BluetoothUtils.java @@ -28,6 +28,7 @@ import java.util.Collections; import java.util.List; import java.util.UUID; +import de.dennisguse.opentracks.data.models.AtmosphericPressure; import de.dennisguse.opentracks.data.models.BatteryLevel; import de.dennisguse.opentracks.data.models.Cadence; import de.dennisguse.opentracks.data.models.Distance; @@ -69,8 +70,9 @@ public class BluetoothUtils { )) ); - public static final ServiceMeasurementUUID PRESSURE = new ServiceMeasurementUUID( - new UUID(0x181A00001000L, 0x800000805f9b34fbL), + private static final UUID ENVIRONMENTAL_SENSING_SERVICE = new UUID(0x181A00001000L, 0x800000805f9b34fbL); + public static final ServiceMeasurementUUID BAROMETRIC_PRESSURE = new ServiceMeasurementUUID( + ENVIRONMENTAL_SENSING_SERVICE, new UUID(0x2A6D00001000L, 0x800000805f9b34fbL) ); @@ -142,15 +144,15 @@ public class BluetoothUtils { return null; } - public static Integer parsePressure(BluetoothGattCharacteristic characteristic) { + public static AtmosphericPressure parseEnvironmentalSensing(BluetoothGattCharacteristic characteristic) { byte[] raw = characteristic.getValue(); if (raw.length < 4) { return null; } - Float fPressure = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_FLOAT, 0); - return Math.round(fPressure); + Integer pressure = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 0); + return AtmosphericPressure.ofPA(pressure / 10f); } public static SensorDataCyclingPower.Data parseCyclingPower(String address, String sensorName, BluetoothGattCharacteristic characteristic) { diff --git a/src/main/java/de/dennisguse/opentracks/sensors/PressureSensorUtils.java b/src/main/java/de/dennisguse/opentracks/sensors/PressureSensorUtils.java index ed447a6f6..d86e88ca7 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/PressureSensorUtils.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/PressureSensorUtils.java @@ -4,6 +4,8 @@ import android.hardware.SensorManager; import androidx.annotation.VisibleForTesting; +import de.dennisguse.opentracks.data.models.AtmosphericPressure; + public class PressureSensorUtils { //Everything above is considered a meaningful change in altitude. @@ -18,17 +20,17 @@ public class PressureSensorUtils { public static class AltitudeChange { - private final float currentSensorValue_hPa; + private final AtmosphericPressure currentSensorValue; private final float altitudeChange_m; - public AltitudeChange(float currentSensorValue_hPa, float altitudeChange_m) { - this.currentSensorValue_hPa = currentSensorValue_hPa; + public AltitudeChange(AtmosphericPressure currentSensorValue, float altitudeChange_m) { + this.currentSensorValue = currentSensorValue; this.altitudeChange_m = altitudeChange_m; } - public float getCurrentSensorValue_hPa() { - return currentSensorValue_hPa; + public AtmosphericPressure getCurrentSensorValue() { + return currentSensorValue; } public float getAltitudeChange_m() { @@ -47,16 +49,16 @@ public class PressureSensorUtils { /** * Applies exponential smoothing to sensor value before computation. */ - public static AltitudeChange computeChangesWithSmoothing_m(float lastAcceptedSensorValue_hPa, float lastSeenSensorValue_hPa, float currentSensorValue_hPa) { - float nextSensorValue_hPa = EXPONENTIAL_SMOOTHING * currentSensorValue_hPa + (1 - EXPONENTIAL_SMOOTHING) * lastSeenSensorValue_hPa; + public static AltitudeChange computeChangesWithSmoothing_m(AtmosphericPressure lastAcceptedSensorValue, AtmosphericPressure lastSeenSensorValue, AtmosphericPressure currentSensorValue) { + AtmosphericPressure nextSensorValue = AtmosphericPressure.ofHPA(EXPONENTIAL_SMOOTHING * currentSensorValue.getHPA() + (1 - EXPONENTIAL_SMOOTHING) * lastSeenSensorValue.getHPA()); - return computeChanges(lastAcceptedSensorValue_hPa, nextSensorValue_hPa); + return computeChanges(lastAcceptedSensorValue, nextSensorValue); } @VisibleForTesting - public static AltitudeChange computeChanges(float lastAcceptedSensorValue_hPa, float currentSensorValue_hPa) { - float lastSensorValue_m = SensorManager.getAltitude(p0, lastAcceptedSensorValue_hPa); - float currentSensorValue_m = SensorManager.getAltitude(p0, currentSensorValue_hPa); + public static AltitudeChange computeChanges(AtmosphericPressure lastAcceptedSensorValue, AtmosphericPressure currentSensorValue) { + float lastSensorValue_m = SensorManager.getAltitude(p0, lastAcceptedSensorValue.getHPA()); + float currentSensorValue_m = SensorManager.getAltitude(p0, currentSensorValue.getHPA()); float altitudeChange_m = currentSensorValue_m - lastSensorValue_m; if (Math.abs(altitudeChange_m) < ALTITUDE_CHANGE_DIFF_M) { @@ -64,7 +66,7 @@ public class PressureSensorUtils { } // Limit altitudeC change by ALTITUDE_CHANGE_DIFF and computes pressure value accordingly. - AltitudeChange altitudeChange = new AltitudeChange(currentSensorValue_hPa, altitudeChange_m); + AltitudeChange altitudeChange = new AltitudeChange(currentSensorValue, altitudeChange_m); if (altitudeChange.getAltitudeChange_m() > 0) { return new AltitudeChange(getBarometricPressure(lastSensorValue_m + ALTITUDE_CHANGE_DIFF_M), ALTITUDE_CHANGE_DIFF_M); } else { @@ -78,7 +80,7 @@ public class PressureSensorUtils { * {\color{White} p(h)} = p_0 \cdot \left( 1 - \frac{0{,}0065 \frac{\mathrm K}{\mathrm m} \cdot h}{T_0\ } \right)^{5{,}255} */ @VisibleForTesting - public static float getBarometricPressure(float altitude_m) { - return (float) (p0 * Math.pow(1.0 - 0.0065 * altitude_m / 288.15, 5.255f)); + public static AtmosphericPressure getBarometricPressure(float altitude_m) { + return AtmosphericPressure.ofHPA((float) (p0 * Math.pow(1.0 - 0.0065 * altitude_m / 288.15, 5.255f))); } }