mirror of
https://github.com/oliexdev/openScale.git
synced 2025-08-23 16:53:04 +02:00
Merge branch 'MartinNowack-sanitas_support'
This commit is contained in:
@@ -30,10 +30,14 @@ import android.util.Log;
|
|||||||
import com.health.openscale.core.datatypes.ScaleData;
|
import com.health.openscale.core.datatypes.ScaleData;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Queue;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public abstract class BluetoothCommunication {
|
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 };
|
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, BT_SCALE_MESSAGE
|
||||||
|
};
|
||||||
public enum BT_MACHINE_STATE {BT_INIT_STATE, BT_CMD_STATE, BT_CLEANUP_STATE}
|
public enum BT_MACHINE_STATE {BT_INIT_STATE, BT_CMD_STATE, BT_CLEANUP_STATE}
|
||||||
|
|
||||||
protected Context context;
|
protected Context context;
|
||||||
@@ -51,7 +55,9 @@ public abstract class BluetoothCommunication {
|
|||||||
private int cleanupStepNr;
|
private int cleanupStepNr;
|
||||||
private BT_MACHINE_STATE btMachineState;
|
private BT_MACHINE_STATE btMachineState;
|
||||||
|
|
||||||
private final UUID WEIGHT_MEASUREMENT_CONFIG = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
|
private Queue<BluetoothGattDescriptor> descriptorRequestQueue;
|
||||||
|
private Queue<BluetoothGattCharacteristic> characteristicRequestQueue;
|
||||||
|
private Boolean openRequest;
|
||||||
|
|
||||||
public BluetoothCommunication(Context context)
|
public BluetoothCommunication(Context context)
|
||||||
{
|
{
|
||||||
@@ -66,7 +72,7 @@ public abstract class BluetoothCommunication {
|
|||||||
* Create and return a new Bluetooth object.
|
* Create and return a new Bluetooth object.
|
||||||
*
|
*
|
||||||
* @param context In which context should the Bluetooth device created
|
* @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)
|
* @param i the specific number of which Bluetooth device should be created (correspond to "deviceTypes" key in BluetoothPreferences)
|
||||||
* @return created object specified by the number i otherwise null
|
* @return created object specified by the number i otherwise null
|
||||||
*/
|
*/
|
||||||
public static BluetoothCommunication getBtDevice(Context context, int i) {
|
public static BluetoothCommunication getBtDevice(Context context, int i) {
|
||||||
@@ -121,6 +127,16 @@ public abstract class BluetoothCommunication {
|
|||||||
callbackBtHandler.obtainMessage(BT_STATUS_CODE.BT_RETRIEVE_SCALE_DATA.ordinal(), scaleData).sendToTarget();
|
callbackBtHandler.obtainMessage(BT_STATUS_CODE.BT_RETRIEVE_SCALE_DATA.ordinal(), scaleData).sendToTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send message to openScale user
|
||||||
|
*
|
||||||
|
* @param msg the string id to be send
|
||||||
|
* @param value the value to be used
|
||||||
|
*/
|
||||||
|
protected void sendMessage(int msg, Object value) {
|
||||||
|
callbackBtHandler.obtainMessage(BT_STATUS_CODE.BT_SCALE_MESSAGE.ordinal(), msg, 0, value).sendToTarget();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is the Bluetooth initialized process supported.
|
* Is the Bluetooth initialized process supported.
|
||||||
*
|
*
|
||||||
@@ -183,7 +199,7 @@ public abstract class BluetoothCommunication {
|
|||||||
/**
|
/**
|
||||||
* Return all hardware addresses of the Bluetooth device.
|
* 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"
|
* The format should be the first six hex values of a known Bluetooth hardware address without any colon e.g. 12:AB:65:12:34:52 becomes "12AB65"
|
||||||
* @note add hw address "FFFFFF" to skip check
|
* @note add hw address "FFFFFF" to skip check
|
||||||
*
|
*
|
||||||
* @return a list of all hardware addresses that are known for this device.
|
* @return a list of all hardware addresses that are known for this device.
|
||||||
@@ -191,7 +207,7 @@ public abstract class BluetoothCommunication {
|
|||||||
abstract public ArrayList<String> hwAddresses();
|
abstract public ArrayList<String> hwAddresses();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* State machine for the initialization process for the Bluetooth device.
|
* State machine for the initialization process of the Bluetooth device.
|
||||||
*
|
*
|
||||||
* @param stateNr the current step number
|
* @param stateNr the current step number
|
||||||
* @return false if no next step is available otherwise true
|
* @return false if no next step is available otherwise true
|
||||||
@@ -199,7 +215,7 @@ public abstract class BluetoothCommunication {
|
|||||||
abstract boolean nextInitCmd(int stateNr);
|
abstract boolean nextInitCmd(int stateNr);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* State machine for the normal/command process for the Bluetooth device.
|
* State machine for the normal/command process of the Bluetooth device.
|
||||||
*
|
*
|
||||||
* This state machine is automatically triggered if initialization process is finished.
|
* This state machine is automatically triggered if initialization process is finished.
|
||||||
*
|
*
|
||||||
@@ -208,6 +224,25 @@ public abstract class BluetoothCommunication {
|
|||||||
*/
|
*/
|
||||||
abstract boolean nextBluetoothCmd(int stateNr);
|
abstract boolean nextBluetoothCmd(int stateNr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the next command number of the current state.
|
||||||
|
*
|
||||||
|
* @param nextCommand next command to select
|
||||||
|
*/
|
||||||
|
protected void setNextCmd(int nextCommand) {
|
||||||
|
switch (btMachineState) {
|
||||||
|
case BT_INIT_STATE:
|
||||||
|
initStepNr = nextCommand - 1;
|
||||||
|
break;
|
||||||
|
case BT_CMD_STATE:
|
||||||
|
cmdStepNr = nextCommand - 1;
|
||||||
|
break;
|
||||||
|
case BT_CLEANUP_STATE:
|
||||||
|
cleanupStepNr = nextCommand - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* State machine for the clean up process for the Bluetooth device.
|
* State machine for the clean up process for the Bluetooth device.
|
||||||
*
|
*
|
||||||
@@ -228,7 +263,7 @@ public abstract class BluetoothCommunication {
|
|||||||
protected void onBluetoothDataRead(BluetoothGatt bluetoothGatt, BluetoothGattCharacteristic gattCharacteristic, int status){};
|
protected void onBluetoothDataRead(BluetoothGatt bluetoothGatt, BluetoothGattCharacteristic gattCharacteristic, int status){};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method is triggered if a Bluetooth data from a device is notified or indicated.
|
* Method is triggered if a Bluetooth data from a device is notified or indicated.
|
||||||
*
|
*
|
||||||
* @param bluetoothGatt the Bluetooth Gatt
|
* @param bluetoothGatt the Bluetooth Gatt
|
||||||
* @param gattCharacteristic the Bluetooth characteristic
|
* @param gattCharacteristic the Bluetooth characteristic
|
||||||
@@ -245,7 +280,7 @@ public abstract class BluetoothCommunication {
|
|||||||
protected void setBtMachineState(BT_MACHINE_STATE btMachineState) {
|
protected void setBtMachineState(BT_MACHINE_STATE btMachineState) {
|
||||||
this.btMachineState = btMachineState;
|
this.btMachineState = btMachineState;
|
||||||
|
|
||||||
nextMachineStateStep();
|
handleRequests();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -260,7 +295,10 @@ public abstract class BluetoothCommunication {
|
|||||||
.getCharacteristic(characteristic);
|
.getCharacteristic(characteristic);
|
||||||
|
|
||||||
gattCharacteristic.setValue(bytes);
|
gattCharacteristic.setValue(bytes);
|
||||||
bluetoothGatt.writeCharacteristic(gattCharacteristic);
|
synchronized (openRequest) {
|
||||||
|
characteristicRequestQueue.add(gattCharacteristic);
|
||||||
|
handleRequests();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -284,16 +322,18 @@ public abstract class BluetoothCommunication {
|
|||||||
* @param service the Bluetooth UUID device service
|
* @param service the Bluetooth UUID device service
|
||||||
* @param characteristic the Bluetooth UUID characteristic
|
* @param characteristic the Bluetooth UUID characteristic
|
||||||
*/
|
*/
|
||||||
protected void setInidicationOn(UUID service, UUID characteristic) {
|
protected void setIndicationOn(UUID service, UUID characteristic, UUID descriptor) {
|
||||||
BluetoothGattCharacteristic gattCharacteristic = bluetoothGatt.getService(service)
|
BluetoothGattCharacteristic gattCharacteristic = bluetoothGatt.getService(service)
|
||||||
.getCharacteristic(characteristic);
|
.getCharacteristic(characteristic);
|
||||||
|
|
||||||
bluetoothGatt.setCharacteristicNotification(gattCharacteristic, true);
|
bluetoothGatt.setCharacteristicNotification(gattCharacteristic, true);
|
||||||
|
|
||||||
BluetoothGattDescriptor gattDescriptor = gattCharacteristic.getDescriptor(WEIGHT_MEASUREMENT_CONFIG);
|
BluetoothGattDescriptor gattDescriptor = gattCharacteristic.getDescriptor(descriptor);
|
||||||
gattDescriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
|
gattDescriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
|
||||||
|
synchronized (openRequest) {
|
||||||
bluetoothGatt.writeDescriptor(gattDescriptor);
|
descriptorRequestQueue.add(gattDescriptor);
|
||||||
|
handleRequests();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -302,15 +342,18 @@ public abstract class BluetoothCommunication {
|
|||||||
* @param service the Bluetooth UUID device service
|
* @param service the Bluetooth UUID device service
|
||||||
* @param characteristic the Bluetooth UUID characteristic
|
* @param characteristic the Bluetooth UUID characteristic
|
||||||
*/
|
*/
|
||||||
protected void setNotificationOn(UUID service, UUID characteristic) {
|
protected void setNotificationOn(UUID service, UUID characteristic, UUID descriptor) {
|
||||||
BluetoothGattCharacteristic gattCharacteristic = bluetoothGatt.getService(service)
|
BluetoothGattCharacteristic gattCharacteristic = bluetoothGatt.getService(service)
|
||||||
.getCharacteristic(characteristic);
|
.getCharacteristic(characteristic);
|
||||||
|
|
||||||
bluetoothGatt.setCharacteristicNotification(gattCharacteristic, true);
|
bluetoothGatt.setCharacteristicNotification(gattCharacteristic, true);
|
||||||
|
|
||||||
BluetoothGattDescriptor gattDescriptor = gattCharacteristic.getDescriptor(WEIGHT_MEASUREMENT_CONFIG);
|
BluetoothGattDescriptor gattDescriptor = gattCharacteristic.getDescriptor(descriptor);
|
||||||
gattDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
|
gattDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
|
||||||
bluetoothGatt.writeDescriptor(gattDescriptor);
|
synchronized (openRequest) {
|
||||||
|
descriptorRequestQueue.add(gattDescriptor);
|
||||||
|
handleRequests();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -319,15 +362,18 @@ public abstract class BluetoothCommunication {
|
|||||||
* @param service the Bluetooth UUID device service
|
* @param service the Bluetooth UUID device service
|
||||||
* @param characteristic the Bluetooth UUID characteristic
|
* @param characteristic the Bluetooth UUID characteristic
|
||||||
*/
|
*/
|
||||||
protected void setNotificationOff(UUID service, UUID characteristic) {
|
protected void setNotificationOff(UUID service, UUID characteristic, UUID descriptor) {
|
||||||
BluetoothGattCharacteristic gattCharacteristic = bluetoothGatt.getService(service)
|
BluetoothGattCharacteristic gattCharacteristic = bluetoothGatt.getService(service)
|
||||||
.getCharacteristic(characteristic);
|
.getCharacteristic(characteristic);
|
||||||
|
|
||||||
bluetoothGatt.setCharacteristicNotification(gattCharacteristic, false);
|
bluetoothGatt.setCharacteristicNotification(gattCharacteristic, false);
|
||||||
|
|
||||||
BluetoothGattDescriptor gattDescriptor = gattCharacteristic.getDescriptor(WEIGHT_MEASUREMENT_CONFIG);
|
BluetoothGattDescriptor gattDescriptor = gattCharacteristic.getDescriptor(descriptor);
|
||||||
gattDescriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
|
gattDescriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
|
||||||
bluetoothGatt.writeDescriptor(gattDescriptor);
|
synchronized (openRequest) {
|
||||||
|
descriptorRequestQueue.add(gattDescriptor);
|
||||||
|
handleRequests();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -459,6 +505,35 @@ public abstract class BluetoothCommunication {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleRequests() {
|
||||||
|
synchronized (openRequest) {
|
||||||
|
// check for pending request
|
||||||
|
if (openRequest)
|
||||||
|
return; // yes, do nothing
|
||||||
|
|
||||||
|
// handle descriptor requests first
|
||||||
|
BluetoothGattDescriptor descriptorRequest = descriptorRequestQueue.poll();
|
||||||
|
if (descriptorRequest != null) {
|
||||||
|
if (!bluetoothGatt.writeDescriptor(descriptorRequest))
|
||||||
|
Log.d("BTC", "Descriptor Write failed(" + byteInHex(descriptorRequest.getValue()) + ")");
|
||||||
|
openRequest = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle characteristics requests second
|
||||||
|
BluetoothGattCharacteristic characteristicRequest = characteristicRequestQueue.poll();
|
||||||
|
if (characteristicRequest != null) {
|
||||||
|
if (!bluetoothGatt.writeCharacteristic(characteristicRequest))
|
||||||
|
Log.d("BTC", "Characteristic Write failed(" + byteInHex(characteristicRequest.getValue()) + ")");
|
||||||
|
openRequest = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// After every command was executed, continue with the next step
|
||||||
|
nextMachineStateStep();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom Gatt callback class to set up a Bluetooth state machine.
|
* Custom Gatt callback class to set up a Bluetooth state machine.
|
||||||
*/
|
*/
|
||||||
@@ -480,6 +555,12 @@ public abstract class BluetoothCommunication {
|
|||||||
initStepNr = 0;
|
initStepNr = 0;
|
||||||
cleanupStepNr = 0;
|
cleanupStepNr = 0;
|
||||||
|
|
||||||
|
// Clear from possible previous setups
|
||||||
|
characteristicRequestQueue = new LinkedList<>();
|
||||||
|
descriptorRequestQueue = new LinkedList<>();
|
||||||
|
openRequest = false;
|
||||||
|
|
||||||
|
|
||||||
btMachineState = BT_MACHINE_STATE.BT_INIT_STATE;
|
btMachineState = BT_MACHINE_STATE.BT_INIT_STATE;
|
||||||
nextMachineStateStep();
|
nextMachineStateStep();
|
||||||
}
|
}
|
||||||
@@ -488,14 +569,20 @@ public abstract class BluetoothCommunication {
|
|||||||
public void onDescriptorWrite(BluetoothGatt gatt,
|
public void onDescriptorWrite(BluetoothGatt gatt,
|
||||||
BluetoothGattDescriptor descriptor,
|
BluetoothGattDescriptor descriptor,
|
||||||
int status) {
|
int status) {
|
||||||
nextMachineStateStep();
|
synchronized (openRequest) {
|
||||||
|
openRequest = false;
|
||||||
|
handleRequests();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCharacteristicWrite (BluetoothGatt gatt,
|
public void onCharacteristicWrite (BluetoothGatt gatt,
|
||||||
BluetoothGattCharacteristic characteristic,
|
BluetoothGattCharacteristic characteristic,
|
||||||
int status) {
|
int status) {
|
||||||
nextMachineStateStep();
|
synchronized (openRequest) {
|
||||||
|
openRequest = false;
|
||||||
|
handleRequests();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -503,8 +590,10 @@ public abstract class BluetoothCommunication {
|
|||||||
BluetoothGattCharacteristic characteristic,
|
BluetoothGattCharacteristic characteristic,
|
||||||
int status) {
|
int status) {
|
||||||
onBluetoothDataRead(gatt, characteristic, status);
|
onBluetoothDataRead(gatt, characteristic, status);
|
||||||
|
synchronized (openRequest) {
|
||||||
nextMachineStateStep();
|
openRequest = false;
|
||||||
|
handleRequests();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -63,6 +63,7 @@ public class BluetoothMedisanaBS444 extends BluetoothCommunication {
|
|||||||
hwAddresses.add("F13A88");
|
hwAddresses.add("F13A88");
|
||||||
hwAddresses.add("C9A68A");
|
hwAddresses.add("C9A68A");
|
||||||
hwAddresses.add("D60211");
|
hwAddresses.add("D60211");
|
||||||
|
hwAddresses.add("DB2FF6");
|
||||||
|
|
||||||
return hwAddresses;
|
return hwAddresses;
|
||||||
}
|
}
|
||||||
@@ -81,15 +82,15 @@ public class BluetoothMedisanaBS444 extends BluetoothCommunication {
|
|||||||
switch (stateNr) {
|
switch (stateNr) {
|
||||||
case 0:
|
case 0:
|
||||||
// set indication on for feature characteristic
|
// set indication on for feature characteristic
|
||||||
setInidicationOn(WEIGHT_MEASUREMENT_SERVICE, FEATURE_MEASUREMENT_CHARACTERISTIC);
|
setIndicationOn(WEIGHT_MEASUREMENT_SERVICE, FEATURE_MEASUREMENT_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
// set indication on for weight measurement
|
// set indication on for weight measurement
|
||||||
setInidicationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_CHARACTERISTIC);
|
setIndicationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
// set indication on for custom5 measurement
|
// set indication on for custom5 measurement
|
||||||
setInidicationOn(WEIGHT_MEASUREMENT_SERVICE, CUSTOM5_MEASUREMENT_CHARACTERISTIC);
|
setIndicationOn(WEIGHT_MEASUREMENT_SERVICE, CUSTOM5_MEASUREMENT_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG);
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
// send magic number to receive weight data
|
// send magic number to receive weight data
|
||||||
|
@@ -133,7 +133,7 @@ public class BluetoothMiScale extends BluetoothCommunication {
|
|||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
// set notification on for weight measurement history
|
// set notification on for weight measurement history
|
||||||
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC);
|
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG);
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
// Set on history weight measurement
|
// Set on history weight measurement
|
||||||
@@ -153,11 +153,11 @@ public class BluetoothMiScale extends BluetoothCommunication {
|
|||||||
switch (stateNr) {
|
switch (stateNr) {
|
||||||
case 0:
|
case 0:
|
||||||
// set notification on for weight measurement
|
// set notification on for weight measurement
|
||||||
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_CHARACTERISTIC);
|
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
// set notification on for weight measurement history
|
// set notification on for weight measurement history
|
||||||
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC);
|
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
// configure scale to get only last measurements
|
// configure scale to get only last measurements
|
||||||
@@ -168,11 +168,11 @@ public class BluetoothMiScale extends BluetoothCommunication {
|
|||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
// set notification off for weight measurement history
|
// set notification off for weight measurement history
|
||||||
setNotificationOff(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC);
|
setNotificationOff(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG);
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
// set notification on for weight measurement history
|
// set notification on for weight measurement history
|
||||||
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC);
|
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG);
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
// invoke receiving history data
|
// invoke receiving history data
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
/* Copyright (C) 2014 olie.xdev <olie.xdev@googlemail.com>
|
/* Copyright (C) 2014 olie.xdev <olie.xdev@googlemail.com>
|
||||||
* 2017 jflesch <jflesch@kwain.net>
|
* 2017 jflesch <jflesch@kwain.net>
|
||||||
|
* 2017 Martin Nowack
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -17,24 +18,26 @@
|
|||||||
package com.health.openscale.core.bluetooth;
|
package com.health.openscale.core.bluetooth;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothGatt;
|
import android.bluetooth.BluetoothGatt;
|
||||||
import android.bluetooth.BluetoothGattCallback;
|
|
||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
import android.bluetooth.BluetoothGattDescriptor;
|
|
||||||
import android.bluetooth.BluetoothProfile;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.health.openscale.R;
|
||||||
|
import com.health.openscale.core.OpenScale;
|
||||||
import com.health.openscale.core.datatypes.ScaleData;
|
import com.health.openscale.core.datatypes.ScaleData;
|
||||||
|
import com.health.openscale.core.datatypes.ScaleUser;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.text.ParseException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedList;
|
import java.util.Arrays;
|
||||||
import java.util.Queue;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.TreeSet;
|
||||||
import java.util.UUID;
|
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_UNEXPECTED_ERROR;
|
|
||||||
|
|
||||||
public class BluetoothSanitasSbf70 extends BluetoothCommunication {
|
public class BluetoothSanitasSbf70 extends BluetoothCommunication {
|
||||||
public final static String TAG = "BluetoothSanitasSbf70";
|
public final static String TAG = "BluetoothSanitasSbf70";
|
||||||
|
|
||||||
@@ -74,7 +77,7 @@ public class BluetoothSanitasSbf70 extends BluetoothCommunication {
|
|||||||
UUID.fromString("00002A05-0000-1000-8000-00805F9B34FB");
|
UUID.fromString("00002A05-0000-1000-8000-00805F9B34FB");
|
||||||
// descriptor ; handle = 0x000f
|
// descriptor ; handle = 0x000f
|
||||||
private static final UUID CLIENT_CHARACTERISTICS_CONFIGURATION =
|
private static final UUID CLIENT_CHARACTERISTICS_CONFIGURATION =
|
||||||
UUID.fromString("00002902-0000-1000-8000-00805F9B34FB");
|
UUID.fromString("00002901-0000-1000-8000-00805F9B34FB");
|
||||||
|
|
||||||
private static final UUID CUSTOM_SERVICE_1 =
|
private static final UUID CUSTOM_SERVICE_1 =
|
||||||
UUID.fromString("0000FFE0-0000-1000-8000-00805F9B34FB");
|
UUID.fromString("0000FFE0-0000-1000-8000-00805F9B34FB");
|
||||||
@@ -96,9 +99,15 @@ public class BluetoothSanitasSbf70 extends BluetoothCommunication {
|
|||||||
private static final UUID CUSTOM_CHARACTERISTIC_IMG_BLOCK = // write-only, notify
|
private static final UUID CUSTOM_CHARACTERISTIC_IMG_BLOCK = // write-only, notify
|
||||||
UUID.fromString("F000FFC2-0451-4000-8000-000000000000");
|
UUID.fromString("F000FFC2-0451-4000-8000-000000000000");
|
||||||
|
|
||||||
|
|
||||||
|
private int currentScaleUserId;
|
||||||
|
private int countRegisteredScaleUsers;
|
||||||
|
private TreeSet<Integer> seenUsers;
|
||||||
|
private int maxRegisteredScaleUser;
|
||||||
|
private ByteArrayOutputStream receivedScaleData;
|
||||||
|
|
||||||
public BluetoothSanitasSbf70(Context context) {
|
public BluetoothSanitasSbf70(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
gattCallback = new BluetoothSanitasGattCallback();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -119,264 +128,481 @@ public class BluetoothSanitasSbf70 extends BluetoothCommunication {
|
|||||||
return hwAddresses;
|
return hwAddresses;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
boolean nextInitCmd(int stateNr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
boolean nextBluetoothCmd(int stateNr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
boolean nextCleanUpCmd(int stateNr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean initSupported() {
|
public boolean initSupported() {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean historySupported() {
|
public boolean historySupported() {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class BluetoothSanitasGattCallback extends BluetoothGattCallback {
|
@Override
|
||||||
/**
|
boolean nextInitCmd(int stateNr) {
|
||||||
* @brief used to collect the data
|
|
||||||
*/
|
|
||||||
private ScaleData scaleBtData;
|
|
||||||
|
|
||||||
/**
|
switch (stateNr) {
|
||||||
* @brief message to send.
|
case 0:
|
||||||
* Messages are sent by writing on a specific characteristic
|
// Initialize data
|
||||||
*/
|
currentScaleUserId = -1;
|
||||||
private Queue<byte[]> msgQueue;
|
countRegisteredScaleUsers = -1;
|
||||||
|
maxRegisteredScaleUser = -1;
|
||||||
|
seenUsers = new TreeSet<>();
|
||||||
|
|
||||||
/**
|
// Setup notification
|
||||||
* @brief true if the next message can be sent immediately. False if another is already
|
setNotificationOn(CUSTOM_SERVICE_1, CUSTOM_CHARACTERISTIC_WEIGHT, CLIENT_CHARACTERISTICS_CONFIGURATION);
|
||||||
* being sent
|
break;
|
||||||
*/
|
case 1:
|
||||||
private boolean canSend;
|
// Say "Hello" to the scale
|
||||||
|
writeBytes(new byte[]{(byte) 0xe6, (byte) 0x01});
|
||||||
/**
|
break;
|
||||||
* @brief true if the communication must be closed after all the message have been sent
|
case 2:
|
||||||
*/
|
// Update timestamp of the scale
|
||||||
private boolean eof;
|
updateDateTimeSanitas();
|
||||||
|
break;
|
||||||
public BluetoothSanitasGattCallback() {
|
case 3:
|
||||||
super();
|
// Request general user information
|
||||||
scaleBtData = new ScaleData();
|
writeBytes(new byte[]{(byte) 0xe7, (byte) 0x33});
|
||||||
scaleBtData.setId(-1);
|
break;
|
||||||
msgQueue = new LinkedList<>();
|
case 4:
|
||||||
canSend = true;
|
// Wait for ack of all users
|
||||||
eof = false;
|
if (seenUsers.size() < countRegisteredScaleUsers || (countRegisteredScaleUsers == -1)) {
|
||||||
}
|
// Request this state again
|
||||||
|
setNextCmd(stateNr);
|
||||||
@Override
|
break;
|
||||||
public void onConnectionStateChange(final BluetoothGatt gatt, int status, int newState) {
|
|
||||||
Log.d(TAG, "onConnectionStatechange(" + status + ", " + newState + ")");
|
|
||||||
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
|
||||||
Log.d(TAG, "Connection established");
|
|
||||||
setBtStatus(BT_CONNECTION_ESTABLISHED);
|
|
||||||
gatt.discoverServices();
|
|
||||||
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
|
||||||
Log.d(TAG, "Connection lost");
|
|
||||||
setBtStatus(BT_CONNECTION_LOST);
|
|
||||||
stopSearching();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServicesDiscovered(final BluetoothGatt gatt, int status) {
|
|
||||||
Log.d(TAG, "onServicesDiscovered(" + status + ")");
|
|
||||||
//invokeNextBluetoothCmd(gatt);
|
|
||||||
if (status == gatt.GATT_SUCCESS) {
|
|
||||||
init(gatt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief configure the scale
|
|
||||||
*/
|
|
||||||
private void init(final BluetoothGatt gatt) {
|
|
||||||
BluetoothGattCharacteristic characteristic;
|
|
||||||
BluetoothGattDescriptor descriptor;
|
|
||||||
|
|
||||||
characteristic = gatt.getService(CUSTOM_SERVICE_1)
|
|
||||||
.getCharacteristic(CUSTOM_CHARACTERISTIC_WEIGHT);
|
|
||||||
gatt.setCharacteristicNotification(characteristic, true);
|
|
||||||
|
|
||||||
descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTICS_CONFIGURATION);
|
|
||||||
descriptor.setValue(new byte[] {
|
|
||||||
(byte)0x01, (byte)0x00,
|
|
||||||
});
|
|
||||||
gatt.writeDescriptor(descriptor);
|
|
||||||
|
|
||||||
msgQueue.add(new byte[] {
|
|
||||||
(byte)0xe6, (byte)0x01,
|
|
||||||
});
|
|
||||||
canSend = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief send the next message in the queue
|
|
||||||
*/
|
|
||||||
private void nextMessage(final BluetoothGatt gatt) {
|
|
||||||
if (!canSend) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] msg = msgQueue.poll();
|
|
||||||
if (msg == null) {
|
|
||||||
if (eof) {
|
|
||||||
stopSearching();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
canSend = false;
|
|
||||||
|
|
||||||
BluetoothGattCharacteristic characteristic;
|
|
||||||
characteristic = gatt.getService(CUSTOM_SERVICE_1)
|
|
||||||
.getCharacteristic(CUSTOM_CHARACTERISTIC_WEIGHT);
|
|
||||||
characteristic.setValue(msg);
|
|
||||||
gatt.writeCharacteristic(characteristic);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDescriptorWrite(BluetoothGatt gatt,
|
|
||||||
BluetoothGattDescriptor descriptor,
|
|
||||||
int status) {
|
|
||||||
Log.d(TAG, "onDescriptorWrite(" + descriptor + ", " + status + ")");
|
|
||||||
canSend = true;
|
|
||||||
nextMessage(gatt);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCharacteristicWrite (BluetoothGatt gatt,
|
|
||||||
BluetoothGattCharacteristic characteristic,
|
|
||||||
int status) {
|
|
||||||
Log.d(TAG, "onCharacteristicWrite(" + characteristic + ", " + status + ")");
|
|
||||||
canSend = true;
|
|
||||||
nextMessage(gatt);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCharacteristicRead (BluetoothGatt gatt,
|
|
||||||
BluetoothGattCharacteristic characteristic,
|
|
||||||
int status) {
|
|
||||||
Log.d(TAG, "onCharacteristicRead(" + characteristic + ", " + status + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCharacteristicChanged(BluetoothGatt gatt,
|
|
||||||
BluetoothGattCharacteristic characteristic) {
|
|
||||||
final UUID uuid = characteristic.getUuid();
|
|
||||||
final byte[] data = characteristic.getValue();
|
|
||||||
|
|
||||||
Log.d(TAG, "onCharacteristicChanged(" + uuid + "): " + byteInHex(data));
|
|
||||||
|
|
||||||
if (!uuid.equals(CUSTOM_CHARACTERISTIC_WEIGHT)) {
|
|
||||||
Log.d(TAG, "Got characteristic changed from unexpected UUID ?");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((data[0] & 0xFF) == 0xe6 && (data[1] & 0xFF) == 0x00) {
|
|
||||||
Log.d(TAG, "ACK");
|
|
||||||
msgQueue.add(new byte[] {
|
|
||||||
(byte)0xe9, (byte)0x59, (byte)0x07, (byte)0x84, (byte)0x4c,
|
|
||||||
});
|
|
||||||
nextMessage(gatt);
|
|
||||||
return;
|
|
||||||
|
|
||||||
} else if ((data[0] & 0xFF) == 0xe7 && (data[1] & 0xFF) == 0x58) {
|
|
||||||
Log.d(TAG, "ACK");
|
|
||||||
msgQueue.add(new byte[] {
|
|
||||||
(byte)0xe7, (byte)0xf1, (byte)(data[1] & 0xFF),
|
|
||||||
(byte)(data[2] & 0xFF), (byte)(data[3] & 0xFF),
|
|
||||||
});
|
|
||||||
nextMessage(gatt);
|
|
||||||
|
|
||||||
// weight
|
|
||||||
if ((data[2] & 0xFF) != 0x00) {
|
|
||||||
// temporary value;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// stabilized value
|
|
||||||
// little endian
|
|
||||||
float weight = ((float)(
|
|
||||||
((int)(data[3] & 0xFF) << 8) + ((int)(data[4] & 0xFF))
|
|
||||||
)) * 50.0f / 1000.0f; // unit is 50g
|
|
||||||
Log.i(TAG, "Got weight: " + weight);
|
|
||||||
scaleBtData.setWeight(weight);
|
|
||||||
return;
|
|
||||||
|
|
||||||
} else if ((data[0] & 0xFF) == 0xe7 && (data[1] & 0xFF) == 0x59) {
|
|
||||||
Log.d(TAG, "ACK Extra data " + ((int)data[3]));
|
|
||||||
msgQueue.add(new byte[] {
|
|
||||||
(byte)0xe7, (byte)0xf1,
|
|
||||||
(byte)(data[1] & 0xFF), (byte)(data[2] & 0xFF),
|
|
||||||
(byte)(data[3] & 0xFF),
|
|
||||||
});
|
|
||||||
|
|
||||||
if ((data[2] & 0xFF) == 0x03 && (data[3] & 0xFF) == 0x02) {
|
|
||||||
// big endian
|
|
||||||
float fat = ((float)(
|
|
||||||
((int)(data[12] & 0xFF) << 8) + ((int)(data[13] & 0xFF))
|
|
||||||
)) / 10.0f; // unit is 0.1kg
|
|
||||||
Log.i(TAG, "Got fat: " + fat + "%");
|
|
||||||
scaleBtData.setFat(fat);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((data[2] & 0xFF) == 0x03 && (data[3] & 0xFF) == 0x03) {
|
// Got all user acks
|
||||||
// little endian
|
|
||||||
float water = ((float)(
|
|
||||||
((int)(data[5] & 0xFF) << 8) + ((int)(data[4] & 0xFF))
|
|
||||||
)) / 10.0f; // unit is 0.1kg
|
|
||||||
Log.i(TAG, "Got water: " + water + "%");
|
|
||||||
scaleBtData.setWater(water);
|
|
||||||
|
|
||||||
// little endian
|
// Check if not found/unknown
|
||||||
float muscle = ((float)(
|
if (currentScaleUserId == 0) {
|
||||||
((int)(data[7] & 0xFF) << 8) + ((int)(data[6] & 0xFF))
|
// Unknown user, request creation of new user
|
||||||
)) / 10.0f; // unit is 0.1kg
|
if (countRegisteredScaleUsers == maxRegisteredScaleUser) {
|
||||||
Log.i(TAG, "Got muscle: " + muscle + "%");
|
setBtMachineState(BT_MACHINE_STATE.BT_CLEANUP_STATE);
|
||||||
scaleBtData.setMuscle(muscle);
|
Log.d(TAG, "Cannot create additional scale user");
|
||||||
|
sendMessage(R.string.error_max_scale_users, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
addScaleData(scaleBtData);
|
// Request creation of user
|
||||||
|
final ScaleUser selectedUser = OpenScale.getInstance(context).getSelectedScaleUser();
|
||||||
|
|
||||||
Log.d(TAG, "ACK Extra data (end)");
|
// We can only use up to 3 characters and have to handle them uppercase
|
||||||
msgQueue.add(new byte[] {
|
int maxIdx = selectedUser.user_name.length() >= 3 ? 3 : selectedUser.user_name.length();
|
||||||
(byte)0xe7, (byte)0x43, (byte)0x0, (byte)0x0, (byte)0x0,
|
byte[] nick = selectedUser.user_name.toUpperCase().substring(0, maxIdx).getBytes();
|
||||||
(byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0,
|
|
||||||
(byte)0x65
|
byte activity = 2; // activity level: 1 - 5
|
||||||
|
Log.d(TAG, "Create User:" + selectedUser.user_name);
|
||||||
|
|
||||||
|
writeBytes(new byte[]{
|
||||||
|
(byte) 0xe7, (byte) 0x31, (byte) 0x0, (byte) 0x0, (byte) 0x0,
|
||||||
|
(byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0,
|
||||||
|
(byte) (seenUsers.size() > 0 ? Collections.max(seenUsers) + 1 : 101),
|
||||||
|
nick[0], nick[1], nick[2],
|
||||||
|
(byte) selectedUser.birthday.getYear(),
|
||||||
|
(byte) selectedUser.birthday.getMonth(),
|
||||||
|
(byte) selectedUser.birthday.getDate(),
|
||||||
|
(byte) selectedUser.body_height,
|
||||||
|
(byte) (((1 - selectedUser.gender) << 7) | activity)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Get existing user information
|
||||||
|
Log.d(TAG, "Request getUserInfo " + currentScaleUserId);
|
||||||
|
writeBytes(new byte[]{
|
||||||
|
(byte) 0xe7, (byte) 0x36, (byte) 0x0, (byte) 0x0, (byte) 0x0,
|
||||||
|
(byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) currentScaleUserId
|
||||||
});
|
});
|
||||||
|
|
||||||
scaleBtData = new ScaleData();
|
|
||||||
scaleBtData.setId(-1);
|
|
||||||
}
|
}
|
||||||
|
Log.d(TAG, "scaleuserid:" + currentScaleUserId + " registered users: " + countRegisteredScaleUsers +
|
||||||
|
" extracted users: " + seenUsers.size());
|
||||||
|
|
||||||
nextMessage(gatt);
|
|
||||||
return;
|
|
||||||
|
|
||||||
} else if ((data[0] & 0xFF) == 0xe7 && (data[1] & 0xFF) == 0xf0) {
|
break;
|
||||||
Log.d(TAG, "ACK");
|
case 5:
|
||||||
msgQueue.add(new byte[]{
|
break;
|
||||||
(byte) 0xea, (byte) 0x02,
|
default:
|
||||||
|
// Finish init if everything is done
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean nextBluetoothCmd(int stateNr) {
|
||||||
|
|
||||||
|
switch (stateNr) {
|
||||||
|
case 0:
|
||||||
|
// If no specific user selected
|
||||||
|
if (currentScaleUserId == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
Log.d(TAG, "Request Saved User Measurements");
|
||||||
|
writeBytes(new byte[]{
|
||||||
|
(byte) 0xe7, (byte) 0x41, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, (byte) currentScaleUserId
|
||||||
});
|
});
|
||||||
nextMessage(gatt);
|
|
||||||
eof = true;
|
|
||||||
return;
|
|
||||||
|
|
||||||
} else {
|
break;
|
||||||
Log.w(TAG, "Unidentified notification !");
|
case 1:
|
||||||
|
// Wait for user measurements to be received
|
||||||
|
setNextCmd(stateNr);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
setBtMachineState(BT_MACHINE_STATE.BT_CLEANUP_STATE);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
|
||||||
setBtStatus(BT_UNEXPECTED_ERROR, "Error while decoding bluetooth value");
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean nextCleanUpCmd(int stateNr) {
|
||||||
|
switch (stateNr) {
|
||||||
|
case 0:
|
||||||
|
// Force disconnect
|
||||||
|
writeBytes(new byte[]{(byte) 0xea, (byte) 0x02});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBluetoothDataChange(BluetoothGatt bluetoothGatt, BluetoothGattCharacteristic gattCharacteristic) {
|
||||||
|
byte[] data = gattCharacteristic.getValue();
|
||||||
|
if (data.length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ((data[0] & 0xFF) == 0xe6 && (data[1] & 0xFF) == 0x00) {
|
||||||
|
Log.d(TAG, "ACK Scale is ready");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((data[0] & 0xFF) == 0xe7 && (data[1] & 0xFF) == 0xf0 && data[2] == 0x33) {
|
||||||
|
Log.d(TAG, "ACK Got general user information");
|
||||||
|
|
||||||
|
int count = (byte) (data[4] & 0xFF);
|
||||||
|
int maxUsers = (byte) (data[5] & 0xFF);
|
||||||
|
Log.d(TAG, "Count:" + count + " maxUsers:" + maxUsers);
|
||||||
|
|
||||||
|
countRegisteredScaleUsers = count;
|
||||||
|
// Check if any scale user is registered
|
||||||
|
if (count == 0)
|
||||||
|
currentScaleUserId = 0; // Unknown user
|
||||||
|
maxRegisteredScaleUser = maxUsers;
|
||||||
|
|
||||||
|
nextMachineStateStep();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((data[0] & 0xFF) == 0xe7 && (data[1] & 0xFF) == 0x34) {
|
||||||
|
Log.d(TAG, "Ack Get UUIDSs List of Users");
|
||||||
|
|
||||||
|
byte currentUserMax = (byte) (data[2] & 0xFF);
|
||||||
|
byte currentUserID = (byte) (data[3] & 0xFF);
|
||||||
|
byte userUuid = (byte) (data[11] & 0xFF);
|
||||||
|
String name = new String(data, 12, 3);
|
||||||
|
int year = (byte) (data[15] & 0xFF);
|
||||||
|
|
||||||
|
final ScaleUser selectedUser = OpenScale.getInstance(context).getSelectedScaleUser();
|
||||||
|
|
||||||
|
// Check if we found the currently selected user
|
||||||
|
if (selectedUser.user_name.toLowerCase().startsWith(name.toLowerCase()) &&
|
||||||
|
selectedUser.birthday.getYear() == year) {
|
||||||
|
// Found user
|
||||||
|
currentScaleUserId = userUuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remember this uuid from the scale
|
||||||
|
if (seenUsers.add((int) userUuid)) {
|
||||||
|
if (currentScaleUserId == -1 && seenUsers.size() == countRegisteredScaleUsers) {
|
||||||
|
// We have seen all users: user is unknown
|
||||||
|
currentScaleUserId = 0;
|
||||||
|
}
|
||||||
|
Log.d(TAG, "Send ack gotUser");
|
||||||
|
writeBytes(new byte[]{
|
||||||
|
(byte) 0xe7, (byte) 0xf1, (byte) 0x34, currentUserMax,
|
||||||
|
currentUserID
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((data[0] & 0xFF) == 0xe7 && (data[1] & 0xFF) == 0xF0 && (data[2] & 0xFF) == 0x36) {
|
||||||
|
Log.d(TAG, "Ack Get User Info Initials");
|
||||||
|
String name = new String(data, 4, 3);
|
||||||
|
byte year = (byte) (data[7] & 0xFF);
|
||||||
|
byte month = (byte) (data[8] & 0xFF);
|
||||||
|
byte day = (byte) (data[9] & 0xFF);
|
||||||
|
|
||||||
|
int height = (data[10] & 0xFF);
|
||||||
|
boolean male = (data[11] & 0xF0) != 0;
|
||||||
|
byte activity = (byte) (data[11] & 0x0F);
|
||||||
|
|
||||||
|
|
||||||
|
Log.d(TAG, "Name " + name + " YY-MM-DD: " + year + " " + month + " " + day +
|
||||||
|
"Height: " + height + " Sex:" + (male ? "M" : "F") + "activity: " + activity);
|
||||||
|
|
||||||
|
// Get scale status for user
|
||||||
|
writeBytes(new byte[]{
|
||||||
|
(byte) 0xe7, (byte) 0x4f, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0,
|
||||||
|
(byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) currentScaleUserId
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((data[0] & 0xFF) == 0xe7 && (data[1] & 0xFF) == 0xf0 && (data[2] & 0xFF) == 0x4F) {
|
||||||
|
Log.d(TAG, "Ack Get scale status");
|
||||||
|
|
||||||
|
int unknown = data[3];
|
||||||
|
int batteryLevel = (data[4] & 0xFF);
|
||||||
|
float weightThreshold = (data[5] & 0xFF) / 10f;
|
||||||
|
float bodyFatThreshold = (data[6] & 0xFF) / 10f;
|
||||||
|
int unit = data[7]; // 1 kg, 2 lb (pounds), 3 st stone
|
||||||
|
boolean userExists = (data[8] == 0);
|
||||||
|
boolean userReferWeightExists = (data[9] == 0);
|
||||||
|
boolean userMeasurementExist = (data[10] == 0);
|
||||||
|
int scaleVersion = data[11];
|
||||||
|
|
||||||
|
Log.d(TAG, "BatteryLevel:" + batteryLevel + " weightThreshold: " + weightThreshold +
|
||||||
|
" BodyFatThresh: " + bodyFatThreshold + " Unit: " + unit + " userExists: " + userExists +
|
||||||
|
" UserReference Weight Exists:" + userReferWeightExists + " UserMeasurementExists " + userMeasurementExist +
|
||||||
|
" scaleVersion" + scaleVersion);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((data[0] & 0xFF) == 0xe7 && (data[1] & 0xFF) == 0xf0 && data[2] == 0x31) {
|
||||||
|
Log.d(TAG, "Acknowledge creation of user");
|
||||||
|
|
||||||
|
// Indicate user to step on scale
|
||||||
|
sendMessage(R.string.info_step_on_scale, 0);
|
||||||
|
|
||||||
|
// Request basement measurement
|
||||||
|
writeBytes(new byte[]{
|
||||||
|
(byte) 0xe7, 0x40, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
(byte) (seenUsers.size() > 0 ? Collections.max(seenUsers) + 1 : 101)
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ((data[0] & 0xFF) == 0xe7 && (data[1] & 0xFF) == 0xf0 && (data[2] & 0xFF) == 0x41) {
|
||||||
|
Log.d(TAG, "Will start to receive measurements User Specific");
|
||||||
|
|
||||||
|
byte nr_measurements = data[3];
|
||||||
|
|
||||||
|
Log.d(TAG, "New measurements: " + nr_measurements / 2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((data[0] & 0xFF) == 0xe7 && (data[1] & 0xFF) == 0x42) {
|
||||||
|
Log.d(TAG, "Specific measurement User specific");
|
||||||
|
|
||||||
|
// Measurements are split into two parts
|
||||||
|
|
||||||
|
int max_items = data[2] & 0xFF;
|
||||||
|
int current_item = data[3] & 0xFF;
|
||||||
|
|
||||||
|
// Received even part
|
||||||
|
if (current_item % 2 == 1) {
|
||||||
|
receivedScaleData = new ByteArrayOutputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
receivedScaleData.write(Arrays.copyOfRange(data, 4, data.length));
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send acknowledgement
|
||||||
|
writeBytes(new byte[]{
|
||||||
|
(byte) 0xe7, (byte) 0xf1, (byte) 0x42, (byte) (data[2] & 0xFF),
|
||||||
|
(byte) (data[3] & 0xFF)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (current_item % 2 == 0) {
|
||||||
|
try {
|
||||||
|
ScaleData parsedData = parseScaleData(receivedScaleData.toByteArray());
|
||||||
|
addScaleData(parsedData);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
Log.d(TAG, "Could not parse byte array: " + byteInHex(receivedScaleData.toByteArray()));
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_item == max_items) {
|
||||||
|
// finish and delete
|
||||||
|
deleteScaleData();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((data[0] & 0xFF) == 0xe7 && (data[1] & 0xFF) == 0x58) {
|
||||||
|
Log.d(TAG, "Active measurement");
|
||||||
|
if ((data[2] & 0xFF) != 0x00) {
|
||||||
|
// little endian
|
||||||
|
float weight = ((float) (
|
||||||
|
((data[3] & 0xFF) << 8) + (data[4] & 0xFF)
|
||||||
|
)) * 50.0f / 1000.0f; // unit is 50g
|
||||||
|
|
||||||
|
// temporary value;
|
||||||
|
sendMessage(R.string.info_measuring, weight);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stabilized value
|
||||||
|
// little endian
|
||||||
|
float weight = ((float) (
|
||||||
|
((data[3] & 0xFF) << 8) + (data[4] & 0xFF)
|
||||||
|
)) * 50.0f / 1000.0f; // unit is 50g
|
||||||
|
|
||||||
|
Log.i(TAG, "Got weight: " + weight);
|
||||||
|
|
||||||
|
|
||||||
|
writeBytes(new byte[]{
|
||||||
|
(byte) 0xe7, (byte) 0xf1, (byte) (data[1] & 0xFF),
|
||||||
|
(byte) (data[2] & 0xFF), (byte) (data[3] & 0xFF),
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((data[0] & 0xFF) == 0xe7 && (data[1] & 0xFF) == 0x59) {
|
||||||
|
// Get stable measurement results
|
||||||
|
Log.d(TAG, "Get measurement data " + ((int) data[3]));
|
||||||
|
|
||||||
|
int max_items = (data[2] & 0xFF);
|
||||||
|
int current_item = (data[3] & 0xFF);
|
||||||
|
|
||||||
|
// Received first part
|
||||||
|
if (current_item == 1) {
|
||||||
|
receivedScaleData = new ByteArrayOutputStream();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
receivedScaleData.write(Arrays.copyOfRange(data, 4, data.length));
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send ack that we got the data
|
||||||
|
writeBytes(new byte[]{
|
||||||
|
(byte) 0xe7, (byte) 0xf1,
|
||||||
|
(byte) (data[1] & 0xFF), (byte) (data[2] & 0xFF),
|
||||||
|
(byte) (data[3] & 0xFF),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (current_item == max_items) {
|
||||||
|
// received all parts
|
||||||
|
ScaleData parsedData = null;
|
||||||
|
try {
|
||||||
|
parsedData = parseScaleData(receivedScaleData.toByteArray());
|
||||||
|
addScaleData(parsedData);
|
||||||
|
// Delete data
|
||||||
|
deleteScaleData();
|
||||||
|
} catch (ParseException e) {
|
||||||
|
Log.d(TAG, "Parse Exception " + byteInHex(receivedScaleData.toByteArray()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((data[0] & 0xFF) == 0xe7 && (data[1] & 0xFF) == 0xf0 && (data[2] & 0xFF) == 0x43) {
|
||||||
|
Log.d(TAG, "Acknowledge: Data deleted.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "DataChanged - not handled: " + byteInHex(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteScaleData() {
|
||||||
|
writeBytes(new byte[]{
|
||||||
|
(byte) 0xe7, (byte) 0x43, (byte) 0x0, (byte) 0x0, (byte) 0x0,
|
||||||
|
(byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0,
|
||||||
|
(byte) currentScaleUserId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private ScaleData parseScaleData(byte[] data) throws ParseException {
|
||||||
|
if (data.length != 11 + 11)
|
||||||
|
throw new ParseException("Parse scala data: unexpected length", 0);
|
||||||
|
|
||||||
|
ScaleData receivedMeasurement = new ScaleData();
|
||||||
|
|
||||||
|
// Parse timestamp
|
||||||
|
long timestamp = ByteBuffer.wrap(data, 0, 4).getInt() * 1000L;
|
||||||
|
SimpleDateFormat sdf = new SimpleDateFormat("MMMM d, yyyy 'at' h:mm a");
|
||||||
|
String date = sdf.format(timestamp);
|
||||||
|
|
||||||
|
// little endian
|
||||||
|
float weight = ((float) (
|
||||||
|
((data[4] & 0xFF) << 8) + (data[5] & 0xFF)
|
||||||
|
)) * 50.0f / 1000.0f; // unit is 50g
|
||||||
|
receivedMeasurement.setWeight(weight);
|
||||||
|
|
||||||
|
// Parse impedance level
|
||||||
|
int impedance = ((data[6] & 0xFF) << 8) + (data[7] & 0xFF);
|
||||||
|
|
||||||
|
// Parse fat
|
||||||
|
float fat = ((float) (
|
||||||
|
((data[8] & 0xFF) << 8) + (data[9] & 0xFF)
|
||||||
|
)) / 10.0f; // unit is 0.1%
|
||||||
|
receivedMeasurement.setFat(fat);
|
||||||
|
|
||||||
|
float water = ((float) (
|
||||||
|
((data[10] & 0xFF) << 8) + (data[11] & 0xFF)
|
||||||
|
)) / 10.0f; // unit is 0.1%
|
||||||
|
receivedMeasurement.setWater(water);
|
||||||
|
|
||||||
|
float muscle = ((float) (
|
||||||
|
((data[12] & 0xFF) << 8) + (data[13] & 0xFF)
|
||||||
|
)) / 10.0f; // unit is 0.1%
|
||||||
|
receivedMeasurement.setMuscle(muscle);
|
||||||
|
|
||||||
|
float boneMass = ((float) (
|
||||||
|
((data[14] & 0xFF) << 8) + (data[15] & 0xFF)
|
||||||
|
)) * 50.0f / 1000.0f; // unit is 50g
|
||||||
|
|
||||||
|
// basal metabolic rate
|
||||||
|
float bmr = ((float) (
|
||||||
|
((data[16] & 0xFF) << 8) + (data[17] & 0xFF)
|
||||||
|
)) / 10.0f;
|
||||||
|
|
||||||
|
// active metabolic rate
|
||||||
|
int amr = ((data[18] & 0xFF) << 8) + (data[19] & 0xFF);
|
||||||
|
|
||||||
|
float bmi = ((data[20] & 0xFF) << 8) + (data[21] & 0xFF);
|
||||||
|
|
||||||
|
Log.i(TAG, "Measurement: " + date + " Impedance: " + impedance + " Weight:" + weight +
|
||||||
|
" Fat: " + fat + " Water: " + water + " Muscle: " + muscle +
|
||||||
|
" BoneMass: " + boneMass + " BMR: " + bmr + " AMR: " + amr + " BMI: " + bmi);
|
||||||
|
|
||||||
|
return receivedMeasurement;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDateTimeSanitas() {
|
||||||
|
// Update date/time of the scale
|
||||||
|
long unixTime = System.currentTimeMillis() / 1000L;
|
||||||
|
byte[] unixTimeBytes = ByteBuffer.allocate(Long.SIZE / 8).putLong(unixTime).array();
|
||||||
|
Log.d(TAG, "Write new Date/Time:" + unixTime + " " + byteInHex(unixTimeBytes));
|
||||||
|
|
||||||
|
writeBytes(new byte[]{(byte) 0xe9, unixTimeBytes[4], unixTimeBytes[5], unixTimeBytes[6], unixTimeBytes[7]});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeBytes(byte[] data) {
|
||||||
|
writeBytes(CUSTOM_SERVICE_1, CUSTOM_CHARACTERISTIC_WEIGHT, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -244,6 +244,10 @@ public class MainActivity extends ActionBarActivity implements
|
|||||||
Toast.makeText(getApplicationContext(), getResources().getString(R.string.info_bluetooth_connection_error) + ": " + msg.obj, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getApplicationContext(), getResources().getString(R.string.info_bluetooth_connection_error) + ": " + msg.obj, Toast.LENGTH_SHORT).show();
|
||||||
Log.e("OpenScale", "Bluetooth unexpected error: " + msg.obj);
|
Log.e("OpenScale", "Bluetooth unexpected error: " + msg.obj);
|
||||||
break;
|
break;
|
||||||
|
case BT_SCALE_MESSAGE:
|
||||||
|
String toastMessage = String.format(getResources().getString(msg.arg1), msg.obj);
|
||||||
|
Toast.makeText(getApplicationContext(), toastMessage, Toast.LENGTH_LONG).show();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -150,4 +150,9 @@
|
|||||||
<string name="label_ignoreOutOfRange">Ignore data that are out of range</string>
|
<string name="label_ignoreOutOfRange">Ignore data that are out of range</string>
|
||||||
<string name="label_initial_weight">Initial weight</string>
|
<string name="label_initial_weight">Initial weight</string>
|
||||||
<string name="label_average_data">Calculate average per day/month</string>
|
<string name="label_average_data">Calculate average per day/month</string>
|
||||||
|
|
||||||
|
<string name="error_max_scale_users">Maximum number of concurrent scale users reached.</string>
|
||||||
|
<string name="info_step_on_scale">Please step barefoot on the scale for reference measurements.</string>
|
||||||
|
<string name="info_measuring">Measuring weight: %.2f</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
Binary file not shown.
Reference in New Issue
Block a user