Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android BLE: onCharacteristicRead() appears to be blocked by thread

I'm implementing a series of characteristic reads against a BLE device. Because readCharacteristic() executes asynchronously, and because we have to wait until it completes before issuing another "read" call, I used a lock to wait() and then in 'onCharacteristicRead() I notify() the lock to get things going again.

When I wait() after calling readCharacteristic(), I never get a call to onCharacteristicRead(). If I don't wait(), then I do get a call to onCharacteristicRead() and the correct value is reported.

Here is the relevant code that seems to block the callback to onCharacteristicRead():

private void doRead() {
    //....internal accounting stuff up here....
    characteristic = mGatt.getService(mCurrServiceUUID).getCharacteristic(mCurrCharacteristicUUID);
    isReading = mGatt.readCharacteristic(characteristic);

    showToast("Is reading in progress? " + isReading);

    showToast("On thread: " + Thread.currentThread().getName());

    // Wait for read to complete before continuing.
    while (isReading) {
        synchronized (readLock) {
            try {
                readLock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
    showToast("onCharacteristicRead()");
    showToast("On thread: " + Thread.currentThread().getName());

    byte[] value = characteristic.getValue();
    StringBuilder sb = new StringBuilder();
    for (byte b : value) {
        sb.append(String.format("%02X", b));
    }

    showToast("Read characteristic value: " + sb.toString());

    synchronized (readLock) {
        isReading = false;
        readLock.notifyAll();
    }
}

If I simply remove the while() statement above, I successfully get the read callback. Of course, that prevents me from waiting to do further reads, so I can't move forward without waiting.

Given that the readCharacteristic() is asynchronous, why would execution of the calling thread have anything to do with the ability to actually do the read, or the ability to call the callback?

To make things more confusing, I show a toast which identifies the thread when I call readCharacteristic(), as well as when onCharacteristicRead() is invoked. These 2 threads have different names. I thought that maybe the callback was being invoked on the calling thread for some reason, but that doesn't appear to be the case. So what is going on here with the threading?

like image 848
SuperDeclarative Avatar asked Oct 09 '14 00:10

SuperDeclarative


2 Answers

The problem here appears to be an obscure issue with threading and it can't be seen in my original post because I didn't post enough of the call history to see it. I will explain what I found here in case it effects someone else.

The full call history leading to my problem went something like this:

  1. Start Le Scan
  2. Find device I care about
  3. Connect to the device's GATT server (which returns a GATT client, and where I provide a BluetoothGattCallback for all the async communication calls)
  4. Tell the GATT client to discoverServices()
  5. A moment later, the BLE system invokes my callback's onServicesDiscovered()
  6. Now I'm ready to start reading characteristics because the service data is loaded, so this is where I invoke that doRead() method in my original post
  7. Tell the GATT client to readCharacteristic()
  8. Go to sleep until the read is done ---- This is where deadlock occurs, but its supposed to:
  9. A moment later, the BLE system invokes my callbacck's onCharacteristicRead()
  10. Notify all waiting threads
  11. Go back to step 7 and repeat

First Mistake:

Originally my onServicesDiscovered() method looked like this:

public void onServicesDiscovered(final BluetoothGatt gatt, int status) {
    doRead();
}

When doRead() executes, it is going to sleep and therefore block execution. This prevents the callback method from finishing and apparently gunks up the entire BLE communicaiton system.

Second Mistake:

Once I realized the above issue, I changed the method to the following:

public void onServicesDiscovered(final BluetoothGatt gatt, int status) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            doRead();
        }
    ).start();
}

As far as I can tell, the above version of the method should work. I'm creating a new thread on which to run doRead(), so sleeping in doRead() should not have any impact on the BLE thread. But it does! This change had no impact.

----------- Edit Note --------------

After posting this, I really couldn't rationalize why the above anonymous thread wouldn't work. So I tried it again, and this time it did work. Not sure what went wrong the first time, maybe I forgot to call start() on the thread or something...

--------- End Edit Note ------------

The Solution:

Finally, on a whim, I decided to create a background HandlerThread when my class gets instantiated (instead of spinning up an anonymous Thread in onServicesDiscovered()). The method now looks like this:

public void onServicesDiscovered(final BluetoothGatt gatt, int status) {
    mBackgroundHandler.post(new Runnable() {
        @Override
        public void run() {
            doRead();
        }
    ).start();
}

The above version of the method works. The call to doRead() successfully iterates over each characteristic as the previous one is read.

like image 190
SuperDeclarative Avatar answered Oct 17 '22 17:10

SuperDeclarative


I have solved this problem by adding the following code

        @Override
    public void onCharacteristicWrite(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, int status) {
        super.onCharacteristicWrite(gatt, characteristic, status);
        new Thread(new Runnable() {
            @Override
            public void run() {
                gatt.readCharacteristic(characteristic);
            }
        }).start();
    }
like image 2
eyal Avatar answered Oct 17 '22 19:10

eyal