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 extends WeightSample> 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 extends DeviceSupport> 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 @@
+
+
+
+
+