diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java
index bc31fa611..322f25086 100644
--- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java
+++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java
@@ -38,6 +38,14 @@ public class GBDaoGenerator {
private static final String SAMPLE_STEPS = "steps";
private static final String SAMPLE_RAW_KIND = "rawKind";
private static final String SAMPLE_HEART_RATE = "heartRate";
+ private static final String SAMPLE_HRV_WEEKLY_AVERAGE = "weeklyAverage";
+ private static final String SAMPLE_HRV_LAST_NIGHT_AVERAGE = "lastNightAverage";
+ private static final String SAMPLE_HRV_LAST_NIGHT_5MIN_HIGH = "lastNight5MinHigh";
+ private static final String SAMPLE_HRV_BASELINE_LOW_UPPER = "baselineLowUpper";
+ private static final String SAMPLE_HRV_BASELINE_BALANCED_LOWER = "baselineBalancedLower";
+ private static final String SAMPLE_HRV_BASELINE_BALANCED_UPPER = "baselineBalancedUpper";
+ private static final String SAMPLE_HRV_STATUS_NUM = "statusNum";
+ private static final String SAMPLE_HRV_VALUE = "value";
private static final String SAMPLE_TEMPERATURE = "temperature";
private static final String SAMPLE_TEMPERATURE_TYPE = "temperatureType";
private static final String SAMPLE_WEIGHT_KG = "weightKg";
@@ -136,6 +144,7 @@ public class GBDaoGenerator {
addColmiSleepSessionSample(schema, user, device);
addColmiSleepStageSample(schema, user, device);
addColmiHrvValueSample(schema, user, device);
+ addColmiHrvSummarySample(schema, user, device);
addHuaweiActivitySample(schema, user, device);
@@ -550,10 +559,23 @@ public class GBDaoGenerator {
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);
+ hrvValueSample.addIntProperty(SAMPLE_HRV_VALUE).notNull().codeBeforeGetter(OVERRIDE);
return hrvValueSample;
}
+ private static Entity addColmiHrvSummarySample(Schema schema, Entity user, Entity device) {
+ Entity hrvSummarySample = addEntity(schema, "ColmiHrvSummarySample");
+ addCommonTimeSampleProperties("AbstractHrvSummarySample", hrvSummarySample, user, device);
+ hrvSummarySample.addIntProperty(SAMPLE_HRV_WEEKLY_AVERAGE).codeBeforeGetter(OVERRIDE);
+ hrvSummarySample.addIntProperty(SAMPLE_HRV_LAST_NIGHT_AVERAGE).codeBeforeGetter(OVERRIDE);
+ hrvSummarySample.addIntProperty(SAMPLE_HRV_LAST_NIGHT_5MIN_HIGH).codeBeforeGetter(OVERRIDE);
+ hrvSummarySample.addIntProperty(SAMPLE_HRV_BASELINE_LOW_UPPER).codeBeforeGetter(OVERRIDE);
+ hrvSummarySample.addIntProperty(SAMPLE_HRV_BASELINE_BALANCED_LOWER).codeBeforeGetter(OVERRIDE);
+ hrvSummarySample.addIntProperty(SAMPLE_HRV_BASELINE_BALANCED_UPPER).codeBeforeGetter(OVERRIDE);
+ hrvSummarySample.addIntProperty(SAMPLE_HRV_STATUS_NUM).codeBeforeGetter(OVERRIDE);
+ return hrvSummarySample;
+ }
+
private static void addHeartRateProperties(Entity activitySample) {
activitySample.addIntProperty(SAMPLE_HEART_RATE).notNull().codeBeforeGetterAndSetter(OVERRIDE);
}
@@ -813,13 +835,13 @@ public class GBDaoGenerator {
private static Entity addGarminHrvSummarySample(Schema schema, Entity user, Entity device) {
Entity hrvSummarySample = addEntity(schema, "GarminHrvSummarySample");
addCommonTimeSampleProperties("AbstractHrvSummarySample", hrvSummarySample, user, device);
- hrvSummarySample.addIntProperty("weeklyAverage").codeBeforeGetter(OVERRIDE);
- hrvSummarySample.addIntProperty("lastNightAverage").codeBeforeGetter(OVERRIDE);
- hrvSummarySample.addIntProperty("lastNight5MinHigh").codeBeforeGetter(OVERRIDE);
- hrvSummarySample.addIntProperty("baselineLowUpper").codeBeforeGetter(OVERRIDE);
- hrvSummarySample.addIntProperty("baselineBalancedLower").codeBeforeGetter(OVERRIDE);
- hrvSummarySample.addIntProperty("baselineBalancedUpper").codeBeforeGetter(OVERRIDE);
- hrvSummarySample.addIntProperty("statusNum").codeBeforeGetter(OVERRIDE);
+ hrvSummarySample.addIntProperty(SAMPLE_HRV_WEEKLY_AVERAGE).codeBeforeGetter(OVERRIDE);
+ hrvSummarySample.addIntProperty(SAMPLE_HRV_LAST_NIGHT_AVERAGE).codeBeforeGetter(OVERRIDE);
+ hrvSummarySample.addIntProperty(SAMPLE_HRV_LAST_NIGHT_5MIN_HIGH).codeBeforeGetter(OVERRIDE);
+ hrvSummarySample.addIntProperty(SAMPLE_HRV_BASELINE_LOW_UPPER).codeBeforeGetter(OVERRIDE);
+ hrvSummarySample.addIntProperty(SAMPLE_HRV_BASELINE_BALANCED_LOWER).codeBeforeGetter(OVERRIDE);
+ hrvSummarySample.addIntProperty(SAMPLE_HRV_BASELINE_BALANCED_UPPER).codeBeforeGetter(OVERRIDE);
+ hrvSummarySample.addIntProperty(SAMPLE_HRV_STATUS_NUM).codeBeforeGetter(OVERRIDE);
return hrvSummarySample;
}
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 db7150c19..1a6136c6d 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
@@ -37,11 +37,13 @@ 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.ColmiHrvSummarySampleProvider;
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.ColmiHrvSummarySampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.ColmiHrvValueSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.ColmiSleepSessionSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.ColmiSleepStageSampleDao;
@@ -51,6 +53,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.HrvSummarySample;
import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample;
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
@@ -71,6 +74,7 @@ public abstract class AbstractColmiR0xCoordinator extends AbstractBLEDeviceCoord
put(session.getColmiStressSampleDao(), ColmiStressSampleDao.Properties.DeviceId);
put(session.getColmiSleepSessionSampleDao(), ColmiSleepSessionSampleDao.Properties.DeviceId);
put(session.getColmiSleepStageSampleDao(), ColmiSleepStageSampleDao.Properties.DeviceId);
+ put(session.getColmiHrvSummarySampleDao(), ColmiHrvSummarySampleDao.Properties.DeviceId);
put(session.getColmiHrvValueSampleDao(), ColmiHrvValueSampleDao.Properties.DeviceId);
}};
@@ -187,6 +191,11 @@ public abstract class AbstractColmiR0xCoordinator extends AbstractBLEDeviceCoord
return new ColmiStressSampleProvider(device, session);
}
+ @Override
+ public TimeSampleProvider extends HrvSummarySample> getHrvSummarySampleProvider(GBDevice device, DaoSession session) {
+ return new ColmiHrvSummarySampleProvider(device, session);
+ }
+
@Override
public TimeSampleProvider extends HrvValueSample> getHrvValueSampleProvider(final GBDevice device, final DaoSession session) {
return new ColmiHrvValueSampleProvider(device, session);
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 7e85ec05c..6238cd55b 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
@@ -37,12 +37,14 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiActivitySampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiHeartRateSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiHrvValueSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiSleepSessionSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiSleepStageSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiSpo2SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.colmi.samples.ColmiStressSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.ColmiActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.ColmiHeartRateSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiHrvValueSample;
import nodomain.freeyourgadget.gadgetbridge.entities.ColmiSleepSessionSample;
import nodomain.freeyourgadget.gadgetbridge.entities.ColmiSleepStageSample;
import nodomain.freeyourgadget.gadgetbridge.entities.ColmiSpo2Sample;
@@ -422,7 +424,59 @@ public class ColmiR0xPacketHandler {
}
}
- public static void historicalHRV(GBDevice device, Context context, byte[] value) {
-
+ public static void historicalHRV(GBDevice device, Context context, byte[] value, int daysAgo) {
+ LOG.info("Received HRV history sync packet: {}", StringUtils.bytesToHex(value));
+ int hrvPacketNr = value[1] & 0xff;
+ if (hrvPacketNr == 0xff) {
+ LOG.info("Empty HRV history, sync aborted");
+ device.unsetBusyTask();
+ device.sendDeviceUpdateIntent(context);
+ } else if (hrvPacketNr == 0) {
+ int packetsTotalNr = value[2];
+ LOG.info("HRV history packet {} out of total {}", hrvPacketNr, packetsTotalNr);
+ } else {
+ LOG.info("HRV history packet {}", hrvPacketNr);
+ Calendar sampleCal = Calendar.getInstance();
+ if (daysAgo != 0) {
+ sampleCal.add(Calendar.DAY_OF_MONTH, 0 - daysAgo);
+ sampleCal.set(Calendar.HOUR_OF_DAY, 0);
+ sampleCal.set(Calendar.MINUTE, 0);
+ }
+ sampleCal.set(Calendar.SECOND, 0);
+ sampleCal.set(Calendar.MILLISECOND, 0);
+ int startValue = hrvPacketNr == 1 ? 3 : 2; // packet 1 contains something in byte 2
+ int minutesInPreviousPackets = 0;
+ if (hrvPacketNr > 1) {
+ minutesInPreviousPackets = 12 * 30; // packet 1
+ minutesInPreviousPackets += (hrvPacketNr - 2) * 13 * 30;
+ }
+ for (int i = startValue; i < value.length - 1; i++) {
+ if (value[i] != 0x00) {
+ // Determine time of day
+ int minuteOfDay = minutesInPreviousPackets + (i - startValue) * 30;
+ sampleCal.set(Calendar.HOUR_OF_DAY, minuteOfDay / 60);
+ sampleCal.set(Calendar.MINUTE, minuteOfDay % 60);
+ LOG.info("Value {} is {} ms, time of day is {}", i, value[i] & 0xff, sampleCal.getTime());
+ // Build sample object and save in database
+ try (DBHandler db = GBApplication.acquireDB()) {
+ ColmiHrvValueSampleProvider sampleProvider = new ColmiHrvValueSampleProvider(device, db.getDaoSession());
+ Long userId = DBHelper.getUser(db.getDaoSession()).getId();
+ Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
+ ColmiHrvValueSample gbSample = new ColmiHrvValueSample();
+ gbSample.setDeviceId(deviceId);
+ gbSample.setUserId(userId);
+ gbSample.setTimestamp(sampleCal.getTimeInMillis());
+ gbSample.setValue(value[i] & 0xff);
+ sampleProvider.addSample(gbSample);
+ } catch (Exception e) {
+ LOG.error("Error acquiring database for recording HRV samples", e);
+ }
+ }
+ }
+ if (hrvPacketNr == 4) {
+ device.unsetBusyTask();
+ device.sendDeviceUpdateIntent(context);
+ }
+ }
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiHrvSummarySampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiHrvSummarySampleProvider.java
new file mode 100644
index 000000000..99214f8c6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/colmi/samples/ColmiHrvSummarySampleProvider.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.ColmiHrvSummarySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.ColmiHrvSummarySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class ColmiHrvSummarySampleProvider extends AbstractTimeSampleProvider {
+ public ColmiHrvSummarySampleProvider(final GBDevice device, final DaoSession session) {
+ super(device, session);
+ }
+
+ @NonNull
+ @Override
+ public AbstractDao getSampleDao() {
+ return getSession().getColmiHrvSummarySampleDao();
+ }
+
+ @NonNull
+ @Override
+ protected Property getTimestampSampleProperty() {
+ return ColmiHrvSummarySampleDao.Properties.Timestamp;
+ }
+
+ @NonNull
+ @Override
+ protected Property getDeviceIdentifierSampleProperty() {
+ return ColmiHrvSummarySampleDao.Properties.DeviceId;
+ }
+
+ @Override
+ public ColmiHrvSummarySample createSample() {
+ return new ColmiHrvSummarySample();
+ }
+}
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 e6f9fe846..646777ce4 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
@@ -290,7 +290,16 @@ public class ColmiR0xDeviceSupport extends AbstractBTLEDeviceSupport {
}
break;
case ColmiR0xConstants.CMD_SYNC_HRV:
- ColmiR0xPacketHandler.historicalHRV(getDevice(), getContext(), value);
+ getDevice().setBusyTask(getContext().getString(R.string.busy_task_fetch_hrv_data));
+ ColmiR0xPacketHandler.historicalHRV(getDevice(), getContext(), value, daysAgo);
+ if (!getDevice().isBusy()) {
+ if (daysAgo < 6) {
+ daysAgo++;
+ fetchHistoryHRV();
+ } else {
+ fetchRecordedDataFinished();
+ }
+ }
break;
case ColmiR0xConstants.CMD_FIND_DEVICE:
LOG.info("Received find device response: {}", StringUtils.bytesToHex(value));
@@ -364,7 +373,10 @@ public class ColmiR0xDeviceSupport extends AbstractBTLEDeviceSupport {
switch (value[1]) {
case ColmiR0xConstants.BIG_DATA_TYPE_SLEEP:
ColmiR0xPacketHandler.historicalSleep(getDevice(), getContext(), value);
+
+ daysAgo = 0;
fetchHistoryHRV();
+
// Signal history sync finished at this point, since older firmwares
// will not send anything back after requesting HRV history
fetchRecordedDataFinished();
@@ -675,11 +687,21 @@ public class ColmiR0xDeviceSupport extends AbstractBTLEDeviceSupport {
}
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));
+ if (daysAgo != 0) {
+ syncingDay.add(Calendar.DAY_OF_MONTH, 0 - daysAgo);
+ syncingDay.set(Calendar.HOUR_OF_DAY, 0);
+ syncingDay.set(Calendar.MINUTE, 0);
+ }
+ syncingDay.set(Calendar.SECOND, 0);
+ syncingDay.set(Calendar.MILLISECOND, 0);
+ ByteBuffer hrvHistoryRequestBB = ByteBuffer.allocate(5);
+ hrvHistoryRequestBB.order(ByteOrder.LITTLE_ENDIAN);
+ hrvHistoryRequestBB.put(0, ColmiR0xConstants.CMD_SYNC_HRV);
+ hrvHistoryRequestBB.putInt(1, daysAgo);
+ byte[] hrvHistoryRequest = buildPacket(hrvHistoryRequestBB.array());
+ LOG.info("Fetch historical HRV data request sent ({}): {}", syncingDay.getTime(), StringUtils.bytesToHex(hrvHistoryRequest));
sendWrite("hrvHistoryRequest", hrvHistoryRequest);
}
}