JuulLabs / able

Able: Android Bluetooth Low Energy library
Apache License 2.0
159 stars 11 forks source link

sample code to subscribe to notifcations #79

Closed syedquadri82 closed 3 years ago

syedquadri82 commented 4 years ago

perhaps a sample code to subscribe to notifications would be handy and helpful for newbies...

i'm struggling to subscribe to onCharacteristicChanged. I have already enabled notifications on my device using the below code, however can't seem to figure out how to subscribe to notification using code.

gatt.setCharacteristicNotification(characteristicTx, true) for (des in characteristicTx.descriptors) { if (null != des) { gatt.writeDescriptor(des, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) } }

twyatt commented 4 years ago

Apologies, I missed this issue earlier.

Subscribing to notifications with Android Bluetooth Low-Energy is complicated and Able could probably provide some functionality to simplify it.

We're currently waiting for SharedFlow to be released and will be updating Able to emit characteristic changes over a SharedFlow. At which time we'll evaluate ways to simplify characteristic notification subscription.

Thanks for the suggestion, we'll add details to the documentation as well.

robbym commented 3 years ago

SharedFlow is merged and in Kotlin 1.4-M1 preview release.

twyatt commented 3 years ago

Thanks for following up. Been heavily focused on https://github.com/JuulLabs/kable (multiplatform BLE) at the moment; which the hope is it will ultimately replace Able.

I'll push a release of Able soon, though I worry that the API has changed significantly and it will be a bit jarring for users of Able to upgrade from 0.7.x, then shortly after migrate to Kable. On the flip side, the latest commits of Able will have SharedFlow so it is a progression towards what Kable will have out-of-the-gate.

twyatt commented 3 years ago

@robbym can you try the 0.8.0-SNAPSHOT, and let me know if it works as expected?

repositories {
    maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
}

dependencies {
    implementation "com.juul.able:core:0.8.0-SNAPSHOT"
}

For observations, here is a rough example:

val serviceUuid = "F000AA80-0451-4000-B000-000000000000".toUuid()
val characteristicUuid = "F000AA83-0451-4000-B000-000000000000".toUuid()
val clientCharacteristicConfigUuid = "00002902-0000-1000-8000-00805f9b34fb".toUuid()

fun connect(context: Context, device: BluetoothDevice) = launch {
    val gatt = device.connectGatt(context, autoConnect = false).let { result ->
        when (result) {
            is Success -> result.gatt
            is Canceled -> throw IllegalStateException("Connection canceled.", result.cause)
            is Failure -> throw IllegalStateException("Connection failed.", result.cause)
        }
    }

    if (gatt.discoverServices() != BluetoothGatt.GATT_SUCCESS) {
        // discover services failed
    }

    val characteristic = gatt.getService(serviceUuid).getCharacteristic(characteristicUuid)
    gatt.setCharacteristicNotification(characteristic, true)

    val descriptor = gatt
        .getService(serviceUuid)
        .getCharacteristic(characteristicUuid)
        .getDescriptor(clientCharacteristicConfigUuid)
    gatt.writeDescriptor(descriptor, ENABLE_NOTIFICATION_VALUE)

    gatt.onCharacteristicChanged.collect { /* todo: handle characteristic changes */ }
}
twyatt commented 3 years ago

As a fun/basic preview of Kable, an observation in Kable will look like:

val peripheral: Peripheral // assumed to have been acquired from a scan

val characteristic = characteristicOf(
    serviceUuid = "F000AA80-0451-4000-B000-000000000000",
    characteristicUuid = "F000AA83-0451-4000-B000-000000000000"
)

// observations can be configured prior to connecting, the Flow will remain active on disconnect and pick up where it left off on reconnect
peripheral.observe(characteristic).onEach { bytes ->
    /* todo: handle characteristic changes */
}.launchIn(scope)

peripheral.connect()
robbym commented 3 years ago

Thanks for taking the time to implement this. I tested the snapshot and it seemed to work great. I ran into an issue with wrapping the gatt.onCharacteristicChanged.collect in a withTimeout block causing a AbstractMethodError, but I don't think its your fault. Might be related to https://github.com/Kotlin/kotlinx.coroutines/issues/2307

We are planning on using KMM for our next mobile application, so we'll be sure to check out Kable. Hopefully we can contribute to the project as well.

twyatt commented 3 years ago

Thanks for testing the snapshot.

I'm going to change the events Flow to be listener based, as I had made incorrect assumptions about Flow behavior (https://github.com/Kotlin/kotlinx.coroutines/issues/2034#issuecomment-718244451).

A listener approach for events will exhibit the desired behavior (i.e. allowing library users to "pause", i.e. suspend, things such as the connection process, to perform custom actions on specific events).

After I make that change I'll publish the 0.8.0 release.

twyatt commented 3 years ago

Ended up not going with the listener approach. Weighed the tradeoffs and the event Flow approach stood out as the better option in my opinion.

Namely, exception handling is significantly simpler with an event Flow as apposed to a listener based approach. The downside to an event Flow being that it won't suspend ("pause") the connect process for a Connected event, so a library consumer can't easily hook into the connection handling process directly. For library users that do need a sequence of events to occur on connect, then they can still be performed when collecting the Connected event, but an external trigger will have to be implemented (e.g. a secondary Flow is one possible option).

subvertallchris commented 3 years ago

Just another 👍 on the use of Flow to handle notifications in this library. Working great for me.