I'm trying to communicate with a BLE data logger/sensor using rxBleAndroid running on both an Android phone as well as the Raspberry Pi using Android Things.
I'm currently having an issue, however, where up to about 5 of the first notifications are never received by my app.
I have verified that the BLE device is actually successfully sending all the expected notifications. I have done that through the nRF Connect app and everything works as expected through there.
When I do it through the nRF Connect app, these are the steps I take:
When doing it through RxAndroidBle, I suspect it may be that the .subscribe() is not being setup fast enough.
Is there maybe some way to do setupNotification(), and then write the characteristics to tell the device to start sending notifications?
Here is my current code:
rxBleClient = RxBleClient.create(this);
RxBleDevice device = rxBleClient.getBleDevice(mac_address);
device.establishConnection(false)
.flatMap(rxBleConnection -> rxBleConnection.writeCharacteristic(pword_uuid, pword)
.flatMap(ignored1 -> rxBleConnection.writeCharacteristic(mode_uuid, mode))
.flatMap(ignored2 -> rxBleConnection.setupNotification(log_uuid))
)
.flatMap(notificationObservable -> notificationObservable)
.subscribe(
bytes -> {
System.out.println(">>> data from device " + bytesToHex(bytes));
},
throwable -> {
System.out.println("error");
System.out.println(throwable);
});
Most actions that one can do over BLE are asynchronous and take some time to finish. Setting up the notifications is no exception—it is a two-step procedure:
If your peripheral is first set to send notifications before the notifications are ready to be received by the central then some of the data may get lost during the notifications setup procedure.
Is there maybe some way to do setupNotification(), and then write the characteristics to tell the device to start sending notifications?
Of course (this is how usually similar scenarios are handled)—there are multiple possible implementations. One of them could look like this:
device.establishConnection(false) // establish the connection
.flatMap(rxBleConnection -> rxBleConnection.setupNotification(log_uuid) // once the connection is available setup the notification
.flatMap(logDataObservable -> Observable.merge( // when the notification is setup start doing three things at once
rxBleConnection.writeCharacteristic(pword_uuid, pword).ignoreElements(), // writing the `pword` but ignore the result so the output of this .merge() will contain only log data
rxBleConnection.writeCharacteristic(mode_uuid, mode).ignoreElements(), // same as the line above but for `mode`
logDataObservable // observing the log data notifications
))
)
.subscribe(
bytes -> System.out.println(">>> data from device " + bytesToHex(bytes)),
throwable -> {
System.out.println("error");
System.out.println(throwable);
}
);
Edit:
As it was mentioned in the comments below—the peripheral does not allow for any BLE interactions before setting the mode and writing the password. As I have written above setting the notifications is a two step-step procedure with a local step and remote (executed on peripheral) one which is executed before the mode/password in the above code snippet. It is possible to separate those two steps by using the NotificationSetupMode.COMPAT
mode and writing the Client Characteristic Configuration Descriptor
manually later:
UUID clientCharacteristicConfigDescriptorUuid = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
device.establishConnection(false) // establish the connection
.flatMap(
RxBleConnection::discoverServices, // once the connection is available discover the services of the peripheral
(rxBleConnection, rxBleDeviceServices) -> // and when we have the connection and services
rxBleDeviceServices.getCharacteristic(log_uuid) // we get the log characteristic (on which we will setup the notification and write the descriptor)
.flatMap(logDataCharacteristic -> // once the log characteristic is retrieved
rxBleConnection.setupNotification(logDataCharacteristic, NotificationSetupMode.COMPAT) // we setup the notification on it in the COMPAT mode (without writing the CCC descriptor)
.flatMap(logDataObservable -> Observable.merge( // when the notification is setup start doing four things at once
rxBleConnection.writeCharacteristic(pword_uuid, pword).ignoreElements(), // writing the `pword` but ignore the result so the output of this .merge() will contain only log data
rxBleConnection.writeCharacteristic(mode_uuid, mode).ignoreElements(), // same as the line above but for `mode`
rxBleConnection.writeDescriptor(logDataCharacteristic.getDescriptor(clientCharacteristicConfigDescriptorUuid), BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE).ignoreElements(), // and we write the CCC descriptor manually
logDataObservable // observing the log data notifications
))
)
)
.flatMap(observable -> observable) // flatMap to get the raw byte[]
.subscribe(
bytes -> System.out.println(">>> data from device " + bytesToHex(bytes)),
throwable -> {
System.out.println("error");
System.out.println(throwable);
}
);
rxBleConnection.discoverServices()
call may be omitted if we would know the Log Characteristic Service UUID
and use rxBleConnection.writeDescriptor(UUID serviceUuid, UUID characteristicUuid, UUID descriptorUuid
function.
UUID clientCharacteristicConfigDescriptorUuid = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
device.establishConnection(false) // establish the connection
.flatMap(rxBleConnection -> rxBleConnection.setupNotification(log_uuid, NotificationSetupMode.COMPAT) // once the connection is available setup the notification w/o setting Client Characteristic Config Descriptor
.flatMap(logDataObservable -> Observable.merge( // when the notification is setup start doing three things at once
rxBleConnection.writeCharacteristic(pword_uuid, pword).ignoreElements(), // writing the `pword` but ignore the result so the output of this .merge() will contain only log data
rxBleConnection.writeCharacteristic(mode_uuid, mode).ignoreElements(), // same as the line above but for `mode`
rxBleConnection.writeDescriptor(log_service_uuid, log_uuid, clientCharacteristicConfigDescriptorUuid).ignoreElements(), // same as the above line but for writing the CCC descriptor
logDataObservable // observing the log data notifications
))
)
.subscribe(
bytes -> System.out.println(">>> data from device " + bytesToHex(bytes)),
throwable -> {
System.out.println("error");
System.out.println(throwable);
}
);
Edit 2:
Since version 1.8.0
there is a new NotificationSetupMode.QUICK_SETUP
which first turns on internal notifications and then writes the CCC descriptor value.
rxBleConnection.setupNotification(log_uuid, NotificationSetupMode.QUICK_SETUP)
Pros:
Observable<byte[]>
is emitted before the descriptor is written allowing for notification observation right from the beginning (if the beginning is writing the descriptor)Cons:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With