5

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:

  1. Write to password characteristic to unlock device
  2. Write to mode characteristic to put device in correct mode
  3. Subscribe to notifications (and notifications immediately start working)

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);
                });
James E
  • 75
  • 1
  • 4

1 Answers1

6

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:

  1. setup a local notification
  2. write a Client Characteristic Configuration Descriptor of the characteristic one wants to get notifications from

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:

  • There is no way to tell when exactly the descriptor has been written.
Dariusz Seweryn
  • 3,212
  • 2
  • 14
  • 21
  • I've just tried it and now I'm receiving zero notifications. It turns out that my BLE device MUST receive the password and mode first, otherwise the notifications will never occur. Is there any way to achieve the following: [password write, then mode write, then subscribe] without missing the first few notifications? – James E Sep 14 '17 at 00:43
  • There is a chance that setting up the notification in `COMPAT` mode and then manually writing Client Characteristic Configuration descriptor after the password and mode could work. Thing is that there is lack of information of what your peripheral allows and what not. I'm not on my computer so I'll not update the answer till Monday. – Dariusz Seweryn Sep 14 '17 at 04:52
  • Thanks very much! Setting up the notifications using COMPAT mode and writing the descriptor separately fixed my issue and it's working well now. Thanks for all your assistance. – James E Sep 16 '17 at 12:31
  • Happy to help. I have edited the answer with two possibilities of how your use case could be addressed depending on wether the `log_service_uuid` is known and not. If you find the answer useful—feel free to mark it as such. – Dariusz Seweryn Sep 18 '17 at 11:09
  • I've been trying to use this code, but I'm not sure I understand the flow. When I try to establishConnection, I might get BleAlreadyConnectedException, for example and onError will be called, so there I manage to disconnect and create a new connection, then the observables inside the establishConnection one get called and everything seems Ok but onNext never gets called. Any idea what I might be doing wrong? @DariuszSeweryn – Carla Urrea Stabile Oct 02 '17 at 11:28
  • No idea. I cannot imagine your code as there is obviously some state managing in it. Feel free to open a new question with related code. – Dariusz Seweryn Oct 02 '17 at 13:03