mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-24 09:35:50 +01:00
Colmi R0x: Add support for HRV
This commit is contained in:
parent
4939de47c1
commit
c66467a915
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 <https://www.gnu.org/licenses/>. */
|
||||
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<ColmiHrvSummarySample> {
|
||||
public ColmiHrvSummarySampleProvider(final GBDevice device, final DaoSession session) {
|
||||
super(device, session);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public AbstractDao<ColmiHrvSummarySample, ?> 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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user