From 69847c19ffea733bcddd3d08dc47ac042da4cf5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Sun, 1 Dec 2024 13:34:27 +0000 Subject: [PATCH] Xiaomi-protobuf: Persist heart pulse samples --- .../gadgetbridge/daogen/GBDaoGenerator.java | 9 ++- .../devices/HeartPulseSampleProvider.java | 55 +++++++++++++++++++ .../activity/impl/SleepDetailsParser.java | 43 +++++++++++++-- 3 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/HeartPulseSampleProvider.java diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java index 8f18dfbb3..66af8f3ae 100644 --- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java +++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java @@ -54,7 +54,7 @@ public class GBDaoGenerator { public static void main(String[] args) throws Exception { - final Schema schema = new Schema(90, MAIN_PACKAGE + ".entities"); + final Schema schema = new Schema(91, MAIN_PACKAGE + ".entities"); Entity userAttributes = addUserAttributes(schema); Entity user = addUserInfo(schema, userAttributes); @@ -81,6 +81,7 @@ public class GBDaoGenerator { addHuamiSleepRespiratoryRateSample(schema, user, device); addXiaomiActivitySample(schema, user, device); addXiaomiSleepTimeSamples(schema, user, device); + addHeartPulseSamples(schema, user, device); addXiaomiSleepStageSamples(schema, user, device); addXiaomiManualSamples(schema, user, device); addXiaomiDailySummarySamples(schema, user, device); @@ -410,6 +411,12 @@ public class GBDaoGenerator { return sample; } + private static Entity addHeartPulseSamples(Schema schema, Entity user, Entity device) { + Entity sample = addEntity(schema, "HeartPulseSample"); + addCommonTimeSampleProperties("AbstractTimeSample", sample, user, device); + return sample; + } + private static Entity addXiaomiSleepStageSamples(Schema schema, Entity user, Entity device) { Entity sample = addEntity(schema, "XiaomiSleepStageSample"); addCommonTimeSampleProperties("AbstractTimeSample", sample, user, device); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/HeartPulseSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/HeartPulseSampleProvider.java new file mode 100644 index 000000000..33b27c24b --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/HeartPulseSampleProvider.java @@ -0,0 +1,55 @@ +/* Copyright (C) 2024 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; + +import androidx.annotation.NonNull; + +import de.greenrobot.dao.AbstractDao; +import de.greenrobot.dao.Property; +import nodomain.freeyourgadget.gadgetbridge.entities.HeartPulseSample; +import nodomain.freeyourgadget.gadgetbridge.entities.HeartPulseSampleDao; +import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; + +public class HeartPulseSampleProvider extends AbstractTimeSampleProvider { + public HeartPulseSampleProvider(final GBDevice device, final DaoSession session) { + super(device, session); + } + + @NonNull + @Override + public AbstractDao getSampleDao() { + return getSession().getHeartPulseSampleDao(); + } + + @NonNull + @Override + protected Property getTimestampSampleProperty() { + return HeartPulseSampleDao.Properties.Timestamp; + } + + @NonNull + @Override + protected Property getDeviceIdentifierSampleProperty() { + return HeartPulseSampleDao.Properties.DeviceId; + } + + @Override + public HeartPulseSample createSample() { + return new HeartPulseSample(); + } +} 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 96cfd4262..d24d464f9 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 @@ -30,10 +30,12 @@ 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.HeartPulseSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiSleepStageSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiSleepTimeSampleProvider; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.Device; +import nodomain.freeyourgadget.gadgetbridge.entities.HeartPulseSample; import nodomain.freeyourgadget.gadgetbridge.entities.User; import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiSleepStageSample; import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiSleepTimeSample; @@ -140,6 +142,7 @@ public class SleepDetailsParser extends XiaomiActivityParser { } final List stages = new ArrayList<>(); + final List heartPulseSamples = new ArrayList<>(); LOG.debug("Sleep stage packets from offset {}", Integer.toHexString(buf.position())); // Do not crash if we face a buffer underflow, as the next parsing is not 100% fool-proof, @@ -181,7 +184,16 @@ public class SleepDetailsParser extends XiaomiActivityParser { final ByteBuffer dataBuf = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN); - if (type == 16) { + if (type == 1) { + long timestampAcc = ts; + while (dataBuf.position() < dataBuf.limit()) { + final int delta = dataBuf.get() & 0xff; + final HeartPulseSample heartPulseSample = new HeartPulseSample(); + heartPulseSample.setTimestamp(timestampAcc + delta); + heartPulseSamples.add(heartPulseSample); + timestampAcc += delta; + } + } else if (type == 16) { final int data_0 = dataBuf.get() & 0xFF; final int sleep_index = data_0 >> 4; final int wake_count = data_0 & 0x0F; @@ -243,6 +255,8 @@ public class SleepDetailsParser extends XiaomiActivityParser { summaries.add(sample); } + boolean persistSuccess = !stagesParseFailed; + // save all the samples that we got try (DBHandler handler = GBApplication.acquireDB()) { final DaoSession session = handler.getDaoSession(); @@ -271,7 +285,7 @@ public class SleepDetailsParser extends XiaomiActivityParser { } 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; + persistSuccess = false; } if (!stagesParseFailed && !stages.isEmpty()) { @@ -295,11 +309,32 @@ public class SleepDetailsParser extends XiaomiActivityParser { } catch (final Exception e) { GB.toast(support.getContext(), "Error saving sleep stage samples", Toast.LENGTH_LONG, GB.ERROR); LOG.error("Error saving sleep stage samples", e); - return false; + persistSuccess = false; } } - return !stagesParseFailed; + // Save the heart pulse samples + try (DBHandler handler = GBApplication.acquireDB()) { + final DaoSession session = handler.getDaoSession(); + final GBDevice gbDevice = support.getDevice(); + final Device device = DBHelper.getDevice(gbDevice, session); + final User user = DBHelper.getUser(session); + + final HeartPulseSampleProvider sampleProvider = new HeartPulseSampleProvider(gbDevice, session); + + for (final HeartPulseSample stageSample : heartPulseSamples) { + stageSample.setDevice(device); + stageSample.setUser(user); + } + + sampleProvider.addSamples(heartPulseSamples); + } catch (final Exception e) { + GB.toast(support.getContext(), "Error saving heart pulse samples", Toast.LENGTH_LONG, GB.ERROR); + LOG.error("Error saving heart pulse samples", e); + persistSuccess = false; + } + + return persistSuccess; } private static boolean readStagePacketHeader(final ByteBuffer buffer) {