From 84692e54326635b483a5af269a7b5904101f350e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Fri, 1 Dec 2023 21:35:29 +0000 Subject: [PATCH] Mi Band 8: Persist bedtime and wakeup times to database --- .../gadgetbridge/daogen/GBDaoGenerator.java | 11 +++- .../xiaomi/XiaomiSleepTimeSampleProvider.java | 56 +++++++++++++++++++ .../activity/impl/SleepDetailsParser.java | 52 +++++++++++++++-- 3 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiSleepTimeSampleProvider.java diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java index d93ed745b..4b457d9e5 100644 --- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java +++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java @@ -45,7 +45,7 @@ public class GBDaoGenerator { public static void main(String[] args) throws Exception { - final Schema schema = new Schema(63, MAIN_PACKAGE + ".entities"); + final Schema schema = new Schema(64, MAIN_PACKAGE + ".entities"); Entity userAttributes = addUserAttributes(schema); Entity user = addUserInfo(schema, userAttributes); @@ -71,6 +71,7 @@ public class GBDaoGenerator { addHuamiPaiSample(schema, user, device); addHuamiSleepRespiratoryRateSample(schema, user, device); addXiaomiActivitySample(schema, user, device); + addXiaomiSleepTimeSamples(schema, user, device); addPebbleHealthActivitySample(schema, user, device); addPebbleHealthActivityKindOverlay(schema, user, device); addPebbleMisfitActivitySample(schema, user, device); @@ -338,6 +339,14 @@ public class GBDaoGenerator { return activitySample; } + private static Entity addXiaomiSleepTimeSamples(Schema schema, Entity user, Entity device) { + Entity sample = addEntity(schema, "XiaomiSleepTimeSample"); + addCommonTimeSampleProperties("AbstractTimeSample", sample, user, device); + sample.addLongProperty("wakeupTime"); + sample.addBooleanProperty("isAwake"); + return sample; + } + private static void addHeartRateProperties(Entity activitySample) { activitySample.addIntProperty(SAMPLE_HEART_RATE).notNull().codeBeforeGetterAndSetter(OVERRIDE); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiSleepTimeSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiSleepTimeSampleProvider.java new file mode 100644 index 000000000..0b031e8db --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiSleepTimeSampleProvider.java @@ -0,0 +1,56 @@ +/* Copyright (C) 2023 José Rebelo + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi; + +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.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiSleepTimeSample; +import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiSleepTimeSampleDao; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; + +public class XiaomiSleepTimeSampleProvider extends AbstractTimeSampleProvider { + public XiaomiSleepTimeSampleProvider(final GBDevice device, final DaoSession session) { + super(device, session); + } + + @NonNull + @Override + public AbstractDao getSampleDao() { + return getSession().getXiaomiSleepTimeSampleDao(); + } + + @NonNull + @Override + protected Property getTimestampSampleProperty() { + return XiaomiSleepTimeSampleDao.Properties.Timestamp; + } + + @NonNull + @Override + protected Property getDeviceIdentifierSampleProperty() { + return XiaomiSleepTimeSampleDao.Properties.DeviceId; + } + + @Override + public XiaomiSleepTimeSample createSample() { + return new XiaomiSleepTimeSample(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/SleepDetailsParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/SleepDetailsParser.java index 2164a69b3..3ae3f1804 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/SleepDetailsParser.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/impl/SleepDetailsParser.java @@ -16,15 +16,26 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl; +import android.widget.Toast; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.List; +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; +import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; +import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiSleepTimeSampleProvider; +import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiSleepTimeSample; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileId; import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityParser; +import nodomain.freeyourgadget.gadgetbridge.util.GB; public class SleepDetailsParser extends XiaomiActivityParser { private static final Logger LOG = LoggerFactory.getLogger(SleepDetailsParser.class); @@ -39,14 +50,45 @@ public class SleepDetailsParser extends XiaomiActivityParser { final ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); buf.get(); // header ? 0xF0 - buf.get(); // ? + final int isAwake = buf.get() & 0xff; // 0/1 final int bedTime = buf.getInt(); final int wakeupTime = buf.getInt(); - LOG.info("Bed time: {}, wake up time: {}", bedTime, wakeupTime); + LOG.debug("Sleep sample: bedTime: {}, wakeupTime: {}, isAwake: {}", bedTime, wakeupTime, isAwake); - // TODO save timestamps and overlay on activity - // TODO everything else... + final XiaomiSleepTimeSample sample = new XiaomiSleepTimeSample(); + sample.setTimestamp(bedTime * 1000L); + sample.setWakeupTime(wakeupTime * 1000L); + sample.setIsAwake(isAwake == 1); - return false; + // save all the samples that we got + try (DBHandler handler = GBApplication.acquireDB()) { + final DaoSession session = handler.getDaoSession(); + final GBDevice gbDevice = support.getDevice(); + + sample.setDevice(DBHelper.getDevice(gbDevice, session)); + sample.setUser(DBHelper.getUser(session)); + + final XiaomiSleepTimeSampleProvider sampleProvider = new XiaomiSleepTimeSampleProvider(gbDevice, session); + + // Check if there is already a later sleep sample - if so, ignore this one + // Samples for the same sleep will always have the same bedtime (timestamp), but we might get + // multiple bedtimes until the user wakes up + final List existingSamples = sampleProvider.getAllSamples(sample.getTimestamp(), sample.getTimestamp()); + if (!existingSamples.isEmpty()) { + final XiaomiSleepTimeSample existingSample = existingSamples.get(0); + if (existingSample.getWakeupTime() > sample.getWakeupTime()) { + LOG.warn("Ignoring sleep sample - existing sample is more recent ({})", existingSample.getWakeupTime()); + return true; + } + } + + sampleProvider.addSample(sample); + + return true; + } catch (final Exception e) { + GB.toast(support.getContext(), "Error saving sleep sample", Toast.LENGTH_LONG, GB.ERROR); + LOG.error("Error saving sleep sample", e); + return false; + } } }