1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-27 12:26:48 +01:00

Garmin: Persist respiratory rate

This commit is contained in:
José Rebelo 2024-10-11 22:29:55 +01:00
parent 27a830fd13
commit 8c949ff6ab
16 changed files with 252 additions and 29 deletions

View File

@ -46,7 +46,7 @@ public class GBDaoGenerator {
public static void main(String[] args) throws Exception { 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 userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes); Entity user = addUserInfo(schema, userAttributes);
@ -117,6 +117,7 @@ public class GBDaoGenerator {
addGarminEventSample(schema, user, device); addGarminEventSample(schema, user, device);
addGarminHrvSummarySample(schema, user, device); addGarminHrvSummarySample(schema, user, device);
addGarminHrvValueSample(schema, user, device); addGarminHrvValueSample(schema, user, device);
addGarminRespiratoryRateSample(schema, user, device);
addPendingFile(schema, user, device); addPendingFile(schema, user, device);
addWena3EnergySample(schema, user, device); addWena3EnergySample(schema, user, device);
addWena3BehaviorSample(schema, user, device); addWena3BehaviorSample(schema, user, device);
@ -354,9 +355,14 @@ public class GBDaoGenerator {
private static Entity addHuamiSleepRespiratoryRateSample(Schema schema, Entity user, Entity device) { private static Entity addHuamiSleepRespiratoryRateSample(Schema schema, Entity user, Entity device) {
Entity sleepRespiratoryRateSample = addEntity(schema, "HuamiSleepRespiratoryRateSample"); Entity sleepRespiratoryRateSample = addEntity(schema, "HuamiSleepRespiratoryRateSample");
addCommonTimeSampleProperties("AbstractSleepRespiratoryRateSample", sleepRespiratoryRateSample, user, device); addCommonTimeSampleProperties("AbstractRespiratoryRateSample", sleepRespiratoryRateSample, user, device);
sleepRespiratoryRateSample.addIntProperty("utcOffset").notNull(); 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; return sleepRespiratoryRateSample;
} }
@ -815,6 +821,13 @@ public class GBDaoGenerator {
return hrvValueSample; 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) { private static Entity addPendingFile(Schema schema, Entity user, Entity device) {
Entity pendingFile = addEntity(schema, "PendingFile"); Entity pendingFile = addEntity(schema, "PendingFile");
pendingFile.setJavaDoc( pendingFile.setJavaDoc(

View File

@ -76,7 +76,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample; import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample;
import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample; import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample;
import nodomain.freeyourgadget.gadgetbridge.model.PaiSample; 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.Spo2Sample;
import nodomain.freeyourgadget.gadgetbridge.model.StressSample; import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample; import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
@ -271,7 +271,7 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
} }
@Override @Override
public TimeSampleProvider<? extends SleepRespiratoryRateSample> getSleepRespiratoryRateSampleProvider(GBDevice device, DaoSession session) { public TimeSampleProvider<? extends RespiratoryRateSample> getRespiratoryRateSampleProvider(GBDevice device, DaoSession session) {
return null; return null;
} }
@ -538,10 +538,15 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
} }
@Override @Override
public boolean supportsSleepRespiratoryRate() { public boolean supportsRespiratoryRate() {
return false; return false;
} }
@Override
public boolean supportsSleepRespiratoryRate() {
return supportsRespiratoryRate();
}
@Override @Override
public boolean supportsWeightMeasurement() { public boolean supportsWeightMeasurement() {
return false; return false;

View File

@ -56,7 +56,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample; import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample;
import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample; import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample;
import nodomain.freeyourgadget.gadgetbridge.model.PaiSample; 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.Spo2Sample;
import nodomain.freeyourgadget.gadgetbridge.model.StressSample; import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample; import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
@ -263,6 +263,11 @@ public interface DeviceCoordinator {
*/ */
boolean supportsPaiTime(); boolean supportsPaiTime();
/**
* Indicates whether the device supports respiratory rate tracking.
*/
boolean supportsRespiratoryRate();
/** /**
* Returns true if sleep respiratory rate measurement and fetching is supported by * Returns true if sleep respiratory rate measurement and fetching is supported by
* the device (with this coordinator). * 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. * Returns the sample provider for sleep respiratory rate data, for the device being supported.
*/ */
TimeSampleProvider<? extends SleepRespiratoryRateSample> getSleepRespiratoryRateSampleProvider(GBDevice device, DaoSession session); TimeSampleProvider<? extends RespiratoryRateSample> getRespiratoryRateSampleProvider(GBDevice device, DaoSession session);
/** /**
* Returns the sample provider for weight data, for the device being supported. * Returns the sample provider for weight data, for the device being supported.

View File

@ -34,6 +34,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.GarminBodyEnergySampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.GarminEventSampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.GarminEventSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvSummarySampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvSummarySampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvValueSampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvValueSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.GarminRespiratoryRateSample;
import nodomain.freeyourgadget.gadgetbridge.entities.GarminSleepStageSampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.GarminSleepStageSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.GarminSpo2SampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.GarminSpo2SampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.GarminStressSampleDao; 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.BodyEnergySample;
import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample; import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample;
import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample; import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample;
import nodomain.freeyourgadget.gadgetbridge.model.RespiratoryRateSample;
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample; import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
import nodomain.freeyourgadget.gadgetbridge.model.StressSample; import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
import nodomain.freeyourgadget.gadgetbridge.model.Vo2MaxSample; import nodomain.freeyourgadget.gadgetbridge.model.Vo2MaxSample;
@ -147,6 +149,11 @@ public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator {
return new GarminSpo2SampleProvider(device, session); return new GarminSpo2SampleProvider(device, session);
} }
@Override
public TimeSampleProvider<? extends RespiratoryRateSample> getRespiratoryRateSampleProvider(final GBDevice device, final DaoSession session) {
return new GarminRespiratoryRateSampleProvider(device, session);
}
@Override @Override
public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) { public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings(); final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
@ -267,6 +274,11 @@ public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator {
return true; return true;
} }
@Override
public boolean supportsRespiratoryRate() {
return true;
}
@Override @Override
public boolean supportsFindDevice() { public boolean supportsFindDevice() {
return true; return true;

View File

@ -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 <https://www.gnu.org/licenses/>. */
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<GarminRespiratoryRateSample> {
public GarminRespiratoryRateSampleProvider(final GBDevice device, final DaoSession session) {
super(device, session);
}
@NonNull
@Override
public AbstractDao<GarminRespiratoryRateSample, ?> 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();
}
}

View File

@ -161,7 +161,7 @@ public abstract class HuamiCoordinator extends AbstractBLEDeviceCoordinator {
} }
@Override @Override
public HuamiSleepRespiratoryRateSampleProvider getSleepRespiratoryRateSampleProvider(GBDevice device, DaoSession session) { public HuamiSleepRespiratoryRateSampleProvider getRespiratoryRateSampleProvider(GBDevice device, DaoSession session) {
return new HuamiSleepRespiratoryRateSampleProvider(device, session); return new HuamiSleepRespiratoryRateSampleProvider(device, session);
} }

View File

@ -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.TestHrvSummarySampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestHrvValueSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestHrvValueSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestPaiSampleProvider; 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.TestSpo2SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestStressSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestStressSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestSampleProvider; 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.HrvSummarySample;
import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample; import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample;
import nodomain.freeyourgadget.gadgetbridge.model.PaiSample; 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.Spo2Sample;
import nodomain.freeyourgadget.gadgetbridge.model.StressSample; import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample; import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
@ -175,9 +176,8 @@ public class TestDeviceCoordinator extends AbstractDeviceCoordinator {
} }
@Override @Override
public TimeSampleProvider<? extends SleepRespiratoryRateSample> getSleepRespiratoryRateSampleProvider(final GBDevice device, final DaoSession session) { public TimeSampleProvider<? extends RespiratoryRateSample> getRespiratoryRateSampleProvider(final GBDevice device, final DaoSession session) {
// TODO getHeartRateManualSampleProvider return supportsRespiratoryRate() ? new TestRespiratoryRateSampleProvider() : super.getRespiratoryRateSampleProvider(device, session);
return super.getSleepRespiratoryRateSampleProvider(device, session);
} }
@Nullable @Nullable
@ -373,6 +373,11 @@ public class TestDeviceCoordinator extends AbstractDeviceCoordinator {
return supports(getTestDevice(), TestFeature.PAI_TIME); return supports(getTestDevice(), TestFeature.PAI_TIME);
} }
@Override
public boolean supportsRespiratoryRate() {
return supports(getTestDevice(), TestFeature.RESPIRATORY_RATE);
}
@Override @Override
public boolean supportsSleepRespiratoryRate() { public boolean supportsSleepRespiratoryRate() {
return supports(getTestDevice(), TestFeature.SLEEP_RESPIRATORY_RATE); return supports(getTestDevice(), TestFeature.SLEEP_RESPIRATORY_RATE);

View File

@ -56,6 +56,7 @@ public enum TestFeature {
RGB_LED_COLOR, RGB_LED_COLOR,
SCREENSHOTS, SCREENSHOTS,
SLEEP_MEASUREMENT, SLEEP_MEASUREMENT,
RESPIRATORY_RATE,
SLEEP_RESPIRATORY_RATE, SLEEP_RESPIRATORY_RATE,
SMART_WAKEUP, SMART_WAKEUP,
SMART_WAKEUP_INTERVAL, SMART_WAKEUP_INTERVAL,

View File

@ -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 <https://www.gnu.org/licenses/>. */
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<RespiratoryRateSample> {
@NonNull
@Override
public List<RespiratoryRateSample> getAllSamples(final long timestampFrom, final long timestampTo) {
final List<RespiratoryRateSample> 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<RespiratoryRateSample> 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);
}
}
}

View File

@ -58,7 +58,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample; import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
import nodomain.freeyourgadget.gadgetbridge.model.PaiSample; 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.Spo2Sample;
import nodomain.freeyourgadget.gadgetbridge.model.StressSample; import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample; import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
@ -170,9 +170,9 @@ public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator {
} }
@Override @Override
public TimeSampleProvider<? extends SleepRespiratoryRateSample> getSleepRespiratoryRateSampleProvider(final GBDevice device, final DaoSession session) { public TimeSampleProvider<? extends RespiratoryRateSample> getRespiratoryRateSampleProvider(final GBDevice device, final DaoSession session) {
// TODO XiaomiSleepRespiratoryRateSampleProvider // TODO XiaomiSleepRespiratoryRateSampleProvider
return super.getSleepRespiratoryRateSampleProvider(device, session); return super.getRespiratoryRateSampleProvider(device, session);
} }
@Nullable @Nullable

View File

@ -18,16 +18,16 @@ package nodomain.freeyourgadget.gadgetbridge.entities;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import nodomain.freeyourgadget.gadgetbridge.model.SleepRespiratoryRateSample; import nodomain.freeyourgadget.gadgetbridge.model.RespiratoryRateSample;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
public abstract class AbstractSleepRespiratoryRateSample extends AbstractTimeSample implements SleepRespiratoryRateSample { public abstract class AbstractRespiratoryRateSample extends AbstractTimeSample implements RespiratoryRateSample {
@NonNull @NonNull
@Override @Override
public String toString() { public String toString() {
return getClass().getSimpleName() + "{" + return getClass().getSimpleName() + "{" +
"timestamp=" + DateTimeUtils.formatDateTime(DateTimeUtils.parseTimestampMillis(getTimestamp())) + "timestamp=" + DateTimeUtils.formatDateTime(DateTimeUtils.parseTimestampMillis(getTimestamp())) +
", rate=" + getRate() + ", respiratoryRate=" + getRespiratoryRate() +
", userId=" + getUserId() + ", userId=" + getUserId() +
", deviceId=" + getDeviceId() + ", deviceId=" + getDeviceId() +
"}"; "}";

View File

@ -16,9 +16,9 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */ along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.model; package nodomain.freeyourgadget.gadgetbridge.model;
public interface SleepRespiratoryRateSample extends TimeSample { public interface RespiratoryRateSample extends TimeSample {
/** /**
* Returns the respiratory rate value, in breaths per minute. * Returns the respiratory rate value, in breaths per minute.
*/ */
int getRate(); float getRespiratoryRate();
} }

View File

@ -9,7 +9,6 @@ import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -17,7 +16,6 @@ import java.util.Objects;
import java.util.SortedMap; import java.util.SortedMap;
import java.util.TreeMap; import java.util.TreeMap;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; 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.GarminEventSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminHrvSummarySampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminHrvSummarySampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminHrvValueSampleProvider; 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.GarminSleepStageSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminSpo2SampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminSpo2SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminStressSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminStressSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminWorkoutParser; import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminWorkoutParser;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary; import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.GarminActivitySample; 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.GarminEventSample;
import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvSummarySample; import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvSummarySample;
import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvValueSample; import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvValueSample;
import nodomain.freeyourgadget.gadgetbridge.entities.GarminRespiratoryRateSample;
import nodomain.freeyourgadget.gadgetbridge.entities.GarminSleepStageSample; import nodomain.freeyourgadget.gadgetbridge.entities.GarminSleepStageSample;
import nodomain.freeyourgadget.gadgetbridge.entities.GarminSpo2Sample; import nodomain.freeyourgadget.gadgetbridge.entities.GarminSpo2Sample;
import nodomain.freeyourgadget.gadgetbridge.entities.GarminStressSample; 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.FitMonitoring;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitPhysiologicalMetrics; 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.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.FitSession;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitSleepDataInfo; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitSleepDataInfo;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitSleepDataRaw; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitSleepDataRaw;
@ -77,6 +77,7 @@ public class FitImporter {
private final List<GarminStressSample> stressSamples = new ArrayList<>(); private final List<GarminStressSample> stressSamples = new ArrayList<>();
private final List<GarminBodyEnergySample> bodyEnergySamples = new ArrayList<>(); private final List<GarminBodyEnergySample> bodyEnergySamples = new ArrayList<>();
private final List<GarminSpo2Sample> spo2samples = new ArrayList<>(); private final List<GarminSpo2Sample> spo2samples = new ArrayList<>();
private final List<GarminRespiratoryRateSample> respiratoryRateSamples = new ArrayList<>();
private final List<GarminEventSample> events = new ArrayList<>(); private final List<GarminEventSample> events = new ArrayList<>();
private final List<GarminSleepStageSample> sleepStageSamples = new ArrayList<>(); private final List<GarminSleepStageSample> sleepStageSamples = new ArrayList<>();
private final List<GarminHrvSummarySample> hrvSummarySamples = new ArrayList<>(); private final List<GarminHrvSummarySample> hrvSummarySamples = new ArrayList<>();
@ -179,6 +180,16 @@ public class FitImporter {
sample.setTimestamp(ts * 1000L); sample.setTimestamp(ts * 1000L);
sample.setSpo2(spo2); sample.setSpo2(spo2);
spo2samples.add(sample); 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) { } else if (record instanceof FitEvent) {
final FitEvent event = (FitEvent) record; final FitEvent event = (FitEvent) record;
if (event.getEvent() == null) { if (event.getEvent() == null) {
@ -276,6 +287,7 @@ public class FitImporter {
case MONITOR: case MONITOR:
persistActivitySamples(); persistActivitySamples();
persistSpo2Samples(); persistSpo2Samples();
persistRespiratoryRateSamples();
persistStressSamples(); persistStressSamples();
persistBodyEnergySamples(); persistBodyEnergySamples();
break; break;
@ -338,6 +350,7 @@ public class FitImporter {
stressSamples.clear(); stressSamples.clear();
bodyEnergySamples.clear(); bodyEnergySamples.clear();
spo2samples.clear(); spo2samples.clear();
respiratoryRateSamples.clear();
events.clear(); events.clear();
sleepStageSamples.clear(); sleepStageSamples.clear();
hrvSummarySamples.clear(); hrvSummarySamples.clear();
@ -645,7 +658,7 @@ public class FitImporter {
return; return;
} }
LOG.debug("Will persist {} spo2 samples", stressSamples.size()); LOG.debug("Will persist {} spo2 samples", spo2samples.size());
try (DBHandler handler = GBApplication.acquireDB()) { try (DBHandler handler = GBApplication.acquireDB()) {
final DaoSession session = handler.getDaoSession(); 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() { private void persistStressSamples() {
if (stressSamples.isEmpty()) { if (stressSamples.isEmpty()) {
return; return;

View File

@ -323,7 +323,7 @@ public class GlobalFITMessage {
)); ));
public static GlobalFITMessage RESPIRATION_RATE = new GlobalFITMessage(297, "RESPIRATION_RATE", Arrays.asList( 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) new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
)); ));

View File

@ -21,8 +21,8 @@ public class FitRespirationRate extends RecordData {
} }
@Nullable @Nullable
public Integer getRespirationRate() { public Float getRespirationRate() {
return (Integer) getFieldByNumber(0); return (Float) getFieldByNumber(0);
} }
@Nullable @Nullable

View File

@ -94,7 +94,7 @@ public class FetchSleepRespiratoryRateOperation extends AbstractRepeatingFetchOp
final User user = DBHelper.getUser(session); final User user = DBHelper.getUser(session);
final HuamiCoordinator coordinator = (HuamiCoordinator) getDevice().getDeviceCoordinator(); 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) { for (final HuamiSleepRespiratoryRateSample sample : samples) {
sample.setDevice(device); sample.setDevice(device);