From 8c949ff6ab0dc58959fe0d7245350a4c8bcc625b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Fri, 11 Oct 2024 22:29:55 +0100 Subject: [PATCH] Garmin: Persist respiratory rate --- .../gadgetbridge/daogen/GBDaoGenerator.java | 19 +++- .../devices/AbstractDeviceCoordinator.java | 11 ++- .../devices/DeviceCoordinator.java | 9 +- .../devices/garmin/GarminCoordinator.java | 12 +++ .../GarminRespiratoryRateSampleProvider.java | 56 ++++++++++++ .../devices/huami/HuamiCoordinator.java | 2 +- .../devices/test/TestDeviceCoordinator.java | 13 ++- .../devices/test/TestFeature.java | 1 + .../TestRespiratoryRateSampleProvider.java | 87 +++++++++++++++++++ .../devices/xiaomi/XiaomiCoordinator.java | 6 +- ...ava => AbstractRespiratoryRateSample.java} | 6 +- ...Sample.java => RespiratoryRateSample.java} | 4 +- .../devices/garmin/fit/FitImporter.java | 47 +++++++++- .../devices/garmin/fit/GlobalFITMessage.java | 2 +- .../fit/messages/FitRespirationRate.java | 4 +- .../FetchSleepRespiratoryRateOperation.java | 2 +- 16 files changed, 252 insertions(+), 29 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminRespiratoryRateSampleProvider.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestRespiratoryRateSampleProvider.java rename app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/{AbstractSleepRespiratoryRateSample.java => AbstractRespiratoryRateSample.java} (83%) rename app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/{SleepRespiratoryRateSample.java => RespiratoryRateSample.java} (90%) diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java index d09b20f1d..fb6d20ad2 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(80, MAIN_PACKAGE + ".entities"); + final Schema schema = new Schema(81, MAIN_PACKAGE + ".entities"); Entity userAttributes = addUserAttributes(schema); Entity user = addUserInfo(schema, userAttributes); @@ -117,6 +117,7 @@ public class GBDaoGenerator { addGarminEventSample(schema, user, device); addGarminHrvSummarySample(schema, user, device); addGarminHrvValueSample(schema, user, device); + addGarminRespiratoryRateSample(schema, user, device); addPendingFile(schema, user, device); addWena3EnergySample(schema, user, device); addWena3BehaviorSample(schema, user, device); @@ -354,9 +355,14 @@ public class GBDaoGenerator { private static Entity addHuamiSleepRespiratoryRateSample(Schema schema, Entity user, Entity device) { Entity sleepRespiratoryRateSample = addEntity(schema, "HuamiSleepRespiratoryRateSample"); - addCommonTimeSampleProperties("AbstractSleepRespiratoryRateSample", sleepRespiratoryRateSample, user, device); + addCommonTimeSampleProperties("AbstractRespiratoryRateSample", sleepRespiratoryRateSample, user, device); sleepRespiratoryRateSample.addIntProperty("utcOffset").notNull(); - sleepRespiratoryRateSample.addIntProperty("rate").notNull().codeBeforeGetter(OVERRIDE); + sleepRespiratoryRateSample.addIntProperty("rate").notNull().codeBeforeGetter( + "@Override\n" + + " public float getRespiratoryRate() {\n" + + " return (float) getRate();\n" + + " }\n\n" + ); return sleepRespiratoryRateSample; } @@ -815,6 +821,13 @@ public class GBDaoGenerator { return hrvValueSample; } + private static Entity addGarminRespiratoryRateSample(Schema schema, Entity user, Entity device) { + Entity garminRespiratoryRateSample = addEntity(schema, "GarminRespiratoryRateSample"); + addCommonTimeSampleProperties("AbstractRespiratoryRateSample", garminRespiratoryRateSample, user, device); + garminRespiratoryRateSample.addFloatProperty("respiratoryRate").notNull().codeBeforeGetter(OVERRIDE); + return garminRespiratoryRateSample; + } + 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 935877260..9ac9cffc5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java @@ -76,7 +76,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample; import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample; import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample; import nodomain.freeyourgadget.gadgetbridge.model.PaiSample; -import nodomain.freeyourgadget.gadgetbridge.model.SleepRespiratoryRateSample; +import nodomain.freeyourgadget.gadgetbridge.model.RespiratoryRateSample; import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample; import nodomain.freeyourgadget.gadgetbridge.model.StressSample; import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample; @@ -271,7 +271,7 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator { } @Override - public TimeSampleProvider getSleepRespiratoryRateSampleProvider(GBDevice device, DaoSession session) { + public TimeSampleProvider getRespiratoryRateSampleProvider(GBDevice device, DaoSession session) { return null; } @@ -538,10 +538,15 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator { } @Override - public boolean supportsSleepRespiratoryRate() { + public boolean supportsRespiratoryRate() { return false; } + @Override + public boolean supportsSleepRespiratoryRate() { + return supportsRespiratoryRate(); + } + @Override public boolean supportsWeightMeasurement() { return false; 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 b58ab0903..76f90cf85 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java @@ -56,7 +56,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample; import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample; import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample; import nodomain.freeyourgadget.gadgetbridge.model.PaiSample; -import nodomain.freeyourgadget.gadgetbridge.model.SleepRespiratoryRateSample; +import nodomain.freeyourgadget.gadgetbridge.model.RespiratoryRateSample; import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample; import nodomain.freeyourgadget.gadgetbridge.model.StressSample; import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample; @@ -263,6 +263,11 @@ public interface DeviceCoordinator { */ boolean supportsPaiTime(); + /** + * Indicates whether the device supports respiratory rate tracking. + */ + boolean supportsRespiratoryRate(); + /** * Returns true if sleep respiratory rate measurement and fetching is supported by * the device (with this coordinator). @@ -360,7 +365,7 @@ public interface DeviceCoordinator { /** * Returns the sample provider for sleep respiratory rate data, for the device being supported. */ - TimeSampleProvider getSleepRespiratoryRateSampleProvider(GBDevice device, DaoSession session); + TimeSampleProvider getRespiratoryRateSampleProvider(GBDevice device, DaoSession session); /** * Returns the sample provider for weight data, 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 e156c418e..53a4d4866 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 @@ -34,6 +34,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.GarminBodyEnergySampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.GarminEventSampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvSummarySampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvValueSampleDao; +import nodomain.freeyourgadget.gadgetbridge.entities.GarminRespiratoryRateSample; import nodomain.freeyourgadget.gadgetbridge.entities.GarminSleepStageSampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.GarminSpo2SampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.GarminStressSampleDao; @@ -44,6 +45,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser; 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.Spo2Sample; import nodomain.freeyourgadget.gadgetbridge.model.StressSample; import nodomain.freeyourgadget.gadgetbridge.model.Vo2MaxSample; @@ -147,6 +149,11 @@ public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator { return new GarminSpo2SampleProvider(device, session); } + @Override + public TimeSampleProvider getRespiratoryRateSampleProvider(final GBDevice device, final DaoSession session) { + return new GarminRespiratoryRateSampleProvider(device, session); + } + @Override public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) { final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings(); @@ -267,6 +274,11 @@ public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator { return true; } + @Override + public boolean supportsRespiratoryRate() { + return true; + } + @Override public boolean supportsFindDevice() { return true; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminRespiratoryRateSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminRespiratoryRateSampleProvider.java new file mode 100644 index 000000000..eca393f0f --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminRespiratoryRateSampleProvider.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.GarminRespiratoryRateSample; +import nodomain.freeyourgadget.gadgetbridge.entities.GarminRespiratoryRateSampleDao; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; + +public class GarminRespiratoryRateSampleProvider extends AbstractTimeSampleProvider { + public GarminRespiratoryRateSampleProvider(final GBDevice device, final DaoSession session) { + super(device, session); + } + + @NonNull + @Override + public AbstractDao getSampleDao() { + return getSession().getGarminRespiratoryRateSampleDao(); + } + + @NonNull + @Override + protected Property getTimestampSampleProperty() { + return GarminRespiratoryRateSampleDao.Properties.Timestamp; + } + + @NonNull + @Override + protected Property getDeviceIdentifierSampleProperty() { + return GarminRespiratoryRateSampleDao.Properties.DeviceId; + } + + @Override + public GarminRespiratoryRateSample createSample() { + return new GarminRespiratoryRateSample(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java index b32c4dde9..acb3e8069 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/HuamiCoordinator.java @@ -161,7 +161,7 @@ public abstract class HuamiCoordinator extends AbstractBLEDeviceCoordinator { } @Override - public HuamiSleepRespiratoryRateSampleProvider getSleepRespiratoryRateSampleProvider(GBDevice device, DaoSession session) { + public HuamiSleepRespiratoryRateSampleProvider getRespiratoryRateSampleProvider(GBDevice device, DaoSession session) { return new HuamiSleepRespiratoryRateSampleProvider(device, session); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestDeviceCoordinator.java index e8106c691..94a6df1ff 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestDeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestDeviceCoordinator.java @@ -52,6 +52,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestBodyEnergyS import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestHrvSummarySampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestHrvValueSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestPaiSampleProvider; +import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestRespiratoryRateSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestSpo2SampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestStressSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestSampleProvider; @@ -70,7 +71,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample; import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample; import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample; import nodomain.freeyourgadget.gadgetbridge.model.PaiSample; -import nodomain.freeyourgadget.gadgetbridge.model.SleepRespiratoryRateSample; +import nodomain.freeyourgadget.gadgetbridge.model.RespiratoryRateSample; import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample; import nodomain.freeyourgadget.gadgetbridge.model.StressSample; import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample; @@ -175,9 +176,8 @@ public class TestDeviceCoordinator extends AbstractDeviceCoordinator { } @Override - public TimeSampleProvider getSleepRespiratoryRateSampleProvider(final GBDevice device, final DaoSession session) { - // TODO getHeartRateManualSampleProvider - return super.getSleepRespiratoryRateSampleProvider(device, session); + public TimeSampleProvider getRespiratoryRateSampleProvider(final GBDevice device, final DaoSession session) { + return supportsRespiratoryRate() ? new TestRespiratoryRateSampleProvider() : super.getRespiratoryRateSampleProvider(device, session); } @Nullable @@ -373,6 +373,11 @@ public class TestDeviceCoordinator extends AbstractDeviceCoordinator { return supports(getTestDevice(), TestFeature.PAI_TIME); } + @Override + public boolean supportsRespiratoryRate() { + return supports(getTestDevice(), TestFeature.RESPIRATORY_RATE); + } + @Override public boolean supportsSleepRespiratoryRate() { return supports(getTestDevice(), TestFeature.SLEEP_RESPIRATORY_RATE); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestFeature.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestFeature.java index ed5a485f4..5ee6d41b1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestFeature.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/TestFeature.java @@ -56,6 +56,7 @@ public enum TestFeature { RGB_LED_COLOR, SCREENSHOTS, SLEEP_MEASUREMENT, + RESPIRATORY_RATE, SLEEP_RESPIRATORY_RATE, SMART_WAKEUP, SMART_WAKEUP_INTERVAL, diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestRespiratoryRateSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestRespiratoryRateSampleProvider.java new file mode 100644 index 000000000..bf880777b --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/test/samples/TestRespiratoryRateSampleProvider.java @@ -0,0 +1,87 @@ +/* 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.test.samples; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.ArrayList; +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider; +import nodomain.freeyourgadget.gadgetbridge.devices.test.TestDeviceRand; +import nodomain.freeyourgadget.gadgetbridge.model.RespiratoryRateSample; + +public class TestRespiratoryRateSampleProvider implements TimeSampleProvider { + @NonNull + @Override + public List getAllSamples(final long timestampFrom, final long timestampTo) { + final List samples = new ArrayList<>(); + + for (long ts = timestampFrom; ts < timestampTo; ts += 15 * 60 * 1000L) { + samples.add(new TestRespiratoryRateSample(ts)); + } + + return samples; + } + + @Override + public void addSample(final RespiratoryRateSample timeSample) { + throw new UnsupportedOperationException("read-only sample provider"); + } + + @Override + public void addSamples(final List timeSamples) { + throw new UnsupportedOperationException("read-only sample provider"); + } + + @Override + public RespiratoryRateSample createSample() { + throw new UnsupportedOperationException("read-only sample provider"); + } + + @Nullable + @Override + public RespiratoryRateSample getLatestSample() { + final long ts = System.currentTimeMillis(); + return new TestRespiratoryRateSample(ts - TestDeviceRand.randLong(ts, 10 * 1000L, 2 * 60 * 60 * 1000L)); + } + + @Nullable + @Override + public RespiratoryRateSample getFirstSample() { + return new TestRespiratoryRateSample(TestDeviceRand.BASE_TIMESTAMP); + } + + protected static class TestRespiratoryRateSample implements RespiratoryRateSample { + private final long timestamp; + + public TestRespiratoryRateSample(final long timestamp) { + this.timestamp = timestamp; + } + + @Override + public long getTimestamp() { + return timestamp; + } + + @Override + public float getRespiratoryRate() { + return TestDeviceRand.randFloat(timestamp, 10, 15); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiCoordinator.java index d39aa98fd..bcfb1be55 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiCoordinator.java @@ -58,7 +58,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser; import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample; import nodomain.freeyourgadget.gadgetbridge.model.PaiSample; -import nodomain.freeyourgadget.gadgetbridge.model.SleepRespiratoryRateSample; +import nodomain.freeyourgadget.gadgetbridge.model.RespiratoryRateSample; import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample; import nodomain.freeyourgadget.gadgetbridge.model.StressSample; import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample; @@ -170,9 +170,9 @@ public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator { } @Override - public TimeSampleProvider getSleepRespiratoryRateSampleProvider(final GBDevice device, final DaoSession session) { + public TimeSampleProvider getRespiratoryRateSampleProvider(final GBDevice device, final DaoSession session) { // TODO XiaomiSleepRespiratoryRateSampleProvider - return super.getSleepRespiratoryRateSampleProvider(device, session); + return super.getRespiratoryRateSampleProvider(device, session); } @Nullable diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractSleepRespiratoryRateSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractRespiratoryRateSample.java similarity index 83% rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractSleepRespiratoryRateSample.java rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractRespiratoryRateSample.java index ca0e020f8..fb51f89b0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractSleepRespiratoryRateSample.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractRespiratoryRateSample.java @@ -18,16 +18,16 @@ package nodomain.freeyourgadget.gadgetbridge.entities; import androidx.annotation.NonNull; -import nodomain.freeyourgadget.gadgetbridge.model.SleepRespiratoryRateSample; +import nodomain.freeyourgadget.gadgetbridge.model.RespiratoryRateSample; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; -public abstract class AbstractSleepRespiratoryRateSample extends AbstractTimeSample implements SleepRespiratoryRateSample { +public abstract class AbstractRespiratoryRateSample extends AbstractTimeSample implements RespiratoryRateSample { @NonNull @Override public String toString() { return getClass().getSimpleName() + "{" + "timestamp=" + DateTimeUtils.formatDateTime(DateTimeUtils.parseTimestampMillis(getTimestamp())) + - ", rate=" + getRate() + + ", respiratoryRate=" + getRespiratoryRate() + ", userId=" + getUserId() + ", deviceId=" + getDeviceId() + "}"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/SleepRespiratoryRateSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/RespiratoryRateSample.java similarity index 90% rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/SleepRespiratoryRateSample.java rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/RespiratoryRateSample.java index 80294486e..fa6a97505 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/SleepRespiratoryRateSample.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/RespiratoryRateSample.java @@ -16,9 +16,9 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.model; -public interface SleepRespiratoryRateSample extends TimeSample { +public interface RespiratoryRateSample extends TimeSample { /** * Returns the respiratory rate value, in breaths per minute. */ - int getRate(); + float getRespiratoryRate(); } 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 c9d9148e4..734c6844b 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 @@ -9,7 +9,6 @@ import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -17,7 +16,6 @@ import java.util.Objects; import java.util.SortedMap; import java.util.TreeMap; -import de.greenrobot.dao.query.QueryBuilder; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; @@ -26,12 +24,12 @@ import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminBodyEnergySampl import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminEventSampleProvider; 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.GarminSleepStageSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminSpo2SampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminStressSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminWorkoutParser; import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary; -import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.entities.GarminActivitySample; @@ -39,6 +37,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.GarminBodyEnergySample; import nodomain.freeyourgadget.gadgetbridge.entities.GarminEventSample; import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvSummarySample; import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvValueSample; +import nodomain.freeyourgadget.gadgetbridge.entities.GarminRespiratoryRateSample; import nodomain.freeyourgadget.gadgetbridge.entities.GarminSleepStageSample; import nodomain.freeyourgadget.gadgetbridge.entities.GarminSpo2Sample; import nodomain.freeyourgadget.gadgetbridge.entities.GarminStressSample; @@ -57,6 +56,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages. import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitMonitoring; 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; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitSession; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitSleepDataInfo; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitSleepDataRaw; @@ -77,6 +77,7 @@ public class FitImporter { private final List stressSamples = new ArrayList<>(); private final List bodyEnergySamples = new ArrayList<>(); private final List spo2samples = new ArrayList<>(); + private final List respiratoryRateSamples = new ArrayList<>(); private final List events = new ArrayList<>(); private final List sleepStageSamples = new ArrayList<>(); private final List hrvSummarySamples = new ArrayList<>(); @@ -179,6 +180,16 @@ public class FitImporter { sample.setTimestamp(ts * 1000L); sample.setSpo2(spo2); spo2samples.add(sample); + } else if (record instanceof FitRespirationRate) { + final Float respiratoryRate = ((FitRespirationRate) record).getRespirationRate(); + if (respiratoryRate == null || respiratoryRate <= 0) { + continue; + } + LOG.trace("Respiratory rate at {}: {}", ts, respiratoryRate); + final GarminRespiratoryRateSample sample = new GarminRespiratoryRateSample(); + sample.setTimestamp(ts * 1000L); + sample.setRespiratoryRate(respiratoryRate); + respiratoryRateSamples.add(sample); } else if (record instanceof FitEvent) { final FitEvent event = (FitEvent) record; if (event.getEvent() == null) { @@ -276,6 +287,7 @@ public class FitImporter { case MONITOR: persistActivitySamples(); persistSpo2Samples(); + persistRespiratoryRateSamples(); persistStressSamples(); persistBodyEnergySamples(); break; @@ -338,6 +350,7 @@ public class FitImporter { stressSamples.clear(); bodyEnergySamples.clear(); spo2samples.clear(); + respiratoryRateSamples.clear(); events.clear(); sleepStageSamples.clear(); hrvSummarySamples.clear(); @@ -645,7 +658,7 @@ public class FitImporter { return; } - LOG.debug("Will persist {} spo2 samples", stressSamples.size()); + LOG.debug("Will persist {} spo2 samples", spo2samples.size()); try (DBHandler handler = GBApplication.acquireDB()) { final DaoSession session = handler.getDaoSession(); @@ -666,6 +679,32 @@ public class FitImporter { } } + private void persistRespiratoryRateSamples() { + if (respiratoryRateSamples.isEmpty()) { + return; + } + + LOG.debug("Will persist {} respiratory rate samples", stressSamples.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 GarminRespiratoryRateSampleProvider sampleProvider = new GarminRespiratoryRateSampleProvider(gbDevice, session); + + for (final GarminRespiratoryRateSample sample : respiratoryRateSamples) { + sample.setDevice(device); + sample.setUser(user); + } + + sampleProvider.addSamples(respiratoryRateSamples); + } catch (final Exception e) { + GB.toast(context, "Error saving respiratory 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 432a5a834..93dd50f97 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 @@ -323,7 +323,7 @@ public class GlobalFITMessage { )); public static GlobalFITMessage RESPIRATION_RATE = new GlobalFITMessage(297, "RESPIRATION_RATE", Arrays.asList( - new FieldDefinitionPrimitive(0, BaseType.SINT16, "respiration_rate"), // breaths / min, scaled by 100 + new FieldDefinitionPrimitive(0, BaseType.SINT16, "respiration_rate", 100, 0), // breaths / min new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP) )); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRespirationRate.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRespirationRate.java index a7cf47d20..6d7fe0eea 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRespirationRate.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitRespirationRate.java @@ -21,8 +21,8 @@ public class FitRespirationRate extends RecordData { } @Nullable - public Integer getRespirationRate() { - return (Integer) getFieldByNumber(0); + public Float getRespirationRate() { + return (Float) getFieldByNumber(0); } @Nullable diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSleepRespiratoryRateOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSleepRespiratoryRateOperation.java index 3019b4e93..6c0ff201a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSleepRespiratoryRateOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/fetch/FetchSleepRespiratoryRateOperation.java @@ -94,7 +94,7 @@ public class FetchSleepRespiratoryRateOperation extends AbstractRepeatingFetchOp final User user = DBHelper.getUser(session); final HuamiCoordinator coordinator = (HuamiCoordinator) getDevice().getDeviceCoordinator(); - final HuamiSleepRespiratoryRateSampleProvider sampleProvider = coordinator.getSleepRespiratoryRateSampleProvider(getDevice(), session); + final HuamiSleepRespiratoryRateSampleProvider sampleProvider = coordinator.getRespiratoryRateSampleProvider(getDevice(), session); for (final HuamiSleepRespiratoryRateSample sample : samples) { sample.setDevice(device);