0

I am using RxAndroidBle library for handling BLE connection and reading/writing to GATT server from my android gatt client app. I have followed the sample application provided on github.

The problem I am facing is my GATT server is running on Intel Edison and it is supporting MTU size of 80 only .It sends data in chunks, I am supposed to read the charcterstics value multiple time until i encounter a special character, something like '/END' . I have tried Custom read operation example which is supposed to read 5 times every 250 ms.

private static class CustomReadOperation implements RxBleRadioOperationCustom<byte[]> {

    private RxBleConnection connection;
    private UUID characteristicUuid;

    CustomReadOperation(RxBleConnection connection, UUID characteristicUuid) {
        this.connection = connection;
        this.characteristicUuid = characteristicUuid;
    }

    /**
     * Reads a characteristic 5 times with a 250ms delay between each. This is easily achieve without
     * a custom operation. The gain here is that only one operation goes into the RxBleRadio queue
     * eliminating the overhead of going on & out of the operation queue.
     */
    @NonNull
    @Override
    public Observable<byte[]> asObservable(BluetoothGatt bluetoothGatt,
                                           RxBleGattCallback rxBleGattCallback,
                                           Scheduler scheduler) throws Throwable {
        return connection.getCharacteristic(characteristicUuid)
                .flatMap(characteristic -> readAndObserve(characteristic, bluetoothGatt, rxBleGattCallback))
                .subscribeOn(scheduler)
                .takeFirst(readResponseForMatchingCharacteristic())
                .map(byteAssociation -> byteAssociation.second)
                .repeatWhen(notificationHandler -> notificationHandler.take(5).delay(250, TimeUnit.MILLISECONDS));
    }

    @NonNull
    private Observable<ByteAssociation<UUID>> readAndObserve(BluetoothGattCharacteristic characteristic,
                                                             BluetoothGatt bluetoothGatt,
                                                             RxBleGattCallback rxBleGattCallback) {
        Observable<ByteAssociation<UUID>> onCharacteristicRead = rxBleGattCallback.getOnCharacteristicRead();

        return Observable.create(emitter -> {
            Subscription subscription = onCharacteristicRead.subscribe(emitter);
            emitter.setCancellation(subscription::unsubscribe);

            try {
                final boolean success = bluetoothGatt.readCharacteristic(characteristic);
                if (!success) {
                    throw new BleGattCannotStartException(bluetoothGatt, BleGattOperationType.CHARACTERISTIC_READ);
                }
            } catch (Throwable throwable) {
                emitter.onError(throwable);
            }
        }, Emitter.BackpressureMode.BUFFER);
    }

    private Func1<ByteAssociation<UUID>, Boolean> readResponseForMatchingCharacteristic() {
        return uuidByteAssociation -> uuidByteAssociation.first.equals(characteristicUuid);
    }
}

and i am calling it like this

public void customRead()
{
    if (isConnected()) {
        connectionObservable
                .flatMap(rxBleConnection -> rxBleConnection.queue(new CustomReadOperation(rxBleConnection, UUID_READ_CHARACTERISTIC)))
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(bytes -> {
                    configureMvpView.showList(bytes);
                }, this::onRunCustomFailure);
    }
}

and i am not getting any data from the server using this code. However if i try simple read operation like this

public void readInfo() {

    if (isConnected()) {
        connectionObservable
                .flatMap(rxBleConnection -> rxBleConnection.readCharacteristic(UUID_READ_CHARACTERISTIC))
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(bytes -> {
                    // parse data
                    configureMvpView.showWifiList(bytes);

                }, this::onReadFailure);
    }

}

I get the first chunk of data, but i need to read rest of data.
I am not very well versed with RxJava. So there might be an easy way to do this, But any suggestion or help will good.

This is my prepareConnectionObservable

private Observable<RxBleConnection> prepareConnectionObservable() {
    return bleDevice
            .establishConnection(false)
            .takeUntil(disconnectTriggerSubject)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .doOnUnsubscribe(this::clearSubscription)
            .compose(this.bindToLifecycle())
            .compose(new ConnectionSharingAdapter());


}

I call

 connectionObservable.subscribe(this::onConnectionReceived, this::onConnectionFailure);

and onConnectionReceived i call CustomRead.

Avijeet
  • 365
  • 4
  • 14

1 Answers1

1

You do not show how the connectionObservable is created and I do not know if anything else is done on that connection before the above code is executed.

My guess is that if you would look into the logs of the application you would see that the radio processing queue starts executing your CustomReadOperation as the first operation after the connection. In your custom operation you are calling RxBleConnection.getCharacteristic(UUID) which tries to execute .discoverServices() (schedule a RxBleRadioOperationDiscoverServices on the radio queue). The problem is that the radio queue is already executing your CustomReadOperation and will not discover services until it will finish.

There is a reason why RxBleConnection is not passed to the RxBleRadioOperationCustom.asObservable() — most of the functionality will not work at that time.

What you can do is to perform RxBleConnection.discoverServices() before scheduling your CustomReadOperation and pass the BluetoothGattCharacteristic retrieved from RxBleDeviceServices in the constructor. So instead of having this:

public void customRead()
{
    if (isConnected()) {
        connectionObservable
                .flatMap(rxBleConnection -> rxBleConnection.queue(new CustomReadOperation(rxBleConnection, UUID_READ_CHARACTERISTIC)))
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(bytes -> {
                    configureMvpView.showList(bytes);
                }, this::onRunCustomFailure);
    }
}

You would have something like:

public void customRead()
{
    if (isConnected()) {
        connectionObservable
                .flatMap(RxBleConnection::discoverServices, (rxBleConnection, services) -> 
                    services.getCharacteristic(UUID_READ_CHARACTERISTIC)
                        .flatMap(characteristic -> rxBleConnection.queue(new CustomReadOperation(characteristic)))
                )
                .flatMap(observable -> observable)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(bytes -> {
                    configureMvpView.showList(bytes);
                }, this::onRunCustomFailure);
    }
}

Edit (clarification):

And the constructor of your CustomReadOperation should look like this:

CustomReadOperation(BluetoothGattCharacteristic characteristic) {
    this.characteristic = characteristic;
}

So you will not have to use the this.rxBleConnection.getCharacteristic(UUID) inside of your CustomReadOperation and use directly bluetoothGatt.readCharacteristic(this.characteristic).

Edit 2: Change these two lines:

    return connection.getCharacteristic(characteristicUuid)
            .flatMap(characteristic -> readAndObserve(characteristic, bluetoothGatt, rxBleGattCallback))

To this (the rest is the same):

    return readAndObserve(this.characteristic, bluetoothGatt, rxBleGattCallback)
Dariusz Seweryn
  • 3,212
  • 2
  • 14
  • 21
  • Thanks for response s_noopy. you are right about RxBleRadioOperationDiscoverServices it gets stuck on that. Can you please post the change that needs to be done on CustomeReadOperation class. I am having trouble changing asObservable function. – Avijeet Jul 28 '17 at 11:36
  • Added a clarification. – Dariusz Seweryn Jul 28 '17 at 11:43
  • Thanks, i did changed the constructor of CustomReadOperation, however i am facing problem in public Observable asObservable(BluetoothGatt bluetoothGatt, RxBleGattCallback rxBleGattCallback, Scheduler scheduler) throws Throwable putting BluetoothGatt.readCharacteristic there gives an error for non static context , as CustomRead is a static class. Can you please post the updated asObservable function. – Avijeet Jul 28 '17 at 12:13
  • I have edited the clarification. Of course you need to call the objects passed to your `CustomReadOperation` -> `bluetoothGatt.readCharacteristic(this.characteristic). – Dariusz Seweryn Jul 28 '17 at 12:37
  • the problem is that asObservable in CustomRead class returns an Observable, while bluetoothGatt.readCharacteristic returns a boolean, i think observable is needed for this to work. Again this may be some RxJava issue and i might be missing it. If you have time then please post the updated CustomRead class , mainly asObservable function. – Avijeet Jul 28 '17 at 13:01
  • I have added what you need to change exactly in your code. When you have changed your constructor and those two lines everything should work fine now. – Dariusz Seweryn Jul 28 '17 at 13:10
  • Also these are the logs i am getting now D/BluetoothGatt: discoverServices() - device: 98:4F:EE:02:E2:D0 D/BluetoothGatt: onSearchComplete() = Device=98:4F:EE:02:E2:D0 D/RxBle#BluetoothGatt: onServicesDiscovered status=0 D/RxBle#Radio: FINISHED RxBleRadioOperationServicesDiscover(138360275) ---------------------------------------------------------------------------------------------------- – Avijeet Jul 28 '17 at 13:11
  • but flatMap(characteristic -> rxBleConnection.queue(new CustomReadOperation(rxBleConnection,characteristic))) is not getting called. – Avijeet Jul 28 '17 at 13:11
  • Yup. My mistake. There should be an additional `.flatMap(observable -> observable)` in the `customRead()` method. – Dariusz Seweryn Jul 28 '17 at 13:18
  • Awesome man, its working now. Thanks a lot for help. I will accept it as answer. BTW do you have any idea on how to do something like this but i need to keep on reading until i get a special character in the bytes received or in other words keep on reading until i have received all the data from gatt server. – Avijeet Jul 28 '17 at 13:31
  • You need to finish the notification `Observable` in `.repeatWhen()` when you will get your `EOF` marker (your special character). – Dariusz Seweryn Jul 28 '17 at 14:08
  • Thanks s_noopy, i am using .takeUntil in observable to check for EOF marker. btw how of i disconnect the read process when i move away from activity. i have seen that read operation keeps on going even when i exist the app quickly. – Avijeet Jul 31 '17 at 09:02
  • I am glad to help. To stop the action you need to unsubscribe from the `Subscription`. How to unsubscribe in RxJava is a different topic and it should not be discussed in this topic. Your `bindToLifecycle()` should handle unsubscribing. – Dariusz Seweryn Jul 31 '17 at 09:32
  • Dariusz , i am facing a strange issue. Most of the time Long Read works fine but it looks like there is an internal buffer that is being maintained and if the data that is being read is greater than that then . subscribe is not getting called second time.. So i get incomplete data, most of the times it happens when data size is like 600 bytes or more.. in this case i get a chunk of data which is almost all the data and then second chunk which is few bytes , for that subscribe is not getting called, – Avijeet Aug 26 '17 at 16:16