1
0
mirror of https://github.com/oliexdev/openScale.git synced 2025-08-18 14:31:23 +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);
throw new Exception("could not find the selected user");
}
Timber.d("Selected user is now %s (%d)",
selectedScaleUser.getUserName(), selectedScaleUser.getId());
return selectedScaleUser;
}
} catch (Exception e) {

View File

@@ -78,10 +78,8 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
private static final UUID SERVICE_CHANGED =
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");
private static final UUID CLIENT_CHARACTERISTICS_CONFIGURATION_SANITAS =
UUID.fromString("00002901-0000-1000-8000-00805F9B34FB");
private static final UUID CUSTOM_SERVICE_1 =
UUID.fromString("0000FFE0-0000-1000-8000-00805F9B34FB");
@@ -156,10 +154,7 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
seenUsers = new TreeSet<>();
// Setup notification
UUID clientCharacteristicsConfiguration = deviceType == DeviceType.SANITAS_SBF70_70
? CLIENT_CHARACTERISTICS_CONFIGURATION_SANITAS
: CLIENT_CHARACTERISTICS_CONFIGURATION_BEURER;
setNotificationOn(CUSTOM_SERVICE_1, CUSTOM_CHARACTERISTIC_WEIGHT, clientCharacteristicsConfiguration);
setNotificationOn(CUSTOM_SERVICE_1, CUSTOM_CHARACTERISTIC_WEIGHT, CLIENT_CHARACTERISTICS_CONFIGURATION);
break;
case 1:
// Say "Hello" to the scale
@@ -486,6 +481,12 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
(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;
}

View File

@@ -56,6 +56,7 @@ public abstract class BluetoothCommunication {
private Handler callbackBtHandler;
private Handler handler;
private static boolean autoConnect = false;
private BluetoothGatt bluetoothGatt;
private boolean connectionEstablished;
private BluetoothGattCallback gattCallback;
@@ -80,7 +81,6 @@ public abstract class BluetoothCommunication {
private Queue<GattObjectValue<BluetoothGattDescriptor>> descriptorRequestQueue;
private Queue<GattObjectValue<BluetoothGattCharacteristic>> characteristicRequestQueue;
private boolean openRequest;
private final Object lock = new Object();
public BluetoothCommunication(Context context)
{
@@ -240,10 +240,8 @@ public abstract class BluetoothCommunication {
* @param btMachineState the machine state that should be set.
*/
protected void setBtMachineState(BT_MACHINE_STATE btMachineState) {
synchronized (lock) {
this.btMachineState = btMachineState;
handleRequests();
}
this.btMachineState = btMachineState;
handleRequests();
}
/**
@@ -254,13 +252,11 @@ public abstract class BluetoothCommunication {
* @param bytes the bytes that should be write
*/
protected void writeBytes(UUID service, UUID characteristic, byte[] bytes) {
synchronized (lock) {
characteristicRequestQueue.add(
new GattObjectValue<>(
bluetoothGatt.getService(service).getCharacteristic(characteristic),
bytes));
handleRequests();
}
characteristicRequestQueue.add(
new GattObjectValue<>(
bluetoothGatt.getService(service).getCharacteristic(characteristic),
bytes));
handleRequests();
}
/**
@@ -301,13 +297,11 @@ public abstract class BluetoothCommunication {
bluetoothGatt.getService(service).getCharacteristic(characteristic);
bluetoothGatt.setCharacteristicNotification(gattCharacteristic, true);
synchronized (lock) {
descriptorRequestQueue.add(
new GattObjectValue<>(
gattCharacteristic.getDescriptor(descriptor),
BluetoothGattDescriptor.ENABLE_INDICATION_VALUE));
handleRequests();
}
descriptorRequestQueue.add(
new GattObjectValue<>(
gattCharacteristic.getDescriptor(descriptor),
BluetoothGattDescriptor.ENABLE_INDICATION_VALUE));
handleRequests();
}
catch (Exception e) {
Timber.e(e);
@@ -328,13 +322,11 @@ public abstract class BluetoothCommunication {
bluetoothGatt.getService(service).getCharacteristic(characteristic);
bluetoothGatt.setCharacteristicNotification(gattCharacteristic, true);
synchronized (lock) {
descriptorRequestQueue.add(
new GattObjectValue<>(
gattCharacteristic.getDescriptor(descriptor),
BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE));
handleRequests();
}
descriptorRequestQueue.add(
new GattObjectValue<>(
gattCharacteristic.getDescriptor(descriptor),
BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE));
handleRequests();
}
catch (Exception e) {
Timber.e(e);
@@ -354,13 +346,11 @@ public abstract class BluetoothCommunication {
bluetoothGatt.getService(service).getCharacteristic(characteristic);
bluetoothGatt.setCharacteristicNotification(gattCharacteristic, false);
synchronized (lock) {
descriptorRequestQueue.add(
new GattObjectValue<>(
gattCharacteristic.getDescriptor(descriptor),
BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE));
handleRequests();
}
descriptorRequestQueue.add(
new GattObjectValue<>(
gattCharacteristic.getDescriptor(descriptor),
BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE));
handleRequests();
}
/**
@@ -417,12 +407,12 @@ public abstract class BluetoothCommunication {
public void connect(String hwAddress) {
logBluetoothStatus();
disconnect(false);
btAdapter.cancelDiscovery();
// 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
btAdapter.cancelDiscovery();
stopLeScan();
// Don't do any cleanup if disconnected before fully connected
btMachineState = BT_MACHINE_STATE.BT_CLEANUP_STATE;
@@ -436,7 +426,7 @@ public abstract class BluetoothCommunication {
}
else {
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) {
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) {
bluetoothGatt = device.connectGatt(
context, false, gattCallback, BluetoothDevice.TRANSPORT_LE);
context, autoConnect, gattCallback, BluetoothDevice.TRANSPORT_LE);
}
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) {
leScanCallback = new BluetoothAdapter.LeScanCallback() {
@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());
if (!device.getAddress().equals(hwAddress)) {
return;
}
synchronized (lock) {
stopLeScan();
connectGatt(device);
}
// Stop timeout and connect to the device on the main thread
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);
}
}
});
}
};
Timber.d("Starting LE scan for device [%s]", hwAddress);
btAdapter.startLeScan(leScanCallback);
// Try to connect to the device directly if the device isn't found in time
handler.postAtTime(new Runnable() {
@Override
public void run() {
Timber.d("Device not found in LE scan, connecting directly");
synchronized (lock) {
stopLeScan();
connectGatt(hwAddress);
}
connectGatt(btAdapter.getRemoteDevice(hwAddress));
}
}, leScanCallback, SystemClock.uptimeMillis() + LE_SCAN_TIMEOUT_MS);
Timber.d("Starting LE scan for device [%s]", hwAddress);
btAdapter.startLeScan(leScanCallback);
}
private void stopLeScan() {
@@ -516,41 +510,38 @@ public abstract class BluetoothCommunication {
* Disconnect from a Bluetooth device
*/
public void disconnect(boolean doCleanup) {
synchronized (lock) {
stopLeScan();
if (bluetoothGatt == null) {
return;
}
Timber.i("Disconnecting%s", doCleanup ? " (with cleanup)" : "");
stopLeScan();
if (bluetoothGatt == null) {
// Could be a pending connectGatt waiting
handler.removeCallbacksAndMessages(null);
callbackBtHandler = null;
return;
}
if (doCleanup) {
if (btMachineState != BT_MACHINE_STATE.BT_CLEANUP_STATE) {
setBtMachineState(BT_MACHINE_STATE.BT_CLEANUP_STATE);
nextMachineStateStep();
}
handler.post(new Runnable() {
@Override
public void run() {
synchronized (lock) {
if (openRequest) {
handler.postDelayed(this, 10);
} else {
bluetoothGatt.close();
bluetoothGatt = null;
}
}
Timber.i("Disconnecting%s", doCleanup ? " (with cleanup)" : "");
handler.removeCallbacksAndMessages(null);
callbackBtHandler = null;
if (doCleanup) {
if (btMachineState != BT_MACHINE_STATE.BT_CLEANUP_STATE) {
setBtMachineState(BT_MACHINE_STATE.BT_CLEANUP_STATE);
nextMachineStateStep();
}
handler.post(new Runnable() {
@Override
public void run() {
if (openRequest) {
handler.postDelayed(this, 10);
} else {
disconnect(false);
}
});
}
else {
bluetoothGatt.close();
bluetoothGatt = null;
}
}
});
}
else {
bluetoothGatt.close();
bluetoothGatt = null;
}
}
@@ -581,49 +572,48 @@ public abstract class BluetoothCommunication {
}
private void handleRequests() {
synchronized (lock) {
// check for pending request
if (openRequest) {
Timber.d("Request pending (queue %d %d)",
descriptorRequestQueue.size(), characteristicRequestQueue.size());
return; // yes, do nothing
}
// handle descriptor requests first
GattObjectValue<BluetoothGattDescriptor> descriptor = descriptorRequestQueue.poll();
if (descriptor != null) {
descriptor.gattObject.setValue(descriptor.value);
Timber.d("Write descriptor %s: %s (queue: %d %d)",
descriptor.gattObject.getUuid(), byteInHex(descriptor.gattObject.getValue()),
descriptorRequestQueue.size(), characteristicRequestQueue.size());
if (!bluetoothGatt.writeDescriptor(descriptor.gattObject)) {
Timber.e("Failed to initiate write of descriptor %s",
descriptor.gattObject.getUuid());
}
openRequest = true;
return;
}
// handle characteristics requests second
GattObjectValue<BluetoothGattCharacteristic> characteristic = characteristicRequestQueue.poll();
if (characteristic != null) {
characteristic.gattObject.setValue(characteristic.value);
Timber.d("Write characteristic %s: %s (queue: %d %d)",
characteristic.gattObject.getUuid(), byteInHex(characteristic.gattObject.getValue()),
descriptorRequestQueue.size(), characteristicRequestQueue.size());
if (!bluetoothGatt.writeCharacteristic(characteristic.gattObject)) {
Timber.e("Failed to initiate write of characteristic %s",
characteristic.gattObject.getUuid());
}
openRequest = true;
return;
}
// After every command was executed, continue with the next step
nextMachineStateStep();
// check for pending request
if (openRequest) {
Timber.d("Request pending (queue %d %d)",
descriptorRequestQueue.size(), characteristicRequestQueue.size());
return; // yes, do nothing
}
// handle descriptor requests first
GattObjectValue<BluetoothGattDescriptor> descriptor = descriptorRequestQueue.poll();
if (descriptor != null) {
descriptor.gattObject.setValue(descriptor.value);
Timber.d("Write descriptor %s: %s (queue: %d %d)",
descriptor.gattObject.getUuid(), byteInHex(descriptor.gattObject.getValue()),
descriptorRequestQueue.size(), characteristicRequestQueue.size());
if (!bluetoothGatt.writeDescriptor(descriptor.gattObject)) {
Timber.e("Failed to initiate write of descriptor %s",
descriptor.gattObject.getUuid());
}
openRequest = true;
return;
}
// handle characteristics requests second
GattObjectValue<BluetoothGattCharacteristic> characteristic = characteristicRequestQueue.poll();
if (characteristic != null) {
characteristic.gattObject.setValue(characteristic.value);
Timber.d("Write characteristic %s: %s (type: %d; queue: %d %d)",
characteristic.gattObject.getUuid(), byteInHex(characteristic.gattObject.getValue()),
characteristic.gattObject.getWriteType(),
descriptorRequestQueue.size(), characteristicRequestQueue.size());
if (!bluetoothGatt.writeCharacteristic(characteristic.gattObject)) {
Timber.e("Failed to initiate write of characteristic %s",
characteristic.gattObject.getUuid());
}
openRequest = true;
return;
}
// After every command was executed, continue with the next step
nextMachineStateStep();
}
/**
@@ -631,38 +621,45 @@ public abstract class BluetoothCommunication {
*/
protected class GattCallback extends BluetoothGattCallback {
@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);
if (newState == BluetoothProfile.STATE_CONNECTED) {
synchronized (lock) {
stopLeScan();
}
handler.post(new Runnable() {
@Override
public void run() {
stopLeScan();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
gatt.readPhy();
}
connectionEstablished = true;
setBtStatus(BT_STATUS_CODE.BT_CONNECTION_ESTABLISHED);
}
});
connectionEstablished = true;
setBtStatus(BT_STATUS_CODE.BT_CONNECTION_ESTABLISHED);
try {
Thread.sleep(1000);
}
catch (Exception e) {
// Empty
}
if (!gatt.discoverServices()) {
Timber.e("Could not start service discovery");
setBtStatus(BT_STATUS_CODE.BT_CONNECTION_LOST);
disconnect(false);
}
// Wait a short while after connecting before scanning for services
handler.postDelayed(new Runnable() {
@Override
public void run() {
if (!gatt.discoverServices()) {
Timber.e("Could not start service discovery");
setBtStatus(BT_STATUS_CODE.BT_CONNECTION_LOST);
disconnect(false);
}
}
}, 1000);
}
else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
setBtStatus(connectionEstablished
? BT_STATUS_CODE.BT_CONNECTION_LOST
: BT_STATUS_CODE.BT_NO_DEVICE_FOUND);
disconnect(false);
handler.post(new Runnable() {
@Override
public void run() {
if (connectionEstablished && status != 0) {
autoConnect = !autoConnect;
}
setBtStatus(connectionEstablished
? BT_STATUS_CODE.BT_CONNECTION_LOST
: BT_STATUS_CODE.BT_NO_DEVICE_FOUND);
disconnect(false);
}
});
}
}
@@ -672,34 +669,35 @@ public abstract class BluetoothCommunication {
status, gatt.getServices().size());
if (gatt.getServices().isEmpty()) {
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "No services found");
disconnect(false);
handler.post(new Runnable() {
@Override
public void run() {
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "No services found");
disconnect(false);
}
});
return;
}
synchronized (lock) {
cmdStepNr = 0;
initStepNr = 0;
cleanupStepNr = 0;
// 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;
initStepNr = 0;
cleanupStepNr = 0;
// Clear from possible previous setups
characteristicRequestQueue = new LinkedList<>();
descriptorRequestQueue = new LinkedList<>();
openRequest = false;
}
// Clear from possible previous setups
characteristicRequestQueue = new LinkedList<>();
descriptorRequestQueue = new LinkedList<>();
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
setBtMachineState(BT_MACHINE_STATE.BT_INIT_STATE);
// Start the state machine
setBtMachineState(BT_MACHINE_STATE.BT_INIT_STATE);
}
}, 1000);
}
@Override
@@ -718,10 +716,8 @@ public abstract class BluetoothCommunication {
handler.postDelayed(new Runnable() {
@Override
public void run() {
synchronized (lock) {
openRequest = false;
handleRequests();
}
openRequest = false;
handleRequests();
}
}, 60);
}
@@ -741,27 +737,33 @@ public abstract class BluetoothCommunication {
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
public void onCharacteristicRead(final BluetoothGatt gatt,
final BluetoothGattCharacteristic characteristic,
final int status) {
Timber.d("onCharacteristicRead %s (status=%d): %s",
characteristic.getUuid(), status, byteInHex(characteristic.getValue()));
synchronized (lock) {
onBluetoothDataRead(gatt, characteristic, status);
postDelayedHandleRequests();
}
handler.post(new Runnable() {
@Override
public void run() {
onBluetoothDataRead(gatt, characteristic, status);
}
});
postDelayedHandleRequests();
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
public void onCharacteristicChanged(final BluetoothGatt gatt,
final BluetoothGattCharacteristic characteristic) {
Timber.d("onCharacteristicChanged %s: %s",
characteristic.getUuid(), byteInHex(characteristic.getValue()));
synchronized (lock) {
onBluetoothDataChange(gatt, characteristic);
}
handler.post(new Runnable() {
@Override
public void run() {
onBluetoothDataChange(gatt, characteristic);
}
});
}
@Override

View File

@@ -74,13 +74,28 @@ public class BluetoothDebug extends BluetoothCommunication {
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) {
Timber.d("Service %s%s", service.getUuid(), included ? " (included)" : "");
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(),
propertiesToString(characteristic.getProperties()), characteristic.getPermissions());
propertiesToString(characteristic.getProperties()),
writeTypeToString(characteristic.getWriteType()),
characteristic.getPermissions());
byte[] value = characteristic.getValue();
if (value != null && value.length > 0) {
Timber.d("|--> value: %s (%s)", byteInHex(value),