androidbluetoothbluetooth-lowenergygattandroid-5.1.1-lollipop

Multiple writeDescriptor() calls fail on Android 5.1


I have an application which communicates with a Bluetooth Low Energy Glucometer.

Currently my application is working OK on Android 4.4.4, but fails on 5.1.1

Debugging I've found that the writeDescriptor() method fails when executed more than once...

With the debugger I stepped into the writeDescriptor() method and found that it fails on this line:

    public boolean writeDescriptor(BluetoothGattDescriptor descriptor) {
    ...
    synchronized(mDeviceBusy) {
        if (mDeviceBusy) return false;
        mDeviceBusy = true;
    }

(BluetoothGatt.java:1029)

I've tried to wait for the onDescriptorWrite() callback but it is never called, and I've also tried waiting (100 ms, 500ms and 2s) to do the second write, in order to see if the mDeviceBusy variable was cleared... Both attempts failed...

I googled the issue and could not find anything on it, the closest thing was this unanswered question: Android SensorTag: writeDescriptor failed

Note: I can attach my code if necessary, but it is very simple and similar to the code on this post: Android BLE notifications for Glucose


Solution

  • I found the problem... In my code I had defined the onDescriptorWrite() callback incorrectly. After fixing this I managed to receive callbacks when writing the descriptor succeeded.

    With that solved, the solution I found is to make BTLE operations blocking by creating a class in charge of sequencing the operations.

    The class I created is pretty simple, a BlockingQueue on a separate thread to enqueue incoming operations.

    BTLE operations are enqueued by calling writeDescriptor(), writeCharacteristic() methods,a separate thread starts executing the operations when free, and the gattCallback informs the thread when operations have been completed, in order to proceed with the next operation.

    Queue class:

    public BtleAsynchronousOperationThread(SensorAbstract sensor, BluetoothGatt gatt) {
    
        log("Creating Btle Operation thread");
        this.gatt = gatt;
        this.sensor = sensor;
    }
    
    public void enable() {
        log("Enabling btle command thread");
        queueReadySemaphore.release();
    }
    
    public void writeDescriptor(BluetoothGattDescriptor descriptor) throws BTCommunicationException {
    
        BtleAsynchronousCommand command = new BtleAsynchronousCommand(descriptor);
        try {
            queue.put(command);
        } catch (InterruptedException e) {
            throw new BTCommunicationException("Error while writing the descriptor");
        }
    }
    
    public void writeCharacteristic(BluetoothGattCharacteristic characteristic) throws BTCommunicationException {
    
        BtleAsynchronousCommand command = new BtleAsynchronousCommand(
                BtleAsynchronousCommand.CommandType.WRITE_CHARACTERISTIC, characteristic);
        try {
            queue.put(command);
        } catch (InterruptedException e) {
            throw new BTCommunicationException("Error while writing the characteristic");
        }
    }
    
    public void readCharacteristic(BluetoothGattCharacteristic characteristic) throws BTCommunicationException {
    
        BtleAsynchronousCommand command = new BtleAsynchronousCommand(
                BtleAsynchronousCommand.CommandType.READ_CHARACTERISTIC, characteristic);
        try {
            queue.put(command);
        } catch (InterruptedException e) {
            throw new BTCommunicationException("Error while reading the characteristic");
        }
    }
    
    public void cancel() {
        this.interrupt();
    }
    
    public void writeCompleted() {
        queueReadySemaphore.release();
    }
    
    @Override
    public void run() {
        BtleAsynchronousCommand command;
    
        try {
            while (!(Thread.currentThread().isInterrupted())) {
    
                queueReadySemaphore.acquire();
    
                log("Waiting for BTLE command");
    
                command = queue.take();
    
                switch (command.getCommandType()) {
                case WRITE_DESCRIPTOR:
                    log("Starting to write descriptor:" + command.getDescriptor().getUuid());
                    if (!gatt.writeDescriptor(command.getDescriptor())) {
                        throw new BTCommunicationException("Error while writing the descriptor");
                    }
    
                    break;
                case WRITE_CHARACTERISTIC:
                    log("Starting to write characteristic:" + command.getCharacteristic().getUuid());
                    if (!gatt.writeCharacteristic(command.getCharacteristic())) {
                        throw new BTCommunicationException("Error while writing the characteristic");
                    }
    
                    break;
                case READ_CHARACTERISTIC:
                    log("Starting to read characteristic:" + command.getCharacteristic().getUuid());
                    if (!gatt.readCharacteristic(command.getCharacteristic())) {
                        throw new BTCommunicationException("Error while writing the characteristic");
                    }
    
                    break;
                default:
                    log("Unknown command received");
                    break;
                }
            }
    
        } catch (InterruptedException e) {
            log("Btle thread interrupted, closing.");
        } catch (BTCommunicationException e) {
            log("Error while reading:" + e.getMessage());
            sensor.retry();
        }
    
        gatt = null;
        queue = null;
        queueReadySemaphore = null;
        Thread.currentThread().interrupt();
    }
    }
    

    When defining the BluetoothCallback:

    mGattCallback = new BluetoothGattCallback() {
    
    //Other methods can be defined around here, such as
    //onConnectionStateChange(), etc
    
    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt,
                                        BluetoothGattCharacteristic characteristic) {
        onSensorCharacteristicChangedInner(gatt, characteristic);
    }
    
    @Override
    public void onDescriptorWrite (BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status){
        log("onDescriptorWrite:"+descriptor.getUuid()+ " status:"+status);
        btleCommunicationThread.writeCompleted();
    }
    
    @Override
    public void onCharacteristicWrite(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic, int status) {
        log("onCharacteristicWrite:"+characteristic.getUuid()+ " status:"+status);
        btleCommunicationThread.writeCompleted();
    }