Finalizing device temperature and adding device location implementation

This commit is contained in:
Gideon Zenz
2025-10-20 00:38:34 +02:00
committed by José Rebelo
parent ec6113307f
commit d10ad8b650
17 changed files with 215 additions and 39 deletions
@@ -0,0 +1,38 @@
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.ColmiTemperatureSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.FemometerVinca2TemperatureSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.GenericTemperatureSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiTemperatureSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.MijiaLywsdRealtimeSampleDao;
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
public class GadgetbridgeUpdate_118 implements DBUpdateScript {
private void addTemperatureColumnsIfNotExists(SQLiteDatabase database, String tableName, int defaultTemperatureType, int defaultTemperatureLocation) {
// Add TemperatureType column if it doesn't exist
if (!DBHelper.existsColumn(tableName, GenericTemperatureSampleDao.Properties.TemperatureType.columnName, database)) {
database.execSQL("ALTER TABLE " + tableName + " ADD COLUMN " + GenericTemperatureSampleDao.Properties.TemperatureType.columnName + " INTEGER DEFAULT " + defaultTemperatureType);
}
// Add TemperatureLocation column if it doesn't exist
if (!DBHelper.existsColumn(tableName, GenericTemperatureSampleDao.Properties.TemperatureLocation.columnName, database)) {
database.execSQL("ALTER TABLE " + tableName + " ADD COLUMN " + GenericTemperatureSampleDao.Properties.TemperatureLocation.columnName + " INTEGER DEFAULT " + defaultTemperatureLocation);
}
}
@Override
public void upgradeSchema(SQLiteDatabase database) {
addTemperatureColumnsIfNotExists(database, HuaweiTemperatureSampleDao.TABLENAME, TemperatureSample.TYPE_SKIN, TemperatureSample.LOCATION_WRIST);
addTemperatureColumnsIfNotExists(database, ColmiTemperatureSampleDao.TABLENAME, TemperatureSample.TYPE_SKIN, TemperatureSample.LOCATION_WRIST);
addTemperatureColumnsIfNotExists(database, FemometerVinca2TemperatureSampleDao.TABLENAME, TemperatureSample.TYPE_BODY, TemperatureSample.LOCATION_MOUTH);
addTemperatureColumnsIfNotExists(database, MijiaLywsdRealtimeSampleDao.TABLENAME, TemperatureSample.TYPE_AMBIENT, TemperatureSample.LOCATION_UNKNOWN);
addTemperatureColumnsIfNotExists(database, GenericTemperatureSampleDao.TABLENAME, TemperatureSample.TYPE_UNKNOWN, TemperatureSample.LOCATION_UNKNOWN);
}
@Override
public void downgradeSchema(SQLiteDatabase db) {
}
}
@@ -74,6 +74,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributes;
import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributesDao;
import nodomain.freeyourgadget.gadgetbridge.entities.GenericTrainingLoadAcuteSample;
import nodomain.freeyourgadget.gadgetbridge.entities.GenericTrainingLoadChronicSample;
import nodomain.freeyourgadget.gadgetbridge.entities.HealthConnectSyncStateDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.AbstractNotificationPattern;
@@ -225,6 +226,8 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
batteryLevelQueryBuilder.where(BatteryLevelDao.Properties.DeviceId.eq(device.getId())).buildDelete().executeDeleteWithoutDetachingEntities();
QueryBuilder<?> alarmDeviceQueryBuilder = session.getAlarmDao().queryBuilder();
alarmDeviceQueryBuilder.where(AlarmDao.Properties.DeviceId.eq(device.getId())).buildDelete().executeDeleteWithoutDetachingEntities();
QueryBuilder<?> healthSyncStateQueryBuilder = session.getHealthConnectSyncStateDao().queryBuilder();
healthSyncStateQueryBuilder.where(HealthConnectSyncStateDao.Properties.DeviceId.eq(device.getId())).buildDelete().executeDeleteWithoutDetachingEntities();
session.getDeviceDao().delete(device);
} else {
LOG.info("device to delete not found in db: {}", gbDevice);
@@ -1,4 +1,5 @@
/* Copyright (C) 2025 Thomas Kuehne
/* Copyright (C) 2025 Thomas Kuehne, José Rebelo, Gideon Zenz
Copyright (C) 2023-2024 Alicia Hormann (Original AbstractTemperatureSample)
This file is part of Gadgetbridge.
@@ -18,6 +19,10 @@
package nodomain.freeyourgadget.gadgetbridge.devices;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.List;
import java.util.stream.Collectors;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property;
@@ -25,10 +30,35 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.GenericTemperatureSample;
import nodomain.freeyourgadget.gadgetbridge.entities.GenericTemperatureSampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
public class GenericTemperatureSampleProvider extends AbstractTimeSampleProvider<GenericTemperatureSample> {
private final Integer defaultType;
private final Integer defaultLocation;
public GenericTemperatureSampleProvider(final GBDevice device, final DaoSession session) {
this(device, session, TemperatureSample.TYPE_UNKNOWN, TemperatureSample.LOCATION_UNKNOWN);
}
public GenericTemperatureSampleProvider(final GBDevice device,
final DaoSession session,
final Integer defaultType,
final Integer defaultLocation) {
super(device, session);
this.defaultType = defaultType;
this.defaultLocation = defaultLocation;
}
private GenericTemperatureSample applyDefaults(final GenericTemperatureSample sample) {
if (sample != null) {
if (defaultType != -1 && (sample.getTemperatureType() == null || sample.getTemperatureType() == 0)) {
sample.setTemperatureType(defaultType);
}
if (defaultLocation != -1 && (sample.getTemperatureLocation() == null || sample.getTemperatureLocation() == 0)) {
sample.setTemperatureLocation(defaultLocation);
}
}
return sample;
}
@NonNull
@@ -53,4 +83,40 @@ public class GenericTemperatureSampleProvider extends AbstractTimeSampleProvider
public GenericTemperatureSample createSample() {
return new GenericTemperatureSample();
}
}
@NonNull
@Override
public List<GenericTemperatureSample> getAllSamples(final long timestampFrom, final long timestampTo) {
return super.getAllSamples(timestampFrom, timestampTo).stream()
.map(this::applyDefaults)
.collect(Collectors.toList());
}
@Nullable
@Override
public GenericTemperatureSample getLatestSample() {
return applyDefaults(super.getLatestSample());
}
@Nullable
@Override
public GenericTemperatureSample getLatestSample(final long until) {
return applyDefaults(super.getLatestSample(until));
}
@Nullable
public GenericTemperatureSample getLastSampleBefore(final long timestampTo) {
return applyDefaults(super.getLastSampleBefore(timestampTo));
}
@Nullable
public GenericTemperatureSample getNextSampleAfter(final long timestampFrom) {
return applyDefaults(super.getNextSampleAfter(timestampFrom));
}
@Nullable
@Override
public GenericTemperatureSample getFirstSample() {
return applyDefaults(super.getFirstSample());
}
}
@@ -25,6 +25,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.FemometerVinca2TemperatureSample;
import nodomain.freeyourgadget.gadgetbridge.entities.FemometerVinca2TemperatureSampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
public class FemometerVinca2SampleProvider extends AbstractTimeSampleProvider<FemometerVinca2TemperatureSample> {
@@ -52,6 +53,9 @@ public class FemometerVinca2SampleProvider extends AbstractTimeSampleProvider<Fe
@Override
public FemometerVinca2TemperatureSample createSample() {
return new FemometerVinca2TemperatureSample();
FemometerVinca2TemperatureSample sample=new FemometerVinca2TemperatureSample();
sample.setTemperatureType(TemperatureSample.TYPE_BODY);
sample.setTemperatureLocation(TemperatureSample.LOCATION_MOUTH);
return sample;
}
}
@@ -74,6 +74,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.model.SleepScoreSample;
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiVibrationPatternNotificationType;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
@@ -209,7 +210,7 @@ public abstract class HuamiCoordinator extends AbstractBLEDeviceCoordinator {
@Override
public GenericTemperatureSampleProvider getTemperatureSampleProvider(GBDevice device, DaoSession session) {
return new GenericTemperatureSampleProvider(device, session);
return new GenericTemperatureSampleProvider(device, session, TemperatureSample.TYPE_SKIN, TemperatureSample.LOCATION_WRIST);
}
@Override
@@ -1,5 +1,8 @@
package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
import static nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample.LOCATION_WRIST;
import static nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample.TYPE_SKIN;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -89,7 +92,8 @@ public class HuaweiCompatTemperatureSampleProvider implements TimeSampleProvider
HuaweiTemperatureSample sample = new HuaweiTemperatureSample();
sample.setTimestamp(vl.getHuaweiDictData().getStartTimestamp());
sample.setTemperature((float) skinTemperature);
sample.setTemperatureType(0);
sample.setTemperatureLocation(LOCATION_WRIST);
sample.setTemperatureType(TYPE_SKIN);
ret.add(idx++, sample);
}
}
@@ -149,7 +153,8 @@ public class HuaweiCompatTemperatureSampleProvider implements TimeSampleProvider
HuaweiTemperatureSample sample = new HuaweiTemperatureSample();
sample.setTimestamp(valuesData.get(0).getHuaweiDictData().getStartTimestamp());
sample.setTemperature((float) HuaweiUtil.convBytes2Double(valuesData.get(0).getValue()));
sample.setTemperatureType(0);
sample.setTemperatureLocation(LOCATION_WRIST);
sample.setTemperatureType(TYPE_SKIN);
return sample;
}
@@ -189,7 +194,8 @@ public class HuaweiCompatTemperatureSampleProvider implements TimeSampleProvider
HuaweiTemperatureSample sample = new HuaweiTemperatureSample();
sample.setTimestamp(valuesData.get(0).getHuaweiDictData().getStartTimestamp());
sample.setTemperature((float) HuaweiUtil.convBytes2Double(valuesData.get(0).getValue()));
sample.setTemperatureType(0);
sample.setTemperatureLocation(LOCATION_WRIST);
sample.setTemperatureType(TYPE_SKIN);
return sample;
}
@@ -222,7 +228,8 @@ public class HuaweiCompatTemperatureSampleProvider implements TimeSampleProvider
HuaweiTemperatureSample sample = new HuaweiTemperatureSample();
sample.setTimestamp(valuesData.get(0).getHuaweiDictData().getStartTimestamp());
sample.setTemperature((float) HuaweiUtil.convBytes2Double(valuesData.get(0).getValue()));
sample.setTemperatureType(0);
sample.setTemperatureLocation(LOCATION_WRIST);
sample.setTemperatureType(TYPE_SKIN);
return sample;
}
@@ -256,7 +263,7 @@ public class HuaweiCompatTemperatureSampleProvider implements TimeSampleProvider
for (HuaweiDictDataValues vl : valuesData) {
double skinTemperature = HuaweiUtil.convBytes2Double(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));
res.add(new HuaweiTemperatureSample(vl.getHuaweiDictData().getStartTimestamp(), vl.getHuaweiDictData().getDeviceId(), vl.getHuaweiDictData().getUserId(), Math.max(vl.getHuaweiDictData().getModifyTimestamp(), vl.getHuaweiDictData().getEndTimestamp()), (float) skinTemperature, TYPE_SKIN, LOCATION_WRIST));
}
}
daoSession.getHuaweiTemperatureSampleDao().insertInTx(res);
@@ -30,6 +30,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.Device;
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 extends AbstractTimeSampleProvider<HuaweiTemperatureSample> {
@@ -57,7 +58,10 @@ public class HuaweiTemperatureSampleProvider extends AbstractTimeSampleProvider<
@Override
public HuaweiTemperatureSample createSample() {
return new HuaweiTemperatureSample();
HuaweiTemperatureSample sample = new HuaweiTemperatureSample();
sample.setTemperatureType(TemperatureSample.TYPE_SKIN);
sample.setTemperatureLocation(TemperatureSample.LOCATION_WRIST);
return sample;
}
@@ -25,6 +25,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.MijiaLywsdRealtimeSample;
import nodomain.freeyourgadget.gadgetbridge.entities.MijiaLywsdRealtimeSampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
public class MijiaLywsdRealtimeSampleProvider extends AbstractTimeSampleProvider<MijiaLywsdRealtimeSample> {
public MijiaLywsdRealtimeSampleProvider(final GBDevice device, final DaoSession session) {
@@ -51,6 +52,9 @@ public class MijiaLywsdRealtimeSampleProvider extends AbstractTimeSampleProvider
@Override
public MijiaLywsdRealtimeSample createSample() {
return new MijiaLywsdRealtimeSample();
MijiaLywsdRealtimeSample sample=new MijiaLywsdRealtimeSample();
sample.setTemperatureType(TemperatureSample.TYPE_AMBIENT);
sample.setTemperatureLocation(TemperatureSample.LOCATION_UNKNOWN);
return sample;
}
}
@@ -78,10 +78,5 @@ public class TestTemperatureSampleProvider extends AbstractTestSampleProvider<Te
public float getTemperature() {
return temperature;
}
@Override
public int getTemperatureType() {
return 0; // ?
}
}
}
@@ -177,7 +177,7 @@ public class UltrahumanDeviceCoordinator extends AbstractBLEDeviceCoordinator {
@Override
public TimeSampleProvider<? extends TemperatureSample> getTemperatureSampleProvider(GBDevice device, DaoSession session) {
return new GenericTemperatureSampleProvider(device, session);
return new GenericTemperatureSampleProvider(device, session, TemperatureSample.TYPE_SKIN, TemperatureSample.LOCATION_FINGER);
}
@Override
@@ -119,8 +119,13 @@ public class XiaomiTemperatureSampleProvider implements TimeSampleProvider<Tempe
}
@Override
public int getTemperatureType() {
return 0; // ?
public Integer getTemperatureType() {
return TemperatureSample.TYPE_SKIN;
}
@Override
public Integer getTemperatureLocation() {
return TemperatureSample.LOCATION_WRIST;
}
}
}
@@ -33,4 +33,6 @@ public abstract class AbstractTemperatureSample extends AbstractTimeSample imple
", deviceId=" + getDeviceId() +
"}";
}
public abstract void setTemperature(float temperature);
}
@@ -17,6 +17,22 @@
package nodomain.freeyourgadget.gadgetbridge.model;
public interface TemperatureSample extends TimeSample {
int TYPE_UNKNOWN = 0;
int TYPE_BODY = 1;
int TYPE_SKIN = 2;
int TYPE_AMBIENT = 3;
int LOCATION_UNKNOWN = 0;
int LOCATION_ARMPIT = 1;
int LOCATION_FINGER = 2;
int LOCATION_FOREHEAD = 3;
int LOCATION_MOUTH = 4;
int LOCATION_RECTUM = 5;
int LOCATION_TEMPORAL_ARTERY = 6;
int LOCATION_TOE = 7;
int LOCATION_EAR = 8;
int LOCATION_WRIST = 9;
/**
* Returns the temperature value.
*/
@@ -24,5 +40,14 @@ public interface TemperatureSample extends TimeSample {
/**
* Returns the temperature type (the position on the body where the measurement was taken).
*/
int getTemperatureType();
default Integer getTemperatureType() {
return TYPE_UNKNOWN;
}
/**
* Returns the temperature measurement location (the position on the body where the measurement was taken).
*/
default Integer getTemperatureLocation() {
return LOCATION_UNKNOWN;
}
}
@@ -283,14 +283,17 @@ public class FemometerVinca2DeviceSupport extends AbstractBTLESingleDeviceSuppor
private void handleMeasurement(TemperatureInfo info) {
Date timestamp = info.getTimestamp();
float temperature = info.getTemperature();
int temperatureType = info.getTemperatureType();
try (DBHandler db = GBApplication.acquireDB()) {
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(getDevice(), db.getDaoSession()).getId();
long time = timestamp.getTime();
FemometerVinca2SampleProvider sampleProvider = new FemometerVinca2SampleProvider(getDevice(), db.getDaoSession());
FemometerVinca2TemperatureSample temperatureSample = new FemometerVinca2TemperatureSample(time, deviceId, userId, temperature, temperatureType);
FemometerVinca2TemperatureSample temperatureSample = sampleProvider.createSample();
temperatureSample.setTimestamp(time);
temperatureSample.setDeviceId(deviceId);
temperatureSample.setUserId(userId);
temperatureSample.setTemperature(temperature);
sampleProvider.addSample(temperatureSample);
} catch (Exception e) {
LOG.error("Error acquiring database", e);
@@ -133,6 +133,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.datasync.HuaweiDataSyncArrhythmia;
@@ -1606,8 +1607,9 @@ public class HuaweiSupportProvider {
HuaweiTemperatureSample sample = new HuaweiTemperatureSample();
sample.setTimestamp(timestamp);
sample.setLastTimestamp(lastTime);
sample.setTemperatureType(0);
sample.setTemperature((float) skinTemperature);
sample.setTemperatureType(TemperatureSample.TYPE_SKIN);
sample.setTemperatureLocation(TemperatureSample.LOCATION_WRIST);
temperatureSamples.add(sample);
}
}
@@ -37,6 +37,8 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.ultrahuman.Ultrahuman
import static nodomain.freeyourgadget.gadgetbridge.devices.ultrahuman.UltrahumanConstants.UUID_TODO;
import static nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State.INITIALIZED;
import static nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State.INITIALIZING;
import static nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample.LOCATION_FINGER;
import static nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample.TYPE_SKIN;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.ultrahuman.UltrahumanCharacteristic.COMMAND;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.ultrahuman.UltrahumanCharacteristic.DATA;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.ultrahuman.UltrahumanCharacteristic.RESPONSE;
@@ -642,7 +644,13 @@ public class UltrahumanDeviceSupport extends AbstractBTLESingleDeviceSupport {
if (temperatureMax != 0.0f || temperatureMin != 0.0f) {
float temperature = (temperatureMax + temperatureMin) / 2.0f;
GenericTemperatureSampleProvider provider = new GenericTemperatureSampleProvider(device, session);
GenericTemperatureSample sample = new GenericTemperatureSample(timestampTemp * 1000L, deviceId, userId, temperature, 0);
GenericTemperatureSample sample = new GenericTemperatureSample();
sample.setTimestamp(timestampTemp * 1000L);
sample.setDeviceId(deviceId);
sample.setUserId(userId);
sample.setTemperature(temperature);
sample.setTemperatureLocation(LOCATION_FINGER);
sample.setTemperatureType(TYPE_SKIN);
provider.addSample(sample);
}