The library simplifies usage of Android Bluetooth Low Energy on Android. It is a wrapper around native API and uses Kotlin Coroutines for asynchronous operations. The usage is designed to be more natural according to the BLE specification.
This module contains a scanner class which provides the list of available Bluetooth LE devices. Each device is kept in an aggregator which keeps devices in map together with their scan records. Scanning works as long as a Flow has an attached consumer. After the Flow is closed the scanning stops.
//Create aggregator which will concat scan records with a device
val aggregator = BleScanResultAggregator()
BleScanner(context).scan()
.map { aggregator.aggregateDevices(it) } //Add new device and return an aggregated list
.onEach { _devices.value = it } //Propagated state to UI
.launchIn(viewModelScope) //Scanning will stop after we leave the screen
implementation 'no.nordicsemi.android.kotlin.ble:scanner:1.0.5'
This module is responsible for handling connection between the phone and the BLE device. It uses Kotlin Coroutines instead of JAVA callbacks to handle asynchronous requests.
Below example is based on the Blinky profile.
Connection to the Blinky DK may look like that:
viewModelScope.launch {
//Connect a Bluetooth LE device. This is a suspend function which waits until device is in conncted state.
val connection = blinkyDevice.connect(context) //blinkyDevice from scanner
//Discover services on the Bluetooth LE Device. This is a suspend function which waits until device discovery is finished.
val services = connection.discoverServices()
//Remember needed service and characteristics which are used to communicate with the DK.
val service = services.findService(BlinkySpecifications.UUID_SERVICE_DEVICE)!!
ledCharacteristic = service.findCharacteristic(BlinkySpecifications.UUID_LED_CHAR)!!
buttonCharacteristic = service.findCharacteristic(BlinkySpecifications.UUID_BUTTON_CHAR)!!
//Observe button characteristic which detects when a button is pressed
//getNotifications() is a suspend function which waits until notification is enabled.
buttonCharacteristic.getNotifications().onEach {
//_state is a MutableStateFlow which propagates data to UI.
_state.value = _state.value.copy(isButtonPressed = BlinkyButtonParser.isButtonPressed(it))
}.launchIn(viewModelScope)
//Check the initial state of the Led. Read() is a suspend function which waits until the value is read from the DK.
val isLedOn = BlinkyLedParser.isLedOn(ledCharacteristic.read())
_state.value = _state.value.copy(isLedOn = isLedOn)
}
Turning on/off a LED light can looks like that:
viewModelScope.launch {
if (state.value.isLedOn) {
//Write is a suspend function which waits for the operation to finish.
ledCharacteristic.write(byteArrayOf(0x00))
//No exception means that writing was a success. We can update the UI.
_state.value = _state.value.copy(isLedOn = false)
} else {
//Write is a suspend function which waits for the operation to finish.
ledCharacteristic.write(byteArrayOf(0x01))
//No exception means that writing was a success. We can update the UI.
_state.value = _state.value.copy(isLedOn = true)
}
}
implementation 'no.nordicsemi.android.kotlin.ble:client:1.0.5'
The library is used to advertise the server.
val advertiser = BleAdvertiser.create(context)
val advertiserConfig = BleAdvertiseConfig(
settings = BleAdvertiseSettings(
deviceName = "My Server" // Advertise a device name
),
advertiseData = BleAdvertiseData(
ParcelUuid(BlinkySpecifications.UUID_SERVICE_DEVICE) //Advertise main service uuid.
)
)
viewModelScope.launch {
advertiser.advertise(advertiserConfig) //Start advertising
.cancellable()
.catch { it.printStackTrace() }
.collect { //Observe advertiser lifecycle events
if (it is OnAdvertisingSetStarted) { //Handle advertising start event
_state.value = _state.value.copy(isAdvertising = true)
}
if (it is OnAdvertisingSetStopped) { //Handle advertising stop event
_state.value = _state.value.copy(isAdvertising = false)
}
}
}
implementation 'no.nordicsemi.android.kotlin.ble:advertiser:1.0.5'
The library is used to create a Bluetooth LE server.
viewModelScope.launch {
//Define led characteristic
val ledCharacteristic = BleServerGattCharacteristicConfig(
BlinkySpecifications.UUID_LED_CHAR,
listOf(BleGattProperty.PROPERTY_READ, BleGattProperty.PROPERTY_WRITE),
listOf(BleGattPermission.PERMISSION_READ, BleGattPermission.PERMISSION_WRITE)
)
//Define button characteristic
val buttonCharacteristic = BleServerGattCharacteristicConfig(
BlinkySpecifications.UUID_BUTTON_CHAR,
listOf(BleGattProperty.PROPERTY_READ, BleGattProperty.PROPERTY_NOTIFY),
listOf(BleGattPermission.PERMISSION_READ, BleGattPermission.PERMISSION_WRITE)
)
//Put led and button characteristics inside a service
val serviceConfig = BleServerGattServiceConfig(
BlinkySpecifications.UUID_SERVICE_DEVICE,
BleGattServerServiceType.SERVICE_TYPE_PRIMARY,
listOf(ledCharacteristic, buttonCharacteristic)
)
val server = BleGattServer.create(context, serviceConfig)
}
server.onNewConnection
.onEach { setUpServices(it) }
.launchIn(viewModelScope)
private fun setUpServices(services: BleGattServerService) {
val ledCharacteristic = services.findCharacteristic(BlinkySpecifications.UUID_LED_CHAR)!!
val buttonCharacteristic = services.findCharacteristic(BlinkySpecifications.UUID_BUTTON_CHAR)!!
ledCharacteristic.value.onEach {
_state.value = _state.value.copy(isLedOn = !it.contentEquals(byteArrayOf(0x00)))
}.launchIn(viewModelScope)
buttonCharacteristic.value.onEach {
_state.value = _state.value.copy(isButtonPressed = !it.contentEquals(byteArrayOf(0x00)))
}.launchIn(viewModelScope)
this.ledCharacteristic = ledCharacteristic
this.buttonCharacteristic = buttonCharacteristic
}
fun onButtonPressedChanged(isButtonPressed: Boolean) {
val value = if (isButtonPressed) {
byteArrayOf(0x01)
} else {
byteArrayOf(0x00)
}
buttonCharacteristic.setValue(value)
}
implementation 'no.nordicsemi.android.kotlin.ble:server:1.0.5'