2

I appreciate that this may have already been answered but I'm unable to find a solution that works for me.

Tl;dr: How do make a function block?

I have the following BLE-related code written in Kotlin for Android API 28.

override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {

    for (gattService: BluetoothGattService in gatt!!.services) {

        for (gattChar: BluetoothGattCharacteristic in gattService.characteristics) {

                if (gattChar.uuid.toString().contains(ADC_SAMPLESET_0) && !subscribed_0) {

                    subscribed_0 = true

                    gatt.setCharacteristicNotification(gattChar, true)                   

                    val descriptor = gattChar.getDescriptor(
                            UUID.fromString(BleNamesResolver.CLIENT_CHARACTERISTIC_CONFIG)
                    )
                    descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
                    gatt.writeDescriptor(descriptor)
                }

The if-statement above is repeated multiple times to facilitate subscription to multiple BLE characteristics. Unfortunately, the gatt.writeDescriptor() function runs asynchronously. I need to wait for it to return before calling gatt.writeDescriptor() for the next characteristic. How do I achieve this?

I've tried using runBlocking and GlobalScope.launch in kotlinx.coroutines.experimental.* but I'm not entirely sure that they're the right thing.

Thanks, Adam

amitchone
  • 1,630
  • 3
  • 21
  • 45

2 Answers2

1

The onDescriptorWrite() method might be helpful. You should already be overriding it.

Try the following:

private var canContinue = false;

override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { //gatt shouldn't be null, so the null-safe ? isn't needed
    loopAsync(gatt);
}

override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
    canContinue = true; //allow the loop to continue once a descriptor is written
}

private fun loopAsync(gatt: BluetoothGatt) {
    async { //Run it async
        gatt.services.forEach { gattService -> //Kotlin has a handy Collections.forEach() extension function
            gattService.characteristics.forEach { gattChar -> //Same for this one
                if (gattChar.uuid.toString().contains(ADC_SAMPLESET_0) && !subscribed_0) {
                    subscribed_0 = true

                    gatt.setCharacteristicNotification(gattChar, true)

                    val descriptor = gattChar.getDescriptor(
                            UUID.fromString(BleNamesResolver.CLIENT_CHARACTERISTIC_CONFIG)
                    }
                    descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
                    gatt.writeDescriptor(descriptor)
                    while(!canContinue); //wait until canContinue becomes true and then continue
                }
            }
        }
    }
}

This is a little hacky. There is probably a way to do this with recursion, but the nested for loops make that tricky.

TheWanderer
  • 16,775
  • 6
  • 49
  • 63
  • 1
    On an unrelated note, not currently having access to an IDE means StackOverflow is great practice for manual code writing. – TheWanderer Sep 26 '18 at 17:02
  • Thanks for taking the time to write this, with a bit of adjustment it appears that this is the way forward! When I've got the code fully working I'll edit my question with an update. Had to stick the `async` in a `coroutineScope` and then called `loopAsync` within a `runBlocking`. Lastly, I had to turn `loopAsync` into a `suspend` function. And then a small adjustment to some of the logic to account for multiple characteristics and we're away. – amitchone Sep 27 '18 at 09:25
  • Further to this, now the `onDescriptorWrite` callback is never actually called, despite verifying that the code runs past that `gatt.writeDescriptor` function... – amitchone Sep 27 '18 at 09:37
  • Unfortunately that seems not to work. Using this answer: https://stackoverflow.com/questions/11123621/running-code-in-main-thread-from-another-thread?answertab=active#tab-top – amitchone Sep 27 '18 at 09:51
  • @AdamMitchell I'm not sure then. Test on another device maybe? – TheWanderer Sep 27 '18 at 09:52
  • I'll keep experimenting and let you know the result. Thanks for taking the time to provide the suggestions! – amitchone Sep 27 '18 at 09:54
  • Finally got there. Had to run the `gatt.writeDescriptor()` **and** `while(!canContinue);` in the main thread! – amitchone Sep 27 '18 at 11:42
1

It's not really a question of Kotlin. BluetoothGatt is an Async API with callbacks (as is often true of Bluetooth, because of the nature of it), and you can't easily use language features to hide that aspect of it.

It's probably possible to write a facade on top of BluetoothGatt that is blocking, but doing a good job of it would be a fair amount of work, and I wouldn't really recommend it.

Geoffrey Wiseman
  • 5,459
  • 3
  • 34
  • 52
  • Thanks for the info, how would this usually be achieved then? It's not uncommon to automatically subscribe to notifications of multiple characteristics but it seems that this is beyond the scope of what the BLE API can do? – amitchone Sep 27 '18 at 09:15