Closed syedquadri82 closed 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.
SharedFlow is merged and in Kotlin 1.4-M1 preview release.
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.
@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 */ }
}
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()
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.
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.
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).
Just another 👍 on the use of Flow to handle notifications in this library. Working great for me.
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) } }