I'm trying to retrieve a value from a Bluetooth Device.
if (rxBleDevice.connectionState != RxBleConnection.RxBleConnectionState.CONNECTED) {
rxBleDevice!!.establishConnection(false) ? .subscribe({
rxBleConnection ->
Log.d("Device: ", "Connection Established")
val stringDeviceUUID = rxBleDevice.bluetoothDevice.uuids[0].toString()
val charUUID = UUID.nameUUIDFromBytes(stringDeviceUUID.toByteArray())
val count = rxBleConnection.readCharacteristic(charUUID)
println("OUTPUT: ${count}")
}, {
throwable -> Log.d("Device: ", "$throwable")
})
}
I'm using the following dependencies, mainly RxJava and a reactive Bluetooth library called RxAndroidBLE:
implementation 'io.reactivex.rxjava2:rxkotlin:2.1.0'
implementation "com.polidea.rxandroidble2:rxandroidble:1.8.1"
implementation "io.reactivex.rxjava2:rxjava:2.2.7"
My output:
I/System.out: OUTPUT: io.reactivex.internal.operators.single.SingleFlatMap#bf9162d
I have no how to process this object. I believe I should be receiving a simple ByteArray from the Bluetooth device.
An example for the value I should see is datc00099, indicating a count of 99.
You are supposed to subscribe to Single. Following the examples provided by RxAndroidBle, something like this might work in your case:
if (rxBleDevice.connectionState != RxBleConnection.RxBleConnectionState.CONNECTED) {
// Have your charUUID ready. Might need extra null checks for rxBleDevice
val charUUID = rxBleDevice.bluetoothDevice.uuids[0].uuid
rxBleDevice!!.establishConnection(false) ?
.doOnNext {
_ -> Log.d("Device: ", "Connection Established")
} ?
.flatMapSingle {
rxBleConnection -> rxBleConnection.readCharacteristic(charUUID)
} ? .subscribe({
count ->
// count should be in bytes
println("OUTPUT: $count")
}, {
throwable ->
Log.d("ERROR: ", "$throwable")
})
}
Related
I am developing an Android app using RxJava.
I have some API call chains.
verify
consume
val verify = Completable.error(Exception("TEST"))
.doOnSubscribe { Log.d(TAG, "1. verify") }
.doOnComplete{ Log.d(TAG, "1. verify - success") }
.doOnError { Log.e(TAG, "1. verify - failed: ${it.message}") }
.retryWhen { attempts ->
attempts.zipWith(
Flowable.range(1, 3), BiFunction<Throwable, Int, Long> { t, i ->
if (i <= 3) {
1L
} else {
throw t
}
}
).flatMap {
Flowable.timer(it, TimeUnit.SECONDS)
}
}
// 2. consume
val consume = Single.just("SUCCESS")
.doOnSubscribe { Log.d(TAG, "2. consume") }
.doOnSuccess { Log.d(TAG, "2. consume - success") }
.doOnError { Log.e(TAG, "2. consume - failed: ${it.message}", it) }
disposable.add(
verify.andThen (consume)
.subscribeOn(ioScheduler)
.observeOn(uiScheduler)
.subscribe({
Log.d(TAG, "done")
}, { t ->
Log.e(TAG, "failed: ${t.message}", t)
})
);
What I excepted is...
"verify" should be called 3 times every 1 seconds.
After 3 retries failed, it should be done with Error.
But in my case, "consume" was run too.
Why?
I want to skip "consume" if "verify" is failed!
How can I do it?
It's because your code is not failing.
With Flowable.range(1, 3) you create a range from 1 to 3 so the else part of your code is never reached.
Try with Flowable.range(1, 4) and you will see the correct behaviour.
I'm making an Android application with some BLE interraction using the RxAndroidBLE API. I followd examples guidelines and samples from https://github.com/Polidea/RxAndroidBle
I establish a BLE connection with a specified device, later while connected I read and write characteristic with no problem, but when i try to setup notification for the battery level characteristic I get the following throwable error message : Already connected to device with MAC address XX:XX..."
I really don't understand the error in that context since I can read and write in characteristic with no problem.
I want to setup notification for this characteristic after an initial read of its value for specific purpose.
Here is a sample code that reproduce my problem :
private lateinit var device: RxBleDevice
private var connectionObservable: Observable<RxBleConnection>? = null
private var rxBleConnection: RxBleConnection? = null
private val connectionDisposable = CompositeDisposable()
private val connectionStateDisposable = CompositeDisposable()
private var notifyValueChangeSubscription = CompositeDisposable()
var enableBatteryNotificationRunnable: Runnable = Runnable {
enableBatteryNotification()
}
private var myHandler = Handler()
val DELAY_BEFORE_ENABLE_NOTIFICATION: Long = 100
private fun connect() {
connectionObservable = device.establishConnection(false)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
connectionObservable?.let {
connectionDisposable.add(it.subscribe(
{ rxBleConnection ->
this.rxBleConnection = rxBleConnection
},
{ _ ->
Log.e("connect", "connexion error")
})
)
}
val state = device.observeConnectionStateChanges().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
connectionStateDisposable.add(
state.subscribe(
{ connectionState ->
Log.i("connect", "connexion state :$connectionState")
if(connectionState == RxBleConnection.RxBleConnectionState.CONNECTED) {
myHandler.postDelayed(enableBatteryNotificationRunnable, DELAY_BEFORE_ENABLE_NOTIFICATION);
}
}
)
{ _ ->
Log.e("connection listener", "connexion state error")
}
)
}
private fun enableBatteryNotification () {
connectionObservable?.let {
var observableToReturn = it
.flatMap { it.setupNotification(UUID_BATTERY_LEVEL) }
.doOnNext {
Log.i("NOTIFICATION", "doOnNext")
}
.flatMap { it }
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
notifyValueChangeSubscription.add(observableToReturn.subscribe({ bytes ->
var strBytes = String(bytes)
Log.i("NOTIFICATION", "value change: $strBytes")
},
{ throwable ->
Log.e("NOTIFICATION", "Error in notification process: " + throwable.message)
})
)
}
}
Thanks in advance for any help :)
setupNotification returns “Error already connected” whereas no connection request is send
Two connection requests are actually made — hence the error. From the RxBleDevice.establishConnection() Javadoc:
* Establishes connection with a given BLE device. {#link RxBleConnection} is a handle, used to process BLE operations with a connected
* device.
In your code there are two subscriptions to the establishConnection() Observable.
private lateinit var device: RxBleDevice
private var connectionObservable: Observable<RxBleConnection>? = null
private var rxBleConnection: RxBleConnection? = null
private val connectionDisposable = CompositeDisposable()
private val connectionStateDisposable = CompositeDisposable()
private var notifyValueChangeSubscription = CompositeDisposable()
var enableBatteryNotificationRunnable: Runnable = Runnable {
enableBatteryNotification()
}
private var myHandler = Handler()
val DELAY_BEFORE_ENABLE_NOTIFICATION: Long = 100
private fun connect() {
connectionObservable = device.establishConnection(false)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
connectionObservable?.let {
connectionDisposable.add(it.subscribe( // << Here is the first subscription
{ rxBleConnection ->
this.rxBleConnection = rxBleConnection
},
{ _ ->
Log.e("connect", "connexion error")
})
)
}
val state = device.observeConnectionStateChanges().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
connectionStateDisposable.add(
state.subscribe(
{ connectionState ->
Log.i("connect", "connexion state :$connectionState")
if(connectionState == RxBleConnection.RxBleConnectionState.CONNECTED) {
myHandler.postDelayed(enableBatteryNotificationRunnable, DELAY_BEFORE_ENABLE_NOTIFICATION);
}
}
)
{ _ ->
Log.e("connection listener", "connexion state error")
}
)
}
private fun enableBatteryNotification () {
connectionObservable?.let {
var observableToReturn = it
.flatMap { it.setupNotification(UUID_BATTERY_LEVEL) }
.doOnNext {
Log.i("NOTIFICATION", "doOnNext")
}
.flatMap { it }
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
notifyValueChangeSubscription.add(observableToReturn.subscribe({ bytes -> // << Here is the second subscription
var strBytes = String(bytes)
Log.i("NOTIFICATION", "value change: $strBytes")
},
{ throwable ->
Log.e("NOTIFICATION", "Error in notification process: " + throwable.message)
})
)
}
}
This situation is a common source of confusion for people learning RxJava. There are three paths to fix your situation. From least to most amount of work:
Share the establishConnection Observable
It is possible to share a single RxBleConnection with RxReplayingShare. Change this:
connectionObservable = device.establishConnection(false)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
To this:
connectionObservable = device.establishConnection(false)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(ReplayingShare.instance())
Use the rxBleConnection: RxBleConnection? property
Instead of:
connectionObservable?.let {
var observableToReturn = it
.flatMap { it.setupNotification(UUID_BATTERY_LEVEL) }
.doOnNext {
Log.i("NOTIFICATION", "doOnNext")
}
.flatMap { it }
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
notifyValueChangeSubscription.add(observableToReturn.subscribe({ bytes -> // << Here is the second subscription
var strBytes = String(bytes)
Log.i("NOTIFICATION", "value change: $strBytes")
},
{ throwable ->
Log.e("NOTIFICATION", "Error in notification process: " + throwable.message)
})
)
}
Make it:
rxBleConnection?.let {
var observableToReturn = rxBleConnection.setupNotification(UUID_BATTERY_LEVEL)
.doOnNext {
Log.i("NOTIFICATION", "doOnNext")
}
.flatMap { it }
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
notifyValueChangeSubscription.add(observableToReturn.subscribe({ bytes -> // << Here is the second subscription
var strBytes = String(bytes)
Log.i("NOTIFICATION", "value change: $strBytes")
},
{ throwable ->
Log.e("NOTIFICATION", "Error in notification process: " + throwable.message)
})
)
}
This is discouraged as you may end up with a RxBleConnection that is no longer valid as it may have been disconnected before calling enableBatteryNotification()
Change the flow of your code to use a single .subscribe()
This is a custom solution tailored to your exact use-case. Unfortunately with the information you have added is not enough to create a drop-in code replacement but it could look something like this:
device.establishConnection(false)
.flatMap { connection ->
Observable.merge(
connection.readCharacteristic(uuid0).map { ReadResult(uuid0, it) }.toObservable(),
connection.setupNotification(uuid1).flatMap { it }.map { NotifyResult(uuid1, it) }.delaySubscription(100, TimeUnit.MILLISECONDS)
)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ /* handle ReadResult/NotifyResult */ },
{ /* handle potential errors */ }
)
Where ReadResult and NotifyResult would be data class that take UUID and ByteArray
I use the following code to listen for an unsolicited message from my Bluetooth LE device. I am expecting a byte array with text that says "datc00099", for example. Instead, the mapper is returning a null SingleSource. What could be going wrong here?
val charUUID = scanResult.scanRecord.serviceUuids?.get(0)?.uuid
/* Establish connection to device */
rxBleDevice!!.establishConnection(false) ?
.doOnNext {
_ ->
Log.d("Device: ", "Connection Established")
} ?
.flatMapSingle {
rxBleConnection ->
charUUID ? .let {
rxBleConnection.readCharacteristic(it)
}
} ? .subscribe({
count ->
// count should be in bytes
println("OUTPUT: $count")
}, {
throwable ->
Log.d("ERROR: ", "$throwable")
})
Error:
D/ERROR:: java.lang.NullPointerException: The mapper returned a null SingleSource
Debug:
I have these two Observables in Kotlin where is just act as a timer and another one is HTTP network call response Observer.
timerDisposable = Observable.timer(daleyABCControlResetSeconds, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
.doOnNext {
if (getABCUpdate() != null) {
Log.d("ABC", "Media status reset after 3 seconds: ")
updateABCResponse(getABCUpdate())
}
}.subscribe()
disposable = audioApi.setABCUpdate(abcUpdate)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
timerDisposable.dispose()
updateABCResponse(it)
Log.d("ABC", "Media Status updated:")
}, {
Log.d("ABC", "Error updating Media Status: " + it.message)
isABCControlChangeRequested = false
})
I am not satisfied with this approach, can anyone please direct me right direction to use the rx's full potential. Thanks in advance.
EDIT
Observable.combineLatest(Observable.timer(daleyABCControlResetSeconds, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
.doOnNext {
if (getABCUpdate() != null) {
Log.d("ABC", "Media status reset after 3 seconds: ")
updateABCResponse(getABCUpdate())
}
},
audioApi.setABCUpdate(abcUpdate)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()),
BiFunction<Long, ABCStatusUpdate, ABCStatusUpdate> { _, abcStatusUpdate ->
abcStatusUpdate
})
.subscribe({
timerDisposable.dispose()
updateABCResponse(abcStatusUpdate)
Log.d("ABC", "Media Status updated:")
}, {
Log.d("ABC", "Error updating Media Status: " + abcStatusUpdate.vol)
isABCControlChangeRequested = false
})
You can use combinelatest, zip or merge for combinig. I think in your case combinelatest is suitable
Observable.combineLatest(
Observable.timer(daleyABCControlResetSeconds, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
.doOnNext {
if (getABCUpdate() != null) {
Log.d("ABC", "Media status reset after 3 seconds: ")
updateABCResponse(getABCUpdate())
}
},
audioApi.setABCUpdate(abcUpdate)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()),
BiFunction<Long, YourApiResponseType, YourApiResponseType> { _, response ->
response})
.subscribe({
timerDisposable.dispose()
updateABCResponse(it)
Log.d("ABC", "Media Status updated:")
}, {
Log.d("ABC", "Error updating Media Status: " + it.message)
isABCControlChangeRequested = false
})
UPD:
You can change your code like this:
Observable.timer(5, TimeUnit.SECONDS, AndroidSchedulers.mainThread()).startWith(-1L)
.doOnNext {
if (it == -1L) return#doOnNext
//your condition
}
I'm doing an app that works as a remote control to a ventilator using RxAndroidBle. I have a problem with the unsubscribe because when I use
.flatMap(rxBleConnection -> rxBleConnection.writeCharacteristic(Uuids.UUID_RX, flaktCommandConcat.getBytes()))
and after that I use subscription.unsubscribe(); the writeCharacteristics doesn´t work because the unsubscribe runs always first and the connection disconect before the data was sent.
What I need is:
When I click the button I want to connect to the ventilator
Then send all values
And then disconnect.
If I repeat the procedure, it will need to do the same thing over and over again.
Can some one help me with some idea? I tried to use .delay(1000, Time.MILISECONDS) and it worked but it took a long time to send the information to the ventilator.
This is my code:
public void writeRxCharacteristics(String flaktCommandConcat){
rxBleDevice = rxBleClient.getBleDevice(Uuids.DEVICE_ADDRESS);
subscription = rxBleDevice.establishConnection(true) //false
.observeOn(AndroidSchedulers.mainThread())
.flatMap(rxBleConnection -> rxBleConnection.createNewLongWriteBuilder()
.setCharacteristicUuid(Uuids.UUID_RX)
.setBytes(flaktCommandConcat.getBytes())
.build())
.subscribe(
byteArray -> {
Log.d("CharacteristicValue","WRITE: " + Arrays.toString(byteArray));
},
throwable -> {
Log.d("CharacteristicValue","Throwable: " + throwable.toString());
rxBleActivity.onScanFailure(throwable, getContext());
}
);
rxBleDevice.observeConnectionStateChanges()
.observeOn(AndroidSchedulers.mainThread())
.delay(1000, TimeUnit.MILLISECONDS)
.subscribe(
rxBleConnectionState -> {
Log.d("RxBleConnectionState", " CON_STATUS: " + rxBleConnectionState);
disconnect();
},
throwable -> {
Log.d("ConnectionStateChanges","Throwable: " + throwable.toString());
}
);
}
public void disconnect() {
if (subscription != null && !subscription.isUnsubscribed()) {
subscription.unsubscribe();
subscription = null;
}
Log.d("CONNECTION2", " CON_STATUS: " + rxBleDevice.getConnectionState().toString());
}
it looks that you don't need a long write here. Is your data longer than 20 bytes?
Anyway, the library releases the connection when the Observable<RxBleConnection> is unsubscribed. What I'd do if I were you is to:
public void writeRxCharacteristics(String flaktCommandConcat){
rxBleDevice = rxBleClient.getBleDevice(Uuids.DEVICE_ADDRESS);
rxBleDevice.establishConnection(true) //false
.flatMap(rxBleConnection -> rxBleConnection.createNewLongWriteBuilder()
.setCharacteristicUuid(Uuids.UUID_RX)
.setBytes(flaktCommandConcat.getBytes())
.build()
)
.take(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
byteArray -> {
Log.d("CharacteristicValue","WRITE: " + Arrays.toString(byteArray));
},
throwable -> {
Log.d("CharacteristicValue","Throwable: " + throwable.toString());
rxBleActivity.onScanFailure(throwable, getContext());
}
);
Please make sure you're not overusing the long write. It has a known bug (unrelated) in 1.2.0 which was recently fixed in 1.3.0-SNAPSHOT.