mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2025-01-10 09:01:55 +01:00
Moyoung: Implement syncing sleep data
This commit is contained in:
parent
938085b5fa
commit
0258905b4a
@ -56,7 +56,7 @@ public class GBDaoGenerator {
|
|||||||
|
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
final Schema schema = new Schema(93, MAIN_PACKAGE + ".entities");
|
final Schema schema = new Schema(94, MAIN_PACKAGE + ".entities");
|
||||||
|
|
||||||
Entity userAttributes = addUserAttributes(schema);
|
Entity userAttributes = addUserAttributes(schema);
|
||||||
Entity user = addUserInfo(schema, userAttributes);
|
Entity user = addUserInfo(schema, userAttributes);
|
||||||
@ -157,6 +157,7 @@ public class GBDaoGenerator {
|
|||||||
addMoyoungHeartRateSample(schema, user, device);
|
addMoyoungHeartRateSample(schema, user, device);
|
||||||
addMoyoungSpo2Sample(schema, user, device);
|
addMoyoungSpo2Sample(schema, user, device);
|
||||||
addMoyoungBloodPressureSample(schema, user, device);
|
addMoyoungBloodPressureSample(schema, user, device);
|
||||||
|
addMoyoungSleepStageSample(schema, user, device);
|
||||||
|
|
||||||
addHuaweiActivitySample(schema, user, device);
|
addHuaweiActivitySample(schema, user, device);
|
||||||
|
|
||||||
@ -1103,6 +1104,13 @@ public class GBDaoGenerator {
|
|||||||
return bpSample;
|
return bpSample;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Entity addMoyoungSleepStageSample(Schema schema, Entity user, Entity device) {
|
||||||
|
Entity sleepStageSample = addEntity(schema, "MoyoungSleepStageSample");
|
||||||
|
addCommonTimeSampleProperties("AbstractTimeSample", sleepStageSample, user, device);
|
||||||
|
sleepStageSample.addIntProperty("stage").notNull();
|
||||||
|
return sleepStageSample;
|
||||||
|
}
|
||||||
|
|
||||||
private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) {
|
private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) {
|
||||||
activitySample.setSuperclass(superClass);
|
activitySample.setSuperclass(superClass);
|
||||||
activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider");
|
activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider");
|
||||||
|
@ -75,4 +75,9 @@ public class ColmiI28UltraCoordinator extends AbstractMoyoungDeviceCoordinator {
|
|||||||
public int getWorldClocksLabelLength() {
|
public int getWorldClocksLabelLength() {
|
||||||
return 30;
|
return 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsRemSleep() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
@ -173,6 +173,7 @@ public class MoyoungConstants {
|
|||||||
public static final byte SLEEP_SOBER = 0;
|
public static final byte SLEEP_SOBER = 0;
|
||||||
public static final byte SLEEP_LIGHT = 1;
|
public static final byte SLEEP_LIGHT = 1;
|
||||||
public static final byte SLEEP_RESTFUL = 2;
|
public static final byte SLEEP_RESTFUL = 2;
|
||||||
|
public static final byte SLEEP_REM = 3;
|
||||||
|
|
||||||
public static final byte CMD_QUERY_SLEEP_ACTION = 58; // (*) {i} -> {hour, x[60]}
|
public static final byte CMD_QUERY_SLEEP_ACTION = 58; // (*) {i} -> {hour, x[60]}
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.ListIterator;
|
import java.util.ListIterator;
|
||||||
@ -41,6 +42,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.entities.MoyoungActivitySample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.MoyoungActivitySample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.MoyoungActivitySampleDao;
|
import nodomain.freeyourgadget.gadgetbridge.entities.MoyoungActivitySampleDao;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.MoyoungHeartRateSample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.MoyoungHeartRateSample;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.MoyoungSleepStageSample;
|
||||||
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;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||||
@ -74,6 +76,7 @@ public class MoyoungActivitySampleProvider extends AbstractSampleProvider<Moyoun
|
|||||||
public static final int ACTIVITY_SLEEP_RESTFUL = 17;
|
public static final int ACTIVITY_SLEEP_RESTFUL = 17;
|
||||||
public static final int ACTIVITY_SLEEP_START = 18;
|
public static final int ACTIVITY_SLEEP_START = 18;
|
||||||
public static final int ACTIVITY_SLEEP_END = 19;
|
public static final int ACTIVITY_SLEEP_END = 19;
|
||||||
|
public static final int ACTIVITY_SLEEP_REM = 20;
|
||||||
|
|
||||||
public MoyoungActivitySampleProvider(GBDevice device, DaoSession session) {
|
public MoyoungActivitySampleProvider(GBDevice device, DaoSession session) {
|
||||||
super(device, session);
|
super(device, session);
|
||||||
@ -115,6 +118,8 @@ public class MoyoungActivitySampleProvider extends AbstractSampleProvider<Moyoun
|
|||||||
return ActivityKind.LIGHT_SLEEP;
|
return ActivityKind.LIGHT_SLEEP;
|
||||||
else if (rawType == ACTIVITY_SLEEP_RESTFUL)
|
else if (rawType == ACTIVITY_SLEEP_RESTFUL)
|
||||||
return ActivityKind.DEEP_SLEEP;
|
return ActivityKind.DEEP_SLEEP;
|
||||||
|
else if (rawType == ACTIVITY_SLEEP_REM)
|
||||||
|
return ActivityKind.REM_SLEEP;
|
||||||
else if (rawType == ACTIVITY_SLEEP_START || rawType == ACTIVITY_SLEEP_END)
|
else if (rawType == ACTIVITY_SLEEP_START || rawType == ACTIVITY_SLEEP_END)
|
||||||
return ActivityKind.NOT_MEASURED;
|
return ActivityKind.NOT_MEASURED;
|
||||||
else if (rawType == ACTIVITY_TRAINING_WALK)
|
else if (rawType == ACTIVITY_TRAINING_WALK)
|
||||||
@ -142,12 +147,29 @@ public class MoyoungActivitySampleProvider extends AbstractSampleProvider<Moyoun
|
|||||||
return ACTIVITY_SLEEP_LIGHT;
|
return ACTIVITY_SLEEP_LIGHT;
|
||||||
else if (activityKind == ActivityKind.DEEP_SLEEP)
|
else if (activityKind == ActivityKind.DEEP_SLEEP)
|
||||||
return ACTIVITY_SLEEP_RESTFUL;
|
return ACTIVITY_SLEEP_RESTFUL;
|
||||||
|
else if (activityKind == ActivityKind.REM_SLEEP)
|
||||||
|
return ACTIVITY_SLEEP_REM;
|
||||||
else if (activityKind == ActivityKind.ACTIVITY)
|
else if (activityKind == ActivityKind.ACTIVITY)
|
||||||
return ACTIVITY_NOT_MEASURED; // TODO: ?
|
return ACTIVITY_NOT_MEASURED; // TODO: ?
|
||||||
else
|
else
|
||||||
throw new IllegalArgumentException("Invalid Gadgetbridge activity kind: " + activityKind);
|
throw new IllegalArgumentException("Invalid Gadgetbridge activity kind: " + activityKind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final ActivityKind sleepStageToActivityKind(final int sleepStage) {
|
||||||
|
switch (sleepStage) {
|
||||||
|
case MoyoungConstants.SLEEP_LIGHT:
|
||||||
|
return ActivityKind.LIGHT_SLEEP;
|
||||||
|
case MoyoungConstants.SLEEP_RESTFUL:
|
||||||
|
return ActivityKind.DEEP_SLEEP;
|
||||||
|
case MoyoungConstants.SLEEP_REM:
|
||||||
|
return ActivityKind.REM_SLEEP;
|
||||||
|
case MoyoungConstants.SLEEP_SOBER:
|
||||||
|
return ActivityKind.AWAKE_SLEEP;
|
||||||
|
default:
|
||||||
|
return ActivityKind.UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float normalizeIntensity(int rawIntensity) {
|
public float normalizeIntensity(int rawIntensity) {
|
||||||
if (rawIntensity == ActivitySample.NOT_MEASURED)
|
if (rawIntensity == ActivitySample.NOT_MEASURED)
|
||||||
@ -177,23 +199,10 @@ public class MoyoungActivitySampleProvider extends AbstractSampleProvider<Moyoun
|
|||||||
}
|
}
|
||||||
|
|
||||||
overlayHeartRate(sampleByTs, timestamp_from, timestamp_to);
|
overlayHeartRate(sampleByTs, timestamp_from, timestamp_to);
|
||||||
// overlaySleep(sampleByTs, timestamp_from, timestamp_to);
|
overlaySleep(sampleByTs, timestamp_from, timestamp_to);
|
||||||
|
|
||||||
// Add empty dummy samples every 5 min to make sure the charts and stats aren't too malformed
|
|
||||||
// This is necessary due to the Colmi rings just reporting steps/calories/distance aggregates per hour
|
|
||||||
// for (int i=timestamp_from; i<=timestamp_to; i+=300) {
|
|
||||||
// MoyoungActivitySample sample = sampleByTs.get(i);
|
|
||||||
// if (sample == null) {
|
|
||||||
// sample = new MoyoungActivitySample();
|
|
||||||
// sample.setTimestamp(i);
|
|
||||||
// sample.setProvider(this);
|
|
||||||
// sample.setRawKind(ActivitySample.NOT_MEASURED);
|
|
||||||
// sampleByTs.put(i, sample);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
final List<MoyoungActivitySample> finalSamples = new ArrayList<>(sampleByTs.values());
|
final List<MoyoungActivitySample> finalSamples = new ArrayList<>(sampleByTs.values());
|
||||||
Collections.sort(finalSamples, (a, b) -> Integer.compare(a.getTimestamp(), b.getTimestamp()));
|
Collections.sort(finalSamples, Comparator.comparingInt(MoyoungActivitySample::getTimestamp));
|
||||||
|
|
||||||
final long nanoEnd = System.nanoTime();
|
final long nanoEnd = System.nanoTime();
|
||||||
final long executionTime = (nanoEnd - nanoStart) / 1000000;
|
final long executionTime = (nanoEnd - nanoStart) / 1000000;
|
||||||
@ -221,6 +230,64 @@ public class MoyoungActivitySampleProvider extends AbstractSampleProvider<Moyoun
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void overlaySleep(final Map<Integer, MoyoungActivitySample> sampleByTs, final int timestamp_from, final int timestamp_to) {
|
||||||
|
final MoyoungSleepStageSampleProvider sleepStageSampleProvider = new MoyoungSleepStageSampleProvider(getDevice(), getSession());
|
||||||
|
final List<MoyoungSleepStageSample> sleepStageSamples = sleepStageSampleProvider.getAllSamples(timestamp_from * 1000L, timestamp_to * 1000L);
|
||||||
|
|
||||||
|
// Retrieve the last stage before this time range, as the user could have been asleep during
|
||||||
|
// the range transition
|
||||||
|
final MoyoungSleepStageSample lastSleepStageBeforeRange = sleepStageSampleProvider.getLastSampleBefore(timestamp_from * 1000L);
|
||||||
|
if (lastSleepStageBeforeRange != null && lastSleepStageBeforeRange.getStage() != MoyoungConstants.SLEEP_SOBER) {
|
||||||
|
LOG.debug("Last sleep stage before range: ts={}, stage={}", lastSleepStageBeforeRange.getTimestamp(), lastSleepStageBeforeRange.getStage());
|
||||||
|
sleepStageSamples.add(0, lastSleepStageBeforeRange);
|
||||||
|
}
|
||||||
|
// Retrieve the next sample after the time range, as the last stage could exceed it
|
||||||
|
final MoyoungSleepStageSample nextSleepStageAfterRange = sleepStageSampleProvider.getNextSampleAfter(timestamp_to * 1000L);
|
||||||
|
if (nextSleepStageAfterRange != null) {
|
||||||
|
LOG.debug("Next sleep stage after range: ts={}, stage={}", nextSleepStageAfterRange.getTimestamp(), nextSleepStageAfterRange.getStage());
|
||||||
|
sleepStageSamples.add(nextSleepStageAfterRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sleepStageSamples.size() > 1) {
|
||||||
|
LOG.debug("Overlaying with data from {} sleep stage samples", sleepStageSamples.size());
|
||||||
|
} else {
|
||||||
|
LOG.warn("Not overlaying sleep data because more than 1 sleep stage sample is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MoyoungSleepStageSample prevSample = null;
|
||||||
|
for (final MoyoungSleepStageSample sleepStageSample : sleepStageSamples) {
|
||||||
|
if (prevSample == null) {
|
||||||
|
prevSample = sleepStageSample;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final ActivityKind sleepRawKind = sleepStageToActivityKind(prevSample.getStage());
|
||||||
|
if (sleepRawKind.equals(ActivityKind.AWAKE_SLEEP)) {
|
||||||
|
prevSample = sleepStageSample;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// round to the nearest minute, we don't need per-second granularity
|
||||||
|
final int tsSecondsPrev = (int) ((prevSample.getTimestamp() / 1000) / 60) * 60;
|
||||||
|
final int tsSecondsCur = (int) ((sleepStageSample.getTimestamp() / 1000) / 60) * 60;
|
||||||
|
for (int i = tsSecondsPrev; i < tsSecondsCur; i += 60) {
|
||||||
|
if (i < timestamp_from || i > timestamp_to) continue;
|
||||||
|
MoyoungActivitySample sample = sampleByTs.get(i);
|
||||||
|
if (sample == null) {
|
||||||
|
sample = new MoyoungActivitySample();
|
||||||
|
sample.setTimestamp(i);
|
||||||
|
sample.setProvider(this);
|
||||||
|
sampleByTs.put(i, sample);
|
||||||
|
}
|
||||||
|
sample.setRawKind(toRawActivityKind(sleepRawKind));
|
||||||
|
sample.setRawIntensity(ActivitySample.NOT_MEASURED);
|
||||||
|
}
|
||||||
|
prevSample = sleepStageSample;
|
||||||
|
}
|
||||||
|
if (prevSample != null && !sleepStageToActivityKind(prevSample.getStage()).equals(ActivityKind.AWAKE_SLEEP)) {
|
||||||
|
LOG.warn("Last sleep stage sample was not of type awake");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the activity kind from NOT_MEASURED to new_raw_activity_kind on the given range
|
* Set the activity kind from NOT_MEASURED to new_raw_activity_kind on the given range
|
||||||
* @param timestamp_from the start timestamp
|
* @param timestamp_from the start timestamp
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
/* Copyright (C) 2025 Arjan Schrijver
|
||||||
|
|
||||||
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Gadgetbridge is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.devices.moyoung.samples;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import de.greenrobot.dao.AbstractDao;
|
||||||
|
import de.greenrobot.dao.Property;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractTimeSampleProvider;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.MoyoungSleepStageSample;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.MoyoungSleepStageSampleDao;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
|
||||||
|
public class MoyoungSleepStageSampleProvider extends AbstractTimeSampleProvider<MoyoungSleepStageSample> {
|
||||||
|
public MoyoungSleepStageSampleProvider(final GBDevice device, final DaoSession session) {
|
||||||
|
super(device, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public AbstractDao<MoyoungSleepStageSample, ?> getSampleDao() {
|
||||||
|
return getSession().getMoyoungSleepStageSampleDao();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Property getTimestampSampleProperty() {
|
||||||
|
return MoyoungSleepStageSampleDao.Properties.Timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Property getDeviceIdentifierSampleProperty() {
|
||||||
|
return MoyoungSleepStageSampleDao.Properties.DeviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MoyoungSleepStageSample createSample() {
|
||||||
|
return new MoyoungSleepStageSample();
|
||||||
|
}
|
||||||
|
}
|
@ -72,6 +72,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.moyoung.MoyoungWeatherToday;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.devices.moyoung.samples.MoyoungActivitySampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.moyoung.samples.MoyoungActivitySampleProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.moyoung.samples.MoyoungBloodPressureSampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.moyoung.samples.MoyoungBloodPressureSampleProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.moyoung.samples.MoyoungHeartRateSampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.moyoung.samples.MoyoungHeartRateSampleProvider;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.moyoung.samples.MoyoungSleepStageSampleProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.moyoung.samples.MoyoungSpo2SampleProvider;
|
import nodomain.freeyourgadget.gadgetbridge.devices.moyoung.samples.MoyoungSpo2SampleProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.moyoung.settings.MoyoungEnumDeviceVersion;
|
import nodomain.freeyourgadget.gadgetbridge.devices.moyoung.settings.MoyoungEnumDeviceVersion;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.moyoung.settings.MoyoungEnumLanguage;
|
import nodomain.freeyourgadget.gadgetbridge.devices.moyoung.settings.MoyoungEnumLanguage;
|
||||||
@ -87,6 +88,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.entities.MoyoungActivitySample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.MoyoungActivitySample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.MoyoungBloodPressureSample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.MoyoungBloodPressureSample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.MoyoungHeartRateSample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.MoyoungHeartRateSample;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.MoyoungSleepStageSample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.MoyoungSpo2Sample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.MoyoungSpo2Sample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
@ -1186,8 +1188,13 @@ public class MoyoungDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
if (data.length % 3 != 0)
|
if (data.length % 3 != 0)
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
|
|
||||||
int prevActivityType = MoyoungActivitySampleProvider.ACTIVITY_SLEEP_START;
|
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||||
int prevSampleTimestamp = -1;
|
MoyoungSleepStageSampleProvider provider = new MoyoungSleepStageSampleProvider(getDevice(), dbHandler.getDaoSession());
|
||||||
|
|
||||||
|
User user = DBHelper.getUser(dbHandler.getDaoSession());
|
||||||
|
Device device = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession());
|
||||||
|
|
||||||
|
List<MoyoungSleepStageSample> samples = new ArrayList<>();
|
||||||
|
|
||||||
for(int i = 0; i < data.length / 3; i++)
|
for(int i = 0; i < data.length / 3; i++)
|
||||||
{
|
{
|
||||||
@ -1197,115 +1204,33 @@ public class MoyoungDeviceSupport extends AbstractBTLEDeviceSupport {
|
|||||||
|
|
||||||
LOG.info("sleep[" + daysAgo + "][" + i + "] type=" + type + ", start_h=" + start_h + ", start_m=" + start_m);
|
LOG.info("sleep[" + daysAgo + "][" + i + "] type=" + type + ", start_h=" + start_h + ", start_m=" + start_m);
|
||||||
|
|
||||||
// SleepAnalysis measures sleep fragment type by marking the END of the fragment.
|
|
||||||
// The watch provides data by marking the START of the fragment.
|
|
||||||
|
|
||||||
// Additionally, ActivityAnalysis (used by the weekly view...) does AVERAGING when
|
|
||||||
// adjacent samples are not of the same type..
|
|
||||||
|
|
||||||
// FIXME: The way Gadgetbridge does it seems kinda broken...
|
|
||||||
|
|
||||||
// This means that we have to convert the data when importing. Each sample gets
|
|
||||||
// converted to two samples - one marking the beginning of the segment, and another
|
|
||||||
// marking the end.
|
|
||||||
|
|
||||||
// Watch: SLEEP_LIGHT ... SLEEP_DEEP ... SLEEP_LIGHT ... SLEEP_SOBER
|
|
||||||
// Gadgetbridge: ANYTHING,SLEEP_LIGHT ... SLEEP_LIGHT,SLEEP_DEEP ... SLEEP_DEEP,SLEEP_LIGHT ... SLEEP_LIGHT,ANYTHING
|
|
||||||
// ^ ^- this is important, it MUST be sleep, to ensure proper detection
|
|
||||||
// Time since the last -| of sleepStart, see SleepAnalysis.calculateSleepSessions
|
|
||||||
// sample must be 0
|
|
||||||
// (otherwise SleepAnalysis will include this fragment...)
|
|
||||||
|
|
||||||
// This means that when inserting samples:
|
|
||||||
// * every sample is converted to (previous_sample_type, current_sample_type) happening
|
|
||||||
// roughly at the same time (but in this order)
|
|
||||||
// * the first sample is prefixed by unspecified activity
|
|
||||||
// * the last sample (SOBER) is converted to unspecified activity
|
|
||||||
|
|
||||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
|
||||||
User user = DBHelper.getUser(dbHandler.getDaoSession());
|
|
||||||
Device device = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession());
|
|
||||||
|
|
||||||
MoyoungActivitySampleProvider provider = new MoyoungActivitySampleProvider(getDevice(), dbHandler.getDaoSession());
|
|
||||||
|
|
||||||
Calendar thisSample = Calendar.getInstance();
|
Calendar thisSample = Calendar.getInstance();
|
||||||
thisSample.add(Calendar.HOUR_OF_DAY, 4); // the clock assumes the sleep day changes at 20:00, so move the time forward to make the day correct
|
thisSample.add(Calendar.DAY_OF_MONTH, -daysAgo);
|
||||||
thisSample.set(Calendar.MINUTE, 0);
|
|
||||||
thisSample.add(Calendar.DATE, -daysAgo);
|
|
||||||
|
|
||||||
thisSample.set(Calendar.HOUR_OF_DAY, start_h);
|
thisSample.set(Calendar.HOUR_OF_DAY, start_h);
|
||||||
thisSample.set(Calendar.MINUTE, start_m);
|
thisSample.set(Calendar.MINUTE, start_m);
|
||||||
thisSample.set(Calendar.SECOND, 0);
|
thisSample.set(Calendar.SECOND, 0);
|
||||||
thisSample.set(Calendar.MILLISECOND, 0);
|
thisSample.set(Calendar.MILLISECOND, 0);
|
||||||
int thisSampleTimestamp = (int) (thisSample.getTimeInMillis() / 1000);
|
if (start_h >= 20) {
|
||||||
|
// Evening sleep is considered to be a day earlier
|
||||||
int activityType;
|
thisSample.add(Calendar.MINUTE, -1440);
|
||||||
if (type == MoyoungConstants.SLEEP_SOBER)
|
|
||||||
activityType = MoyoungActivitySampleProvider.ACTIVITY_SLEEP_END;
|
|
||||||
else if (type == MoyoungConstants.SLEEP_LIGHT)
|
|
||||||
activityType = MoyoungActivitySampleProvider.ACTIVITY_SLEEP_LIGHT;
|
|
||||||
else if (type == MoyoungConstants.SLEEP_RESTFUL)
|
|
||||||
activityType = MoyoungActivitySampleProvider.ACTIVITY_SLEEP_RESTFUL;
|
|
||||||
else
|
|
||||||
throw new IllegalArgumentException("Invalid sleep type");
|
|
||||||
|
|
||||||
// Insert the end of previous segment sample
|
|
||||||
MoyoungActivitySample prevSegmentSample = new MoyoungActivitySample();
|
|
||||||
prevSegmentSample.setDevice(device);
|
|
||||||
prevSegmentSample.setUser(user);
|
|
||||||
prevSegmentSample.setProvider(provider);
|
|
||||||
prevSegmentSample.setTimestamp(thisSampleTimestamp - 1);
|
|
||||||
|
|
||||||
prevSegmentSample.setRawKind(prevActivityType);
|
|
||||||
prevSegmentSample.setDataSource(MoyoungActivitySampleProvider.SOURCE_SLEEP_SUMMARY);
|
|
||||||
|
|
||||||
// prevSegmentSample.setBatteryLevel(ActivitySample.NOT_MEASURED);
|
|
||||||
prevSegmentSample.setSteps(ActivitySample.NOT_MEASURED);
|
|
||||||
prevSegmentSample.setDistanceMeters(ActivitySample.NOT_MEASURED);
|
|
||||||
prevSegmentSample.setCaloriesBurnt(ActivitySample.NOT_MEASURED);
|
|
||||||
|
|
||||||
prevSegmentSample.setHeartRate(ActivitySample.NOT_MEASURED);
|
|
||||||
// prevSegmentSample.setBloodPressureSystolic(ActivitySample.NOT_MEASURED);
|
|
||||||
// prevSegmentSample.setBloodPressureDiastolic(ActivitySample.NOT_MEASURED);
|
|
||||||
// prevSegmentSample.setBloodOxidation(ActivitySample.NOT_MEASURED);
|
|
||||||
|
|
||||||
// addGBActivitySampleIfNotExists(provider, prevSegmentSample);
|
|
||||||
|
|
||||||
// Insert the start of new segment sample
|
|
||||||
MoyoungActivitySample nextSegmentSample = new MoyoungActivitySample();
|
|
||||||
nextSegmentSample.setDevice(device);
|
|
||||||
nextSegmentSample.setUser(user);
|
|
||||||
nextSegmentSample.setProvider(provider);
|
|
||||||
nextSegmentSample.setTimestamp(thisSampleTimestamp);
|
|
||||||
|
|
||||||
nextSegmentSample.setRawKind(activityType);
|
|
||||||
nextSegmentSample.setDataSource(MoyoungActivitySampleProvider.SOURCE_SLEEP_SUMMARY);
|
|
||||||
|
|
||||||
// nextSegmentSample.setBatteryLevel(ActivitySample.NOT_MEASURED);
|
|
||||||
nextSegmentSample.setSteps(ActivitySample.NOT_MEASURED);
|
|
||||||
nextSegmentSample.setDistanceMeters(ActivitySample.NOT_MEASURED);
|
|
||||||
nextSegmentSample.setCaloriesBurnt(ActivitySample.NOT_MEASURED);
|
|
||||||
|
|
||||||
nextSegmentSample.setHeartRate(ActivitySample.NOT_MEASURED);
|
|
||||||
// nextSegmentSample.setBloodPressureSystolic(ActivitySample.NOT_MEASURED);
|
|
||||||
// nextSegmentSample.setBloodPressureDiastolic(ActivitySample.NOT_MEASURED);
|
|
||||||
// nextSegmentSample.setBloodOxidation(ActivitySample.NOT_MEASURED);
|
|
||||||
|
|
||||||
// addGBActivitySampleIfNotExists(provider, nextSegmentSample);
|
|
||||||
|
|
||||||
// Set the activity type on all samples in this time period
|
|
||||||
if (prevActivityType != MoyoungActivitySampleProvider.ACTIVITY_SLEEP_START)
|
|
||||||
// provider.updateActivityInRange(prevSampleTimestamp, thisSampleTimestamp, prevActivityType);
|
|
||||||
|
|
||||||
prevActivityType = activityType;
|
|
||||||
if (prevActivityType == MoyoungActivitySampleProvider.ACTIVITY_SLEEP_END)
|
|
||||||
prevActivityType = MoyoungActivitySampleProvider.ACTIVITY_SLEEP_START;
|
|
||||||
prevSampleTimestamp = thisSampleTimestamp;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
LOG.error("Error saving samples: ", ex);
|
|
||||||
GB.toast(getContext(), "Error saving samples: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
|
|
||||||
GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MoyoungSleepStageSample currentSample = new MoyoungSleepStageSample();
|
||||||
|
currentSample.setDevice(device);
|
||||||
|
currentSample.setUser(user);
|
||||||
|
currentSample.setStage(type);
|
||||||
|
currentSample.setTimestamp(thisSample.getTimeInMillis());
|
||||||
|
samples.add(currentSample);
|
||||||
|
|
||||||
|
LOG.debug("Adding sleep stage sample: ts={} stage={}", thisSample.getTime(), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("Will persist {} sleep stage samples", samples.size());
|
||||||
|
provider.addSamples(samples);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOG.error("Error saving sleep stage samples: ", ex);
|
||||||
|
GB.toast(getContext(), "Error saving sleep stage samples: " + ex.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
|
||||||
|
GB.updateTransferNotification(null, "Data transfer failed", false, 0, getContext());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user