diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java
index 8cabb6c52..2a2acbc52 100644
--- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java
+++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java
@@ -38,12 +38,14 @@ public class GBDaoGenerator {
private static final String SAMPLE_STEPS = "steps";
private static final String SAMPLE_RAW_KIND = "rawKind";
private static final String SAMPLE_HEART_RATE = "heartRate";
+ private static final String SAMPLE_TEMPERATURE = "temperature";
+ private static final String SAMPLE_TEMPERATURE_TYPE = "temperatureType";
private static final String TIMESTAMP_FROM = "timestampFrom";
private static final String TIMESTAMP_TO = "timestampTo";
public static void main(String[] args) throws Exception {
- final Schema schema = new Schema(59, MAIN_PACKAGE + ".entities");
+ final Schema schema = new Schema(60, MAIN_PACKAGE + ".entities");
Entity userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes);
@@ -101,6 +103,7 @@ public class GBDaoGenerator {
addWena3HeartRateSample(schema, user, device);
addWena3Vo2Sample(schema, user, device);
addWena3StressSample(schema, user, device);
+ addFemometerVinca2TemperatureSample(schema, user, device);
addCalendarSyncState(schema, device);
addAlarms(schema, user, device);
@@ -938,4 +941,17 @@ public class GBDaoGenerator {
perAppSetting.addStringProperty("vibrationRepetition");
return perAppSetting;
}
+
+ private static void addTemperatureProperties(Entity activitySample) {
+ activitySample.addFloatProperty(SAMPLE_TEMPERATURE).notNull().codeBeforeGetter(OVERRIDE);
+ activitySample.addIntProperty(SAMPLE_TEMPERATURE_TYPE).notNull().codeBeforeGetter(OVERRIDE);
+ }
+
+ private static Entity addFemometerVinca2TemperatureSample(Schema schema, Entity user, Entity device) {
+ Entity sample = addEntity(schema, "FemometerVinca2TemperatureSample");
+ addCommonTimeSampleProperties("AbstractTemperatureSample", sample, user, device);
+ addTemperatureProperties(sample);
+ return sample;
+ }
+
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java
index dc2e01a1b..7c6bcd6bf 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java
@@ -362,4 +362,5 @@ public class DeviceSettingsPreferenceConst {
public static final String PREF_VOICE_SERVICE_LANGUAGE = "voice_service_language";
public static final String PREF_TEMPERATURE_SCALE_CF = "temperature_scale_cf";
+ public static final String PREF_FEMOMETER_MEASUREMENT_MODE = "femometer_measurement_mode";
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java
index 1a43252f6..696d43989 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java
@@ -506,6 +506,7 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i
addPreferenceHandlerFor(PREF_SONY_SPEAK_TO_CHAT_FOCUS_ON_VOICE);
addPreferenceHandlerFor(PREF_SONY_SPEAK_TO_CHAT_TIMEOUT);
addPreferenceHandlerFor(PREF_SONY_CONNECT_TWO_DEVICES);
+ addPreferenceHandlerFor(PREF_FEMOMETER_MEASUREMENT_MODE);
addPreferenceHandlerFor(PREF_QC35_NOISE_CANCELLING_LEVEL);
addPreferenceHandlerFor(PREF_USER_FITNESS_GOAL);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
index d99749459..4624e0420 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
@@ -71,6 +71,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.PaiSample;
import nodomain.freeyourgadget.gadgetbridge.model.SleepRespiratoryRateSample;
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
+import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
import nodomain.freeyourgadget.gadgetbridge.service.ServiceDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
@@ -190,6 +191,11 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
return null;
}
+ @Override
+ public TimeSampleProvider extends TemperatureSample> getTemperatureSampleProvider(GBDevice device, DaoSession session) {
+ return null;
+ }
+
@Override
public TimeSampleProvider extends Spo2Sample> getSpo2SampleProvider(GBDevice device, DaoSession session) {
return null;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
index e6d310550..5f2267a37 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
@@ -51,6 +51,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.PaiSample;
import nodomain.freeyourgadget.gadgetbridge.model.SleepRespiratoryRateSample;
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
+import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.ServiceDeviceSupport;
@@ -235,6 +236,11 @@ public interface DeviceCoordinator {
*/
TimeSampleProvider extends StressSample> getStressSampleProvider(GBDevice device, DaoSession session);
+ /**
+ * Returns the sample provider for temperature data, for the device being supported.
+ */
+ TimeSampleProvider extends TemperatureSample> getTemperatureSampleProvider(GBDevice device, DaoSession session);
+
/**
* Returns the sample provider for SpO2 data, for the device being supported.
*/
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/femometer/FemometerVinca2DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/femometer/FemometerVinca2DeviceCoordinator.java
new file mode 100755
index 000000000..7b1f19ea5
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/femometer/FemometerVinca2DeviceCoordinator.java
@@ -0,0 +1,112 @@
+/* Copyright (C) 2023 Alicia Hormann
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+package nodomain.freeyourgadget.gadgetbridge.devices.femometer;
+
+import android.app.Activity;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.regex.Pattern;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.FemometerVinca2TemperatureSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.FemometerVinca2TemperatureSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.femometer.FemometerVinca2DeviceSupport;
+
+public class FemometerVinca2DeviceCoordinator extends AbstractDeviceCoordinator {
+ @Override
+ public String getManufacturer() {
+ return "Joytech Healthcare";
+ }
+
+ @NonNull
+ @Override
+ public Class extends DeviceSupport> getDeviceSupportClass() {
+ return FemometerVinca2DeviceSupport.class;
+ }
+
+ @Override
+ public TimeSampleProvider getTemperatureSampleProvider(GBDevice device, DaoSession session) {
+ return new FemometerVinca2SampleProvider(device, session);
+ }
+
+ @Override
+ protected Pattern getSupportedDeviceName() {
+ return Pattern.compile("BM-Vinca2");
+ }
+
+
+ @Override
+ public int getDeviceNameResource() {
+ return R.string.devicetype_femometer_vinca2;
+ }
+
+ @Override
+ public int getDefaultIconResource() {
+ return R.drawable.ic_device_thermometer;
+ }
+
+ @Override
+ public int getDisabledIconResource() {
+ return R.drawable.ic_device_thermometer_disabled;
+ }
+
+
+ @Override
+ protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+ Long deviceId = device.getId();
+ QueryBuilder> qb = session.getFemometerVinca2TemperatureSampleDao().queryBuilder();
+ qb.where(FemometerVinca2TemperatureSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+ }
+
+
+ @Override
+ public int getBondingStyle(){
+ return BONDING_STYLE_NONE;
+ }
+
+ @Nullable
+ @Override
+ public Class extends Activity> getPairingActivity() {
+ return null;
+ }
+
+ @Override
+ public int getAlarmSlotCount(final GBDevice device) {
+ return 1;
+ }
+
+ @Override
+ public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+ return new int[]{
+ R.xml.devicesettings_volume,
+ R.xml.devicesettings_femometer,
+ R.xml.devicesettings_temperature_scale_cf,
+ };
+ }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/femometer/FemometerVinca2SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/femometer/FemometerVinca2SampleProvider.java
new file mode 100644
index 000000000..1f1f278f3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/femometer/FemometerVinca2SampleProvider.java
@@ -0,0 +1,56 @@
+/* Copyright (C) 2023 Alicia Hormann
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+package nodomain.freeyourgadget.gadgetbridge.devices.femometer;
+
+import androidx.annotation.NonNull;
+
+import de.greenrobot.dao.AbstractDao;
+import de.greenrobot.dao.Property;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractTimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.FemometerVinca2TemperatureSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.FemometerVinca2TemperatureSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class FemometerVinca2SampleProvider extends AbstractTimeSampleProvider {
+
+ public FemometerVinca2SampleProvider(GBDevice device, DaoSession session) {
+ super(device, session);
+ }
+
+ @Override
+ @NonNull
+ public AbstractDao getSampleDao() {
+ return getSession().getFemometerVinca2TemperatureSampleDao();
+ }
+
+ @NonNull
+ protected Property getTimestampSampleProperty() {
+ return FemometerVinca2TemperatureSampleDao.Properties.Timestamp;
+ }
+
+ @NonNull
+ protected Property getDeviceIdentifierSampleProperty() {
+ return FemometerVinca2TemperatureSampleDao.Properties.DeviceId;
+ }
+
+ @Override
+ public FemometerVinca2TemperatureSample createSample() {
+ return new FemometerVinca2TemperatureSample();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractTemperatureSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractTemperatureSample.java
new file mode 100644
index 000000000..40b75c3bc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/AbstractTemperatureSample.java
@@ -0,0 +1,37 @@
+/* Copyright (C) 2023 Alicia Hormann
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+package nodomain.freeyourgadget.gadgetbridge.entities;
+
+import androidx.annotation.NonNull;
+
+import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+public abstract class AbstractTemperatureSample extends AbstractTimeSample implements TemperatureSample {
+ @NonNull
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "{" +
+ "timestamp=" + DateTimeUtils.formatDateTime(DateTimeUtils.parseTimestampMillis(getTimestamp())) +
+ ", temperature=" + getTemperature() +
+ ", temperatureType=" + getTemperatureType() +
+ ", userId=" + getUserId() +
+ ", deviceId=" + getDeviceId() +
+ "}";
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
index 5250555c6..8156056d1 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
@@ -35,6 +35,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.casio.gbx100.CasioGBX100Devi
import nodomain.freeyourgadget.gadgetbridge.devices.casio.gwb5600.CasioGMWB5000DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.casio.gwb5600.CasioGWB5600DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.domyos.DomyosT540Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.femometer.FemometerVinca2DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.flipper.zero.FlipperZeroCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.galaxy_buds.GalaxyBuds2DeviceCoordinator;
@@ -265,6 +266,8 @@ public enum DeviceType {
SOFLOW_SO6(550, SoFlowCoordinator.class),
WITHINGS_STEEL_HR(560, WithingsSteelHRDeviceCoordinator.class),
SONY_WENA_3(570, SonyWena3Coordinator.class),
+
+ FEMOMETER_VINCA2(580, FemometerVinca2DeviceCoordinator.class),
TEST(1000, TestDeviceCoordinator.class);
private final int key;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/TemperatureSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/TemperatureSample.java
new file mode 100644
index 000000000..2eac5acbe
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/TemperatureSample.java
@@ -0,0 +1,29 @@
+/* Copyright (C) 2023 Alicia Hormann
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public interface TemperatureSample extends TimeSample {
+ /**
+ * Returns the temperature value.
+ */
+ float getTemperature();
+ /**
+ * Returns the temperature type (the position on the body where the measurement was taken).
+ */
+ int getTemperatureType();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java
index 85ec34388..e966254ed 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java
@@ -310,7 +310,9 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
if (gbDevice == null) {
return;
}
- gbDevice.setFirmwareVersion(infoEvent.fwVersion);
+ if (infoEvent.fwVersion != null) {
+ gbDevice.setFirmwareVersion(infoEvent.fwVersion);
+ }
if (infoEvent.fwVersion2 != null) {
gbDevice.setFirmwareVersion2(infoEvent.fwVersion2);
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/NotifyAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/NotifyAction.java
index 791f8ca2a..0e923fb52 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/NotifyAction.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/NotifyAction.java
@@ -28,7 +28,6 @@ import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
import static nodomain.freeyourgadget.gadgetbridge.service.btle.GattDescriptor.UUID_DESCRIPTOR_GATT_CLIENT_CHARACTERISTIC_CONFIGURATION;
-
/**
* Enables or disables notifications for a given GATT characteristic.
* The result will be made available asynchronously through the
@@ -53,15 +52,16 @@ public class NotifyAction extends BtLEAction {
if (notifyDescriptor != null) {
int properties = getCharacteristic().getProperties();
if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
- LOG.debug("use NOTIFICATION");
+ LOG.debug("use NOTIFICATION for Characteristic " + getCharacteristic().getUuid());
notifyDescriptor.setValue(enableFlag ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
result = gatt.writeDescriptor(notifyDescriptor);
} else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0) {
- LOG.debug("use INDICATION");
+ LOG.debug("use INDICATION for Characteristic " + getCharacteristic().getUuid());
notifyDescriptor.setValue(enableFlag ? BluetoothGattDescriptor.ENABLE_INDICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
result = gatt.writeDescriptor(notifyDescriptor);
hasWrittenDescriptor = true;
} else {
+ LOG.debug("use neither NOTIFICATION nor INDICATION for Characteristic " + getCharacteristic().getUuid());
hasWrittenDescriptor = false;
}
} else {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/ValueDecoder.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/ValueDecoder.java
index 161b54ed1..70689dbad 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/ValueDecoder.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/ValueDecoder.java
@@ -26,6 +26,10 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
public class ValueDecoder {
private static final Logger LOG = LoggerFactory.getLogger(ValueDecoder.class);
+ public static int decodeInt(BluetoothGattCharacteristic characteristic) {
+ return characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
+ }
+
public static int decodePercent(BluetoothGattCharacteristic characteristic) {
int percent = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
if (percent > 100 || percent < 0) {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/healthThermometer/HealthThermometerProfile.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/healthThermometer/HealthThermometerProfile.java
new file mode 100644
index 000000000..e749f6d84
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/healthThermometer/HealthThermometerProfile.java
@@ -0,0 +1,141 @@
+/* Copyright (C) 2023 Alicia Hormann
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.healthThermometer;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Intent;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.AbstractBleProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.ValueDecoder;
+
+/***
+ * This class handles the HealthThermometer as implemented on the Femometer Vinca II.
+ * This might or might not be up to GATT standard.
+ * @param
+ */
+public class HealthThermometerProfile extends AbstractBleProfile {
+ private static final Logger LOG = LoggerFactory.getLogger(HealthThermometerProfile.class);
+
+ private static final String ACTION_PREFIX = HealthThermometerProfile.class.getName() + "_";
+
+ public static final String ACTION_TEMPERATURE_INFO = ACTION_PREFIX + "TEMPERATURE_INFO";
+ public static final String EXTRA_TEMPERATURE_INFO = "TEMPERATURE_INFO";
+
+ public static final UUID SERVICE_UUID = GattService.UUID_SERVICE_HEALTH_THERMOMETER;
+ public static final UUID UUID_CHARACTERISTIC_TEMPERATURE_MEASUREMENT = GattCharacteristic.UUID_CHARACTERISTIC_TEMPERATURE_MEASUREMENT;
+ public static final UUID UUID_CHARACTERISTIC_MEASUREMENT_INTERVAL = GattCharacteristic.UUID_CHARACTERISTIC_MEASUREMENT_INTERVAL;
+ private final TemperatureInfo temperatureInfo = new TemperatureInfo();
+
+ public HealthThermometerProfile(T support) {
+ super(support);
+ }
+
+ public void requestMeasurementInterval(TransactionBuilder builder) {
+ builder.read(getCharacteristic(UUID_CHARACTERISTIC_MEASUREMENT_INTERVAL));
+ }
+
+ public void setMeasurementInterval(TransactionBuilder builder, byte[] value) {
+ builder.write(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_MEASUREMENT_INTERVAL), value);
+ }
+
+ @Override
+ public void enableNotify(TransactionBuilder builder, boolean enable) {
+ builder.notify(getCharacteristic(UUID_CHARACTERISTIC_TEMPERATURE_MEASUREMENT), enable);
+ }
+
+ @Override
+ public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ UUID charUuid = characteristic.getUuid();
+ if (charUuid.equals(UUID_CHARACTERISTIC_MEASUREMENT_INTERVAL)) {
+ handleMeasurementInterval(gatt, characteristic);
+ return true;
+ } else if (charUuid.equals(UUID_CHARACTERISTIC_TEMPERATURE_MEASUREMENT)) {
+ handleTemperatureMeasurement(gatt, characteristic);
+ return true;
+ } else {
+ LOG.info("Unexpected onCharacteristicRead: " + GattCharacteristic.toString(characteristic));
+ }
+ } else {
+ LOG.warn("error reading from characteristic:" + GattCharacteristic.toString(characteristic));
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+ return onCharacteristicRead(gatt, characteristic, BluetoothGatt.GATT_SUCCESS);
+ }
+
+
+ private void handleMeasurementInterval(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+ // todo: not implemented
+ LOG.debug("Health thermometer received Measurement Interval: " + ValueDecoder.decodeInt(characteristic));
+ }
+
+ private void handleTemperatureMeasurement(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+ /*
+ * This metadata contains as bits:
+ * the unit (celsius (0) or fahrenheit (1)) (bit 7 (last bit))
+ * if a timestamp is present (1) or not present (0) (bit 6)
+ * if a temperature type is present (1) or not present (0) (bit 5)
+ */
+ byte metadata = characteristic.getValue()[0];
+ // todo: evaluate this byte to enable support for devices without timestamp or temperature-type
+
+ int year = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 5);
+ int month = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 7);
+ int day = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 8);
+ int hour = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 9);
+ int minute = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 10);
+ int second = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 11);
+
+ Calendar c = GregorianCalendar.getInstance();
+ c.set(year, month - 1, day, hour, minute, second);
+ Date date = c.getTime();
+
+ float temperature = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_FLOAT, 1); // bytes 1 - 4
+ int temperature_type = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 12); // encodes where the measurement was taken
+
+ LOG.debug("Received measurement of " + temperature + "° with Timestamp " + date + ", metadata is " + Integer.toBinaryString((metadata & 0xFF) + 0x100).substring(1));
+
+ temperatureInfo.setTemperature(temperature);
+ temperatureInfo.setTemperatureType(temperature_type);
+ temperatureInfo.setTimestamp(date);
+ notify(createIntent(temperatureInfo));
+ }
+
+ private Intent createIntent(TemperatureInfo temperatureInfo) {
+ Intent intent = new Intent(ACTION_TEMPERATURE_INFO);
+ intent.putExtra(EXTRA_TEMPERATURE_INFO, temperatureInfo);
+ return intent;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/healthThermometer/TemperatureInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/healthThermometer/TemperatureInfo.java
new file mode 100644
index 000000000..4d5781b2e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/profiles/healthThermometer/TemperatureInfo.java
@@ -0,0 +1,85 @@
+/* Copyright (C) 2023 Alicia Hormann
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.healthThermometer;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Date;
+
+public class TemperatureInfo implements Parcelable{
+
+ private float temperature;
+ private int temperatureType;
+ private Date timestamp;
+
+ public TemperatureInfo() {
+ }
+
+ protected TemperatureInfo(Parcel in) {
+ timestamp = new Date(in.readLong());
+ temperature = in.readFloat();
+ temperatureType = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(timestamp.getTime());
+ dest.writeFloat(temperature);
+ dest.writeInt(temperatureType);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public TemperatureInfo createFromParcel(Parcel in) {
+ return new TemperatureInfo(in);
+ }
+
+ @Override
+ public TemperatureInfo[] newArray(int size) {
+ return new TemperatureInfo[size];
+ }
+ };
+
+ public float getTemperature() {
+ return temperature;
+ }
+ public Date getTimestamp() {
+ return timestamp;
+ }
+ public void setTimestamp(Date date) {
+ timestamp = date;
+ }
+
+ public void setTemperature(float temperature) {
+ this.temperature = temperature;
+ }
+
+ public int getTemperatureType() {
+ return temperatureType;
+ }
+
+ public void setTemperatureType(int temperatureType) {
+ this.temperatureType = temperatureType;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/femometer/FemometerVinca2DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/femometer/FemometerVinca2DeviceSupport.java
new file mode 100644
index 000000000..3315baa89
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/femometer/FemometerVinca2DeviceSupport.java
@@ -0,0 +1,300 @@
+/* Copyright (C) 2023 Alicia Hormann
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+package nodomain.freeyourgadget.gadgetbridge.service.devices.femometer;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.SharedPreferences;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+import nodomain.freeyourgadget.gadgetbridge.devices.femometer.FemometerVinca2SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.FemometerVinca2TemperatureSample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfoProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.healthThermometer.HealthThermometerProfile;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.healthThermometer.TemperatureInfo;
+
+public class FemometerVinca2DeviceSupport extends AbstractBTLEDeviceSupport {
+
+ private final DeviceInfoProfile deviceInfoProfile;
+ private final BatteryInfoProfile batteryInfoProfile;
+ private final HealthThermometerProfile healthThermometerProfile;
+ private static final Logger LOG = LoggerFactory.getLogger(FemometerVinca2DeviceSupport.class);
+
+ public static final UUID UNKNOWN_SERVICE_UUID = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "fef5")));
+ // Characteristic 8082caa8-41a6-4021-91c6-56f9b954cc34 READ WRITE
+ // Characteristic 9d84b9a3-000c-49d8-9183-855b673fda31 READ WRITE
+ // Characteristic 457871e8-d516-4ca1-9116-57d0b17b9cb2 READ WRITE NO RESPONSE WRITE
+ // Characteristic 5f78df94-798c-46f5-990a-b3eb6a065c88 READ NOTIFY
+
+ public static final UUID CONFIGURATION_SERVICE_UUID = UUID.fromString("0f0e0d0c-0b0a-0908-0706-050403020100");
+ public static final UUID CONFIGURATION_SERVICE_ALARM_CHARACTERISTIC = UUID.fromString("1f1e1d1c-1b1a-1918-1716-151413121110"); // READ WRITE
+ public static final UUID CONFIGURATION_SERVICE_SETTING_CHARACTERISTIC = UUID.fromString("2f2e2d2c-2b2a-2928-2726-252423222120"); // WRITE
+ public static final UUID CONFIGURATION_SERVICE_INDICATION_CHARACTERISTIC = UUID.fromString("3f3e3d3c-3b3a-3938-3736-353433323130"); // INDICATE
+
+ public FemometerVinca2DeviceSupport() {
+ super(LOG);
+
+ /// Initialize Services
+ addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
+ addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
+ addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE);
+ addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
+ addSupportedService(GattService.UUID_SERVICE_HEALTH_THERMOMETER);
+ addSupportedService(GattService.UUID_SERVICE_CURRENT_TIME);
+ addSupportedService(GattService.UUID_SERVICE_REFERENCE_TIME_UPDATE);
+ addSupportedService(UNKNOWN_SERVICE_UUID);
+ addSupportedService(CONFIGURATION_SERVICE_UUID);
+
+ /// Device Info
+ IntentListener deviceInfoListener = intent -> {
+ String action = intent.getAction();
+ if (DeviceInfoProfile.ACTION_DEVICE_INFO.equals(action)) {
+ DeviceInfo info = intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO);
+ if (info == null) return;
+ GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
+ versionCmd.hwVersion = info.getHardwareRevision();
+ versionCmd.fwVersion = info.getSoftwareRevision(); // firmwareRevision always reported as null
+ handleGBDeviceEvent(versionCmd);
+ }
+ };
+
+ deviceInfoProfile = new DeviceInfoProfile<>(this);
+ deviceInfoProfile.addListener(deviceInfoListener);
+ addSupportedProfile(deviceInfoProfile);
+
+ /// Battery
+ IntentListener batteryListener = intent -> {
+ BatteryInfo info = intent.getParcelableExtra(BatteryInfoProfile.EXTRA_BATTERY_INFO);
+ if (info == null) return;
+ GBDeviceEventBatteryInfo batteryEvent = new GBDeviceEventBatteryInfo();
+ batteryEvent.state = BatteryState.BATTERY_NORMAL;
+ batteryEvent.level = info.getPercentCharged();
+ evaluateGBDeviceEvent(batteryEvent);
+ handleGBDeviceEvent(batteryEvent);
+ };
+ batteryInfoProfile = new BatteryInfoProfile<>(this);
+ batteryInfoProfile.addListener(batteryListener);
+ addSupportedProfile(batteryInfoProfile);
+
+
+ /// Temperature
+ IntentListener temperatureListener = intent -> {
+ TemperatureInfo info = intent.getParcelableExtra(HealthThermometerProfile.EXTRA_TEMPERATURE_INFO);
+ if (info == null) return;
+ handleMeasurement(info);
+ };
+ healthThermometerProfile = new HealthThermometerProfile<>(this);
+ healthThermometerProfile.addListener(temperatureListener);
+ addSupportedProfile(healthThermometerProfile);
+ }
+
+ @Override
+ public boolean useAutoConnect() {
+ return false;
+ }
+
+ /**
+ * @param data An int smaller equal 255 (0xff)
+ */
+ private byte[] byteArray(int data) {
+ return new byte[]{(byte) data};
+ }
+
+ @Override
+ protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+ builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+
+ // Init Battery
+ batteryInfoProfile.requestBatteryInfo(builder);
+ batteryInfoProfile.enableNotify(builder, true);
+
+ // Init Device Info
+ getDevice().setFirmwareVersion("N/A");
+ getDevice().setFirmwareVersion2("N/A");
+ deviceInfoProfile.requestDeviceInfo(builder);
+
+ // Mystery stuff that happens in original app, not sure if its required
+ BluetoothGattCharacteristic c2 = getCharacteristic(CONFIGURATION_SERVICE_SETTING_CHARACTERISTIC);
+ builder.write(c2, byteArray(0x21));
+ builder.write(c2, byteArray(0x02));
+ builder.write(c2, byteArray(0x03));
+ builder.write(c2, byteArray(0x05));
+
+ // Sync Time
+ setCurrentTime(builder);
+
+ // Init Thermometer
+ builder.notify(getCharacteristic(CONFIGURATION_SERVICE_INDICATION_CHARACTERISTIC), true);
+ healthThermometerProfile.enableNotify(builder, true);
+ healthThermometerProfile.setMeasurementInterval(builder, new byte[]{(byte) 0x01, (byte) 0x00});
+
+ // mark the device as initialized
+ builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+ return builder;
+ }
+
+ @Override
+ public void onSetTime() {
+ TransactionBuilder builder = new TransactionBuilder("set time");
+ setCurrentTime(builder);
+ builder.queue(getQueue());
+ }
+
+ private void setCurrentTime(TransactionBuilder builder) {
+ // Same Code as in PineTime (without the local time)
+ GregorianCalendar now = BLETypeConversions.createCalendar();
+ byte[] bytesCurrentTime = BLETypeConversions.calendarToCurrentTime(now, 0);
+ builder.write(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_CURRENT_TIME), bytesCurrentTime);
+ }
+
+ @Override
+ public void onSetAlarms(ArrayList extends Alarm> alarms) {
+ try {
+ TransactionBuilder builder = performInitialized("applyThermometerSetting");
+
+ Alarm alarm = alarms.get(0);
+ byte[] alarm_bytes = new byte[] {
+ (byte) (alarm.getEnabled()? 0x01 : 0x00), // first byte 01/00: turn alarm on/off
+ (byte) alarm.getHour(), // second byte: hour
+ (byte) alarm.getMinute() // third byte: minute
+ };
+
+ builder.write(getCharacteristic(CONFIGURATION_SERVICE_ALARM_CHARACTERISTIC), alarm_bytes);
+ builder.write(getCharacteristic(CONFIGURATION_SERVICE_SETTING_CHARACTERISTIC), byteArray(0x01));
+ // read-request on char1 results in given alarm
+ builder.queue(getQueue());
+ } catch (IOException e) {
+ LOG.warn(" Unable to apply setting ", e);
+ }
+ }
+
+ @Override
+ public void onSendConfiguration(String config) {
+ TransactionBuilder builder;
+ SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(this.getDevice().getAddress());
+ LOG.info(" onSendConfiguration: " + config);
+ try {
+ builder = performInitialized("sendConfig: " + config);
+ switch (config) {
+ case DeviceSettingsPreferenceConst.PREF_FEMOMETER_MEASUREMENT_MODE:
+ setMeasurementMode(sharedPreferences);
+ break;
+ case DeviceSettingsPreferenceConst.PREF_VOLUME:
+ setVolume(sharedPreferences);
+ break;
+ case DeviceSettingsPreferenceConst.PREF_TEMPERATURE_SCALE_CF:
+ String scale = sharedPreferences.getString(DeviceSettingsPreferenceConst.PREF_TEMPERATURE_SCALE_CF, "c");
+ int value = "c".equals(scale) ? 0x0a : 0x0b;
+ applySetting(byteArray(value), null);
+ }
+ builder.queue(getQueue());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /** Set Measurement Mode
+ * modes (0- quick, 1- normal, 2- long)
+ */
+ private void setMeasurementMode(SharedPreferences sharedPreferences) {
+ String measurementMode = sharedPreferences.getString(DeviceSettingsPreferenceConst.PREF_FEMOMETER_MEASUREMENT_MODE, "normal");
+ byte[] confirmation = byteArray(0x1e);
+ switch (measurementMode) {
+ case "quick":
+ applySetting(byteArray(0x1a), confirmation);
+ break;
+ case "normal":
+ applySetting(byteArray(0x1c), confirmation);
+ break;
+ case "precise":
+ applySetting(byteArray(0x1d), confirmation);
+ break;
+ }
+ }
+
+ /** Set Volume
+ * volumes 0-30 (0-10: quiet, 11-20: normal, 21-30: loud)
+ */
+ private void setVolume(SharedPreferences sharedPreferences) {
+ int volume = sharedPreferences.getInt(DeviceSettingsPreferenceConst.PREF_VOLUME, 50);
+ byte[] confirmation = byteArray(0xfd);
+ if (volume < 11) {
+ applySetting(byteArray(0x09), confirmation);
+ } else if (volume < 21) {
+ applySetting(byteArray(0x14), confirmation);
+ } else {
+ applySetting(byteArray(0x16), confirmation);
+ }
+ }
+
+ private void applySetting(byte[] value, byte[] confirmation) {
+ try {
+ TransactionBuilder builder = performInitialized("applyThermometerSetting");
+ builder.write(getCharacteristic(CONFIGURATION_SERVICE_SETTING_CHARACTERISTIC), value);
+ if (confirmation != null) {
+ builder.write(getCharacteristic(CONFIGURATION_SERVICE_SETTING_CHARACTERISTIC), confirmation);
+ }
+ builder.queue(getQueue());
+ } catch (IOException e) {
+ LOG.warn(" Unable to apply setting ", e);
+ }
+ }
+
+ 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);
+ sampleProvider.addSample(temperatureSample);
+ } catch (Exception e) {
+ LOG.error("Error acquiring database", e);
+ }
+ }
+}
diff --git a/app/src/main/res/drawable/ic_device_thermometer.xml b/app/src/main/res/drawable/ic_device_thermometer.xml
new file mode 100644
index 000000000..7baeec1af
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_thermometer.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_device_thermometer_disabled.xml b/app/src/main/res/drawable/ic_device_thermometer_disabled.xml
new file mode 100644
index 000000000..b97f8789e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_thermometer_disabled.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 89233e609..f05f651f6 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -3411,4 +3411,16 @@
- c
- f
+
+
+ - @string/femometer_measurement_mode_quick
+ - @string/femometer_measurement_mode_normal
+ - @string/femometer_measurement_mode_precise
+
+
+ - quick
+ - normal
+ - precise
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 897cf8140..d39539a87 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -428,6 +428,11 @@
Normal
Power saving
Only watch
+
+ Measurement mode
+ Quick Mode (30s)
+ Normal Mode (60s-90s)
+ Precise Mode (3min)
Makibes HR3 settings
@@ -1373,6 +1378,7 @@
Sony WF-1000XM5
Sony LinkBuds S
Binary sensor
+ Femometer Vinca II
Choose export location
General
High-priority
diff --git a/app/src/main/res/xml/devicesettings_femometer.xml b/app/src/main/res/xml/devicesettings_femometer.xml
new file mode 100644
index 000000000..1e41c4665
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_femometer.xml
@@ -0,0 +1,11 @@
+
+
+
+
\ No newline at end of file