From 54844b9a9a12e3e8947d3049169981228ebf921e Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Sun, 4 Jan 2026 09:56:44 +0100 Subject: [PATCH] Sensor: add temperature. Fixes #2241. --- .../opentracks/content/data/TestDataUtil.java | 1 + .../content/data/TestSensorDataUtil.java | 1 + .../data/CustomContentProviderUtilsTest.java | 2 + .../opentracks/data/model/TrackPointTest.java | 4 ++ .../io/file/importer/ExportImportTest.java | 46 ++++++++++++---- .../file/importer/GPXTrackImporterTest.java | 5 ++ .../io/file/importer/TrackPointAssert.java | 2 + .../TrackRecordingServiceRecordingTest.java | 27 ++++++++++ .../VoiceAnnouncementUtilsTest.java | 32 +++++------ .../stats/TrackStatisticsUpdaterTest.java | 10 ++++ .../opentracks/data/ContentProviderUtils.java | 6 +++ .../data/CustomSQLiteOpenHelper.java | 32 ++++++++++- .../opentracks/data/models/Temperature.java | 18 +++++++ .../opentracks/data/models/TrackPoint.java | 7 +++ .../data/tables/TrackPointsColumns.java | 2 + .../io/file/exporter/GPXTrackExporter.java | 8 +++ .../io/file/exporter/KMLTrackExporter.java | 12 ++++- .../io/file/importer/GPXTrackImporter.java | 21 +++++++- .../io/file/importer/KMLTrackImporter.java | 9 ++++ .../BluetoothHandlerBarometricPressure.java | 8 ++- .../sensors/BluetoothHandlerTemperature.java | 53 +++++++++++++++++++ .../sensors/BluetoothRemoteSensorManager.java | 12 +++-- .../sensors/sensorData/Aggregator.java | 12 +++-- .../sensorData/AggregatorTemperature.java | 34 ++++++++++++ .../sensorData/SensorDataAggregator.java | 17 ++++++ .../sensors/sensorData/SensorDataSet.java | 2 + .../services/handlers/TrackPointCreator.java | 2 + .../opentracks/settings/PreferencesUtils.java | 7 ++- .../BluetoothLeTemperaturePreference.java | 35 ++++++++++++ .../opentracks/util/StringUtils.java | 11 +++- .../opentracks/viewmodels/Mapping.java | 1 + .../SensorStatisticsViewHolder.java | 23 ++++++++ src/main/res/values/do_not_translate.xml | 3 +- src/main/res/values/settings.xml | 7 +++ src/main/res/values/strings.xml | 3 ++ src/main/res/xml/settings_sensors.xml | 4 ++ 36 files changed, 435 insertions(+), 44 deletions(-) create mode 100644 src/main/java/de/dennisguse/opentracks/data/models/Temperature.java create mode 100644 src/main/java/de/dennisguse/opentracks/sensors/BluetoothHandlerTemperature.java create mode 100644 src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorTemperature.java create mode 100644 src/main/java/de/dennisguse/opentracks/settings/bluetooth/BluetoothLeTemperaturePreference.java diff --git a/src/androidTest/java/de/dennisguse/opentracks/content/data/TestDataUtil.java b/src/androidTest/java/de/dennisguse/opentracks/content/data/TestDataUtil.java index d2ee20b54..42f58e752 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/content/data/TestDataUtil.java +++ b/src/androidTest/java/de/dennisguse/opentracks/content/data/TestDataUtil.java @@ -132,6 +132,7 @@ public class TestDataUtil { ), null, HeartRate.of(100f + i % 80), + null, Cadence.of(300f + i), Power.of(400f + i), altitudeGainLoss diff --git a/src/androidTest/java/de/dennisguse/opentracks/content/data/TestSensorDataUtil.java b/src/androidTest/java/de/dennisguse/opentracks/content/data/TestSensorDataUtil.java index 5027d02b7..1b34385df 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/content/data/TestSensorDataUtil.java +++ b/src/androidTest/java/de/dennisguse/opentracks/content/data/TestSensorDataUtil.java @@ -39,6 +39,7 @@ public class TestSensorDataUtil { ), null, HeartRate.ofOrNull(hr), + null, cadence != null ? Cadence.of(cadence) : null, power != null ? Power.of(power) : null, new AltitudeGainLoss(3, 3) diff --git a/src/androidTest/java/de/dennisguse/opentracks/data/CustomContentProviderUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/data/CustomContentProviderUtilsTest.java index 28dddb87c..edcfac15c 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/data/CustomContentProviderUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/data/CustomContentProviderUtilsTest.java @@ -812,6 +812,7 @@ public class CustomContentProviderUtilsTest { TestDataUtil.createTrackPoint(5).position(), null, HeartRate.of(1), + null, Cadence.of(2), Power.of(3), null @@ -896,6 +897,7 @@ public class CustomContentProviderUtilsTest { null, null, null, + null, null ); diff --git a/src/androidTest/java/de/dennisguse/opentracks/data/model/TrackPointTest.java b/src/androidTest/java/de/dennisguse/opentracks/data/model/TrackPointTest.java index 5f654e497..63e8d10e0 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/data/model/TrackPointTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/data/model/TrackPointTest.java @@ -58,6 +58,7 @@ public class TrackPointTest { null, null, null, + null, null ); @@ -105,6 +106,7 @@ public class TrackPointTest { null, null, null, + null, null); // tp2 has sensor distance @@ -129,6 +131,7 @@ public class TrackPointTest { null, null, null, + null, null ); @@ -148,6 +151,7 @@ public class TrackPointTest { null, null, null, + null, null); // tp1 and tp2 have sensor distance diff --git a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java index 32173ede3..6743393fc 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java @@ -60,6 +60,7 @@ import de.dennisguse.opentracks.data.models.Position; import de.dennisguse.opentracks.data.models.Power; import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.data.models.Statistics; +import de.dennisguse.opentracks.data.models.Temperature; import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.TrackBuilder; import de.dennisguse.opentracks.data.models.TrackPoint; @@ -72,6 +73,7 @@ import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingCadence; import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingDistanceSpeed; import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingPower; import de.dennisguse.opentracks.sensors.sensorData.AggregatorHeartRate; +import de.dennisguse.opentracks.sensors.sensorData.AggregatorTemperature; import de.dennisguse.opentracks.sensors.sensorData.Raw; import de.dennisguse.opentracks.sensors.sensorData.SensorDataAggregator; import de.dennisguse.opentracks.services.TrackRecordingService; @@ -173,14 +175,14 @@ public class ExportImportTest { // A sensor-only TrackPoint trackPointCreator.setClock("2020-02-02T02:02:04Z"); - mockSensorData(trackPointCreator, 15f, sensorDistance, 66f, 3f, 50f, 1f); + mockSensorData(trackPointCreator, 15f, sensorDistance, 66f, 3f, 50f, 1f, 27); trackPointCreator.setClock("2020-02-02T02:02:14Z"); //ignored - mockSensorData(trackPointCreator, 15f, null, 67f, 3f, 50f, null); + mockSensorData(trackPointCreator, 15f, null, 67f, 3f, 50f, null, 28); trackPointCreator.setClock("2020-02-02T02:02:15Z"); - mockSensorData(trackPointCreator, null, null, 68f, 3f, 50f, null); + mockSensorData(trackPointCreator, null, null, 68f, 3f, 50f, null, 29); trackPointCreator.setClock("2020-02-02T02:02:16Z"); - mockSensorData(trackPointCreator, 5f, Distance.of(2), 69f, 3f, 50f, null); // Distance will be added to next TrackPoint + mockSensorData(trackPointCreator, 5f, Distance.of(2), 69f, 3f, 50f, null, 30); //Distance will be added to next TrackPoint sendLocation(trackPointCreator, "2020-02-02T02:02:17Z", 3.1234567, 14.0014567, 10, 13, 15, 1020.25, 0f); contentProviderUtils.insertMarker( @@ -248,6 +250,7 @@ public class ExportImportTest { null, null, null, + null, new AltitudeGainLoss(1, 1) ), new TrackPoint( @@ -261,6 +264,7 @@ public class ExportImportTest { Speed.of(15)), Distance.of(10), HeartRate.of(66), + Temperature.of(27), Cadence.of(3), Power.of(50), new AltitudeGainLoss(1, 1) @@ -271,6 +275,7 @@ public class ExportImportTest { Position.of(Instant.parse("2020-02-02T02:02:15Z")), null, HeartRate.of(68), + Temperature.of(29), Cadence.of(3), Power.of(50), null @@ -286,6 +291,7 @@ public class ExportImportTest { Speed.of(5)), Distance.of(2), HeartRate.of(69), + Temperature.of(30), Cadence.of(3), Power.of(50), new AltitudeGainLoss(0, 0) @@ -305,6 +311,7 @@ public class ExportImportTest { null, null, null, + null, new AltitudeGainLoss(0, 0) ), new TrackPoint( @@ -319,6 +326,7 @@ public class ExportImportTest { null, null, null, + null, new AltitudeGainLoss(0, 0) ), new TrackPoint( @@ -329,6 +337,7 @@ public class ExportImportTest { null, null, null, + null, new AltitudeGainLoss(0, 0) ), new TrackPoint( @@ -343,6 +352,7 @@ public class ExportImportTest { null, null, null, + null, new AltitudeGainLoss(0, 0)), new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse("2020-02-02T02:04:00Z")) ), actual); @@ -365,7 +375,7 @@ public class ExportImportTest { //TODO Does not test marker images @LargeTest @Test - public void kmz_with_trackdetail_and_sensordata() throws TimeoutException, IOException { + public void kmz_with_trackdetail_and_sensordata() throws IOException { // given Track track = contentProviderUtils.getTrack(trackId); @@ -417,7 +427,7 @@ public class ExportImportTest { @LargeTest @Test(expected = ImportAlreadyExistsException.class) - public void kml_with_trackdetail_and_sensordata_duplicate_trackUUID() throws TimeoutException, IOException { + public void kml_with_trackdetail_and_sensordata_duplicate_trackUUID() throws IOException { // given SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); editor.putBoolean(context.getString(R.string.import_prevent_reimport_key), true); @@ -487,6 +497,7 @@ public class ExportImportTest { null, null, null, + null, new AltitudeGainLoss(1, 1) ), new TrackPoint( @@ -500,6 +511,7 @@ public class ExportImportTest { Speed.of(5)), Distance.of(12), HeartRate.of(69), + Temperature.of(30), Cadence.of(3), Power.of(50), new AltitudeGainLoss(1, 1) @@ -517,6 +529,7 @@ public class ExportImportTest { null, null, null, + null, new AltitudeGainLoss(0, 0) ), new TrackPoint( @@ -532,6 +545,7 @@ public class ExportImportTest { null, null, null, + null, new AltitudeGainLoss(0, 0) ), new TrackPoint( @@ -547,6 +561,7 @@ public class ExportImportTest { null, null, null, + null, new AltitudeGainLoss(0, 0) ) ), actual); @@ -575,7 +590,7 @@ public class ExportImportTest { @LargeTest @Test(expected = ImportAlreadyExistsException.class) - public void gpx_duplicate_trackUUID() throws TimeoutException, IOException { + public void gpx_duplicate_trackUUID() throws IOException { // given SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); editor.putBoolean(context.getString(R.string.import_prevent_reimport_key), true); @@ -601,7 +616,7 @@ public class ExportImportTest { @LargeTest @Test - public void csv_export_only() throws TimeoutException, IOException { + public void csv_export_only() throws IOException { // given Track track = contentProviderUtils.getTrack(trackId); @@ -645,7 +660,7 @@ public class ExportImportTest { } } - private void mockSensorData(TrackPointCreator trackPointCreator, Float speed, Distance distance, float heartRate, float cadence, Float power, Float altitudeGain) { + private void mockSensorData(TrackPointCreator trackPointCreator, Float speed, Distance distance, float heartRate, float cadence, Float power, Float altitudeGain, float temperature) { SensorDataAggregator sensorDataAggregator = trackPointCreator.getSensorManager().sensorDataAggregator; AggregatorCyclingPower cyclingPower = new AggregatorCyclingPower("", ""); @@ -691,6 +706,19 @@ public class ExportImportTest { mockAltitudeChange(trackPointCreator, altitudeGain); + sensorDataAggregator.add(new AggregatorTemperature("", "'") { + @NonNull + @Override + public Temperature getAggregatedValue(Instant now) { + return Temperature.of(temperature); + } + + @Override + public boolean hasReceivedData() { + return true; + } + }); + trackPointCreator.onChange(); } diff --git a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporterTest.java b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporterTest.java index b4b37253e..52c479ee3 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporterTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporterTest.java @@ -210,6 +210,7 @@ public class GPXTrackImporterTest { null, null, null, + null, new AltitudeGainLoss(1, 1) ), new TrackPoint( @@ -223,6 +224,7 @@ public class GPXTrackImporterTest { Speed.of(5)), Distance.of(12), HeartRate.of(69), + null, Cadence.of(3), Power.of(50), new AltitudeGainLoss(1, 1) @@ -240,6 +242,7 @@ public class GPXTrackImporterTest { null, null, null, + null, new AltitudeGainLoss(0, 0) ), new TrackPoint( @@ -255,6 +258,7 @@ public class GPXTrackImporterTest { null, null, null, + null, new AltitudeGainLoss(0, 0) ), new TrackPoint( @@ -270,6 +274,7 @@ public class GPXTrackImporterTest { null, null, null, + null, new AltitudeGainLoss(0, 0) ) ), actual); diff --git a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/TrackPointAssert.java b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/TrackPointAssert.java index 96239821e..2c92003f3 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/TrackPointAssert.java +++ b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/TrackPointAssert.java @@ -52,6 +52,8 @@ public class TrackPointAssert { Assert.assertEquals("heartRate", expected.heartRate(), actual.heartRate()); + Assert.assertEquals("temperature", expected.temperature(), actual.temperature()); + Assert.assertEquals("power", expected.power(), actual.power()); Assert.assertEquals("cadence", expected.cadence(), actual.cadence()); diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceRecordingTest.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceRecordingTest.java index 97cd6a0b4..74dec63dc 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceRecordingTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceRecordingTest.java @@ -135,6 +135,7 @@ public class TrackRecordingServiceRecordingTest { null, null, null, + null, new AltitudeGainLoss(0, 0)) ), TestDataUtil.getTrackPoints(contentProviderUtils, trackId)); } @@ -217,6 +218,7 @@ public class TrackRecordingServiceRecordingTest { null, null, null, + null, new AltitudeGainLoss(0, 0)) ), TestDataUtil.getTrackPoints(contentProviderUtils, trackId)); @@ -239,6 +241,7 @@ public class TrackRecordingServiceRecordingTest { null, null, null, + null, new AltitudeGainLoss(0, 0) ), new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(resumeTime)) @@ -280,6 +283,7 @@ public class TrackRecordingServiceRecordingTest { null, null, null, + null, new AltitudeGainLoss(0, 0)) ), trackPoints); } @@ -316,6 +320,7 @@ public class TrackRecordingServiceRecordingTest { null, null, null, + null, new AltitudeGainLoss(0, 0) ), new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(resumeTime)) @@ -360,6 +365,7 @@ public class TrackRecordingServiceRecordingTest { HeartRate.of(7), null, null, + null, new AltitudeGainLoss(0, 0) ), new TrackPoint( @@ -370,6 +376,7 @@ public class TrackRecordingServiceRecordingTest { HeartRate.of(7), null, null, + null, new AltitudeGainLoss(0, 0) ) ), TestDataUtil.getTrackPoints(contentProviderUtils, trackId)); @@ -434,6 +441,7 @@ public class TrackRecordingServiceRecordingTest { null, null, null, + null, new AltitudeGainLoss(0, 0) ), new TrackPoint( @@ -449,6 +457,7 @@ public class TrackRecordingServiceRecordingTest { null, null, null, + null, new AltitudeGainLoss(0, 0) ), new TrackPoint( @@ -464,6 +473,7 @@ public class TrackRecordingServiceRecordingTest { null, null, null, + null, new AltitudeGainLoss(0, 0) ), new TrackPoint( @@ -474,6 +484,7 @@ public class TrackRecordingServiceRecordingTest { null, null, null, + null, new AltitudeGainLoss(0, 0) ) ), TestDataUtil.getTrackPoints(contentProviderUtils, trackId)); @@ -535,6 +546,7 @@ public class TrackRecordingServiceRecordingTest { null, null, null, + null, new AltitudeGainLoss(0, 0) ), new TrackPoint( @@ -550,6 +562,7 @@ public class TrackRecordingServiceRecordingTest { null, null, null, + null, new AltitudeGainLoss(0, 0) ), new TrackPoint( @@ -565,6 +578,7 @@ public class TrackRecordingServiceRecordingTest { null, null, null, + null, new AltitudeGainLoss(0, 0) ) ), TestDataUtil.getTrackPoints(contentProviderUtils, trackId)); @@ -614,6 +628,7 @@ public class TrackRecordingServiceRecordingTest { null, null, null, + null, new AltitudeGainLoss(0, 0) ), new TrackPoint( @@ -629,6 +644,7 @@ public class TrackRecordingServiceRecordingTest { null, null, null, + null, new AltitudeGainLoss(0, 0)), new TrackPoint( null, @@ -638,6 +654,7 @@ public class TrackRecordingServiceRecordingTest { null, null, null, + null, new AltitudeGainLoss(0, 0)) ), TestDataUtil.getTrackPoints(contentProviderUtils, trackId)); } @@ -689,6 +706,7 @@ public class TrackRecordingServiceRecordingTest { null, null, null, + null, new AltitudeGainLoss(0, 0)) ), TestDataUtil.getTrackPoints(contentProviderUtils, trackId)); } @@ -745,6 +763,7 @@ public class TrackRecordingServiceRecordingTest { null, null, null, + null, new AltitudeGainLoss(0, 0) ), new TrackPoint( @@ -760,6 +779,7 @@ public class TrackRecordingServiceRecordingTest { null, null, null, + null, new AltitudeGainLoss(0, 0) ), new TrackPoint( @@ -770,6 +790,7 @@ public class TrackRecordingServiceRecordingTest { null, null, null, + null, new AltitudeGainLoss(0, 0)) ), TestDataUtil.getTrackPoints(contentProviderUtils, trackId)); } @@ -852,6 +873,7 @@ public class TrackRecordingServiceRecordingTest { Speed.of(5)), Distance.of(2), null, + null, Cadence.of(2), null, null @@ -867,6 +889,7 @@ public class TrackRecordingServiceRecordingTest { Speed.of(5)), Distance.ZERO, null, + null, Cadence.of(2), null, null @@ -882,6 +905,7 @@ public class TrackRecordingServiceRecordingTest { Speed.of(5)), Distance.of(10), null, + null, Cadence.of(3), null, null @@ -897,6 +921,7 @@ public class TrackRecordingServiceRecordingTest { Speed.of(5)), Distance.of(4.0), null, + null, Cadence.of(5), null, null @@ -912,6 +937,7 @@ public class TrackRecordingServiceRecordingTest { Speed.of(5)), Distance.ZERO, null, + null, Cadence.of(5), null, null @@ -927,6 +953,7 @@ public class TrackRecordingServiceRecordingTest { Speed.ZERO), //Sensor data is now outdated, but we do not fall back to GPS. Distance.ZERO, null, + null, Cadence.of(0), //TODO This could be null, right? null, null diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtilsTest.java index d5d15252e..9fa1a6400 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtilsTest.java @@ -89,7 +89,7 @@ public class VoiceAnnouncementUtilsTest { null, null, new SensorData<>(HeartRate.of(60), "unused"), - null, + null, null, null, null ); @@ -129,7 +129,7 @@ public class VoiceAnnouncementUtilsTest { null, null, new SensorData<>(HeartRate.of(60), "unused"), - null, + null, null, null, null ); @@ -169,7 +169,7 @@ public class VoiceAnnouncementUtilsTest { null, null, new SensorData<>(HeartRate.of(60), "unused"), - null, + null, null, null, null ); @@ -209,7 +209,7 @@ public class VoiceAnnouncementUtilsTest { null, null, new SensorData<>(HeartRate.of(60), "unused"), - null, + null, null, null, null ); @@ -231,7 +231,7 @@ public class VoiceAnnouncementUtilsTest { null, null, new SensorData<>(HeartRate.of(60), "unused"), - null, + null, null, null, null ); @@ -271,7 +271,7 @@ public class VoiceAnnouncementUtilsTest { null, null, new SensorData<>(HeartRate.of(60), "unused"), - null, + null, null, null, null ); @@ -293,7 +293,7 @@ public class VoiceAnnouncementUtilsTest { null, null, new SensorData<>(HeartRate.of(60), "unused"), - null, + null, null, null, null ); @@ -333,7 +333,7 @@ public class VoiceAnnouncementUtilsTest { null, null, new SensorData<>(HeartRate.of(60), "unused"), - null, + null, null, null, null ); @@ -373,7 +373,7 @@ public class VoiceAnnouncementUtilsTest { null, null, new SensorData<>(HeartRate.of(60), "unused"), - null, + null, null, null, null ); @@ -413,7 +413,7 @@ public class VoiceAnnouncementUtilsTest { null, null, new SensorData<>(HeartRate.of(60), "unused"), - null, + null, null, null, null ); @@ -453,7 +453,7 @@ public class VoiceAnnouncementUtilsTest { null, null, new SensorData<>(HeartRate.of(60), "unused"), - null, + null, null, null, null ); @@ -475,7 +475,7 @@ public class VoiceAnnouncementUtilsTest { null, null, new SensorData<>(HeartRate.of(60), "unused"), - null, + null, null, null, null ); @@ -515,7 +515,7 @@ public class VoiceAnnouncementUtilsTest { null, null, new SensorData<>(HeartRate.of(60), "unused"), - null, + null, null, null, null ); @@ -537,7 +537,7 @@ public class VoiceAnnouncementUtilsTest { null, null, new SensorData<>(HeartRate.of(60), "unused"), - null, + null, null, null, null ); @@ -564,7 +564,7 @@ public class VoiceAnnouncementUtilsTest { null, null, new SensorData<>(HeartRate.of(60), "unused"), - null, + null, null, null, null ); @@ -597,7 +597,7 @@ public class VoiceAnnouncementUtilsTest { null, null, new SensorData<>(HeartRate.of(60), "unused"), - null, + null, null, null, null ); diff --git a/src/androidTest/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdaterTest.java b/src/androidTest/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdaterTest.java index 678c5cbe3..7a7d1aee9 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdaterTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdaterTest.java @@ -112,6 +112,7 @@ public class TrackStatisticsUpdaterTest { null, null, null, + null, null); // when @@ -191,6 +192,7 @@ public class TrackStatisticsUpdaterTest { HeartRate.of(50), null, null, + null, null ), new TrackPoint( @@ -201,6 +203,7 @@ public class TrackStatisticsUpdaterTest { HeartRate.of(50), null, null, + null, null ), createTrackPoint(0, 1, Altitude.WGS84.of(0), Instant.ofEpochSecond(50)), @@ -228,6 +231,7 @@ public class TrackStatisticsUpdaterTest { null, null, null, + null, null ), new TrackPoint( @@ -238,6 +242,7 @@ public class TrackStatisticsUpdaterTest { HeartRate.of(50), null, null, + null, null ), new TrackPoint( @@ -248,6 +253,7 @@ public class TrackStatisticsUpdaterTest { HeartRate.of(50), null, null, + null, null ), createTrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(50), null, Distance.of(10)), @@ -287,6 +293,7 @@ public class TrackStatisticsUpdaterTest { null, null, null, + null, null ) ); @@ -306,6 +313,7 @@ public class TrackStatisticsUpdaterTest { null, null, null, + null, null ) ); @@ -325,6 +333,7 @@ public class TrackStatisticsUpdaterTest { null, null, null, + null, null ) ); @@ -407,6 +416,7 @@ public class TrackStatisticsUpdaterTest { null, null, null, + null, null ); } diff --git a/src/main/java/de/dennisguse/opentracks/data/ContentProviderUtils.java b/src/main/java/de/dennisguse/opentracks/data/ContentProviderUtils.java index b134b5c9c..b7f040889 100644 --- a/src/main/java/de/dennisguse/opentracks/data/ContentProviderUtils.java +++ b/src/main/java/de/dennisguse/opentracks/data/ContentProviderUtils.java @@ -49,6 +49,7 @@ import de.dennisguse.opentracks.data.models.Position; import de.dennisguse.opentracks.data.models.Power; import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.data.models.Statistics; +import de.dennisguse.opentracks.data.models.Temperature; import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.data.statistics.SensorStatistics; @@ -538,6 +539,7 @@ public class ContentProviderUtils { int speedIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SPEED); int bearingIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.BEARING); int sensorHeartRateIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE); + int temperatureIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_TEMPERATURE); int sensorCadenceIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_CADENCE); int sensorDistanceIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_DISTANCE); int sensorPowerIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_POWER); @@ -564,6 +566,7 @@ public class ContentProviderUtils { ), !cursor.isNull(sensorDistanceIndex) ? Distance.of(cursor.getFloat(sensorDistanceIndex)) : null, !cursor.isNull(sensorHeartRateIndex) ? HeartRate.of(cursor.getFloat(sensorHeartRateIndex)) : null, + !cursor.isNull(temperatureIndex) ? Temperature.of(cursor.getFloat(temperatureIndex)) : null, !cursor.isNull(sensorCadenceIndex) ? Cadence.of(cursor.getFloat(sensorCadenceIndex)) : null, !cursor.isNull(sensorPowerIndex) ? Power.of(cursor.getFloat(sensorPowerIndex)) : null, altitudeGainLoss @@ -652,6 +655,9 @@ public class ContentProviderUtils { if (trackPoint.heartRate() != null) { values.put(TrackPointsColumns.SENSOR_HEARTRATE, trackPoint.heartRate().getBPM()); } + if (trackPoint.temperature() != null) { + values.put(TrackPointsColumns.SENSOR_TEMPERATURE, trackPoint.temperature().getCelsius()); + } if (trackPoint.cadence() != null) { values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.cadence().getRPM()); } diff --git a/src/main/java/de/dennisguse/opentracks/data/CustomSQLiteOpenHelper.java b/src/main/java/de/dennisguse/opentracks/data/CustomSQLiteOpenHelper.java index 29f1088ff..8e7660ad0 100644 --- a/src/main/java/de/dennisguse/opentracks/data/CustomSQLiteOpenHelper.java +++ b/src/main/java/de/dennisguse/opentracks/data/CustomSQLiteOpenHelper.java @@ -29,7 +29,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper { private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName(); - private static final int DATABASE_VERSION = 40; + private static final int DATABASE_VERSION = 41; private final Context context; @@ -84,6 +84,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper { case 38 -> upgradeFrom37to38(db); case 39 -> upgradeFrom38to39(db); case 40 -> upgradeFrom39to40(db); + case 41 -> upgradeFrom40to41(db); default -> throw new RuntimeException("Not implemented: upgrade to " + toVersion); } } @@ -111,6 +112,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper { case 37 -> downgradeFrom38to37(db); case 38 -> downgradeFrom39to38(db); case 39 -> downgradeFrom40to39(db); + case 40 -> downgradeFrom41to40(db); default -> throw new RuntimeException("Not implemented: downgrade to " + toVersion); } } @@ -734,4 +736,32 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper { db.setTransactionSuccessful(); db.endTransaction(); } + + private void upgradeFrom40to41(SQLiteDatabase db) { + db.beginTransaction(); + + db.execSQL("ALTER TABLE trackpoints RENAME TO trackpoints_old"); + db.execSQL("CREATE TABLE trackpoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, trackid INTEGER NOT NULL, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, sensor_heartrate FLOAT, sensor_temperature FLOAT, sensor_cadence FLOAT, sensor_power FLOAT, elevation_gain FLOAT, elevation_loss FLOAT, type TEXT CHECK(type IN (-2, -1, 0, 1, 3)), sensor_distance FLOAT, accuracy_vertical FLOAT, FOREIGN KEY (trackid) REFERENCES tracks(_id) ON UPDATE CASCADE ON DELETE CASCADE)"); + db.execSQL("INSERT INTO trackpoints SELECT _id, trackid, longitude, latitude, time, elevation, accuracy, speed, bearing, sensor_heartrate, null, sensor_cadence, sensor_power, elevation_gain, elevation_gain, type, sensor_distance, accuracy_vertical FROM trackpoints_old"); + db.execSQL("DROP TABLE trackpoints_old"); + + db.execSQL("CREATE INDEX trackpoints_trackid_index ON trackpoints(trackid)"); + + db.setTransactionSuccessful(); + db.endTransaction(); + } + + private void downgradeFrom41to40(SQLiteDatabase db) { + db.beginTransaction(); + + db.execSQL("ALTER TABLE trackpoints RENAME TO trackpoints_old"); + db.execSQL("CREATE TABLE trackpoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, trackid INTEGER NOT NULL, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, sensor_heartrate FLOAT, sensor_cadence FLOAT, sensor_power FLOAT, elevation_gain FLOAT, elevation_loss FLOAT, type TEXT CHECK(type IN (-2, -1, 0, 1, 3)), sensor_distance FLOAT, accuracy_vertical FLOAT, FOREIGN KEY (trackid) REFERENCES tracks(_id) ON UPDATE CASCADE ON DELETE CASCADE)"); + db.execSQL("INSERT INTO trackpoints SELECT _id, trackid, longitude, latitude, time, elevation, accuracy, speed, bearing, sensor_heartrate, sensor_cadence, sensor_power, elevation_gain, elevation_gain, type, sensor_distance, accuracy_vertical FROM trackpoints_old"); + db.execSQL("DROP TABLE trackpoints_old"); + + db.execSQL("CREATE INDEX trackpoints_trackid_index ON trackpoints(trackid)"); + + db.setTransactionSuccessful(); + db.endTransaction(); + } } diff --git a/src/main/java/de/dennisguse/opentracks/data/models/Temperature.java b/src/main/java/de/dennisguse/opentracks/data/models/Temperature.java new file mode 100644 index 000000000..5966bc15b --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/data/models/Temperature.java @@ -0,0 +1,18 @@ +package de.dennisguse.opentracks.data.models; + +public record Temperature( + float value_celsius +) { + + public static Temperature of(float value_celsius) { + return new Temperature(value_celsius); + } + + public static Temperature ofCentiCelsius(int value_centi_celsius) { + return Temperature.of((float) (value_centi_celsius / 100.0)); + } + + public float getCelsius() { + return value_celsius; + } +} diff --git a/src/main/java/de/dennisguse/opentracks/data/models/TrackPoint.java b/src/main/java/de/dennisguse/opentracks/data/models/TrackPoint.java index 6196d265d..2744be7a6 100644 --- a/src/main/java/de/dennisguse/opentracks/data/models/TrackPoint.java +++ b/src/main/java/de/dennisguse/opentracks/data/models/TrackPoint.java @@ -43,6 +43,7 @@ public record TrackPoint( Distance sensorDistance, HeartRate heartRate, + Temperature temperature, Cadence cadence, Power power, AltitudeGainLoss altitudeGainLoss @@ -57,6 +58,7 @@ public record TrackPoint( null, null, null, + null, null ); } @@ -71,6 +73,7 @@ public record TrackPoint( null, null, null, + null, null ); } @@ -95,6 +98,7 @@ public record TrackPoint( ), trackPoint.sensorDistance, trackPoint.heartRate, + null, trackPoint.cadence, trackPoint.power, trackPoint.altitudeGainLoss @@ -108,6 +112,7 @@ public record TrackPoint( position, sensorDistance, heartRate, + null, cadence, power, altitudeGainLoss @@ -121,6 +126,7 @@ public record TrackPoint( newPosition, sensorDistance, heartRate, + null, cadence, power, altitudeGainLoss @@ -150,6 +156,7 @@ public record TrackPoint( position, newSensorDistance, heartRate, + null, cadence, power, newAltitudeGainLoss diff --git a/src/main/java/de/dennisguse/opentracks/data/tables/TrackPointsColumns.java b/src/main/java/de/dennisguse/opentracks/data/tables/TrackPointsColumns.java index 931c9a43b..04501d8cd 100644 --- a/src/main/java/de/dennisguse/opentracks/data/tables/TrackPointsColumns.java +++ b/src/main/java/de/dennisguse/opentracks/data/tables/TrackPointsColumns.java @@ -50,6 +50,7 @@ public interface TrackPointsColumns extends BaseColumns { String SPEED = "speed"; String BEARING = "bearing"; String SENSOR_HEARTRATE = "sensor_heartrate"; + String SENSOR_TEMPERATURE = "sensor_temperature"; String SENSOR_CADENCE = "sensor_cadence"; String SENSOR_DISTANCE = "sensor_distance"; //DISTANCE from previous TrackPoint String SENSOR_POWER = "sensor_power"; @@ -75,6 +76,7 @@ public interface TrackPointsColumns extends BaseColumns { + SPEED + " FLOAT, " + BEARING + " FLOAT, " + SENSOR_HEARTRATE + " FLOAT, " + + SENSOR_TEMPERATURE + " FLOAT, " + SENSOR_CADENCE + " FLOAT, " + SENSOR_POWER + " FLOAT, " + ALTITUDE_GAIN + " FLOAT, " diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/GPXTrackExporter.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/GPXTrackExporter.java index 402a7185b..f4ba3f978 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/GPXTrackExporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/GPXTrackExporter.java @@ -61,6 +61,7 @@ public class GPXTrackExporter implements TrackExporter { private static final NumberFormat SPEED_FORMAT = NumberFormat.getInstance(Locale.US); private static final NumberFormat DISTANCE_FORMAT = NumberFormat.getInstance(Locale.US); private static final NumberFormat HEARTRATE_FORMAT = NumberFormat.getInstance(Locale.US); + private static final NumberFormat TEMPERATURE_FORMAT = NumberFormat.getInstance(Locale.US); private static final NumberFormat CADENCE_FORMAT = NumberFormat.getInstance(Locale.US); private static final NumberFormat POWER_FORMAT = NumberFormat.getInstance(Locale.US); @@ -82,6 +83,9 @@ public class GPXTrackExporter implements TrackExporter { HEARTRATE_FORMAT.setMaximumFractionDigits(0); HEARTRATE_FORMAT.setGroupingUsed(false); + TEMPERATURE_FORMAT.setMaximumFractionDigits(2); + TEMPERATURE_FORMAT.setGroupingUsed(false); + CADENCE_FORMAT.setMaximumFractionDigits(0); CADENCE_FORMAT.setGroupingUsed(false); @@ -321,6 +325,10 @@ public class GPXTrackExporter implements TrackExporter { trackPointExtensionV2Content += "" + HEARTRATE_FORMAT.format(trackPoint.heartRate().getBPM()) + "\n"; } + if (trackPoint.temperature() != null) { + trackPointExtensionV2Content += "" + TEMPERATURE_FORMAT.format(trackPoint.temperature().getCelsius()) + "\n"; + } + if (trackPoint.cadence() != null) { trackPointExtensionV2Content += "" + CADENCE_FORMAT.format(trackPoint.cadence().getRPM()) + "\n"; } diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/KMLTrackExporter.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/KMLTrackExporter.java index dee163490..9f1425a61 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/KMLTrackExporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/KMLTrackExporter.java @@ -67,8 +67,9 @@ public class KMLTrackExporter implements TrackExporter { public static final String EXTENDED_DATA_TYPE_TRACKPOINT = "trackpoint_type"; public static final String EXTENDED_DATA_TYPE_SPEED = "speed"; public static final String EXTENDED_DATA_TYPE_DISTANCE = "distance"; - public static final String EXTENDED_DATA_TYPE_CADENCE = "cadence"; public static final String EXTENDED_DATA_TYPE_HEARTRATE = "heartrate"; + public static final String EXTENDED_DATA_TYPE_TEMPERATURE = "temperature"; + public static final String EXTENDED_DATA_TYPE_CADENCE = "cadence"; public static final String EXTENDED_DATA_TYPE_POWER = "power"; public static final String EXTENDED_DATA_TYPE_ALTITUDE_GAIN = "elevation_gain"; public static final String EXTENDED_DATA_TYPE_ALTITUDE_LOSS = "elevation_loss"; @@ -95,6 +96,7 @@ public class KMLTrackExporter implements TrackExporter { private final List powerList = new ArrayList<>(); private final List cadenceList = new ArrayList<>(); private final List heartRateList = new ArrayList<>(); + private final List temperatureList = new ArrayList<>(); private final List altitudeGainList = new ArrayList<>(); //TODO Join with altitudeLossList private final List altitudeLossList = new ArrayList<>(); private final List accuracyHorizontal = new ArrayList<>(); @@ -237,7 +239,7 @@ public class KMLTrackExporter implements TrackExporter { xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/kml/2.3 http://schemas.opengis.net/kml/2.3/ogckml23.xsd http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd"> - """); //TODO ADD xsi:schemaLocation for atom + """); //NOTE: There is no XSD for Atom printWriter.println(""); printWriter.println("1"); printWriter.println("1"); @@ -254,6 +256,7 @@ public class KMLTrackExporter implements TrackExporter { writeSimpleArrayStyle(EXTENDED_DATA_TYPE_POWER, context.getString(R.string.description_sensor_power)); writeSimpleArrayStyle(EXTENDED_DATA_TYPE_CADENCE, context.getString(R.string.description_sensor_cadence)); writeSimpleArrayStyle(EXTENDED_DATA_TYPE_HEARTRATE, context.getString(R.string.description_sensor_heart_rate)); + writeSimpleArrayStyle(EXTENDED_DATA_TYPE_TEMPERATURE, context.getString(R.string.description_sensor_temperature)); printWriter.println(""); } @@ -321,6 +324,7 @@ public class KMLTrackExporter implements TrackExporter { powerList.clear(); cadenceList.clear(); heartRateList.clear(); + temperatureList.clear(); altitudeGainList.clear(); altitudeLossList.clear(); accuracyHorizontal.clear(); @@ -349,6 +353,9 @@ public class KMLTrackExporter implements TrackExporter { if (heartRateList.stream().anyMatch(Objects::nonNull)) { writeSimpleArraySensorData(heartRateList, EXTENDED_DATA_TYPE_HEARTRATE); } + if (temperatureList.stream().anyMatch(Objects::nonNull)) { + writeSimpleArraySensorData(temperatureList, EXTENDED_DATA_TYPE_TEMPERATURE); + } if (altitudeGainList.stream().anyMatch(Objects::nonNull)) { writeSimpleArraySensorData(altitudeGainList, EXTENDED_DATA_TYPE_ALTITUDE_GAIN); } @@ -381,6 +388,7 @@ public class KMLTrackExporter implements TrackExporter { distanceList.add(trackPoint.sensorDistance() != null ? (float) trackPoint.sensorDistance().toM() : null); heartRateList.add(trackPoint.heartRate() != null ? trackPoint.heartRate().getBPM() : null); + temperatureList.add(trackPoint.temperature() != null ? trackPoint.temperature().getCelsius() : null); cadenceList.add(trackPoint.cadence() != null ? trackPoint.cadence().getRPM() : null); powerList.add(trackPoint.power() != null ? trackPoint.power().getW() : null); if (trackPoint.altitudeGainLoss() != null) { diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporter.java index d517e94c7..0c4f82465 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/GPXTrackImporter.java @@ -40,6 +40,7 @@ import de.dennisguse.opentracks.data.models.Marker; import de.dennisguse.opentracks.data.models.Position; import de.dennisguse.opentracks.data.models.Power; import de.dennisguse.opentracks.data.models.Speed; +import de.dennisguse.opentracks.data.models.Temperature; import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.data.statistics.SegmentStatisticUpdater; @@ -83,6 +84,7 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac */ private static final String TAG_EXTENSION_SPEED_COMPAT = "speed"; private static final String TAG_EXTENSION_HEARTRATE = "gpxtpx:hr"; + private static final String TAG_EXTENSION_TEMPERATURE = "gpxtpx:atemp"; private static final String TAG_EXTENSION_CADENCE = "gpxtpx:cad"; private static final String TAG_EXTENSION_POWER = "pwr:PowerInWatts"; @@ -113,6 +115,7 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac private String time; private String speed; private String heartrate; + private String temperature; private String cadence; private String power; private String markerType; @@ -218,6 +221,11 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac heartrate = content.trim(); } } + case TAG_EXTENSION_TEMPERATURE -> { + if (content != null) { + temperature = content.trim(); + } + } case TAG_EXTENSION_CADENCE -> { if (content != null) { cadence = content.trim(); @@ -354,6 +362,15 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac } } + Temperature parsedTemperature = null; + if (temperature != null) { + try { + parsedTemperature = Temperature.of(Float.parseFloat(temperature)); + } catch (NumberFormatException e) { + throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse temperature: %s", temperature)), e); + } + } + Cadence parsedCadence = null; if (cadence != null) { try { @@ -396,6 +413,7 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac ), parsedSensorDistance, parsedHeartRate, + parsedTemperature, parsedCadence, parsedPower, altitudeGainLoss @@ -415,9 +433,10 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac sensorDistance = null; accuracyHorizontal = null; accuracyVertical = null; - power = null; heartrate = null; + temperature = null; cadence = null; + power = null; } private void onMarkerStart(Attributes attributes) { diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/KMLTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/KMLTrackImporter.java index 88deb2633..7e0ae6ba8 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/KMLTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/KMLTrackImporter.java @@ -42,6 +42,7 @@ import de.dennisguse.opentracks.data.models.Marker; import de.dennisguse.opentracks.data.models.Position; import de.dennisguse.opentracks.data.models.Power; import de.dennisguse.opentracks.data.models.Speed; +import de.dennisguse.opentracks.data.models.Temperature; import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.io.file.exporter.KMLTrackExporter; @@ -109,6 +110,7 @@ public class KMLTrackImporter extends DefaultHandler implements XMLImporter.Trac private final ArrayList sensorDistanceList = new ArrayList<>(); private final ArrayList sensorCadenceList = new ArrayList<>(); private final ArrayList sensorHeartRateList = new ArrayList<>(); + private final ArrayList sensorTemperatureList = new ArrayList<>(); private final ArrayList sensorPowerList = new ArrayList<>(); private final ArrayList altitudeGainList = new ArrayList<>(); private final ArrayList altitudeLossList = new ArrayList<>(); @@ -312,6 +314,7 @@ public class KMLTrackImporter extends DefaultHandler implements XMLImporter.Trac sensorSpeedList.clear(); sensorDistanceList.clear(); sensorHeartRateList.clear(); + sensorTemperatureList.clear(); sensorCadenceList.clear(); sensorPowerList.clear(); altitudeGainList.clear(); @@ -334,6 +337,7 @@ public class KMLTrackImporter extends DefaultHandler implements XMLImporter.Trac Speed speed = null; Distance sensorDistance = null; HeartRate heartRate = null; + Temperature temperature = null; Cadence cadence = null; Power power = null; AltitudeGainLoss altitudeGainLoss = null; @@ -352,6 +356,9 @@ public class KMLTrackImporter extends DefaultHandler implements XMLImporter.Trac if (i < sensorHeartRateList.size()) { heartRate = HeartRate.ofOrNull(sensorHeartRateList.get(i)); } + if (i < sensorTemperatureList.size() && sensorTemperatureList.get(i) != null) { + temperature = Temperature.of(sensorTemperatureList.get(i)); + } if (i < sensorCadenceList.size() && sensorCadenceList.get(i) != null) { cadence = Cadence.of(sensorCadenceList.get(i)); } @@ -399,6 +406,7 @@ public class KMLTrackImporter extends DefaultHandler implements XMLImporter.Trac position, sensorDistance, heartRate, + temperature, cadence, power, altitudeGainLoss @@ -467,6 +475,7 @@ public class KMLTrackImporter extends DefaultHandler implements XMLImporter.Trac case KMLTrackExporter.EXTENDED_DATA_TYPE_POWER -> sensorPowerList.add(value); case KMLTrackExporter.EXTENDED_DATA_TYPE_HEARTRATE, EXTENDED_DATA_TYPE_HEART_RATE_LEGACY -> sensorHeartRateList.add(value); + case KMLTrackExporter.EXTENDED_DATA_TYPE_TEMPERATURE -> sensorTemperatureList.add(value); case KMLTrackExporter.EXTENDED_DATA_TYPE_CADENCE -> sensorCadenceList.add(value); case KMLTrackExporter.EXTENDED_DATA_TYPE_ALTITUDE_GAIN -> altitudeGainList.add(value); case KMLTrackExporter.EXTENDED_DATA_TYPE_ALTITUDE_LOSS -> altitudeLossList.add(value); diff --git a/src/main/java/de/dennisguse/opentracks/sensors/BluetoothHandlerBarometricPressure.java b/src/main/java/de/dennisguse/opentracks/sensors/BluetoothHandlerBarometricPressure.java index a3ed9ba28..ce7f58823 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/BluetoothHandlerBarometricPressure.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/BluetoothHandlerBarometricPressure.java @@ -12,10 +12,10 @@ import de.dennisguse.opentracks.sensors.sensorData.Raw; import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface; public class BluetoothHandlerBarometricPressure implements SensorHandlerInterface { - private static final UUID ENVIRONMENTAL_SENSING_SERVICE = new UUID(0x181A00001000L, 0x800000805f9b34fbL); + 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) + new UUID(0x2A6D00001000L, 0x800000805f9b34fbL) //TODO extract leastSigBits ); @Override @@ -37,9 +37,7 @@ public class BluetoothHandlerBarometricPressure implements SensorHandlerInterfac } /** - * Decoding: - * org.bluetooth.service.environmental_sensing.xml - * org.bluetooth.characteristic.pressure.xml + * Decoding: org.bluetooth.characteristic.pressure.xml */ public static AtmosphericPressure parseEnvironmentalSensing(BluetoothGattCharacteristic characteristic) { byte[] raw = characteristic.getValue(); diff --git a/src/main/java/de/dennisguse/opentracks/sensors/BluetoothHandlerTemperature.java b/src/main/java/de/dennisguse/opentracks/sensors/BluetoothHandlerTemperature.java new file mode 100644 index 000000000..18b1ac1fa --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/sensors/BluetoothHandlerTemperature.java @@ -0,0 +1,53 @@ +package de.dennisguse.opentracks.sensors; + +import static de.dennisguse.opentracks.sensors.BluetoothHandlerBarometricPressure.ENVIRONMENTAL_SENSING_SERVICE; + +import android.bluetooth.BluetoothGattCharacteristic; + +import java.util.List; +import java.util.UUID; + +import de.dennisguse.opentracks.data.models.Temperature; +import de.dennisguse.opentracks.sensors.sensorData.Aggregator; +import de.dennisguse.opentracks.sensors.sensorData.AggregatorTemperature; +import de.dennisguse.opentracks.sensors.sensorData.Raw; +import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface; + +public class BluetoothHandlerTemperature implements SensorHandlerInterface { + public static final ServiceMeasurementUUID TEMPERATURE = new ServiceMeasurementUUID( + ENVIRONMENTAL_SENSING_SERVICE, + new UUID(0x2A6E00001000L, 0x800000805f9b34fbL) + ); + + @Override + public List getServices() { + return List.of(TEMPERATURE); + } + + @Override + public Aggregator createEmptySensorData(String address, String name) { + return new AggregatorTemperature(address, name); + } + + @Override + public void handlePayload(SensorManager.SensorDataChangedObserver observer, ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) { + Temperature value = parseEnvironmentalSensing(characteristic); + if (value == null) return; + + observer.onChange(new Raw<>(observer.getNow(), value)); + } + + /** + * Decoding: org.bluetooth.characteristic.temperature.xml + */ + public static Temperature parseEnvironmentalSensing(BluetoothGattCharacteristic characteristic) { + byte[] raw = characteristic.getValue(); + + if (raw.length < 2) { + return null; + } + + Integer pressure = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_SINT16, 0); + return Temperature.ofCentiCelsius(pressure); + } +} diff --git a/src/main/java/de/dennisguse/opentracks/sensors/BluetoothRemoteSensorManager.java b/src/main/java/de/dennisguse/opentracks/sensors/BluetoothRemoteSensorManager.java index 301cbccdb..66bccbdce 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/BluetoothRemoteSensorManager.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/BluetoothRemoteSensorManager.java @@ -24,8 +24,6 @@ import android.os.Handler; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import java.time.Duration; - import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.settings.PreferencesUtils; @@ -40,13 +38,12 @@ public class BluetoothRemoteSensorManager implements SensorConnector, SharedPref private static final String TAG = BluetoothRemoteSensorManager.class.getSimpleName(); - public static final Duration MAX_SENSOR_DATE_SET_AGE = Duration.ofSeconds(5); - private final Context context; private final Handler handler; private boolean started = false; private final BluetoothConnectionManager heartRate; + private final BluetoothConnectionManager temperature; private final BluetoothConnectionManager cyclingCadence; private final BluetoothConnectionManager cyclingSpeed; private final BluetoothConnectionManager cyclingPower; @@ -58,6 +55,7 @@ public class BluetoothRemoteSensorManager implements SensorConnector, SharedPref BluetoothAdapter bluetoothAdapter = BluetoothUtils.getAdapter(context); this.heartRate = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerHeartRate()); + this.temperature = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerTemperature()); this.cyclingCadence = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerCyclingCadence()); this.cyclingSpeed = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerCyclingDistanceSpeed()); this.cyclingPower = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerCyclingPower()); @@ -75,6 +73,7 @@ public class BluetoothRemoteSensorManager implements SensorConnector, SharedPref @Override public synchronized void stop(Context context) { heartRate.disconnect(); + temperature.disconnect(); cyclingCadence.disconnect(); cyclingSpeed.disconnect(); cyclingPower.disconnect(); @@ -96,6 +95,11 @@ public class BluetoothRemoteSensorManager implements SensorConnector, SharedPref connect(heartRate, address); } + if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_temperature_key, key)) { + String address = PreferencesUtils.getBluetoothTemperatureSensorAddress(); + connect(temperature, address); + } + if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_cycling_cadence_key, key)) { String address = PreferencesUtils.getBluetoothCyclingCadenceSensorAddress(); connect(cyclingCadence, address); diff --git a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/Aggregator.java b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/Aggregator.java index 749d5a27c..44f66136c 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/Aggregator.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/Aggregator.java @@ -2,12 +2,13 @@ package de.dennisguse.opentracks.sensors.sensorData; import androidx.annotation.NonNull; +import java.time.Duration; import java.time.Instant; -import de.dennisguse.opentracks.sensors.BluetoothRemoteSensorManager; - public abstract class Aggregator { + private static final Duration MAX_SENSOR_DATE_SET_AGE = Duration.ofSeconds(5); + protected Raw previous; protected Output aggregatedValue; @@ -43,6 +44,7 @@ public abstract class Aggregator { return aggregatedValue != null; } + @Deprecated //TODO Remove: for sensor there is no default value. Make getAggregatedValue() @Nullable @NonNull protected abstract Output getNoneValue(); @@ -84,10 +86,14 @@ public abstract class Aggregator { return now .isAfter( previous.time() - .plus(BluetoothRemoteSensorManager.MAX_SENSOR_DATE_SET_AGE) + .plus(getMaxSensorAge()) ); } + protected Duration getMaxSensorAge() { + return MAX_SENSOR_DATE_SET_AGE; + } + @NonNull @Override public String toString() { diff --git a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorTemperature.java b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorTemperature.java new file mode 100644 index 000000000..18609ff01 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorTemperature.java @@ -0,0 +1,34 @@ +package de.dennisguse.opentracks.sensors.sensorData; + +import androidx.annotation.NonNull; + +import java.time.Duration; + +import de.dennisguse.opentracks.data.models.Temperature; + +public class AggregatorTemperature extends Aggregator { + + public AggregatorTemperature(String sensorAddress, String sensorName) { + super(sensorAddress, sensorName); + } + + protected void computeValue(Raw current) { + this.aggregatedValue = current.value(); + } + + @NonNull + protected Temperature getNoneValue() { + return Temperature.ofCentiCelsius(0); + } + + protected void resetImmediate() { + aggregatedValue = getNoneValue(); + } + + public void resetAggregated() {}; + + @Override + protected Duration getMaxSensorAge() { + return Duration.ofMinutes(1); + } +} diff --git a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataAggregator.java b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataAggregator.java index 89fe0bc7c..61990e1d6 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataAggregator.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataAggregator.java @@ -14,6 +14,7 @@ import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.HeartRate; import de.dennisguse.opentracks.data.models.Position; import de.dennisguse.opentracks.data.models.Speed; +import de.dennisguse.opentracks.data.models.Temperature; import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingCadence; import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingDistanceSpeed; import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingPower; @@ -27,6 +28,9 @@ public class SensorDataAggregator { @VisibleForTesting public AggregatorHeartRate heartRate; + @VisibleForTesting + public AggregatorTemperature temperature; + @VisibleForTesting public AggregatorCyclingCadence cyclingCadence; @@ -97,6 +101,7 @@ public class SensorDataAggregator { set(data, data); } + //TODO Refactor generic "fun" here: go explicit. public void update(@NonNull Raw data) { Object value = data.value(); @@ -105,6 +110,11 @@ public class SensorDataAggregator { return; } + if (value instanceof Temperature) { + this.temperature.add((Raw) data); + return; + } + if (value instanceof BluetoothHandlerCyclingCadence.CrankData) { this.cyclingCadence.add((Raw) data); return; @@ -142,6 +152,7 @@ public class SensorDataAggregator { public void clear() { Log.i(TAG, "Removing all aggregators"); this.heartRate = null; + this.temperature = null; this.cyclingCadence = null; this.cyclingDistanceSpeed = null; this.cyclingPower = null; @@ -156,6 +167,7 @@ public class SensorDataAggregator { getSpeed(now), getDistance(now), getHeartRate(now), + temperature != null && temperature.hasReceivedData() ? temperature.getAggregatedValueWithSensorName(now) : null, getCadence(now), cyclingPower != null && cyclingPower.hasReceivedData() ? cyclingPower.getAggregatedValueWithSensorName(now) : null, barometer != null && barometer.hasReceivedData() ? barometer.getAggregatedValueWithSensorName(now) : null @@ -166,6 +178,7 @@ public class SensorDataAggregator { Log.i(TAG, "Resetting data"); if (heartRate != null) heartRate.resetAggregated(); + if (temperature != null) temperature.resetAggregated(); if (cyclingCadence != null) cyclingCadence.resetAggregated(); if (cyclingDistanceSpeed != null) cyclingDistanceSpeed.resetAggregated(); if (cyclingPower != null) cyclingPower.resetAggregated(); @@ -182,6 +195,10 @@ public class SensorDataAggregator { heartRate = (AggregatorHeartRate) sensorData; return; } + if (type instanceof AggregatorTemperature) { + temperature = (AggregatorTemperature) sensorData; + return; + } if (type instanceof AggregatorCyclingCadence) { cyclingCadence = (AggregatorCyclingCadence) sensorData; return; diff --git a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataSet.java b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataSet.java index fbb05121b..8445bb318 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataSet.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataSet.java @@ -10,6 +10,7 @@ import de.dennisguse.opentracks.data.models.HeartRate; import de.dennisguse.opentracks.data.models.Position; import de.dennisguse.opentracks.data.models.Power; import de.dennisguse.opentracks.data.models.Speed; +import de.dennisguse.opentracks.data.models.Temperature; public record SensorDataSet( @NonNull @@ -21,6 +22,7 @@ public record SensorDataSet( @Nullable SensorData heartRate, @Nullable + SensorData temperature, SensorData cadence, @Nullable SensorData power, diff --git a/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java b/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java index 827e263e0..ff1e6191a 100644 --- a/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java +++ b/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java @@ -113,6 +113,7 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh position, sensorDataSet.distance() != null ? sensorDataSet.distance().data() : null, sensorDataSet.heartRate() != null ? sensorDataSet.heartRate().data() : null, + sensorDataSet.temperature() != null ? sensorDataSet.temperature().data() : null, sensorDataSet.cadence() != null ? sensorDataSet.cadence().data() : null, sensorDataSet.power() != null ? sensorDataSet.power().data() : null, sensorDataSet.altitudeGainLoss() != null ? sensorDataSet.altitudeGainLoss().data() : null @@ -138,6 +139,7 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh position, sensorDataSet.distance() != null ? sensorDataSet.distance().data() : null, sensorDataSet.heartRate() != null ? sensorDataSet.heartRate().data() : null, + sensorDataSet.temperature() != null ? sensorDataSet.temperature().data() : null, sensorDataSet.cadence() != null ? sensorDataSet.cadence().data() : null, sensorDataSet.power() != null ? sensorDataSet.power().data() : null, sensorDataSet.altitudeGainLoss() != null ? sensorDataSet.altitudeGainLoss().data() : null diff --git a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java index 264ee7d68..24c144176 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java +++ b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java @@ -274,6 +274,10 @@ public class PreferencesUtils { return getString(R.string.settings_sensor_bluetooth_heart_rate_key, getBluetoothSensorAddressNone()); } + public static String getBluetoothTemperatureSensorAddress() { + return getString(R.string.settings_sensor_bluetooth_temperature_key, getBluetoothSensorAddressNone()); + } + public static String getBluetoothCyclingCadenceSensorAddress() { return getString(R.string.settings_sensor_bluetooth_cycling_cadence_key, getBluetoothSensorAddressNone()); } @@ -830,7 +834,8 @@ public class PreferencesUtils { R.array.stats_custom_layout_fields_default_value_17, R.array.stats_custom_layout_fields_default_value_18, R.array.stats_custom_layout_fields_default_value_19, - R.array.stats_custom_layout_fields_default_value_20 + R.array.stats_custom_layout_fields_default_value_20, + R.array.stats_custom_layout_fields_default_value_21 ).map(id -> resources.obtainTypedArray(id)) .toList(); } diff --git a/src/main/java/de/dennisguse/opentracks/settings/bluetooth/BluetoothLeTemperaturePreference.java b/src/main/java/de/dennisguse/opentracks/settings/bluetooth/BluetoothLeTemperaturePreference.java new file mode 100644 index 000000000..6cca492ad --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/settings/bluetooth/BluetoothLeTemperaturePreference.java @@ -0,0 +1,35 @@ +package de.dennisguse.opentracks.settings.bluetooth; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.preference.PreferenceDialogFragmentCompat; + +import java.util.List; + +import de.dennisguse.opentracks.sensors.BluetoothHandlerTemperature; + +public class BluetoothLeTemperaturePreference extends BluetoothLeSensorPreference { + + public BluetoothLeTemperaturePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public BluetoothLeTemperaturePreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public BluetoothLeTemperaturePreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public BluetoothLeTemperaturePreference(Context context) { + super(context); + } + + @Override + public PreferenceDialogFragmentCompat createInstance() { + return BluetoothLeSensorPreferenceDialog + .newInstance(getKey(), List.of(BluetoothHandlerTemperature.TEMPERATURE)); + } +} \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/util/StringUtils.java b/src/main/java/de/dennisguse/opentracks/util/StringUtils.java index 9a68d734b..d556de921 100644 --- a/src/main/java/de/dennisguse/opentracks/util/StringUtils.java +++ b/src/main/java/de/dennisguse/opentracks/util/StringUtils.java @@ -18,7 +18,6 @@ package de.dennisguse.opentracks.util; import static java.time.temporal.ChronoUnit.DAYS; import android.content.Context; -import android.location.Location; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.Log; @@ -47,6 +46,7 @@ import de.dennisguse.opentracks.data.models.DistanceFormatter; import de.dennisguse.opentracks.data.models.HeartRate; import de.dennisguse.opentracks.data.models.Position; import de.dennisguse.opentracks.data.models.Power; +import de.dennisguse.opentracks.data.models.Temperature; import de.dennisguse.opentracks.settings.UnitSystem; /** @@ -156,6 +156,15 @@ public class StringUtils { return new Pair<>(value, context.getString(R.string.sensor_unit_beats_per_minute)); } + public static Pair getTemperatureParts(Context context, Temperature temperature) { + String value = context.getString(R.string.value_unknown); + if (temperature != null) { + value = StringUtils.formatDecimal(temperature.getCelsius(), 1); + } + + return new Pair<>(value, context.getString(R.string.sensor_unit_temperature)); + } + public static Pair getCadenceParts(Context context, Cadence cadence) { String value = context.getString(R.string.value_unknown); if (cadence != null) { diff --git a/src/main/java/de/dennisguse/opentracks/viewmodels/Mapping.java b/src/main/java/de/dennisguse/opentracks/viewmodels/Mapping.java index 781846762..88a37aa84 100644 --- a/src/main/java/de/dennisguse/opentracks/viewmodels/Mapping.java +++ b/src/main/java/de/dennisguse/opentracks/viewmodels/Mapping.java @@ -34,6 +34,7 @@ public class Mapping { m.put(context.getString(R.string.stats_custom_layout_coordinates_key), GenericStatisticsViewHolder.Coordinates::new); m.put(context.getString(R.string.stats_custom_layout_heart_rate_key), SensorStatisticsViewHolder.SensorHeartRate::new); + m.put(context.getString(R.string.stats_custom_layout_temperature_key), SensorStatisticsViewHolder.SensorTemperature::new); m.put(context.getString(R.string.stats_custom_layout_cadence_key), SensorStatisticsViewHolder.SensorCadence::new); m.put(context.getString(R.string.stats_custom_layout_power_key), SensorStatisticsViewHolder.SensorPower::new); m.put(context.getString(R.string.stats_custom_layout_clock_key), ClockViewHolder::new); diff --git a/src/main/java/de/dennisguse/opentracks/viewmodels/SensorStatisticsViewHolder.java b/src/main/java/de/dennisguse/opentracks/viewmodels/SensorStatisticsViewHolder.java index cdf15e3ad..12e9374a4 100644 --- a/src/main/java/de/dennisguse/opentracks/viewmodels/SensorStatisticsViewHolder.java +++ b/src/main/java/de/dennisguse/opentracks/viewmodels/SensorStatisticsViewHolder.java @@ -60,6 +60,29 @@ public abstract class SensorStatisticsViewHolder extends StatisticViewHolder valueAndUnit; + if (sensorDataSet != null && sensorDataSet.temperature() != null) { + valueAndUnit = StringUtils.getTemperatureParts(getContext(), sensorDataSet.temperature().data()); + sensorName = sensorDataSet.temperature().sensorNameOrAddress(); + } else { + valueAndUnit = StringUtils.getTemperatureParts(getContext(), null); + } + + getBinding().statsValue.setText(valueAndUnit.first); + getBinding().statsUnit.setText(valueAndUnit.second); + getBinding().statsDescriptionMain.setText(R.string.stats_sensors_temperature); + + getBinding().statsDescriptionSecondary.setText(sensorName); + } + } + public static class SensorCadence extends SensorStatisticsViewHolder { @Override diff --git a/src/main/res/values/do_not_translate.xml b/src/main/res/values/do_not_translate.xml index 329467930..0bc8fc9ad 100644 --- a/src/main/res/values/do_not_translate.xml +++ b/src/main/res/values/do_not_translate.xml @@ -41,7 +41,8 @@ limitations under the License. bpm rpm - w + W + C - diff --git a/src/main/res/values/settings.xml b/src/main/res/values/settings.xml index 7163b2c2d..e2194c760 100644 --- a/src/main/res/values/settings.xml +++ b/src/main/res/values/settings.xml @@ -20,6 +20,7 @@ settingsReset bluetoothSensor + bluetoothTemperature bluetoothCyclingCadenceSensor bluetoothCyclingSpeedSensor bluetoothCyclingPowerSensor @@ -361,6 +362,7 @@ coordinates heart_rate average_heart_rate + temperature cadence average_cadence power @@ -370,6 +372,7 @@ Every array has two items: - Key value for the dataField. - Comma separated flags for: visible, primary and span/wide for the dataField. + TODO: refactor, so this data is not stored here, but in de.dennisguse.opentracks.viewmodels.Mapping. No need to have two places. --> @string/stats_custom_layout_distance_key @@ -455,6 +458,10 @@ @string/stats_custom_layout_clock_key 0,0,0 + + @string/stats_custom_layout_temperature_key + 0,0,0 + 0 diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index c79ce7642..af6b17cb6 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -173,6 +173,7 @@ limitations under the License. Speed (m/s) Cadence (rpm) Heart rate (bpm) + Temperature (Celsius) Power (W) By activity type Speed (mi/hr) @@ -312,6 +313,7 @@ limitations under the License. bpm %1$d bpm Power + Thermometer Barometer Avg Power Max Power @@ -471,6 +473,7 @@ limitations under the License. Total time Split interval Heart rate + Temperature Cadence Power Clock diff --git a/src/main/res/xml/settings_sensors.xml b/src/main/res/xml/settings_sensors.xml index 5f3114784..582e1bcfe 100644 --- a/src/main/res/xml/settings_sensors.xml +++ b/src/main/res/xml/settings_sensors.xml @@ -29,6 +29,10 @@ android:key="@string/settings_sensor_heart_rate_max_key" android:title="@string/settings_sensor_heart_rate_max" app:useSimpleSummaryProvider="true" /> +