diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java index d29953ac7..cf6c34216 100644 --- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java +++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java @@ -127,6 +127,7 @@ public class GBDaoGenerator { addGarminHrvValueSample(schema, user, device); addGarminRespiratoryRateSample(schema, user, device); addGarminHeartRateRestingSample(schema, user, device); + addGarminRestingMetabolicRateSample(schema, user, device); addPendingFile(schema, user, device); addWena3EnergySample(schema, user, device); addWena3BehaviorSample(schema, user, device); @@ -870,6 +871,14 @@ public class GBDaoGenerator { return hrRestingSample; } + private static Entity addGarminRestingMetabolicRateSample(Schema schema, Entity user, Entity device) { + Entity sample = addEntity(schema, "GarminRestingMetabolicRateSample"); + sample.addImport(MAIN_PACKAGE + ".model.RestingMetabolicRateSample"); + addCommonTimeSampleProperties("RestingMetabolicRateSample", sample, user, device); + sample.addIntProperty("restingMetabolicRate").notNull().codeBeforeGetter(OVERRIDE); + return sample; + } + 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/AbstractDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java index 78201df3f..8458f89a3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java @@ -77,6 +77,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample; import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample; import nodomain.freeyourgadget.gadgetbridge.model.PaiSample; import nodomain.freeyourgadget.gadgetbridge.model.RespiratoryRateSample; +import nodomain.freeyourgadget.gadgetbridge.model.RestingMetabolicRateSample; import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample; import nodomain.freeyourgadget.gadgetbridge.model.StressSample; import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample; @@ -280,6 +281,11 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator { return null; } + @Override + public TimeSampleProvider getRestingMetabolicRateProvider(final GBDevice device, final DaoSession session) { + return null; // FIXME return new DefaultRestingMetabolicRateProvider(device, session); + } + @Override @Nullable public ActivitySummaryParser getActivitySummaryParser(final GBDevice device, final Context context) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java index 19e218866..813bd8527 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java @@ -57,6 +57,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample; import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample; import nodomain.freeyourgadget.gadgetbridge.model.PaiSample; import nodomain.freeyourgadget.gadgetbridge.model.RespiratoryRateSample; +import nodomain.freeyourgadget.gadgetbridge.model.RestingMetabolicRateSample; import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample; import nodomain.freeyourgadget.gadgetbridge.model.StressSample; import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample; @@ -372,6 +373,8 @@ public interface DeviceCoordinator { */ TimeSampleProvider getWeightSampleProvider(GBDevice device, DaoSession session); + TimeSampleProvider getRestingMetabolicRateProvider(GBDevice device, DaoSession session); + /** * Returns the {@link ActivitySummaryParser} for the device being supported. * 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 23b090dab..8de544a70 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 @@ -20,6 +20,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpec import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.DefaultRestingMetabolicRateProvider; import nodomain.freeyourgadget.gadgetbridge.devices.WorkoutVo2MaxSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; @@ -45,6 +46,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.BodyEnergySample; import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample; import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample; import nodomain.freeyourgadget.gadgetbridge.model.RespiratoryRateSample; +import nodomain.freeyourgadget.gadgetbridge.model.RestingMetabolicRateSample; import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample; import nodomain.freeyourgadget.gadgetbridge.model.StressSample; import nodomain.freeyourgadget.gadgetbridge.model.Vo2MaxSample; @@ -153,6 +155,11 @@ public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator { return new GarminRespiratoryRateSampleProvider(device, session); } + @Override + public TimeSampleProvider getRestingMetabolicRateProvider(final GBDevice device, final DaoSession session) { + return new GarminRestingMetabolicRateSampleProvider(device, session); + } + @Override public GarminHeartRateRestingSampleProvider getHeartRateRestingSampleProvider(final GBDevice device, final DaoSession session) { return new GarminHeartRateRestingSampleProvider(device, session); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminRestingMetabolicRateSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminRestingMetabolicRateSampleProvider.java new file mode 100644 index 000000000..969f6e444 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminRestingMetabolicRateSampleProvider.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.GarminRestingMetabolicRateSample; +import nodomain.freeyourgadget.gadgetbridge.entities.GarminRestingMetabolicRateSampleDao; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; + +public class GarminRestingMetabolicRateSampleProvider extends AbstractTimeSampleProvider { + public GarminRestingMetabolicRateSampleProvider(final GBDevice device, final DaoSession session) { + super(device, session); + } + + @NonNull + @Override + public AbstractDao getSampleDao() { + return getSession().getGarminRestingMetabolicRateSampleDao(); + } + + @NonNull + @Override + protected Property getTimestampSampleProperty() { + return GarminRestingMetabolicRateSampleDao.Properties.Timestamp; + } + + @NonNull + @Override + protected Property getDeviceIdentifierSampleProperty() { + return GarminRestingMetabolicRateSampleDao.Properties.DeviceId; + } + + @Override + public GarminRestingMetabolicRateSample createSample() { + return new GarminRestingMetabolicRateSample(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/RestingMetabolicRateSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/RestingMetabolicRateSample.java new file mode 100644 index 000000000..bfd4f1d95 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/RestingMetabolicRateSample.java @@ -0,0 +1,23 @@ +/* Copyright (C) 2024 Severin von Wnuck-Lipinski + + 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.model; + +import nodomain.freeyourgadget.gadgetbridge.entities.AbstractTimeSample; + +public abstract class RestingMetabolicRateSample extends AbstractTimeSample { + public abstract int getRestingMetabolicRate(); +} 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 54703bb71..2f97dbf34 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 @@ -26,6 +26,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminHeartRateRestin import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminHrvSummarySampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminHrvValueSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminRespiratoryRateSampleProvider; +import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminRestingMetabolicRateSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminSleepStageSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminSpo2SampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminStressSampleProvider; @@ -37,6 +38,7 @@ 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.GarminRestingMetabolicRateSample; import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvSummarySample; import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvValueSample; import nodomain.freeyourgadget.gadgetbridge.entities.GarminRespiratoryRateSample; @@ -48,6 +50,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser; +import nodomain.freeyourgadget.gadgetbridge.model.RestingMetabolicRateSample; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.FileType; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionHrvStatus; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionSleepStage; @@ -57,6 +60,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages. 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.FitMonitoringInfo; 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; @@ -86,6 +90,7 @@ public class FitImporter { private final List sleepStageSamples = new ArrayList<>(); private final List hrvSummarySamples = new ArrayList<>(); private final List hrvValueSamples = new ArrayList<>(); + private final List restingMetabolicRateSamples = new ArrayList<>(); private final Map unknownRecords = new HashMap<>(); private FitSleepDataInfo fitSleepDataInfo = null; private final List fitSleepDataRawSamples = new ArrayList<>(); @@ -264,6 +269,16 @@ public class FitImporter { sample.setTimestamp(ts * 1000L); sample.setValue(Math.round(hrvValue.getValue())); hrvValueSamples.add(sample); + } else if (record instanceof FitMonitoringInfo) { + final FitMonitoringInfo monitoringInfo = (FitMonitoringInfo) record; + if (monitoringInfo.getRestingMetabolicRate() == null) { + continue; + } + LOG.trace("Monitoring info at {}: {}", ts, record); + final GarminRestingMetabolicRateSample sample = new GarminRestingMetabolicRateSample(); + sample.setTimestamp(ts * 1000L); + sample.setRestingMetabolicRate(monitoringInfo.getRestingMetabolicRate()); + restingMetabolicRateSamples.add(sample); } else if (record instanceof FitMonitoringHrData) { final FitMonitoringHrData monitoringHrData = (FitMonitoringHrData) record; if (monitoringHrData.getRestingHeartRate() == null) { @@ -308,6 +323,7 @@ public class FitImporter { persistRestingHrSamples(); persistStressSamples(); persistBodyEnergySamples(); + persistRestingMetabolicRateSamples(); break; case SLEEP: persistEvents(); @@ -374,6 +390,7 @@ public class FitImporter { sleepStageSamples.clear(); hrvSummarySamples.clear(); hrvValueSamples.clear(); + restingMetabolicRateSamples.clear(); unknownRecords.clear(); fitSleepDataInfo = null; fitSleepDataRawSamples.clear(); @@ -801,4 +818,30 @@ public class FitImporter { GB.toast(context, "Error saving body energy samples", Toast.LENGTH_LONG, GB.ERROR, e); } } + + private void persistRestingMetabolicRateSamples() { + if (restingMetabolicRateSamples.isEmpty()) { + return; + } + + LOG.debug("Will persist {} resting metabolic rate samples", restingMetabolicRateSamples.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 GarminRestingMetabolicRateSampleProvider sampleProvider = new GarminRestingMetabolicRateSampleProvider(gbDevice, session); + + for (final GarminRestingMetabolicRateSample sample : restingMetabolicRateSamples) { + sample.setDevice(device); + sample.setUser(user); + } + + sampleProvider.addSamples(restingMetabolicRateSamples); + } catch (final Exception e) { + GB.toast(context, "Error saving body energy samples", Toast.LENGTH_LONG, GB.ERROR, e); + } + } }