how to call multiple setupIndication using RxAndroidBle - android

I am trying to read the value after firing the rxBleConnection.setupIndication(UUID) I have many characteristics UUIDs and I want to line up using RxJava in a way that I can get consolidated values just the way we get using Single.Zip or Observable.zip
For example, using RxAndroidBle we can read multiple characteristics, would it be possible to do the same for setupIndication. As setupIndication is returning Observable<Observable<byte[]>> it is not possible to zip i guess.
here is the library that I am using
What I expect
Disposable disposable = device.establishConnection(false)
.flatMap(rxBleConnection -> Observable.zip(
rxBleConnection.setupIndication(UUID1),
rxBleConnection.setupIndication(UUID2),
rxBleConnection.setupIndication(UUID3),
rxBleConnection.setupIndication(UUID4),
BLEReading::new
))
.subscribe(
model -> {
// Process your model.
Log.e(TAG , "FINAL DATA ");
},
throwable -> {
// Handle an error here.
}
);
Currently, I have to perform the setupIndication for all 5 charactericts.
connectDisposible = device.establishConnection(false)
.flatMap(rxBleConnection->rxBleConnection.setupIndication(UUID1))
.flatMap(notificationObservable -> notificationObservable)
.subscribe(
bytes -> {
Log.e(TAG,"Notification bytes"+Arrays.toString(BLEUtils.toHex(bytes)));
},
throwable -> {
Log.e(TAG,"Notification Error "+throwable.getMessage());
}
);
Edit
connectDisposible = device.establishConnection(false)
.flatMap(rxBleConnection -> Observable.zip(
rxBleConnection.setupIndication(UUID1).flatMap(it -> it),
rxBleConnection.setupIndication(UUID2).flatMap(it -> it),
rxBleConnection.setupIndication(UUID3).flatMap(it -> it),
rxBleConnection.setupIndication(UUID4).flatMap(it -> it),
rxBleConnection.setupIndication(UUID5).flatMap(it -> it),
BLEReading::new
))
.subscribe(
model -> {
//control never reaches here
Log.e(TAG , "FINAL DATA "+model);
},
throwable -> {
// Handle an error here.
Log.e(TAG , "error"+throwable.getMessage());
}
);
however, in logcat i can the indications are successfully set.
setCharacteristicNotification() - uuid: 705f68f7-83c9-6562-b2c5 enable: true
setCharacteristicNotification() - uuid: 314fae3a-d0cf-51c4-4a67 enable: true
setCharacteristicNotification() - uuid: 8599c5ba-f827-2d16-ce14 enable: true
setCharacteristicNotification() - uuid: 6fbba050-e87b-6ea8-6e5d enable: true

The simplest way to have all indications lined up is to flatMap the indication observables into individual indications. Just have in mind that each emission to the subscribe block will happen when all of the indication observables will emit.
Using NotificationSetupMode.QUICK_SETUP to not miss emissions that happen in response to setting Client Characteristic Configuration (CCC) Descriptor.
Disposable disposable = device.establishConnection(false)
.flatMap(rxBleConnection -> Observable.zip(
rxBleConnection.setupIndication(UUID1, NotificationSetupMode.QUICK_SETUP).flatMap(it -> it),
rxBleConnection.setupIndication(UUID2, NotificationSetupMode.QUICK_SETUP).flatMap(it -> it),
rxBleConnection.setupIndication(UUID3, NotificationSetupMode.QUICK_SETUP).flatMap(it -> it),
rxBleConnection.setupIndication(UUID4, NotificationSetupMode.QUICK_SETUP).flatMap(it -> it),
BLEReading::new
))
.subscribe(
model -> {
// Process your model.
Log.e(TAG , "FINAL DATA ");
},
throwable -> {
// Handle an error here.
}
);

Related

Sending data over the air and after completion end the operation using RxAndroidBLE

I am trying to send the 128 bytes of the block to BLE Controller using the RxAndroidBle library. the flow to send data from mobile to BLE controller is as follows
Connect with BLE Controller
Start OTA (sending 1)
Send CRC (of the data block)
Send data block
wait for 2 seconds
repeat step 3
END OTA (sending 2)
Here is snapshot of a code
.flatMap(rxBleConnection -> prepareWriting())
.flatMapIterable(otaMetaData -> otaMetaData)
.zipWith(Observable.interval(2, TimeUnit.SECONDS), (item, interval) -> item)
.doOnNext(metaData -> {
otaMetaData = metaData;
})
.map(otaMetaData -> {
return mRxBleConnection.writeCharacteristic(OTA_CHECKSUM, otaMetaData.getCrcBlock()).toObservable();
})
.doOnNext(otaMetaData -> {
Log.e(TAG, "Writing CRC " + Arrays.toString(BLEUtils.toHex(otaMetaData.getCrcBlock())));
})
.map(bytes -> {
return mRxBleConnection.writeCharacteristic(OTA_DATA, otaMetaData.getDataBlock()).toObservable();
})
.doOnNext(otaMetaData -> {
Log.e(TAG, "Writing Data " + Arrays.toString(BLEUtils.toHex(otaMetaData.getDataBlock())));
})
.flatMap(bytes -> mRxBleConnection.writeCharacteristic(OTA_CONTROL,OTA_DATA_END).toObservable())
The problem is while sending the END OTA because as the flatMapIterable returns 20 items, .flatMap(bytes -> mRxBleConnection.writeCharacteristic(OTA_CONTROL,OTA_DATA_END) is getting called 20 times.
So, I am not sure how I can send the OTA_DATA_END command when all the 20 items get processed. Moreover, any suggestion to improve the existing code is welcome.
You can use flatMapIterable() with toList(). Try to add toList() operator before OTA_DATA_END command like:
.toList() // wait for all commands to complete
.flatMap(bytes -> mRxBleConnection.writeCharacteristic(OTA_CONTROL,OTA_DATA_END).toObservable())
EDIT
Better to separate steps like
.flatMap(rxBleConnection -> prepareWriting())
.flatMap(otaMetaData -> Observable.fromIterable(otaMetaData)
.zipWith(Observable.interval(2, TimeUnit.SECONDS), (metaData, interval) -> metaData)
.flatMap(metaData -> {
return mRxBleConnection.writeCharacteristic(OTA_CHECKSUM, metaData.getCrcBlock())
.toObservable();
}, (metaData, bytes) -> metaData) /* result selector */
.flatMap(metaData -> {
return mRxBleConnection.writeCharacteristic(OTA_DATA, metaData.getDataBlock())
.toObservable();
}, (metaData, bytes) -> metaData)
.toList()
.toObservable()
)
.flatMap(otaMetaData -> {
return mRxBleConnection.writeCharacteristic(OTA_CONTROL, OTA_DATA_END)
.toObservable();
})

How can I emit if only another stream has already emitted item and it is not consumed in Rxjava

My question looks like simple but please see below.
stream alphabet : ----------------------(A)----------------------------(B)-----
stream number : ---(1)-----(2)------------------(3)----(4)----(5)----------(6)----
emission : true true false true true false
emit true if there was no alphabet item emission
emit false there was alphabet item emission and it is not consumed yet
emit true if last emitted alphabet item has been consumed
is there any good operator for it..?
If Alphabet (steamA) is a Hot Observable, I think this hackish take/skip based code can answer your problem:
Observable.merge(streamA, Observable.just("init"))
.switchMap(a -> {
if ("init".equals(a)) return streamN.map(n -> true);
return Observable.merge(
streamN.take(1).map(n -> false),
streamN.skip(1).map(n -> true)
)
}
It is unclear to me what "alphabet item emission [...] is not consumed yet" means for you. For this, one needs to know or control the recipient of the alphabet. Also there is a second consumer for the boolean output so there is a correlation between two flows.
Observable<String> alphabetFlow = ...
Observable<Integer> numberFlow = ...
AtomicBoolean consumed = new AtomicBoolean(true);
alphabetFlow
.observeOn(Schedulers.io())
.doOnNext(letter -> consumed.set(false))
.doAfterNext(letter -> consumed.set(true))
.subscribe(letter -> {
System.out.println("I'm consuming" + letter));
Thread.sleep(2000); // simulate processing
System.out.println("I've consumed" + letter));
});
numberFlow
.map(number -> consumed.get())
.subscribe(status -> System.out.println("Status: " + status));
Thread.sleep(100000); // in case you have this in main(String[] args)

What is the proper way to write multiple characteristics with RxAndroidBle?

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.
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
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);
}

How to disable a notification with rxandroidble?

I'm currently trying to use rxandroidble in order to replace the native BLE API of Android of one of our app.
How to disable a notification? I'm able to enable it with the sample code, this one:
device.establishConnection(context, false)
.flatMap(rxBleConnection -> rxBleConnection.setupNotification(characteristicUuid))
.doOnNext(notificationObservable -> { // OK })
.flatMap(notificationObservable -> notificationObservable)
.subscribe(bytes -> { // OK });
But in my product I have a use case where I have to disable / enable the notification(s) on demand.
Plus, I tried to directly unsubscribe / reconnect instead of disable / enable the notification but the unsubscribe command is never executed apparently, my hypothesis is because I have a high throughput (my device notifies at 300 - 400Hz), is it plausible?
(I know that BLE is not the most appropriate technology for high throughput but it's for R&D purpose here :) )
Thanks for your help!
Enabling notifications happens whenever the Observable from RxBleConnection.setupNotification() will be subscribed. To disable the notification one must unsubscribe from the above subscription.
There are several ways in which it can be coded. One of them is:
final RxBleDevice rxBleDevice = // your RxBleDevice
final Observable<RxBleConnection> sharedConnectionObservable = rxBleDevice.establishConnection(this, false).share();
final Observable<Boolean> firstNotificationStateObservable = // an observable that will emit true when notification should be enabled and false when disabled
final UUID firstNotificationUuid = // first of the needed UUIDs to enable / disable
final Subscription subscription = firstNotificationStateObservable
.distinctUntilChanged() // to be sure that we won't get more than one enable commands
.filter(enabled -> enabled) // whenever it will emit true
.flatMap(enabled -> sharedConnectionObservable // we take the shared connection
.flatMap(rxBleConnection -> rxBleConnection.setupNotification(firstNotificationUuid)) // enable the notification
.flatMap(notificationObservable -> notificationObservable) // and take the bytes
.takeUntil(firstNotificationStateObservable.filter(enabled1 -> !enabled1)) // and we are subscribing to this Observable until we want to disable - note that only the observable from sharedConnectionObservable will be unsubscribed
)
.subscribe(
notificationBytes -> { /* handle the bytes */ },
throwable -> { /* handle exception */ }
);
Note that in the above example the connection will be closed whenever the last subscription to sharedConnectionObservable will end.
To enable / disable different characteristics you can copy and paste the above code with different Observable<Boolean> as enable / disable inputs and different UUID's.
Kotlin
In kotlin, Use dispose() to disable the notification.
#ENABLE NOTIFICATION
val result = device.establishConnection(false)
.flatMap(rxBleConnection -> rxBleConnection.setupNotification(characteristicUuid))
.doOnNext(notificationObservable -> {
// Notification has been set up
})
.flatMap(notificationObservable -> notificationObservable) // <-- Notification has been set up, now observe value changes.
.subscribe(
bytes -> {
// Given characteristic has been changes, here is the value.
},
throwable -> {
// Handle an error here.
}
);
#DISABLE NOTIFICATION
result.dispose()
Tried to unsubscribe but it didn't work, still not sure which one is best dispose() or unsubscribe()

Use RxJava and Retrofit to iterate through list and augment results based on subqueries

I'm using retrofit and I feel like rxjava (with retrolambda) would be a good fit for the following flow:
get list of widgets (http)
for each widget
a) get a list of articles (http) for the given widget type
b) save all those to db
c) take the first (latest) article in list and update widget.articleName and widget.articleUrl with appropriate values from this article
transform back to list and complete
However I'm unsure what to do after step 2a. Here's my code so far
apiService.getWidgets(token)
.flatMapIterable(widgets -> widgets)
.flatMap(widget -> apiService.getArticles(token, widget.type))
...
.toList()
.subscribe(
modifiedWidgets -> saveWidgets(modifiedWidgets),
throwable -> processWidgetError(throwable)
);
I've played around with some operators but when chaining, I always seem to narrow down
too far (e.g. get a handle on a single article) and then no longer have access to the
original widget to make modifications.
#GET("/widgets")
Observable<List<Widget>> getWidgets(#Header("Authorization") String token);
#GET("/articles")
Observable<List<Article>> getArticles(#Header("Authorization") String token, #Query("type") String type);
You could insert doOnNext at certain points of the stream to add side-effects:
apiService.getWidgets(token)
.flatMapIterable(v -> v)
.flatMap(w ->
apiService.getArticles(token, w.type)
.flatMapIterable(a -> a)
.doOnNext(a -> db.insert(a))
.doOnNext(a -> {
w.articleName = a.name;
w.articleUrl = a.url;
})
.takeLast(1)
.map(a -> w)
)
.toList()
.subscribe(
modifiedWidgets -> saveWidgets(modifiedWidgets),
throwable -> processWidgetError(throwable)
);
Here is runnable example of this.
adding this here since I couldn't find an example of iterating a list that is returned in an object as variable.
getUserAccount(token)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(userResponse -> Observable.just(userResponse.list)) //get list from response
.flatMapIterable(baseDatas -> baseDatas) //make the list iterable
.flatMap(baseData -> //on each project, get the details
getProjectDetails(baseData.name,token)
.subscribeOn(Schedulers.io()) //get network call off the main thread
.observeOn(AndroidSchedulers.mainThread()))
.subscribe(
(dataResponse) -> {
Timber.d( "Got Data Details:" + dataResponse);
},
(error) -> {
Timber.e( "Got Error:" + error.getMessage());
},
() -> {
Timber.d("Completed Data Details");
}
);
akarnokd's answer is quite helpful but that may cause NetworkOnMainThreadException.
To solve that I have added
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
on every requests
apiService.getWidgets(token)
.observeOn(AndroidSchedulers.mainThread()) //added this
.subscribeOn(Schedulers.io()) //added this
.flatMapIterable(v -> v)
.flatMap(w ->
apiService.getArticles(token, w.type)
.observeOn(AndroidSchedulers.mainThread()) //added this
.subscribeOn(Schedulers.io()) //added this
.flatMapIterable(a -> a)
.doOnNext(a -> db.insert(a))
.doOnNext(a -> {
w.articleName = a.name;
w.articleUrl = a.url;
})
.takeLast(1)
.map(a -> w)
)
.toList()
.subscribe(
modifiedWidgets -> saveWidgets(modifiedWidgets),
throwable -> processWidgetError(throwable)
);

Categories

Resources