Skip to content
Able: Android Bluetooth Low Energy library
Kotlin Shell
Branch: master
Clone or download

README.md

codecov

Able

Provides a Kotlin Coroutines powered interaction with Android's Bluetooth Low Energy (BLE) framework.

See Recipes page for usage examples.

API

When feasible, the API closely matches the Android Bluetooth Low Energy API, replacing methods that traditionally rely on BluetoothGattCallback calls with suspension functions.

Android BluetoothDevice Able Device
fun connectGatt(
    context: Context,
    autoConnect: Boolean,
    callback: BluetoothGattCallback
): BluetoothGatt
suspend fun connectGatt(
    context: Context,
    autoConnect: Boolean = false
): ConnectGattResult1

1 Suspends until STATE_CONNECTED or non-GATT_SUCCESS is received, then returns ConnectGattResult:

sealed class ConnectGattResult {
    data class Success(val gatt: Gatt) : ConnectGattResult()
    data class Canceled(val cause: CancellationException) : ConnectGattResult()
    data class Failure(val cause: Throwable) : ConnectGattResult()
}
Android BluetoothGatt Able Gatt
fun connect(): Boolean
suspend fun connect(): Boolean1
fun disconnect(): Boolean
suspend fun disconnect(): Unit2
fun discoverServices(): Boolean
suspend fun discoverServices(): GattStatus3
fun getServices(): List
val services: List
fun getService(uuid: UUID): BluetoothGattService
fun getService(uuid: UUID): BluetoothGattService
fun readCharacteristic(
    characteristic: BluetoothGattCharacteristic
): Boolean
suspend fun readCharacteristic(
    characteristic: BluetoothGattCharacteristic
): OnCharacteristicRead3
fun writeCharacteristic(
    characteristic: BluetoothGattCharacteristic
): Boolean
suspend fun writeCharacteristic(
    characteristic: BluetoothGattCharacteristic,
    value: ByteArray,
    writeType: WriteType
): OnCharacteristicWrite3
fun writeDescriptor(
    descriptor: BluetoothGattDescriptor
): Boolean
suspend fun writeDescriptor(
    descriptor: BluetoothGattDescriptor,
    value: ByteArray
): OnDescriptorWrite3
fun requestMtu(mtu: Int): Boolean
suspend fun requestMtu(mtu: Int): OnMtuChanged3
fun setCharacteristicNotification(
    characteristic: BluetoothGattCharacteristic,
    enable: Boolean
): Boolean
fun setCharacteristicNotification(
    characteristic: BluetoothGattCharacteristic,
    enable: Boolean
): Boolean

1 Suspends until STATE_CONNECTED or non-GATT_SUCCESS is received.
2 Suspends until STATE_DISCONNECTED or non-GATT_SUCCESS is received.
3 Throws RemoteException if underlying BluetoothGatt call returns false.
3 Throws GattClosed if Gatt is closed while method is executing.
3 Throws GattConnectionLost if Gatt is disconnects while method is executing.

Details

The primary entry point is the BluetoothDevice.connectGatt(context: Context, autoConnect: Boolean): ConnectGattResult extension function. This extension function acts as a replacement for Android's BluetoothDevice.connectGatt(context: Context, autoConnect: Boolean, callback: BluetoothCallback): BluetoothGatt? method (which relies on a BluetoothGattCallback).

Prerequisites

Able expects that Android Bluetooth Low Energy is supported (BluetoothAdapter.getDefaultAdapter() returns non-null) and usage prerequisites (e.g. bluetooth permissions) are satisfied prior to use; failing to do so will result in RemoteException for most Able methods.

Structured Concurrency

Kotlin Coroutines 0.26.0 introduced structured concurrency.

When establishing a connection (e.g. via BluetoothDevice.connectGatt(context: Context, autoConnect: Boolean): ConnectGattResult extension function), if the Coroutine is cancelled then the in-flight connection attempt will be cancelled and corresponding BluetoothGatt will be closed:

fun connect(context: Context, device: BluetoothDevice) {
    val deferred = async {
        device.connectGatt(context, autoConnect = false)
    }

    launch {
        delay(1000L) // Assume, for this example, that BLE connection takes more than 1 second.

        // Cancels the `async` Coroutine and automatically closes the underlying `BluetoothGatt`.
        deferred.cancel()
    }

    val result = deferred.await() // `result` will be `ConnectGattResult.Canceled`.
}

Note that in the above example, if the BLE connection takes less than 1 second, then the established connection will not be cancelled (and Gatt will not be closed), and result will be ConnectGattResult.Success.

Gatt Coroutine Scope

Able's Gatt provides a CoroutineScope, allowing any Coroutine builders to be scoped to the Gatt instance. For example, you can continually read a characteristic and the Coroutine will automatically cancel when the Gatt is closed (error handling omitted for simplicity):

fun continuallyReadCharacteristic(gatt: Gatt, serviceUuid: UUID, characteristicUuid: UUID) {
    val characteristic = gatt.getService(serviceUuid)!!.getCharacteristic(characteristicUuid)!!

    // This Coroutine will automatically cancel when `gatt.close()` is called.
    gatt.launch {
        while (isActive) {
            println("value = ${gatt.readCharacteristic(characteristic).value}")
        }
    }
}

Setup

Gradle

To use Able in your Android project, setup your build.gradle as follows:

dependencies {
    implementation "com.juul.able:core:0.7.1"
}

Packages

Able provides a number of packages to help extend it's functionality:

Package Functionality
processor A Processor adds the ability to process (and optionally modify) GATT data
pre-write or post-read.
retry Retry wraps a Gatt to add I/O retry functionality and on-demand connection
establishment.
throw Adds extension functions that throw exceptions on failures for various BLE
operations.
timber-logger Routes Able logging through Timber.
device Provides BluetoothDevice extension functions as a single access point for
connectivity and communication.

License

Copyright 2018 JUUL Labs

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
You can’t perform that action at this time.