1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-09-01 03:55:47 +02:00

Garmin: Fix sleep data if there is a gap in activity samples

This commit is contained in:
José Rebelo 2024-08-04 18:04:37 +01:00
parent 60d5a2ae70
commit 2f21c4bd9d
10 changed files with 108 additions and 4 deletions

View File

@ -20,11 +20,16 @@ package nodomain.freeyourgadget.gadgetbridge.devices;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Instant;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.ListIterator;
import de.greenrobot.dao.AbstractDao; import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property; import de.greenrobot.dao.Property;
@ -36,6 +41,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
/** /**
* Base class for all sample providers. A Sample provider is device specific and provides * Base class for all sample providers. A Sample provider is device specific and provides
@ -43,6 +49,8 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
* @param <T> the sample type * @param <T> the sample type
*/ */
public abstract class AbstractSampleProvider<T extends AbstractActivitySample> implements SampleProvider<T> { public abstract class AbstractSampleProvider<T extends AbstractActivitySample> implements SampleProvider<T> {
private static final Logger LOG = LoggerFactory.getLogger(AbstractSampleProvider.class);
private static final WhereCondition[] NO_CONDITIONS = new WhereCondition[0]; private static final WhereCondition[] NO_CONDITIONS = new WhereCondition[0];
private final DaoSession mSession; private final DaoSession mSession;
private final GBDevice mDevice; private final GBDevice mDevice;
@ -60,11 +68,13 @@ public abstract class AbstractSampleProvider<T extends AbstractActivitySample> i
return mSession; return mSession;
} }
@NonNull
@Override @Override
public List<T> getAllActivitySamples(int timestamp_from, int timestamp_to) { public List<T> getAllActivitySamples(int timestamp_from, int timestamp_to) {
return getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_ALL); return getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_ALL);
} }
@NonNull
@Override @Override
public List<T> getActivitySamples(int timestamp_from, int timestamp_to) { public List<T> getActivitySamples(int timestamp_from, int timestamp_to) {
if (getRawKindSampleProperty() != null) { if (getRawKindSampleProperty() != null) {
@ -74,6 +84,7 @@ public abstract class AbstractSampleProvider<T extends AbstractActivitySample> i
} }
} }
@NonNull
@Override @Override
public List<T> getSleepSamples(int timestamp_from, int timestamp_to) { public List<T> getSleepSamples(int timestamp_from, int timestamp_to) {
final DeviceCoordinator coordinator = getDevice().getDeviceCoordinator(); final DeviceCoordinator coordinator = getDevice().getDeviceCoordinator();
@ -167,14 +178,14 @@ public abstract class AbstractSampleProvider<T extends AbstractActivitySample> i
/** /**
* Detaches all samples of this type from the session. Changes to them may not be * Detaches all samples of this type from the session. Changes to them may not be
* written back to the database. * written back to the database.
* * <p>
* Subclasses should call this method after performing custom queries. * Subclasses should call this method after performing custom queries.
*/ */
protected void detachFromSession() { protected void detachFromSession() {
getSampleDao().detachAll(); getSampleDao().detachAll();
} }
private WhereCondition[] getClauseForActivityType(QueryBuilder qb, int activityTypes) { private WhereCondition[] getClauseForActivityType(QueryBuilder<T> qb, int activityTypes) {
if (activityTypes == ActivityKind.TYPE_ALL) { if (activityTypes == ActivityKind.TYPE_ALL) {
return NO_CONDITIONS; return NO_CONDITIONS;
} }
@ -184,7 +195,7 @@ public abstract class AbstractSampleProvider<T extends AbstractActivitySample> i
return new WhereCondition[] { activityTypeCondition }; return new WhereCondition[] { activityTypeCondition };
} }
private WhereCondition getActivityTypeConditions(QueryBuilder qb, int[] dbActivityTypes) { private WhereCondition getActivityTypeConditions(QueryBuilder<T> qb, int[] dbActivityTypes) {
// What a crappy QueryBuilder API ;-( QueryBuilder.or(WhereCondition[]) with a runtime array length // What a crappy QueryBuilder API ;-( QueryBuilder.or(WhereCondition[]) with a runtime array length
// check would have worked just fine. // check would have worked just fine.
if (dbActivityTypes.length == 0) { if (dbActivityTypes.length == 0) {
@ -295,4 +306,73 @@ public abstract class AbstractSampleProvider<T extends AbstractActivitySample> i
return d1.equals(d2); return d1.equals(d2);
} }
protected List<T> fillGaps(final List<T> samples, final int timestamp_from, final int timestamp_to) {
if (samples.isEmpty()) {
return samples;
}
final long nanoStart = System.nanoTime();
final List<T> ret = new ArrayList<>(samples);
//ret.sort(Comparator.comparingLong(T::getTimestamp));
final int firstTimestamp = ret.get(0).getTimestamp();
if (firstTimestamp - timestamp_from > 60) {
// Gap at the start
for (int ts = timestamp_from; ts <= firstTimestamp + 60; ts += 60) {
final T dummySample = createActivitySample();
dummySample.setTimestamp(ts);
dummySample.setRawKind(ActivityKind.TYPE_UNKNOWN);
dummySample.setRawIntensity(ActivitySample.NOT_MEASURED);
dummySample.setSteps(ActivitySample.NOT_MEASURED);
dummySample.setProvider(this);
ret.add(0, dummySample);
}
}
final int lastTimestamp = ret.get(ret.size() - 1).getTimestamp();
if (timestamp_to - lastTimestamp > 60) {
// Gap at the end
for (int ts = lastTimestamp + 60; ts <= timestamp_to; ts += 60) {
final T dummySample = createActivitySample();
dummySample.setTimestamp(ts);
dummySample.setRawKind(ActivityKind.TYPE_UNKNOWN);
dummySample.setRawIntensity(ActivitySample.NOT_MEASURED);
dummySample.setSteps(ActivitySample.NOT_MEASURED);
dummySample.setProvider(this);
ret.add(dummySample);
}
}
final ListIterator<T> it = ret.listIterator();
T previousSample = it.next();
while (it.hasNext()) {
final T sample = it.next();
if (sample.getTimestamp() - previousSample.getTimestamp() > 60) {
LOG.trace("Filling gap between {} and {}", Instant.ofEpochSecond(previousSample.getTimestamp() + 60), Instant.ofEpochSecond(sample.getTimestamp()));
for (int ts = previousSample.getTimestamp() + 60; ts < sample.getTimestamp(); ts += 60) {
final T dummySample = createActivitySample();
dummySample.setTimestamp(ts);
dummySample.setRawKind(ActivityKind.TYPE_UNKNOWN);
dummySample.setRawIntensity(ActivitySample.NOT_MEASURED);
dummySample.setSteps(ActivitySample.NOT_MEASURED);
dummySample.setProvider(this);
it.add(dummySample);
}
}
previousSample = sample;
}
final long nanoEnd = System.nanoTime();
final long executionTime = (nanoEnd - nanoStart) / 1000000;
final int dummyCount = ret.size() - samples.size();
LOG.trace("Filled gaps with {} samples in {}ms", dummyCount, executionTime);
return ret;
}
} }

View File

@ -85,11 +85,13 @@ public class CasioGBX100SampleProvider extends AbstractSampleProvider<CasioGBX10
} }
@NonNull
@Override @Override
public List<CasioGBX100ActivitySample> getActivitySamples(int timestamp_from, int timestamp_to) { public List<CasioGBX100ActivitySample> getActivitySamples(int timestamp_from, int timestamp_to) {
return super.getActivitySamples(timestamp_from, timestamp_to); return super.getActivitySamples(timestamp_from, timestamp_to);
} }
@NonNull
@Override @Override
public List<CasioGBX100ActivitySample> getAllActivitySamples(int timestamp_from, int timestamp_to) { public List<CasioGBX100ActivitySample> getAllActivitySamples(int timestamp_from, int timestamp_to) {
return super.getActivitySamples(timestamp_from, timestamp_to); return super.getActivitySamples(timestamp_from, timestamp_to);

View File

@ -98,7 +98,11 @@ public class GarminActivitySampleProvider extends AbstractSampleProvider<GarminA
final long nanoStart = System.nanoTime(); final long nanoStart = System.nanoTime();
final List<GarminActivitySample> samples = super.getGBActivitySamples(timestamp_from, timestamp_to, activityType); final List<GarminActivitySample> samples = fillGaps(
super.getGBActivitySamples(timestamp_from, timestamp_to, activityType),
timestamp_from,
timestamp_to
);
if (!samples.isEmpty()) { if (!samples.isEmpty()) {
convertCumulativeSteps(samples, GarminActivitySampleDao.Properties.Steps); convertCumulativeSteps(samples, GarminActivitySampleDao.Properties.Steps);

View File

@ -118,6 +118,7 @@ public class HPlusHealthSampleProvider extends AbstractSampleProvider<HPlusHealt
return getAllActivitySamples(timestamp_from, timestamp_to); return getAllActivitySamples(timestamp_from, timestamp_to);
} }
@NonNull
public List<HPlusHealthActivitySample> getSleepSamples(int timestamp_from, int timestamp_to) { public List<HPlusHealthActivitySample> getSleepSamples(int timestamp_from, int timestamp_to) {
return getAllActivitySamples(timestamp_from, timestamp_to); return getAllActivitySamples(timestamp_from, timestamp_to);
} }

View File

@ -132,6 +132,7 @@ public class WatchXPlusSampleProvider extends AbstractSampleProvider<WatchXPlusA
@NonNull
@Override @Override
public List<WatchXPlusActivitySample> getAllActivitySamples(int timestamp_from, int timestamp_to) { public List<WatchXPlusActivitySample> getAllActivitySamples(int timestamp_from, int timestamp_to) {
boolean showRawData = GBApplication.getDeviceSpecificSharedPrefs(mDevice.getAddress()).getBoolean(WatchXPlusConstants.PREF_SHOW_RAW_GRAPH, false); boolean showRawData = GBApplication.getDeviceSpecificSharedPrefs(mDevice.getAddress()).getBoolean(WatchXPlusConstants.PREF_SHOW_RAW_GRAPH, false);

View File

@ -17,6 +17,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */ along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.pebble; package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
import androidx.annotation.NonNull;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -50,6 +52,7 @@ public class PebbleHealthSampleProvider extends AbstractSampleProvider<PebbleHea
super(device, session); super(device, session);
} }
@NonNull
@Override @Override
public List<PebbleHealthActivitySample> getAllActivitySamples(int timestamp_from, int timestamp_to) { public List<PebbleHealthActivitySample> getAllActivitySamples(int timestamp_from, int timestamp_to) {
List<PebbleHealthActivitySample> samples = super.getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_ALL); List<PebbleHealthActivitySample> samples = super.getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_ALL);
@ -84,6 +87,7 @@ public class PebbleHealthSampleProvider extends AbstractSampleProvider<PebbleHea
return getSession().getPebbleHealthActivitySampleDao(); return getSession().getPebbleHealthActivitySampleDao();
} }
@NonNull
@Override @Override
protected Property getTimestampSampleProperty() { protected Property getTimestampSampleProperty() {
return PebbleHealthActivitySampleDao.Properties.Timestamp; return PebbleHealthActivitySampleDao.Properties.Timestamp;
@ -96,6 +100,7 @@ public class PebbleHealthSampleProvider extends AbstractSampleProvider<PebbleHea
//return PebbleHealthActivitySampleDao.Properties.RawKind; //return PebbleHealthActivitySampleDao.Properties.RawKind;
} }
@NonNull
@Override @Override
protected Property getDeviceIdentifierSampleProperty() { protected Property getDeviceIdentifierSampleProperty() {
return PebbleHealthActivitySampleDao.Properties.DeviceId; return PebbleHealthActivitySampleDao.Properties.DeviceId;

View File

@ -16,6 +16,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */ along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.pebble; package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
import androidx.annotation.NonNull;
import de.greenrobot.dao.AbstractDao; import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property; import de.greenrobot.dao.Property;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
@ -62,10 +64,12 @@ public class PebbleMisfitSampleProvider extends AbstractSampleProvider<PebbleMis
return null; return null;
} }
@NonNull
protected Property getTimestampSampleProperty() { protected Property getTimestampSampleProperty() {
return PebbleMisfitSampleDao.Properties.Timestamp; return PebbleMisfitSampleDao.Properties.Timestamp;
} }
@NonNull
protected Property getDeviceIdentifierSampleProperty() { protected Property getDeviceIdentifierSampleProperty() {
return PebbleMisfitSampleDao.Properties.DeviceId; return PebbleMisfitSampleDao.Properties.DeviceId;
} }

View File

@ -16,6 +16,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */ along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.pebble; package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
import androidx.annotation.NonNull;
import de.greenrobot.dao.AbstractDao; import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property; import de.greenrobot.dao.Property;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
@ -37,6 +39,7 @@ public class PebbleMorpheuzSampleProvider extends AbstractSampleProvider<PebbleM
return getSession().getPebbleMorpheuzSampleDao(); return getSession().getPebbleMorpheuzSampleDao();
} }
@NonNull
@Override @Override
protected Property getTimestampSampleProperty() { protected Property getTimestampSampleProperty() {
return PebbleMorpheuzSampleDao.Properties.Timestamp; return PebbleMorpheuzSampleDao.Properties.Timestamp;
@ -47,6 +50,7 @@ public class PebbleMorpheuzSampleProvider extends AbstractSampleProvider<PebbleM
return null; // not supported return null; // not supported
} }
@NonNull
@Override @Override
protected Property getDeviceIdentifierSampleProperty() { protected Property getDeviceIdentifierSampleProperty() {
return PebbleMorpheuzSampleDao.Properties.DeviceId; return PebbleMorpheuzSampleDao.Properties.DeviceId;

View File

@ -79,11 +79,13 @@ public class HybridHRActivitySampleProvider extends AbstractSampleProvider<Hybri
return new HybridHRActivitySample(); return new HybridHRActivitySample();
} }
@NonNull
@Override @Override
public List<HybridHRActivitySample> getActivitySamples(int timestamp_from, int timestamp_to) { public List<HybridHRActivitySample> getActivitySamples(int timestamp_from, int timestamp_to) {
return super.getActivitySamples(timestamp_from, timestamp_to); return super.getActivitySamples(timestamp_from, timestamp_to);
} }
@NonNull
@Override @Override
public List<HybridHRActivitySample> getAllActivitySamples(int timestamp_from, int timestamp_to) { public List<HybridHRActivitySample> getAllActivitySamples(int timestamp_from, int timestamp_to) {
return super.getAllActivitySamples(timestamp_from, timestamp_to); return super.getAllActivitySamples(timestamp_from, timestamp_to);

View File

@ -70,6 +70,7 @@ public class WithingsSteelHRSampleProvider extends AbstractSampleProvider<Within
return WithingsSteelHRActivitySampleDao.Properties.DeviceId; return WithingsSteelHRActivitySampleDao.Properties.DeviceId;
} }
@NonNull
@Override @Override
public List<WithingsSteelHRActivitySample> getActivitySamples(int timestamp_from, int timestamp_to) { public List<WithingsSteelHRActivitySample> getActivitySamples(int timestamp_from, int timestamp_to) {
return super.getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_ALL); return super.getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_ALL);