Reactive Bluetooth Low Energy peripheral mode on Android.
Handling Bluetooth Low Energy on Android involves working with a lot of callbacks. Starting from connection state changes to write requests and read requests, everything is a callback with a reference back to it's original source (for example the Descriptor that the client is trying to read).
This library is an approach at stepping away from callbacks towards a
Reactive approach. Wrappers around Android's default BLE components
encapsule their behavior and manage their state, while exposing it via
Observables.
Starting with Observables monitoring the connected clients, to clients
trying to write onto Descriptors or Characteristics, all of this can
easily be handled by utilizing RxJava.
The BLE stack for peripheral devices contains certain layers that need
to be available. On the bottom, there's the Server. Each Server can
contain multiple Services, which can be discovered by possible
clients. Services can host certain Characteristics, which are the
I/O channel the GATT service offers. Characteristics have certain
properties and permissions, allowing clients to either read, write or
both from and onto them. Characteristics can contain Descriptors,
which should - as their name suggests - describe the data the
Characteristic might contain.
Creating the server is straight-forward. All it requires, is the
application's Context. The application also needs to obtain the
Bluetooth permission in its Manifest.
val server = RxBleGattServer(context)
val service = server.addService(serviceUuid, RxBleService.Type.PRIMARY) {
/**
* Configure service
*/
addCharacteristics(...) { ... }
}
val advertising = server.advertiser.apply {
data = AdvertiseData.Builder()
.setIncludeDeviceName(true)
.setIncludeTxPowerLevel(true)
.build()
settings = AdvertiseSettings.Builder()
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
.setConnectable(true)
.setTimeout(0)
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
.build()
}.start()
val service = server.addService(UUID.randomUUID(), RxBleService.Type.PRIMARY) {
addCharacteristic {
setUuid(UUID.randomUUID())
setProperties(BluetoothGattCharacteristic.PROPERTY_BROADCAST)
setPermissions(BluetoothGattCharacteristic.PERMISSION_WRITE)
// add notification descriptor to characteristic
enableNotificationSubscription()
}
}
val service = server.addService(UUID.randomUUID(), RxBleService.Type.PRIMARY) {
addCharacteristic {
...
addDescriptor {
setUuid(UUID.randomUUID())
setPermissions(BluetoothGattDescriptor.PERMISSION_READ)
}
}
}
Connecting the dots in a reactive way.
val serverDisposable = server.start()
.andThen(advertising)
.subscribe({
println("Server online and advertising initialized!")
}, {
println("Something went wrong: $it")
})
We're observing the client with the address 1.2.3.4
The client wants to write information onto our characteristic with the
UUID == UUID.randomUUID().
Each write request the client executes, we'll send a response
mirroring the write request with a success status code.
Afterwards, we'll parse the received ByteArray with an instance of
our GogoParser, which contains a byte buffer in case the client's
mtu was too low to receive the complete message at once.
Afterwards, we'll subscribe to the emitted parsed messages.
val device = server.devices()
.filter { it.device.address == "1.2.3.4" }
.onCharacteristicWriteRequest { it.characteristic.uuid == UUID.randomUUID() }
.respondIfRequired {
RxBleResponse(it.device, it.requestId, BluetoothGatt.GATT_SUCCESS, it.offset, it.value)
}
.parseWith(GogoParser())
.subscribe({ message ->
}, { error ->
println("Something went wrong...")
})
char.observeWriteRequests()
.respondIfRequired {
RxBleResponse(it.device, it.requestId, BluetoothGatt.GATT_SUCCESS, it.offset, it.value)
}
.parseWith(GogoParser())
.subscribe({ message ->
}, {
})
This software is released under the Apache License v2.