diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java index d08559c79..bc31fa611 100644 --- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java +++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java @@ -135,6 +135,7 @@ public class GBDaoGenerator { addColmiStressSample(schema, user, device); addColmiSleepSessionSample(schema, user, device); addColmiSleepStageSample(schema, user, device); + addColmiHrvValueSample(schema, user, device); addHuaweiActivitySample(schema, user, device); @@ -546,6 +547,13 @@ public class GBDaoGenerator { return sleepStageSample; } + private static Entity addColmiHrvValueSample(Schema schema, Entity user, Entity device) { + Entity hrvValueSample = addEntity(schema, "ColmiHrvValueSample"); + addCommonTimeSampleProperties("AbstractHrvValueSample", hrvValueSample, user, device); + hrvValueSample.addIntProperty("value").notNull().codeBeforeGetter(OVERRIDE); + return hrvValueSample; + } + private static void addHeartRateProperties(Entity activitySample) { activitySample.addIntProperty(SAMPLE_HEART_RATE).notNull().codeBeforeGetterAndSetter(OVERRIDE); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java index 19c0d45ea..141768f8b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java @@ -199,6 +199,7 @@ public class DeviceSettingsPreferenceConst { public static final String PREF_HEARTRATE_SLEEP_BREATHING_QUALITY_MONITORING = "heartrate_sleep_breathing_quality_monitoring"; public static final String PREF_SPO2_ALL_DAY_MONITORING = "spo2_all_day_monitoring_enabled"; public static final String PREF_SPO2_LOW_ALERT_THRESHOLD = "spo2_low_alert_threshold"; + public static final String PREF_HRV_ALL_DAY_MONITORING = "hrv_all_day_monitoring_enabled"; public static final String PREF_AUTOHEARTRATE_SWITCH = "pref_autoheartrate_switch"; public static final String PREF_AUTOHEARTRATE_SLEEP = "pref_autoheartrate_sleep"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java index 4c62a7152..ec5b491ac 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java @@ -552,6 +552,7 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i addPreferenceHandlerFor(PREF_HEARTRATE_SLEEP_BREATHING_QUALITY_MONITORING); addPreferenceHandlerFor(PREF_SPO2_ALL_DAY_MONITORING); addPreferenceHandlerFor(PREF_SPO2_LOW_ALERT_THRESHOLD); + addPreferenceHandlerFor(PREF_HRV_ALL_DAY_MONITORING); addPreferenceHandlerFor(PREF_DO_NOT_DISTURB_NOAUTO); addPreferenceHandlerFor(PREF_DO_NOT_DISTURB_NOAUTO_START); addPreferenceHandlerFor(PREF_DO_NOT_DISTURB_NOAUTO_END); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/AbstractColmiR0xCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/AbstractColmiR0xCoordinator.java index fe1c90bac..db7150c19 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/AbstractColmiR0xCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/AbstractColmiR0xCoordinator.java @@ -22,9 +22,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; -import de.greenrobot.dao.query.QueryBuilder; +import de.greenrobot.dao.AbstractDao; +import de.greenrobot.dao.Property; import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings; @@ -34,10 +37,12 @@ import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiActivitySampleProvider; +import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiHrvValueSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiSpo2SampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiStressSampleProvider; import nodomain.freeyourgadget.gadgetbridge.entities.ColmiActivitySampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.ColmiHeartRateSampleDao; +import nodomain.freeyourgadget.gadgetbridge.entities.ColmiHrvValueSampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.ColmiSleepSessionSampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.ColmiSleepStageSampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.ColmiSpo2SampleDao; @@ -46,6 +51,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; +import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample; import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample; import nodomain.freeyourgadget.gadgetbridge.model.StressSample; import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport; @@ -56,21 +62,23 @@ public abstract class AbstractColmiR0xCoordinator extends AbstractBLEDeviceCoord @Override protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException { - Long deviceId = device.getId(); - QueryBuilder qb; + final Long deviceId = device.getId(); - qb = session.getColmiActivitySampleDao().queryBuilder(); - qb.where(ColmiActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities(); - qb = session.getColmiHeartRateSampleDao().queryBuilder(); - qb.where(ColmiHeartRateSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities(); - qb = session.getColmiSpo2SampleDao().queryBuilder(); - qb.where(ColmiSpo2SampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities(); - qb = session.getColmiStressSampleDao().queryBuilder(); - qb.where(ColmiStressSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities(); - qb = session.getColmiSleepSessionSampleDao().queryBuilder(); - qb.where(ColmiSleepSessionSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities(); - qb = session.getColmiSleepStageSampleDao().queryBuilder(); - qb.where(ColmiSleepStageSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities(); + final Map, Property> daoMap = new HashMap, Property>() {{ + put(session.getColmiActivitySampleDao(), ColmiActivitySampleDao.Properties.DeviceId); + put(session.getColmiHeartRateSampleDao(), ColmiHeartRateSampleDao.Properties.DeviceId); + put(session.getColmiSpo2SampleDao(), ColmiSpo2SampleDao.Properties.DeviceId); + put(session.getColmiStressSampleDao(), ColmiStressSampleDao.Properties.DeviceId); + put(session.getColmiSleepSessionSampleDao(), ColmiSleepSessionSampleDao.Properties.DeviceId); + put(session.getColmiSleepStageSampleDao(), ColmiSleepStageSampleDao.Properties.DeviceId); + put(session.getColmiHrvValueSampleDao(), ColmiHrvValueSampleDao.Properties.DeviceId); + }}; + + for (final Map.Entry, Property> e : daoMap.entrySet()) { + e.getKey().queryBuilder() + .where(e.getValue().eq(deviceId)) + .buildDelete().executeDeleteWithoutDetachingEntities(); + } } @Override @@ -159,6 +167,11 @@ public abstract class AbstractColmiR0xCoordinator extends AbstractBLEDeviceCoord return true; } + @Override + public boolean supportsHrvMeasurement() { + return true; + } + @Override public SampleProvider getSampleProvider(GBDevice device, DaoSession session) { return new ColmiActivitySampleProvider(device, session); @@ -174,6 +187,11 @@ public abstract class AbstractColmiR0xCoordinator extends AbstractBLEDeviceCoord return new ColmiStressSampleProvider(device, session); } + @Override + public TimeSampleProvider getHrvValueSampleProvider(final GBDevice device, final DaoSession session) { + return new ColmiHrvValueSampleProvider(device, session); + } + @Override public List getHeartRateMeasurementIntervals() { return Arrays.asList( diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR0xConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR0xConstants.java index 8749e9417..fd80e8cbb 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR0xConstants.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR0xConstants.java @@ -38,6 +38,8 @@ public class ColmiR0xConstants { public static final byte CMD_PACKET_SIZE = 0x2f; public static final byte CMD_AUTO_STRESS_PREF = 0x36; public static final byte CMD_SYNC_STRESS = 0x37; + public static final byte CMD_AUTO_HRV_PREF = 0x38; + public static final byte CMD_SYNC_HRV = 0x39; public static final byte CMD_SYNC_ACTIVITY = 0x43; public static final byte CMD_FIND_DEVICE = 0x50; public static final byte CMD_MANUAL_HEART_RATE = 0x69; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR0xPacketHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR0xPacketHandler.java index 909f9e38d..7e85ec05c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR0xPacketHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/ColmiR0xPacketHandler.java @@ -96,6 +96,17 @@ public class ColmiR0xPacketHandler { support.evaluateGBDeviceEvent(eventUpdatePreferences); } + public static void hrvSettings(ColmiR0xDeviceSupport support, byte[] value) { + boolean enabled = value[2] == 0x01; + LOG.info("Received HRV preference: {}", enabled ? "enabled" : "disabled"); + GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences(); + eventUpdatePreferences.withPreference( + DeviceSettingsPreferenceConst.PREF_HRV_ALL_DAY_MONITORING, + enabled + ); + support.evaluateGBDeviceEvent(eventUpdatePreferences); + } + public static void goalsSettings(byte[] value) { int steps = BLETypeConversions.toUint32(value[2], value[3], value[4], (byte) 0); int calories = BLETypeConversions.toUint32(value[5], value[6], value[7], (byte) 0); @@ -410,4 +421,8 @@ public class ColmiR0xPacketHandler { } } } + + public static void historicalHRV(GBDevice device, Context context, byte[] value) { + + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiHrvValueSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiHrvValueSampleProvider.java new file mode 100644 index 000000000..6bb4e1cad --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiHrvValueSampleProvider.java @@ -0,0 +1,56 @@ +/* Copyright (C) 2024 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 . */ +package nodomain.freeyourgadget.gadgetbridge.devices.colmi.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.ColmiHrvValueSample; +import nodomain.freeyourgadget.gadgetbridge.entities.ColmiHrvValueSampleDao; +import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; + +public class ColmiHrvValueSampleProvider extends AbstractTimeSampleProvider { + public ColmiHrvValueSampleProvider(final GBDevice device, final DaoSession session) { + super(device, session); + } + + @NonNull + @Override + public AbstractDao getSampleDao() { + return getSession().getColmiHrvValueSampleDao(); + } + + @NonNull + @Override + protected Property getTimestampSampleProperty() { + return ColmiHrvValueSampleDao.Properties.Timestamp; + } + + @NonNull + @Override + protected Property getDeviceIdentifierSampleProperty() { + return ColmiHrvValueSampleDao.Properties.DeviceId; + } + + @Override + public ColmiHrvValueSample createSample() { + return new ColmiHrvValueSample(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/colmi/ColmiR0xDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/colmi/ColmiR0xDeviceSupport.java index 13a3903e2..e6f9fe846 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/colmi/ColmiR0xDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/colmi/ColmiR0xDeviceSupport.java @@ -91,15 +91,6 @@ public class ColmiR0xDeviceSupport extends AbstractBTLEDeviceSupport { deviceInfoProfile = new DeviceInfoProfile<>(this); deviceInfoProfile.addListener(mListener); addSupportedProfile(deviceInfoProfile); - -// try (DBHandler db = GBApplication.acquireDB()) { -// db.getDatabase().execSQL("DROP TABLE IF EXISTS 'COLMI_ACTIVITY_SAMPLE'"); -// db.getDatabase().execSQL("DROP TABLE IF EXISTS 'COLMI_HEART_RATE_SAMPLE'"); -// db.getDatabase().execSQL("DROP TABLE IF EXISTS 'COLMI_SPO2_SAMPLE'"); -// db.getDatabase().execSQL("DROP TABLE IF EXISTS 'COLMI_STRESS_SAMPLE'"); -// } catch (Exception e) { -// LOG.error("Error acquiring database", e); -// } } @Override @@ -277,6 +268,9 @@ public class ColmiR0xDeviceSupport extends AbstractBTLEDeviceSupport { case ColmiR0xConstants.CMD_AUTO_STRESS_PREF: ColmiR0xPacketHandler.stressSettings(this, value); break; + case ColmiR0xConstants.CMD_AUTO_HRV_PREF: + ColmiR0xPacketHandler.hrvSettings(this, value); + break; case ColmiR0xConstants.CMD_SYNC_STRESS: ColmiR0xPacketHandler.historicalStress(getDevice(), getContext(), value); if (!getDevice().isBusy()) { @@ -295,6 +289,9 @@ public class ColmiR0xDeviceSupport extends AbstractBTLEDeviceSupport { } } break; + case ColmiR0xConstants.CMD_SYNC_HRV: + ColmiR0xPacketHandler.historicalHRV(getDevice(), getContext(), value); + break; case ColmiR0xConstants.CMD_FIND_DEVICE: LOG.info("Received find device response: {}", StringUtils.bytesToHex(value)); break; @@ -367,6 +364,9 @@ public class ColmiR0xDeviceSupport extends AbstractBTLEDeviceSupport { switch (value[1]) { case ColmiR0xConstants.BIG_DATA_TYPE_SLEEP: ColmiR0xPacketHandler.historicalSleep(getDevice(), getContext(), value); + fetchHistoryHRV(); + // Signal history sync finished at this point, since older firmwares + // will not send anything back after requesting HRV history fetchRecordedDataFinished(); break; case ColmiR0xConstants.BIG_DATA_TYPE_SPO2: @@ -478,6 +478,12 @@ public class ColmiR0xDeviceSupport extends AbstractBTLEDeviceSupport { LOG.info("Stress preference request sent: {}", StringUtils.bytesToHex(stressPrefsPacket)); sendWrite("stressPreferenceRequest", stressPrefsPacket); break; + case DeviceSettingsPreferenceConst.PREF_HRV_ALL_DAY_MONITORING: + final boolean hrvEnabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HRV_ALL_DAY_MONITORING, false); + byte[] hrvPrefsPacket = buildPacket(new byte[]{ColmiR0xConstants.CMD_AUTO_HRV_PREF, ColmiR0xConstants.PREF_WRITE, (byte) (hrvEnabled ? 0x01 : 0x00)}); + LOG.info("HRV preference request sent: {}", StringUtils.bytesToHex(hrvPrefsPacket)); + sendWrite("hrvPreferenceRequest", hrvPrefsPacket); + break; } } @@ -538,6 +544,9 @@ public class ColmiR0xDeviceSupport extends AbstractBTLEDeviceSupport { request = buildPacket(new byte[]{ColmiR0xConstants.CMD_AUTO_SPO2_PREF, ColmiR0xConstants.PREF_READ}); LOG.info("Request SpO2 measurement setting from ring: {}", StringUtils.bytesToHex(request)); sendWrite("spo2SettingRequest", request); + request = buildPacket(new byte[]{ColmiR0xConstants.CMD_AUTO_HRV_PREF, ColmiR0xConstants.PREF_READ}); + LOG.info("Request HRV measurement setting from ring: {}", StringUtils.bytesToHex(request)); + sendWrite("hrvSettingRequest", request); request = buildPacket(new byte[]{ColmiR0xConstants.CMD_GOALS, ColmiR0xConstants.PREF_READ}); LOG.info("Request goals from ring: {}", StringUtils.bytesToHex(request)); sendWrite("goalsSettingRequest", request); @@ -664,4 +673,13 @@ public class ColmiR0xDeviceSupport extends AbstractBTLEDeviceSupport { LOG.info("Fetch historical sleep data request sent: {}", StringUtils.bytesToHex(sleepHistoryRequest)); sendCommand("sleepHistoryRequest", sleepHistoryRequest); } + + private void fetchHistoryHRV() { + getDevice().setBusyTask(getContext().getString(R.string.busy_task_fetch_hrv_data)); + getDevice().sendDeviceUpdateIntent(getContext()); + syncingDay = Calendar.getInstance(); + byte[] hrvHistoryRequest = buildPacket(new byte[]{ColmiR0xConstants.CMD_SYNC_HRV}); + LOG.info("Fetch historical HRV data request sent: {}", StringUtils.bytesToHex(hrvHistoryRequest)); + sendWrite("hrvHistoryRequest", hrvHistoryRequest); + } } diff --git a/app/src/main/res/drawable/baseline_bloodtype_24.xml b/app/src/main/res/drawable/baseline_bloodtype_24.xml new file mode 100644 index 000000000..1c78a42f9 --- /dev/null +++ b/app/src/main/res/drawable/baseline_bloodtype_24.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 63cd7848d..8bca66dda 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -693,6 +693,7 @@ Fetching stress data Fetching PAI data Fetching SpO2 data + Fetching HRV data Fetching heart rate data Fetching sleep data Fetching sleep respiratory rate data @@ -3355,4 +3356,10 @@ Show data from yesterday dimmed between the current time and midnight Current time indicator Show an indicator at the current time, to visually separate data from yesterday and today + Follow phone DND setting + When DND is enabled or disabled on the phone, automatically toggle it on the device too + Minimum amount of steps + Minimum amount of steps that need to be taken during the threshold minutes + HRV monitoring + Automatically monitor heart rate variability throughout the day diff --git a/app/src/main/res/xml/devicesettings_colmi_r0x.xml b/app/src/main/res/xml/devicesettings_colmi_r0x.xml index dd9de0758..1bb8fa204 100644 --- a/app/src/main/res/xml/devicesettings_colmi_r0x.xml +++ b/app/src/main/res/xml/devicesettings_colmi_r0x.xml @@ -1,24 +1,32 @@ - - - + + + +