diff --git a/src/androidTest/java/de/dennisguse/opentracks/TrackStubUtils.java b/src/androidTest/java/de/dennisguse/opentracks/TrackStubUtils.java index 0a6420038..e258bce30 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/TrackStubUtils.java +++ b/src/androidTest/java/de/dennisguse/opentracks/TrackStubUtils.java @@ -19,7 +19,7 @@ package de.dennisguse.opentracks; import android.location.Location; import de.dennisguse.opentracks.content.data.TrackPoint; -import de.dennisguse.opentracks.content.sensor.SensorDataSet; +import de.dennisguse.opentracks.content.data.TrackPointSensorDataSet; /** * Commons utilities for creating stubs of track, location. @@ -63,6 +63,6 @@ public class TrackStubUtils { location.setTime(INITIAL_TIME); location.setBearing(INITIAL_BEARING); - return new TrackPoint(location, new SensorDataSet(Float.NaN, Float.NaN)); + return new TrackPoint(location, new TrackPointSensorDataSet()); } } diff --git a/src/androidTest/java/de/dennisguse/opentracks/content/sensor/SensorDataCyclingTest.java b/src/androidTest/java/de/dennisguse/opentracks/content/sensor/SensorDataCyclingTest.java new file mode 100644 index 000000000..b80b990a7 --- /dev/null +++ b/src/androidTest/java/de/dennisguse/opentracks/content/sensor/SensorDataCyclingTest.java @@ -0,0 +1,79 @@ +package de.dennisguse.opentracks.content.sensor; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import de.dennisguse.opentracks.util.UintUtils; + +@RunWith(AndroidJUnit4.class) +public class SensorDataCyclingTest { + + @Test + public void compute_cadence() { + // given + SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence(1, 1024); // 1s + SensorDataCycling.Cadence current = new SensorDataCycling.Cadence(2, 2048); // 2s + + // when + current.compute(previous); + + // then + Assert.assertEquals(57.2519, current.getCadence_rpm(), 0.01); + } + + @Test + public void compute_cadence_sameCount() { + // given + SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence(1, 1024); + SensorDataCycling.Cadence current = new SensorDataCycling.Cadence(1, 2048); + + // when + current.compute(previous); + + // then + Assert.assertEquals(0, current.getCadence_rpm(), 0.01); + } + + + @Test + public void compute_cadence_sameTime() { + // given + SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence(1, 1024); + SensorDataCycling.Cadence current = new SensorDataCycling.Cadence(2, 1024); + + // when + current.compute(previous); + + // then + Assert.assertFalse(current.hasCadence_rpm()); + } + + @Test + public void compute_cadence_rollOverTime() { + // given + SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence(1, UintUtils.UINT16_MAX - 1024); + SensorDataCycling.Cadence current = new SensorDataCycling.Cadence(2, 0); + + // when + current.compute(previous); + + // then + Assert.assertEquals(57.2519, current.getCadence_rpm(), 0.01); + } + + @Test + public void compute_cadence_rollOverCount() { + // given + SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence(UintUtils.UINT32_MAX - 1, 1024); + SensorDataCycling.Cadence current = new SensorDataCycling.Cadence(0, 2048); + + // when + current.compute(previous); + + // then + Assert.assertEquals(57.2519, current.getCadence_rpm(), 0.01); + } +} \ No newline at end of file diff --git a/src/androidTest/java/de/dennisguse/opentracks/fragments/ChartFragmentTest.java b/src/androidTest/java/de/dennisguse/opentracks/fragments/ChartFragmentTest.java index 22c45f79e..acddf2688 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/fragments/ChartFragmentTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/fragments/ChartFragmentTest.java @@ -33,7 +33,7 @@ import de.dennisguse.opentracks.TrackStubUtils; import de.dennisguse.opentracks.chart.ChartPoint; import de.dennisguse.opentracks.chart.ChartView; import de.dennisguse.opentracks.content.data.TrackPoint; -import de.dennisguse.opentracks.content.sensor.SensorDataSet; +import de.dennisguse.opentracks.content.data.TrackPointSensorDataSet; import de.dennisguse.opentracks.util.UnitConversions; /** @@ -82,7 +82,7 @@ public class ChartFragmentTest { // Input incorrect state. // Creates SensorData. - SensorDataSet sensorDataSet = new SensorDataSet(SensorDataSet.DATA_UNAVAILABLE, SensorDataSet.DATA_UNAVAILABLE); + TrackPointSensorDataSet sensorDataSet = new TrackPointSensorDataSet(); trackPoint.setSensorDataSet(sensorDataSet); // Test. point = chartFragment.createPendingPoint(trackPoint); @@ -104,7 +104,10 @@ public class ChartFragmentTest { Assert.assertEquals(Float.NaN, point.getPower(), 0.01); // Creates SensorData. - SensorDataSet sensorDataSet = new SensorDataSet(100, 101, 102); + TrackPointSensorDataSet sensorDataSet = new TrackPointSensorDataSet(); + sensorDataSet.setHeartRate_bpm(100f); + sensorDataSet.setCyclingCadence(101); + sensorDataSet.setPower(102f); // Creates SensorDataSet. trackPoint.setSensorDataSet(sensorDataSet); diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLocation.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLocation.java index eaeda2d6c..81a64c416 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLocation.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLocation.java @@ -244,13 +244,8 @@ public class TrackRecordingServiceTestLocation { } @Override - public boolean isSensorDataSetValid() { - return true; - } - - @Override - public SensorDataSet getSensorDataSet() { - return new SensorDataSet(1, 2); + public SensorDataSet getSensorData() { + return new SensorDataSet(); } }); diff --git a/src/androidTest/java/de/dennisguse/opentracks/util/BluetoothUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/util/BluetoothUtilsTest.java new file mode 100644 index 000000000..2ff661487 --- /dev/null +++ b/src/androidTest/java/de/dennisguse/opentracks/util/BluetoothUtilsTest.java @@ -0,0 +1,76 @@ +package de.dennisguse.opentracks.util; + +import android.bluetooth.BluetoothGattCharacteristic; + +import org.junit.Assert; +import org.junit.Test; + +import de.dennisguse.opentracks.content.sensor.SensorDataCycling; + +public class BluetoothUtilsTest { + + @Test + public void parseHeartRate_uint8() { + // given + BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.HEART_RATE_SERVICE_UUID, 0, 0); + characteristic.setValue(new byte[]{0x02, 0x3C}); + + // when + int heartRate = BluetoothUtils.parseHeartRate(characteristic); + + // then + Assert.assertEquals(60, heartRate); + } + + @Test + public void parseHeartRate_uint16() { + // given + BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.HEART_RATE_SERVICE_UUID, 0, 0); + characteristic.setValue(new byte[]{0x01, 0x01, 0x01}); + + // when + int heartRate = BluetoothUtils.parseHeartRate(characteristic); + + // then + Assert.assertEquals(257, heartRate); + } + + @Test + public void parseCyclingSpeedCadence_crankOnly() { + BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, 0, 0); + characteristic.setValue(new byte[]{0x01, (byte) 0xC8, 0x00, 0x00, 0x00, 0x06, (byte) 0x99}); + + // when + SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic); + + // then + Assert.assertEquals(200, sensor.getCadence().getCrankRevolutionsCount()); + Assert.assertNull(sensor.getSpeed()); + } + + @Test + public void parseCyclingSpeedCadence_wheelOnly() { + BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, 0, 0); + characteristic.setValue(new byte[]{0x02, (byte) 0xE1, 0x00, 0x45, (byte) 0x99}); + + // when + SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic); + + // then + Assert.assertNull(sensor.getCadence()); + Assert.assertEquals(225, sensor.getSpeed().getWheelRevolutionsCount()); + } + + @Test + public void parseCyclingSpeedCadence_crankWheel() { + BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, 0, 0); + characteristic.setValue(new byte[]{0x03, (byte) 0xC8, 0x00, 0x00, 0x00, 0x06, (byte) 0x99, (byte) 0xE1, 0x00, 0x45, (byte) 0x99}); + + // when + SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic); + + // then + Assert.assertEquals(200, sensor.getCadence().getCrankRevolutionsCount()); + Assert.assertEquals(225, sensor.getSpeed().getWheelRevolutionsCount()); + } +} \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/chart/ChartPoint.java b/src/main/java/de/dennisguse/opentracks/chart/ChartPoint.java index 940769378..262ad6231 100644 --- a/src/main/java/de/dennisguse/opentracks/chart/ChartPoint.java +++ b/src/main/java/de/dennisguse/opentracks/chart/ChartPoint.java @@ -3,7 +3,7 @@ package de.dennisguse.opentracks.chart; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; -import de.dennisguse.opentracks.content.sensor.SensorDataSet; +import de.dennisguse.opentracks.content.data.TrackPointSensorDataSet; import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.stats.TrackStatisticsUpdater; import de.dennisguse.opentracks.util.UnitConversions; @@ -25,7 +25,7 @@ public class ChartPoint { this.elevation = elevation; } - public ChartPoint(@NonNull TrackStatisticsUpdater trackStatisticsUpdater, SensorDataSet sensorDataSet, boolean chartByDistance, boolean metricUnits) { + public ChartPoint(@NonNull TrackStatisticsUpdater trackStatisticsUpdater, TrackPointSensorDataSet sensorDataSet, boolean chartByDistance, boolean metricUnits) { TrackStatistics trackStatistics = trackStatisticsUpdater.getTrackStatistics(); if (chartByDistance) { @@ -50,10 +50,10 @@ public class ChartPoint { pace = speed == 0 ? 0.0 : 60.0 / speed; if (sensorDataSet != null) { if (sensorDataSet.hasHeartRate()) { - heartRate = sensorDataSet.getHeartRate(); + heartRate = sensorDataSet.getHeartRate_bpm(); } - if (sensorDataSet.hasCadence()) { - cadence = sensorDataSet.getCadence(); + if (sensorDataSet.hasCyclingCadence()) { + cadence = sensorDataSet.getCyclingCadence_rpm(); } if (sensorDataSet.hasPower()) { power = sensorDataSet.getPower(); diff --git a/src/main/java/de/dennisguse/opentracks/content/data/TrackPoint.java b/src/main/java/de/dennisguse/opentracks/content/data/TrackPoint.java index a3f230bb8..992260471 100644 --- a/src/main/java/de/dennisguse/opentracks/content/data/TrackPoint.java +++ b/src/main/java/de/dennisguse/opentracks/content/data/TrackPoint.java @@ -20,8 +20,6 @@ import android.location.LocationManager; import androidx.annotation.NonNull; -import de.dennisguse.opentracks.content.sensor.SensorDataSet; - /** * This class extends the standard Android location with extra information. * @@ -39,17 +37,17 @@ public class TrackPoint { this.location = location; } - public TrackPoint(@NonNull Location location, SensorDataSet sensorDataSet) { + public TrackPoint(@NonNull Location location, TrackPointSensorDataSet sensorDataSet) { this.location = location; this.sensorDataSet = sensorDataSet; } - public TrackPoint(@NonNull TrackPoint trackPoint, SensorDataSet sensorDataSet) { + public TrackPoint(@NonNull TrackPoint trackPoint, TrackPointSensorDataSet sensorDataSet) { this.location = trackPoint.getLocation(); this.sensorDataSet = sensorDataSet; } - private SensorDataSet sensorDataSet = null; + private TrackPointSensorDataSet sensorDataSet = null; public TrackPoint(double latitude, double longitude, Double altitude, long time) { location = new Location(LocationManager.GPS_PROVIDER); @@ -90,11 +88,11 @@ public class TrackPoint { return location; } - public SensorDataSet getSensorDataSet() { + public TrackPointSensorDataSet getSensorDataSet() { return sensorDataSet; } - public void setSensorDataSet(SensorDataSet sensorDataSet) { + public void setSensorDataSet(TrackPointSensorDataSet sensorDataSet) { this.sensorDataSet = sensorDataSet; } diff --git a/src/main/java/de/dennisguse/opentracks/content/data/TrackPointSensorDataSet.java b/src/main/java/de/dennisguse/opentracks/content/data/TrackPointSensorDataSet.java new file mode 100644 index 000000000..e16118b9d --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/content/data/TrackPointSensorDataSet.java @@ -0,0 +1,99 @@ +package de.dennisguse.opentracks.content.data; + +import androidx.annotation.NonNull; + +//TODO Rename. +public final class TrackPointSensorDataSet { + private long time_ms; + + private Float heartRate_bpm = null; + private Float cyclingCadence = null; + private Float cyclingSpeed = null; + private Float power = null; + + public TrackPointSensorDataSet() { + this(System.currentTimeMillis()); + } + + public TrackPointSensorDataSet(long time_ms) { + this.time_ms = time_ms; + } + + public boolean hasHeartRate() { + return heartRate_bpm != null && heartRate_bpm > 0; + } + + public float getHeartRate_bpm() { + return heartRate_bpm; + } + + public void setHeartRate_bpm(Float heartRate_bpm) { + this.heartRate_bpm = heartRate_bpm; + } + + public boolean hasCyclingCadence() { + return cyclingCadence != null; + } + + public Float getCyclingCadence_rpm() { + return cyclingCadence; + } + + public void setCyclingCadence(Float cyclingCadence) { + this.cyclingCadence = cyclingCadence; + } + + public void setCyclingCadence(Integer cyclingCadence) { + this.cyclingCadence = cyclingCadence != null ? cyclingCadence.floatValue() : null; + } + + public boolean hasCyclingSpeed() { + return cyclingSpeed != null; + } + + public Float getCyclingSpeed() { + return cyclingSpeed; + } + + public void setCyclingSpeed(Float cyclingSpeed) { + this.cyclingSpeed = cyclingSpeed; + } + + public boolean hasPower() { + return power != null; + } + + public Float getPower() { + return power; + } + + public void setPower(Float power) { + this.power = power; + } + + public long getTime() { + return time_ms; + } + + public void setTime(long time_ms) { + this.time_ms = time_ms; + } + + /** + * Is the data recent considering the current time. + * + * @param maxAge the maximal age in milliseconds. + */ + public boolean isRecent(long maxAge) { + return time_ms + maxAge > System.currentTimeMillis(); + } + + @NonNull + @Override + public String toString() { + return "time=" + getTime() + + (hasHeartRate() ? " heart=" + getHeartRate_bpm() : "") + + (hasCyclingCadence() ? " cyclingCad=" + getCyclingCadence_rpm() : "") + + (hasCyclingSpeed() ? " cyclingSpeed=" + getCyclingSpeed() : ""); + } +} diff --git a/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java b/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java index 409b3cab2..b429be852 100644 --- a/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java +++ b/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java @@ -36,11 +36,11 @@ import de.dennisguse.opentracks.android.ContentResolverWrapper; import de.dennisguse.opentracks.android.IContentResolver; import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.TrackPoint; +import de.dennisguse.opentracks.content.data.TrackPointSensorDataSet; import de.dennisguse.opentracks.content.data.TrackPointsColumns; import de.dennisguse.opentracks.content.data.TracksColumns; import de.dennisguse.opentracks.content.data.Waypoint; import de.dennisguse.opentracks.content.data.WaypointsColumns; -import de.dennisguse.opentracks.content.sensor.SensorDataSet; import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.util.FileUtils; @@ -621,12 +621,19 @@ public class ContentProviderUtils { trackPoint.setBearing(cursor.getFloat(indexes.bearingIndex)); } - float heartRate = cursor.isNull(indexes.sensorHeartRateIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorHeartRateIndex); - float cadence = cursor.isNull(indexes.sensorCadenceIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorCadenceIndex); - float power = cursor.isNull(indexes.sensorPowerIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorPowerIndex); + TrackPointSensorDataSet sensorDataSet = new TrackPointSensorDataSet(); - trackPoint.setSensorDataSet(new SensorDataSet(heartRate, cadence, power, SensorDataSet.DATA_UNAVAILABLE, trackPoint.getTime())); + if (cursor.isNull(indexes.sensorHeartRateIndex)) { + sensorDataSet.setHeartRate_bpm(cursor.getFloat(indexes.sensorHeartRateIndex)); + } + if (cursor.isNull(indexes.sensorCadenceIndex)) { + sensorDataSet.setCyclingCadence(cursor.getFloat(indexes.sensorCadenceIndex)); + } + if (cursor.isNull(indexes.sensorPowerIndex)) { + sensorDataSet.setCyclingSpeed(cursor.getFloat(indexes.sensorPowerIndex)); + } + trackPoint.setSensorDataSet(sensorDataSet); return trackPoint; } @@ -806,15 +813,15 @@ public class ContentProviderUtils { } //SensorData - SensorDataSet sensorDataSet = trackPoint.getSensorDataSet(); + TrackPointSensorDataSet sensorDataSet = trackPoint.getSensorDataSet(); if (sensorDataSet != null && sensorDataSet.hasHeartRate()) { - values.put(TrackPointsColumns.SENSOR_HEARTRATE, trackPoint.getSensorDataSet().getHeartRate()); + values.put(TrackPointsColumns.SENSOR_HEARTRATE, sensorDataSet.getHeartRate_bpm()); } - if (sensorDataSet != null && sensorDataSet.hasCadence()) { - values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.getSensorDataSet().getCadence()); + if (sensorDataSet != null && sensorDataSet.hasCyclingCadence()) { + values.put(TrackPointsColumns.SENSOR_CADENCE, sensorDataSet.getCyclingCadence_rpm()); } if (sensorDataSet != null && sensorDataSet.hasPower()) { - values.put(TrackPointsColumns.SENSOR_POWER, trackPoint.getSensorDataSet().getPower()); + values.put(TrackPointsColumns.SENSOR_POWER, sensorDataSet.getPower()); } return values; } diff --git a/src/main/java/de/dennisguse/opentracks/content/sensor/SensorData.java b/src/main/java/de/dennisguse/opentracks/content/sensor/SensorData.java new file mode 100644 index 000000000..b073df96e --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/content/sensor/SensorData.java @@ -0,0 +1,39 @@ +package de.dennisguse.opentracks.content.sensor; + +import androidx.annotation.VisibleForTesting; + +import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager; + +public class SensorData { + + private String sensorAddress; + private String sensorName; + + private long timestamp_ms; + + SensorData(String sensorAddress, String sensorName) { + this(sensorAddress, sensorName, System.currentTimeMillis()); + } + + @VisibleForTesting + SensorData(String sensorAddress, String sensorName, long timestamp_ms) { + this.sensorAddress = sensorAddress; + this.sensorName = sensorName; + this.timestamp_ms = timestamp_ms; + } + + public String getSensorAddress() { + return sensorAddress; + } + + public String getSensorName() { + return sensorName; + } + + /** + * Is the data recent considering the current time. + */ + public boolean isRecent() { + return timestamp_ms + BluetoothRemoteSensorManager.MAX_SENSOR_DATE_SET_AGE_MS > System.currentTimeMillis(); + } +} diff --git a/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataCycling.java b/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataCycling.java new file mode 100644 index 000000000..74dd70dd9 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataCycling.java @@ -0,0 +1,184 @@ +package de.dennisguse.opentracks.content.sensor; + +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import de.dennisguse.opentracks.util.UintUtils; +import de.dennisguse.opentracks.util.UnitConversions; + +/** + * Provides cadence in rpm and speed in milliseconds from Bluetooth LE Cycling Cadence and Speed sensors. + *
+ * https://www.bluetooth.org/docman/handlers/downloaddoc.ashx?doc_id=261449
+ */
+public final class SensorDataCycling {
+
+ private static final String TAG = SensorDataCycling.class.getSimpleName();
+
+ private static final int INVALID_VALUE_INT = -1;
+ private static final float INVALID_VALUE_FLOAT = Float.NaN;
+
+ private SensorDataCycling() {
+ }
+
+ public static class Cadence extends SensorData {
+
+ private long crankRevolutionsCount; // UINT32
+ private int crankRevolutionsTime; // UINT16; 1/1024s
+ private float cadence_rpm = INVALID_VALUE_FLOAT;
+
+ public Cadence(String sensorAddress, String sensorName, long crankRevolutionsCount, int crankRevolutionsTime) {
+ super(sensorAddress, sensorName);
+ this.crankRevolutionsCount = crankRevolutionsCount;
+ this.crankRevolutionsTime = crankRevolutionsTime;
+ }
+
+ /**
+ * Workaround for Wahoo CADENCE: provides speed instead of cadence
+ */
+ public Cadence(@NonNull SensorDataCycling.Speed speed) {
+ this(speed.getSensorAddress(), speed.getSensorName(), speed.getWheelRevolutionsCount(), speed.getWheelRevolutionsTime());
+ }
+
+ @VisibleForTesting
+ public Cadence(long crankRevolutionsCount, int crankRevolutionsTime) {
+ super("sensorAddress", "sensorName");
+ this.crankRevolutionsCount = crankRevolutionsCount;
+ this.crankRevolutionsTime = crankRevolutionsTime;
+ }
+
+ public boolean hasData() {
+ return crankRevolutionsCount != INVALID_VALUE_INT && crankRevolutionsTime != INVALID_VALUE_INT;
+ }
+
+ public long getCrankRevolutionsCount() {
+ return crankRevolutionsCount;
+ }
+
+ public int getCrankRevolutionsTime() {
+ return crankRevolutionsTime;
+ }
+
+ public boolean hasCadence_rpm() {
+ return !Float.isNaN(cadence_rpm);
+ }
+
+ public float getCadence_rpm() {
+ return cadence_rpm;
+ }
+
+ public void compute(Cadence previous) {
+ if (hasData() && previous != null && previous.hasData()) {
+ Log.e(TAG, previous.getCrankRevolutionsTime() + " " + previous.getCrankRevolutionsCount() + " - " + this.getCrankRevolutionsTime() + " " + getCrankRevolutionsCount()); //TODO REMOVE
+ long timeDiff_ms = UintUtils.diff(crankRevolutionsTime, previous.crankRevolutionsTime, UintUtils.UINT16_MAX) * 1024 / UnitConversions.S_TO_MS;
+ if (timeDiff_ms <= 0) {
+ Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
+ cadence_rpm = INVALID_VALUE_FLOAT;
+ } else {
+ long crankDiff = UintUtils.diff(crankRevolutionsCount, previous.crankRevolutionsCount, UintUtils.UINT32_MAX);
+ float cadence_ms = crankDiff / (float) timeDiff_ms;
+ cadence_rpm = (float) (cadence_ms / UnitConversions.MS_TO_S / UnitConversions.S_TO_MIN);
+ }
+ }
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "cadence=" + getCadence_rpm() + "_" + getCrankRevolutionsTime();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof Cadence)) return false;
+
+ Cadence comp = (Cadence) obj;
+ return getCrankRevolutionsCount() == comp.getCrankRevolutionsCount() && getCrankRevolutionsTime() == comp.getCrankRevolutionsTime();
+ }
+ }
+
+ //TODO Speed computation; needs wheel diameter / circumference
+ public static class Speed extends SensorData {
+
+ private int wheelRevolutionsCount; // UINT16
+ private int wheelRevolutionsTime; // UINT16; 1/1024s
+ private float speed_ms = INVALID_VALUE_FLOAT;
+
+ public Speed(String sensorAddress, String sensorName, int wheelRevolutionsCount, int wheelRevolutionsTime) {
+ super(sensorAddress, sensorName);
+ this.wheelRevolutionsCount = wheelRevolutionsCount;
+ this.wheelRevolutionsTime = wheelRevolutionsTime;
+ }
+
+ public boolean hasData() {
+ return wheelRevolutionsCount != INVALID_VALUE_INT && wheelRevolutionsTime != INVALID_VALUE_INT;
+ }
+
+ public int getWheelRevolutionsCount() {
+ return wheelRevolutionsCount;
+ }
+
+ public int getWheelRevolutionsTime() {
+ return wheelRevolutionsTime;
+ }
+
+ public boolean hasSpeed() {
+ return !Float.isNaN(speed_ms);
+ }
+
+ public float getSpeed_ms() {
+ return speed_ms;
+ }
+
+ public void compute(Speed previous) {
+ if (hasData() && previous != null && previous.hasData()) {
+ long timeDiff_ms = UintUtils.diff(wheelRevolutionsTime, previous.wheelRevolutionsTime, UintUtils.UINT16_MAX) * 1024 / UnitConversions.S_TO_MS;
+ if (timeDiff_ms <= 0) {
+ Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
+ speed_ms = INVALID_VALUE_FLOAT;
+ } else {
+ long crankDiff = UintUtils.diff(wheelRevolutionsCount, previous.wheelRevolutionsCount, UintUtils.UINT32_MAX);
+ speed_ms = crankDiff / (float) timeDiff_ms;
+ }
+ }
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "speed=" + getSpeed_ms() + "_" + getWheelRevolutionsTime();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof Speed)) return false;
+
+ Speed comp = (Speed) obj;
+ return getWheelRevolutionsCount() == comp.getWheelRevolutionsCount() && getWheelRevolutionsTime() == comp.getWheelRevolutionsTime();
+ }
+ }
+
+ public static class CadenceAndSpeed extends SensorData {
+
+ private Cadence cadence;
+ private Speed speed;
+
+ public CadenceAndSpeed(String sensorAddress, String sensorName, @NonNull Cadence cadence, @NonNull Speed speed) {
+ super(sensorAddress, sensorName);
+ this.cadence = cadence;
+ this.speed = speed;
+ }
+
+ public Cadence getCadence() {
+ return cadence;
+ }
+
+ public Speed getSpeed() {
+ return speed;
+ }
+ }
+}
+
diff --git a/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataHeartRate.java b/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataHeartRate.java
new file mode 100644
index 000000000..c42c13f39
--- /dev/null
+++ b/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataHeartRate.java
@@ -0,0 +1,27 @@
+package de.dennisguse.opentracks.content.sensor;
+
+import androidx.annotation.NonNull;
+
+public class SensorDataHeartRate extends SensorData {
+
+ private float heartRate_bpm;
+
+ public SensorDataHeartRate(String name, String address, float heartRate_bpm) {
+ super(name, address);
+ this.heartRate_bpm = heartRate_bpm;
+ }
+
+ public boolean hasHeartRate_bpm() {
+ return !Float.isNaN(heartRate_bpm);
+ }
+
+ public float getHeartRate_bpm() {
+ return heartRate_bpm;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "heart=" + heartRate_bpm;
+ }
+}
diff --git a/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataSet.java b/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataSet.java
index 60b1677ca..2ff83b011 100644
--- a/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataSet.java
+++ b/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataSet.java
@@ -1,102 +1,97 @@
package de.dennisguse.opentracks.content.sensor;
import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+import de.dennisguse.opentracks.content.data.TrackPointSensorDataSet;
+
+/**
+ *
+ */
public final class SensorDataSet {
- public static final float DATA_UNAVAILABLE = Float.NaN;
+ private SensorDataHeartRate heartRate;
- //TODO It might be necessary to consider: if sensor was connected as well.
- private String sensorName;
- private String sensorAddress;
- private float heartRate;
- private float cadence;
- private float power;
- private float batteryLevel;
- private long time;
+ private SensorDataCycling.Cadence cyclingCadence;
- public SensorDataSet(float heartRate, float cadence, float power, float batteryLevel, long time) {
+ private SensorDataCycling.Speed cyclingSpeed;
+
+ public SensorDataSet() {
+ }
+
+ @VisibleForTesting
+ public SensorDataSet(SensorDataHeartRate heartRate, SensorDataCycling.Cadence cyclingCadence, SensorDataCycling.Speed cyclingSpeed) {
this.heartRate = heartRate;
- this.cadence = cadence;
- this.power = power;
- this.batteryLevel = batteryLevel;
- this.time = time;
+ this.cyclingCadence = cyclingCadence;
+ this.cyclingSpeed = cyclingSpeed;
}
- public SensorDataSet(float heartRate, float cadence, float power, float batteryLevel) {
- this(heartRate, cadence, power, batteryLevel, System.currentTimeMillis());
- }
-
- public SensorDataSet(float heartRate, float cadence, float power) {
- this(heartRate, cadence, power, DATA_UNAVAILABLE, System.currentTimeMillis());
- }
-
- public SensorDataSet(float heartRate, float cadence) {
- this(heartRate, cadence, DATA_UNAVAILABLE, DATA_UNAVAILABLE, System.currentTimeMillis());
- }
-
- public SensorDataSet(float heartRate, String sensorName, String sensorAddress) {
- this(heartRate, DATA_UNAVAILABLE, DATA_UNAVAILABLE, DATA_UNAVAILABLE, System.currentTimeMillis());
- this.sensorName = sensorName;
- this.sensorAddress = sensorAddress;
- }
-
- public boolean hasHeartRate() {
- return !Float.isNaN(heartRate) && heartRate > 0;
- }
-
- public float getHeartRate() {
+ public SensorDataHeartRate getHeartRate() {
return heartRate;
}
- public boolean hasCadence() {
- return !Float.isNaN(cadence);
+ public SensorDataCycling.Cadence getCyclingCadence() {
+ return cyclingCadence;
}
- public float getCadence() {
- return cadence;
+ public SensorDataCycling.Speed getCyclingSpeed() {
+ return cyclingSpeed;
}
- public boolean hasPower() {
- return !Float.isNaN(power);
+ public void set(SensorData data) {
+ if (data == null) {
+ return;
+ }
+
+ if (data instanceof SensorDataHeartRate) {
+ this.heartRate = (SensorDataHeartRate) data;
+ return;
+ }
+
+ if (data instanceof SensorDataCycling.Cadence) {
+ this.cyclingCadence = (SensorDataCycling.Cadence) data;
+ return;
+ }
+ if (data instanceof SensorDataCycling.Speed) {
+ this.cyclingSpeed = (SensorDataCycling.Speed) data;
+ return;
+ }
+ if (data instanceof SensorDataCycling.CadenceAndSpeed) {
+ set(((SensorDataCycling.CadenceAndSpeed) data).getCadence());
+ set(((SensorDataCycling.CadenceAndSpeed) data).getSpeed());
+ }
+
+ throw new UnsupportedOperationException();
}
- public float getPower() {
- return power;
+ public void clear() {
+ this.heartRate = null;
+ this.cyclingCadence = null;
+ this.cyclingSpeed = null;
}
- public long getTime() {
- return time;
- }
+ public TrackPointSensorDataSet createTrackPointSensorDataSet() {
+ TrackPointSensorDataSet sensorDataSet = new TrackPointSensorDataSet();
+ if (heartRate != null) {
+ sensorDataSet.setHeartRate_bpm(heartRate.getHeartRate_bpm());
+ }
- /**
- * Is the data recent considering the current time.
- *
- * @param maxAge the maximal age in milliseconds.
- */
- public boolean isRecent(long maxAge) {
- return time + maxAge > System.currentTimeMillis();
- }
+ if (cyclingCadence != null && cyclingCadence.hasCadence_rpm()) {
+ sensorDataSet.setCyclingCadence(cyclingCadence.getCadence_rpm());
+ }
- public boolean hasBatteryLevel() {
- return !Float.isNaN(batteryLevel);
- }
+ if (cyclingSpeed != null && cyclingSpeed.hasSpeed()) {
+ sensorDataSet.setCyclingCadence(cyclingSpeed.getSpeed_ms());
+ }
- public float getBatteryLevel() {
- return batteryLevel;
- }
-
- public String getSensorName() {
- return sensorName;
- }
-
- public String getSensorAddress() {
- return sensorAddress;
+ return sensorDataSet;
}
@NonNull
@Override
public String toString() {
- return "time=" + getTime() + " sensor=" + getSensorAddress() + " heart=" + getHeartRate();
+ return (getHeartRate() != null ? "" + getHeartRate() : "")
+ + (getCyclingCadence() != null ? " " + getCyclingCadence() : "")
+ + (getCyclingSpeed() != null ? " " + getCyclingSpeed() : "");
}
}
diff --git a/src/main/java/de/dennisguse/opentracks/content/sensor/SensorState.java b/src/main/java/de/dennisguse/opentracks/content/sensor/SensorState.java
deleted file mode 100644
index 25da972e1..000000000
--- a/src/main/java/de/dennisguse/opentracks/content/sensor/SensorState.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package de.dennisguse.opentracks.content.sensor;
-
-import android.content.Context;
-
-import de.dennisguse.opentracks.R;
-
-public enum SensorState {
- NONE,
- CONNECTING,
- CONNECTED,
- DISCONNECTING,
- DISCONNECTED;
-
- public static String getStateAsString(SensorState state, Context c) {
- switch (state) {
- case NONE:
- return c.getString(R.string.value_none);
- case CONNECTING:
- return c.getString(R.string.sensor_state_connecting);
- case CONNECTED:
- return c.getString(R.string.sensor_state_connected);
- case DISCONNECTING:
- case DISCONNECTED:
- return c.getString(R.string.sensor_state_disconnected);
- default:
- return "";
- }
- }
-}
diff --git a/src/main/java/de/dennisguse/opentracks/fragments/StatsFragment.java b/src/main/java/de/dennisguse/opentracks/fragments/StatsFragment.java
index af45112d8..6696bd4c5 100644
--- a/src/main/java/de/dennisguse/opentracks/fragments/StatsFragment.java
+++ b/src/main/java/de/dennisguse/opentracks/fragments/StatsFragment.java
@@ -39,11 +39,11 @@ import de.dennisguse.opentracks.content.TrackDataHub;
import de.dennisguse.opentracks.content.TrackDataListener;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
+import de.dennisguse.opentracks.content.data.TrackPointSensorDataSet;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
-import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
@@ -114,9 +114,12 @@ public class StatsFragment extends Fragment implements TrackDataListener {
}
/* Views */
- private View sensorContainerView;
+ private View heartRateContainer;
private TextView heartRateValueView;
private TextView heartRateSensorView;
+ private View cadenceContainer;
+ private TextView cadenceValueView;
+ private TextView cadenceSensorView;
private TextView totalTimeValueView;
private final Runnable updateUIeachSecond = new Runnable() {
@@ -162,10 +165,14 @@ public class StatsFragment extends Fragment implements TrackDataListener {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
- sensorContainerView = view.findViewById(R.id.stats_sensor_container);
+ heartRateContainer = view.findViewById(R.id.stats_sensor_heart_rate_container);
heartRateValueView = view.findViewById(R.id.stats_sensor_heart_rate_value);
heartRateSensorView = view.findViewById(R.id.stats_sensor_heart_rate_sensor_value);
+ cadenceContainer = view.findViewById(R.id.stats_sensor_cadence_container);
+ cadenceValueView = view.findViewById(R.id.stats_sensor_cadence_value);
+ cadenceSensorView = view.findViewById(R.id.stats_sensor_cadence_sensor_value);
+
totalTimeValueView = view.findViewById(R.id.stats_total_time_value);
distanceValue = view.findViewById(R.id.stats_distance_value);
@@ -269,9 +276,12 @@ public class StatsFragment extends Fragment implements TrackDataListener {
@Override
public void onDestroyView() {
super.onDestroyView();
- sensorContainerView = null;
+ heartRateContainer = null;
heartRateValueView = null;
heartRateSensorView = null;
+ cadenceContainer = null;
+ cadenceValueView = null;
+ cadenceSensorView = null;
totalTimeValueView = null;
@@ -426,7 +436,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
}
/**
- * Tries to fetch most recent {@link SensorDataSet} from {@link de.dennisguse.opentracks.services.TrackRecordingService}.
+ * Tries to fetch most recent {@link TrackPointSensorDataSet} from {@link de.dennisguse.opentracks.services.TrackRecordingService}.
*/
private void updateSensorDataUI() {
TrackRecordingServiceInterface trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
@@ -435,33 +445,56 @@ public class StatsFragment extends Fragment implements TrackDataListener {
if (trackRecordingService == null) {
Log.d(STATS_FRAGMENT_TAG, "Cannot get the track recording service.");
} else {
- //TODO sensorState = trackRecordingService.getSensorState();
sensorDataSet = trackRecordingService.getSensorData();
}
setHeartRateSensorData(sensorDataSet, isSelectedTrackRecording());
+ setCadenceSensorData(sensorDataSet, isSelectedTrackRecording());
}
private void setHeartRateSensorData(SensorDataSet sensorDataSet, boolean isRecording) {
// heart rate
int isVisible = View.VISIBLE;
- if (!isRecording || PreferencesUtils.isBluetoothHeartRateSensorAddressDefault(getContext())) {
+ if (!isRecording || PreferencesUtils.isBluetoothHeartRateSensorAddressNone(getContext())) {
isVisible = View.INVISIBLE;
}
- sensorContainerView.setVisibility(isVisible);
+ heartRateContainer.setVisibility(isVisible);
if (isRecording) {
- String heartRate = getContext().getString(R.string.value_unknown);
+ String sensorValue = getContext().getString(R.string.value_unknown);
String sensorName = getContext().getString(R.string.value_unknown);
- if (sensorDataSet != null && sensorDataSet.isRecent(BluetoothRemoteSensorManager.MAX_SENSOR_DATE_SET_AGE_MS)) {
- sensorName = sensorDataSet.getSensorName();
- if (sensorDataSet.hasHeartRate()) {
- heartRate = StringUtils.formatDecimal(sensorDataSet.getHeartRate(), 0);
+ if (sensorDataSet != null && sensorDataSet.getHeartRate() != null) {
+ sensorName = sensorDataSet.getHeartRate().getSensorName();
+ if (sensorDataSet.getHeartRate().hasHeartRate_bpm() && sensorDataSet.getHeartRate().isRecent()) {
+ sensorValue = StringUtils.formatDecimal(sensorDataSet.getHeartRate().getHeartRate_bpm(), 0);
}
}
heartRateSensorView.setText(sensorName);
- heartRateValueView.setText(heartRate);
+ heartRateValueView.setText(sensorValue);
+ }
+ }
+
+ private void setCadenceSensorData(SensorDataSet sensorDataSet, boolean isRecording) {
+ // heart rate
+ int isVisible = View.VISIBLE;
+ if (!isRecording || PreferencesUtils.isBluetoothCyclingCadenceSensorAddressNone(getContext())) {
+ isVisible = View.INVISIBLE;
+ }
+ cadenceContainer.setVisibility(isVisible);
+
+ if (isRecording) {
+ String sensorValue = getContext().getString(R.string.value_unknown);
+ String sensorName = getContext().getString(R.string.value_unknown);
+ if (sensorDataSet != null && sensorDataSet.getCyclingCadence() != null) {
+ sensorName = sensorDataSet.getCyclingCadence().getSensorName();
+ if (sensorDataSet.getCyclingCadence().hasCadence_rpm() && sensorDataSet.getCyclingCadence().isRecent()) {
+ sensorValue = StringUtils.formatDecimal(sensorDataSet.getCyclingCadence().getCadence_rpm(), 0);
+ }
+ }
+
+ cadenceSensorView.setText(sensorName);
+ cadenceValueView.setText(sensorValue);
}
}
diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackWriter.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackWriter.java
index 4ed5314f7..8b2e37bca 100644
--- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackWriter.java
+++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackWriter.java
@@ -18,7 +18,6 @@ package de.dennisguse.opentracks.io.file.exporter;
import android.content.Context;
import android.database.Cursor;
import android.location.Location;
-import android.net.Uri;
import java.io.OutputStream;
import java.io.PrintWriter;
@@ -29,9 +28,9 @@ import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.DescriptionGenerator;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
+import de.dennisguse.opentracks.content.data.TrackPointSensorDataSet;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
-import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.StringUtils;
@@ -73,11 +72,11 @@ public class KmlTrackWriter implements TrackWriter {
private TrackPoint startTrackPoint;
/**
- * @param context the context
- * @param hasMultipleTracks should encode multiple tracks into one file?
- * @param exportTrackDetail should detailed information about the track be exported (e.g., title, description, waypoints, timing)?
- * @param exportSensorData should {@link SensorDataSet} be exported?
- * @param exportPhotos should pictures be exported (if true: exports to KMZ)?
+ * @param context the context
+ * @param hasMultipleTracks should encode multiple tracks into one file?
+ * @param exportTrackDetail should detailed information about the track be exported (e.g., title, description, waypoints, timing)?
+ * @param exportSensorData should {@link TrackPointSensorDataSet} be exported?
+ * @param exportPhotos should pictures be exported (if true: exports to KMZ)?
*/
public KmlTrackWriter(Context context, boolean hasMultipleTracks, boolean exportTrackDetail, boolean exportSensorData, boolean exportPhotos) {
this.context = context;
@@ -269,13 +268,13 @@ public class KmlTrackWriter implements TrackWriter {
printWriter.println("
* When recording a track, GPS locations are automatically inserted.
@@ -107,13 +112,6 @@ public interface TrackRecordingServiceInterface {
@VisibleForTesting
void insertLocation(Location location);
- /**
- * Gets the current sensor data. Returns null if there is no data.
- *
- * @return SensorDataSet object.
- */
- SensorDataSet getSensorData();
-
/**
* Disables processing of location updates from {@link android.location.LocationManager}.
*/
diff --git a/src/main/java/de/dennisguse/opentracks/services/sensors/BluetoothConnectionManager.java b/src/main/java/de/dennisguse/opentracks/services/sensors/BluetoothConnectionManager.java
index bd9b31b3a..fbd4c4240 100644
--- a/src/main/java/de/dennisguse/opentracks/services/sensors/BluetoothConnectionManager.java
+++ b/src/main/java/de/dennisguse/opentracks/services/sensors/BluetoothConnectionManager.java
@@ -30,27 +30,24 @@ import androidx.annotation.NonNull;
import java.util.UUID;
-import de.dennisguse.opentracks.content.sensor.SensorDataSet;
-import de.dennisguse.opentracks.content.sensor.SensorState;
+import de.dennisguse.opentracks.content.sensor.SensorData;
+import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
+import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate;
import de.dennisguse.opentracks.util.BluetoothUtils;
/**
- * Manages connection to Bluetooth LE heart rate monitor.
+ * Manages connection to a Bluetooth LE sensor and subscribes for onChange-notifications.
+ * Also parses the transferred data into {@link SensorDataObserver}.
*/
-public class BluetoothConnectionManager {
-
- private static final UUID HEART_RATE_MEASUREMENT_CHAR_UUID = new UUID(0x2A3700001000L, 0x800000805f9b34fbL);
- private static final UUID CLIENT_CHARACTERISTIC_CONFIG_UUID = new UUID(0x290200001000L, 0x800000805f9b34fbL);
+public abstract class BluetoothConnectionManager {
private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
- private final Context context;
- private final EventCallback observer;
-
- private SensorState sensorState;
+ private final SensorDataObserver observer;
+ private final UUID serviceUUUID;
+ private final UUID measurementUUID;
private BluetoothGatt bluetoothGatt;
- private final BluetoothDevice bluetoothDevice;
private final BluetoothGattCallback connectCallback = new BluetoothGattCallback() {
@Override
@@ -58,80 +55,70 @@ public class BluetoothConnectionManager {
switch (newState) {
case BluetoothProfile.STATE_CONNECTING:
Log.d(TAG, "Connecting to sensor: " + gatt.getDevice());
- setState(SensorState.CONNECTING);
-
- observer.connecting(gatt.getDevice().getName());
case BluetoothProfile.STATE_CONNECTED:
Log.d(TAG, "Connected to sensor: " + gatt.getDevice());
- setState(SensorState.CONNECTED);
gatt.discoverServices();
-
- observer.connected(gatt.getDevice().getName());
break;
case BluetoothProfile.STATE_DISCONNECTING:
Log.d(TAG, "Disconnecting from sensor: " + gatt.getDevice());
- setState(SensorState.DISCONNECTING);
case BluetoothProfile.STATE_DISCONNECTED:
Log.d(TAG, "Disconnected from sensor: " + gatt.getDevice());
- setState(SensorState.DISCONNECTED);
-
- observer.disconnected(gatt.getDevice().getName());
}
}
@Override
public void onServicesDiscovered(@NonNull BluetoothGatt gatt, int status) {
- BluetoothGattService service = gatt.getService(BluetoothUtils.HEART_RATE_SERVICE_UUID);
+ BluetoothGattService service = gatt.getService(serviceUUUID);
if (service == null) {
- Log.e(TAG, "Could not get heart rate service for " + gatt.getDevice().getAddress());
+ Log.e(TAG, "Could not get service for address=" + gatt.getDevice().getAddress() + " serviceUUID=" + serviceUUUID);
return;
}
-
- BluetoothGattCharacteristic characteristic = service.getCharacteristic(HEART_RATE_MEASUREMENT_CHAR_UUID);
+ BluetoothGattCharacteristic characteristic = service.getCharacteristic(measurementUUID);
if (characteristic == null) {
- Log.e(TAG, "Could not get BluetoothCharacteristic for " + gatt.getDevice().getAddress());
+ Log.e(TAG, "Could not get BluetoothCharacteristic for address=" + gatt.getDevice().getAddress() + " serviceUUID=" + serviceUUUID + " characteristicUUID=" + measurementUUID);
return;
}
gatt.setCharacteristicNotification(characteristic, true);
- BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID);
+ // Register for updates.
+ BluetoothGattDescriptor descriptor = characteristic.getDescriptor(BluetoothUtils.CLIENT_CHARACTERISTIC_CONFIG_UUID);
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
gatt.writeDescriptor(descriptor);
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic) {
- int heartRate = BluetoothUtils.parseHeartRate(characteristic);
- String deviceName = gatt.getDevice().getName();
+ String sensorName = gatt.getDevice().getName();
+ Log.d(TAG, "Received data from " + sensorName);
- Log.d(TAG, "Received heart beat rate " + deviceName + ": " + heartRate);
- observer.onSensorDataReceived(new SensorDataSet(heartRate, deviceName, gatt.getDevice().getAddress()));
+ SensorData sensorData = parsePayload(sensorName, gatt.getDevice().getAddress(), characteristic);
+ if (sensorData != null) {
+ observer.onChanged(sensorData);
+ }
}
};
- BluetoothConnectionManager(@NonNull Context context, @NonNull BluetoothDevice bluetoothDevice, EventCallback observer) {
- this.context = context;
- this.bluetoothDevice = bluetoothDevice;
- this.sensorState = SensorState.NONE;
+ BluetoothConnectionManager(UUID serviceUUUID, UUID measurementUUID, SensorDataObserver observer) {
+ this.serviceUUUID = serviceUUUID;
+ this.measurementUUID = measurementUUID;
this.observer = observer;
}
- public synchronized void connect() {
+ synchronized void connect(Context context, @NonNull BluetoothDevice device) {
if (bluetoothGatt != null) {
Log.w(TAG, "Already connected; ignoring.");
}
- Log.d(TAG, "Connecting to: " + bluetoothDevice);
+ Log.d(TAG, "Connecting to: " + device);
- bluetoothGatt = bluetoothDevice.connectGatt(this.context, true, this.connectCallback);
+ bluetoothGatt = device.connectGatt(context, true, this.connectCallback);
- setState(SensorState.CONNECTING);
}
- public synchronized void disconnect() {
+ synchronized void disconnect() {
if (bluetoothGatt == null) {
Log.w(TAG, "Cannot disconnect if not connected.");
return;
@@ -140,27 +127,78 @@ public class BluetoothConnectionManager {
bluetoothGatt = null;
}
- public synchronized boolean isSameBluetoothDevice(String address) {
- return this.bluetoothDevice.getAddress().equals(address);
+ synchronized boolean isSameBluetoothDevice(String address) {
+ if (bluetoothGatt == null) {
+ return false;
+ }
+
+ return address.equals(bluetoothGatt.getDevice().getAddress());
}
- synchronized SensorState getSensorState() {
- return sensorState;
+ /**
+ * @return null if data could not be parsed.
+ */
+ protected abstract de.dennisguse.opentracks.content.sensor.SensorData parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic);
+
+ public static class HeartRate extends BluetoothConnectionManager {
+
+ HeartRate(@NonNull SensorDataObserver observer) {
+ super(BluetoothUtils.HEART_RATE_SERVICE_UUID, BluetoothUtils.HEART_RATE_MEASUREMENT_CHAR_UUID, observer);
+ }
+
+ @Override
+ protected SensorDataHeartRate parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) {
+ Integer heartRate = BluetoothUtils.parseHeartRate(characteristic);
+
+ return heartRate != null ? new SensorDataHeartRate(address, sensorName, heartRate) : null;
+ }
}
- private synchronized void setState(SensorState sensorState) {
- this.sensorState = sensorState;
+ public static class CyclingCadence extends BluetoothConnectionManager {
+
+ CyclingCadence(SensorDataObserver observer) {
+ super(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, BluetoothUtils.CYCLING_SPPED_CADENCE_MEASUREMENT_CHAR_UUID, observer);
+ }
+
+ @Override
+ protected SensorDataCycling.Cadence parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) {
+ SensorDataCycling.CadenceAndSpeed cadenceAndSpeed = BluetoothUtils.parseCyclingCrankAndWheel(address, sensorName, characteristic);
+ if (cadenceAndSpeed == null) {
+ return null;
+ }
+
+ if (cadenceAndSpeed.getCadence() != null) {
+ return cadenceAndSpeed.getCadence();
+ }
+
+ //Workaround for Wahoo CADENCE: this sensor reports speed (instead of cadence)
+ if (cadenceAndSpeed.getSpeed() != null) {
+ return new SensorDataCycling.Cadence(cadenceAndSpeed.getSpeed());
+ }
+
+ return null;
+ }
}
+ public static class CyclingSpeed extends BluetoothConnectionManager {
- interface EventCallback {
+ CyclingSpeed(SensorDataObserver observer) {
+ super(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, BluetoothUtils.CYCLING_SPPED_CADENCE_MEASUREMENT_CHAR_UUID, observer);
+ }
- void connecting(String sensorName);
+ @Override
+ protected SensorDataCycling.Speed parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) {
+ SensorDataCycling.CadenceAndSpeed cadenceAndSpeed = BluetoothUtils.parseCyclingCrankAndWheel(address, sensorName, characteristic);
+ if (cadenceAndSpeed != null) {
+ return cadenceAndSpeed.getSpeed();
+ }
+ return null;
+ }
+ }
- void connected(String sensorName);
+ interface SensorDataObserver {
- void onSensorDataReceived(SensorDataSet sensorDataSet);
+ void onChanged(SensorData sensorData);
- void disconnected(String sensorName);
}
}
diff --git a/src/main/java/de/dennisguse/opentracks/services/sensors/BluetoothRemoteSensorManager.java b/src/main/java/de/dennisguse/opentracks/services/sensors/BluetoothRemoteSensorManager.java
index 702cddda6..74f06457d 100644
--- a/src/main/java/de/dennisguse/opentracks/services/sensors/BluetoothRemoteSensorManager.java
+++ b/src/main/java/de/dennisguse/opentracks/services/sensors/BluetoothRemoteSensorManager.java
@@ -21,53 +21,72 @@ import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
-import android.widget.Toast;
import de.dennisguse.opentracks.R;
+import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.util.BluetoothUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
+import de.dennisguse.opentracks.util.UnitConversions;
/**
- * Bluetooth LE sensor manager.
- * Should only be instantiated once!
+ * Bluetooth LE sensor manager: manages connections to Bluetooth LE sensors.
+ *
+ * Note: should only be instantiated once.
+ *
+ * TODO: listen for Bluetooth enabled/disabled events.
+ *
+ * TODO: In case, a cycling (Cadence and Speed) sensor reports both values, testing is required.
+ * We establish two GATT separate GATT connections (as if two different sensors were used).
+ * However, it is not clear if this is allowed.
+ * Even if this works, it is not clear what happens if a user (while recording) changes one of the sensors in the settings as this will trigger a disconnect of one GATT.
*
* @author Sandor Dornbush
*/
-public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.EventCallback {
+public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.SensorDataObserver {
- public static final long MAX_SENSOR_DATE_SET_AGE_MS = 5000;
+ private static final String TAG = BluetoothRemoteSensorManager.class.getSimpleName();
- private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
+ public static final long MAX_SENSOR_DATE_SET_AGE_MS = 5 * UnitConversions.S_TO_MS;
private static final BluetoothAdapter bluetoothAdapter = BluetoothUtils.getDefaultBluetoothAdapter(TAG);
private final Context context;
-
private final SharedPreferences sharedPreferences;
+ private boolean started = false;
+
+ private final BluetoothConnectionManager heartRate = new BluetoothConnectionManager.HeartRate(this);
+ private final BluetoothConnectionManager cyclingCadence = new BluetoothConnectionManager.CyclingCadence(this);
+
+ private final SensorDataSet sensorDataSet = new SensorDataSet();
+
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
- if (bluetoothConnectionManager != null && PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_heart_rate_key, key)) {
- if (PreferencesUtils.isBluetoothHeartRateSensorAddressDefault(context)) {
+ if (!started) return;
+
+ if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_heart_rate_key, key)) {
+ if (PreferencesUtils.isBluetoothHeartRateSensorAddressNone(context)) {
stop();
return;
}
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress(context);
- if (bluetoothConnectionManager.isSameBluetoothDevice(address)) {
+ connect(heartRate, address);
+ }
+
+ if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_cadence_key, key)) {
+ if (PreferencesUtils.isBluetoothCyclingCadenceSensorAddressNone(context)) {
+ stop();
return;
}
+ String address = PreferencesUtils.getBluetoothCyclingCadenceSensorAddress(context);
- disconnect();
- startCurrentSensor();
+ connect(cyclingCadence, address);
}
}
};
- private SensorDataSet sensorDataSet = null;
- private BluetoothConnectionManager bluetoothConnectionManager;
-
/**
* @param context the context
*/
@@ -77,84 +96,76 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
}
public void start() {
+ started = true;
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
- startCurrentSensor();
+
+ //Trigger connection startup
+ sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
}
- public void stop() {
- disconnect();
+ public synchronized void stop() {
+ // Disconnecting
+ heartRate.disconnect();
+ cyclingCadence.disconnect();
+
+ sensorDataSet.clear();
+
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
+ started = false;
}
-
public boolean isEnabled() {
return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
}
- public SensorDataSet getSensorDataSet() {
- return sensorDataSet;
- }
-
- public boolean isSensorDataSetValid() {
- SensorDataSet sensorDataSet = getSensorDataSet();
- if (sensorDataSet == null) {
- return false;
- }
- return sensorDataSet.isRecent(MAX_SENSOR_DATE_SET_AGE_MS);
- }
-
- private void startCurrentSensor() {
+ private synchronized void connect(BluetoothConnectionManager connectionManager, String address) {
if (!isEnabled()) {
Log.w(TAG, "Bluetooth not enabled.");
return;
}
- if (PreferencesUtils.isBluetoothHeartRateSensorAddressDefault(context)) {
- Log.w(TAG, "No bluetooth address.");
+ if (PreferencesUtils.isBluetoothSensorAddressNone(context, address)) {
+ Log.w(TAG, "No Bluetooth address.");
return;
}
- String address = PreferencesUtils.getBluetoothHeartRateSensorAddress(context);
+
+ // Check if there is an ongoing connection; if yes, check if the address changed.
+ if (connectionManager.isSameBluetoothDevice(address)) {
+ return;
+ } else {
+ connectionManager.disconnect();
+ }
+
Log.i(TAG, "Connecting to bluetooth address: " + address);
-
- BluetoothDevice device;
try {
- device = bluetoothAdapter.getRemoteDevice(address);
+ BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address);
+ connectionManager.connect(context, device);
} catch (IllegalArgumentException e) {
- Log.w(TAG, "Unable to get remote device for: " + address, e);
-
- String toastMessage = context.getString(R.string.sensor_not_known, address);
- Toast.makeText(context, toastMessage, Toast.LENGTH_LONG).show();
-
- return;
- }
-
- disconnect();
-
- bluetoothConnectionManager = new BluetoothConnectionManager(context, device, this);
- bluetoothConnectionManager.connect();
- }
-
- private void disconnect() {
- if (bluetoothConnectionManager != null) {
- bluetoothConnectionManager.disconnect();
- bluetoothConnectionManager = null;
+ Log.e(TAG, "Unable to get remote device for: " + address, e);
}
}
- @Override
- public void connecting(String sensorName) {
+ public SensorDataSet getSensorData() {
+ return sensorDataSet;
}
@Override
- public void connected(String sensorName) {
- }
+ public synchronized void onChanged(de.dennisguse.opentracks.content.sensor.SensorData sensorData) {
+ if (sensorData instanceof SensorDataCycling.Cadence) {
+ if (sensorData.equals(sensorDataSet.getCyclingCadence())) {
+ Log.d(TAG, "onChanged: cadence data repeated.");
+ return;
+ }
+ ((SensorDataCycling.Cadence) sensorData).compute(sensorDataSet.getCyclingCadence());
+ }
+ if (sensorData instanceof SensorDataCycling.Speed) {
+ if (sensorData.equals(sensorDataSet.getCyclingSpeed())) {
+ Log.d(TAG, "onChanged: speed data repeated.");
+ return;
+ }
+ ((SensorDataCycling.Speed) sensorData).compute(sensorDataSet.getCyclingSpeed());
+ }
- @Override
- public void onSensorDataReceived(SensorDataSet sensorDataSet) {
- this.sensorDataSet = sensorDataSet;
- }
-
- @Override
- public void disconnected(String sensorName) {
+ sensorDataSet.set(sensorData);
}
}
diff --git a/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java b/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java
index e904e5f1c..5eccca161 100644
--- a/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java
+++ b/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java
@@ -13,6 +13,9 @@ import androidx.preference.PreferenceFragmentCompat;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
+import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingCadenceAndSpeedPreference;
+import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeHeartRatePreference;
+import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeSensorPreference;
import de.dennisguse.opentracks.util.BluetoothUtils;
import de.dennisguse.opentracks.util.HackUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
@@ -111,8 +114,10 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
} else if (preference instanceof ActivityTypePreference) {
activityPreferenceDialog = ActivityTypePreference.ActivityPreferenceDialog.newInstance(preference.getKey());
dialogFragment = activityPreferenceDialog;
- } else if (preference instanceof BluetoothLePreference) {
- dialogFragment = BluetoothLePreference.BluetoothLePreferenceDialog.newInstance(preference.getKey());
+ } else if (preference instanceof BluetoothLeHeartRatePreference) {
+ dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.HEART_RATE_SERVICE_UUID);
+ } else if (preference instanceof BluetoothLeCyclingCadenceAndSpeedPreference) {
+ dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID);
}
if (dialogFragment != null) {
@@ -139,7 +144,7 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
private void updateBluetooth() {
// Disable Bluetooth preference if device does not have Bluetooth
- BluetoothLePreference bluetoothPreference = findPreference(getString(R.string.settings_sensor_bluetooth_heart_rate_key));
+ BluetoothLeSensorPreference bluetoothPreference = findPreference(getString(R.string.settings_sensor_bluetooth_heart_rate_key));
bluetoothPreference.setVisible(BluetoothUtils.hasBluetooth(TAG));
}
diff --git a/src/main/java/de/dennisguse/opentracks/settings/bluetooth/BluetoothLeCyclingCadenceAndSpeedPreference.java b/src/main/java/de/dennisguse/opentracks/settings/bluetooth/BluetoothLeCyclingCadenceAndSpeedPreference.java
new file mode 100644
index 000000000..aba91ba79
--- /dev/null
+++ b/src/main/java/de/dennisguse/opentracks/settings/bluetooth/BluetoothLeCyclingCadenceAndSpeedPreference.java
@@ -0,0 +1,23 @@
+package de.dennisguse.opentracks.settings.bluetooth;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+public class BluetoothLeCyclingCadenceAndSpeedPreference extends BluetoothLeSensorPreference {
+
+ public BluetoothLeCyclingCadenceAndSpeedPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public BluetoothLeCyclingCadenceAndSpeedPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public BluetoothLeCyclingCadenceAndSpeedPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public BluetoothLeCyclingCadenceAndSpeedPreference(Context context) {
+ super(context);
+ }
+}
diff --git a/src/main/java/de/dennisguse/opentracks/settings/bluetooth/BluetoothLeHeartRatePreference.java b/src/main/java/de/dennisguse/opentracks/settings/bluetooth/BluetoothLeHeartRatePreference.java
new file mode 100644
index 000000000..8021228a9
--- /dev/null
+++ b/src/main/java/de/dennisguse/opentracks/settings/bluetooth/BluetoothLeHeartRatePreference.java
@@ -0,0 +1,23 @@
+package de.dennisguse.opentracks.settings.bluetooth;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+public class BluetoothLeHeartRatePreference extends BluetoothLeSensorPreference {
+
+ public BluetoothLeHeartRatePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public BluetoothLeHeartRatePreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public BluetoothLeHeartRatePreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public BluetoothLeHeartRatePreference(Context context) {
+ super(context);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/de/dennisguse/opentracks/settings/BluetoothLePreference.java b/src/main/java/de/dennisguse/opentracks/settings/bluetooth/BluetoothLeSensorPreference.java
similarity index 78%
rename from src/main/java/de/dennisguse/opentracks/settings/BluetoothLePreference.java
rename to src/main/java/de/dennisguse/opentracks/settings/bluetooth/BluetoothLeSensorPreference.java
index 4f8468408..275faf4c5 100644
--- a/src/main/java/de/dennisguse/opentracks/settings/BluetoothLePreference.java
+++ b/src/main/java/de/dennisguse/opentracks/settings/bluetooth/BluetoothLeSensorPreference.java
@@ -1,4 +1,4 @@
-package de.dennisguse.opentracks.settings;
+package de.dennisguse.opentracks.settings.bluetooth;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.le.BluetoothLeScanner;
@@ -21,8 +21,10 @@ import androidx.preference.PreferenceDialogFragmentCompat;
import java.util.ArrayList;
import java.util.List;
+import java.util.UUID;
import de.dennisguse.opentracks.R;
+import de.dennisguse.opentracks.settings.BluetoothLeAdapter;
import de.dennisguse.opentracks.util.BluetoothUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
@@ -30,25 +32,27 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
* Preference to select a discoverable Bluetooth LE device.
* Based upon ListPreference.
*/
-public class BluetoothLePreference extends DialogPreference {
+public abstract class BluetoothLeSensorPreference extends DialogPreference {
- private static final String TAG = BluetoothLePreference.class.getSimpleName();
+ private static final String TAG = BluetoothLeSensorPreference.class.getSimpleName();
+
+ private static final String ARG_BLUETOOTH_UUID = "bluetoothUUID";
private static final int DEVICE_NONE_RESOURCEID = R.string.value_none;
- public BluetoothLePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ public BluetoothLeSensorPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
- public BluetoothLePreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ public BluetoothLeSensorPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
- public BluetoothLePreference(Context context, AttributeSet attrs) {
+ public BluetoothLeSensorPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
- public BluetoothLePreference(Context context) {
+ public BluetoothLeSensorPreference(Context context) {
super(context);
}
@@ -78,18 +82,20 @@ public class BluetoothLePreference extends DialogPreference {
@Override
public CharSequence getSummary() {
- if (getValue() == null || PreferencesUtils.isBluetoothHeartRateSensorAddressDefault(getContext(), getValue())) {
+ if (getValue() == null || PreferencesUtils.isBluetoothSensorAddressNone(getContext(), getValue())) {
return getContext().getString(DEVICE_NONE_RESOURCEID);
}
return getValue();
}
- public static class BluetoothLePreferenceDialog extends PreferenceDialogFragmentCompat {
+ public static class BluetoothLeSensorPreferenceDialog extends PreferenceDialogFragmentCompat {
private int selectedEntryIndex;
private final BluetoothLeAdapter listAdapter = new BluetoothLeAdapter();
+ private UUID sensorUUID;
+
private BluetoothLeScanner scanner = null;
private final ScanCallback scanCallback = new ScanCallback() {
@Override
@@ -116,10 +122,11 @@ public class BluetoothLePreference extends DialogPreference {
}
};
- static BluetoothLePreferenceDialog newInstance(String key) {
- final BluetoothLePreferenceDialog fragment = new BluetoothLePreferenceDialog();
+ public static BluetoothLeSensorPreferenceDialog newInstance(String preferenceKey, UUID sensorUUID) {
+ final BluetoothLeSensorPreferenceDialog fragment = new BluetoothLeSensorPreferenceDialog();
final Bundle b = new Bundle(1);
- b.putString(ARG_KEY, key);
+ b.putString(ARG_KEY, preferenceKey);
+ b.putSerializable(ARG_BLUETOOTH_UUID, sensorUUID);
fragment.setArguments(b);
return fragment;
}
@@ -128,6 +135,9 @@ public class BluetoothLePreference extends DialogPreference {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ sensorUUID = (UUID) getArguments().getSerializable(ARG_BLUETOOTH_UUID);
+ Log.i(TAG, "UUID: " + sensorUUID);
+
BluetoothAdapter bluetoothAdapter = BluetoothUtils.getDefaultBluetoothAdapter(TAG);
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
Log.w(TAG, "Bluetooth adapter is present or not enabled.");
@@ -152,14 +162,14 @@ public class BluetoothLePreference extends DialogPreference {
listAdapter.add(getContext().getString(DEVICE_NONE_RESOURCEID), deviceNone);
selectedEntryIndex = 0;
- BluetoothLePreference preference = (BluetoothLePreference) getPreference();
+ BluetoothLeSensorPreference preference = (BluetoothLeSensorPreference) getPreference();
String deviceSelected = preference.getValue();
if (deviceSelected != null && !deviceNone.equals(deviceSelected)) {
listAdapter.add(preference.getValue(), preference.getValue());
selectedEntryIndex = 1;
}
- ScanFilter.Builder scanFilterBuilder = new ScanFilter.Builder().setServiceUuid(new ParcelUuid(BluetoothUtils.HEART_RATE_SERVICE_UUID));
+ ScanFilter.Builder scanFilterBuilder = new ScanFilter.Builder().setServiceUuid(new ParcelUuid(sensorUUID));
List