简体   繁体   中英

How to Read BLE GATT characterstics in chunks form GATT server using RxAndroidBLE

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.

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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM