diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java
index fb6d20ad2..d08559c79 100644
--- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java
+++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java
@@ -46,7 +46,7 @@ public class GBDaoGenerator {
public static void main(String[] args) throws Exception {
- final Schema schema = new Schema(81, MAIN_PACKAGE + ".entities");
+ final Schema schema = new Schema(82, MAIN_PACKAGE + ".entities");
Entity userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes);
@@ -118,6 +118,7 @@ public class GBDaoGenerator {
addGarminHrvSummarySample(schema, user, device);
addGarminHrvValueSample(schema, user, device);
addGarminRespiratoryRateSample(schema, user, device);
+ addGarminHeartRateRestingSample(schema, user, device);
addPendingFile(schema, user, device);
addWena3EnergySample(schema, user, device);
addWena3BehaviorSample(schema, user, device);
@@ -828,6 +829,13 @@ public class GBDaoGenerator {
return garminRespiratoryRateSample;
}
+ private static Entity addGarminHeartRateRestingSample(Schema schema, Entity user, Entity device) {
+ Entity hrRestingSample = addEntity(schema, "GarminHeartRateRestingSample");
+ addCommonTimeSampleProperties("AbstractHeartRateSample", hrRestingSample, user, device);
+ hrRestingSample.addIntProperty(SAMPLE_HEART_RATE).notNull().codeBeforeGetter(OVERRIDE);
+ return hrRestingSample;
+ }
+
private static Entity addPendingFile(Schema schema, Entity user, Entity device) {
Entity pendingFile = addEntity(schema, "PendingFile");
pendingFile.setJavaDoc(
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminCoordinator.java
index 53a4d4866..d3eeef484 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminCoordinator.java
@@ -154,6 +154,11 @@ public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator {
return new GarminRespiratoryRateSampleProvider(device, session);
}
+ @Override
+ public GarminHeartRateRestingSampleProvider getHeartRateRestingSampleProvider(final GBDevice device, final DaoSession session) {
+ return new GarminHeartRateRestingSampleProvider(device, session);
+ }
+
@Override
public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminHeartRateRestingSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminHeartRateRestingSampleProvider.java
new file mode 100644
index 000000000..15a3779d2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminHeartRateRestingSampleProvider.java
@@ -0,0 +1,56 @@
+/* Copyright (C) 2024 José Rebelo
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see . */
+package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
+
+import androidx.annotation.NonNull;
+
+import de.greenrobot.dao.AbstractDao;
+import de.greenrobot.dao.Property;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractTimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminHeartRateRestingSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminHeartRateRestingSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class GarminHeartRateRestingSampleProvider extends AbstractTimeSampleProvider {
+ public GarminHeartRateRestingSampleProvider(final GBDevice device, final DaoSession session) {
+ super(device, session);
+ }
+
+ @NonNull
+ @Override
+ public AbstractDao getSampleDao() {
+ return getSession().getGarminHeartRateRestingSampleDao();
+ }
+
+ @NonNull
+ @Override
+ protected Property getTimestampSampleProperty() {
+ return GarminHeartRateRestingSampleDao.Properties.Timestamp;
+ }
+
+ @NonNull
+ @Override
+ protected Property getDeviceIdentifierSampleProperty() {
+ return GarminHeartRateRestingSampleDao.Properties.DeviceId;
+ }
+
+ @Override
+ public GarminHeartRateRestingSample createSample() {
+ return new GarminHeartRateRestingSample();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporter.java
index 734c6844b..54703bb71 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporter.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/FitImporter.java
@@ -22,6 +22,7 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminActivitySampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminBodyEnergySampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminEventSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminHeartRateRestingSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminHrvSummarySampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminHrvValueSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminRespiratoryRateSampleProvider;
@@ -35,6 +36,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.GarminActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.GarminBodyEnergySample;
import nodomain.freeyourgadget.gadgetbridge.entities.GarminEventSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.GarminHeartRateRestingSample;
import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvSummarySample;
import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvValueSample;
import nodomain.freeyourgadget.gadgetbridge.entities.GarminRespiratoryRateSample;
@@ -54,6 +56,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitHrvSummary;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitHrvValue;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitMonitoring;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitMonitoringHrData;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitPhysiologicalMetrics;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitRecord;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitRespirationRate;
@@ -78,6 +81,7 @@ public class FitImporter {
private final List bodyEnergySamples = new ArrayList<>();
private final List spo2samples = new ArrayList<>();
private final List respiratoryRateSamples = new ArrayList<>();
+ private final List restingHrSamples = new ArrayList<>();
private final List events = new ArrayList<>();
private final List sleepStageSamples = new ArrayList<>();
private final List hrvSummarySamples = new ArrayList<>();
@@ -95,7 +99,9 @@ public class FitImporter {
this.workoutParser = new GarminWorkoutParser(context);
}
- /** @noinspection StatementWithEmptyBody*/
+ /**
+ * @noinspection StatementWithEmptyBody
+ */
public void importFile(final File file) throws IOException {
reset();
@@ -258,6 +264,17 @@ public class FitImporter {
sample.setTimestamp(ts * 1000L);
sample.setValue(Math.round(hrvValue.getValue()));
hrvValueSamples.add(sample);
+ } else if (record instanceof FitMonitoringHrData) {
+ final FitMonitoringHrData monitoringHrData = (FitMonitoringHrData) record;
+ if (monitoringHrData.getRestingHeartRate() == null) {
+ LOG.warn("Resting HR at {} is null", ts);
+ continue;
+ }
+ LOG.trace("Resting HR at {}: {}", ts, monitoringHrData.getRestingHeartRate());
+ final GarminHeartRateRestingSample sample = new GarminHeartRateRestingSample();
+ sample.setTimestamp(ts * 1000L);
+ sample.setHeartRate(monitoringHrData.getRestingHeartRate());
+ restingHrSamples.add(sample);
} else {
LOG.trace("Unknown record: {}", record);
@@ -288,6 +305,7 @@ public class FitImporter {
persistActivitySamples();
persistSpo2Samples();
persistRespiratoryRateSamples();
+ persistRestingHrSamples();
persistStressSamples();
persistBodyEnergySamples();
break;
@@ -351,6 +369,7 @@ public class FitImporter {
bodyEnergySamples.clear();
spo2samples.clear();
respiratoryRateSamples.clear();
+ restingHrSamples.clear();
events.clear();
sleepStageSamples.clear();
hrvSummarySamples.clear();
@@ -705,6 +724,32 @@ public class FitImporter {
}
}
+ private void persistRestingHrSamples() {
+ if (restingHrSamples.isEmpty()) {
+ return;
+ }
+
+ LOG.debug("Will persist {} resting heart rate samples", restingHrSamples.size());
+
+ try (DBHandler handler = GBApplication.acquireDB()) {
+ final DaoSession session = handler.getDaoSession();
+
+ final Device device = DBHelper.getDevice(gbDevice, session);
+ final User user = DBHelper.getUser(session);
+
+ final GarminHeartRateRestingSampleProvider sampleProvider = new GarminHeartRateRestingSampleProvider(gbDevice, session);
+
+ for (final GarminHeartRateRestingSample sample : restingHrSamples) {
+ sample.setDevice(device);
+ sample.setUser(user);
+ }
+
+ sampleProvider.addSamples(restingHrSamples);
+ } catch (final Exception e) {
+ GB.toast(context, "Error saving resting heart rate samples", Toast.LENGTH_LONG, GB.ERROR, e);
+ }
+ }
+
private void persistStressSamples() {
if (stressSamples.isEmpty()) {
return;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java
index 82c3ac2de..7322c6e58 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/GlobalFITMessage.java
@@ -269,6 +269,12 @@ public class GlobalFITMessage {
new FieldDefinitionPrimitive(3, BaseType.UINT8, "developer_data_index")
));
+ public static GlobalFITMessage MONITORING_HR_DATA = new GlobalFITMessage(211, "MONITORING_HR_DATA", Arrays.asList(
+ new FieldDefinitionPrimitive(0, BaseType.UINT8, "resting_heart_rate"),
+ new FieldDefinitionPrimitive(1, BaseType.UINT8, "current_day_resting_heart_rate"),
+ new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
+ ));
+
public static GlobalFITMessage TIME_IN_ZONE = new GlobalFITMessage(216, "TIME_IN_ZONE", Arrays.asList(
new FieldDefinitionPrimitive(0, BaseType.UINT16, "reference_message"),
new FieldDefinitionPrimitive(1, BaseType.UINT16, "reference_index"),
@@ -388,6 +394,7 @@ public class GlobalFITMessage {
put(162, TIMESTAMP_CORRELATION);
put(206, FIELD_DESCRIPTION);
put(207, DEVELOPER_DATA);
+ put(211, MONITORING_HR_DATA);
put(216, TIME_IN_ZONE);
put(222, ALARM_SETTINGS);
put(225, SET);
@@ -403,6 +410,7 @@ public class GlobalFITMessage {
put(397, SKIN_TEMP_RAW);
put(398, SKIN_TEMP_OVERNIGHT);
}};
+
private final int number;
private final String name;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoringHrData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoringHrData.java
new file mode 100644
index 000000000..9dc337890
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoringHrData.java
@@ -0,0 +1,37 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages;
+
+import androidx.annotation.Nullable;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
+
+//
+// WARNING: This class was auto-generated, please avoid modifying it directly.
+// See nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.codegen.FitCodeGen
+//
+public class FitMonitoringHrData extends RecordData {
+ public FitMonitoringHrData(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
+ super(recordDefinition, recordHeader);
+
+ final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
+ if (globalNumber != 211) {
+ throw new IllegalArgumentException("FitMonitoringHrData expects global messages of " + 211 + ", got " + globalNumber);
+ }
+ }
+
+ @Nullable
+ public Integer getRestingHeartRate() {
+ return (Integer) getFieldByNumber(0);
+ }
+
+ @Nullable
+ public Integer getCurrentDayRestingHeartRate() {
+ return (Integer) getFieldByNumber(1);
+ }
+
+ @Nullable
+ public Long getTimestamp() {
+ return (Long) getFieldByNumber(253);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecordDataFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecordDataFactory.java
index 7f0063b8f..9eb722085 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecordDataFactory.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRecordDataFactory.java
@@ -61,6 +61,8 @@ public class FitRecordDataFactory {
return new FitFieldDescription(recordDefinition, recordHeader);
case 207:
return new FitDeveloperData(recordDefinition, recordHeader);
+ case 211:
+ return new FitMonitoringHrData(recordDefinition, recordHeader);
case 216:
return new FitTimeInZone(recordDefinition, recordHeader);
case 222: