mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-09 03:37:03 +01:00
Add support for Mi Smart Scale 2
This commit is contained in:
parent
3be6ec0007
commit
ef1d7171e8
@ -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_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_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 PREF_QC35_NOISE_CANCELLING_LEVEL = "qc35_noise_cancelling_level";
|
||||||
|
|
||||||
public static final String PREFS_ACTIVITY_IN_DEVICE_CARD = "prefs_activity_in_device_card";
|
public static final String PREFS_ACTIVITY_IN_DEVICE_CARD = "prefs_activity_in_device_card";
|
||||||
|
@ -757,6 +757,9 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i
|
|||||||
addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_ANC_MODE_EARBUD);
|
addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_ANC_MODE_EARBUD);
|
||||||
addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_ANC_MODE_TRIGGER);
|
addPreferenceHandlerFor(PREF_MOONDROP_TOUCH_ANC_MODE_TRIGGER);
|
||||||
|
|
||||||
|
addPreferenceHandlerFor(PREF_MISCALE_WEIGHT_UNIT);
|
||||||
|
addPreferenceHandlerFor(PREF_MISCALE_SMALL_OBJECTS);
|
||||||
|
|
||||||
addPreferenceHandlerFor(PREF_FEMOMETER_MEASUREMENT_MODE);
|
addPreferenceHandlerFor(PREF_FEMOMETER_MEASUREMENT_MODE);
|
||||||
|
|
||||||
addPreferenceHandlerFor(PREF_QC35_NOISE_CANCELLING_LEVEL);
|
addPreferenceHandlerFor(PREF_QC35_NOISE_CANCELLING_LEVEL);
|
||||||
|
@ -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 <https://www.gnu.org/licenses/>. */
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -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.MijiaLywsd02Coordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd.MijiaLywsd03Coordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd.MijiaLywsd03Coordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.mijia_lywsd.MijiaMhoC303Coordinator;
|
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.miscale.MiCompositionScaleCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.moondrop.MoondropSpaceTravelCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.moondrop.MoondropSpaceTravelCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.no1f1.No1F1Coordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.no1f1.No1F1Coordinator;
|
||||||
@ -355,6 +356,7 @@ public enum DeviceType {
|
|||||||
CASIOGBX100(CasioGBX100DeviceCoordinator.class),
|
CASIOGBX100(CasioGBX100DeviceCoordinator.class),
|
||||||
CASIOGWB5600(CasioGWB5600DeviceCoordinator.class),
|
CASIOGWB5600(CasioGWB5600DeviceCoordinator.class),
|
||||||
CASIOGMWB5000(CasioGMWB5000DeviceCoordinator.class),
|
CASIOGMWB5000(CasioGMWB5000DeviceCoordinator.class),
|
||||||
|
MISMARTSCALE(MiSmartScaleCoordinator.class),
|
||||||
MICOMPOSITIONSCALE(MiCompositionScaleCoordinator.class),
|
MICOMPOSITIONSCALE(MiCompositionScaleCoordinator.class),
|
||||||
BFH16(BFH16DeviceCoordinator.class),
|
BFH16(BFH16DeviceCoordinator.class),
|
||||||
MAKIBESHR3(MakibesHR3Coordinator.class),
|
MAKIBESHR3(MakibesHR3Coordinator.class),
|
||||||
|
@ -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 <https://www.gnu.org/licenses/>. */
|
||||||
|
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<MiSmartScaleDeviceSupport> 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<WeightMeasurement> 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<WeightMeasurement> 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<MiScaleWeightSample> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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 <https://www.gnu.org/licenses/>. */
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -3722,6 +3722,18 @@
|
|||||||
<item>5</item>
|
<item>5</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="miscale_weight_unit_names">
|
||||||
|
<item>@string/miscale_weight_unit_metric</item>
|
||||||
|
<item>@string/miscale_weight_unit_imperial</item>
|
||||||
|
<item>@string/miscale_weight_unit_chinese</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="miscale_weight_unit_values">
|
||||||
|
<item>0</item>
|
||||||
|
<item>1</item>
|
||||||
|
<item>2</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
<string-array name="fitness_tracking_apps_package_names">
|
<string-array name="fitness_tracking_apps_package_names">
|
||||||
<item>de.dennisguse.opentracks</item>
|
<item>de.dennisguse.opentracks</item>
|
||||||
<item>de.dennisguse.opentracks.playStore</item>
|
<item>de.dennisguse.opentracks.playStore</item>
|
||||||
|
@ -1720,6 +1720,7 @@
|
|||||||
<string name="devicetype_casiogbx100">Casio GBX-100</string>
|
<string name="devicetype_casiogbx100">Casio GBX-100</string>
|
||||||
<string name="devicetype_casiogwb5600">Casio GW-B5600</string>
|
<string name="devicetype_casiogwb5600">Casio GW-B5600</string>
|
||||||
<string name="devicetype_casiogmwb5000">Casio GMW-B5000</string>
|
<string name="devicetype_casiogmwb5000">Casio GMW-B5000</string>
|
||||||
|
<string name="devicetype_mismartscale">Mi Smart Scale 2</string>
|
||||||
<string name="devicetype_micompositionscale">Mi Body Composition Scale 2</string>
|
<string name="devicetype_micompositionscale">Mi Body Composition Scale 2</string>
|
||||||
<string name="devicetype_itag">iTag</string>
|
<string name="devicetype_itag">iTag</string>
|
||||||
<string name="devicetype_bfh16">BFH-16</string>
|
<string name="devicetype_bfh16">BFH-16</string>
|
||||||
@ -2606,6 +2607,13 @@
|
|||||||
<string name="soundcore_equalizer_band9">Band 9</string>
|
<string name="soundcore_equalizer_band9">Band 9</string>
|
||||||
<string name="soundcore_equalizer_frequency">Frequency</string>
|
<string name="soundcore_equalizer_frequency">Frequency</string>
|
||||||
<string name="soundcore_equalizer_value">Value</string>
|
<string name="soundcore_equalizer_value">Value</string>
|
||||||
|
<string name="miscale_weight_unit_title">Weight Unit</string>
|
||||||
|
<string name="miscale_weight_unit_summary">Set unit of weight for displayed measurements</string>
|
||||||
|
<string name="miscale_weight_unit_metric">Metric (kg)</string>
|
||||||
|
<string name="miscale_weight_unit_imperial">Imperial (lbs)</string>
|
||||||
|
<string name="miscale_weight_unit_chinese">Chinese (jin)</string>
|
||||||
|
<string name="miscale_small_objects_title">Small Objects</string>
|
||||||
|
<string name="miscale_small_objects_summary">Store weight of objects lighter than 10 kg</string>
|
||||||
<string name="protocol_version">Protocol Version</string>
|
<string name="protocol_version">Protocol Version</string>
|
||||||
<string name="pref_screen_auto_brightness_title">Auto Brightness</string>
|
<string name="pref_screen_auto_brightness_title">Auto Brightness</string>
|
||||||
<string name="pref_screen_auto_brightness_summary">Adjust screen brightness according to ambient light</string>
|
<string name="pref_screen_auto_brightness_summary">Adjust screen brightness according to ambient light</string>
|
||||||
|
18
app/src/main/res/xml/devicesettings_mismartscale.xml
Normal file
18
app/src/main/res/xml/devicesettings_mismartscale.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<ListPreference
|
||||||
|
android:defaultValue="0"
|
||||||
|
android:entries="@array/miscale_weight_unit_names"
|
||||||
|
android:entryValues="@array/miscale_weight_unit_values"
|
||||||
|
android:icon="@drawable/ic_calculate"
|
||||||
|
android:key="pref_miscale_weight_unit"
|
||||||
|
android:title="@string/miscale_weight_unit_title"
|
||||||
|
android:summary="@string/miscale_weight_unit_summary" />
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:icon="@drawable/ic_pressure"
|
||||||
|
android:key="pref_miscale_small_objects"
|
||||||
|
android:layout="@layout/preference_checkbox"
|
||||||
|
android:title="@string/miscale_small_objects_title"
|
||||||
|
android:summary="@string/miscale_small_objects_summary" />
|
||||||
|
</androidx.preference.PreferenceScreen>
|
Loading…
Reference in New Issue
Block a user