mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2025-01-12 18:57:36 +01:00
Garmin: Fix sleep data if there is a gap in activity samples
This commit is contained in:
parent
60d5a2ae70
commit
2f21c4bd9d
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user