mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2025-01-10 17:11:56 +01:00
Garmin: Persist sleep score
This commit is contained in:
parent
5531ddc49a
commit
c52fd53ebd
@ -54,7 +54,7 @@ public class GBDaoGenerator {
|
||||
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
final Schema schema = new Schema(87, MAIN_PACKAGE + ".entities");
|
||||
final Schema schema = new Schema(88, MAIN_PACKAGE + ".entities");
|
||||
|
||||
Entity userAttributes = addUserAttributes(schema);
|
||||
Entity user = addUserInfo(schema, userAttributes);
|
||||
@ -128,6 +128,7 @@ public class GBDaoGenerator {
|
||||
addGarminRespiratoryRateSample(schema, user, device);
|
||||
addGarminHeartRateRestingSample(schema, user, device);
|
||||
addGarminRestingMetabolicRateSample(schema, user, device);
|
||||
addGarminSleepStatsSample(schema, user, device);
|
||||
addPendingFile(schema, user, device);
|
||||
addWena3EnergySample(schema, user, device);
|
||||
addWena3BehaviorSample(schema, user, device);
|
||||
@ -879,6 +880,14 @@ public class GBDaoGenerator {
|
||||
return sample;
|
||||
}
|
||||
|
||||
private static Entity addGarminSleepStatsSample(Schema schema, Entity user, Entity device) {
|
||||
Entity sample = addEntity(schema, "GarminSleepStatsSample");
|
||||
sample.addImport(MAIN_PACKAGE + ".model.SleepScoreSample");
|
||||
addCommonTimeSampleProperties("SleepScoreSample", sample, user, device);
|
||||
sample.addIntProperty("sleepScore").notNull().codeBeforeGetter(OVERRIDE);
|
||||
return sample;
|
||||
}
|
||||
|
||||
private static Entity addPendingFile(Schema schema, Entity user, Entity device) {
|
||||
Entity pendingFile = addEntity(schema, "PendingFile");
|
||||
pendingFile.setJavaDoc(
|
||||
|
@ -78,6 +78,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.PaiSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.RespiratoryRateSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.RestingMetabolicRateSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.SleepScoreSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
|
||||
@ -286,6 +287,11 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
|
||||
return new DefaultRestingMetabolicRateProvider(device, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeSampleProvider<? extends SleepScoreSample> getSleepScoreProvider(final GBDevice device, final DaoSession session) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public ActivitySummaryParser getActivitySummaryParser(final GBDevice device, final Context context) {
|
||||
@ -684,6 +690,11 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSleepScore() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsWeather() {
|
||||
return false;
|
||||
|
@ -25,6 +25,8 @@ import java.util.List;
|
||||
import de.greenrobot.dao.AbstractDao;
|
||||
import de.greenrobot.dao.Property;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.RestingMetabolicRateSample;
|
||||
@ -170,6 +172,16 @@ public class DefaultRestingMetabolicRateProvider extends AbstractTimeSampleProvi
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDevice(final Device device) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUser(final User user) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
|
@ -58,6 +58,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.PaiSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.RespiratoryRateSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.RestingMetabolicRateSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.SleepScoreSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
|
||||
@ -376,6 +377,8 @@ public interface DeviceCoordinator {
|
||||
|
||||
TimeSampleProvider<? extends RestingMetabolicRateSample> getRestingMetabolicRateProvider(GBDevice device, DaoSession session);
|
||||
|
||||
TimeSampleProvider<? extends SleepScoreSample> getSleepScoreProvider(GBDevice device, DaoSession session);
|
||||
|
||||
/**
|
||||
* Returns the {@link ActivitySummaryParser} for the device being supported.
|
||||
*
|
||||
@ -574,6 +577,11 @@ public interface DeviceCoordinator {
|
||||
*/
|
||||
boolean supportsAwakeSleep();
|
||||
|
||||
/**
|
||||
* Indicates whether the device supports determining a sleep score in a 0-100 range.
|
||||
*/
|
||||
boolean supportsSleepScore();
|
||||
|
||||
/**
|
||||
* Indicates whether the device supports current weather and/or weather
|
||||
* forecast display.
|
||||
|
@ -46,6 +46,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.HrvSummarySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.RespiratoryRateSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.RestingMetabolicRateSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.SleepScoreSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Vo2MaxSample;
|
||||
@ -159,6 +160,11 @@ public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
return new GarminRestingMetabolicRateSampleProvider(device, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeSampleProvider<? extends SleepScoreSample> getSleepScoreProvider(final GBDevice device, final DaoSession session) {
|
||||
return new GarminSleepStatsSampleProvider(device, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GarminHeartRateRestingSampleProvider getHeartRateRestingSampleProvider(final GBDevice device, final DaoSession session) {
|
||||
return new GarminHeartRateRestingSampleProvider(device, session);
|
||||
@ -294,6 +300,11 @@ public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSleepScore() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRespiratoryRate() {
|
||||
return true;
|
||||
|
@ -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.GarminSleepStatsSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.GarminSleepStatsSampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
|
||||
public class GarminSleepStatsSampleProvider extends AbstractTimeSampleProvider<GarminSleepStatsSample> {
|
||||
public GarminSleepStatsSampleProvider(final GBDevice device, final DaoSession session) {
|
||||
super(device, session);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public AbstractDao<GarminSleepStatsSample, ?> getSampleDao() {
|
||||
return getSession().getGarminSleepStatsSampleDao();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getTimestampSampleProperty() {
|
||||
return GarminSleepStatsSampleDao.Properties.Timestamp;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getDeviceIdentifierSampleProperty() {
|
||||
return GarminSleepStatsSampleDao.Properties.DeviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GarminSleepStatsSample createSample() {
|
||||
return new GarminSleepStatsSample();
|
||||
}
|
||||
}
|
@ -35,6 +35,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
|
||||
public class HuaweiSpo2SampleProvider extends AbstractTimeSampleProvider<HuaweiSpo2SampleProvider.HuaweiSpo2Sample> {
|
||||
@ -206,6 +207,16 @@ public class HuaweiSpo2SampleProvider extends AbstractTimeSampleProvider<HuaweiS
|
||||
this.deviceId = deviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDevice(final Device device) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUser(final User user) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSpo2() {
|
||||
return spo2;
|
||||
|
@ -33,6 +33,10 @@ public abstract class AbstractTimeSample implements TimeSample {
|
||||
|
||||
public abstract void setDeviceId(long deviceId);
|
||||
|
||||
public abstract void setDevice(Device device);
|
||||
|
||||
public abstract void setUser(User user);
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
|
@ -0,0 +1,37 @@
|
||||
/* 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.model;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractTimeSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
|
||||
public abstract class SleepScoreSample extends AbstractTimeSample {
|
||||
public abstract int getSleepScore();
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "{" +
|
||||
"timestamp=" + DateTimeUtils.formatDateTime(DateTimeUtils.parseTimestampMillis(getTimestamp())) +
|
||||
", userId=" + getUserId() +
|
||||
", deviceId=" + getDeviceId() +
|
||||
", sleepScore=" + getSleepScore() +
|
||||
"}";
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ import java.util.TreeMap;
|
||||
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.garmin.GarminActivitySampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminBodyEnergySampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminEventSampleProvider;
|
||||
@ -27,10 +28,12 @@ import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminHrvSummarySampl
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminHrvValueSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminRespiratoryRateSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminRestingMetabolicRateSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminSleepStatsSampleProvider;
|
||||
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.AbstractTimeSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
@ -43,6 +46,7 @@ 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.GarminSleepStatsSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.GarminSpo2Sample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.GarminStressSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
@ -50,7 +54,6 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.RestingMetabolicRateSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.FileType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionHrvStatus;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionSleepStage;
|
||||
@ -68,6 +71,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.
|
||||
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.FitSleepStage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitSleepStats;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitSpo2;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitSport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitStressLevel;
|
||||
@ -87,6 +91,7 @@ public class FitImporter {
|
||||
private final List<GarminRespiratoryRateSample> respiratoryRateSamples = new ArrayList<>();
|
||||
private final List<GarminHeartRateRestingSample> restingHrSamples = new ArrayList<>();
|
||||
private final List<GarminEventSample> events = new ArrayList<>();
|
||||
private final List<GarminSleepStatsSample> sleepStatsSamples = new ArrayList<>();
|
||||
private final List<GarminSleepStageSample> sleepStageSamples = new ArrayList<>();
|
||||
private final List<GarminHrvSummarySample> hrvSummarySamples = new ArrayList<>();
|
||||
private final List<GarminHrvValueSample> hrvValueSamples = new ArrayList<>();
|
||||
@ -162,6 +167,16 @@ public class FitImporter {
|
||||
final FitSleepDataRaw fitSleepDataRaw = (FitSleepDataRaw) record;
|
||||
//LOG.debug("Sleep Data Raw: {}", fitSleepDataRaw);
|
||||
fitSleepDataRawSamples.add(fitSleepDataRaw);
|
||||
} else if (record instanceof FitSleepStats) {
|
||||
final Integer score = ((FitSleepStats) record).getOverallSleepScore();
|
||||
if (score == null) {
|
||||
continue;
|
||||
}
|
||||
LOG.trace("Sleep stats at {}: {}", ts, record);
|
||||
final GarminSleepStatsSample sample = new GarminSleepStatsSample();
|
||||
sample.setTimestamp(ts * 1000L);
|
||||
sample.setSleepScore(score);
|
||||
sleepStatsSamples.add(sample);
|
||||
} else if (record instanceof FitSleepStage) {
|
||||
final FieldDefinitionSleepStage.SleepStage stage = ((FitSleepStage) record).getSleepStage();
|
||||
if (stage == null) {
|
||||
@ -312,45 +327,59 @@ public class FitImporter {
|
||||
return;
|
||||
}
|
||||
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
final DaoSession session = handler.getDaoSession();
|
||||
|
||||
switch (fileId.getType()) {
|
||||
case ACTIVITY:
|
||||
persistWorkout(file);
|
||||
persistWorkout(file, session);
|
||||
break;
|
||||
case MONITOR:
|
||||
persistActivitySamples();
|
||||
persistSpo2Samples();
|
||||
persistRespiratoryRateSamples();
|
||||
persistRestingHrSamples();
|
||||
persistStressSamples();
|
||||
persistBodyEnergySamples();
|
||||
persistRestingMetabolicRateSamples();
|
||||
persistActivitySamples(session);
|
||||
persistAbstractSamples(spo2samples, new GarminSpo2SampleProvider(gbDevice, session));
|
||||
persistAbstractSamples(respiratoryRateSamples, new GarminRespiratoryRateSampleProvider(gbDevice, session));
|
||||
persistAbstractSamples(restingHrSamples, new GarminHeartRateRestingSampleProvider(gbDevice, session));
|
||||
persistAbstractSamples(stressSamples, new GarminStressSampleProvider(gbDevice, session));
|
||||
persistAbstractSamples(bodyEnergySamples, new GarminBodyEnergySampleProvider(gbDevice, session));
|
||||
persistAbstractSamples(restingMetabolicRateSamples, new GarminRestingMetabolicRateSampleProvider(gbDevice, session));
|
||||
break;
|
||||
case SLEEP:
|
||||
persistEvents();
|
||||
persistSleepStageSamples();
|
||||
processRawSleepSamples();
|
||||
persistAbstractSamples(events, new GarminEventSampleProvider(gbDevice, session));
|
||||
persistAbstractSamples(sleepStatsSamples, new GarminSleepStatsSampleProvider(gbDevice, session));
|
||||
|
||||
// We may have samples, but not sleep samples - #4048
|
||||
// 0 unmeasurable, 1 awake
|
||||
final boolean anySleepSample = sleepStageSamples.stream()
|
||||
.anyMatch(s -> s.getStage() != 0 && s.getStage() != 1);
|
||||
if (anySleepSample) {
|
||||
persistAbstractSamples(sleepStageSamples, new GarminSleepStageSampleProvider(gbDevice, session));
|
||||
}
|
||||
|
||||
processRawSleepSamples(session);
|
||||
break;
|
||||
case HRV_STATUS:
|
||||
persistHrvSummarySamples();
|
||||
persistHrvValueSamples();
|
||||
persistAbstractSamples(hrvSummarySamples, new GarminHrvSummarySampleProvider(gbDevice, session));
|
||||
persistAbstractSamples(hrvValueSamples, new GarminHrvValueSampleProvider(gbDevice, session));
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unable to handle fit file of type {}", fileId.getType());
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
GB.toast(context, "Error saving samples", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
}
|
||||
|
||||
for (final Map.Entry<Integer, Integer> e : unknownRecords.entrySet()) {
|
||||
LOG.warn("Unknown record of global number {} seen {} times", e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void persistWorkout(final File file) {
|
||||
private void persistWorkout(final File file, final DaoSession session) {
|
||||
LOG.debug("Persisting workout for {}", fileId);
|
||||
|
||||
final BaseActivitySummary summary;
|
||||
|
||||
// This ensures idempotency when re-processing
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
final DaoSession session = dbHandler.getDaoSession();
|
||||
try {
|
||||
summary = ActivitySummaryParser.findOrCreateBaseActivitySummary(
|
||||
session,
|
||||
gbDevice,
|
||||
@ -365,8 +394,7 @@ public class FitImporter {
|
||||
|
||||
summary.setRawDetailsPath(file.getAbsolutePath());
|
||||
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
final DaoSession session = dbHandler.getDaoSession();
|
||||
try {
|
||||
final Device device = DBHelper.getDevice(gbDevice, session);
|
||||
final User user = DBHelper.getUser(session);
|
||||
|
||||
@ -387,6 +415,7 @@ public class FitImporter {
|
||||
respiratoryRateSamples.clear();
|
||||
restingHrSamples.clear();
|
||||
events.clear();
|
||||
sleepStatsSamples.clear();
|
||||
sleepStageSamples.clear();
|
||||
hrvSummarySamples.clear();
|
||||
hrvValueSamples.clear();
|
||||
@ -398,7 +427,7 @@ public class FitImporter {
|
||||
workoutParser.reset();
|
||||
}
|
||||
|
||||
private void persistActivitySamples() {
|
||||
private void persistActivitySamples(final DaoSession session) {
|
||||
if (activitySamplesPerTimestamp.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@ -507,9 +536,7 @@ public class FitImporter {
|
||||
|
||||
LOG.debug("Will persist {} activity samples", activitySamples.size());
|
||||
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
final DaoSession session = handler.getDaoSession();
|
||||
|
||||
try {
|
||||
final Device device = DBHelper.getDevice(gbDevice, session);
|
||||
final User user = DBHelper.getUser(session);
|
||||
|
||||
@ -526,69 +553,13 @@ public class FitImporter {
|
||||
}
|
||||
}
|
||||
|
||||
private void persistEvents() {
|
||||
if (events.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.debug("Will persist {} event samples", events.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 GarminEventSampleProvider sampleProvider = new GarminEventSampleProvider(gbDevice, session);
|
||||
|
||||
for (final GarminEventSample sample : events) {
|
||||
sample.setDevice(device);
|
||||
sample.setUser(user);
|
||||
}
|
||||
|
||||
sampleProvider.addSamples(events);
|
||||
} catch (final Exception e) {
|
||||
GB.toast(context, "Error saving event samples", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void persistSleepStageSamples() {
|
||||
// We may have samples, but not sleep samples - #4048
|
||||
// 0 unmeasurable, 1 awake
|
||||
final boolean anySleepSample = sleepStageSamples.stream()
|
||||
.anyMatch(s -> s.getStage() != 0 && s.getStage() != 1);
|
||||
if (!anySleepSample) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.debug("Will persist {} sleep stage samples", sleepStageSamples.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 GarminSleepStageSampleProvider sampleProvider = new GarminSleepStageSampleProvider(gbDevice, session);
|
||||
|
||||
for (final GarminSleepStageSample sample : sleepStageSamples) {
|
||||
sample.setDevice(device);
|
||||
sample.setUser(user);
|
||||
}
|
||||
|
||||
sampleProvider.addSamples(sleepStageSamples);
|
||||
} catch (final Exception e) {
|
||||
GB.toast(context, "Error saving sleep stage samples", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* As per #4048, devices that do not have a sleep widget send raw sleep samples, which we do not
|
||||
* know how to parse. Therefore, we don't persist the sleep stages they report (they're all awake),
|
||||
* but we fake light sleep for the duration of the raw sleep samples, in order to have some data
|
||||
* at all.
|
||||
*/
|
||||
private void processRawSleepSamples() {
|
||||
private void processRawSleepSamples(final DaoSession session) {
|
||||
if (fitSleepDataRawSamples.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@ -606,9 +577,7 @@ public class FitImporter {
|
||||
LOG.debug("Got {} raw sleep samples - faking sleep events from {} to {}", fitSleepDataRawSamples.size(), asleepTimeMillis, wakeTimeMillis);
|
||||
|
||||
// We only need to fake sleep start and end times, the sample provider will take care of the rest
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
final DaoSession session = handler.getDaoSession();
|
||||
|
||||
try {
|
||||
final Device device = DBHelper.getDevice(gbDevice, session);
|
||||
final User user = DBHelper.getUser(session);
|
||||
|
||||
@ -637,211 +606,32 @@ public class FitImporter {
|
||||
}
|
||||
}
|
||||
|
||||
private void persistHrvSummarySamples() {
|
||||
if (hrvSummarySamples.isEmpty()) {
|
||||
private <T extends AbstractTimeSample> void persistAbstractSamples(final List<T> samples,
|
||||
final AbstractTimeSampleProvider<T> sampleProvider) {
|
||||
if (samples.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.debug("Will persist {} HRV summary samples", hrvSummarySamples.size());
|
||||
LOG.debug(
|
||||
"Will persist {} {} samples",
|
||||
samples.size(),
|
||||
sampleProvider.getClass().getSimpleName().replace("Garmin", "").replace("SampleProvider", "")
|
||||
);
|
||||
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
final DaoSession session = handler.getDaoSession();
|
||||
try {
|
||||
final DaoSession session = sampleProvider.getSession();
|
||||
|
||||
final Device device = DBHelper.getDevice(gbDevice, session);
|
||||
final User user = DBHelper.getUser(session);
|
||||
|
||||
final GarminHrvSummarySampleProvider sampleProvider = new GarminHrvSummarySampleProvider(gbDevice, session);
|
||||
|
||||
for (final GarminHrvSummarySample sample : hrvSummarySamples) {
|
||||
for (final T sample : samples) {
|
||||
sample.setDevice(device);
|
||||
sample.setUser(user);
|
||||
}
|
||||
|
||||
sampleProvider.addSamples(hrvSummarySamples);
|
||||
sampleProvider.addSamples(samples);
|
||||
} catch (final Exception e) {
|
||||
GB.toast(context, "Error saving HRV summary samples", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void persistHrvValueSamples() {
|
||||
if (hrvValueSamples.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.debug("Will persist {} HRV value samples", hrvValueSamples.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 GarminHrvValueSampleProvider sampleProvider = new GarminHrvValueSampleProvider(gbDevice, session);
|
||||
|
||||
for (final GarminHrvValueSample sample : hrvValueSamples) {
|
||||
sample.setDevice(device);
|
||||
sample.setUser(user);
|
||||
}
|
||||
|
||||
sampleProvider.addSamples(hrvValueSamples);
|
||||
} catch (final Exception e) {
|
||||
GB.toast(context, "Error saving HRV value samples", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void persistSpo2Samples() {
|
||||
if (spo2samples.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.debug("Will persist {} spo2 samples", spo2samples.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 GarminSpo2SampleProvider sampleProvider = new GarminSpo2SampleProvider(gbDevice, session);
|
||||
|
||||
for (final GarminSpo2Sample sample : spo2samples) {
|
||||
sample.setDevice(device);
|
||||
sample.setUser(user);
|
||||
}
|
||||
|
||||
sampleProvider.addSamples(spo2samples);
|
||||
} catch (final Exception e) {
|
||||
GB.toast(context, "Error saving spo2 samples", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
}
|
||||
}
|
||||
|
||||
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 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() {
|
||||
if (stressSamples.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.debug("Will persist {} stress 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 GarminStressSampleProvider sampleProvider = new GarminStressSampleProvider(gbDevice, session);
|
||||
|
||||
for (final GarminStressSample sample : stressSamples) {
|
||||
sample.setDevice(device);
|
||||
sample.setUser(user);
|
||||
}
|
||||
|
||||
sampleProvider.addSamples(stressSamples);
|
||||
} catch (final Exception e) {
|
||||
GB.toast(context, "Error saving stress samples", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void persistBodyEnergySamples() {
|
||||
if (bodyEnergySamples.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.debug("Will persist {} body energy samples", bodyEnergySamples.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 GarminBodyEnergySampleProvider sampleProvider = new GarminBodyEnergySampleProvider(gbDevice, session);
|
||||
|
||||
for (final GarminBodyEnergySample sample : bodyEnergySamples) {
|
||||
sample.setDevice(device);
|
||||
sample.setUser(user);
|
||||
}
|
||||
|
||||
sampleProvider.addSamples(bodyEnergySamples);
|
||||
} catch (final Exception e) {
|
||||
GB.toast(context, "Error saving body energy samples", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void persistRestingMetabolicRateSamples() {
|
||||
if (restingMetabolicRateSamples.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.debug("Will persist {} resting metabolic rate samples", restingMetabolicRateSamples.size());
|
||||
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
final DaoSession session = handler.getDaoSession();
|
||||
|
||||
final Device device = DBHelper.getDevice(gbDevice, session);
|
||||
final User user = DBHelper.getUser(session);
|
||||
|
||||
final GarminRestingMetabolicRateSampleProvider sampleProvider = new GarminRestingMetabolicRateSampleProvider(gbDevice, session);
|
||||
|
||||
for (final GarminRestingMetabolicRateSample sample : restingMetabolicRateSamples) {
|
||||
sample.setDevice(device);
|
||||
sample.setUser(user);
|
||||
}
|
||||
|
||||
sampleProvider.addSamples(restingMetabolicRateSamples);
|
||||
} catch (final Exception e) {
|
||||
GB.toast(context, "Error saving body energy samples", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
GB.toast(context, "Error saving samples", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -363,6 +363,23 @@ public class GlobalFITMessage {
|
||||
));
|
||||
|
||||
public static GlobalFITMessage SLEEP_STATS = new GlobalFITMessage(346, "SLEEP_STATS", Arrays.asList(
|
||||
new FieldDefinitionPrimitive(0, BaseType.UINT8, "combined_awake_score"),
|
||||
new FieldDefinitionPrimitive(1, BaseType.UINT8, "awake_time_score"),
|
||||
new FieldDefinitionPrimitive(2, BaseType.UINT8, "awakenings_count_score"),
|
||||
new FieldDefinitionPrimitive(3, BaseType.UINT8, "deep_sleep_score"),
|
||||
new FieldDefinitionPrimitive(4, BaseType.UINT8, "sleep_duration_score"),
|
||||
new FieldDefinitionPrimitive(5, BaseType.UINT8, "light_sleep_score"),
|
||||
new FieldDefinitionPrimitive(6, BaseType.UINT8, "overall_sleep_score"),
|
||||
new FieldDefinitionPrimitive(7, BaseType.UINT8, "sleep_quality_score"),
|
||||
new FieldDefinitionPrimitive(8, BaseType.UINT8, "sleep_recovery_score"),
|
||||
new FieldDefinitionPrimitive(9, BaseType.UINT8, "rem_sleep_score"),
|
||||
new FieldDefinitionPrimitive(10, BaseType.UINT8, "sleep_restlessness_score"),
|
||||
new FieldDefinitionPrimitive(11, BaseType.UINT8, "awakenings_count"),
|
||||
new FieldDefinitionPrimitive(12, BaseType.ENUM, "unk_12"),
|
||||
new FieldDefinitionPrimitive(13, BaseType.ENUM, "unk_13"),
|
||||
new FieldDefinitionPrimitive(14, BaseType.UINT8, "interruptions_score"),
|
||||
new FieldDefinitionPrimitive(15, BaseType.UINT16, "average_stress_during_sleep", 100, 0),
|
||||
new FieldDefinitionPrimitive(16, BaseType.ENUM, "unk_16")
|
||||
));
|
||||
|
||||
public static GlobalFITMessage HRV_SUMMARY = new GlobalFITMessage(370, "HRV_SUMMARY", Arrays.asList(
|
||||
|
@ -19,4 +19,89 @@ public class FitSleepStats extends RecordData {
|
||||
throw new IllegalArgumentException("FitSleepStats expects global messages of " + 346 + ", got " + globalNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getCombinedAwakeScore() {
|
||||
return (Integer) getFieldByNumber(0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getAwakeTimeScore() {
|
||||
return (Integer) getFieldByNumber(1);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getAwakeningsCountScore() {
|
||||
return (Integer) getFieldByNumber(2);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getDeepSleepScore() {
|
||||
return (Integer) getFieldByNumber(3);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getSleepDurationScore() {
|
||||
return (Integer) getFieldByNumber(4);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getLightSleepScore() {
|
||||
return (Integer) getFieldByNumber(5);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getOverallSleepScore() {
|
||||
return (Integer) getFieldByNumber(6);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getSleepQualityScore() {
|
||||
return (Integer) getFieldByNumber(7);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getSleepRecoveryScore() {
|
||||
return (Integer) getFieldByNumber(8);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getRemSleepScore() {
|
||||
return (Integer) getFieldByNumber(9);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getSleepRestlessnessScore() {
|
||||
return (Integer) getFieldByNumber(10);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getAwakeningsCount() {
|
||||
return (Integer) getFieldByNumber(11);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getUnk12() {
|
||||
return (Integer) getFieldByNumber(12);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getUnk13() {
|
||||
return (Integer) getFieldByNumber(13);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getInterruptionsScore() {
|
||||
return (Integer) getFieldByNumber(14);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Float getAverageStressDuringSleep() {
|
||||
return (Float) getFieldByNumber(15);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getUnk16() {
|
||||
return (Integer) getFieldByNumber(16);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user