Search code examples
androidrxandroidble

RxBleConnection.setupNotification work for only the first of two characteristics


i'm having difficulty figuring out the unexpected behavior using RxAndroidBle.

the short form of the issue is that i need to receive in-order notification from two characteristics of a particular device. in my example below, i scan for SERVICE_UUID, and setup notification (in order) for CHARACTERISTIC_FOO_UUID and CHARACTERISTIC_BAR_UUID. i need to do something based on the responses from both characteristics --- in my example, i simply store the byte[] in member variables.

the issue i'm having is that the first characteristic reports back but not the second. if i flip the order, it is still whichever characteristic is first in the chain. below, i present the debug output, which shows that the notification call occurs for both, including the low-level descriptor write, but for some reason the second one does not report back. (assume i subscribe to the Observable.)

i have been able to get this to work without RxAndroidBle. i also have one version employing RxAndroidBle that works but is setup in a different manner using ConnectionSharingAdapter and several subscriptions. the example below is an attempt at a cleaner approach but, as i said, doesn't seem to work.

rxBleClient.scanBleDevices(SERVICE_UUID)
    .first()
    .flatMap(rxBleScanResult -> {
        return Observable.just(rxBleScanResult.getBleDevice());
    })
    .flatMap(rxBleDevice -> {
        return rxBleDevice.establishConnection(context, IS_AUTO_CONNECT);
    })
    .flatMap(rxBleConnection -> 
        rxBleConnection.setupNotification(CHARACTERISTIC_FOO_UUID)
        .flatMap(observable -> observable)
        .flatMap(new Func1<byte[], Observable<RxBleConnection>>() {
            @Override
            public Observable<RxBleConnection> call(final byte[] notificationBytes) {
                mFooBytes = notificationBytes;
                return Observable.just(rxBleConnection);
            }
        })
    )
    .flatMap(rxBleConnection -> 
        rxBleConnection.setupNotification(CHARACTERISTIC_BAR_UUID)
        .flatMap(observable -> observable)
        .flatMap(new Func1<byte[], Observable<RxBleConnection>>() {
            @Override
            public Observable<RxBleConnection> call(final byte[] notificationBytes) {
                mBarBytes = notificationBytes;
                return Observable.just(rxBleConnection);
            }
        })
    )

here is the RxBle debug output --- i redacted the actual uuid with "CHARACTERISTIC_FOO_UUID" in the output.

12-22 12:13:43.322 12074-12074/com.foo.example D/RxBle#Radio:   QUEUED RxBleRadioOperationScan(217963087)
12-22 12:13:43.322 12074-12281/com.foo.example D/RxBle#Radio:  STARTED RxBleRadioOperationScan(217963087)
12-22 12:13:43.412 12074-12281/com.foo.example D/RxBle#Radio: FINISHED RxBleRadioOperationScan(217963087)
12-22 12:13:43.682 12074-12074/com.foo.example D/RxBle#Radio:   QUEUED RxBleRadioOperationConnect(37012551)
12-22 12:13:43.682 12074-12281/com.foo.example D/RxBle#Radio:  STARTED RxBleRadioOperationConnect(37012551)
12-22 12:13:44.052 12074-12085/com.foo.example D/RxBle#BluetoothGatt: onConnectionStateChange newState=2 status=0
12-22 12:13:44.092 12074-12558/com.foo.example D/RxBle#Radio:   QUEUED RxBleRadioOperationServicesDiscover(72789039)
12-22 12:13:44.092 12074-12281/com.foo.example D/RxBle#Radio: FINISHED RxBleRadioOperationConnect(37012551)
12-22 12:13:44.092 12074-12281/com.foo.example D/RxBle#Radio:  STARTED RxBleRadioOperationServicesDiscover(72789039)
12-22 12:13:45.232 12074-12086/com.foo.example D/RxBle#BluetoothGatt: onServicesDiscovered status=0
12-22 12:13:45.262 12074-12558/com.foo.example D/RxBle#Radio:   QUEUED RxBleRadioOperationDescriptorWrite(8700606)
12-22 12:13:45.262 12074-12281/com.foo.example D/RxBle#Radio: FINISHED RxBleRadioOperationServicesDiscover(72789039)
12-22 12:13:45.262 12074-12281/com.foo.example D/RxBle#Radio:  STARTED RxBleRadioOperationDescriptorWrite(8700606)
12-22 12:13:45.342 12074-12085/com.foo.example D/RxBle#BluetoothGatt: onDescriptorWrite descriptor=00002902-0000-1000-8000-00805f9b34fb status=0
12-22 12:13:45.362 12074-12281/com.foo.example D/RxBle#Radio: FINISHED RxBleRadioOperationDescriptorWrite(8700606)
12-22 12:13:46.172 12074-12086/com.foo.example D/RxBle#BluetoothGatt: onCharacteristicChanged characteristic=CHARACTERISTIC_FOO_UUID
12-22 12:13:46.192 12074-12558/com.foo.example D/RxBle#Radio:   QUEUED RxBleRadioOperationDescriptorWrite(179103302)
12-22 12:13:46.192 12074-12281/com.foo.example D/RxBle#Radio:  STARTED RxBleRadioOperationDescriptorWrite(179103302)
12-22 12:13:46.272 12074-12201/com.foo.example D/RxBle#BluetoothGatt: onDescriptorWrite descriptor=00002902-0000-1000-8000-00805f9b34fb status=0
12-22 12:13:46.272 12074-12281/com.foo.example D/RxBle#Radio: FINISHED RxBleRadioOperationDescriptorWrite(179103302)

below is a simplified example of a version using RxAndroidBle that does work but with multiple subscriptions and a ConnectionSharingAdapter. in both versions, the Observable will get subscribed to elsewhere, so i'm trying to avoid all the bookkeeping of the multiple subscriptions --- the CompositeSubscription here and the other subscription(s) elsewhere. the approach above, which is having problems, seems more functional. in my actual application, what i'm doing is more complicated, so the version above actually become easier to follow, where in this simplified version it might seem like a bit more code.

CompositeSubscription bleConnectionSubscriptions = new CompositeSubscription();

Observable<RxBleConnection> bleConnectionObservable =
    bleConnectionObservable = bleDevice.establishConnection(context, IS_AUTO_CONNECT)
        .compose(new ConnectionSharingAdapter());

Subscription subscription =
    bleConnectionObservable
        .flatMap(rxBleConnection ->
                 rxBleConnection.setupNotification(CHARACTERISTIC_FOO_INGREDIENTS))
        .flatMap(observable -> observable)
        .subscribe(notificationBytes -> mFooBytes = notificationBytes);

bleConnectionSubscriptions.add(subscription);

subscription =
    bleConnectionObservable
        .flatMap(rxBleConnection ->
                 rxBleConnection.setupNotification(CHARACTERISTIC_BAR_INGREDIENTS))
        .flatMap(observable -> observable)
        .subscribe(notificationBytes -> mBarBytes = notificationBytes);

bleConnectionSubscriptions.add(subscription);

Solution

  • As you can see in the logs both notifications are being properly set but the second one does not receive any values. The suspicion is that the BAR value is emitted before the notification is setup.

    You can setup both notifications at the very beginning of the connection using code like below:

    rxBleClient.scanBleDevices(SERVICE_UUID)
            .first() // subscribe to the first device that is available...
            .flatMap(rxBleScanResult -> rxBleScanResult.getBleDevice().establishConnection(context, IS_AUTO_CONNECT)) // ...establish the connection...
            .flatMap(rxBleConnection -> Observable.combineLatest( // ...when connection is established we combine latest results from...
                    rxBleConnection.setupNotification(CHARACTERISTIC_FOO_INGREDIENTS) // ...setup notification on FOO...
                            .flatMap(observable -> observable) // ...flatMap it to values...
                            .first(), // ...take the first value so the notification will be disposed...
                    rxBleConnection.setupNotification(CHARACTERISTIC_BAR_INGREDIENTS) // ...setup notification on BAR...
                            .flatMap(observable -> observable) // ...flatMap it to values...
                            .first(), // ...take the first value so the notification will be disposed...
                    ((fooBytes, barBytes) -> {
                        mFooBytes = fooBytes; 
                        mBarBytes = barBytes;
                        return true; // return whatever
                    })
            ))
            .first(); // after the first returned value the connection will be disconnected