From 98775b752be9610d35de671622a2306bae4405de Mon Sep 17 00:00:00 2001 From: Severin von Wnuck-Lipinski Date: Tue, 20 Aug 2024 23:09:39 +0200 Subject: [PATCH] Add Mijia comfort level preferences --- .../DeviceSettingsPreferenceConst.java | 7 + .../DeviceSpecificSettingsFragment.java | 5 + .../AbstractMijiaLywsdCoordinator.java | 7 + .../MijiaLywsdSettingsCustomizer.java | 95 ++++++++++++++ .../mijia_lywsd/MijiaLywsdSupport.java | 124 ++++++++++++++++-- app/src/main/res/drawable/ic_humidity_mid.xml | 10 ++ app/src/main/res/values/strings.xml | 8 ++ .../res/xml/devicesettings_mijia_lywsd.xml | 45 +++++++ 8 files changed, 292 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/MijiaLywsdSettingsCustomizer.java create mode 100644 app/src/main/res/drawable/ic_humidity_mid.xml create mode 100644 app/src/main/res/xml/devicesettings_mijia_lywsd.xml 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 3cc46111e..19c0d45ea 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 @@ -459,6 +459,13 @@ public class DeviceSettingsPreferenceConst { public static final String PREF_MISCALE_WEIGHT_UNIT = "pref_miscale_weight_unit"; public static final String PREF_MISCALE_SMALL_OBJECTS = "pref_miscale_small_objects"; + public static final String PREF_MIJIA_LYWSD_COMFORT_CHARACTERISTIC_LENGTH = "pref_mijia_lywsd_comfort_characteristic_length"; + public static final String PREF_MIJIA_LYWSD_COMFORT_LEVEL = "pref_mijia_lywsd_comfort_level"; + public static final String PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_LOWER = "pref_mijia_lywsd_comfort_temperature_lower"; + public static final String PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_UPPER = "pref_mijia_lywsd_comfort_temperature_upper"; + public static final String PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_LOWER = "pref_mijia_lywsd_comfort_humidity_lower"; + public static final String PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_UPPER = "pref_mijia_lywsd_comfort_humidity_upper"; + public static final String PREF_QC35_NOISE_CANCELLING_LEVEL = "qc35_noise_cancelling_level"; public static final String PREFS_ACTIVITY_IN_DEVICE_CARD = "prefs_activity_in_device_card"; 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 997ca36da..4c62a7152 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 @@ -764,6 +764,11 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i addPreferenceHandlerFor(PREF_MISCALE_WEIGHT_UNIT); addPreferenceHandlerFor(PREF_MISCALE_SMALL_OBJECTS); + addPreferenceHandlerFor(PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_LOWER); + addPreferenceHandlerFor(PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_UPPER); + addPreferenceHandlerFor(PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_LOWER); + addPreferenceHandlerFor(PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_UPPER); + addPreferenceHandlerFor(PREF_FEMOMETER_MEASUREMENT_MODE); addPreferenceHandlerFor(PREF_QC35_NOISE_CANCELLING_LEVEL); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/AbstractMijiaLywsdCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/AbstractMijiaLywsdCoordinator.java index 448ad590a..0cad25382 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/AbstractMijiaLywsdCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/AbstractMijiaLywsdCoordinator.java @@ -22,6 +22,7 @@ import android.net.Uri; import androidx.annotation.NonNull; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; @@ -51,6 +52,11 @@ public abstract class AbstractMijiaLywsdCoordinator extends AbstractBLEDeviceCoo return "Xiaomi"; } + @Override + public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) { + return new MijiaLywsdSettingsCustomizer(); + } + @NonNull @Override public Class getDeviceSupportClass() { @@ -60,6 +66,7 @@ public abstract class AbstractMijiaLywsdCoordinator extends AbstractBLEDeviceCoo @Override public int[] getSupportedDeviceSpecificSettings(GBDevice device) { return new int[]{ + R.xml.devicesettings_mijia_lywsd, R.xml.devicesettings_temperature_scale_cf, }; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/MijiaLywsdSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/MijiaLywsdSettingsCustomizer.java new file mode 100644 index 000000000..ce2017557 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/mijia_lywsd/MijiaLywsdSettingsCustomizer.java @@ -0,0 +1,95 @@ +/* Copyright (C) 2024 Severin von Wnuck-Lipinski + + 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.mijia_lywsd; + +import android.os.Parcel; +import androidx.preference.Preference; +import androidx.preference.SeekBarPreference; + +import java.util.Collections; +import java.util.Set; + +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer; +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler; +import nodomain.freeyourgadget.gadgetbridge.service.devices.mijia_lywsd.MijiaLywsdSupport; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; + +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*; + +public class MijiaLywsdSettingsCustomizer implements DeviceSpecificSettingsCustomizer { + public static final Creator CREATOR = new Creator() { + @Override + public MijiaLywsdSettingsCustomizer createFromParcel(final Parcel in) { + return new MijiaLywsdSettingsCustomizer(); + } + + @Override + public MijiaLywsdSettingsCustomizer[] newArray(final int size) { + return new MijiaLywsdSettingsCustomizer[size]; + } + }; + + @Override + public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) { + String key = preference.getKey(); + + if (!key.equals(PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_LOWER) && !key.equals(PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_UPPER) && + !key.equals(PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_LOWER) && !key.equals(PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_UPPER)) + return; + + SeekBarPreference temperatureLower = handler.findPreference(PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_LOWER); + SeekBarPreference temperatureUpper = handler.findPreference(PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_UPPER); + SeekBarPreference humidityLower = handler.findPreference(PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_LOWER); + SeekBarPreference humidityUpper = handler.findPreference(PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_UPPER); + + if (temperatureLower == null || temperatureUpper == null || humidityLower == null || humidityUpper == null) + return; + + // Clamp minimum value of upper limit to lower limit + if (temperatureLower.getValue() > temperatureUpper.getValue()) + temperatureUpper.setValue(temperatureLower.getValue()); + + if (humidityLower.getValue() > humidityUpper.getValue()) + humidityUpper.setValue(humidityLower.getValue()); + } + + @Override + public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String rootKey) { + Preference comfortLevel = handler.findPreference(PREF_MIJIA_LYWSD_COMFORT_LEVEL); + + if (comfortLevel != null) { + int length = prefs.getInt(PREF_MIJIA_LYWSD_COMFORT_CHARACTERISTIC_LENGTH, 0); + + // Hide comfort level for unknown characteristic length + comfortLevel.setVisible(length == MijiaLywsdSupport.COMFORT_LEVEL_LENGTH_LYWSD03 || + length == MijiaLywsdSupport.COMFORT_LEVEL_LENGTH_XMWSDJ04); + } + } + + @Override + public Set getPreferenceKeysWithSummary() { + return Collections.emptySet(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(final Parcel dest, final int flags) {} +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/mijia_lywsd/MijiaLywsdSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/mijia_lywsd/MijiaLywsdSupport.java index 5bac80878..f89aefdb5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/mijia_lywsd/MijiaLywsdSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/mijia_lywsd/MijiaLywsdSupport.java @@ -1,4 +1,4 @@ -/* Copyright (C) 2023-2024 José Rebelo +/* Copyright (C) 2023-2024 José Rebelo, Severin von Wnuck-Lipinski This file is part of Gadgetbridge. @@ -19,18 +19,20 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.mijia_lywsd; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.content.Intent; +import android.content.SharedPreferences; import android.widget.Toast; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.Objects; import java.util.SimpleTimeZone; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; -import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; import nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd.AbstractMijiaLywsdCoordinator; @@ -44,12 +46,16 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile; import nodomain.freeyourgadget.gadgetbridge.util.GB; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*; +import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump; + public class MijiaLywsdSupport extends AbstractBTLEDeviceSupport { private static final Logger LOG = LoggerFactory.getLogger(MijiaLywsdSupport.class); private static final UUID UUID_TIME = UUID.fromString("ebe0ccb7-7a0a-4b0c-8a1a-6ff2997da3a6"); private static final UUID UUID_BATTERY = UUID.fromString("ebe0ccc4-7a0a-4b0c-8a1a-6ff2997da3a6"); private static final UUID UUID_SCALE = UUID.fromString("ebe0ccbe-7a0a-4b0c-8a1a-6ff2997da3a6"); + private static final UUID UUID_COMFORT_LEVEL = UUID.fromString("ebe0ccd7-7a0a-4b0c-8a1a-6ff2997da3a6"); private static final UUID UUID_CONN_INTERVAL = UUID.fromString("ebe0ccd8-7a0a-4b0c-8a1a-6ff2997da3a6"); private final DeviceInfoProfile deviceInfoProfile; private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo(); @@ -64,6 +70,10 @@ public class MijiaLywsdSupport extends AbstractBTLEDeviceSupport { } }; + // Length of comfort level characteristic for different devices + public static final int COMFORT_LEVEL_LENGTH_LYWSD03 = 6; + public static final int COMFORT_LEVEL_LENGTH_XMWSDJ04 = 8; + public MijiaLywsdSupport() { super(LOG); addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS); @@ -86,6 +96,7 @@ public class MijiaLywsdSupport extends AbstractBTLEDeviceSupport { } getBatteryInfo(builder); + getComfortLevel(builder); setConnectionInterval(builder); setInitialized(builder); return builder; @@ -118,11 +129,53 @@ public class MijiaLywsdSupport extends AbstractBTLEDeviceSupport { builder.read(batteryCharacteristc); } - private void setTemperatureScale(TransactionBuilder builder, String scale) { + private void getComfortLevel(TransactionBuilder builder) { + BluetoothGattCharacteristic comfortCharacteristc = getCharacteristic(MijiaLywsdSupport.UUID_COMFORT_LEVEL); + builder.read(comfortCharacteristc); + } + + private void setTemperatureScale(TransactionBuilder builder, SharedPreferences prefs) { + String scale = prefs.getString(PREF_TEMPERATURE_SCALE_CF, ""); BluetoothGattCharacteristic scaleCharacteristc = getCharacteristic(MijiaLywsdSupport.UUID_SCALE); builder.write(scaleCharacteristc, new byte[]{(byte) ("f".equals(scale) ? 0x01 : 0xff)}); } + private void setComfortLevel(TransactionBuilder builder, SharedPreferences prefs) { + int length = prefs.getInt(PREF_MIJIA_LYWSD_COMFORT_CHARACTERISTIC_LENGTH, 0); + int temperatureLower = prefs.getInt(PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_LOWER, 19); + int temperatureUpper = prefs.getInt(PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_UPPER, 27); + int humidityLower = prefs.getInt(PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_LOWER, 20); + int humidityUpper = prefs.getInt(PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_UPPER, 85); + + // Ignore invalid values + if (temperatureLower > temperatureUpper || humidityLower > humidityUpper) + return; + + BluetoothGattCharacteristic comfortCharacteristc = getCharacteristic(MijiaLywsdSupport.UUID_COMFORT_LEVEL); + ByteBuffer buf = ByteBuffer.allocate(length); + + buf.order(ByteOrder.LITTLE_ENDIAN); + + switch (length) { + case COMFORT_LEVEL_LENGTH_LYWSD03: + buf.putShort((short)(temperatureUpper * 100)); + buf.putShort((short)(temperatureLower * 100)); + buf.put((byte)humidityUpper); + buf.put((byte)humidityLower); + break; + case COMFORT_LEVEL_LENGTH_XMWSDJ04: + buf.putShort((short)(temperatureUpper * 10)); + buf.putShort((short)(temperatureLower * 10)); + buf.putShort((short)(humidityUpper * 10)); + buf.putShort((short)(humidityLower * 10)); + break; + default: + return; + } + + builder.write(comfortCharacteristc, buf.array()); + } + private void handleBatteryInfo(byte[] value, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { batteryCmd.level = ((short) value[0]); @@ -131,6 +184,45 @@ public class MijiaLywsdSupport extends AbstractBTLEDeviceSupport { } } + private void handleComfortLevel(byte[] value, int status) { + if (status != BluetoothGatt.GATT_SUCCESS) + return; + + ByteBuffer buf = ByteBuffer.wrap(value); + int temperatureLower, temperatureUpper; + int humidityLower, humidityUpper; + + buf.order(ByteOrder.LITTLE_ENDIAN); + + switch (value.length) { + case COMFORT_LEVEL_LENGTH_LYWSD03: + temperatureUpper = buf.getShort() / 100; + temperatureLower = buf.getShort() / 100; + humidityUpper = buf.get(); + humidityLower = buf.get(); + break; + case COMFORT_LEVEL_LENGTH_XMWSDJ04: + temperatureUpper = buf.getShort() / 10; + temperatureLower = buf.getShort() / 10; + humidityUpper = buf.getShort() / 10; + humidityLower = buf.getShort() / 10; + break; + default: + LOG.error("Unknown comfort level characteristic: {}", hexdump(value)); + return; + } + + SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); + + prefs.edit() + .putInt(PREF_MIJIA_LYWSD_COMFORT_CHARACTERISTIC_LENGTH, value.length) + .putInt(PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_LOWER, temperatureLower) + .putInt(PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_UPPER, temperatureUpper) + .putInt(PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_LOWER, humidityLower) + .putInt(PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_UPPER, humidityUpper) + .apply(); + } + private void requestDeviceInfo(TransactionBuilder builder) { LOG.debug("Requesting Device Info!"); deviceInfoProfile.requestDeviceInfo(builder); @@ -189,22 +281,36 @@ public class MijiaLywsdSupport extends AbstractBTLEDeviceSupport { handleBatteryInfo(characteristic.getValue(), status); return true; } + + if (MijiaLywsdSupport.UUID_COMFORT_LEVEL.equals(characteristicUUID)) { + handleComfortLevel(characteristic.getValue(), status); + return true; + } + LOG.info("Unhandled characteristic read: " + characteristicUUID); return false; } @Override public void onSendConfiguration(String config) { - TransactionBuilder builder; + SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()); + try { + TransactionBuilder builder = performInitialized("Sending configuration for option: " + config); + switch (config) { - case DeviceSettingsPreferenceConst.PREF_TEMPERATURE_SCALE_CF: - String temperatureScale = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString(DeviceSettingsPreferenceConst.PREF_TEMPERATURE_SCALE_CF, ""); - builder = performInitialized("Sending configuration for option: " + config); - setTemperatureScale(builder, temperatureScale); - builder.queue(getQueue()); + case PREF_TEMPERATURE_SCALE_CF: + setTemperatureScale(builder, prefs); + break; + case PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_LOWER: + case PREF_MIJIA_LYWSD_COMFORT_TEMPERATURE_UPPER: + case PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_LOWER: + case PREF_MIJIA_LYWSD_COMFORT_HUMIDITY_UPPER: + setComfortLevel(builder, prefs); break; } + + builder.queue(getQueue()); } catch (IOException e) { LOG.error("Error setting configuration on LYWSD02", e); GB.toast("Error setting configuration", Toast.LENGTH_LONG, GB.ERROR, e); diff --git a/app/src/main/res/drawable/ic_humidity_mid.xml b/app/src/main/res/drawable/ic_humidity_mid.xml new file mode 100644 index 000000000..fcfd6980a --- /dev/null +++ b/app/src/main/res/drawable/ic_humidity_mid.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 493f9738c..4ad4e11f3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2725,6 +2725,14 @@ Chinese (jin) Small Objects Store weight of objects lighter than 10 kg + Comfort Level + Configure the temperature and humidity limits for the displayed emoji + Temperature (°C) + Recommended range: 19 - 27 + Humidity (%) + Recommended range: 20 - 85 + Lower Limit + Upper Limit Protocol Version Auto Brightness Adjust screen brightness according to ambient light diff --git a/app/src/main/res/xml/devicesettings_mijia_lywsd.xml b/app/src/main/res/xml/devicesettings_mijia_lywsd.xml new file mode 100644 index 000000000..8f894361f --- /dev/null +++ b/app/src/main/res/xml/devicesettings_mijia_lywsd.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + +