Huawei: HRV, more sleep stat, resting HR, and emotions sync. No UI.

This commit is contained in:
Me7c7
2025-11-20 20:07:21 +01:00
committed by José Rebelo
parent 76735e5acd
commit c892a86ac5
14 changed files with 518 additions and 39 deletions
@@ -56,7 +56,7 @@ public class GBDaoGenerator {
private static final String TIMESTAMP_TO = "timestampTo";
public static void main(String[] args) throws Exception {
final Schema schema = new Schema(118, MAIN_PACKAGE + ".entities");
final Schema schema = new Schema(119, MAIN_PACKAGE + ".entities");
Entity userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes);
@@ -172,6 +172,8 @@ public class GBDaoGenerator {
addHuaweiSleepStageSample(schema, user, device);
addHuaweiSleepStatsSample(schema, user, device);
addHuaweiTemperatureSample(schema, user, device);
addHuaweiHrvValuesSample(schema, user, device);
addHuaweiEmotionsSample(schema, user, device);
addUltrahumanActivitySample(schema, user, device);
addUltrahumanDeviceStateSample(schema, user, device);
@@ -1558,6 +1560,7 @@ public class GBDaoGenerator {
);
activitySample.addIntProperty("spo").notNull();
activitySample.addIntProperty("heartRate").notNull();
activitySample.addIntProperty("restingHeartRate").notNull();
return activitySample;
}
@@ -1596,6 +1599,29 @@ public class GBDaoGenerator {
sample.addDoubleProperty("minBreathRate").notNull();
sample.addDoubleProperty("maxBreathRate").notNull();
sample.addIntProperty("hrvDayToBaseline").notNull();
sample.addIntProperty("maxHrvBaseline").notNull();
sample.addIntProperty("minHrvBaseline").notNull();
sample.addIntProperty("avgHrv").notNull();
sample.addIntProperty("breathRateDayToBaseline").notNull();
sample.addIntProperty("maxBreathRateBaseline").notNull();
sample.addIntProperty("minBreathRateBaseline").notNull();
sample.addIntProperty("avgBreathRate").notNull();
sample.addIntProperty("oxygenSaturationDayToBaseline").notNull();
sample.addIntProperty("maxOxygenSaturationBaseline").notNull();
sample.addIntProperty("minOxygenSaturationBaseline").notNull();
sample.addIntProperty("avgOxygenSaturation").notNull();
sample.addIntProperty("heartRateDayToBaseline").notNull();
sample.addIntProperty("maxHeartRateBaseline").notNull();
sample.addIntProperty("minHeartRateBaseline").notNull();
sample.addIntProperty("avgHeartRate").notNull();
sample.addIntProperty("rdi").notNull();
sample.addIntProperty("wakeCount").notNull();
sample.addIntProperty("turnOverCount").notNull();
sample.addLongProperty("prepareSleepTime").notNull();
sample.addIntProperty("wakeUpFeeling").notNull();
sample.addIntProperty("sleepVersion").notNull();
final Index indexWakeUp = new Index();
indexWakeUp.addProperty(wakeupTime);
sample.addIndex(indexWakeUp);
@@ -1613,6 +1639,26 @@ public class GBDaoGenerator {
return sample;
}
private static Entity addHuaweiHrvValuesSample(Schema schema, Entity user, Entity device) {
Entity sample = addEntity(schema, "HuaweiHrvValueSample");
addCommonTimeSampleProperties("AbstractHrvValueSample", sample, user, device);
sample.addLongProperty("lastTimestamp").notNull().index();
sample.addIntProperty("value").notNull().codeBeforeGetter(OVERRIDE);
return sample;
}
private static Entity addHuaweiEmotionsSample(Schema schema, Entity user, Entity device) {
Entity sample = addEntity(schema, "HuaweiEmotionsSample");
addCommonTimeSampleProperties("AbstractTimeSample", sample, user, device);
sample.addLongProperty("lastTimestamp").notNull().index();
sample.addIntProperty("status").notNull();
sample.addDoubleProperty("valenceCharacter");
sample.addIntProperty("originStatus");
sample.addDoubleProperty("arousalCharacter");
return sample;
}
private static Entity addHuaweiWorkoutSummarySample(Schema schema, Entity user, Entity device) {
Entity workoutSummary = addEntity(schema, "HuaweiWorkoutSummarySample");
@@ -54,3 +54,4 @@ public class GadgetbridgeUpdate_118 implements DBUpdateScript {
public void downgradeSchema(SQLiteDatabase db) {
}
}
@@ -0,0 +1,135 @@
package nodomain.freeyourgadget.gadgetbridge.database.schema;
import android.database.sqlite.SQLiteDatabase;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStatsSampleDao;
public class GadgetbridgeUpdate_119 implements DBUpdateScript {
@Override
public void upgradeSchema(final SQLiteDatabase db) {
if (!DBHelper.existsColumn(HuaweiSleepStatsSampleDao.TABLENAME, HuaweiSleepStatsSampleDao.Properties.HrvDayToBaseline.columnName, db)) {
final String statement = "ALTER TABLE " + HuaweiSleepStatsSampleDao.TABLENAME + " ADD COLUMN \""
+ HuaweiSleepStatsSampleDao.Properties.HrvDayToBaseline.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
db.execSQL(statement);
}
if (!DBHelper.existsColumn(HuaweiSleepStatsSampleDao.TABLENAME, HuaweiSleepStatsSampleDao.Properties.MaxHrvBaseline.columnName, db)) {
final String statement = "ALTER TABLE " + HuaweiSleepStatsSampleDao.TABLENAME + " ADD COLUMN \""
+ HuaweiSleepStatsSampleDao.Properties.MaxHrvBaseline.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
db.execSQL(statement);
}
if (!DBHelper.existsColumn(HuaweiSleepStatsSampleDao.TABLENAME, HuaweiSleepStatsSampleDao.Properties.MinHrvBaseline.columnName, db)) {
final String statement = "ALTER TABLE " + HuaweiSleepStatsSampleDao.TABLENAME + " ADD COLUMN \""
+ HuaweiSleepStatsSampleDao.Properties.MinHrvBaseline.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
db.execSQL(statement);
}
if (!DBHelper.existsColumn(HuaweiSleepStatsSampleDao.TABLENAME, HuaweiSleepStatsSampleDao.Properties.AvgHrv.columnName, db)) {
final String statement = "ALTER TABLE " + HuaweiSleepStatsSampleDao.TABLENAME + " ADD COLUMN \""
+ HuaweiSleepStatsSampleDao.Properties.AvgHrv.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
db.execSQL(statement);
}
if (!DBHelper.existsColumn(HuaweiSleepStatsSampleDao.TABLENAME, HuaweiSleepStatsSampleDao.Properties.BreathRateDayToBaseline.columnName, db)) {
final String statement = "ALTER TABLE " + HuaweiSleepStatsSampleDao.TABLENAME + " ADD COLUMN \""
+ HuaweiSleepStatsSampleDao.Properties.BreathRateDayToBaseline.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
db.execSQL(statement);
}
if (!DBHelper.existsColumn(HuaweiSleepStatsSampleDao.TABLENAME, HuaweiSleepStatsSampleDao.Properties.MaxBreathRateBaseline.columnName, db)) {
final String statement = "ALTER TABLE " + HuaweiSleepStatsSampleDao.TABLENAME + " ADD COLUMN \""
+ HuaweiSleepStatsSampleDao.Properties.MaxBreathRateBaseline.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
db.execSQL(statement);
}
if (!DBHelper.existsColumn(HuaweiSleepStatsSampleDao.TABLENAME, HuaweiSleepStatsSampleDao.Properties.MinBreathRateBaseline.columnName, db)) {
final String statement = "ALTER TABLE " + HuaweiSleepStatsSampleDao.TABLENAME + " ADD COLUMN \""
+ HuaweiSleepStatsSampleDao.Properties.MinBreathRateBaseline.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
db.execSQL(statement);
}
if (!DBHelper.existsColumn(HuaweiSleepStatsSampleDao.TABLENAME, HuaweiSleepStatsSampleDao.Properties.AvgBreathRate.columnName, db)) {
final String statement = "ALTER TABLE " + HuaweiSleepStatsSampleDao.TABLENAME + " ADD COLUMN \""
+ HuaweiSleepStatsSampleDao.Properties.AvgBreathRate.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
db.execSQL(statement);
}
if (!DBHelper.existsColumn(HuaweiSleepStatsSampleDao.TABLENAME, HuaweiSleepStatsSampleDao.Properties.OxygenSaturationDayToBaseline.columnName, db)) {
final String statement = "ALTER TABLE " + HuaweiSleepStatsSampleDao.TABLENAME + " ADD COLUMN \""
+ HuaweiSleepStatsSampleDao.Properties.OxygenSaturationDayToBaseline.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
db.execSQL(statement);
}
if (!DBHelper.existsColumn(HuaweiSleepStatsSampleDao.TABLENAME, HuaweiSleepStatsSampleDao.Properties.MaxOxygenSaturationBaseline.columnName, db)) {
final String statement = "ALTER TABLE " + HuaweiSleepStatsSampleDao.TABLENAME + " ADD COLUMN \""
+ HuaweiSleepStatsSampleDao.Properties.MaxOxygenSaturationBaseline.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
db.execSQL(statement);
}
if (!DBHelper.existsColumn(HuaweiSleepStatsSampleDao.TABLENAME, HuaweiSleepStatsSampleDao.Properties.MinOxygenSaturationBaseline.columnName, db)) {
final String statement = "ALTER TABLE " + HuaweiSleepStatsSampleDao.TABLENAME + " ADD COLUMN \""
+ HuaweiSleepStatsSampleDao.Properties.MinOxygenSaturationBaseline.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
db.execSQL(statement);
}
if (!DBHelper.existsColumn(HuaweiSleepStatsSampleDao.TABLENAME, HuaweiSleepStatsSampleDao.Properties.AvgOxygenSaturation.columnName, db)) {
final String statement = "ALTER TABLE " + HuaweiSleepStatsSampleDao.TABLENAME + " ADD COLUMN \""
+ HuaweiSleepStatsSampleDao.Properties.AvgOxygenSaturation.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
db.execSQL(statement);
}
if (!DBHelper.existsColumn(HuaweiSleepStatsSampleDao.TABLENAME, HuaweiSleepStatsSampleDao.Properties.HeartRateDayToBaseline.columnName, db)) {
final String statement = "ALTER TABLE " + HuaweiSleepStatsSampleDao.TABLENAME + " ADD COLUMN \""
+ HuaweiSleepStatsSampleDao.Properties.HeartRateDayToBaseline.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
db.execSQL(statement);
}
if (!DBHelper.existsColumn(HuaweiSleepStatsSampleDao.TABLENAME, HuaweiSleepStatsSampleDao.Properties.MaxHeartRateBaseline.columnName, db)) {
final String statement = "ALTER TABLE " + HuaweiSleepStatsSampleDao.TABLENAME + " ADD COLUMN \""
+ HuaweiSleepStatsSampleDao.Properties.MaxHeartRateBaseline.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
db.execSQL(statement);
}
if (!DBHelper.existsColumn(HuaweiSleepStatsSampleDao.TABLENAME, HuaweiSleepStatsSampleDao.Properties.MinHeartRateBaseline.columnName, db)) {
final String statement = "ALTER TABLE " + HuaweiSleepStatsSampleDao.TABLENAME + " ADD COLUMN \""
+ HuaweiSleepStatsSampleDao.Properties.MinHeartRateBaseline.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
db.execSQL(statement);
}
if (!DBHelper.existsColumn(HuaweiSleepStatsSampleDao.TABLENAME, HuaweiSleepStatsSampleDao.Properties.AvgHeartRate.columnName, db)) {
final String statement = "ALTER TABLE " + HuaweiSleepStatsSampleDao.TABLENAME + " ADD COLUMN \""
+ HuaweiSleepStatsSampleDao.Properties.AvgHeartRate.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
db.execSQL(statement);
}
if (!DBHelper.existsColumn(HuaweiSleepStatsSampleDao.TABLENAME, HuaweiSleepStatsSampleDao.Properties.Rdi.columnName, db)) {
final String statement = "ALTER TABLE " + HuaweiSleepStatsSampleDao.TABLENAME + " ADD COLUMN \""
+ HuaweiSleepStatsSampleDao.Properties.Rdi.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
db.execSQL(statement);
}
if (!DBHelper.existsColumn(HuaweiSleepStatsSampleDao.TABLENAME, HuaweiSleepStatsSampleDao.Properties.WakeCount.columnName, db)) {
final String statement = "ALTER TABLE " + HuaweiSleepStatsSampleDao.TABLENAME + " ADD COLUMN \""
+ HuaweiSleepStatsSampleDao.Properties.WakeCount.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
db.execSQL(statement);
}
if (!DBHelper.existsColumn(HuaweiSleepStatsSampleDao.TABLENAME, HuaweiSleepStatsSampleDao.Properties.TurnOverCount.columnName, db)) {
final String statement = "ALTER TABLE " + HuaweiSleepStatsSampleDao.TABLENAME + " ADD COLUMN \""
+ HuaweiSleepStatsSampleDao.Properties.TurnOverCount.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
db.execSQL(statement);
}
if (!DBHelper.existsColumn(HuaweiSleepStatsSampleDao.TABLENAME, HuaweiSleepStatsSampleDao.Properties.PrepareSleepTime.columnName, db)) {
final String statement = "ALTER TABLE " + HuaweiSleepStatsSampleDao.TABLENAME + " ADD COLUMN \""
+ HuaweiSleepStatsSampleDao.Properties.PrepareSleepTime.columnName + "\" LONG NOT NULL DEFAULT -1;";
db.execSQL(statement);
}
if (!DBHelper.existsColumn(HuaweiSleepStatsSampleDao.TABLENAME, HuaweiSleepStatsSampleDao.Properties.WakeUpFeeling.columnName, db)) {
final String statement = "ALTER TABLE " + HuaweiSleepStatsSampleDao.TABLENAME + " ADD COLUMN \""
+ HuaweiSleepStatsSampleDao.Properties.WakeUpFeeling.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
db.execSQL(statement);
}
if (!DBHelper.existsColumn(HuaweiSleepStatsSampleDao.TABLENAME, HuaweiSleepStatsSampleDao.Properties.SleepVersion.columnName, db)) {
final String statement = "ALTER TABLE " + HuaweiSleepStatsSampleDao.TABLENAME + " ADD COLUMN \""
+ HuaweiSleepStatsSampleDao.Properties.SleepVersion.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
db.execSQL(statement);
}
if (!DBHelper.existsColumn(HuaweiActivitySampleDao.TABLENAME, HuaweiActivitySampleDao.Properties.RestingHeartRate.columnName, db)) {
final String statement = "ALTER TABLE " + HuaweiActivitySampleDao.TABLENAME + " ADD COLUMN \""
+ HuaweiActivitySampleDao.Properties.RestingHeartRate.columnName + "\" INTEGER NOT NULL DEFAULT -1;";
db.execSQL(statement);
}
}
@Override
public void downgradeSchema(SQLiteDatabase database) {
}
}
@@ -40,6 +40,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample;
import nodomain.freeyourgadget.gadgetbridge.model.SleepScoreSample;
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
@@ -235,6 +236,8 @@ public abstract class HuaweiBRCoordinator extends AbstractBLClassicDeviceCoordin
@Override
public boolean supportsSleepScore(@NonNull final GBDevice device) { return huaweiCoordinator.getSupportsNewTrueSleep(device); }
@Override
public boolean supportsHrvMeasurement(@NonNull GBDevice device) { return huaweiCoordinator.supportsHRV();}
@Override
public InstallHandler findInstallHandler(Uri uri, Context context) {
@@ -271,6 +274,11 @@ public abstract class HuaweiBRCoordinator extends AbstractBLClassicDeviceCoordin
return new HuaweiSleepScoreSampleProvider(device, session);
}
@Override
public TimeSampleProvider<? extends HrvValueSample> getHrvValueSampleProvider(final GBDevice device, final DaoSession session) {
return new HuaweiHrvValueSampleProvider(device, session);
}
@Override
public int[] getStressRanges() {
return huaweiCoordinator.getStressRanges();
@@ -50,9 +50,12 @@ import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictData;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataValuesDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiEmotionsSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiHrvValueSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStageSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStatsSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiStressSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiTemperatureSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutPaceSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSpO2SampleDao;
@@ -140,6 +143,13 @@ public class HuaweiCoordinator {
QueryBuilder<?> stressQb = session.getHuaweiStressSampleDao().queryBuilder();
stressQb.where(HuaweiStressSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
QueryBuilder<?> hrvQb = session.getHuaweiHrvValueSampleDao().queryBuilder();
hrvQb.where(HuaweiHrvValueSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
session.getHuaweiTemperatureSampleDao().queryBuilder().where(HuaweiTemperatureSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
session.getHuaweiEmotionsSampleDao().queryBuilder().where(HuaweiEmotionsSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
QueryBuilder<HuaweiWorkoutSummarySample> qb2 = session.getHuaweiWorkoutSummarySampleDao().queryBuilder();
List<HuaweiWorkoutSummarySample> workouts = qb2.where(HuaweiWorkoutSummarySampleDao.Properties.DeviceId.eq(deviceId)).build().list();
for (HuaweiWorkoutSummarySample sample : workouts) {
@@ -972,7 +982,11 @@ public class HuaweiCoordinator {
return false;
}
public boolean supportsHRV() {
if (supportsExpandCapability())
return supportsExpandCapability(235);
return false;
}
public boolean supportsPromptPushMessage () {
// do not ask for capabilities under specific condition
@@ -27,4 +27,15 @@ public class HuaweiDictTypes {
public static final int SLEEP_DETAILS_CLASS = 700013;
public static final int ARRHYTHMIA_CLASS = 700004;
public static final int HRV_CLASS = 500044;
public static final int HRV_RMSSD_VALUE = 500044831;
public static final int EMOTION_CLASS = 500031;
public static final int EMOTION_STATUS_VALUE = 500031195;
public static final int EMOTION_VALENCE_CHARACTER_VALUE = 500031347;
public static final int EMOTION_ORIGIN_STATUS_VALUE = 500031160;
public static final int EMOTION_AROUSAL_CHARACTER_VALUE = 500031842;
}
@@ -0,0 +1,67 @@
package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
import androidx.annotation.NonNull;
import java.util.List;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractTimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiEmotionsSample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiEmotionsSampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class HuaweiEmotionsSampleProvider extends AbstractTimeSampleProvider<HuaweiEmotionsSample> {
public HuaweiEmotionsSampleProvider(final GBDevice device, final DaoSession session) {
super(device, session);
}
@NonNull
@Override
public AbstractDao<HuaweiEmotionsSample, ?> getSampleDao() {
return getSession().getHuaweiEmotionsSampleDao();
}
@NonNull
@Override
protected Property getTimestampSampleProperty() {
return HuaweiEmotionsSampleDao.Properties.Timestamp;
}
@NonNull
@Override
protected Property getDeviceIdentifierSampleProperty() {
return HuaweiEmotionsSampleDao.Properties.DeviceId;
}
@Override
public HuaweiEmotionsSample createSample() {
return new HuaweiEmotionsSample();
}
public long getLastFetchTimestamp() {
QueryBuilder<HuaweiEmotionsSample> qb = getSampleDao().queryBuilder();
Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
if (dbDevice == null)
return 0;
final Property deviceProperty = HuaweiEmotionsSampleDao.Properties.DeviceId;
final Property timestampProperty = HuaweiEmotionsSampleDao.Properties.LastTimestamp;
qb.where(deviceProperty.eq(dbDevice.getId()))
.orderDesc(timestampProperty)
.limit(1);
List<HuaweiEmotionsSample> samples = qb.build().list();
if (samples.isEmpty())
return 0;
HuaweiEmotionsSample sample = samples.get(0);
return sample.getLastTimestamp();
}
}
@@ -0,0 +1,67 @@
package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
import androidx.annotation.NonNull;
import java.util.List;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractTimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiHrvValueSample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiHrvValueSampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class HuaweiHrvValueSampleProvider extends AbstractTimeSampleProvider<HuaweiHrvValueSample> {
public HuaweiHrvValueSampleProvider(final GBDevice device, final DaoSession session) {
super(device, session);
}
@NonNull
@Override
public AbstractDao<HuaweiHrvValueSample, ?> getSampleDao() {
return getSession().getHuaweiHrvValueSampleDao();
}
@NonNull
@Override
protected Property getTimestampSampleProperty() {
return HuaweiHrvValueSampleDao.Properties.Timestamp;
}
@NonNull
@Override
protected Property getDeviceIdentifierSampleProperty() {
return HuaweiHrvValueSampleDao.Properties.DeviceId;
}
@Override
public HuaweiHrvValueSample createSample() {
return new HuaweiHrvValueSample();
}
public long getLastFetchTimestamp() {
QueryBuilder<HuaweiHrvValueSample> qb = getSampleDao().queryBuilder();
Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
if (dbDevice == null)
return 0;
final Property deviceProperty = HuaweiHrvValueSampleDao.Properties.DeviceId;
final Property timestampProperty = HuaweiHrvValueSampleDao.Properties.LastTimestamp;
qb.where(deviceProperty.eq(dbDevice.getId()))
.orderDesc(timestampProperty)
.limit(1);
List<HuaweiHrvValueSample> samples = qb.build().list();
if (samples.isEmpty())
return 0;
HuaweiHrvValueSample sample = samples.get(0);
return sample.getLastTimestamp();
}
}
@@ -41,6 +41,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample;
import nodomain.freeyourgadget.gadgetbridge.model.SleepScoreSample;
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
@@ -244,6 +245,9 @@ public abstract class HuaweiLECoordinator extends AbstractBLEDeviceCoordinator i
@Override
public boolean supportsSleepScore(@NonNull final GBDevice device) { return huaweiCoordinator.getSupportsNewTrueSleep(device); }
@Override
public boolean supportsHrvMeasurement(@NonNull GBDevice device) { return huaweiCoordinator.supportsHRV();}
@Override
public InstallHandler findInstallHandler(Uri uri, Context context) {
return huaweiCoordinator.getInstallHandler(uri, context);
@@ -279,6 +283,11 @@ public abstract class HuaweiLECoordinator extends AbstractBLEDeviceCoordinator i
return new HuaweiSleepScoreSampleProvider(device, session);
}
@Override
public TimeSampleProvider<? extends HrvValueSample> getHrvValueSampleProvider(final GBDevice device, final DaoSession session) {
return new HuaweiHrvValueSampleProvider(device, session);
}
@Override
public int[] getStressRanges() {
return huaweiCoordinator.getStressRanges();
@@ -215,7 +215,8 @@ public class HuaweiSampleProvider extends AbstractSampleProvider<HuaweiActivityS
sample.getCalories(),
sample.getDistance(),
sample.getSpo(),
sample.getHeartRate()
sample.getHeartRate(),
sample.getRestingHeartRate()
);
sampleCopy.setProvider(sample.getProvider());
return sampleCopy;
@@ -249,6 +250,7 @@ public class HuaweiSampleProvider extends AbstractSampleProvider<HuaweiActivityS
end.setDistance(ActivitySample.NOT_MEASURED);
end.setSpo(ActivitySample.NOT_MEASURED);
end.setHeartRate(ActivitySample.NOT_MEASURED);
end.setRestingHeartRate(ActivitySample.NOT_MEASURED);
end.setOtherTimestamp(start.getTimestamp());
newSamples.add(start);
@@ -379,6 +381,7 @@ public class HuaweiSampleProvider extends AbstractSampleProvider<HuaweiActivityS
ActivitySample.NOT_MEASURED,
ActivitySample.NOT_MEASURED,
ActivitySample.NOT_MEASURED,
ActivitySample.NOT_MEASURED,
ActivitySample.NOT_MEASURED);
activitySample.setProvider(this);
return activitySample;
@@ -587,7 +590,8 @@ public class HuaweiSampleProvider extends AbstractSampleProvider<HuaweiActivityS
ActivitySample.NOT_MEASURED,
ActivitySample.NOT_MEASURED,
ActivitySample.NOT_MEASURED,
hr
hr,
ActivitySample.NOT_MEASURED
);
newSample.setProvider(this);
return newSample;
@@ -356,6 +356,8 @@ public class FitnessData {
public int spo = -1;
public int restingHeartRate = -1;
public List<TV> unknownTVs = null;
@Override
@@ -414,7 +416,7 @@ public class FitnessData {
private static void parseData(SubContainer returnValue, byte[] data) {
int i = 0;
if (data.length <= 0) {
if (data.length == 0) {
returnValue.parsedData = null;
returnValue.parsedDataError = "Data is missing feature bitmap.";
return;
@@ -491,6 +493,8 @@ public class FitnessData {
if (bitToCheck == 0x01)
returnValue.spo = value;
else if (bitToCheck == 0x02)
returnValue.restingHeartRate = value;
else
returnValue.unknownTVs.add(tv);
}
@@ -71,7 +71,9 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinatorSupp
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinatorSupplier.HuaweiDeviceType;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCrypto;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiDictTypes;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiEmotionsSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiGpsParser;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiHrvValueSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiSequenceDataFileParser;
@@ -92,6 +94,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiEmotionsSample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiHrvValueSample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStageSample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStatsSample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiStressSample;
@@ -864,7 +868,7 @@ public class HuaweiSupportProvider {
try {
// NOTE: register all DAta Sync handlers on the early stage. We can receive requests from the watch during initialization.
if(getHuaweiCoordinator().getSendCountryCodeEnabled(getDevice())) {
if (getHuaweiCoordinator().getSendCountryCodeEnabled(getDevice())) {
huaweiDataSyncFeatureManager = new HuaweiDataSyncFeatureManager(HuaweiSupportProvider.this);
}
@@ -883,13 +887,13 @@ public class HuaweiSupportProvider {
if (getHuaweiCoordinator().supportsArrhythmia() && getHuaweiCoordinator().isShowForceCountrySpecificFeatures(getCoordinator().getDevice())) {
huaweiDataSyncArrhythmia = new HuaweiDataSyncArrhythmia(HuaweiSupportProvider.this);
}
if (getHuaweiCoordinator().supportsECG() && getHuaweiCoordinator().isShowForceCountrySpecificFeatures(getCoordinator().getDevice())) {
if (getHuaweiCoordinator().supportsECG() && getHuaweiCoordinator().isShowForceCountrySpecificFeatures(getCoordinator().getDevice())) {
huaweiDataSyncEcg = new HuaweiDataSyncEcg(HuaweiSupportProvider.this);
}
if(getHuaweiCoordinator().supportsSleepApnea()) {
if (getHuaweiCoordinator().supportsSleepApnea()) {
huaweiDataSyncSleepApnea = new HuaweiDataSyncSleepApnea(HuaweiSupportProvider.this);
}
if(getHuaweiCoordinator().supportsArterialStiffnessDetection()) {
if (getHuaweiCoordinator().supportsArterialStiffnessDetection()) {
huaweiDataSyncArterialStiffnessDetection = new HuaweiDataSyncArterialStiffnessDetection(HuaweiSupportProvider.this);
}
@@ -1573,7 +1577,7 @@ public class HuaweiSupportProvider {
HuaweiP2PDataDictionarySyncService P2PSyncService = HuaweiP2PDataDictionarySyncService.getRegisteredInstance(huaweiP2PManager);
if (P2PSyncService != null) {
List<Integer> list = P2PSyncService.checkSupported(this.getHuaweiCoordinator(), Arrays.asList(HuaweiDictTypes.SKIN_TEMPERATURE_CLASS));
List<Integer> list = P2PSyncService.checkSupported(this.getHuaweiCoordinator(), Arrays.asList(HuaweiDictTypes.SKIN_TEMPERATURE_CLASS, HuaweiDictTypes.HRV_CLASS, HuaweiDictTypes.EMOTION_CLASS));
if (!list.isEmpty()) {
syncState.setP2pSync(true);
P2PSyncService.startSync(list, new HuaweiP2PDataDictionarySyncService.DictionarySyncCallback() {
@@ -1588,6 +1592,22 @@ public class HuaweiSupportProvider {
LOG.warn("Exception for getting temperature start time", e);
}
return 0;
} else if (dictClass == HuaweiDictTypes.HRV_CLASS) {
try (DBHandler db = GBApplication.acquireDB()) {
HuaweiHrvValueSampleProvider hrvStatsSampleProvider = new HuaweiHrvValueSampleProvider(gbDevice, db.getDaoSession());
return hrvStatsSampleProvider.getLastFetchTimestamp();
} catch (Exception e) {
LOG.warn("Exception for getting HRV start time", e);
}
return 0;
} else if (dictClass == HuaweiDictTypes.EMOTION_CLASS) {
try (DBHandler db = GBApplication.acquireDB()) {
HuaweiEmotionsSampleProvider emotionsStatsSampleProvider = new HuaweiEmotionsSampleProvider(gbDevice, db.getDaoSession());
return emotionsStatsSampleProvider.getLastFetchTimestamp();
} catch (Exception e) {
LOG.warn("Exception for getting HRV start time", e);
}
return 0;
}
return -1;
}
@@ -1613,7 +1633,6 @@ public class HuaweiSupportProvider {
temperatureSamples.add(sample);
}
}
}
}
try (DBHandler db = GBApplication.acquireDB()) {
@@ -1622,6 +1641,88 @@ public class HuaweiSupportProvider {
} catch (Exception e) {
LOG.error("Cannot save skin temperature samples, continue");
}
} else if (dictClass == HuaweiDictTypes.HRV_CLASS) {
List<HuaweiHrvValueSample> hrvSamples = new ArrayList<>();
for (HuaweiP2PDataDictionarySyncService.DictData dt : dictData) {
long timestamp = dt.getStartTimestamp();
long lastTime = Math.max(dt.getEndTimestamp(), dt.getModifyTimestamp());
for (HuaweiP2PDataDictionarySyncService.DictData.DictDataValue val : dt.getData()) {
if (val.getTag() == 10 && val.getDataType() == HuaweiDictTypes.HRV_RMSSD_VALUE) {
double rmssd = HuaweiUtil.convBytes2Double(val.getValue());
int value = (int) rmssd;
if (value >= 0 && value <= 200) {
HuaweiHrvValueSample sample = new HuaweiHrvValueSample();
sample.setTimestamp(timestamp);
sample.setLastTimestamp(lastTime);
sample.setValue(value);
hrvSamples.add(sample);
}
}
}
}
try (DBHandler db = GBApplication.acquireDB()) {
final DaoSession session = db.getDaoSession();
new HuaweiHrvValueSampleProvider(gbDevice, session).persistForDevice(context, gbDevice, hrvSamples);
} catch (Exception e) {
LOG.error("Cannot save skin HRV samples, continue");
}
} else if (dictClass == HuaweiDictTypes.EMOTION_CLASS) {
List<HuaweiEmotionsSample> emotionsSamples = new ArrayList<>();
for (HuaweiP2PDataDictionarySyncService.DictData dt : dictData) {
long timestamp = dt.getStartTimestamp();
long lastTime = Math.max(dt.getEndTimestamp(), dt.getModifyTimestamp());
Integer status = null;
Double valenceCharacter = null;
Integer originStatus = null;
Double arousalCharacter = null;
for (HuaweiP2PDataDictionarySyncService.DictData.DictDataValue val : dt.getData()) {
if (val.getTag() != 10) {
LOG.info("emotions unexpected tag: {}", val.getTag());
continue;
}
if (val.getDataType() == HuaweiDictTypes.EMOTION_STATUS_VALUE) {
double value = HuaweiUtil.convBytes2Double(val.getValue());
if (value >= 0 && value < 100) {
status = (int) value;
}
} else if (val.getDataType() == HuaweiDictTypes.EMOTION_VALENCE_CHARACTER_VALUE) {
double value = HuaweiUtil.convBytes2Double(val.getValue());
if (value >= 0 && value <= 100) {
valenceCharacter = value;
}
} else if (val.getDataType() == HuaweiDictTypes.EMOTION_ORIGIN_STATUS_VALUE) {
double value = HuaweiUtil.convBytes2Double(val.getValue());
if (value >= 0 && value < 100) {
originStatus = (int) value;
}
} else if (val.getDataType() == HuaweiDictTypes.EMOTION_AROUSAL_CHARACTER_VALUE) {
double value = HuaweiUtil.convBytes2Double(val.getValue());
if (value >= 0 && value <= 100) {
arousalCharacter = value;
}
} else {
LOG.info("emotions unknown data type: {}", val.getDataType());
}
}
if (status != null || valenceCharacter != null || originStatus != null || arousalCharacter != null) {
HuaweiEmotionsSample sample = new HuaweiEmotionsSample();
sample.setTimestamp(timestamp);
sample.setLastTimestamp(lastTime);
sample.setStatus(status == null ? 0 : status);
sample.setValenceCharacter(valenceCharacter);
sample.setOriginStatus(originStatus);
sample.setArousalCharacter(arousalCharacter);
emotionsSamples.add(sample);
}
}
try (DBHandler db = GBApplication.acquireDB()) {
final DaoSession session = db.getDaoSession();
new HuaweiEmotionsSampleProvider(gbDevice, session).persistForDevice(context, gbDevice, emotionsSamples);
} catch (Exception e) {
LOG.error("Cannot save skin emotions samples, continue");
}
}
}
@@ -1896,6 +1997,7 @@ public class HuaweiSupportProvider {
ActivitySample.NOT_MEASURED,
ActivitySample.NOT_MEASURED,
ActivitySample.NOT_MEASURED,
ActivitySample.NOT_MEASURED,
ActivitySample.NOT_MEASURED
);
activitySample.setProvider(sampleProvider);
@@ -2319,14 +2421,14 @@ public class HuaweiSupportProvider {
}
public void setSleepBreath() {
if(huaweiDataSyncSleepApnea != null) {
if (huaweiDataSyncSleepApnea != null) {
boolean sleepBreathSwitch = GBApplication
.getDeviceSpecificSharedPrefs(this.getDevice().getAddress())
.getBoolean(HuaweiConstants.PREF_HUAWEI_SLEEP_BREATH, false);
if(!huaweiDataSyncSleepApnea.changeSleepBreatheState(sleepBreathSwitch)) {
if (!huaweiDataSyncSleepApnea.changeSleepBreatheState(sleepBreathSwitch)) {
LOG.error("Failed to configure sleep breathing");
}
if(!huaweiDataSyncSleepApnea.changeSleepApneaState(sleepBreathSwitch)) {
if (!huaweiDataSyncSleepApnea.changeSleepApneaState(sleepBreathSwitch)) {
LOG.error("Failed to configure sleep apnea");
}
} else {
@@ -2568,11 +2670,11 @@ public class HuaweiSupportProvider {
}
private void activateArrhythmia() {
if(huaweiDataSyncArrhythmia != null) {
if (huaweiDataSyncArrhythmia != null) {
boolean arrhythmiaEnabled = GBApplication
.getDeviceSpecificSharedPrefs(getDevice().getAddress())
.getBoolean(HuaweiConstants.PREF_HUAWEI_ARRHYTHMIA_SWITCH, false);
if(!huaweiDataSyncArrhythmia.changeState(arrhythmiaEnabled)) {
if (!huaweiDataSyncArrhythmia.changeState(arrhythmiaEnabled)) {
LOG.error("Error Arrhythmia change state");
}
}
@@ -2580,22 +2682,22 @@ public class HuaweiSupportProvider {
}
private void setArrhythmiaAutomatic() {
if(huaweiDataSyncArrhythmia != null) {
if (huaweiDataSyncArrhythmia != null) {
boolean automaticArrhythmiaEnabled = GBApplication
.getDeviceSpecificSharedPrefs(getDevice().getAddress())
.getBoolean(HuaweiConstants.PREF_HUAWEI_ARRHYTHMIA_AUTOMATIC, false);
if(!huaweiDataSyncArrhythmia.setAutomatic(automaticArrhythmiaEnabled)) {
if (!huaweiDataSyncArrhythmia.setAutomatic(automaticArrhythmiaEnabled)) {
LOG.error("Error Arrhythmia change automatic");
}
}
}
private void setArrhythmiaAlert() {
if(huaweiDataSyncArrhythmia != null) {
if (huaweiDataSyncArrhythmia != null) {
boolean arrhythmiaAlertEnabled = GBApplication
.getDeviceSpecificSharedPrefs(getDevice().getAddress())
.getBoolean(HuaweiConstants.PREF_HUAWEI_ARRHYTHMIA_ALERT, false);
if(!huaweiDataSyncArrhythmia.setAlert(arrhythmiaAlertEnabled)) {
if (!huaweiDataSyncArrhythmia.setAlert(arrhythmiaAlertEnabled)) {
LOG.error("Error Arrhythmia change alert");
}
}
@@ -2970,25 +3072,29 @@ public class HuaweiSupportProvider {
sleepStat.setMaxOxygenSaturation(sleepDataSummary.maxOxygenSaturation);
sleepStat.setMinBreathRate(sleepDataSummary.minBreathRate);
sleepStat.setMaxBreathRate(sleepDataSummary.maxBreathRate);
//TODO:
// validData -- not needed
// hrvDayToBaseline
// maxHrvBaseline
// minHrvBaseline
// avgHrv
// breathRateDayToBaseline
// maxBreathRateBaseline
// minBreathRateBaseline
// avgBreathRate
// oxygenSaturationDayToBaseline
// maxOxygenSaturationBaseline
// minOxygenSaturationBaseline
// avgOxygenSaturation
// heartRateDayToBaseline
// maxHeartRateBaseline
// minHeartRateBaseline
// avgHeartRate
// rdi
sleepStat.setHrvDayToBaseline(sleepDataSummary.hrvDayToBaseline);
sleepStat.setMaxHrvBaseline(sleepDataSummary.maxHrvBaseline);
sleepStat.setMinHrvBaseline(sleepDataSummary.minHrvBaseline);
sleepStat.setAvgHrv(sleepDataSummary.avgHrv);
sleepStat.setBreathRateDayToBaseline(sleepDataSummary.breathRateDayToBaseline);
sleepStat.setMaxBreathRateBaseline(sleepDataSummary.maxBreathRateBaseline);
sleepStat.setMinBreathRateBaseline(sleepDataSummary.minBreathRateBaseline);
sleepStat.setAvgBreathRate(sleepDataSummary.avgBreathRate);
sleepStat.setOxygenSaturationDayToBaseline(sleepDataSummary.oxygenSaturationDayToBaseline);
sleepStat.setMaxOxygenSaturationBaseline(sleepDataSummary.maxOxygenSaturationBaseline);
sleepStat.setMinOxygenSaturationBaseline(sleepDataSummary.minOxygenSaturationBaseline);
sleepStat.setAvgOxygenSaturation(sleepDataSummary.avgOxygenSaturation);
sleepStat.setHeartRateDayToBaseline(sleepDataSummary.heartRateDayToBaseline);
sleepStat.setMaxHeartRateBaseline(sleepDataSummary.maxHeartRateBaseline);
sleepStat.setMinHeartRateBaseline(sleepDataSummary.minHeartRateBaseline);
sleepStat.setAvgHeartRate(sleepDataSummary.avgHeartRate);
sleepStat.setRdi(sleepDataSummary.rdi);
sleepStat.setWakeCount(sleepDataSummary.wakeCount);
sleepStat.setTurnOverCount(sleepDataSummary.turnOverCount);
sleepStat.setPrepareSleepTime(sleepDataSummary.prepareSleepTime);
sleepStat.setWakeUpFeeling(sleepDataSummary.wakeUpFeeling);
sleepStat.setSleepVersion(sleepDataSummary.sleepVersion);
sleepStatsSamples.add(sleepStat);
long time = HuaweiTrueSleepSequenceDataParser.getTime(sleepDataSummary.fallAsleepTime, sleepDataSummary.bedTime, sleepDataSummary.validData, getHuaweiCoordinator().supportsBedTime());
@@ -87,6 +87,10 @@ public class HuaweiP2PDataDictionarySyncService extends HuaweiBaseP2PService {
result.add(cl);
} else if(cl == HuaweiDictTypes.BLOOD_PRESSURE_CLASS && coordinator.supportsBloodPressure()) {
result.add(cl);
} else if(cl == HuaweiDictTypes.HRV_CLASS && coordinator.supportsHRV()) {
result.add(cl);
} else if(cl == HuaweiDictTypes.EMOTION_CLASS && coordinator.supportsEmotion()) {
result.add(cl);
}
}
return result;
@@ -84,6 +84,8 @@ public class GetStepDataRequest extends Request {
byte heartrate = (byte) subContainer.heartrate;
byte spo = (byte) subContainer.spo;
byte restingHeartRate = (byte) subContainer.restingHeartRate;
if (steps == -1)
steps = 0;
if (calories == -1)
@@ -109,7 +111,8 @@ public class GetStepDataRequest extends Request {
calories,
distance,
spo,
heartrate
heartrate,
restingHeartRate
);
activitySample.setProvider(sampleProvider);
samples.add(activitySample);