mirror of
https://github.com/oliexdev/openScale.git
synced 2025-08-20 07:21:40 +02:00
read Mi Scale history weight measurements
This commit is contained in:
@@ -27,7 +27,6 @@ import android.bluetooth.BluetoothGatt;
|
|||||||
import android.bluetooth.BluetoothGattCallback;
|
import android.bluetooth.BluetoothGattCallback;
|
||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
import android.bluetooth.BluetoothGattDescriptor;
|
import android.bluetooth.BluetoothGattDescriptor;
|
||||||
import android.bluetooth.BluetoothGattService;
|
|
||||||
import android.bluetooth.BluetoothProfile;
|
import android.bluetooth.BluetoothProfile;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@@ -35,26 +34,32 @@ import android.util.Log;
|
|||||||
|
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.Arrays;
|
||||||
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public class BluetoothMiScale extends BluetoothCommunication {
|
public class BluetoothMiScale extends BluetoothCommunication {
|
||||||
|
|
||||||
private BluetoothGatt bluetoothGatt;
|
private BluetoothGatt bluetoothGatt;
|
||||||
private BluetoothGattCharacteristic weightCharacteristic;
|
private BluetoothAdapter.LeScanCallback scanCallback;
|
||||||
|
|
||||||
private BluetoothAdapter.LeScanCallback scanCallback = null;
|
private final UUID WEIGHT_MEASUREMENT_SERVICE = UUID.fromString("0000181d-0000-1000-8000-00805f9b34fb");
|
||||||
private BluetoothGattCallback gattCallback = null;
|
private final UUID WEIGHT_MEASUREMENT_CHARACTERISTIC = UUID.fromString("00002a9d-0000-1000-8000-00805f9b34fb");
|
||||||
|
private final UUID WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC = UUID.fromString("00002a2f-0000-3512-2118-0009af100700");
|
||||||
|
private final UUID WEIGHT_MEASUREMENT_TIME_CHARACTERISTIC = UUID.fromString("00002a2b-0000-1000-8000-00805f9b34fb");
|
||||||
|
private final UUID WEIGHT_MEASUREMENT_CONFIG = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
|
||||||
|
|
||||||
private Handler searchHandler;
|
private Handler searchHandler;
|
||||||
|
|
||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
private String btDeviceName;
|
private String btDeviceName;
|
||||||
|
private int nextState;
|
||||||
|
private boolean restartDiscovery;;
|
||||||
|
|
||||||
public BluetoothMiScale(Context con) {
|
public BluetoothMiScale(Context con) {
|
||||||
searchHandler = new Handler();
|
searchHandler = new Handler();
|
||||||
context = con;
|
context = con;
|
||||||
|
scanCallback = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -63,8 +68,24 @@ public class BluetoothMiScale extends BluetoothCommunication {
|
|||||||
|
|
||||||
if (scanCallback == null)
|
if (scanCallback == null)
|
||||||
{
|
{
|
||||||
Log.d("StartStopBLEScanning", "No callback method, making one...");
|
scanCallback = new BluetoothAdapter.LeScanCallback()
|
||||||
prepareBLECallback();
|
{
|
||||||
|
@Override
|
||||||
|
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord)
|
||||||
|
{
|
||||||
|
if (device.getAddress().replace(":", "").startsWith("880f10") ||
|
||||||
|
device.getAddress().replace(":", "").startsWith("880F10")) // Xiaomi
|
||||||
|
{
|
||||||
|
if (device.getName().equals(btDeviceName)) { // It really is scale
|
||||||
|
Log.d("BluetoothMiScale", "Mi Scale found trying to connect...");
|
||||||
|
bluetoothGatt = device.connectGatt(context, false, gattCallback);
|
||||||
|
|
||||||
|
searchHandler.removeCallbacksAndMessages(null);
|
||||||
|
btAdapter.stopLeScan(scanCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -93,110 +114,180 @@ public class BluetoothMiScale extends BluetoothCommunication {
|
|||||||
btAdapter.stopLeScan(scanCallback);
|
btAdapter.stopLeScan(scanCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void prepareBLECallback()
|
private BluetoothGattCallback gattCallback= new BluetoothGattCallback() {
|
||||||
{
|
|
||||||
scanCallback = new BluetoothAdapter.LeScanCallback()
|
|
||||||
{
|
|
||||||
@Override
|
@Override
|
||||||
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord)
|
public void onConnectionStateChange(final BluetoothGatt gatt, int status, int newState) {
|
||||||
{
|
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
||||||
if (device.getAddress().replace(":", "").startsWith("880f10") ||
|
Log.d("BluetoothMiScale", "Connection established");
|
||||||
device.getAddress().replace(":", "").startsWith("880F10")) // Xiaomi
|
|
||||||
{
|
|
||||||
if (device.getName().equals(btDeviceName)) { // It really is scale
|
|
||||||
if (gattCallback == null) {
|
|
||||||
Log.d("StartGatt", "No callback method, making one...");
|
|
||||||
prepareGATTCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
bluetoothGatt = device.connectGatt(context, false, gattCallback);
|
|
||||||
|
|
||||||
searchHandler.removeCallbacksAndMessages(null);
|
|
||||||
btAdapter.stopLeScan(scanCallback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void prepareGATTCallback()
|
|
||||||
{
|
|
||||||
gattCallback = new BluetoothGattCallback()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onConnectionStateChange(final BluetoothGatt gatt, int status, int newState)
|
|
||||||
{
|
|
||||||
super.onConnectionStateChange(gatt, status, newState);
|
|
||||||
|
|
||||||
if (newState == BluetoothProfile.STATE_CONNECTED)
|
|
||||||
{
|
|
||||||
callbackBtHandler.obtainMessage(BluetoothCommunication.BT_CONNECTION_ESTABLISHED).sendToTarget();
|
callbackBtHandler.obtainMessage(BluetoothCommunication.BT_CONNECTION_ESTABLISHED).sendToTarget();
|
||||||
gatt.discoverServices();
|
gatt.discoverServices();
|
||||||
}
|
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
||||||
else if (newState == BluetoothProfile.STATE_DISCONNECTED)
|
Log.d("BluetoothMiScale", "Connection lost");
|
||||||
{
|
|
||||||
callbackBtHandler.obtainMessage(BluetoothCommunication.BT_CONNECTION_LOST).sendToTarget();
|
callbackBtHandler.obtainMessage(BluetoothCommunication.BT_CONNECTION_LOST).sendToTarget();
|
||||||
stopSearching();
|
stopSearching();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onServicesDiscovered(final BluetoothGatt gatt, int status)
|
public void onServicesDiscovered(final BluetoothGatt gatt, int status) {
|
||||||
{
|
restartDiscovery = false;
|
||||||
super.onServicesDiscovered(gatt, status);
|
nextState = 0;
|
||||||
|
|
||||||
ArrayList<BluetoothGattService> serviceList = (ArrayList) gatt.getServices();
|
invokeNextBluetoothCmd(gatt);
|
||||||
|
|
||||||
for (BluetoothGattService one : serviceList)
|
|
||||||
{
|
|
||||||
if (one != null)
|
|
||||||
{
|
|
||||||
ArrayList<BluetoothGattCharacteristic> characteristicsList = (ArrayList) one.getCharacteristics();
|
|
||||||
for (BluetoothGattCharacteristic two : characteristicsList)
|
|
||||||
{
|
|
||||||
if (two.getUuid().toString().startsWith("00002a9d"))
|
|
||||||
{
|
|
||||||
// Weight param discovered
|
|
||||||
Log.d(gatt.getDevice().getName(), "(" + gatt.getDevice().getAddress() + ")");
|
|
||||||
|
|
||||||
weightCharacteristic = two;
|
|
||||||
|
|
||||||
// Start constant notification
|
|
||||||
bluetoothGatt.setCharacteristicNotification(weightCharacteristic, true);
|
|
||||||
|
|
||||||
ArrayList<BluetoothGattDescriptor> descriptors = (ArrayList<BluetoothGattDescriptor>) weightCharacteristic.getDescriptors();
|
|
||||||
|
|
||||||
if ( descriptors.size() == 1 )
|
|
||||||
{
|
|
||||||
descriptors.get(0).setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
|
|
||||||
bluetoothGatt.writeDescriptor(descriptors.get(0));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Log.e("GattCallback", "Multiple descriptors found in weight characteristic, unhandled exception!");
|
|
||||||
System.exit(-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void invokeNextBluetoothCmd(BluetoothGatt gatt) {
|
||||||
|
BluetoothGattCharacteristic characteristic;
|
||||||
|
BluetoothGattDescriptor descriptor;
|
||||||
|
|
||||||
|
Log.d("BluetoothMiScale", "Bluetooth Cmd State: " + nextState);
|
||||||
|
|
||||||
|
switch (nextState) {
|
||||||
|
case 0:
|
||||||
|
// read device time
|
||||||
|
characteristic = gatt.getService(WEIGHT_MEASUREMENT_SERVICE)
|
||||||
|
.getCharacteristic(WEIGHT_MEASUREMENT_TIME_CHARACTERISTIC);
|
||||||
|
|
||||||
|
gatt.readCharacteristic(characteristic);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
// set notification on for weight measurement
|
||||||
|
characteristic = gatt.getService(WEIGHT_MEASUREMENT_SERVICE)
|
||||||
|
.getCharacteristic(WEIGHT_MEASUREMENT_CHARACTERISTIC);
|
||||||
|
|
||||||
|
gatt.setCharacteristicNotification(characteristic, true);
|
||||||
|
|
||||||
|
descriptor = characteristic.getDescriptor(WEIGHT_MEASUREMENT_CONFIG);
|
||||||
|
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
|
||||||
|
gatt.writeDescriptor(descriptor);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
// set notification on for weight measurement history
|
||||||
|
characteristic = gatt.getService(WEIGHT_MEASUREMENT_SERVICE)
|
||||||
|
.getCharacteristic(WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC);
|
||||||
|
|
||||||
|
gatt.setCharacteristicNotification(characteristic, true);
|
||||||
|
|
||||||
|
descriptor = characteristic.getDescriptor(WEIGHT_MEASUREMENT_CONFIG);
|
||||||
|
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
|
||||||
|
gatt.writeDescriptor(descriptor);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
// Invoke receiving history data
|
||||||
|
characteristic = gatt.getService(WEIGHT_MEASUREMENT_SERVICE)
|
||||||
|
.getCharacteristic(WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC);
|
||||||
|
|
||||||
|
characteristic.setValue(new byte[]{0x02});
|
||||||
|
gatt.writeCharacteristic(characteristic);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
// Stop receiving history data
|
||||||
|
/*characteristic = gatt.getService(WEIGHT_MEASUREMENT_SERVICE)
|
||||||
|
.getCharacteristic(WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC);
|
||||||
|
|
||||||
|
characteristic.setValue(new byte[]{0x03});
|
||||||
|
gatt.writeCharacteristic(characteristic);*/
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
// Stop state machine
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log.e("BluetoothMiScale", "Error invalid Bluetooth State");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDescriptorWrite(BluetoothGatt gatt,
|
||||||
|
BluetoothGattDescriptor descriptor,
|
||||||
|
int status) {
|
||||||
|
nextState++;
|
||||||
|
invokeNextBluetoothCmd(gatt);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCharacteristicWrite (BluetoothGatt gatt,
|
||||||
|
BluetoothGattCharacteristic characteristic,
|
||||||
|
int status) {
|
||||||
|
if (restartDiscovery) {
|
||||||
|
stopSearching();
|
||||||
|
startSearching(btDeviceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextState++;
|
||||||
|
invokeNextBluetoothCmd(gatt);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCharacteristicRead (BluetoothGatt gatt,
|
||||||
|
BluetoothGattCharacteristic characteristic,
|
||||||
|
int status) {
|
||||||
|
byte[] data = characteristic.getValue();
|
||||||
|
|
||||||
|
int currentYear = Calendar.getInstance().get(Calendar.YEAR);
|
||||||
|
int scaleYear = ((data[1] & 0xFF) << 8) | (data[0] & 0xFF);
|
||||||
|
|
||||||
|
if (currentYear != scaleYear) {
|
||||||
|
Log.d("BluetoothMiScale", "Current year and scale year is different");
|
||||||
|
setCurrentTimeOnDevice(gatt);
|
||||||
|
} else {
|
||||||
|
nextState++;
|
||||||
|
invokeNextBluetoothCmd(gatt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCharacteristicChanged(BluetoothGatt gatt,
|
public void onCharacteristicChanged(BluetoothGatt gatt,
|
||||||
BluetoothGattCharacteristic characteristic)
|
BluetoothGattCharacteristic characteristic) {
|
||||||
{
|
|
||||||
super.onCharacteristicChanged(gatt, characteristic);
|
|
||||||
|
|
||||||
final byte[] data = characteristic.getValue();
|
final byte[] data = characteristic.getValue();
|
||||||
if (data != null && data.length > 0) {
|
|
||||||
|
|
||||||
|
Log.d("BluetoothMiScale", "Char changed");
|
||||||
|
|
||||||
|
printByteInHex(data);
|
||||||
|
|
||||||
|
if (data != null && data.length > 0) {
|
||||||
|
if (data.length == 20) {
|
||||||
|
final byte[] firstWeight = Arrays.copyOfRange(data, 0, 10);
|
||||||
|
final byte[] secondWeight = Arrays.copyOfRange(data, 10, 20);
|
||||||
|
parseBytes(firstWeight);
|
||||||
|
parseBytes(secondWeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.length == 10) {
|
||||||
|
parseBytes(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setCurrentTimeOnDevice(BluetoothGatt gatt) {
|
||||||
|
// set current time
|
||||||
|
BluetoothGattCharacteristic characteristic = gatt.getService(WEIGHT_MEASUREMENT_SERVICE)
|
||||||
|
.getCharacteristic(WEIGHT_MEASUREMENT_TIME_CHARACTERISTIC);
|
||||||
|
|
||||||
|
Log.d("BluetoothMiScale", "Set current time on Mi scale");
|
||||||
|
|
||||||
|
Calendar currentDateTime = Calendar.getInstance();
|
||||||
|
int year = currentDateTime.get(Calendar.YEAR);
|
||||||
|
byte month = (byte)(currentDateTime.get(Calendar.MONTH)+1);
|
||||||
|
byte day = (byte)currentDateTime.get(Calendar.DAY_OF_MONTH);
|
||||||
|
byte hour = (byte)currentDateTime.get(Calendar.HOUR_OF_DAY);
|
||||||
|
byte min = (byte)currentDateTime.get(Calendar.MINUTE);
|
||||||
|
byte sec = (byte)currentDateTime.get(Calendar.SECOND);
|
||||||
|
|
||||||
|
byte[] dateTimeByte = {(byte)(year), (byte)(year >> 8), month, day, hour, min, sec, 0x03, 0x00, 0x00};
|
||||||
|
|
||||||
|
restartDiscovery = true;
|
||||||
|
|
||||||
|
characteristic.setValue(dateTimeByte);
|
||||||
|
gatt.writeCharacteristic(characteristic);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseBytes(byte[] weightBytes) {
|
||||||
try {
|
try {
|
||||||
float weight = 0.0f;
|
float weight = 0.0f;
|
||||||
|
|
||||||
final byte ctrlByte = data[0];
|
final byte ctrlByte = weightBytes[0];
|
||||||
|
|
||||||
final boolean isWeightRemoved = isBitSet(ctrlByte, 7);
|
final boolean isWeightRemoved = isBitSet(ctrlByte, 7);
|
||||||
final boolean isStabilized = isBitSet(ctrlByte, 5);
|
final boolean isStabilized = isBitSet(ctrlByte, 5);
|
||||||
@@ -215,17 +306,17 @@ public class BluetoothMiScale extends BluetoothCommunication {
|
|||||||
// Only if the value is stabilized and the weight is *not* removed, the date is valid
|
// Only if the value is stabilized and the weight is *not* removed, the date is valid
|
||||||
if (isStabilized && !isWeightRemoved) {
|
if (isStabilized && !isWeightRemoved) {
|
||||||
|
|
||||||
final int year = ((data[4] & 0xFF) << 8) | (data[3] & 0xFF);
|
final int year = ((weightBytes[4] & 0xFF) << 8) | (weightBytes[3] & 0xFF);
|
||||||
final int month = (int) data[5];
|
final int month = (int) weightBytes[5];
|
||||||
final int day = (int) data[6];
|
final int day = (int) weightBytes[6];
|
||||||
final int hours = (int) data[7];
|
final int hours = (int) weightBytes[7];
|
||||||
final int min = (int) data[8];
|
final int min = (int) weightBytes[8];
|
||||||
final int sec = (int) data[9];
|
final int sec = (int) weightBytes[9];
|
||||||
|
|
||||||
if (isLBSUnit || isCattyUnit) {
|
if (isLBSUnit || isCattyUnit) {
|
||||||
weight = (float) (((data[2] & 0xFF) << 8) | (data[1] & 0xFF)) / 100.0f;
|
weight = (float) (((weightBytes[2] & 0xFF) << 8) | (weightBytes[1] & 0xFF)) / 100.0f;
|
||||||
} else {
|
} else {
|
||||||
weight = (float) (((data[2] & 0xFF) << 8) | (data[1] & 0xFF)) / 200.0f;
|
weight = (float) (((weightBytes[2] & 0xFF) << 8) | (weightBytes[1] & 0xFF)) / 200.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
String date_string = year + "/" + month + "/" + day + "/" + hours + "/" + min;
|
String date_string = year + "/" + month + "/" + day + "/" + hours + "/" + min;
|
||||||
@@ -242,11 +333,22 @@ public class BluetoothMiScale extends BluetoothCommunication {
|
|||||||
callbackBtHandler.obtainMessage(BluetoothCommunication.BT_UNEXPECTED_ERROR, "Error while decoding bluetooth date string (" + e.getMessage() + ")").sendToTarget();
|
callbackBtHandler.obtainMessage(BluetoothCommunication.BT_UNEXPECTED_ERROR, "Error while decoding bluetooth date string (" + e.getMessage() + ")").sendToTarget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isBitSet(byte value, int bit) {
|
private boolean isBitSet(byte value, int bit) {
|
||||||
return (value & (1 << bit)) != 0;
|
return (value & (1 << bit)) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void printByteInHex(byte[] data) {
|
||||||
|
if (data == null) {
|
||||||
|
Log.e("BluetoothMiScale", "Data is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final StringBuilder stringBuilder = new StringBuilder(data.length);
|
||||||
|
for(byte byteChar : data) {
|
||||||
|
stringBuilder.append(String.format("%02X ", byteChar));
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("BluetoothMiScale", "Raw hex data: " + stringBuilder);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user