1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-07-03 11:33:19 +02:00

Garmin: Fix overcounting of steps in some cases

We need to take into account the previous sample that is outside the
range that was queried, so that we can correct the first sample in the
range.
This commit is contained in:
José Rebelo 2024-05-19 23:09:23 +01:00 committed by Daniele Gobbetti
parent 39bbd2e579
commit 8e1511bd6e
3 changed files with 66 additions and 63 deletions

View File

@ -18,11 +18,15 @@
package nodomain.freeyourgadget.gadgetbridge.devices;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.threeten.bp.LocalDate;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property;
import de.greenrobot.dao.query.QueryBuilder;
@ -33,7 +37,6 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
/**
* Base class for all sample providers. A Sample provider is device specific and provides
@ -233,4 +236,64 @@ public abstract class AbstractSampleProvider<T extends AbstractActivitySample> i
@NonNull
protected abstract Property getDeviceIdentifierSampleProperty();
public void convertCumulativeSteps(final List<T> samples, final Property stepsSampleProperty) {
final T lastSample = getLastSampleWithStepsBefore(samples.get(0).getTimestamp(), stepsSampleProperty);
if (lastSample != null && sameDay(lastSample, samples.get(0)) && samples.get(0).getSteps() > 0) {
samples.get(0).setSteps(samples.get(0).getSteps() - lastSample.getSteps());
}
// Steps on the Garmin Watch are reported cumulatively per day - convert them to
// This slightly breaks activity recognition, because we don't have per-minute granularity...
int prevSteps = samples.get(0).getSteps();
samples.get(0).setTimestamp((samples.get(0).getTimestamp() / 60) * 60);
for (int i = 1; i < samples.size(); i++) {
final T s1 = samples.get(i - 1);
final T s2 = samples.get(i);
s2.setTimestamp((s2.getTimestamp() / 60) * 60);
if (!sameDay(s1, s2)) {
// went past midnight - reset steps
prevSteps = s2.getSteps() > 0 ? s2.getSteps() : 0;
} else if (s2.getSteps() > 0) {
// New steps sample for the current day - subtract the previous seen sample
int bak = s2.getSteps();
s2.setSteps(s2.getSteps() - prevSteps);
prevSteps = bak;
}
}
}
@Nullable
public T getLastSampleWithStepsBefore(final int timestampTo, final Property stepsSampleProperty) {
final Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
if (dbDevice == null) {
// no device, no sample
return null;
}
final List<T> samples = getSampleDao().queryBuilder()
.where(
getDeviceIdentifierSampleProperty().eq(dbDevice.getId()),
getTimestampSampleProperty().le(timestampTo),
stepsSampleProperty.gt(-1)
).orderDesc(getTimestampSampleProperty())
.limit(1)
.list();
return !samples.isEmpty() ? samples.get(0) : null;
}
public boolean sameDay(final T s1, final T s2) {
final Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(s1.getTimestamp() * 1000L - 1000L);
final LocalDate d1 = LocalDate.of(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH));
cal.setTimeInMillis(s2.getTimestamp() * 1000L - 1000L);
final LocalDate d2 = LocalDate.of(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH));
return d1.equals(d2);
}
}

View File

@ -105,7 +105,7 @@ public class CmfActivitySampleProvider extends AbstractSampleProvider<CmfActivit
final List<CmfActivitySample> samples = super.getGBActivitySamples(timestamp_from, timestamp_to, activityType);
if (!samples.isEmpty()) {
convertCumulativeSteps(samples);
convertCumulativeSteps(samples, CmfActivitySampleDao.Properties.Steps);
}
final Map<Integer, CmfActivitySample> sampleByTs = new HashMap<>();
@ -128,32 +128,6 @@ public class CmfActivitySampleProvider extends AbstractSampleProvider<CmfActivit
return finalSamples;
}
private void convertCumulativeSteps(final List<CmfActivitySample> samples) {
final Calendar cal = Calendar.getInstance();
// Steps on the Cmf Watch are reported cumulatively per day - convert them to
// This slightly breaks activity recognition, because we don't have per-minute granularity...
int prevSteps = samples.get(0).getSteps();
samples.get(0).setTimestamp((int) (samples.get(0).getTimestamp() / 60) * 60);
for (int i = 1; i < samples.size(); i++) {
final CmfActivitySample s1 = samples.get(i - 1);
final CmfActivitySample s2 = samples.get(i);
s2.setTimestamp((int) (s2.getTimestamp() / 60) * 60);
cal.setTimeInMillis(s1.getTimestamp() * 1000L - 1000L);
final LocalDate d1 = LocalDate.of(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH));
cal.setTimeInMillis(s2.getTimestamp() * 1000L - 1000L);
final LocalDate d2 = LocalDate.of(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH));
if (d1.equals(d2)) {
int bak = s2.getSteps();
s2.setSteps(s2.getSteps() - prevSteps);
prevSteps = bak;
}
}
}
private void overlayHeartRate(final Map<Integer, CmfActivitySample> sampleByTs, final int timestamp_from, final int timestamp_to) {
final CmfHeartRateSampleProvider heartRateSampleProvider = new CmfHeartRateSampleProvider(getDevice(), getSession());
final List<CmfHeartRateSample> hrSamples = heartRateSampleProvider.getAllSamples(timestamp_from * 1000L, timestamp_to * 1000L);

View File

@ -21,21 +21,17 @@ import androidx.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.threeten.bp.LocalDate;
import java.util.Calendar;
import java.util.List;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiSleepTimeSampleProvider;
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.GarminSleepStageSample;
import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiSleepTimeSample;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionSleepStage;
@ -105,7 +101,7 @@ public class GarminActivitySampleProvider extends AbstractSampleProvider<GarminA
final List<GarminActivitySample> samples = super.getGBActivitySamples(timestamp_from, timestamp_to, activityType);
if (!samples.isEmpty()) {
convertCumulativeSteps(samples);
convertCumulativeSteps(samples, GarminActivitySampleDao.Properties.Steps);
}
overlaySleep(samples, timestamp_from, timestamp_to);
@ -119,36 +115,6 @@ public class GarminActivitySampleProvider extends AbstractSampleProvider<GarminA
return samples;
}
private void convertCumulativeSteps(final List<GarminActivitySample> samples) {
final Calendar cal = Calendar.getInstance();
// Steps on the Garmin Watch are reported cumulatively per day - convert them to
// This slightly breaks activity recognition, because we don't have per-minute granularity...
int prevSteps = samples.get(0).getSteps();
samples.get(0).setTimestamp((samples.get(0).getTimestamp() / 60) * 60);
for (int i = 1; i < samples.size(); i++) {
final GarminActivitySample s1 = samples.get(i - 1);
final GarminActivitySample s2 = samples.get(i);
s2.setTimestamp((s2.getTimestamp() / 60) * 60);
cal.setTimeInMillis(s1.getTimestamp() * 1000L - 1000L);
final LocalDate d1 = LocalDate.of(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH));
cal.setTimeInMillis(s2.getTimestamp() * 1000L - 1000L);
final LocalDate d2 = LocalDate.of(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH));
if (!d1.equals(d2)) {
// went past midnight - reset steps
prevSteps = s2.getSteps() > 0 ? s2.getSteps() : 0;
} else if (s2.getSteps() > 0) {
// New steps sample for the current day - subtract the previous seen sample
int bak = s2.getSteps();
s2.setSteps(s2.getSteps() - prevSteps);
prevSteps = bak;
}
}
}
public void overlaySleep(final List<GarminActivitySample> samples, final int timestamp_from, final int timestamp_to) {
// The samples provided by Garmin are upper-bound timestamps of the sleep stage
final RangeMap<Long, Integer> stagesMap = new RangeMap<>(RangeMap.Mode.UPPER_BOUND);