Huawei: use specific table for temperature sync

This commit is contained in:
Me7c7
2025-08-26 01:05:55 +02:00
committed by José Rebelo
parent 805bc35299
commit efeba6f12d
7 changed files with 478 additions and 325 deletions
@@ -58,7 +58,7 @@ public class GBDaoGenerator {
public static void main(String[] args) throws Exception {
final Schema schema = new Schema(113, MAIN_PACKAGE + ".entities");
final Schema schema = new Schema(114, MAIN_PACKAGE + ".entities");
Entity userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes);
@@ -172,6 +172,7 @@ public class GBDaoGenerator {
addHuaweiStressSample(schema, user, device);
addHuaweiSleepStageSample(schema, user, device);
addHuaweiSleepStatsSample(schema, user, device);
addHuaweiTemperatureSample(schema, user, device);
addUltrahumanActivitySample(schema, user, device);
addUltrahumanDeviceStateSample(schema, user, device);
@@ -1589,6 +1590,16 @@ public class GBDaoGenerator {
return sample;
}
private static Entity addHuaweiTemperatureSample(Schema schema, Entity user, Entity device) {
Entity sample = addEntity(schema, "HuaweiTemperatureSample");
addCommonTimeSampleProperties("AbstractTemperatureSample", sample, user, device);
sample.addLongProperty("lastTimestamp").notNull().index();
sample.addFloatProperty(SAMPLE_TEMPERATURE).notNull().codeBeforeGetter(OVERRIDE);
sample.addIntProperty(SAMPLE_TEMPERATURE_TYPE).notNull().primaryKey().codeBeforeGetter(OVERRIDE);
return sample;
}
private static Entity addHuaweiWorkoutSummarySample(Schema schema, Entity user, Entity device) {
Entity workoutSummary = addEntity(schema, "HuaweiWorkoutSummarySample");
@@ -257,7 +257,7 @@ public abstract class HuaweiBRCoordinator extends AbstractBLClassicDeviceCoordin
@Override
public TimeSampleProvider<? extends TemperatureSample> getTemperatureSampleProvider(final GBDevice device, final DaoSession session) {
return new HuaweiTemperatureSampleProvider(device, session);
return new HuaweiCompatTemperatureSampleProvider(device, session);
}
@Override
@@ -0,0 +1,226 @@
package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.stream.Collectors;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictData;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataValues;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataValuesDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiTemperatureSample;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
public class HuaweiCompatTemperatureSampleProvider implements TimeSampleProvider<HuaweiTemperatureSample> {
private final HuaweiTemperatureSampleProvider sp;
private final GBDevice device;
private final DaoSession session;
public HuaweiCompatTemperatureSampleProvider(GBDevice device, DaoSession session) {
this.device = device;
this.session = session;
sp = new HuaweiTemperatureSampleProvider(device, session);
}
private double conv2Double(byte[] b) {
return ByteBuffer.wrap(b).getDouble();
}
@NonNull
@Override
public List<HuaweiTemperatureSample> getAllSamples(long timestampFrom, long timestampTo) {
TemperatureSample newFirst = sp.getFirstSample();
if (newFirst != null && newFirst.getTimestamp() < timestampFrom) {
return sp.getAllSamples(timestampFrom, timestampTo);
}
List<HuaweiTemperatureSample> ret = sp.getAllSamples(timestampFrom, timestampTo);
long oldTo = timestampFrom;
if (!ret.isEmpty()) {
oldTo = ret.get(0).getTimestamp();
}
Long userId = DBHelper.getUser(this.session).getId();
Long deviceId = DBHelper.getDevice(this.device, this.session).getId();
if (deviceId == null || userId == null)
return ret;
QueryBuilder<HuaweiDictData> qb = this.session.getHuaweiDictDataDao().queryBuilder();
qb.where(HuaweiDictDataDao.Properties.DeviceId.eq(deviceId))
.where(HuaweiDictDataDao.Properties.UserId.eq(userId))
.where(HuaweiDictDataDao.Properties.DictClass.eq(HuaweiDictTypes.SKIN_TEMPERATURE_CLASS))
.where(HuaweiDictDataDao.Properties.StartTimestamp.between(timestampFrom, oldTo));
final List<HuaweiDictData> dictData = qb.build().list();
if (dictData.isEmpty())
return ret;
List<Long> ids = dictData.stream().map(HuaweiDictData::getDictId).collect(Collectors.toList());
QueryBuilder<HuaweiDictDataValues> qbv = this.session.getHuaweiDictDataValuesDao().queryBuilder();
qbv.where(HuaweiDictDataValuesDao.Properties.DictType.eq(HuaweiDictTypes.SKIN_TEMPERATURE_VALUE)).where(HuaweiDictDataValuesDao.Properties.Tag.eq(10)).where(HuaweiDictDataValuesDao.Properties.DictId.in(ids));
final List<HuaweiDictDataValues> valuesData = qbv.build().list();
if (valuesData.isEmpty())
return ret;
int idx = 0;
for (HuaweiDictDataValues vl : valuesData) {
double skinTemperature = conv2Double(vl.getValue());
if (skinTemperature >= 20 && skinTemperature <= 42) {
HuaweiTemperatureSample sample = new HuaweiTemperatureSample();
sample.setTimestamp(vl.getHuaweiDictData().getStartTimestamp());
sample.setTemperature((float) skinTemperature);
sample.setTemperatureType(0);
ret.add(idx++, sample);
}
}
return ret;
}
@Override
public void addSample(HuaweiTemperatureSample timeSample) {
throw new UnsupportedOperationException("read-only sample provider");
}
@Override
public void addSamples(List<HuaweiTemperatureSample> timeSamples) {
throw new UnsupportedOperationException("read-only sample provider");
}
@Override
public HuaweiTemperatureSample createSample() {
throw new UnsupportedOperationException("read-only sample provider");
}
@Nullable
@Override
public HuaweiTemperatureSample getLatestSample() {
HuaweiTemperatureSample newSample = sp.getLatestSample();
if (newSample != null)
return newSample;
Long userId = DBHelper.getUser(this.session).getId();
Long deviceId = DBHelper.getDevice(this.device, this.session).getId();
if (deviceId == null || userId == null)
return null;
QueryBuilder<HuaweiDictData> qb = this.session.getHuaweiDictDataDao().queryBuilder();
qb.where(HuaweiDictDataDao.Properties.DeviceId.eq(deviceId))
.where(HuaweiDictDataDao.Properties.UserId.eq(userId))
.where(HuaweiDictDataDao.Properties.DictClass.eq(HuaweiDictTypes.SKIN_TEMPERATURE_CLASS));
qb.orderDesc(HuaweiDictDataDao.Properties.StartTimestamp).limit(1);
final List<HuaweiDictData> data = qb.build().list();
if (data.isEmpty())
return null;
QueryBuilder<HuaweiDictDataValues> qbv = this.session.getHuaweiDictDataValuesDao().queryBuilder();
qbv.where(HuaweiDictDataValuesDao.Properties.DictType.eq(HuaweiDictTypes.SKIN_TEMPERATURE_VALUE)).where(HuaweiDictDataValuesDao.Properties.Tag.eq(10)).where(HuaweiDictDataValuesDao.Properties.DictId.eq(data.get(0).getDictId()));
final List<HuaweiDictDataValues> valuesData = qbv.build().list();
if (valuesData.isEmpty())
return null;
HuaweiTemperatureSample sample = new HuaweiTemperatureSample();
sample.setTimestamp(valuesData.get(0).getHuaweiDictData().getStartTimestamp());
sample.setTemperature((float) conv2Double(valuesData.get(0).getValue()));
sample.setTemperatureType(0);
return sample;
}
@Nullable
@Override
public HuaweiTemperatureSample getLatestSample(final long until) {
HuaweiTemperatureSample newSample = sp.getLatestSample(until);
if (newSample != null)
return newSample;
Long userId = DBHelper.getUser(this.session).getId();
Long deviceId = DBHelper.getDevice(this.device, this.session).getId();
if (deviceId == null || userId == null)
return null;
QueryBuilder<HuaweiDictData> qb = this.session.getHuaweiDictDataDao().queryBuilder();
qb.where(HuaweiDictDataDao.Properties.StartTimestamp.le(until))
.where(HuaweiDictDataDao.Properties.DeviceId.eq(deviceId))
.where(HuaweiDictDataDao.Properties.UserId.eq(userId))
.where(HuaweiDictDataDao.Properties.DictClass.eq(HuaweiDictTypes.SKIN_TEMPERATURE_CLASS));
qb.orderDesc(HuaweiDictDataDao.Properties.StartTimestamp).limit(1);
final List<HuaweiDictData> data = qb.build().list();
if (data.isEmpty())
return null;
QueryBuilder<HuaweiDictDataValues> qbv = this.session.getHuaweiDictDataValuesDao().queryBuilder();
qbv.where(HuaweiDictDataValuesDao.Properties.DictType.eq(HuaweiDictTypes.SKIN_TEMPERATURE_VALUE)).where(HuaweiDictDataValuesDao.Properties.Tag.eq(10)).where(HuaweiDictDataValuesDao.Properties.DictId.eq(data.get(0).getDictId()));
final List<HuaweiDictDataValues> valuesData = qbv.build().list();
if (valuesData.isEmpty())
return null;
HuaweiTemperatureSample sample = new HuaweiTemperatureSample();
sample.setTimestamp(valuesData.get(0).getHuaweiDictData().getStartTimestamp());
sample.setTemperature((float) conv2Double(valuesData.get(0).getValue()));
sample.setTemperatureType(0);
return sample;
}
@Nullable
@Override
public HuaweiTemperatureSample getFirstSample() {
Long userId = DBHelper.getUser(this.session).getId();
Long deviceId = DBHelper.getDevice(this.device, this.session).getId();
if (deviceId == null || userId == null)
return null;
QueryBuilder<HuaweiDictData> qb = this.session.getHuaweiDictDataDao().queryBuilder();
qb.where(HuaweiDictDataDao.Properties.DeviceId.eq(deviceId))
.where(HuaweiDictDataDao.Properties.UserId.eq(userId))
.where(HuaweiDictDataDao.Properties.DictClass.eq(HuaweiDictTypes.SKIN_TEMPERATURE_CLASS));
qb.orderAsc(HuaweiDictDataDao.Properties.StartTimestamp).limit(1);
final List<HuaweiDictData> data = qb.build().list();
if (data.isEmpty())
return sp.getFirstSample();
QueryBuilder<HuaweiDictDataValues> qbv = this.session.getHuaweiDictDataValuesDao().queryBuilder();
qbv.where(HuaweiDictDataValuesDao.Properties.DictType.eq(HuaweiDictTypes.SKIN_TEMPERATURE_VALUE)).where(HuaweiDictDataValuesDao.Properties.Tag.eq(10)).where(HuaweiDictDataValuesDao.Properties.DictId.eq(data.get(0).getDictId()));
final List<HuaweiDictDataValues> valuesData = qbv.build().list();
if (valuesData.isEmpty())
return sp.getFirstSample();
HuaweiTemperatureSample sample = new HuaweiTemperatureSample();
sample.setTimestamp(valuesData.get(0).getHuaweiDictData().getStartTimestamp());
sample.setTemperature((float) conv2Double(valuesData.get(0).getValue()));
sample.setTemperatureType(0);
return sample;
}
}
@@ -265,7 +265,7 @@ public abstract class HuaweiLECoordinator extends AbstractBLEDeviceCoordinator i
@Override
public TimeSampleProvider<? extends TemperatureSample> getTemperatureSampleProvider(final GBDevice device, final DaoSession session) {
return new HuaweiTemperatureSampleProvider(device, session);
return new HuaweiCompatTemperatureSampleProvider(device, session);
}
@Override
@@ -27,203 +27,76 @@ import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
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.devices.TimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictData;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataValues;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataValuesDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStageSample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStageSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStatsSample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStatsSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiTemperatureSample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiTemperatureSampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
public class HuaweiTemperatureSampleProvider implements TimeSampleProvider<TemperatureSample> {
public class HuaweiTemperatureSampleProvider extends AbstractTimeSampleProvider<HuaweiTemperatureSample> {
private final Logger LOG = LoggerFactory.getLogger(HuaweiTemperatureSampleProvider.class);
protected static class HuaweiTemperatureSample implements TemperatureSample {
private final long timestamp;
private final float temperature;
public HuaweiTemperatureSample(long timestamp, float temperature) {
this.timestamp = timestamp;
this.temperature = temperature;
}
@Override
public long getTimestamp() {
return timestamp;
}
@Override
public float getTemperature() {
return temperature;
}
@Override
public int getTemperatureType() { return 0;}
}
private final GBDevice device;
private final DaoSession session;
public HuaweiTemperatureSampleProvider(GBDevice device, DaoSession session) {
this.device = device;
this.session = session;
}
private double conv2Double(byte[] b) {
return ByteBuffer.wrap(b).getDouble();
public HuaweiTemperatureSampleProvider(final GBDevice device, final DaoSession session) {
super(device, session);
}
@NonNull
@Override
public List<TemperatureSample> getAllSamples(long timestampFrom, long timestampTo) {
List<TemperatureSample> ret = new ArrayList<>();
Long userId = DBHelper.getUser(this.session).getId();
Long deviceId = DBHelper.getDevice(this.device, this.session).getId();
if (deviceId == null || userId == null)
return ret;
QueryBuilder<HuaweiDictData> qb = this.session.getHuaweiDictDataDao().queryBuilder();
qb.where(HuaweiDictDataDao.Properties.DeviceId.eq(deviceId))
.where(HuaweiDictDataDao.Properties.UserId.eq(userId))
.where(HuaweiDictDataDao.Properties.DictClass.eq(HuaweiDictTypes.SKIN_TEMPERATURE_CLASS))
.where(HuaweiDictDataDao.Properties.StartTimestamp.between(timestampFrom, timestampTo));
final List<HuaweiDictData> dictData = qb.build().list();
if (dictData.isEmpty())
return ret;
List<Long> ids = dictData.stream().map(HuaweiDictData::getDictId).collect(Collectors.toList());
QueryBuilder<HuaweiDictDataValues> qbv = this.session.getHuaweiDictDataValuesDao().queryBuilder();
qbv.where(HuaweiDictDataValuesDao.Properties.DictType.eq(HuaweiDictTypes.SKIN_TEMPERATURE_VALUE)).where(HuaweiDictDataValuesDao.Properties.Tag.eq(10)).where(HuaweiDictDataValuesDao.Properties.DictId.in(ids));
final List<HuaweiDictDataValues> valuesData = qbv.build().list();
if (valuesData.isEmpty())
return ret;
for(HuaweiDictDataValues vl: valuesData) {
double skinTemperature = conv2Double(vl.getValue());
if(skinTemperature >= 20 && skinTemperature <= 42) {
ret.add(new HuaweiTemperatureSample(vl.getHuaweiDictData().getStartTimestamp(), (float) skinTemperature));
}
public AbstractDao<HuaweiTemperatureSample, ?> getSampleDao() {
return getSession().getHuaweiTemperatureSampleDao();
}
return ret;
@NonNull
@Override
protected Property getTimestampSampleProperty() {
return HuaweiTemperatureSampleDao.Properties.Timestamp;
}
@NonNull
@Override
protected Property getDeviceIdentifierSampleProperty() {
return HuaweiTemperatureSampleDao.Properties.DeviceId;
}
@Override
public void addSample(TemperatureSample timeSample) {
throw new UnsupportedOperationException("read-only sample provider");
public HuaweiTemperatureSample createSample() {
return new HuaweiTemperatureSample();
}
@Override
public void addSamples(List<TemperatureSample> timeSamples) {
throw new UnsupportedOperationException("read-only sample provider");
public long getLastFetchTimestamp() {
QueryBuilder<HuaweiTemperatureSample> qb = getSampleDao().queryBuilder();
Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
if (dbDevice == null)
return 0;
final Property deviceProperty = HuaweiTemperatureSampleDao.Properties.DeviceId;
final Property timestampProperty = HuaweiTemperatureSampleDao.Properties.LastTimestamp;
qb.where(deviceProperty.eq(dbDevice.getId()))
.orderDesc(timestampProperty)
.limit(1);
List<HuaweiTemperatureSample> samples = qb.build().list();
if (samples.isEmpty())
return 0;
HuaweiTemperatureSample sample = samples.get(0);
return sample.getLastTimestamp();
}
@Override
public TemperatureSample createSample() {
throw new UnsupportedOperationException("read-only sample provider");
}
@Nullable
@Override
public TemperatureSample getLatestSample() {
Long userId = DBHelper.getUser(this.session).getId();
Long deviceId = DBHelper.getDevice(this.device, this.session).getId();
if (deviceId == null || userId == null)
return null;
QueryBuilder<HuaweiDictData> qb = this.session.getHuaweiDictDataDao().queryBuilder();
qb.where(HuaweiDictDataDao.Properties.DeviceId.eq(deviceId))
.where(HuaweiDictDataDao.Properties.UserId.eq(userId))
.where(HuaweiDictDataDao.Properties.DictClass.eq(HuaweiDictTypes.SKIN_TEMPERATURE_CLASS));
qb.orderDesc(HuaweiDictDataDao.Properties.StartTimestamp).limit(1);
final List<HuaweiDictData> data = qb.build().list();
if (data.isEmpty())
return null;
QueryBuilder<HuaweiDictDataValues> qbv = this.session.getHuaweiDictDataValuesDao().queryBuilder();
qbv.where(HuaweiDictDataValuesDao.Properties.DictType.eq(HuaweiDictTypes.SKIN_TEMPERATURE_VALUE)).where(HuaweiDictDataValuesDao.Properties.Tag.eq(10)).where(HuaweiDictDataValuesDao.Properties.DictId.eq(data.get(0).getDictId()));
final List<HuaweiDictDataValues> valuesData = qbv.build().list();
if (valuesData.isEmpty())
return null;
return new HuaweiTemperatureSample(valuesData.get(0).getHuaweiDictData().getStartTimestamp(), (float) conv2Double(valuesData.get(0).getValue()));
}
@Nullable
@Override
public TemperatureSample getLatestSample(final long until) {
Long userId = DBHelper.getUser(this.session).getId();
Long deviceId = DBHelper.getDevice(this.device, this.session).getId();
if (deviceId == null || userId == null)
return null;
QueryBuilder<HuaweiDictData> qb = this.session.getHuaweiDictDataDao().queryBuilder();
qb.where(HuaweiDictDataDao.Properties.StartTimestamp.le(until))
.where(HuaweiDictDataDao.Properties.DeviceId.eq(deviceId))
.where(HuaweiDictDataDao.Properties.UserId.eq(userId))
.where(HuaweiDictDataDao.Properties.DictClass.eq(HuaweiDictTypes.SKIN_TEMPERATURE_CLASS));
qb.orderDesc(HuaweiDictDataDao.Properties.StartTimestamp).limit(1);
final List<HuaweiDictData> data = qb.build().list();
if (data.isEmpty())
return null;
QueryBuilder<HuaweiDictDataValues> qbv = this.session.getHuaweiDictDataValuesDao().queryBuilder();
qbv.where(HuaweiDictDataValuesDao.Properties.DictType.eq(HuaweiDictTypes.SKIN_TEMPERATURE_VALUE)).where(HuaweiDictDataValuesDao.Properties.Tag.eq(10)).where(HuaweiDictDataValuesDao.Properties.DictId.eq(data.get(0).getDictId()));
final List<HuaweiDictDataValues> valuesData = qbv.build().list();
if (valuesData.isEmpty())
return null;
return new HuaweiTemperatureSample(valuesData.get(0).getHuaweiDictData().getStartTimestamp(), (float) conv2Double(valuesData.get(0).getValue()));
}
@Nullable
@Override
public TemperatureSample getFirstSample() {
Long userId = DBHelper.getUser(this.session).getId();
Long deviceId = DBHelper.getDevice(this.device, this.session).getId();
if (deviceId == null || userId == null)
return null;
QueryBuilder<HuaweiDictData> qb = this.session.getHuaweiDictDataDao().queryBuilder();
qb.where(HuaweiDictDataDao.Properties.DeviceId.eq(deviceId))
.where(HuaweiDictDataDao.Properties.UserId.eq(userId))
.where(HuaweiDictDataDao.Properties.DictClass.eq(HuaweiDictTypes.SKIN_TEMPERATURE_CLASS));
qb.orderAsc(HuaweiDictDataDao.Properties.StartTimestamp).limit(1);
final List<HuaweiDictData> data = qb.build().list();
if (data.isEmpty())
return null;
QueryBuilder<HuaweiDictDataValues> qbv = this.session.getHuaweiDictDataValuesDao().queryBuilder();
qbv.where(HuaweiDictDataValuesDao.Properties.DictType.eq(HuaweiDictTypes.SKIN_TEMPERATURE_VALUE)).where(HuaweiDictDataValuesDao.Properties.Tag.eq(10)).where(HuaweiDictDataValuesDao.Properties.DictId.eq(data.get(0).getDictId()));
final List<HuaweiDictDataValues> valuesData = qbv.build().list();
if (valuesData.isEmpty())
return null;
return new HuaweiTemperatureSample(valuesData.get(0).getHuaweiDictData().getStartTimestamp(), (float) conv2Double(valuesData.get(0).getValue()));
}
}
@@ -27,6 +27,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.location.Location;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
@@ -39,6 +40,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.util.ArrayList;
@@ -53,6 +55,7 @@ import de.greenrobot.dao.query.DeleteQuery;
import de.greenrobot.dao.query.QueryBuilder;
import kotlin.Triple;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
@@ -77,6 +80,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiSleepStageSampl
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiSleepStatsSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiStressParser;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiStressSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTemperatureSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTruSleepParser;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTrueSleepSequenceDataParser;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.CameraRemote;
@@ -95,6 +99,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiDictDataValuesDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStageSample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiSleepStatsSample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiStressSample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiTemperatureSample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutPaceSample;
@@ -226,7 +231,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetT
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetWearLocationRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetWearMessagePushRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetNotificationCapabilitiesRequest;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetWorkModeRequest;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
@@ -952,7 +956,7 @@ public class HuaweiSupportProvider {
huaweiDataSyncFindDevice = new HuaweiDataSyncFindDevice(HuaweiSupportProvider.this);
}
if(getHuaweiCoordinator().supportsEmotion()) {
if (getHuaweiCoordinator().supportsEmotion()) {
huaweiDataSyncEmotion = new HuaweiDataSyncEmotion(HuaweiSupportProvider.this);
}
}
@@ -1513,12 +1517,64 @@ public class HuaweiSupportProvider {
HuaweiP2PDataDictionarySyncService P2PSyncService = HuaweiP2PDataDictionarySyncService.getRegisteredInstance(huaweiP2PManager);
if (P2PSyncService != null) {
List<Integer> list = P2PSyncService.checkSupported(this.getHuaweiCoordinator(), Arrays.asList(HuaweiDictTypes.SKIN_TEMPERATURE_CLASS, HuaweiDictTypes.BLOOD_PRESSURE_CLASS));
List<Integer> list = P2PSyncService.checkSupported(this.getHuaweiCoordinator(), Arrays.asList(HuaweiDictTypes.SKIN_TEMPERATURE_CLASS));
if (!list.isEmpty()) {
syncState.setP2pSync(true);
P2PSyncService.startSync(list, complete -> {
P2PSyncService.startSync(list, new HuaweiP2PDataDictionarySyncService.DictionarySyncCallback() {
@Override
public long onGetLastDataSyncTimestamp(int dictClass) {
LOG.info("DictionarySyncCallback onGetLastDataDictLastTimestamp: {}", dictClass);
if (dictClass == HuaweiDictTypes.SKIN_TEMPERATURE_CLASS) {
try (DBHandler db = GBApplication.acquireDB()) {
HuaweiTemperatureSampleProvider sleepStatsSampleProvider = new HuaweiTemperatureSampleProvider(gbDevice, db.getDaoSession());
return sleepStatsSampleProvider.getLastFetchTimestamp();
} catch (Exception e) {
LOG.warn("Exception for getting temperature start time", e);
}
return 0;
}
return -1;
}
@Override
public void onData(int dictClass, List<HuaweiP2PDataDictionarySyncService.DictData> dictData) {
LOG.info("DictionarySyncCallback onData: {}", dictClass);
if (dictClass == HuaweiDictTypes.SKIN_TEMPERATURE_CLASS) {
List<HuaweiTemperatureSample> temperatureSamples = 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.SKIN_TEMPERATURE_VALUE) {
double skinTemperature = conv2Double(val.getValue());
if (skinTemperature >= 20 && skinTemperature <= 42) {
HuaweiTemperatureSample sample = new HuaweiTemperatureSample();
sample.setTimestamp(timestamp);
sample.setLastTimestamp(lastTime);
sample.setTemperatureType(0);
sample.setTemperature((float) skinTemperature);
temperatureSamples.add(sample);
}
}
}
}
try (DBHandler db = GBApplication.acquireDB()) {
final DaoSession session = db.getDaoSession();
new HuaweiTemperatureSampleProvider(gbDevice, session).persistForDevice(context, gbDevice, temperatureSamples);
} catch (Exception e) {
LOG.error("Cannot save skin temperature samples, continue");
}
}
}
@Override
public void onComplete(boolean complete) {
LOG.info("Sync P2P Data complete");
syncState.setP2pSync(false);
}
});
}
}
@@ -1761,6 +1817,7 @@ public class HuaweiSupportProvider {
/**
* Add sleep activities
*
* @param data List of triples with [timestamp_start, timestamp_end, type]
* @param source Source of the data
*/
@@ -2129,125 +2186,6 @@ public class HuaweiSupportProvider {
}
}
public void addDictData(List<HuaweiP2PDataDictionarySyncService.DictData> dictData) {
if (dictData.isEmpty())
return;
try (DBHandler db = GBApplication.acquireDB()) {
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(gbDevice, db.getDaoSession()).getId();
// TODO: Cache probably not necessary with an index on StartTimestamp
// Assume it's in order, though we'll be fine if it is not
// Note that SQLite BETWEEN is inclusive
long cacheStartTime = dictData.get(0).getStartTimestamp();
long cacheEndTime = dictData.get(dictData.size() - 1).getStartTimestamp();
QueryBuilder<HuaweiDictData> qbCache = db.getDaoSession().getHuaweiDictDataDao().queryBuilder().where(
HuaweiDictDataDao.Properties.UserId.eq(userId),
HuaweiDictDataDao.Properties.DeviceId.eq(deviceId),
HuaweiDictDataDao.Properties.StartTimestamp.between(cacheStartTime, cacheEndTime)
);
List<HuaweiDictData> cache = qbCache.build().list();
for (HuaweiP2PDataDictionarySyncService.DictData data : dictData) {
// Avoid duplicates
Long dictId = null;
if (data.getStartTimestamp() >= cacheStartTime && data.getStartTimestamp() <= cacheEndTime) {
// Cache hit - if it exists it will be in the cache
for (HuaweiDictData d : cache) {
if (d.getStartTimestamp() == data.getStartTimestamp() && d.getDictClass() == data.getDictClass()) {
dictId = d.getDictId();
break;
}
}
} else {
// Cache miss - check the database
LOG.warn("P2P dedup cache miss");
// TODO: No index on StartTimestamp/DictClass, so terribly slow
QueryBuilder<HuaweiDictData> qb = db.getDaoSession().getHuaweiDictDataDao().queryBuilder().where(
HuaweiDictDataDao.Properties.UserId.eq(userId),
HuaweiDictDataDao.Properties.DeviceId.eq(deviceId),
HuaweiDictDataDao.Properties.DictClass.eq(data.getDictClass()),
HuaweiDictDataDao.Properties.StartTimestamp.eq(data.getStartTimestamp())
);
List<HuaweiDictData> results = qb.build().list();
if (!results.isEmpty())
dictId = results.get(0).getDictId();
}
HuaweiDictData dictSample = new HuaweiDictData(
dictId,
deviceId,
userId,
data.getDictClass(),
data.getStartTimestamp(),
data.getEndTimestamp(),
data.getModifyTimestamp()
);
db.getDaoSession().getHuaweiDictDataDao().insertOrReplace(dictSample);
addDictDataValue(dictSample.getDictId(), data.getData());
}
} catch (Exception e) {
LOG.error("Failed to add dict data", e);
}
}
public void addDictDataValue(Long dictId, List<HuaweiP2PDataDictionarySyncService.DictData.DictDataValue> dictDataValues) {
if (dictId == null)
return;
try (DBHandler db = GBApplication.acquireDB()) {
HuaweiDictDataValuesDao dao = db.getDaoSession().getHuaweiDictDataValuesDao();
for (HuaweiP2PDataDictionarySyncService.DictData.DictDataValue dataValues : dictDataValues) {
HuaweiDictDataValues dictValue = new HuaweiDictDataValues(
dictId,
dataValues.getDataType(),
dataValues.getTag(),
dataValues.getValue()
);
dao.insertOrReplace(dictValue);
}
} catch (Exception e) {
LOG.error("Failed to add dict value to database", e);
}
}
public long getLastDataDictLastTimestamp(int dictClass) {
long lastTimestamp = 0;
if (dictClass == 0)
return lastTimestamp;
try (DBHandler db = GBApplication.acquireDB()) {
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(gbDevice, db.getDaoSession()).getId();
QueryBuilder<HuaweiDictData> qb = db.getDaoSession().getHuaweiDictDataDao().queryBuilder().where(
HuaweiDictDataDao.Properties.UserId.eq(userId),
HuaweiDictDataDao.Properties.DeviceId.eq(deviceId),
HuaweiDictDataDao.Properties.DictClass.eq(dictClass)
).orderRaw(
// "max(MODIFY_TIMESTAMP, END_TIMESTAMP) DESC"
String.format(
"max(%s, %s) DESC",
HuaweiDictDataDao.Properties.ModifyTimestamp.columnName,
HuaweiDictDataDao.Properties.EndTimestamp.columnName
)
).limit(1);
List<HuaweiDictData> results = qb.build().list();
if (!results.isEmpty()) {
lastTimestamp = Math.max(lastTimestamp, results.get(0).getModifyTimestamp());
lastTimestamp = Math.max(lastTimestamp, results.get(0).getEndTimestamp());
}
} catch (Exception e) {
LOG.error("Failed to select last timestamp value to database", e);
}
return lastTimestamp;
}
public void setWearLocation() {
try {
SetWearLocationRequest setWearLocationReq = new SetWearLocationRequest(this);
@@ -2456,7 +2394,7 @@ public class HuaweiSupportProvider {
}
}
if(huaweiDataSyncEmotion != null && !automaticStressEnabled) {
if (huaweiDataSyncEmotion != null && !automaticStressEnabled) {
return;
}
@@ -2872,7 +2810,7 @@ public class HuaweiSupportProvider {
for (HuaweiSequenceDataParser.SequenceData sd : sequenceFileData.getSequenceDataList()) {
LOG.info("SLEEP SequenceData: {}", sd);
HuaweiTrueSleepSequenceDataParser.SleepSummary sleepDataSummary = HuaweiTrueSleepSequenceDataParser.parseSleepDataSummary(sequenceFileData, sd);
if(sleepDataSummary == null) {
if (sleepDataSummary == null) {
LOG.warn("SLEEP DataSummary is null");
continue;
}
@@ -2904,7 +2842,7 @@ public class HuaweiSupportProvider {
LOG.info("SLEEP Time: {}", time);
List<HuaweiTrueSleepSequenceDataParser.SleepStage> stages = HuaweiTrueSleepSequenceDataParser.parseSleepDetails(sd.getDetails(), time);
LOG.info("SLEEP Stages: {}", stages);
if(stages != null) {
if (stages != null) {
for (HuaweiTrueSleepSequenceDataParser.SleepStage st : stages) {
HuaweiSleepStageSample sample = new HuaweiSleepStageSample();
sample.setTimestamp(st.getTime() * 1000L);
@@ -3183,12 +3121,93 @@ public class HuaweiSupportProvider {
syncState.updateState(needSync);
}
private double conv2Double(byte[] b) {
return ByteBuffer.wrap(b).getDouble();
}
public void onTestNewFunction() {
gbDevice.setBusyTask(R.string.busy_task_downloading, getContext());
gbDevice.sendDeviceUpdateIntent(getContext());
downloadDictTrueSleepData(0, (int) (System.currentTimeMillis() / 1000));
AsyncTask.execute(new Runnable() {
@Override
public void run() {
long startTime = System.nanoTime();
long count = 0;
try (DBHandler db = GBApplication.acquireDB()) {
DaoSession daoSession = db.getDaoSession();
QueryBuilder<HuaweiDictDataValues> qbv1 = daoSession.getHuaweiDictDataValuesDao().queryBuilder();
count = qbv1.count();
} catch (
GBException e) {
LOG.error("EX", e);
} catch (
Exception e) {
LOG.error("EX2", e);
}
int limit = 10000;
int offset = 0;
while (true) {
try (DBHandler db = GBApplication.acquireDB()) {
DaoSession daoSession = db.getDaoSession();
QueryBuilder<HuaweiDictDataValues> qbv = daoSession.getHuaweiDictDataValuesDao().queryBuilder();
qbv.where(HuaweiDictDataValuesDao.Properties.DictType.eq(HuaweiDictTypes.SKIN_TEMPERATURE_VALUE)).where(HuaweiDictDataValuesDao.Properties.Tag.eq(10)).limit(limit).offset(offset);
final List<HuaweiDictDataValues> valuesData = qbv.build().list();
if (valuesData.isEmpty())
break;
List<HuaweiTemperatureSample> res = new ArrayList<>();
for (HuaweiDictDataValues vl : valuesData) {
double skinTemperature = conv2Double(vl.getValue());
if (skinTemperature >= 20 && skinTemperature <= 42) {
res.add(new HuaweiTemperatureSample(vl.getHuaweiDictData().getStartTimestamp(), vl.getHuaweiDictData().getDeviceId(), vl.getHuaweiDictData().getUserId(), Math.max(vl.getHuaweiDictData().getModifyTimestamp(), vl.getHuaweiDictData().getEndTimestamp()), (float) skinTemperature, 0));
}
}
daoSession.getHuaweiTemperatureSampleDao().insertInTx(res);
offset += limit;
LOG.info("Migrating: {}/{}", offset, count);
} catch (GBException e) {
LOG.error("EX", e);
return;
} catch (Exception e) {
LOG.error("EX2", e);
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
LOG.error("Sleep error", e);
}
}
try (DBHandler db = GBApplication.acquireDB()) {
DaoSession daoSession = db.getDaoSession();
final DeleteQuery<HuaweiDictDataValues> tableValuesDeleteQuery = daoSession.getHuaweiDictDataValuesDao().queryBuilder()
.where(HuaweiDictDataValuesDao.Properties.DictType.eq(HuaweiDictTypes.SKIN_TEMPERATURE_VALUE))
.buildDelete();
tableValuesDeleteQuery.executeDeleteWithoutDetachingEntities();
final DeleteQuery<HuaweiDictData> tableDataDeleteQuery = daoSession.getHuaweiDictDataDao().queryBuilder()
.where(HuaweiDictDataDao.Properties.DictClass.eq(HuaweiDictTypes.SKIN_TEMPERATURE_CLASS))
.buildDelete();
tableDataDeleteQuery.executeDeleteWithoutDetachingEntities();
} catch (
GBException e) {
LOG.error("EX", e);
} catch (
Exception e) {
LOG.error("EX2", e);
}
LOG.info("Migrating took : {} ms", ((System.nanoTime() - startTime) / 1000000.0));
}
});
}
public void onMusicListReq() {
@@ -16,6 +16,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.p2p;
import androidx.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -37,12 +39,14 @@ public class HuaweiP2PDataDictionarySyncService extends HuaweiBaseP2PService {
public static final String MODULE = "hw.unitedevice.datadictionarysync";
private AtomicBoolean serviceAvailable = new AtomicBoolean(false);
private final AtomicBoolean serviceAvailable = new AtomicBoolean(false);
private List<Integer> classesToSync = null;
public interface DictionarySyncCallback {
long onGetLastDataSyncTimestamp(int dictClass);
void onData(int dictClass, List<HuaweiP2PDataDictionarySyncService.DictData> dictData);
void onComplete(boolean complete);
}
@@ -90,17 +94,25 @@ public class HuaweiP2PDataDictionarySyncService extends HuaweiBaseP2PService {
public void startSync(List<Integer> dictClasses, DictionarySyncCallback callback) {
LOG.info("P2PDataDictionarySyncService startSync {}", dictClasses);
if(callback == null) {
LOG.error("P2PDataDictionarySyncService startSync callback is null");
return;
}
classesToSync = dictClasses;
if(classesToSync.isEmpty()) {
callback.onComplete(false);
return;
}
sendSyncRequest(classesToSync.remove(0), callback);
sendSyncRequest(classesToSync.remove(0), 0, callback);
}
private void sendSyncRequest(int dictClass, DictionarySyncCallback callback) {
private void sendSyncRequest(int dictClass, long startTime, DictionarySyncCallback callback) {
LOG.info("P2PDataDictionarySyncService class {}", dictClass);
if(callback == null) {
LOG.error("P2PDataDictionarySyncService sendSyncRequest callback is null");
return;
}
if (!serviceAvailable.get()) {
LOG.info("P2PDataDictionarySyncService not available");
@@ -113,8 +125,15 @@ public class HuaweiP2PDataDictionarySyncService extends HuaweiBaseP2PService {
callback.onComplete(false);
return;
}
if(startTime == 0) {
startTime = callback.onGetLastDataSyncTimestamp(dictClass);
}
if(startTime < 0) {
LOG.info("P2PDataDictionarySyncService start time is less then 0");
callback.onComplete(false);
return;
}
long startTime = manager.getSupportProvider().getLastDataDictLastTimestamp(dictClass);
if(startTime > 0) {
startTime += 1000;
}
@@ -146,15 +165,11 @@ public class HuaweiP2PDataDictionarySyncService extends HuaweiBaseP2PService {
@Override
public void registered() {
sendPing(new HuaweiP2PCallback() {
@Override
public void onResponse(int code, byte[] data) {
sendPing((code, data) -> {
if ((byte) code != (byte) 0xca)
return;
serviceAvailable.set(true);
}
});
}
@Override
@@ -186,6 +201,7 @@ public class HuaweiP2PDataDictionarySyncService extends HuaweiBaseP2PService {
return value;
}
@NonNull
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("HuaweiDictDataValue{");
@@ -237,6 +253,7 @@ public class HuaweiP2PDataDictionarySyncService extends HuaweiBaseP2PService {
return data;
}
@NonNull
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("HuaweiDictSample{");
@@ -255,12 +272,10 @@ public class HuaweiP2PDataDictionarySyncService extends HuaweiBaseP2PService {
if (data[0] == 1) {
DictionarySyncCallback callback = null;
try {
HuaweiTLV tlv = new HuaweiTLV();
tlv.parse(data, 1, data.length - 1);
int operation = tlv.getInteger(0x01); ///???
int operation = tlv.getInteger(0x01);
int dictClass = tlv.getInteger(0x02);
if(!currentRequests.containsKey(dictClass)) {
@@ -272,10 +287,18 @@ public class HuaweiP2PDataDictionarySyncService extends HuaweiBaseP2PService {
return;
}
if(operation != 1) {
return;
//I never see value differ from 1. So I don't know how to interpret others. Just ignore for now
//callback.onComplete(true);
}
//NOTE: all tags with high bit set should be parsed as container
List<DictData> result = new ArrayList<>();
long lastTimestamp = 0;
for (HuaweiTLV blockTlv : tlv.getObjects(0x83)) {
for (HuaweiTLV l : blockTlv.getObjects(0x84)) {
//5 - start time, 6 - end time, 0xc - modify time
@@ -297,19 +320,20 @@ public class HuaweiP2PDataDictionarySyncService extends HuaweiBaseP2PService {
}
}
result.add(new DictData(dictClass, startTimestamp, endTimestamp, modifyTimestamp, dataValues));
lastTimestamp = Math.max(lastTimestamp, Math.max(endTimestamp, modifyTimestamp));
}
}
manager.getSupportProvider().addDictData(result);
callback.onData(dictClass, result);
if (!result.isEmpty()) {
sendSyncRequest(dictClass, callback);
sendSyncRequest(dictClass, lastTimestamp, callback);
} else {
if(classesToSync.isEmpty()) {
classesToSync = null;
callback.onComplete(true);
} else {
sendSyncRequest(classesToSync.remove(0), callback);
sendSyncRequest(classesToSync.remove(0), 0, callback);
}
}
} catch (HuaweiPacket.MissingTagException e) {