AbleLib allows you to set up and run a local GATT server, allowing you device to behave as if it was a BLE peripheral. What's even better, you can set your services and characteristics declaratively, making them easy to write and maintan. We'll call the block of code that contains the server specification server builder, and you'll add more features to it as the guide progresses.
The server described here is the same one as in our demo apps (Android and iOS). It's meant to manage an L2CAP channel for a server socket.
Use AbleManager
's startGattServer
to obtain a new AbleGattServer
instance and start it up. As its parameter, pass the server builder block that contains:
// Store the UUIDs for services, characteristics and descriptors for later use
val serviceId = AbleUUID("12E61727-B41A-436F-B64D-4777B35F2294")
val charId = AbleUUID("ABDD3056-28FA-441D-A470-55A75A52553A")
val descId = AbleUUID("00002902-0000-1000-8000-00805f9b34fb")
var char: AbleCharacteristic
var myPsm: AblePSM
// If you plan on opening the server multiple times, it's best to store the builder
// in a local variable.
val serverBuilder: AbleGattServerBuilder = {
// define a service with its UUID, type and builder block
service(serviceId, AbleService.Type.PRIMARY) {
// define and store characteristic with its UUID, properties,
// permissions and builder block
char = characteristic(charId,
setOf(AbleCharacteristic.Property.READ, AbleCharacteristic.Property.INDICATE),
setOf(AbleCharacteristic.Permission.READ)) {
// define descriptor with its UUID and permissions
descriptor(descId, setOf(AbleCharacteristic.Permission.READ, AbleCharacteristic.Permission.WRITE))
}
}
// the server will manage connected devices automatically
autoManageDevices()
// define what happens when characteristic with 'charId' is read
onReadCharacteristic(charId) { request ->
myPsm?.let { psm ->
val data = psm.getData()
request.characteristic.value = data
respondTo(request.withValue(data), AbleGattServer.Status.SUCCESS)
}
}
// track connection changes and notify connected peripherals
onConnectionStateChanged { device, state, i ->
if (state == AbleGattConnectionState.CONNECTED) {
char?.let { char ->
myPsm?.let { psm ->
notifyCharacteristicChanged(
device,
char,
psm.getData(),
false
)
}
}
}
}
// define what happens when descriptor with 'descId' is written to
onWriteDescriptor(descId) { request, _, _ ->
respondTo(request, AbleGattServer.Status.SUCCESS)
}
}
val server = AbleManager.shared.startGattServer(serverBuilder)
Here's a table of all the components you can put in the server builder block:
Android server builder is a block with the server instance set as its receiver, so you're effectively just invoking methods on the server instance.
Component | Description |
---|---|
service |
Add a service (and, through it, characteristics and descriptors). |
autoManageDevices |
Should the server manage connected devices automatically. |
onConnectionStateChanged |
Callback for when a peripheral connects or disconnects. |
onWriteCharacteristic |
Callback for when the listed characteristics are written to. You can have as many of these for any number of characteristics. |
onWriteDescriptor |
Callback for when the listed descriptors are written to. You can have as many of these for any number of descriptors. |
onReadCharacteristic |
Callback for when the listed characteristics are read. You can have as many of these for any number of characteristics. |
onReadDescriptor |
Callback for when the listed descriptors are read. You can have as many of these for any number of descriptors. |
onWriteAnyCharacteristic |
Callback for when a characteristic not covered by an onWriteCharacteristic is written to. |
onReadAnyCharacteristic |
Callback for when a characteristic not covered by an onReadCharacteristic is read. |
onWriteAnyDescriptor |
Callback for when a descriptor not covered by an onWriteDescriptor is written to. |
onReadAnyDescriptor |
Callback for when a descriptor not covered by an onReadDescriptor is read. |
Once your server is open, make sure to turn on BLE advertising to let other devices detect and connect to it.
CoroutineScope(Dispatchers.IO).launch {
AbleManager.shared.startAdvertising(
AdvertiseSettings.Builder()
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED)
.setConnectable(true)
.setTimeout(0)
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
.build(),
AdvertiseData.Builder()
.setIncludeDeviceName(false)
.setIncludeTxPowerLevel(false)
.addServiceUuid(ParcelUuid(serviceId.toUUID))
.build())
}
Of course, you can stop advertising at any time:
There are two ways for a server to communicate with its connected peripherals - responding to requests and notifying characteristic changes.
respondTo
/respond(to:status:)
method and providing the request and its status:onReadCharacteristic(charId) { request ->
val data = psm.getData()
request.characteristic.value = data // update the requests's value
respondTo(request.withValue(data), AbleGattServer.Status.SUCCESS)
}
AbleCharacteristic
.You need to provide the connected AbleDevice
to respond to, the characteristic, data and if the response is a notification or an indication:
notifyCharacteristicChanged(device, char, psm.getData(), false)
After you're done using your server, be sure to close it: