From ef1d7171e80b36f176c1eaf885b341dea57cf78d Mon Sep 17 00:00:00 2001 From: Severin von Wnuck-Lipinski Date: Mon, 12 Aug 2024 20:36:55 +0200 Subject: [PATCH] Add support for Mi Smart Scale 2 --- .../DeviceSettingsPreferenceConst.java | 3 + .../DeviceSpecificSettingsFragment.java | 3 + .../miscale/MiSmartScaleCoordinator.java | 130 +++++++++ .../gadgetbridge/model/DeviceType.java | 2 + .../miscale/MiSmartScaleDeviceSupport.java | 266 ++++++++++++++++++ .../devices/miscale/WeightMeasurement.java | 87 ++++++ app/src/main/res/values/arrays.xml | 12 + app/src/main/res/values/strings.xml | 8 + .../res/xml/devicesettings_mismartscale.xml | 18 ++ 9 files changed, 529 insertions(+) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miscale/MiSmartScaleCoordinator.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miscale/MiSmartScaleDeviceSupport.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miscale/WeightMeasurement.java create mode 100644 app/src/main/res/xml/devicesettings_mismartscale.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 965c358b7..7c84c6f4a 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 @@ -449,6 +449,9 @@ public class DeviceSettingsPreferenceConst { public static final String PREF_MOONDROP_TOUCH_ANC_MODE_EARBUD = "pref_moondrop_touch_anc_mode_earbud"; public static final String PREF_MOONDROP_TOUCH_ANC_MODE_TRIGGER = "pref_moondrop_touch_anc_mode_trigger"; + 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_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 04a7523ef..dd53f1f6a 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 @@ -757,6 +757,9 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_ANC_MODE_EARBUD); addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_ANC_MODE_TRIGGER); + addPreferenceHandlerFor(PREF_MISCALE_WEIGHT_UNIT); + addPreferenceHandlerFor(PREF_MISCALE_SMALL_OBJECTS); + addPreferenceHandlerFor(PREF_FEMOMETER_MEASUREMENT_MODE); addPreferenceHandlerFor(PREF_QC35_NOISE_CANCELLING_LEVEL); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miscale/MiSmartScaleCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miscale/MiSmartScaleCoordinator.java new file mode 100644 index 000000000..6a8f49242 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miscale/MiSmartScaleCoordinator.java @@ -0,0 +1,130 @@ +/* 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.miscale; + +import androidx.annotation.NonNull; + +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.activities.devicesettings.DeviceSpecificSettings; +import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider; +import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.entities.Device; +import nodomain.freeyourgadget.gadgetbridge.entities.MiScaleWeightSampleDao; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.WeightSample; +import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miscale.MiSmartScaleDeviceSupport; + +public class MiSmartScaleCoordinator extends AbstractBLEDeviceCoordinator { + @Override + public String getManufacturer() { + return "Huami"; + } + + @Override + protected Pattern getSupportedDeviceName() { + return Pattern.compile("MI SCALE2"); + } + + @Override + public int getDeviceNameResource() { + return R.string.devicetype_mismartscale; + } + + @Override + public int getDefaultIconResource() { + return R.drawable.ic_device_miscale; + } + + @Override + public int getDisabledIconResource() { + return R.drawable.ic_device_miscale_disabled; + } + + @Override + public int getBatteryCount() { + return 0; + } + + @Override + public int getBondingStyle() { + return BONDING_STYLE_NONE; + } + + @Override + protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException { + Long deviceId = device.getId(); + QueryBuilder qb = session.getMiScaleWeightSampleDao().queryBuilder(); + + qb.where(MiScaleWeightSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities(); + } + + @Override + public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) { + final DeviceSpecificSettings settings = new DeviceSpecificSettings(); + + settings.addRootScreen(R.xml.devicesettings_mismartscale); + + return settings; + } + + @Override + public TimeSampleProvider getWeightSampleProvider(GBDevice device, DaoSession session) { + return new MiScaleSampleProvider(device, session); + } + + @Override + public boolean supportsWeightMeasurement() { + return true; + } + + @Override + public boolean supportsActivityTracking() { + return true; + } + + @Override + public boolean supportsActivityTabs() { + return false; + } + + @Override + public boolean supportsSleepMeasurement() { + return false; + } + + @Override + public boolean supportsStepCounter() { + return false; + } + + @Override + public boolean supportsSpeedzones() { + return false; + } + + @NonNull + @Override + public Class getDeviceSupportClass() { + return MiSmartScaleDeviceSupport.class; + } +} 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 36649efbc..6a25f5a04 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java @@ -188,6 +188,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd.MijiaLywsd02Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd.MijiaLywsd03Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd.MijiaMhoC303Coordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.miscale.MiSmartScaleCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.miscale.MiCompositionScaleCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.moondrop.MoondropSpaceTravelCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.no1f1.No1F1Coordinator; @@ -355,6 +356,7 @@ public enum DeviceType { CASIOGBX100(CasioGBX100DeviceCoordinator.class), CASIOGWB5600(CasioGWB5600DeviceCoordinator.class), CASIOGMWB5000(CasioGMWB5000DeviceCoordinator.class), + MISMARTSCALE(MiSmartScaleCoordinator.class), MICOMPOSITIONSCALE(MiCompositionScaleCoordinator.class), BFH16(BFH16DeviceCoordinator.class), MAKIBESHR3(MakibesHR3Coordinator.class), diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miscale/MiSmartScaleDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miscale/MiSmartScaleDeviceSupport.java new file mode 100644 index 000000000..ed55ae11c --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miscale/MiSmartScaleDeviceSupport.java @@ -0,0 +1,266 @@ +/* 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.service.devices.miscale; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.content.SharedPreferences; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; +import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; +import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService; +import nodomain.freeyourgadget.gadgetbridge.devices.miscale.MiScaleSampleProvider; +import nodomain.freeyourgadget.gadgetbridge.entities.MiScaleWeightSample; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +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.deviceinfo.DeviceInfo; +import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile; +import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol; + +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*; + +public class MiSmartScaleDeviceSupport extends AbstractBTLEDeviceSupport { + private static final Logger LOG = LoggerFactory.getLogger(MiSmartScaleDeviceSupport.class); + + private static final UUID UUID_CHARACTERISTIC_CONFIG = UUID.fromString("00001542-0000-3512-2118-0009af100700"); + private static final UUID UUID_CHARACTERISTIC_WEIGHT_HISTORY = UUID.fromString("00002a2f-0000-3512-2118-0009af100700"); + + // There's unfortunately no way to query the config options, they can only be set + private static final byte CFG_WEIGHT_UNIT = (byte)0x04; + private static final byte CFG_SMALL_OBJECTS = (byte)0x10; + private static final byte CFG_RESET_HISTORY = (byte)0x12; + + private static final byte CMD_HISTORY_START = (byte)0x01; + private static final byte CMD_HISTORY_QUERY = (byte)0x02; + private static final byte CMD_HISTORY_COMPLETE = (byte)0x03; + private static final byte CMD_HISTORY_END = (byte)0x04; + + // Threshold for small objects + private static final int SMALL_OBJECT_MAX_WEIGHT = 10; + + private long userId = -1; + + private final DeviceInfoProfile deviceInfoProfile; + + public MiSmartScaleDeviceSupport() { + super(LOG); + + // Get unique user ID for weight history querying + try (DBHandler db = GBApplication.acquireDB()) { + userId = DBHelper.getUser(db.getDaoSession()).getId(); + } catch (Exception e) { + LOG.error("Error acquiring database", e); + } + + deviceInfoProfile = new DeviceInfoProfile<>(this); + deviceInfoProfile.addListener(intent -> { + if (!DeviceInfoProfile.ACTION_DEVICE_INFO.equals(intent.getAction())) + return; + + DeviceInfo info = intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO); + + if (info == null) + return; + + GBDeviceEventVersionInfo event = new GBDeviceEventVersionInfo(); + event.fwVersion = info.getSoftwareRevision(); + event.hwVersion = info.getHardwareRevision(); + + handleGBDeviceEvent(event); + }); + + addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS); + addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE); + addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION); + addSupportedService(GattService.UUID_SERVICE_WEIGHT_SCALE); + addSupportedService(UUID.fromString(MiBandService.UUID_SERVICE_WEIGHT_SERVICE)); + addSupportedProfile(deviceInfoProfile); + } + + @Override + protected TransactionBuilder initializeDevice(TransactionBuilder builder) { + builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext())); + + deviceInfoProfile.requestDeviceInfo(builder); + + if (GBApplication.getPrefs().getBoolean("datetime_synconconnect", true)) + setTime(builder); + + builder.notify(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_WEIGHT_MEASUREMENT), true); + builder.notify(getCharacteristic(UUID_CHARACTERISTIC_WEIGHT_HISTORY), true); + + // Query weight measurements saved by the scale + sendHistoryCommand(builder, CMD_HISTORY_START, true); + sendHistoryCommand(builder, CMD_HISTORY_QUERY, false); + + builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext())); + + return builder; + } + + @Override + public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + if (super.onCharacteristicChanged(gatt, characteristic)) + return true; + + UUID uuid = characteristic.getUuid(); + + if (!uuid.equals(GattCharacteristic.UUID_CHARACTERISTIC_WEIGHT_MEASUREMENT) && + !uuid.equals(UUID_CHARACTERISTIC_WEIGHT_HISTORY)) + return false; + + byte[] data = characteristic.getValue(); + + if (data.length == 1 && data[0] == CMD_HISTORY_COMPLETE) { + TransactionBuilder builder = createTransactionBuilder("ack"); + + // Acknowledge weight history reception + sendHistoryCommand(builder, CMD_HISTORY_COMPLETE, false); + sendHistoryCommand(builder, CMD_HISTORY_END, true); + builder.notify(getCharacteristic(UUID_CHARACTERISTIC_WEIGHT_HISTORY), false); + builder.queue(getQueue()); + } else { + ByteBuffer buf = ByteBuffer.wrap(characteristic.getValue()); + List measurements = new ArrayList<>(); + WeightMeasurement measurement = WeightMeasurement.decode(buf); + + // Weight history characteristic often has two measurements in one packet + while (measurement != null) { + measurements.add(measurement); + measurement = WeightMeasurement.decode(buf); + } + + saveMeasurements(measurements); + } + + return true; + } + + @Override + public void onReset(int flags) { + if ((flags & GBDeviceProtocol.RESET_FLAGS_FACTORY_RESET) == 0) + return; + + try { + TransactionBuilder builder = performInitialized("reset"); + + setConfigValue(builder, CFG_RESET_HISTORY, (byte)0x00); + builder.queue(getQueue()); + } catch (IOException e) { + LOG.error("Error", e); + } + } + + @Override + public void onSendConfiguration(String config) { + SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); + + try { + TransactionBuilder builder = performInitialized("config"); + + if (config.equals(PREF_MISCALE_WEIGHT_UNIT)) { + int unit = Integer.parseInt(prefs.getString(PREF_MISCALE_WEIGHT_UNIT, "0")); + + setConfigValue(builder, CFG_WEIGHT_UNIT, (byte)unit); + } else if (config.equals(PREF_MISCALE_SMALL_OBJECTS)) { + boolean enabled = prefs.getBoolean(PREF_MISCALE_SMALL_OBJECTS, false); + + setConfigValue(builder, CFG_SMALL_OBJECTS, enabled ? (byte)0x01: (byte)0x00); + } + + builder.queue(getQueue()); + } catch (IOException e) { + LOG.error("Error", e); + } + } + + @Override + public boolean useAutoConnect() { + return false; + } + + private void setTime(TransactionBuilder builder) { + GregorianCalendar now = BLETypeConversions.createCalendar(); + byte[] time = BLETypeConversions.calendarToCurrentTime(now, 0); + + builder.write(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_CURRENT_TIME), time); + } + + private void setConfigValue(TransactionBuilder builder, byte config, byte value) { + byte[] data = new byte[] { (byte)0x06, config, (byte)0x00, value }; + + builder.write(getCharacteristic(UUID_CHARACTERISTIC_CONFIG), data); + } + + private void sendHistoryCommand(TransactionBuilder builder, byte cmd, boolean includeUserId) { + ByteBuffer buf = ByteBuffer.allocate(includeUserId ? 5 : 1); + + buf.put(cmd); + + // The user ID is directly related to the account ID in the Zepp Life app + if (includeUserId) + buf.putInt((int)userId); + + builder.write(getCharacteristic(UUID_CHARACTERISTIC_WEIGHT_HISTORY), buf.array()); + } + + private void saveMeasurements(List measurements) { + SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); + boolean allowSmallObjects = prefs.getBoolean(PREF_MISCALE_SMALL_OBJECTS, false); + + try (DBHandler db = GBApplication.acquireDB()) { + MiScaleSampleProvider provider = new MiScaleSampleProvider(getDevice(), db.getDaoSession()); + List samples = new ArrayList<>(); + Long userId = DBHelper.getUser(db.getDaoSession()).getId(); + Long deviceId = DBHelper.getDevice(getDevice(), db.getDaoSession()).getId(); + + for (WeightMeasurement measurement : measurements) { + // Skip measurements of small objects if not allowed + if (!allowSmallObjects && measurement.getWeightKg() < SMALL_OBJECT_MAX_WEIGHT) + continue; + + samples.add(new MiScaleWeightSample( + measurement.getTimestamp().getTime(), + deviceId, + userId, + measurement.getWeightKg() + )); + } + + provider.addSamples(samples); + } catch (Exception e) { + LOG.error("Error acquiring database", e); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miscale/WeightMeasurement.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miscale/WeightMeasurement.java new file mode 100644 index 000000000..e590c72b4 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miscale/WeightMeasurement.java @@ -0,0 +1,87 @@ +/* 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.service.devices.miscale; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Calendar; +import java.util.Date; + +import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; + +public class WeightMeasurement { + private static final Logger LOG = LoggerFactory.getLogger(WeightMeasurement.class); + + private Date timestamp; + private float weightKg; + + public Date getTimestamp() { + return timestamp; + } + + public float getWeightKg() { + return weightKg; + } + + private WeightMeasurement(Date timestamp, float weightKg) { + LOG.debug("Measurement: timestamp={}, weightKg={}", timestamp, String.format("%.2f", weightKg)); + + this.timestamp = timestamp; + this.weightKg = weightKg; + } + + public static WeightMeasurement decode(ByteBuffer buf) { + if (buf.remaining() < 10) + return null; + + buf.order(ByteOrder.LITTLE_ENDIAN); + + byte flags = buf.get(); + boolean stabilized = testBit(flags, 5) && !testBit(flags, 7); + + // Only decode measurement once weight reading has stabilized + if (!stabilized) + return null; + + float weightKg = weightToKg(buf.getShort(), flags); + + byte[] timestamp = new byte[7]; + buf.get(timestamp); + Calendar calendar = BLETypeConversions.rawBytesToCalendar(timestamp); + + return new WeightMeasurement(calendar.getTime(), weightKg); + } + + private static float weightToKg(float weight, byte flags) { + boolean isLbs = testBit(flags, 0); + boolean isJin = testBit(flags, 4); + + if (isLbs) + return (weight / 100) * 0.45359237f; + else if (isJin) + return (weight / 100) * 0.5f; + + return weight / 200; + } + + private static boolean testBit(byte value, int offset) { + return ((value >> offset) & 1) == 1; + } +} diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 8dfa52dff..2306ab0e2 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -3722,6 +3722,18 @@ 5 + + @string/miscale_weight_unit_metric + @string/miscale_weight_unit_imperial + @string/miscale_weight_unit_chinese + + + + 0 + 1 + 2 + + de.dennisguse.opentracks de.dennisguse.opentracks.playStore diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0a3124f06..b9d0d75fb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1720,6 +1720,7 @@ Casio GBX-100 Casio GW-B5600 Casio GMW-B5000 + Mi Smart Scale 2 Mi Body Composition Scale 2 iTag BFH-16 @@ -2606,6 +2607,13 @@ Band 9 Frequency Value + Weight Unit + Set unit of weight for displayed measurements + Metric (kg) + Imperial (lbs) + Chinese (jin) + Small Objects + Store weight of objects lighter than 10 kg Protocol Version Auto Brightness Adjust screen brightness according to ambient light diff --git a/app/src/main/res/xml/devicesettings_mismartscale.xml b/app/src/main/res/xml/devicesettings_mismartscale.xml new file mode 100644 index 000000000..767b53330 --- /dev/null +++ b/app/src/main/res/xml/devicesettings_mismartscale.xml @@ -0,0 +1,18 @@ + + + + +