From 57d6398c50380b2457affbdd89f79cb3b7c89af1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Sat, 2 Aug 2025 21:58:22 +0100 Subject: [PATCH] Garmin: Parse max met data and training load --- .../gadgetbridge/daogen/GBDaoGenerator.java | 18 +- .../devices/AbstractDeviceCoordinator.java | 10 + .../devices/DeviceCoordinator.java | 2 + ...enericTrainingLoadAcuteSampleProvider.java | 56 +++++ ...ericTrainingLoadChronicSampleProvider.java | 56 +++++ .../devices/WorkoutLoadSampleProvider.java | 211 ++++++++++++++++++ .../watches/GarminWatchCoordinator.java | 11 + .../gadgetbridge/model/WorkoutLoadSample.java | 21 ++ .../devices/garmin/fit/FitImporter.java | 28 +++ 9 files changed, 412 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/GenericTrainingLoadAcuteSampleProvider.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/GenericTrainingLoadChronicSampleProvider.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/WorkoutLoadSampleProvider.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/WorkoutLoadSample.java diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java index 4ee37ce86e..f3b30a3b67 100644 --- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java +++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java @@ -58,7 +58,7 @@ public class GBDaoGenerator { public static void main(String[] args) throws Exception { - final Schema schema = new Schema(109, MAIN_PACKAGE + ".entities"); + final Schema schema = new Schema(110, MAIN_PACKAGE + ".entities"); Entity userAttributes = addUserAttributes(schema); Entity user = addUserInfo(schema, userAttributes); @@ -209,6 +209,8 @@ public class GBDaoGenerator { addGenericHrvValueSample(schema, user, device); addGenericTemperatureSample(schema, user, device); addGenericSleepStageSample(schema, user, device); + addGenericTrainingLoadAcuteSample(schema, user, device); + addGenericTrainingLoadChronicSample(schema, user, device); new DaoGenerator().generateAll(schema, "app/src/main/java"); } @@ -1948,4 +1950,18 @@ public class GBDaoGenerator { sleepStageSample.addIntProperty("stage").notNull(); return sleepStageSample; } + + private static Entity addGenericTrainingLoadAcuteSample(Schema schema, Entity user, Entity device) { + Entity sample = addEntity(schema, "GenericTrainingLoadAcuteSample"); + addCommonTimeSampleProperties("AbstractTimeSample", sample, user, device); + sample.addIntProperty("value").notNull(); + return sample; + } + + private static Entity addGenericTrainingLoadChronicSample(Schema schema, Entity user, Entity device) { + Entity sample = addEntity(schema, "GenericTrainingLoadChronicSample"); + addCommonTimeSampleProperties("AbstractTimeSample", sample, user, device); + sample.addIntProperty("value").notNull(); + return sample; + } } 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 b97f691121..15e52d0295 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java @@ -592,6 +592,16 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator { return false; } + @Override + public boolean supportsTrainingLoad() { + return false; + } + + @Override + public boolean supportsWorkoutLoad() { + return false; + } + @Override public boolean supportsActivityTabs() { return supportsActivityTracking(); 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 c695e9f691..b7c20c87d9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java @@ -257,6 +257,8 @@ public interface DeviceCoordinator { boolean supportsSpeedzones(); boolean supportsActivityTabs(); boolean supportsActiveCalories(); + boolean supportsTrainingLoad(); + boolean supportsWorkoutLoad(); /** * Returns true if measurement and fetching of body temperature is supported by the device diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/GenericTrainingLoadAcuteSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/GenericTrainingLoadAcuteSampleProvider.java new file mode 100644 index 0000000000..8df34ad6f2 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/GenericTrainingLoadAcuteSampleProvider.java @@ -0,0 +1,56 @@ +/* Copyright (C) 2025 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; + +import androidx.annotation.NonNull; + +import de.greenrobot.dao.AbstractDao; +import de.greenrobot.dao.Property; +import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.entities.GenericTrainingLoadAcuteSample; +import nodomain.freeyourgadget.gadgetbridge.entities.GenericTrainingLoadAcuteSampleDao; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; + +public class GenericTrainingLoadAcuteSampleProvider extends AbstractTimeSampleProvider { + public GenericTrainingLoadAcuteSampleProvider(final GBDevice device, final DaoSession session) { + super(device, session); + } + + @NonNull + @Override + public AbstractDao getSampleDao() { + return getSession().getGenericTrainingLoadAcuteSampleDao(); + } + + @NonNull + @Override + protected Property getTimestampSampleProperty() { + return GenericTrainingLoadAcuteSampleDao.Properties.Timestamp; + } + + @NonNull + @Override + protected Property getDeviceIdentifierSampleProperty() { + return GenericTrainingLoadAcuteSampleDao.Properties.DeviceId; + } + + @Override + public GenericTrainingLoadAcuteSample createSample() { + return new GenericTrainingLoadAcuteSample(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/GenericTrainingLoadChronicSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/GenericTrainingLoadChronicSampleProvider.java new file mode 100644 index 0000000000..17648535da --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/GenericTrainingLoadChronicSampleProvider.java @@ -0,0 +1,56 @@ +/* Copyright (C) 2025 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; + +import androidx.annotation.NonNull; + +import de.greenrobot.dao.AbstractDao; +import de.greenrobot.dao.Property; +import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.entities.GenericTrainingLoadChronicSample; +import nodomain.freeyourgadget.gadgetbridge.entities.GenericTrainingLoadChronicSampleDao; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; + +public class GenericTrainingLoadChronicSampleProvider extends AbstractTimeSampleProvider { + public GenericTrainingLoadChronicSampleProvider(final GBDevice device, final DaoSession session) { + super(device, session); + } + + @NonNull + @Override + public AbstractDao getSampleDao() { + return getSession().getGenericTrainingLoadChronicSampleDao(); + } + + @NonNull + @Override + protected Property getTimestampSampleProperty() { + return GenericTrainingLoadChronicSampleDao.Properties.Timestamp; + } + + @NonNull + @Override + protected Property getDeviceIdentifierSampleProperty() { + return GenericTrainingLoadChronicSampleDao.Properties.DeviceId; + } + + @Override + public GenericTrainingLoadChronicSample createSample() { + return new GenericTrainingLoadChronicSample(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/WorkoutLoadSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/WorkoutLoadSampleProvider.java new file mode 100644 index 0000000000..44a2a4fb25 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/WorkoutLoadSampleProvider.java @@ -0,0 +1,211 @@ +/* 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; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import de.greenrobot.dao.query.QueryBuilder; +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; +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.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryData; +import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries; +import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser; +import nodomain.freeyourgadget.gadgetbridge.model.WorkoutLoadSample; + +/** + * An implementation of {@link WorkoutLoadSampleProvider} that extracts VO2 Max values from + * workouts, for devices that provide them as part of the workout data. + */ +public class WorkoutLoadSampleProvider implements TimeSampleProvider { + private static final Logger LOG = LoggerFactory.getLogger(WorkoutLoadSampleProvider.class); + + private final GBDevice device; + private final DaoSession session; + + public WorkoutLoadSampleProvider(final GBDevice device, final DaoSession session) { + this.device = device; + this.session = session; + } + + @NonNull + @Override + public List getAllSamples(final long timestampFrom, final long timestampTo) { + final BaseActivitySummaryDao summaryDao = session.getBaseActivitySummaryDao(); + final Device dbDevice = DBHelper.findDevice(device, session); + if (dbDevice == null) { + // no device, no samples + return Collections.emptyList(); + } + + final DeviceCoordinator coordinator = device.getDeviceCoordinator(); + + final QueryBuilder qb = summaryDao.queryBuilder(); + qb.where(BaseActivitySummaryDao.Properties.DeviceId.eq(dbDevice.getId())) + .where(BaseActivitySummaryDao.Properties.StartTime.gt(new Date(timestampFrom))) + .where(BaseActivitySummaryDao.Properties.StartTime.lt(new Date(timestampTo))) + .orderAsc(BaseActivitySummaryDao.Properties.StartTime); + + final List samples = qb.build().list(); + summaryDao.detachAll(); + fillSummaryData(coordinator, samples); + + return samples.stream() + .map(GarminWorkoutLoadSample::fromActivitySummary) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + @Override + public void addSample(final WorkoutLoadSample timeSample) { + throw new UnsupportedOperationException("Read-only sample provider"); + } + + @Override + public void addSamples(final List timeSamples) { + throw new UnsupportedOperationException("Read-only sample provider"); + } + + @Override + public WorkoutLoadSample createSample() { + throw new UnsupportedOperationException("Read-only sample provider"); + } + + @Nullable + @Override + public WorkoutLoadSample getLatestSample(final long until) { + final BaseActivitySummaryDao summaryDao = session.getBaseActivitySummaryDao(); + final Device dbDevice = DBHelper.findDevice(device, session); + if (dbDevice == null) { + // no device, no samples + return null; + } + + final DeviceCoordinator coordinator = device.getDeviceCoordinator(); + final QueryBuilder qb = summaryDao.queryBuilder(); + + if (until != 0) { + qb.where(BaseActivitySummaryDao.Properties.StartTime.le(new Date(until))); + } + + qb.where(BaseActivitySummaryDao.Properties.DeviceId.eq(dbDevice.getId())) + .orderDesc(BaseActivitySummaryDao.Properties.StartTime) + .limit(1); + + final List samples = qb.build().list(); + summaryDao.detachAll(); + fillSummaryData(coordinator, samples); + + return !samples.isEmpty() ? GarminWorkoutLoadSample.fromActivitySummary(samples.get(0)) : null; + } + + private void fillSummaryData(final DeviceCoordinator coordinator, + final Collection summaries) { + ActivitySummaryParser activitySummaryParser = coordinator.getActivitySummaryParser(device, GBApplication.getContext()); + for (final BaseActivitySummary summary : summaries) { + if (summary.getSummaryData() == null) { + activitySummaryParser.parseBinaryData(summary, true); + } + } + } + + @Nullable + @Override + public WorkoutLoadSample getLatestSample() { + return getLatestSample(0); + } + + @Nullable + @Override + public WorkoutLoadSample getFirstSample() { + final BaseActivitySummaryDao summaryDao = session.getBaseActivitySummaryDao(); + final Device dbDevice = DBHelper.findDevice(device, session); + if (dbDevice == null) { + // no device, no samples + return null; + } + + final QueryBuilder qb = summaryDao.queryBuilder(); + qb.where(BaseActivitySummaryDao.Properties.DeviceId.eq(dbDevice.getId())) + .orderAsc(BaseActivitySummaryDao.Properties.StartTime) + .limit(1); + + final List samples = qb.build().list(); + summaryDao.detachAll(); + + return !samples.isEmpty() ? GarminWorkoutLoadSample.fromActivitySummary(samples.get(0)) : null; + } + + public static class GarminWorkoutLoadSample implements WorkoutLoadSample { + private final long timestamp; + private final int value; + + public GarminWorkoutLoadSample(final long timestamp, final int value) { + this.timestamp = timestamp; + this.value = value; + } + + @Override + public long getTimestamp() { + return timestamp; + } + + @Override + public int getValue() { + return value; + } + + @Nullable + public static GarminWorkoutLoadSample fromActivitySummary(final BaseActivitySummary summary) { + final String summaryDataJson = summary.getSummaryData(); + if (summaryDataJson == null) { + return null; + } + + if (!summaryDataJson.contains(ActivitySummaryEntries.TRAINING_LOAD)) { + return null; + } + + final ActivitySummaryData summaryData = ActivitySummaryData.fromJson(summaryDataJson); + if (summaryData == null) { + return null; + } + + final int value = summaryData.getNumber(ActivitySummaryEntries.TRAINING_LOAD, 0).intValue(); + if (value == 0) { + return null; + } + + return new GarminWorkoutLoadSample(summary.getStartTime().getTime(), value); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/GarminWatchCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/GarminWatchCoordinator.java index ea5ed6fc10..12fd280e31 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/GarminWatchCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/watches/GarminWatchCoordinator.java @@ -145,6 +145,17 @@ public abstract class GarminWatchCoordinator extends GarminCoordinator { return 150; } + @Override + public boolean supportsTrainingLoad() { + // TODO: Not all devices support it + return false; + } + + @Override + public boolean supportsWorkoutLoad() { + return true; + } + @Override public boolean supportsFindDevice() { return true; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/WorkoutLoadSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/WorkoutLoadSample.java new file mode 100644 index 0000000000..09e1915263 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/WorkoutLoadSample.java @@ -0,0 +1,21 @@ +/* Copyright (C) 2025 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.model; + +public interface WorkoutLoadSample extends TimeSample { + int getValue(); +} 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 2bf9e47c38..cb8d572177 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 @@ -23,6 +23,8 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractTimeSampleProvider; +import nodomain.freeyourgadget.gadgetbridge.devices.GenericTrainingLoadAcuteSampleProvider; +import nodomain.freeyourgadget.gadgetbridge.devices.GenericTrainingLoadChronicSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminActivitySampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminBodyEnergySampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminEventSampleProvider; @@ -56,6 +58,8 @@ import nodomain.freeyourgadget.gadgetbridge.entities.GarminSleepStageSample; import nodomain.freeyourgadget.gadgetbridge.entities.GarminSleepStatsSample; import nodomain.freeyourgadget.gadgetbridge.entities.GarminSpo2Sample; import nodomain.freeyourgadget.gadgetbridge.entities.GarminStressSample; +import nodomain.freeyourgadget.gadgetbridge.entities.GenericTrainingLoadAcuteSample; +import nodomain.freeyourgadget.gadgetbridge.entities.GenericTrainingLoadChronicSample; import nodomain.freeyourgadget.gadgetbridge.entities.User; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; @@ -85,6 +89,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages. import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitSport; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitStressLevel; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitTimeInZone; +import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitTrainingLoad; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitUserProfile; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; @@ -108,6 +113,8 @@ public class FitImporter { private final List hrvSummarySamples = new ArrayList<>(); private final List hrvValueSamples = new ArrayList<>(); private final List restingMetabolicRateSamples = new ArrayList<>(); + private final List trainingLoadAcuteSamples = new ArrayList<>(); + private final List trainingLoadChronicSamples = new ArrayList<>(); private final Map unknownRecords = new HashMap<>(); private FitSleepDataInfo fitSleepDataInfo = null; private final List fitSleepDataRawSamples = new ArrayList<>(); @@ -318,6 +325,21 @@ public class FitImporter { sample.setTimestamp(ts * 1000L); sample.setRestingMetabolicRate(monitoringInfo.getRestingMetabolicRate()); restingMetabolicRateSamples.add(sample); + } else if (record instanceof FitTrainingLoad) { + final FitTrainingLoad trainingLoad = (FitTrainingLoad) record; + LOG.trace("Training load at {}: {}", ts, record); + if (trainingLoad.getTrainingLoadAcute() != null) { + final GenericTrainingLoadAcuteSample sample = new GenericTrainingLoadAcuteSample(); + sample.setTimestamp(ts * 1000L); + sample.setValue(trainingLoad.getTrainingLoadAcute()); + trainingLoadAcuteSamples.add(sample); + } + if (trainingLoad.getTrainingLoadChronic() != null) { + final GenericTrainingLoadChronicSample sample = new GenericTrainingLoadChronicSample(); + sample.setTimestamp(ts * 1000L); + sample.setValue(trainingLoad.getTrainingLoadChronic()); + trainingLoadChronicSamples.add(sample); + } } else if (record instanceof FitMonitoringHrData) { final FitMonitoringHrData monitoringHrData = (FitMonitoringHrData) record; if (monitoringHrData.getRestingHeartRate() == null) { @@ -396,6 +418,10 @@ public class FitImporter { persistAbstractSamples(bodyEnergySamples, new GarminBodyEnergySampleProvider(gbDevice, session)); persistAbstractSamples(restingMetabolicRateSamples, new GarminRestingMetabolicRateSampleProvider(gbDevice, session)); break; + case METRICS: + persistAbstractSamples(trainingLoadAcuteSamples, new GenericTrainingLoadAcuteSampleProvider(gbDevice, session)); + persistAbstractSamples(trainingLoadChronicSamples, new GenericTrainingLoadChronicSampleProvider(gbDevice, session)); + break; case SLEEP: persistAbstractSamples(events, new GarminEventSampleProvider(gbDevice, session)); persistAbstractSamples(sleepStatsSamples, new GarminSleepStatsSampleProvider(gbDevice, session)); @@ -475,6 +501,8 @@ public class FitImporter { hrvSummarySamples.clear(); hrvValueSamples.clear(); restingMetabolicRateSamples.clear(); + trainingLoadAcuteSamples.clear(); + trainingLoadChronicSamples.clear(); unknownRecords.clear(); fitSleepDataInfo = null; fitSleepDataRawSamples.clear();