At Drivy, we want to enable users to open the car even if it’s on the bottom floor of the deepest, underground parking. Since we can’t rely on a GSM connection when so deep underground, we need to use a Bluetooth connection.
But communicating with a Bluetooth device is easier said than done, due to the fact that it’s low-level and requires many asynchronous calls. Let’s see how we can improve this.
Bluetooth communication is not exactly like HTTP communication. We don’t have URLs or ports. All we have are services and caracteristics. And UUIDs, lots of UUIDs.
According to the official doc, Bluetooth GATT services are collections of characteristics and relationships to other services that encapsulate the behavior of part of a device. So basically a service is a set of characteristics.
According to the same official doc, “Characteristics are defined attribute types that contain a single logical value.”
Characteristics are where the data is, that’s what we want to read or write.
Last thing, services and characteristics are identified by UUIDs.
On Android, a Bluetooth device communicates with us via a BluetoothGattCallback
:
Here is our issue: when we write a characteristic to the Bluetooth device to send a command, we want to wait for the device’s acknowledgement to continue. In other words, we want to communicate synchronously with the device.
To do so, we need to block the execution until onCharacteristicWrite
is called back for my characteristic.
Coroutines are a great tool for dealing with asynchronous calls. Combined with channels, we have here the perfect tools to communicate synchronously with a Bluetooth device.
Here is a simple “Hello World!” using a channel:
channel.receive()
will wait for the channel to have something to offer. In this way, “Hello World!” will be displayed 3 seconds later.
What we need is a way to wait for the device acknowledgment before sending another command. We’ll use a coroutine and a channel to achieve this.
We’ll use a channel of BluetoothResult
s, a data class composed of the characteristic’s UUID and value, and the event status:
Each call to onCharacteristicRead
or onCharacteristicWrite
will offer to the channel a BluetoothResult
:
We now need a function that will wait for the channel to have a matching BluetoothResult
to offer:
This waitForResult
function will wait for the channel for 3 seconds, or throw a custom BluetoothTimeoutException
.
Then, we’ll use a new BluetoothGatt.readCharacteristic
function that will wait for the response, via the channel:
Et voilà! Now we can communicate synchronously with a Bluetooth device: