mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2025-03-12 09:10:11 +01:00
Garmin: Add nap support
This commit is contained in:
parent
624fb5cf64
commit
00b5420fbe
@ -54,7 +54,7 @@ public class GBDaoGenerator {
|
||||
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
final Schema schema = new Schema(96, MAIN_PACKAGE + ".entities");
|
||||
final Schema schema = new Schema(97, MAIN_PACKAGE + ".entities");
|
||||
|
||||
Entity userAttributes = addUserAttributes(schema);
|
||||
Entity user = addUserInfo(schema, userAttributes);
|
||||
@ -132,6 +132,7 @@ public class GBDaoGenerator {
|
||||
addGarminRestingMetabolicRateSample(schema, user, device);
|
||||
addGarminSleepStatsSample(schema, user, device);
|
||||
addGarminIntensityMinutesSample(schema, user, device);
|
||||
addGarminNapSample(schema, user, device);
|
||||
addPendingFile(schema, user, device);
|
||||
addWena3EnergySample(schema, user, device);
|
||||
addWena3BehaviorSample(schema, user, device);
|
||||
@ -925,6 +926,13 @@ public class GBDaoGenerator {
|
||||
return sample;
|
||||
}
|
||||
|
||||
private static Entity addGarminNapSample(Schema schema, Entity user, Entity device) {
|
||||
Entity sample = addEntity(schema, "GarminNapSample");
|
||||
addCommonTimeSampleProperties("AbstractTimeSample", sample, user, device);
|
||||
sample.addLongProperty("endTimestamp").notNull();
|
||||
return sample;
|
||||
}
|
||||
|
||||
private static Entity addPendingFile(Schema schema, Entity user, Entity device) {
|
||||
Entity pendingFile = addEntity(schema, "PendingFile");
|
||||
pendingFile.setJavaDoc(
|
||||
|
@ -22,6 +22,7 @@ import androidx.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.greenrobot.dao.AbstractDao;
|
||||
@ -31,6 +32,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.GarminActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.GarminActivitySampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.GarminEventSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.GarminNapSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.GarminSleepStageSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
@ -182,6 +184,22 @@ public class GarminActivitySampleProvider extends AbstractSampleProvider<GarminA
|
||||
}
|
||||
}
|
||||
|
||||
// Overlap nap samples as light sleep
|
||||
// TODO: Dedicated nap support in Gb?
|
||||
final GarminNapSampleProvider napSampleProvider = new GarminNapSampleProvider(getDevice(), getSession());
|
||||
final List<GarminNapSample> napSamples = new ArrayList<>(napSampleProvider.getAllSamples(
|
||||
timestamp_from * 1000L,
|
||||
timestamp_to * 1000L
|
||||
));
|
||||
final GarminNapSample lastNapSample = napSampleProvider.getLastSampleBefore(timestamp_from * 1000L);
|
||||
if (lastNapSample != null) {
|
||||
napSamples.add(lastNapSample);
|
||||
}
|
||||
for (final GarminNapSample napSample : napSamples) {
|
||||
stagesMap.put(napSample.getTimestamp(), ActivityKind.UNKNOWN);
|
||||
stagesMap.put(napSample.getEndTimestamp(), ActivityKind.LIGHT_SLEEP);
|
||||
}
|
||||
|
||||
if (!stagesMap.isEmpty()) {
|
||||
for (final GarminActivitySample sample : samples) {
|
||||
final long ts = sample.getTimestamp() * 1000L;
|
||||
|
@ -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 <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.GarminNapSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.GarminNapSampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
|
||||
public class GarminNapSampleProvider extends AbstractTimeSampleProvider<GarminNapSample> {
|
||||
public GarminNapSampleProvider(final GBDevice device, final DaoSession session) {
|
||||
super(device, session);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public AbstractDao<GarminNapSample, ?> getSampleDao() {
|
||||
return getSession().getGarminNapSampleDao();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getTimestampSampleProperty() {
|
||||
return GarminNapSampleDao.Properties.Timestamp;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Property getDeviceIdentifierSampleProperty() {
|
||||
return GarminNapSampleDao.Properties.DeviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GarminNapSample createSample() {
|
||||
return new GarminNapSample();
|
||||
}
|
||||
}
|
@ -30,6 +30,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminHeartRateRestin
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminHrvSummarySampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminHrvValueSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminIntensityMinutesSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminNapSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminRespiratoryRateSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminRestingMetabolicRateSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminSleepStatsSampleProvider;
|
||||
@ -46,6 +47,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.GarminBodyEnergySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.GarminEventSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.GarminHeartRateRestingSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.GarminIntensityMinutesSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.GarminNapSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.GarminRestingMetabolicRateSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvSummarySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.GarminHrvValueSample;
|
||||
@ -70,6 +72,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.FitMonitoringHrData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitMonitoringInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitNap;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitPhysiologicalMetrics;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitRecord;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitRespirationRate;
|
||||
@ -100,6 +103,7 @@ public class FitImporter {
|
||||
private final List<GarminEventSample> events = new ArrayList<>();
|
||||
private final List<GarminSleepStatsSample> sleepStatsSamples = new ArrayList<>();
|
||||
private final List<GarminSleepStageSample> sleepStageSamples = new ArrayList<>();
|
||||
private final List<GarminNapSample> napSamples = new ArrayList<>();
|
||||
private final List<GarminHrvSummarySample> hrvSummarySamples = new ArrayList<>();
|
||||
private final List<GarminHrvValueSample> hrvValueSamples = new ArrayList<>();
|
||||
private final List<GarminRestingMetabolicRateSample> restingMetabolicRateSamples = new ArrayList<>();
|
||||
@ -194,6 +198,16 @@ public class FitImporter {
|
||||
sample.setTimestamp(ts * 1000L);
|
||||
sample.setStage(stage.getId());
|
||||
sleepStageSamples.add(sample);
|
||||
} else if (record instanceof FitNap) {
|
||||
final FitNap nap = (FitNap) record;
|
||||
if (nap.getStartTimestamp() == null || nap.getEndTimestamp() == null) {
|
||||
continue;
|
||||
}
|
||||
LOG.trace("Nap at {}: from {} to {}", ts, nap.getStartTimestamp(), nap.getEndTimestamp());
|
||||
final GarminNapSample sample = new GarminNapSample();
|
||||
sample.setTimestamp(nap.getStartTimestamp() * 1000L);
|
||||
sample.setEndTimestamp(nap.getEndTimestamp() * 1000L);
|
||||
napSamples.add(sample);
|
||||
} else if (record instanceof FitMonitoring) {
|
||||
LOG.trace("Monitoring at {}: {}", ts, record);
|
||||
final FitMonitoring monitoringRecord = (FitMonitoring) record;
|
||||
@ -382,6 +396,7 @@ public class FitImporter {
|
||||
case SLEEP:
|
||||
persistAbstractSamples(events, new GarminEventSampleProvider(gbDevice, session));
|
||||
persistAbstractSamples(sleepStatsSamples, new GarminSleepStatsSampleProvider(gbDevice, session));
|
||||
persistAbstractSamples(napSamples, new GarminNapSampleProvider(gbDevice, session));
|
||||
|
||||
// We may have samples, but not sleep samples - #4048
|
||||
// 0 unmeasurable, 1 awake
|
||||
@ -453,6 +468,7 @@ public class FitImporter {
|
||||
events.clear();
|
||||
sleepStatsSamples.clear();
|
||||
sleepStageSamples.clear();
|
||||
napSamples.clear();
|
||||
hrvSummarySamples.clear();
|
||||
hrvValueSamples.clear();
|
||||
restingMetabolicRateSamples.clear();
|
||||
|
@ -490,6 +490,17 @@ public class GlobalFITMessage {
|
||||
new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
|
||||
));
|
||||
|
||||
public static GlobalFITMessage NAP = new GlobalFITMessage(412, "NAP", Arrays.asList(
|
||||
new FieldDefinitionPrimitive(0, BaseType.UINT32, "start_timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP),
|
||||
new FieldDefinitionPrimitive(1, BaseType.SINT16, "unknown_1"), // 0
|
||||
new FieldDefinitionPrimitive(2, BaseType.UINT32, "end_timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP),
|
||||
new FieldDefinitionPrimitive(3, BaseType.SINT16, "unknown_3"), // 0
|
||||
new FieldDefinitionPrimitive(4, BaseType.ENUM, "unknown_4"), // 8
|
||||
new FieldDefinitionPrimitive(6, BaseType.ENUM, "unknown_6"), // 0
|
||||
new FieldDefinitionPrimitive(7, BaseType.UINT32, "timestamp_7", FieldDefinitionFactory.FIELD.TIMESTAMP),
|
||||
new FieldDefinitionPrimitive(253, BaseType.UINT32, "timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP)
|
||||
));
|
||||
|
||||
public static Map<Integer, GlobalFITMessage> KNOWN_MESSAGES = new HashMap<Integer, GlobalFITMessage>() {{
|
||||
put(0, FILE_ID);
|
||||
put(2, DEVICE_SETTINGS);
|
||||
@ -531,6 +542,7 @@ public class GlobalFITMessage {
|
||||
put(371, HRV_VALUE);
|
||||
put(397, SKIN_TEMP_RAW);
|
||||
put(398, SKIN_TEMP_OVERNIGHT);
|
||||
put(412, NAP);
|
||||
}};
|
||||
|
||||
private final int number;
|
||||
|
@ -0,0 +1,62 @@
|
||||
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 FitNap extends RecordData {
|
||||
public FitNap(final RecordDefinition recordDefinition, final RecordHeader recordHeader) {
|
||||
super(recordDefinition, recordHeader);
|
||||
|
||||
final int globalNumber = recordDefinition.getGlobalFITMessage().getNumber();
|
||||
if (globalNumber != 412) {
|
||||
throw new IllegalArgumentException("FitNap expects global messages of " + 412 + ", got " + globalNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getStartTimestamp() {
|
||||
return (Long) getFieldByNumber(0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getUnknown1() {
|
||||
return (Integer) getFieldByNumber(1);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getEndTimestamp() {
|
||||
return (Long) getFieldByNumber(2);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getUnknown3() {
|
||||
return (Integer) getFieldByNumber(3);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getUnknown4() {
|
||||
return (Integer) getFieldByNumber(4);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getUnknown6() {
|
||||
return (Integer) getFieldByNumber(6);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getTimestamp7() {
|
||||
return (Long) getFieldByNumber(7);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getTimestamp() {
|
||||
return (Long) getFieldByNumber(253);
|
||||
}
|
||||
}
|
@ -95,6 +95,8 @@ public class FitRecordDataFactory {
|
||||
return new FitSkinTempRaw(recordDefinition, recordHeader);
|
||||
case 398:
|
||||
return new FitSkinTempOvernight(recordDefinition, recordHeader);
|
||||
case 412:
|
||||
return new FitNap(recordDefinition, recordHeader);
|
||||
}
|
||||
|
||||
return new RecordData(recordDefinition, recordHeader);
|
||||
|
Loading…
x
Reference in New Issue
Block a user