1
0
mirror of https://github.com/oliexdev/openScale.git synced 2025-08-18 22:41:44 +02:00

Merge BLE rework/fixes done in #335

This commit is contained in:
Erik Johansson
2018-10-29 21:36:09 +01:00
4 changed files with 222 additions and 202 deletions

View File

@@ -196,6 +196,8 @@ public class OpenScale {
selectScaleUser(-1); selectScaleUser(-1);
throw new Exception("could not find the selected user"); throw new Exception("could not find the selected user");
} }
Timber.d("Selected user is now %s (%d)",
selectedScaleUser.getUserName(), selectedScaleUser.getId());
return selectedScaleUser; return selectedScaleUser;
} }
} catch (Exception e) { } catch (Exception e) {

View File

@@ -78,10 +78,8 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
private static final UUID SERVICE_CHANGED = private static final UUID SERVICE_CHANGED =
UUID.fromString("00002A05-0000-1000-8000-00805F9B34FB"); UUID.fromString("00002A05-0000-1000-8000-00805F9B34FB");
private static final UUID CLIENT_CHARACTERISTICS_CONFIGURATION_BEURER = private static final UUID CLIENT_CHARACTERISTICS_CONFIGURATION =
UUID.fromString("00002902-0000-1000-8000-00805F9B34FB"); UUID.fromString("00002902-0000-1000-8000-00805F9B34FB");
private static final UUID CLIENT_CHARACTERISTICS_CONFIGURATION_SANITAS =
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");
@@ -156,10 +154,7 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
seenUsers = new TreeSet<>(); seenUsers = new TreeSet<>();
// Setup notification // Setup notification
UUID clientCharacteristicsConfiguration = deviceType == DeviceType.SANITAS_SBF70_70 setNotificationOn(CUSTOM_SERVICE_1, CUSTOM_CHARACTERISTIC_WEIGHT, CLIENT_CHARACTERISTICS_CONFIGURATION);
? CLIENT_CHARACTERISTICS_CONFIGURATION_SANITAS
: CLIENT_CHARACTERISTICS_CONFIGURATION_BEURER;
setNotificationOn(CUSTOM_SERVICE_1, CUSTOM_CHARACTERISTIC_WEIGHT, clientCharacteristicsConfiguration);
break; break;
case 1: case 1:
// Say "Hello" to the scale // Say "Hello" to the scale
@@ -486,6 +481,12 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
(byte) (data[2] & 0xFF), (byte) (data[3] & 0xFF), (byte) (data[2] & 0xFF), (byte) (data[3] & 0xFF),
}); });
if (currentScaleUserId == 0) {
Timber.i("Initial weight set; disconnecting...");
setBtMachineState(BT_MACHINE_STATE.BT_CLEANUP_STATE);
return;
}
return; return;
} }

View File

@@ -56,6 +56,7 @@ public abstract class BluetoothCommunication {
private Handler callbackBtHandler; private Handler callbackBtHandler;
private Handler handler; private Handler handler;
private static boolean autoConnect = false;
private BluetoothGatt bluetoothGatt; private BluetoothGatt bluetoothGatt;
private boolean connectionEstablished; private boolean connectionEstablished;
private BluetoothGattCallback gattCallback; private BluetoothGattCallback gattCallback;
@@ -80,7 +81,6 @@ public abstract class BluetoothCommunication {
private Queue<GattObjectValue<BluetoothGattDescriptor>> descriptorRequestQueue; private Queue<GattObjectValue<BluetoothGattDescriptor>> descriptorRequestQueue;
private Queue<GattObjectValue<BluetoothGattCharacteristic>> characteristicRequestQueue; private Queue<GattObjectValue<BluetoothGattCharacteristic>> characteristicRequestQueue;
private boolean openRequest; private boolean openRequest;
private final Object lock = new Object();
public BluetoothCommunication(Context context) public BluetoothCommunication(Context context)
{ {
@@ -240,11 +240,9 @@ public abstract class BluetoothCommunication {
* @param btMachineState the machine state that should be set. * @param btMachineState the machine state that should be set.
*/ */
protected void setBtMachineState(BT_MACHINE_STATE btMachineState) { protected void setBtMachineState(BT_MACHINE_STATE btMachineState) {
synchronized (lock) {
this.btMachineState = btMachineState; this.btMachineState = btMachineState;
handleRequests(); handleRequests();
} }
}
/** /**
* Write a byte array to a Bluetooth device. * Write a byte array to a Bluetooth device.
@@ -254,14 +252,12 @@ public abstract class BluetoothCommunication {
* @param bytes the bytes that should be write * @param bytes the bytes that should be write
*/ */
protected void writeBytes(UUID service, UUID characteristic, byte[] bytes) { protected void writeBytes(UUID service, UUID characteristic, byte[] bytes) {
synchronized (lock) {
characteristicRequestQueue.add( characteristicRequestQueue.add(
new GattObjectValue<>( new GattObjectValue<>(
bluetoothGatt.getService(service).getCharacteristic(characteristic), bluetoothGatt.getService(service).getCharacteristic(characteristic),
bytes)); bytes));
handleRequests(); handleRequests();
} }
}
/** /**
* Read bytes from a Bluetooth device. * Read bytes from a Bluetooth device.
@@ -301,14 +297,12 @@ public abstract class BluetoothCommunication {
bluetoothGatt.getService(service).getCharacteristic(characteristic); bluetoothGatt.getService(service).getCharacteristic(characteristic);
bluetoothGatt.setCharacteristicNotification(gattCharacteristic, true); bluetoothGatt.setCharacteristicNotification(gattCharacteristic, true);
synchronized (lock) {
descriptorRequestQueue.add( descriptorRequestQueue.add(
new GattObjectValue<>( new GattObjectValue<>(
gattCharacteristic.getDescriptor(descriptor), gattCharacteristic.getDescriptor(descriptor),
BluetoothGattDescriptor.ENABLE_INDICATION_VALUE)); BluetoothGattDescriptor.ENABLE_INDICATION_VALUE));
handleRequests(); handleRequests();
} }
}
catch (Exception e) { catch (Exception e) {
Timber.e(e); Timber.e(e);
} }
@@ -328,14 +322,12 @@ public abstract class BluetoothCommunication {
bluetoothGatt.getService(service).getCharacteristic(characteristic); bluetoothGatt.getService(service).getCharacteristic(characteristic);
bluetoothGatt.setCharacteristicNotification(gattCharacteristic, true); bluetoothGatt.setCharacteristicNotification(gattCharacteristic, true);
synchronized (lock) {
descriptorRequestQueue.add( descriptorRequestQueue.add(
new GattObjectValue<>( new GattObjectValue<>(
gattCharacteristic.getDescriptor(descriptor), gattCharacteristic.getDescriptor(descriptor),
BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)); BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE));
handleRequests(); handleRequests();
} }
}
catch (Exception e) { catch (Exception e) {
Timber.e(e); Timber.e(e);
} }
@@ -354,14 +346,12 @@ public abstract class BluetoothCommunication {
bluetoothGatt.getService(service).getCharacteristic(characteristic); bluetoothGatt.getService(service).getCharacteristic(characteristic);
bluetoothGatt.setCharacteristicNotification(gattCharacteristic, false); bluetoothGatt.setCharacteristicNotification(gattCharacteristic, false);
synchronized (lock) {
descriptorRequestQueue.add( descriptorRequestQueue.add(
new GattObjectValue<>( new GattObjectValue<>(
gattCharacteristic.getDescriptor(descriptor), gattCharacteristic.getDescriptor(descriptor),
BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)); BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE));
handleRequests(); handleRequests();
} }
}
/** /**
* Convert a byte array to hex for debugging purpose * Convert a byte array to hex for debugging purpose
@@ -417,12 +407,12 @@ public abstract class BluetoothCommunication {
public void connect(String hwAddress) { public void connect(String hwAddress) {
logBluetoothStatus(); logBluetoothStatus();
disconnect(false);
btAdapter.cancelDiscovery();
// Some good tips to improve BLE connections: // Some good tips to improve BLE connections:
// https://android.jlelse.eu/lessons-for-first-time-android-bluetooth-le-developers-i-learned-the-hard-way-fee07646624 // https://android.jlelse.eu/lessons-for-first-time-android-bluetooth-le-developers-i-learned-the-hard-way-fee07646624
btAdapter.cancelDiscovery();
stopLeScan();
// Don't do any cleanup if disconnected before fully connected // Don't do any cleanup if disconnected before fully connected
btMachineState = BT_MACHINE_STATE.BT_CLEANUP_STATE; btMachineState = BT_MACHINE_STATE.BT_CLEANUP_STATE;
@@ -436,7 +426,7 @@ public abstract class BluetoothCommunication {
} }
else { else {
Timber.d("No coarse location permission, connecting without LE scan"); Timber.d("No coarse location permission, connecting without LE scan");
connectGatt(hwAddress); connectGatt(btAdapter.getRemoteDevice(hwAddress));
} }
} }
@@ -458,49 +448,53 @@ public abstract class BluetoothCommunication {
} }
private void connectGatt(BluetoothDevice device) { private void connectGatt(BluetoothDevice device) {
Timber.i("Connecting to [%s] (driver: %s)", device.getAddress(), driverName()); Timber.i("Connecting to [%s] (%sdriver: %s)",
device.getAddress(), autoConnect ? "auto connect, " : "", driverName());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
bluetoothGatt = device.connectGatt( bluetoothGatt = device.connectGatt(
context, false, gattCallback, BluetoothDevice.TRANSPORT_LE); context, autoConnect, gattCallback, BluetoothDevice.TRANSPORT_LE);
} }
else { else {
bluetoothGatt = device.connectGatt(context, false, gattCallback); bluetoothGatt = device.connectGatt(context, autoConnect, gattCallback);
} }
} }
private void connectGatt(String hwAddress) {
connectGatt(btAdapter.getRemoteDevice(hwAddress));
}
private void startLeScanForDevice(final String hwAddress) { private void startLeScanForDevice(final String hwAddress) {
leScanCallback = new BluetoothAdapter.LeScanCallback() { leScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override @Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
Timber.d("Found LE device %s [%s]", device.getName(), device.getAddress()); Timber.d("Found LE device %s [%s]", device.getName(), device.getAddress());
if (!device.getAddress().equals(hwAddress)) { if (!device.getAddress().equals(hwAddress)) {
return; return;
} }
synchronized (lock) { // Stop timeout and connect to the device on the main thread
stopLeScan(); handler.removeCallbacksAndMessages(leScanCallback);
handler.post(new Runnable() {
@Override
public void run() {
// Check that bluetoothGatt == null in case the same device is found multiple times
// and thus multiple calls to connect to it are queued. Only the first will
// trigger the connect.
if (bluetoothGatt == null) {
connectGatt(device); connectGatt(device);
} }
} }
});
}
}; };
Timber.d("Starting LE scan for device [%s]", hwAddress); // Try to connect to the device directly if the device isn't found in time
btAdapter.startLeScan(leScanCallback);
handler.postAtTime(new Runnable() { handler.postAtTime(new Runnable() {
@Override @Override
public void run() { public void run() {
Timber.d("Device not found in LE scan, connecting directly"); Timber.d("Device not found in LE scan, connecting directly");
synchronized (lock) { connectGatt(btAdapter.getRemoteDevice(hwAddress));
stopLeScan();
connectGatt(hwAddress);
}
} }
}, leScanCallback, SystemClock.uptimeMillis() + LE_SCAN_TIMEOUT_MS); }, leScanCallback, SystemClock.uptimeMillis() + LE_SCAN_TIMEOUT_MS);
Timber.d("Starting LE scan for device [%s]", hwAddress);
btAdapter.startLeScan(leScanCallback);
} }
private void stopLeScan() { private void stopLeScan() {
@@ -516,10 +510,11 @@ public abstract class BluetoothCommunication {
* Disconnect from a Bluetooth device * Disconnect from a Bluetooth device
*/ */
public void disconnect(boolean doCleanup) { public void disconnect(boolean doCleanup) {
synchronized (lock) {
stopLeScan(); stopLeScan();
if (bluetoothGatt == null) { if (bluetoothGatt == null) {
// Could be a pending connectGatt waiting
handler.removeCallbacksAndMessages(null);
return; return;
} }
@@ -536,13 +531,10 @@ public abstract class BluetoothCommunication {
handler.post(new Runnable() { handler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
synchronized (lock) {
if (openRequest) { if (openRequest) {
handler.postDelayed(this, 10); handler.postDelayed(this, 10);
} else { } else {
bluetoothGatt.close(); disconnect(false);
bluetoothGatt = null;
}
} }
} }
}); });
@@ -552,7 +544,6 @@ public abstract class BluetoothCommunication {
bluetoothGatt = null; bluetoothGatt = null;
} }
} }
}
/** /**
* Invoke next step for internal Bluetooth state machine. * Invoke next step for internal Bluetooth state machine.
@@ -581,7 +572,6 @@ public abstract class BluetoothCommunication {
} }
private void handleRequests() { private void handleRequests() {
synchronized (lock) {
// check for pending request // check for pending request
if (openRequest) { if (openRequest) {
Timber.d("Request pending (queue %d %d)", Timber.d("Request pending (queue %d %d)",
@@ -610,8 +600,9 @@ public abstract class BluetoothCommunication {
if (characteristic != null) { if (characteristic != null) {
characteristic.gattObject.setValue(characteristic.value); characteristic.gattObject.setValue(characteristic.value);
Timber.d("Write characteristic %s: %s (queue: %d %d)", Timber.d("Write characteristic %s: %s (type: %d; queue: %d %d)",
characteristic.gattObject.getUuid(), byteInHex(characteristic.gattObject.getValue()), characteristic.gattObject.getUuid(), byteInHex(characteristic.gattObject.getValue()),
characteristic.gattObject.getWriteType(),
descriptorRequestQueue.size(), characteristicRequestQueue.size()); descriptorRequestQueue.size(), characteristicRequestQueue.size());
if (!bluetoothGatt.writeCharacteristic(characteristic.gattObject)) { if (!bluetoothGatt.writeCharacteristic(characteristic.gattObject)) {
Timber.e("Failed to initiate write of characteristic %s", Timber.e("Failed to initiate write of characteristic %s",
@@ -624,46 +615,52 @@ public abstract class BluetoothCommunication {
// After every command was executed, continue with the next step // After every command was executed, continue with the next step
nextMachineStateStep(); nextMachineStateStep();
} }
}
/** /**
* Custom Gatt callback class to set up a Bluetooth state machine. * Custom Gatt callback class to set up a Bluetooth state machine.
*/ */
protected class GattCallback extends BluetoothGattCallback { protected class GattCallback extends BluetoothGattCallback {
@Override @Override
public void onConnectionStateChange(final BluetoothGatt gatt, int status, int newState) { public void onConnectionStateChange(final BluetoothGatt gatt, final int status, int newState) {
Timber.d("onConnectionStateChange: status=%d, newState=%d", status, newState); Timber.d("onConnectionStateChange: status=%d, newState=%d", status, newState);
if (newState == BluetoothProfile.STATE_CONNECTED) { if (newState == BluetoothProfile.STATE_CONNECTED) {
synchronized (lock) { handler.post(new Runnable() {
@Override
public void run() {
stopLeScan(); stopLeScan();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
gatt.readPhy();
}
connectionEstablished = true; connectionEstablished = true;
setBtStatus(BT_STATUS_CODE.BT_CONNECTION_ESTABLISHED); setBtStatus(BT_STATUS_CODE.BT_CONNECTION_ESTABLISHED);
}
});
try { // Wait a short while after connecting before scanning for services
Thread.sleep(1000); handler.postDelayed(new Runnable() {
} @Override
catch (Exception e) { public void run() {
// Empty
}
if (!gatt.discoverServices()) { if (!gatt.discoverServices()) {
Timber.e("Could not start service discovery"); Timber.e("Could not start service discovery");
setBtStatus(BT_STATUS_CODE.BT_CONNECTION_LOST); setBtStatus(BT_STATUS_CODE.BT_CONNECTION_LOST);
disconnect(false); disconnect(false);
} }
} }
}, 1000);
}
else if (newState == BluetoothProfile.STATE_DISCONNECTED) { else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
handler.post(new Runnable() {
@Override
public void run() {
if (connectionEstablished && status != 0) {
autoConnect = !autoConnect;
}
setBtStatus(connectionEstablished setBtStatus(connectionEstablished
? BT_STATUS_CODE.BT_CONNECTION_LOST ? BT_STATUS_CODE.BT_CONNECTION_LOST
: BT_STATUS_CODE.BT_NO_DEVICE_FOUND); : BT_STATUS_CODE.BT_NO_DEVICE_FOUND);
disconnect(false); disconnect(false);
} }
});
}
} }
@Override @Override
@@ -672,12 +669,22 @@ public abstract class BluetoothCommunication {
status, gatt.getServices().size()); status, gatt.getServices().size());
if (gatt.getServices().isEmpty()) { if (gatt.getServices().isEmpty()) {
handler.post(new Runnable() {
@Override
public void run() {
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "No services found"); setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "No services found");
disconnect(false); disconnect(false);
}
});
return; return;
} }
synchronized (lock) { // Sleeping a while after discovering services fixes connection problems.
// See https://github.com/NordicSemiconductor/Android-DFU-Library/issues/10
// for some technical background.
handler.postDelayed(new Runnable() {
@Override
public void run() {
cmdStepNr = 0; cmdStepNr = 0;
initStepNr = 0; initStepNr = 0;
cleanupStepNr = 0; cleanupStepNr = 0;
@@ -686,21 +693,12 @@ public abstract class BluetoothCommunication {
characteristicRequestQueue = new LinkedList<>(); characteristicRequestQueue = new LinkedList<>();
descriptorRequestQueue = new LinkedList<>(); descriptorRequestQueue = new LinkedList<>();
openRequest = false; openRequest = false;
}
try {
// Sleeping a while after discovering services fixes connection problems.
// See https://github.com/NordicSemiconductor/Android-DFU-Library/issues/10
// for some technical background.
Thread.sleep(1000);
}
catch (Exception e) {
// Empty
}
// Start the state machine // Start the state machine
setBtMachineState(BT_MACHINE_STATE.BT_INIT_STATE); setBtMachineState(BT_MACHINE_STATE.BT_INIT_STATE);
} }
}, 1000);
}
@Override @Override
public void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) { public void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
@@ -718,11 +716,9 @@ public abstract class BluetoothCommunication {
handler.postDelayed(new Runnable() { handler.postDelayed(new Runnable() {
@Override @Override
public void run() { public void run() {
synchronized (lock) {
openRequest = false; openRequest = false;
handleRequests(); handleRequests();
} }
}
}, 60); }, 60);
} }
@@ -741,27 +737,33 @@ public abstract class BluetoothCommunication {
} }
@Override @Override
public void onCharacteristicRead(BluetoothGatt gatt, public void onCharacteristicRead(final BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, final BluetoothGattCharacteristic characteristic,
int status) { final int status) {
Timber.d("onCharacteristicRead %s (status=%d): %s", Timber.d("onCharacteristicRead %s (status=%d): %s",
characteristic.getUuid(), status, byteInHex(characteristic.getValue())); characteristic.getUuid(), status, byteInHex(characteristic.getValue()));
synchronized (lock) { handler.post(new Runnable() {
@Override
public void run() {
onBluetoothDataRead(gatt, characteristic, status); onBluetoothDataRead(gatt, characteristic, status);
postDelayedHandleRequests();
} }
});
postDelayedHandleRequests();
} }
@Override @Override
public void onCharacteristicChanged(BluetoothGatt gatt, public void onCharacteristicChanged(final BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) { final BluetoothGattCharacteristic characteristic) {
Timber.d("onCharacteristicChanged %s: %s", Timber.d("onCharacteristicChanged %s: %s",
characteristic.getUuid(), byteInHex(characteristic.getValue())); characteristic.getUuid(), byteInHex(characteristic.getValue()));
synchronized (lock) { handler.post(new Runnable() {
@Override
public void run() {
onBluetoothDataChange(gatt, characteristic); onBluetoothDataChange(gatt, characteristic);
} }
});
} }
@Override @Override

View File

@@ -74,13 +74,28 @@ public class BluetoothDebug extends BluetoothCommunication {
return names.substring(0, names.length() - 2); return names.substring(0, names.length() - 2);
} }
private String writeTypeToString(int type) {
if (type == BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) {
return "no response";
}
if (type == BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT) {
return "default";
}
if (type == BluetoothGattCharacteristic.WRITE_TYPE_SIGNED) {
return "signed";
}
return String.format("unknown type %d", type);
}
private void logService(BluetoothGattService service, boolean included) { private void logService(BluetoothGattService service, boolean included) {
Timber.d("Service %s%s", service.getUuid(), included ? " (included)" : ""); Timber.d("Service %s%s", service.getUuid(), included ? " (included)" : "");
for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) { for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
Timber.d("|- characteristic %s (instance %d): %s (permissions=0x%x)", Timber.d("|- characteristic %s (instance %d): %s, write type: %s (permissions=0x%x)",
characteristic.getUuid(), characteristic.getInstanceId(), characteristic.getUuid(), characteristic.getInstanceId(),
propertiesToString(characteristic.getProperties()), characteristic.getPermissions()); propertiesToString(characteristic.getProperties()),
writeTypeToString(characteristic.getWriteType()),
characteristic.getPermissions());
byte[] value = characteristic.getValue(); byte[] value = characteristic.getValue();
if (value != null && value.length > 0) { if (value != null && value.length > 0) {
Timber.d("|--> value: %s (%s)", byteInHex(value), Timber.d("|--> value: %s (%s)", byteInHex(value),