0

I'm new to Rx and still trying to figure out how to properly handle observables. I was wondering if there is a better way to write multiple characteristics than to do them one at a time with RxAndroidBle? Currently I'm doing them one at a time with the code down below.

Observable<RxBleConnection> mConnectionObservable;

private void saveChanges(String serialNumber, Date date, MachineTypeEnum machineType, MachineConfig machineConfig) {
    mWriteSubscription = mConnectionObservable
            .flatMap(rxBleConnection -> Observable.merge(
                    getWrites(rxBleConnection, serialNumber, machineType, machineConfig, date)
            ))
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(bytes -> {
                Toast.makeText(getContext(), "Saved Changes", Toast.LENGTH_SHORT).show();
                ((MainActivity)getActivity()).back();
            }, BleUtil::logError);
}

private Iterable<? extends Observable<? extends byte[]>> getWrites(RxBleConnection rxBleConnection,
                                                                   String serialNumber,
                                                                   MachineTypeEnum machineType,
                                                                   MachineConfig machineConfig,
                                                                   Date date) {
    List<Observable<byte[]>> observables = new ArrayList<>();


    observables.add(rxBleConnection.writeCharacteristic(
            Constants.Bluetooth.Services.DrainCleaner.Characteristics.UUID_WRITE_SERIAL_NUMBER,
            Utf8Util.nullPad(serialNumber, 16).getBytes()).doOnError(throwable -> Log.e("Write", "serial failed", throwable)));

    observables.add(rxBleConnection.writeCharacteristic(
            Constants.Bluetooth.Services.DrainCleaner.Characteristics.UUID_MACHINE_TYPE,
            new byte[]{(byte) machineType.val()}).doOnError(throwable -> Log.e("Write", "machine type failed", throwable)));

    observables.add(rxBleConnection.writeCharacteristic(
            Constants.Bluetooth.Services.DrainCleaner.Characteristics.UUID_CHARACTERISTIC,
            MachineConfigBitLogic.toBytes(machineConfig)).doOnError(throwable -> Log.e("Write", "machine config failed", throwable)));

    observables.add(rxBleConnection.writeCharacteristic(
            Constants.Bluetooth.Services.CurrentTime.Characteristics.UUID_CURRENT_TIME,
            TimeBitLogic.bytesFor(date)).doOnError(throwable -> Log.e("Write", "date failed", throwable)));

    return observables;
}

So I changed my old code to what is above which now uses merge but only one of the characteristics seems to update now.

David Carek
  • 1,103
  • 1
  • 12
  • 26

2 Answers2

2

I would use merge operator:

mConnectionObservable
        .flatMap(rxBleConnection -> 
        Observable.merge(
        rxBleConnection.writeCharacteristic(
            Constants.Bluetooth.Services.DeviceInformation.Characteristics.UUID_SERIAL_NUMBER,
            Utf8Util.nullPad(serialNumber, 16).getBytes()
        ),
        rxBleConnection.writeCharacteristic(
            Constants.Bluetooth.Services.DrainCleaner.Characteristics.UUID_MACHINE_TYPE,
            new byte[]{(byte) machineType.val()}
        ))
        .subscribe(bytes -> {/* do something*/}, BleUtil::logError);

Also you could pass a list of observables to that operator:

Instead of passing multiple Observables (up to nine) into merge, you could also pass in a List<> (or other Iterable) of Observables, an Array of Observables, or even an Observable that emits Observables, and merge will merge their output into the output of a single Observable

Denys Vasylenko
  • 2,135
  • 18
  • 20
  • I do run into issues using merge though. It seems only one of the characteristics gets written to with this approach. I will add an edit with my current code. – David Carek Sep 25 '17 at 20:45
1

The RxAndroidBle library is serializing any BLE requests under the hood as BLE implementation on Android is mostly synchronous (though the Android vanilla API suggests that it is not).

Mergeing of the writes is a good approach though you need to be aware of what the merge operator does:

 * You can combine the items emitted by multiple Observables so that they appear as a single Observable, by
 * using the {@code merge} method.

So I changed my old code to what is above which now uses merge but only one of the characteristics seems to update now.

The reason for this behaviour may be how you consume the stream:

        .subscribe(bytes -> {
            Toast.makeText(getContext(), "Saved Changes", Toast.LENGTH_SHORT).show();
            ((MainActivity)getActivity()).back();
        }, BleUtil::logError);

Whenever the bytes are emitted you are calling Activity.back(). .merge() operator does emit bytes for every write command that is executed. If you unsubscribe from the Subscription in i.e. .onPause() then it would be unsubscribed right after the first write is completed. You could make your flow to wait until all writes are completed like this:

private void saveChanges(String serialNumber, Date date, MachineTypeEnum machineType, MachineConfig machineConfig) {
    mWriteSubscription = mConnectionObservable
        .flatMap(rxBleConnection -> 
            Observable.merge(
                getWrites(rxBleConnection, serialNumber, machineType, machineConfig, date)
            )
            .toCompletable() // we are only interested in the merged writes completion
            .andThen(Observable.just(new byte[0])) // once the merged writes complete we send a single value that will be reacted upon (ignored) in .subscribe()
        )
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(ignored -> {
            Toast.makeText(getContext(), "Saved Changes", Toast.LENGTH_SHORT).show();
            ((MainActivity)getActivity()).back();
        }, BleUtil::logError);
}
Dariusz Seweryn
  • 3,212
  • 2
  • 14
  • 21