mirror of
https://github.com/oliexdev/openScale.git
synced 2025-08-09 10:16:44 +02:00
refactoring existing Bluetooth classes for easy programming new Bluetooth classes.
This commit is contained in:
@@ -17,27 +17,58 @@
|
||||
package com.health.openscale.core.bluetooth;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCallback;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattDescriptor;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import com.health.openscale.core.datatypes.ScaleData;
|
||||
|
||||
import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_STATUS_CODE.BT_RETRIEVE_SCALE_DATA;
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class BluetoothCommunication {
|
||||
public enum BT_STATUS_CODE {BT_RETRIEVE_SCALE_DATA, BT_INIT_PROCESS, BT_CONNECTION_ESTABLISHED, BT_CONNECTION_LOST, BT_NO_DEVICE_FOUND, BT_UNEXPECTED_ERROR };
|
||||
|
||||
private Handler callbackBtHandler;
|
||||
protected BluetoothAdapter btAdapter;
|
||||
public enum BT_MACHINE_STATE {BT_INIT_STATE, BT_CMD_STATE, BT_CLEANUP_STATE}
|
||||
|
||||
protected Context context;
|
||||
|
||||
private Handler callbackBtHandler;
|
||||
private BluetoothGatt bluetoothGatt;
|
||||
protected BluetoothGattCallback gattCallback;
|
||||
private BluetoothAdapter.LeScanCallback scanCallback;
|
||||
protected BluetoothAdapter btAdapter;
|
||||
private Handler searchHandler;
|
||||
private String btDeviceName;
|
||||
|
||||
private int cmdStepNr;
|
||||
private int initStepNr;
|
||||
private int cleanupStepNr;
|
||||
private BT_MACHINE_STATE btMachineState;
|
||||
|
||||
private final UUID WEIGHT_MEASUREMENT_CONFIG = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
|
||||
|
||||
public BluetoothCommunication(Context context)
|
||||
{
|
||||
this.context = context;
|
||||
btAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||
searchHandler = new Handler();
|
||||
scanCallback = null;
|
||||
gattCallback = new GattCallback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a new Bluetooth object.
|
||||
*
|
||||
* @param context In which context should the Bluetooth device created
|
||||
* @param i the specidific number of which Bluetooth device should be created (correspond to "deviceTypes" key in BluetoothPreferences)
|
||||
* @return created object specified by the number i otherwise null
|
||||
*/
|
||||
public static BluetoothCommunication getBtDevice(Context context, int i) {
|
||||
switch (i) {
|
||||
case 0:
|
||||
@@ -53,43 +84,429 @@ public abstract class BluetoothCommunication {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback Bluetooth handler that notify any BT_STATUS_CODE changes for GUI/CORE.
|
||||
*
|
||||
* @param cbBtHandler a handler that is registered
|
||||
*/
|
||||
public void registerCallbackHandler(Handler cbBtHandler) {
|
||||
callbackBtHandler = cbBtHandler;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set for the openScale GUI/CORE the Bluetooth status code.
|
||||
*
|
||||
* @param statusCode the status code that should be set
|
||||
*/
|
||||
protected void setBtStatus(BT_STATUS_CODE statusCode) {
|
||||
setBtStatus(statusCode, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set for the openScale GUI/CORE the Bluetooth status code.
|
||||
*
|
||||
* @param statusCode the status code that should be set
|
||||
* @param infoText the information text that is displayed to the status code.
|
||||
*/
|
||||
protected void setBtStatus(BT_STATUS_CODE statusCode, String infoText) {
|
||||
callbackBtHandler.obtainMessage(statusCode.ordinal(), infoText).sendToTarget();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new scale data to openScale
|
||||
*
|
||||
* @param scaleData the scale data that should be added to openScale
|
||||
*/
|
||||
protected void addScaleData(ScaleData scaleData) {
|
||||
callbackBtHandler.obtainMessage(BT_RETRIEVE_SCALE_DATA.ordinal(), scaleData).sendToTarget();
|
||||
callbackBtHandler.obtainMessage(BT_STATUS_CODE.BT_RETRIEVE_SCALE_DATA.ordinal(), scaleData).sendToTarget();
|
||||
}
|
||||
|
||||
abstract public String deviceName();
|
||||
abstract public String defaultDeviceName();
|
||||
|
||||
abstract public void startSearching(String deviceName);
|
||||
abstract public void stopSearching();
|
||||
|
||||
/**
|
||||
* Is the Bluetooth initialized process supported.
|
||||
*
|
||||
* @return true if it supported otherwise false
|
||||
*/
|
||||
public boolean initSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the Bluetooth transfer process supported.
|
||||
*
|
||||
* @return true if it supported otherwise false
|
||||
*/
|
||||
public boolean transferSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the Bluetooth history process supported.
|
||||
*
|
||||
* @return true if it supported otherwise false
|
||||
*/
|
||||
public boolean historySupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supports Bluetooth device BLE (Bluetooth 4.x/smart).
|
||||
*
|
||||
* @return true if it Bluetooth 4.x (smart) otherwise false
|
||||
*/
|
||||
public boolean isBLE() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the device name checked while searching for a Bluetooth device.
|
||||
*
|
||||
* @return true if device name is checked otherwise false
|
||||
*/
|
||||
public boolean isDeviceNameCheck() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Bluetooth device name
|
||||
*
|
||||
* @return a string in a human readable name
|
||||
*/
|
||||
abstract public String deviceName();
|
||||
|
||||
/**
|
||||
* Return the Bluetooth default device name
|
||||
*
|
||||
* @return the Bluetooth default device name for the scale
|
||||
*/
|
||||
abstract public String defaultDeviceName();
|
||||
|
||||
/**
|
||||
* Return all hardware addresses of the Bluetooth device.
|
||||
*
|
||||
* The format should be the first six hex values of a know Bluetooth hardware address without any colon e.g. 12:AB:65:12:34:52 becomes "12AB65"
|
||||
*
|
||||
* @return a list of all hardware addresses that are known for this device.
|
||||
*/
|
||||
abstract public ArrayList<String> hwAddresses();
|
||||
|
||||
/**
|
||||
* State machine for the initialization process for the Bluetooth device.
|
||||
*
|
||||
* @param stateNr the current step number
|
||||
* @return false if no next step is available otherwise true
|
||||
*/
|
||||
abstract boolean nextInitCmd(int stateNr);
|
||||
|
||||
/**
|
||||
* State machine for the normal/command process for the Bluetooth device.
|
||||
*
|
||||
* This state machine is automatically triggered if initialization process is finished.
|
||||
*
|
||||
* @param stateNr the current step number
|
||||
* @return false if no next step is available otherwise true
|
||||
*/
|
||||
abstract boolean nextBluetoothCmd(int stateNr);
|
||||
|
||||
/**
|
||||
* State machine for the clean up process for the Bluetooth device.
|
||||
*
|
||||
* This state machine is *not* automatically triggered. You have to setBtMachineState(BT_MACHINE_STATE.BT_CLEANUP_STATE) to trigger this process if necessary.
|
||||
*
|
||||
* @param stateNr the current step number
|
||||
* @return false if no next step is available otherwise true
|
||||
*/
|
||||
abstract boolean nextCleanUpCmd(int stateNr);
|
||||
|
||||
/**
|
||||
* Method is triggered if a Bluetooth data is read from a device.
|
||||
*
|
||||
* @param bluetoothGatt the Bluetooth Gatt
|
||||
* @param gattCharacteristic the Bluetooth Gatt characteristic
|
||||
* @param status the status code
|
||||
*/
|
||||
protected void onBluetoothDataRead(BluetoothGatt bluetoothGatt, BluetoothGattCharacteristic gattCharacteristic, int status){};
|
||||
|
||||
/**
|
||||
* Method is triggered if a Bluetooth data from a device is notified or indicated.
|
||||
*
|
||||
* @param bluetoothGatt the Bluetooth Gatt
|
||||
* @param gattCharacteristic the Bluetooth characteristic
|
||||
*/
|
||||
protected void onBluetoothDataChange(BluetoothGatt bluetoothGatt, BluetoothGattCharacteristic gattCharacteristic){};
|
||||
|
||||
/**
|
||||
* Set the Bluetooth machine state to a specific state.
|
||||
*
|
||||
* @note after setting a new state the next step is automatically triggered.
|
||||
*
|
||||
* @param btMachineState the machine state that should be set.
|
||||
*/
|
||||
protected void setBtMachineState(BT_MACHINE_STATE btMachineState) {
|
||||
this.btMachineState = btMachineState;
|
||||
|
||||
nextMachineStateStep();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a byte array to a Bluetooth device.
|
||||
*
|
||||
* @param service the Bluetooth UUID device service
|
||||
* @param characteristic the Bluetooth UUID characteristic
|
||||
* @param bytes the bytes that should be write
|
||||
*/
|
||||
protected void writeBytes(UUID service, UUID characteristic, byte[] bytes) {
|
||||
BluetoothGattCharacteristic gattCharacteristic = bluetoothGatt.getService(service)
|
||||
.getCharacteristic(characteristic);
|
||||
|
||||
gattCharacteristic.setValue(bytes);
|
||||
bluetoothGatt.writeCharacteristic(gattCharacteristic);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read bytes from a Bluetooth device.
|
||||
*
|
||||
* @note onBluetoothDataRead() will be triggered if read command was successful.
|
||||
*
|
||||
* @param service the Bluetooth UUID device service
|
||||
* @param characteristic the Bluetooth UUID characteristic
|
||||
*/
|
||||
protected void readBytes(UUID service, UUID characteristic) {
|
||||
BluetoothGattCharacteristic gattCharacteristic = bluetoothGatt.getService(service)
|
||||
.getCharacteristic(characteristic);
|
||||
|
||||
bluetoothGatt.readCharacteristic(gattCharacteristic);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set indication flag on for the Bluetooth device.
|
||||
*
|
||||
* @param service the Bluetooth UUID device service
|
||||
* @param characteristic the Bluetooth UUID characteristic
|
||||
*/
|
||||
protected void setInidicationOn(UUID service, UUID characteristic) {
|
||||
BluetoothGattCharacteristic gattCharacteristic = bluetoothGatt.getService(service)
|
||||
.getCharacteristic(characteristic);
|
||||
|
||||
bluetoothGatt.setCharacteristicNotification(gattCharacteristic, true);
|
||||
|
||||
BluetoothGattDescriptor gattDescriptor = gattCharacteristic.getDescriptor(WEIGHT_MEASUREMENT_CONFIG);
|
||||
gattDescriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
|
||||
|
||||
bluetoothGatt.writeDescriptor(gattDescriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set notification flag on for the Bluetooth device.
|
||||
*
|
||||
* @param service the Bluetooth UUID device service
|
||||
* @param characteristic the Bluetooth UUID characteristic
|
||||
*/
|
||||
protected void setNotificationOn(UUID service, UUID characteristic) {
|
||||
BluetoothGattCharacteristic gattCharacteristic = bluetoothGatt.getService(service)
|
||||
.getCharacteristic(characteristic);
|
||||
|
||||
bluetoothGatt.setCharacteristicNotification(gattCharacteristic, true);
|
||||
|
||||
BluetoothGattDescriptor gattDescriptor = gattCharacteristic.getDescriptor(WEIGHT_MEASUREMENT_CONFIG);
|
||||
gattDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
|
||||
bluetoothGatt.writeDescriptor(gattDescriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set notification flag off for the Bluetooth device.
|
||||
*
|
||||
* @param service the Bluetooth UUID device service
|
||||
* @param characteristic the Bluetooth UUID characteristic
|
||||
*/
|
||||
protected void setNotificationOff(UUID service, UUID characteristic) {
|
||||
BluetoothGattCharacteristic gattCharacteristic = bluetoothGatt.getService(service)
|
||||
.getCharacteristic(characteristic);
|
||||
|
||||
bluetoothGatt.setCharacteristicNotification(gattCharacteristic, false);
|
||||
|
||||
BluetoothGattDescriptor gattDescriptor = gattCharacteristic.getDescriptor(WEIGHT_MEASUREMENT_CONFIG);
|
||||
gattDescriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
|
||||
bluetoothGatt.writeDescriptor(gattDescriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a byte array to hex for debugging purpose
|
||||
*
|
||||
* @param data data we want to make human-readable (hex)
|
||||
* @return a human-readable string representing the content of 'data'
|
||||
*/
|
||||
protected String byteInHex(byte[] data) {
|
||||
if (data == null) {
|
||||
Log.e("BluetoothCommunication", "Data is null");
|
||||
return new String();
|
||||
}
|
||||
|
||||
final StringBuilder stringBuilder = new StringBuilder(data.length);
|
||||
for(byte byteChar : data) {
|
||||
stringBuilder.append(String.format("%02X ", byteChar));
|
||||
}
|
||||
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test in a byte if a bit is set (1) or not (0)
|
||||
*
|
||||
* @param value byte which is tested
|
||||
* @param bit bit position which is tested
|
||||
* @return true if bit is set (1) ohterwise false (0)
|
||||
*/
|
||||
protected boolean isBitSet(byte value, int bit) {
|
||||
return (value & (1 << bit)) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start searching for a Bluetooth device.
|
||||
*
|
||||
* @note the hardware address is checked. Bluetooth device address has to be start with one of hwAddresses().
|
||||
*
|
||||
* On successfully connection Bluetooth machine state is automatically triggered.
|
||||
* If no device was found the search process is automatically stopped.
|
||||
*
|
||||
* @param deviceName the Bluetooth device name that is compared to the found devices.
|
||||
*/
|
||||
public void startSearching(String deviceName) {
|
||||
btDeviceName = deviceName;
|
||||
|
||||
if (scanCallback == null)
|
||||
{
|
||||
scanCallback = new BluetoothAdapter.LeScanCallback()
|
||||
{
|
||||
@Override
|
||||
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
|
||||
for (int i=0; i<hwAddresses().size(); i++) {
|
||||
if (device.getAddress().replace(":", "").toUpperCase().startsWith(hwAddresses().get(i)))
|
||||
{
|
||||
if (isDeviceNameCheck()) {
|
||||
if (!device.getName().toLowerCase().equals(btDeviceName.toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Log.d("BluetoothMiScale", "Mi Scale found trying to connect...");
|
||||
|
||||
bluetoothGatt = device.connectGatt(context, false, gattCallback);
|
||||
|
||||
searchHandler.removeCallbacksAndMessages(null);
|
||||
btAdapter.stopLeScan(scanCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
searchHandler.postDelayed(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
btAdapter.stopLeScan(scanCallback);
|
||||
setBtStatus(BT_STATUS_CODE.BT_NO_DEVICE_FOUND);
|
||||
}
|
||||
}, 10000);
|
||||
|
||||
btAdapter.startLeScan(scanCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop searching for a Bluetooth device
|
||||
*/
|
||||
public void stopSearching() {
|
||||
if (bluetoothGatt != null)
|
||||
{
|
||||
bluetoothGatt.close();
|
||||
bluetoothGatt = null;
|
||||
}
|
||||
|
||||
searchHandler.removeCallbacksAndMessages(null);
|
||||
btAdapter.stopLeScan(scanCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke next step for internal Bluetooth state machine.
|
||||
*/
|
||||
protected void nextMachineStateStep() {
|
||||
switch (btMachineState) {
|
||||
case BT_INIT_STATE:
|
||||
Log.d("BluetoothCommunication", "INIT STATE: " + initStepNr);
|
||||
if (!nextInitCmd(initStepNr)) {
|
||||
btMachineState = BT_MACHINE_STATE.BT_CMD_STATE;
|
||||
nextMachineStateStep();
|
||||
}
|
||||
initStepNr++;
|
||||
break;
|
||||
case BT_CMD_STATE:
|
||||
Log.d("BluetoothCommunication", "CMD STATE: " + cmdStepNr);
|
||||
nextBluetoothCmd(cmdStepNr);
|
||||
cmdStepNr++;
|
||||
break;
|
||||
case BT_CLEANUP_STATE:
|
||||
Log.d("BluetoothCommunication", "CLEANUP STATE: " + cleanupStepNr);
|
||||
nextCleanUpCmd(cleanupStepNr);
|
||||
cleanupStepNr++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom Gatt callback class to set up a Bluetooth state machine.
|
||||
*/
|
||||
protected class GattCallback extends BluetoothGattCallback {
|
||||
@Override
|
||||
public void onConnectionStateChange(final BluetoothGatt gatt, int status, int newState) {
|
||||
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
||||
setBtStatus(BT_STATUS_CODE.BT_CONNECTION_ESTABLISHED);
|
||||
gatt.discoverServices();
|
||||
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
||||
setBtStatus(BT_STATUS_CODE.BT_CONNECTION_LOST);
|
||||
stopSearching();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServicesDiscovered(final BluetoothGatt gatt, int status) {
|
||||
cmdStepNr = 0;
|
||||
initStepNr = 0;
|
||||
cleanupStepNr = 0;
|
||||
|
||||
btMachineState = BT_MACHINE_STATE.BT_INIT_STATE;
|
||||
nextMachineStateStep();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDescriptorWrite(BluetoothGatt gatt,
|
||||
BluetoothGattDescriptor descriptor,
|
||||
int status) {
|
||||
nextMachineStateStep();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicWrite (BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic,
|
||||
int status) {
|
||||
nextMachineStateStep();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicRead (BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic,
|
||||
int status) {
|
||||
onBluetoothDataRead(gatt, characteristic, status);
|
||||
|
||||
nextMachineStateStep();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicChanged(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic) {
|
||||
onBluetoothDataChange(gatt, characteristic);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -27,6 +27,7 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -57,6 +58,26 @@ public class BluetoothCustomOpenScale extends BluetoothCommunication {
|
||||
return "openScale_MCU";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<String> hwAddresses() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean nextInitCmd(int stateNr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean nextBluetoothCmd(int stateNr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean nextCleanUpCmd(int stateNr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isBLE() {
|
||||
return false;
|
||||
}
|
||||
|
@@ -15,30 +15,17 @@
|
||||
*/
|
||||
package com.health.openscale.core.bluetooth;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCallback;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattDescriptor;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import com.health.openscale.core.datatypes.ScaleData;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_STATUS_CODE.BT_CONNECTION_ESTABLISHED;
|
||||
import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_STATUS_CODE.BT_CONNECTION_LOST;
|
||||
import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_STATUS_CODE.BT_NO_DEVICE_FOUND;
|
||||
|
||||
public class BluetoothMedisanaBS444 extends BluetoothCommunication {
|
||||
private BluetoothGatt bluetoothGatt;
|
||||
private BluetoothAdapter.LeScanCallback scanCallback;
|
||||
|
||||
private final UUID WEIGHT_MEASUREMENT_SERVICE = UUID.fromString("000078b2-0000-1000-8000-00805f9b34fb");
|
||||
private final UUID WEIGHT_MEASUREMENT_CHARACTERISTIC = UUID.fromString("00008a21-0000-1000-8000-00805f9b34fb"); // indication, read-only
|
||||
private final UUID FEATURE_MEASUREMENT_CHARACTERISTIC = UUID.fromString("00008a22-0000-1000-8000-00805f9b34fb"); // indication, read-only
|
||||
@@ -47,16 +34,11 @@ public class BluetoothMedisanaBS444 extends BluetoothCommunication {
|
||||
private final UUID CUSTOM5_MEASUREMENT_CHARACTERISTIC = UUID.fromString("00008a82-0000-1000-8000-00805f9b34fb"); // indication, read-only
|
||||
private final UUID WEIGHT_MEASUREMENT_CONFIG = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
|
||||
|
||||
private String btDeviceName;
|
||||
private Handler searchHandler;
|
||||
private ScaleData btScaleData;
|
||||
private int nextCmdState;
|
||||
|
||||
public BluetoothMedisanaBS444(Context context) {
|
||||
super(context);
|
||||
searchHandler = new Handler();
|
||||
btScaleData = new ScaleData();
|
||||
scanCallback = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -69,163 +51,76 @@ public class BluetoothMedisanaBS444 extends BluetoothCommunication {
|
||||
return "Medisana BS444";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeviceNameCheck() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<String> hwAddresses() {
|
||||
ArrayList hwAddresses = new ArrayList();
|
||||
hwAddresses.add("E454EB");
|
||||
|
||||
return hwAddresses;
|
||||
}
|
||||
|
||||
public boolean initSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startSearching(String deviceName) {
|
||||
btDeviceName = deviceName;
|
||||
|
||||
if (scanCallback == null) {
|
||||
scanCallback = new BluetoothAdapter.LeScanCallback() {
|
||||
@Override
|
||||
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
|
||||
if (device.getAddress().replace(":", "").toUpperCase().startsWith("E454EB")) {
|
||||
//if (device.getName().equals(btDeviceName)) {
|
||||
bluetoothGatt = device.connectGatt(context, false, gattCallback);
|
||||
|
||||
searchHandler.removeCallbacksAndMessages(null);
|
||||
btAdapter.stopLeScan(scanCallback);
|
||||
//}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
searchHandler.postDelayed(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
btAdapter.stopLeScan(scanCallback);
|
||||
setBtStatus(BT_NO_DEVICE_FOUND);
|
||||
}
|
||||
}, 10000);
|
||||
|
||||
btAdapter.startLeScan(scanCallback);
|
||||
boolean nextInitCmd(int stateNr){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopSearching() {
|
||||
if (bluetoothGatt != null)
|
||||
{
|
||||
bluetoothGatt.close();
|
||||
bluetoothGatt = null;
|
||||
boolean nextBluetoothCmd(int stateNr){
|
||||
switch (stateNr) {
|
||||
case 0:
|
||||
// set indication on for feature characteristic
|
||||
setInidicationOn(WEIGHT_MEASUREMENT_SERVICE, FEATURE_MEASUREMENT_CHARACTERISTIC);
|
||||
break;
|
||||
case 1:
|
||||
// set indication on for weight measurement
|
||||
setInidicationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_CHARACTERISTIC);
|
||||
break;
|
||||
case 2:
|
||||
// set indication on for custom5 measurement
|
||||
setInidicationOn(WEIGHT_MEASUREMENT_SERVICE, CUSTOM5_MEASUREMENT_CHARACTERISTIC);
|
||||
break;
|
||||
case 3:
|
||||
// send magic number to receive weight data
|
||||
byte[] magicBytes = new byte[]{(byte)0x02, (byte)0x7B, (byte)0x7B, (byte)0xF6, (byte)0x0D}; // 02:7b:7b:f6:0d
|
||||
|
||||
writeBytes(WEIGHT_MEASUREMENT_SERVICE, CMD_MEASUREMENT_CHARACTERISTIC, magicBytes);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
searchHandler.removeCallbacksAndMessages(null);
|
||||
btAdapter.stopLeScan(scanCallback);
|
||||
return true;
|
||||
}
|
||||
|
||||
private BluetoothGattCallback gattCallback= new BluetoothGattCallback() {
|
||||
@Override
|
||||
public void onConnectionStateChange(final BluetoothGatt gatt, int status, int newState) {
|
||||
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
||||
setBtStatus(BT_CONNECTION_ESTABLISHED);
|
||||
gatt.discoverServices();
|
||||
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
||||
setBtStatus(BT_CONNECTION_LOST);
|
||||
stopSearching();
|
||||
}
|
||||
@Override
|
||||
boolean nextCleanUpCmd(int stateNr){
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onBluetoothDataChange(BluetoothGatt bluetoothGatt, BluetoothGattCharacteristic gattCharacteristic){
|
||||
final byte[] data = gattCharacteristic.getValue();
|
||||
|
||||
if (gattCharacteristic.getUuid().equals(WEIGHT_MEASUREMENT_CHARACTERISTIC)) {
|
||||
parseWeightData(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServicesDiscovered(final BluetoothGatt gatt, int status) {
|
||||
nextCmdState = 0;
|
||||
if (gattCharacteristic.getUuid().equals(FEATURE_MEASUREMENT_CHARACTERISTIC)) {
|
||||
parseFeatureData(data);
|
||||
|
||||
invokeNextBluetoothCmd(gatt);
|
||||
addScaleData(btScaleData);
|
||||
}
|
||||
|
||||
private void invokeNextBluetoothCmd(BluetoothGatt gatt) {
|
||||
BluetoothGattCharacteristic characteristic;
|
||||
BluetoothGattDescriptor descriptor;
|
||||
|
||||
switch (nextCmdState) {
|
||||
case 0:
|
||||
// set indication on for feature characteristic
|
||||
characteristic = gatt.getService(WEIGHT_MEASUREMENT_SERVICE)
|
||||
.getCharacteristic(FEATURE_MEASUREMENT_CHARACTERISTIC);
|
||||
|
||||
gatt.setCharacteristicNotification(characteristic, true);
|
||||
|
||||
descriptor = characteristic.getDescriptor(WEIGHT_MEASUREMENT_CONFIG);
|
||||
descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
|
||||
|
||||
gatt.writeDescriptor(descriptor);
|
||||
break;
|
||||
case 1:
|
||||
// set indication 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_INDICATION_VALUE);
|
||||
gatt.writeDescriptor(descriptor);
|
||||
break;
|
||||
case 2:
|
||||
// set indication on for custom5 measurement
|
||||
characteristic = gatt.getService(WEIGHT_MEASUREMENT_SERVICE)
|
||||
.getCharacteristic(CUSTOM5_MEASUREMENT_CHARACTERISTIC);
|
||||
|
||||
gatt.setCharacteristicNotification(characteristic, true);
|
||||
|
||||
descriptor = characteristic.getDescriptor(WEIGHT_MEASUREMENT_CONFIG);
|
||||
descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
|
||||
gatt.writeDescriptor(descriptor);
|
||||
break;
|
||||
case 3:
|
||||
// send magic number to receive weight data
|
||||
characteristic = gatt.getService(WEIGHT_MEASUREMENT_SERVICE)
|
||||
.getCharacteristic(CMD_MEASUREMENT_CHARACTERISTIC);
|
||||
|
||||
characteristic.setValue(new byte[]{(byte)0x02, (byte)0x7B, (byte)0x7B, (byte)0xF6, (byte)0x0D}); // 02:7b:7b:f6:0d
|
||||
gatt.writeCharacteristic(characteristic);
|
||||
break;
|
||||
case 4:
|
||||
// end of command mode
|
||||
break;
|
||||
default:
|
||||
Log.e("BluetoothMedisanaScale", "Error invalid Bluetooth State");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDescriptorWrite(BluetoothGatt gatt,
|
||||
BluetoothGattDescriptor descriptor,
|
||||
int status) {
|
||||
nextCmdState++;
|
||||
invokeNextBluetoothCmd(gatt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicWrite (BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic,
|
||||
int status) {
|
||||
nextCmdState++;
|
||||
invokeNextBluetoothCmd(gatt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicChanged(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic) {
|
||||
final byte[] data = characteristic.getValue();
|
||||
|
||||
if (characteristic.getUuid().equals(WEIGHT_MEASUREMENT_CHARACTERISTIC)) {
|
||||
parseWeightData(data);
|
||||
}
|
||||
|
||||
if (characteristic.getUuid().equals(FEATURE_MEASUREMENT_CHARACTERISTIC)) {
|
||||
parseFeatureData(data);
|
||||
|
||||
addScaleData(btScaleData);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
private void parseWeightData(byte[] weightData) {
|
||||
float weight = (float)(((weightData[2] & 0xFF) << 8) | (weightData[1] & 0xFF)) / 100.0f;
|
||||
@@ -250,18 +145,4 @@ public class BluetoothMedisanaBS444 extends BluetoothCommunication {
|
||||
private float decodeFeature(byte highByte, byte lowByte) {
|
||||
return (float)(((lowByte& 0x0F) << 8) | (highByte & 0xFF)) / 10.0f;
|
||||
}
|
||||
|
||||
private void printByteInHex(byte[] data) {
|
||||
if (data == null) {
|
||||
Log.e("BluetoothMedisanaScale", "Data is null");
|
||||
return;
|
||||
}
|
||||
|
||||
final StringBuilder stringBuilder = new StringBuilder(data.length);
|
||||
for(byte byteChar : data) {
|
||||
stringBuilder.append(String.format("%02X ", byteChar));
|
||||
}
|
||||
|
||||
Log.d("BluetoothMedisanaScale", "Raw hex data: " + stringBuilder);
|
||||
}
|
||||
}
|
||||
|
@@ -16,16 +16,10 @@
|
||||
|
||||
package com.health.openscale.core.bluetooth;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCallback;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattDescriptor;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -33,42 +27,24 @@ import com.health.openscale.core.datatypes.ScaleData;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_STATUS_CODE.BT_CONNECTION_ESTABLISHED;
|
||||
import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_STATUS_CODE.BT_CONNECTION_LOST;
|
||||
import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_STATUS_CODE.BT_INIT_PROCESS;
|
||||
import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_STATUS_CODE.BT_NO_DEVICE_FOUND;
|
||||
import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_STATUS_CODE.BT_UNEXPECTED_ERROR;
|
||||
|
||||
public class BluetoothMiScale extends BluetoothCommunication {
|
||||
|
||||
private BluetoothGatt bluetoothGatt;
|
||||
private BluetoothAdapter.LeScanCallback scanCallback;
|
||||
|
||||
private final UUID WEIGHT_MEASUREMENT_SERVICE = UUID.fromString("0000181d-0000-1000-8000-00805f9b34fb");
|
||||
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 String btDeviceName;
|
||||
private int nextCmdState;
|
||||
private int nextInitState;
|
||||
private int nextClearUpState;
|
||||
private boolean initProcessOn;
|
||||
private boolean clearUpProcessOn;
|
||||
|
||||
public BluetoothMiScale(Context context) {
|
||||
super(context);
|
||||
|
||||
searchHandler = new Handler();
|
||||
scanCallback = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -82,57 +58,154 @@ public class BluetoothMiScale extends BluetoothCommunication {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startSearching(String deviceName) {
|
||||
btDeviceName = deviceName;
|
||||
public ArrayList<String> hwAddresses() {
|
||||
ArrayList hwAddresses = new ArrayList();
|
||||
hwAddresses.add("880F10");
|
||||
hwAddresses.add("C80F10");
|
||||
|
||||
if (scanCallback == null)
|
||||
{
|
||||
scanCallback = new BluetoothAdapter.LeScanCallback()
|
||||
{
|
||||
@Override
|
||||
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
|
||||
if (device.getAddress().replace(":", "").toUpperCase().startsWith("880F10") ||
|
||||
device.getAddress().replace(":", "").toUpperCase().startsWith("C80F10") ) // Xiaomi
|
||||
{
|
||||
if (device.getName().equals(btDeviceName)) {
|
||||
Log.d("BluetoothMiScale", "Mi Scale found trying to connect...");
|
||||
|
||||
bluetoothGatt = device.connectGatt(context, false, gattCallback);
|
||||
|
||||
searchHandler.removeCallbacksAndMessages(null);
|
||||
btAdapter.stopLeScan(scanCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
searchHandler.postDelayed(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
btAdapter.stopLeScan(scanCallback);
|
||||
setBtStatus(BT_NO_DEVICE_FOUND);
|
||||
}
|
||||
}, 10000);
|
||||
|
||||
btAdapter.startLeScan(scanCallback);
|
||||
return hwAddresses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopSearching() {
|
||||
if (bluetoothGatt != null)
|
||||
{
|
||||
bluetoothGatt.close();
|
||||
bluetoothGatt = null;
|
||||
}
|
||||
public void onBluetoothDataRead(BluetoothGatt bluetoothGatt, BluetoothGattCharacteristic gattCharacteristic, int status) {
|
||||
byte[] data = gattCharacteristic.getValue();
|
||||
|
||||
searchHandler.removeCallbacksAndMessages(null);
|
||||
btAdapter.stopLeScan(scanCallback);
|
||||
int currentYear = Calendar.getInstance().get(Calendar.YEAR);
|
||||
int currentMonth = Calendar.getInstance().get(Calendar.MONTH)+1;
|
||||
int currentDay = Calendar.getInstance().get(Calendar.DAY_OF_MONTH);
|
||||
int scaleYear = ((data[1] & 0xFF) << 8) | (data[0] & 0xFF);
|
||||
int scaleMonth = (int) data[2];
|
||||
int scaleDay = (int) data[3];
|
||||
|
||||
if (currentYear == scaleYear && currentMonth == scaleMonth && currentDay == scaleDay) {
|
||||
setBtMachineState(BT_MACHINE_STATE.BT_CMD_STATE);
|
||||
} else {
|
||||
Log.d("BluetoothMiScale", "Current year and scale year is different");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBluetoothDataChange(BluetoothGatt bluetoothGatt, BluetoothGattCharacteristic gattCharacteristic) {
|
||||
final byte[] data = gattCharacteristic.getValue();
|
||||
|
||||
if (data != null && data.length > 0) {
|
||||
|
||||
// Stop command from mi scale received
|
||||
if (data[0] == 0x03) {
|
||||
setBtMachineState(BT_MACHINE_STATE.BT_CLEANUP_STATE);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
boolean nextInitCmd(int stateNr) {
|
||||
switch (stateNr) {
|
||||
case 0:
|
||||
// read device time
|
||||
readBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_TIME_CHARACTERISTIC);
|
||||
break;
|
||||
case 1:
|
||||
// set current time
|
||||
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};
|
||||
|
||||
writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_TIME_CHARACTERISTIC, dateTimeByte);
|
||||
break;
|
||||
case 2:
|
||||
// set notification on for weight measurement history
|
||||
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC);
|
||||
break;
|
||||
case 3:
|
||||
// Set on history weight measurement
|
||||
byte[] magicBytes = new byte[]{(byte)0x01, (byte)0x96, (byte)0x8a, (byte)0xbd, (byte)0x62};
|
||||
|
||||
writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, magicBytes);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean nextBluetoothCmd(int stateNr) {
|
||||
switch (stateNr) {
|
||||
case 0:
|
||||
// set notification on for weight measurement
|
||||
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_CHARACTERISTIC);
|
||||
break;
|
||||
case 1:
|
||||
// set notification on for weight measurement history
|
||||
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC);
|
||||
break;
|
||||
case 2:
|
||||
// configure scale to get only last measurements
|
||||
int uniqueNumber = getUniqueNumber();
|
||||
|
||||
byte[] userIdentifier = new byte[]{(byte)0x01, (byte)0xFF, (byte)0xFF, (byte) ((uniqueNumber & 0xFF00) >> 8), (byte) ((uniqueNumber & 0xFF) >> 0)};
|
||||
writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, userIdentifier);
|
||||
break;
|
||||
case 3:
|
||||
// set notification off for weight measurement history
|
||||
setNotificationOff(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC);
|
||||
break;
|
||||
case 4:
|
||||
// set notification on for weight measurement history
|
||||
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC);
|
||||
break;
|
||||
case 5:
|
||||
// invoke receiving history data
|
||||
writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, new byte[]{0x02});
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean nextCleanUpCmd(int stateNr) {
|
||||
|
||||
switch (stateNr) {
|
||||
case 0:
|
||||
// send stop command to mi scale
|
||||
writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, new byte[]{0x03});
|
||||
break;
|
||||
case 1:
|
||||
// acknowledge that you received the last history data
|
||||
int uniqueNumber = getUniqueNumber();
|
||||
|
||||
byte[] userIdentifier = new byte[]{(byte)0x04, (byte)0xFF, (byte)0xFF, (byte) ((uniqueNumber & 0xFF00) >> 8), (byte) ((uniqueNumber & 0xFF) >> 0)};
|
||||
writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, userIdentifier);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void parseBytes(byte[] weightBytes) {
|
||||
try {
|
||||
@@ -205,10 +278,6 @@ public class BluetoothMiScale extends BluetoothCommunication {
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isBitSet(byte value, int bit) {
|
||||
return (value & (1 << bit)) != 0;
|
||||
}
|
||||
|
||||
private int getUniqueNumber() {
|
||||
int uniqueNumber;
|
||||
|
||||
@@ -227,299 +296,4 @@ public class BluetoothMiScale extends BluetoothCommunication {
|
||||
|
||||
return uniqueNumber + userId;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private BluetoothGattCallback gattCallback= new BluetoothGattCallback() {
|
||||
@Override
|
||||
public void onConnectionStateChange(final BluetoothGatt gatt, int status, int newState) {
|
||||
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
||||
Log.d("BluetoothMiScale", "Connection established");
|
||||
setBtStatus(BT_CONNECTION_ESTABLISHED);
|
||||
gatt.discoverServices();
|
||||
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
||||
Log.d("BluetoothMiScale", "Connection lost");
|
||||
setBtStatus(BT_CONNECTION_LOST);
|
||||
stopSearching();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServicesDiscovered(final BluetoothGatt gatt, int status) {
|
||||
nextCmdState = 0;
|
||||
nextInitState = 0;
|
||||
nextClearUpState = 0;
|
||||
|
||||
initProcessOn = false;
|
||||
clearUpProcessOn = false;
|
||||
|
||||
invokeNextBluetoothCmd(gatt);
|
||||
}
|
||||
|
||||
private void invokeNextBluetoothCmd(BluetoothGatt gatt) {
|
||||
BluetoothGattCharacteristic characteristic;
|
||||
BluetoothGattDescriptor descriptor;
|
||||
|
||||
switch (nextCmdState) {
|
||||
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:
|
||||
// configure scale to get only last measurements
|
||||
characteristic = gatt.getService(WEIGHT_MEASUREMENT_SERVICE)
|
||||
.getCharacteristic(WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC);
|
||||
|
||||
int uniqueNumber = getUniqueNumber();
|
||||
|
||||
characteristic.setValue(new byte[]{(byte)0x01, (byte)0xFF, (byte)0xFF, (byte) ((uniqueNumber & 0xFF00) >> 8), (byte) ((uniqueNumber & 0xFF) >> 0)});
|
||||
gatt.writeCharacteristic(characteristic);
|
||||
break;
|
||||
case 4:
|
||||
// set notification off for weight measurement history
|
||||
characteristic = gatt.getService(WEIGHT_MEASUREMENT_SERVICE)
|
||||
.getCharacteristic(WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC);
|
||||
|
||||
gatt.setCharacteristicNotification(characteristic, false);
|
||||
|
||||
descriptor = characteristic.getDescriptor(WEIGHT_MEASUREMENT_CONFIG);
|
||||
descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
|
||||
gatt.writeDescriptor(descriptor);
|
||||
break;
|
||||
case 5:
|
||||
// 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 6:
|
||||
// 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 7:
|
||||
// end of command mode
|
||||
break;
|
||||
default:
|
||||
Log.e("BluetoothMiScale", "Error invalid Bluetooth State");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void invokeClearUpBluetooth(BluetoothGatt gatt) {
|
||||
BluetoothGattCharacteristic characteristic;
|
||||
|
||||
clearUpProcessOn = true;
|
||||
|
||||
switch (nextClearUpState) {
|
||||
case 0:
|
||||
// send stop command to mi scale
|
||||
characteristic = gatt.getService(WEIGHT_MEASUREMENT_SERVICE)
|
||||
.getCharacteristic(WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC);
|
||||
|
||||
characteristic.setValue(new byte[]{0x03});
|
||||
gatt.writeCharacteristic(characteristic);
|
||||
break;
|
||||
case 1:
|
||||
// acknowledge that you received the last history data
|
||||
characteristic = gatt.getService(WEIGHT_MEASUREMENT_SERVICE)
|
||||
.getCharacteristic(WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC);
|
||||
|
||||
int uniqueNumber = getUniqueNumber();
|
||||
|
||||
characteristic.setValue(new byte[]{(byte)0x04, (byte)0xFF, (byte)0xFF, (byte) ((uniqueNumber & 0xFF00) >> 8), (byte) ((uniqueNumber & 0xFF) >> 0)});
|
||||
gatt.writeCharacteristic(characteristic);
|
||||
break;
|
||||
case 2:
|
||||
// end of clear up process
|
||||
break;
|
||||
default:
|
||||
Log.e("BluetoothMiScale", "Error invalid Bluetooth State");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void invokeInitBluetoothCmd(BluetoothGatt gatt) {
|
||||
BluetoothGattCharacteristic characteristic;
|
||||
BluetoothGattDescriptor descriptor;
|
||||
|
||||
initProcessOn = true;
|
||||
|
||||
switch (nextInitState) {
|
||||
case 0:
|
||||
setBtStatus(BT_INIT_PROCESS);
|
||||
|
||||
// set current time
|
||||
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};
|
||||
|
||||
characteristic.setValue(dateTimeByte);
|
||||
gatt.writeCharacteristic(characteristic);
|
||||
break;
|
||||
case 1:
|
||||
// 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 2:
|
||||
// Set on history weight measurement
|
||||
characteristic = gatt.getService(WEIGHT_MEASUREMENT_SERVICE)
|
||||
.getCharacteristic(WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC);
|
||||
|
||||
characteristic.setValue(new byte[]{(byte)0x01, (byte)0x96, (byte)0x8a, (byte)0xbd, (byte)0x62});
|
||||
gatt.writeCharacteristic(characteristic);
|
||||
break;
|
||||
case 3:
|
||||
initProcessOn = false;
|
||||
|
||||
stopSearching();
|
||||
startSearching(btDeviceName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDescriptorWrite(BluetoothGatt gatt,
|
||||
BluetoothGattDescriptor descriptor,
|
||||
int status) {
|
||||
if (initProcessOn) {
|
||||
nextInitState++;
|
||||
invokeInitBluetoothCmd(gatt);
|
||||
} else if (clearUpProcessOn) {
|
||||
nextClearUpState++;
|
||||
invokeClearUpBluetooth(gatt);
|
||||
}
|
||||
else {
|
||||
nextCmdState++;
|
||||
invokeNextBluetoothCmd(gatt);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicWrite (BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic,
|
||||
int status) {
|
||||
if (initProcessOn) {
|
||||
nextInitState++;
|
||||
invokeInitBluetoothCmd(gatt);
|
||||
} else if (clearUpProcessOn) {
|
||||
nextClearUpState++;
|
||||
invokeClearUpBluetooth(gatt);
|
||||
}else {
|
||||
nextCmdState++;
|
||||
invokeNextBluetoothCmd(gatt);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicRead (BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic,
|
||||
int status) {
|
||||
byte[] data = characteristic.getValue();
|
||||
|
||||
int currentYear = Calendar.getInstance().get(Calendar.YEAR);
|
||||
int currentMonth = Calendar.getInstance().get(Calendar.MONTH)+1;
|
||||
int currentDay = Calendar.getInstance().get(Calendar.DAY_OF_MONTH);
|
||||
int scaleYear = ((data[1] & 0xFF) << 8) | (data[0] & 0xFF);
|
||||
int scaleMonth = (int) data[2];
|
||||
int scaleDay = (int) data[3];
|
||||
|
||||
if (currentYear != scaleYear || currentMonth != scaleMonth || currentDay != scaleDay) {
|
||||
Log.d("BluetoothMiScale", "Current year and scale year is different");
|
||||
invokeInitBluetoothCmd(gatt);
|
||||
} else {
|
||||
nextCmdState++;
|
||||
invokeNextBluetoothCmd(gatt);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicChanged(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic) {
|
||||
final byte[] data = characteristic.getValue();
|
||||
|
||||
if (data != null && data.length > 0) {
|
||||
|
||||
// Stop command from mi scale received
|
||||
if (data[0] == 0x03) {
|
||||
invokeClearUpBluetooth(gatt);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@@ -16,26 +16,23 @@
|
||||
*/
|
||||
package com.health.openscale.core.bluetooth;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCallback;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattDescriptor;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import com.health.openscale.core.datatypes.ScaleData;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_STATUS_CODE.BT_CONNECTION_ESTABLISHED;
|
||||
import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_STATUS_CODE.BT_CONNECTION_LOST;
|
||||
import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_STATUS_CODE.BT_NO_DEVICE_FOUND;
|
||||
import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_STATUS_CODE.BT_UNEXPECTED_ERROR;
|
||||
|
||||
public class BluetoothSanitasSbf70 extends BluetoothCommunication {
|
||||
@@ -99,17 +96,9 @@ public class BluetoothSanitasSbf70 extends BluetoothCommunication {
|
||||
private static final UUID CUSTOM_CHARACTERISTIC_IMG_BLOCK = // write-only, notify
|
||||
UUID.fromString("F000FFC2-0451-4000-8000-000000000000");
|
||||
|
||||
private BluetoothAdapter.LeScanCallback scanCallback = null;
|
||||
// default name is usually "SANITAS SBF70"
|
||||
private String btDeviceName = null;
|
||||
|
||||
private Handler searchHandler = null;
|
||||
private BluetoothGattCallback gattCallback = null;
|
||||
private BluetoothGatt bluetoothGatt;
|
||||
|
||||
public BluetoothSanitasSbf70(Context context) {
|
||||
super(context);
|
||||
searchHandler = new Handler();
|
||||
gattCallback = new BluetoothSanitasGattCallback();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -122,26 +111,32 @@ public class BluetoothSanitasSbf70 extends BluetoothCommunication {
|
||||
return "SANITAS SBF70";
|
||||
}
|
||||
|
||||
public boolean initSupported() {
|
||||
@Override
|
||||
public ArrayList<String> hwAddresses() {
|
||||
ArrayList hwAddresses = new ArrayList();
|
||||
hwAddresses.add("C4BE84");
|
||||
hwAddresses.add("209148");
|
||||
|
||||
return hwAddresses;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean nextInitCmd(int stateNr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static final String HEX_DIGITS = "0123456789ABCDEF";
|
||||
@Override
|
||||
boolean nextBluetoothCmd(int stateNr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief for debugging purpose
|
||||
* @param data data we want to make human-readable (hex)
|
||||
* @return a human-readable string representing the content of 'data'
|
||||
*/
|
||||
public static String toHex(byte[] data) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
for (int i = 0; i != data.length; i++) {
|
||||
int v = data[i] & 0xff;
|
||||
buf.append(HEX_DIGITS.charAt(v >> 4));
|
||||
buf.append(HEX_DIGITS.charAt(v & 0xf));
|
||||
buf.append(" ");
|
||||
}
|
||||
return buf.toString();
|
||||
@Override
|
||||
boolean nextCleanUpCmd(int stateNr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean initSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private class BluetoothSanitasGattCallback extends BluetoothGattCallback {
|
||||
@@ -278,7 +273,7 @@ public class BluetoothSanitasSbf70 extends BluetoothCommunication {
|
||||
final UUID uuid = characteristic.getUuid();
|
||||
final byte[] data = characteristic.getValue();
|
||||
|
||||
Log.d(TAG, "onCharacteristicChanged(" + uuid + "): " + toHex(data));
|
||||
Log.d(TAG, "onCharacteristicChanged(" + uuid + "): " + byteInHex(data));
|
||||
|
||||
if (!uuid.equals(CUSTOM_CHARACTERISTIC_WEIGHT)) {
|
||||
Log.d(TAG, "Got characteristic changed from unexpected UUID ?");
|
||||
@@ -379,64 +374,4 @@ public class BluetoothSanitasSbf70 extends BluetoothCommunication {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startSearching(String deviceName) {
|
||||
btDeviceName = deviceName;
|
||||
|
||||
if (gattCallback == null) {
|
||||
gattCallback = new BluetoothSanitasGattCallback();
|
||||
}
|
||||
|
||||
if (scanCallback == null)
|
||||
{
|
||||
scanCallback = new BluetoothAdapter.LeScanCallback()
|
||||
{
|
||||
@Override
|
||||
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
|
||||
Log.d(TAG,
|
||||
"LeScan: device found: "
|
||||
+ device.getAddress() + " : " + device.getName()
|
||||
);
|
||||
// Texas Instrument (Sanitas)
|
||||
if (device.getAddress().replace(":", "").toUpperCase().startsWith("C4BE84") ||
|
||||
device.getAddress().replace(":", "").toUpperCase().startsWith("209148")) {
|
||||
|
||||
if (!device.getName().toLowerCase().equals(btDeviceName.toLowerCase()))
|
||||
return;
|
||||
Log.d(TAG, "Sanitas scale found. Connecting...");
|
||||
|
||||
bluetoothGatt = device.connectGatt(context, false, gattCallback);
|
||||
|
||||
searchHandler.removeCallbacksAndMessages(null);
|
||||
btAdapter.stopLeScan(scanCallback);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
searchHandler.postDelayed(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
btAdapter.stopLeScan(scanCallback);
|
||||
setBtStatus(BT_NO_DEVICE_FOUND);
|
||||
}
|
||||
}, 10000);
|
||||
|
||||
btAdapter.startLeScan(scanCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopSearching() {
|
||||
if (bluetoothGatt != null)
|
||||
{
|
||||
bluetoothGatt.close();
|
||||
bluetoothGatt = null;
|
||||
}
|
||||
|
||||
searchHandler.removeCallbacksAndMessages(null);
|
||||
btAdapter.stopLeScan(scanCallback);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user