mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-28 04:46:51 +01: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:
parent
39bbd2e579
commit
8e1511bd6e
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user