mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2025-01-10 17:11:56 +01:00
Garmin: Add resting heart rate sample provider
This commit is contained in:
parent
b331c53763
commit
fb1d0a92cc
@ -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(81, MAIN_PACKAGE + ".entities");
|
final Schema schema = new Schema(82, MAIN_PACKAGE + ".entities");
|
||||||
|
|
||||||
Entity userAttributes = addUserAttributes(schema);
|
Entity userAttributes = addUserAttributes(schema);
|
||||||
Entity user = addUserInfo(schema, userAttributes);
|
Entity user = addUserInfo(schema, userAttributes);
|
||||||
@ -118,6 +118,7 @@ public class GBDaoGenerator {
|
|||||||
addGarminHrvSummarySample(schema, user, device);
|
addGarminHrvSummarySample(schema, user, device);
|
||||||
addGarminHrvValueSample(schema, user, device);
|
addGarminHrvValueSample(schema, user, device);
|
||||||
addGarminRespiratoryRateSample(schema, user, device);
|
addGarminRespiratoryRateSample(schema, user, device);
|
||||||
|
addGarminHeartRateRestingSample(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);
|
||||||
@ -828,6 +829,13 @@ public class GBDaoGenerator {
|
|||||||
return garminRespiratoryRateSample;
|
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) {
|
private static Entity addPendingFile(Schema schema, Entity user, Entity device) {
|
||||||
Entity pendingFile = addEntity(schema, "PendingFile");
|
Entity pendingFile = addEntity(schema, "PendingFile");
|
||||||
pendingFile.setJavaDoc(
|
pendingFile.setJavaDoc(
|
||||||
|
@ -154,6 +154,11 @@ public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
return new GarminRespiratoryRateSampleProvider(device, session);
|
return new GarminRespiratoryRateSampleProvider(device, session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GarminHeartRateRestingSampleProvider getHeartRateRestingSampleProvider(final GBDevice device, final DaoSession session) {
|
||||||
|
return new GarminHeartRateRestingSampleProvider(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();
|
||||||
|
@ -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.GarminHeartRateRestingSample;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.GarminHeartRateRestingSampleDao;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
|
||||||
|
public class GarminHeartRateRestingSampleProvider extends AbstractTimeSampleProvider<GarminHeartRateRestingSample> {
|
||||||
|
public GarminHeartRateRestingSampleProvider(final GBDevice device, final DaoSession session) {
|
||||||
|
super(device, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public AbstractDao<GarminHeartRateRestingSample, ?> 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();
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,7 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminActivitySampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminActivitySampleProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminBodyEnergySampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminBodyEnergySampleProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminEventSampleProvider;
|
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.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.GarminRespiratoryRateSampleProvider;
|
||||||
@ -35,6 +36,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.entities.GarminActivitySample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.GarminActivitySample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.GarminBodyEnergySample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.GarminBodyEnergySample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.GarminEventSample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.GarminEventSample;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.GarminHeartRateRestingSample;
|
||||||
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.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.FitHrvSummary;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitHrvValue;
|
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.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.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.FitRespirationRate;
|
||||||
@ -78,6 +81,7 @@ public class FitImporter {
|
|||||||
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<GarminRespiratoryRateSample> respiratoryRateSamples = new ArrayList<>();
|
||||||
|
private final List<GarminHeartRateRestingSample> restingHrSamples = 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<>();
|
||||||
@ -95,7 +99,9 @@ public class FitImporter {
|
|||||||
this.workoutParser = new GarminWorkoutParser(context);
|
this.workoutParser = new GarminWorkoutParser(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @noinspection StatementWithEmptyBody*/
|
/**
|
||||||
|
* @noinspection StatementWithEmptyBody
|
||||||
|
*/
|
||||||
public void importFile(final File file) throws IOException {
|
public void importFile(final File file) throws IOException {
|
||||||
reset();
|
reset();
|
||||||
|
|
||||||
@ -258,6 +264,17 @@ public class FitImporter {
|
|||||||
sample.setTimestamp(ts * 1000L);
|
sample.setTimestamp(ts * 1000L);
|
||||||
sample.setValue(Math.round(hrvValue.getValue()));
|
sample.setValue(Math.round(hrvValue.getValue()));
|
||||||
hrvValueSamples.add(sample);
|
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 {
|
} else {
|
||||||
LOG.trace("Unknown record: {}", record);
|
LOG.trace("Unknown record: {}", record);
|
||||||
|
|
||||||
@ -288,6 +305,7 @@ public class FitImporter {
|
|||||||
persistActivitySamples();
|
persistActivitySamples();
|
||||||
persistSpo2Samples();
|
persistSpo2Samples();
|
||||||
persistRespiratoryRateSamples();
|
persistRespiratoryRateSamples();
|
||||||
|
persistRestingHrSamples();
|
||||||
persistStressSamples();
|
persistStressSamples();
|
||||||
persistBodyEnergySamples();
|
persistBodyEnergySamples();
|
||||||
break;
|
break;
|
||||||
@ -351,6 +369,7 @@ public class FitImporter {
|
|||||||
bodyEnergySamples.clear();
|
bodyEnergySamples.clear();
|
||||||
spo2samples.clear();
|
spo2samples.clear();
|
||||||
respiratoryRateSamples.clear();
|
respiratoryRateSamples.clear();
|
||||||
|
restingHrSamples.clear();
|
||||||
events.clear();
|
events.clear();
|
||||||
sleepStageSamples.clear();
|
sleepStageSamples.clear();
|
||||||
hrvSummarySamples.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() {
|
private void persistStressSamples() {
|
||||||
if (stressSamples.isEmpty()) {
|
if (stressSamples.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
|
@ -269,6 +269,12 @@ public class GlobalFITMessage {
|
|||||||
new FieldDefinitionPrimitive(3, BaseType.UINT8, "developer_data_index")
|
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(
|
public static GlobalFITMessage TIME_IN_ZONE = new GlobalFITMessage(216, "TIME_IN_ZONE", Arrays.asList(
|
||||||
new FieldDefinitionPrimitive(0, BaseType.UINT16, "reference_message"),
|
new FieldDefinitionPrimitive(0, BaseType.UINT16, "reference_message"),
|
||||||
new FieldDefinitionPrimitive(1, BaseType.UINT16, "reference_index"),
|
new FieldDefinitionPrimitive(1, BaseType.UINT16, "reference_index"),
|
||||||
@ -388,6 +394,7 @@ public class GlobalFITMessage {
|
|||||||
put(162, TIMESTAMP_CORRELATION);
|
put(162, TIMESTAMP_CORRELATION);
|
||||||
put(206, FIELD_DESCRIPTION);
|
put(206, FIELD_DESCRIPTION);
|
||||||
put(207, DEVELOPER_DATA);
|
put(207, DEVELOPER_DATA);
|
||||||
|
put(211, MONITORING_HR_DATA);
|
||||||
put(216, TIME_IN_ZONE);
|
put(216, TIME_IN_ZONE);
|
||||||
put(222, ALARM_SETTINGS);
|
put(222, ALARM_SETTINGS);
|
||||||
put(225, SET);
|
put(225, SET);
|
||||||
@ -403,6 +410,7 @@ public class GlobalFITMessage {
|
|||||||
put(397, SKIN_TEMP_RAW);
|
put(397, SKIN_TEMP_RAW);
|
||||||
put(398, SKIN_TEMP_OVERNIGHT);
|
put(398, SKIN_TEMP_OVERNIGHT);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
private final int number;
|
private final int number;
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -61,6 +61,8 @@ public class FitRecordDataFactory {
|
|||||||
return new FitFieldDescription(recordDefinition, recordHeader);
|
return new FitFieldDescription(recordDefinition, recordHeader);
|
||||||
case 207:
|
case 207:
|
||||||
return new FitDeveloperData(recordDefinition, recordHeader);
|
return new FitDeveloperData(recordDefinition, recordHeader);
|
||||||
|
case 211:
|
||||||
|
return new FitMonitoringHrData(recordDefinition, recordHeader);
|
||||||
case 216:
|
case 216:
|
||||||
return new FitTimeInZone(recordDefinition, recordHeader);
|
return new FitTimeInZone(recordDefinition, recordHeader);
|
||||||
case 222:
|
case 222:
|
||||||
|
Loading…
Reference in New Issue
Block a user