From ce387396fd5a7f69565616cd54a6121337e93fd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Tue, 24 Sep 2024 19:23:02 +0100 Subject: [PATCH] Garmin: Fix monitoring sample timestamp16 handling --- .../devices/garmin/fit/FitImporter.java | 19 ++++++++++++------- .../devices/garmin/fit/RecordData.java | 8 +++++++- .../garmin/fit/messages/FitMonitoring.java | 15 +++++++-------- 3 files changed, 26 insertions(+), 16 deletions(-) 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 451bd605e..6dc42d9b8 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 @@ -73,7 +73,7 @@ public class FitImporter { private final Context context; private final GBDevice gbDevice; - private final SortedMap> activitySamplesPerTimestamp = new TreeMap<>(); + private final SortedMap> activitySamplesPerTimestamp = new TreeMap<>(); private final List stressSamples = new ArrayList<>(); private final List bodyEnergySamples = new ArrayList<>(); private final List spo2samples = new ArrayList<>(); @@ -100,6 +100,8 @@ public class FitImporter { final FitFile fitFile = FitFile.parseIncoming(file); + Long lastMonitoringTimestamp = null; + for (final RecordData record : fitFile.getRecords()) { if (fileId != null && fileId.getType() == FileType.FILETYPE.ACTIVITY) { if (workoutParser.handleRecord(record)) { @@ -160,10 +162,13 @@ public class FitImporter { sleepStageSamples.add(sample); } else if (record instanceof FitMonitoring) { LOG.trace("Monitoring at {}: {}", ts, record); - if (!activitySamplesPerTimestamp.containsKey(ts.intValue())) { - activitySamplesPerTimestamp.put(ts.intValue(), new ArrayList<>()); + final FitMonitoring monitoringRecord = (FitMonitoring) record; + final Long currentMonitoringTimestamp = monitoringRecord.computeTimestamp(lastMonitoringTimestamp); + if (!activitySamplesPerTimestamp.containsKey(currentMonitoringTimestamp)) { + activitySamplesPerTimestamp.put(currentMonitoringTimestamp, new ArrayList<>()); } - Objects.requireNonNull(activitySamplesPerTimestamp.get(ts.intValue())).add((FitMonitoring) record); + Objects.requireNonNull(activitySamplesPerTimestamp.get(currentMonitoringTimestamp)).add(monitoringRecord); + lastMonitoringTimestamp = currentMonitoringTimestamp; } else if (record instanceof FitSpo2) { final Integer spo2 = ((FitSpo2) record).getReadingSpo2(); if (spo2 == null || spo2 <= 0) { @@ -359,7 +364,7 @@ public class FitImporter { int prevActivityKind = ActivityKind.UNKNOWN.getCode(); int prevTs = -1; - for (final int ts : activitySamplesPerTimestamp.keySet()) { + for (final long ts : activitySamplesPerTimestamp.keySet()) { if (prevTs > 0 && ts - prevTs > 60) { // Fill gaps between samples LOG.debug("Filling gap between {} and {}", prevTs, ts); @@ -376,7 +381,7 @@ public class FitImporter { final List records = activitySamplesPerTimestamp.get(ts); final GarminActivitySample sample = new GarminActivitySample(); - sample.setTimestamp(ts); + sample.setTimestamp((int) ts); sample.setRawKind(ActivityKind.ACTIVITY.getCode()); sample.setRawIntensity(ActivitySample.NOT_MEASURED); sample.setSteps(ActivitySample.NOT_MEASURED); @@ -413,7 +418,7 @@ public class FitImporter { activitySamples.add(sample); prevActivityKind = sample.getRawKind(); - prevTs = ts; + prevTs = (int) ts; } LOG.debug("Will persist {} activity samples", activitySamples.size()); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordData.java index eeb08944c..f15599309 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordData.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/RecordData.java @@ -26,7 +26,13 @@ public class RecordData { private final List fieldDataList; protected ByteBuffer valueHolder; - private Long computedTimestamp = null; + /** + * The computed timestamp consists of the running timestamp for this record, which may come from + * a timestamp field 253, or from a compressed timestamp, or simply be the same timestamp as the + * previously seen sample. This does not take into account sample-specific timestamps such as + * timestamp16. + */ + public Long computedTimestamp = null; public RecordData(final RecordDefinition recordDefinition, final RecordHeader recordHeader) { if (null == recordDefinition.getFieldDefinitions()) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoring.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoring.java index 77441935c..78653b2e3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoring.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/fit/messages/FitMonitoring.java @@ -75,16 +75,15 @@ public class FitMonitoring extends RecordData { // manual changes below - @Override - public Long getComputedTimestamp() { + public Long computeTimestamp(final Long lastMonitoringTimestamp) { final Integer timestamp16 = getTimestamp16(); - final Long computedTimestamp = super.getComputedTimestamp(); - if (timestamp16 != null && computedTimestamp != null) { - return (long) GarminTimeUtils.garminTimestampToUnixTime( - (GarminTimeUtils.unixTimeToGarminTimestamp(computedTimestamp.intValue()) & ~0xFFFF) | (timestamp16 & 0xFFFF) - ); + + if (timestamp16 != null && lastMonitoringTimestamp != null) { + final int referenceGarminTs = GarminTimeUtils.unixTimeToGarminTimestamp(lastMonitoringTimestamp.intValue()); + return (long) (lastMonitoringTimestamp.intValue() + ((timestamp16 - (referenceGarminTs & 0xffff)) & 0xffff)); } - return computedTimestamp; + + return getComputedTimestamp(); } public Optional getComputedActivityType() {