I'm trying to read an unsolicited data stream from my Bluetooth device. The data should appear as a byte array. Unfortunately, the UUID I'm supplying doesn't seem to be the correct one. What could be going wrong?
val stringDeviceUUID = rxBleDevice.bluetoothDevice.uuids[0].toString()
val charUUID = UUID.fromString(stringDeviceUUID)
println("$stringDeviceUUID = $charUUID?")
/* If device if it is not already connected... */
if (rxBleDevice.connectionState != RxBleConnection.RxBleConnectionState.CONNECTED) {
/* Establish connection to device */
device !!.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("Device: ", "$throwable")
})
}
I get the following error:
D/Device:: com.polidea.rxandroidble2.exceptions.BleCharacteristicNotFoundException: Characteristic not found with UUID 00001101-0000-1000-8000-00805f9b34fb
What is wrong with this UUID? This is precisely the UUID I retrieve from the device so why won't it let me communicate?
It can't be seen from your code snippet, but are rxBleDevice and device the same RxAndroidBle instance? If not, perhaps replace device !!.establishConnection(false) with rxBleDevice.establishConnection(false)
Related
I have the following code using an. RxAndroidBle Bluetooth Low Energy Connection:
private val connectionDisposable = CompositeDisposable()
private fun writeBle(writeCharacteristicUuid: UUID, command: ByteArray)
if (bleDevice.connectionState == RxBleConnection.RxBleConnectionState.CONNECTED) {
activeConnection
.flatMapSingle {
it.writeCharacteristic(writeCharacteristicUuid, command)
}
.subscribe({
Log.d(
TAG,
"${connectionDisposable.size()} - Command successful: ${it.toHexString()}"
)
})
{ Log.e(TAG, "Error executing command: $it") }
.let { connectionDisposable.add(it) }
} else {
Log.e(TAG, "You are not connected")
}
}
The connectionDisposable is .clear()ed when the connection to the device is closed.
But until then several hundreds, thousands or more disposable will land in the connectionDisposable.
I am not completely clear if this presents a Problem in regard to memory usage, or whether I am missing the right way to execute a lot of write commands (that should not be send simultaneously to the device).
i have an application, and my application can connect to a bluetooth device.
After that, i want to send message (Int) to my Blutooth Low Energy device.
I have this code, but i can't figure it out what is the problem.
If you want i have : Characteristic UUID, Service UUID.
Really, i need your help...
I've edited the question :
My code :
val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
lateinit var bluetoothAdapter: BluetoothAdapter
val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
bluetoothAdapter = bluetoothManager.adapter
settingViewModel.bluetooth(bluetoothAdapter = bluetoothAdapter)
val mReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
val action = intent.action
if (action == BluetoothAdapter.ACTION_STATE_CHANGED) {
val state = intent.getIntExtra(
BluetoothAdapter.EXTRA_STATE,
BluetoothAdapter.ERROR
)
when (state) {
BluetoothAdapter.STATE_OFF -> {
settingViewModel.setIsConnected(false)
//settingViewModel.stopScan()
settingViewModel.setListDevices(null)
}
BluetoothAdapter.STATE_ON -> {
settingViewModel.setIsConnected(true)
//scan()
settingViewModel.setListDevices(bluetoothAdapter.bondedDevices)
context!!.unregisterReceiver(this)
}
}
}
}
}
context.registerReceiver(mReceiver, filter)
val SERVICE_UUID = "00000000-0001-11e1-9ab4-0002a5d5c51c"
val ConfigCharacteristic = descriptorOf(
service = SERVICE_UUID,
characteristic = "00E00000-0001-11e1-ac36-0002a5d5c51b",
descriptor = "00000000-0000-0000-0000-000000000000",
)
Button(
onClick = {
if (settingViewModel.isConnected.value == true) {
coroutine.launch(Dispatchers.IO) {
try {
settingViewModel.peripheral.write(ConfigCharacteristic, byteArrayOf(1))
} catch (e: Exception) {
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
}
}
}
// try {
// val Service =
// settingViewModel.deviceSocket.value.get .getService(UUID.fromString("0000ffe0-0000-1000-8000-00805f9b34fb"))
// val charac: BluetoothGattCharacteristic =
// Service.getCharacteristic(UUID.fromString("00E00000-0001-11e1-ac36-0002a5d5c51b"))
// settingViewModel.deviceSocket.value!!.outputStream.write("1".toByteArray())
// } catch (e: Exception) {
// Toast.makeText(context, e.message.toString(), Toast.LENGTH_LONG).show()
// }
}
) {
Text(text = "HelloWorld")
}
I Already have the mac adress, the caracteristic and the service UUID of the device i want to connect to.
Again, i really need your help
First of all:
When developing an app for a BLE device it is best to first use a generic BLE scanner app to test the connection and to find out which commands need to be sent. If you confirm that the BLE device works as expected you can continue with your own custom app. I would recommend nRF Connect for this task.
Regarding your problem:
There are still many things missing from your sourcecode. You said you can connect to the device but have problems sending a message. Your code does not contain anything related to a BLE connection so I can only assume that you connected to the device using the Bluetooth settings of your phone. This would be correct for Bluetooth Classic but BLE requires you to connect through your own custom app.
The Ultimate Guide to Android Bluetooth Low Energy explains all steps necessary for a successful BLE connection. These steps are:
Setting the correct permissions
Scan for nearby BLE devices
Connect to a BLE device of your choosing
Scan for Services
Read and Write a characteristic of your choosing
All these steps are explained in the Guide using Kotlin as programming language.
I'm trying to subscribe to notification changes from a BLE device that I built, and print the value of the notification. I know the characteristic UUID that I want to read the notifications from:
scanSubscription = rxBleClient.scanBleDevices(
ScanSettings.Builder().build(),
ScanFilter.Builder().setDeviceName("MyDevice").build() // Filter for devices named MyDevice
)
.take(1) // stop the scan when a matching device will be scanned for the first time
.flatMap {
val device = it.bleDevice
device.establishConnection(false)
.flatMap < Any > {
rxBleConnection: RxBleConnection - > rxBleConnection.setupNotification(charUUID)
}
.doOnNext {
notificationObservable: Any ? - >
}
.flatMap < Any > {
notificationObservable: Any ? - > notificationObservable
} // <-- Notification has been set up, now observe value changes.
}
.subscribe({ /* written */ }, {
throwable - >
// Handle an error here.
// println("Scan Error: $throwable")
})
To prove that the device is working as intended, I observe the notification changes using the BLE Scanner app for Android.
When I wave my hand over the sensor, the value changes. In my case, it increments.
My question is how can I print that value when the notification value changes? When debugging, I can't get the Count to print. It doesn't even appear in my console.
Usually on Android one uses Log class to log data to logcat. You could use it like this:
scanSubscription = rxBleClient.scanBleDevices(
ScanSettings.Builder().build(),
ScanFilter.Builder().setDeviceName("MyDevice").build() // Filter for devices named MyDevice
)
.take(1) // stop the scan when a matching device will be scanned for the first time
.flatMap {
val device = it.bleDevice
device.establishConnection(false)
.flatMap < Any > {
rxBleConnection: RxBleConnection - > rxBleConnection.setupNotification(charUUID)
}
.doOnNext {
notificationObservable: Any ? - >
}
.flatMap < Any > {
notificationObservable: Any ? - > notificationObservable
} // <-- Notification has been set up, now observe value changes.
}
.subscribe(
{ notification -> Log.i("Notification!", notification.contentToString()) },
{ throwable -> Log.e("Whoops!", "Scan Error", throwable) }
)
I'm trying to listen to whether my app is connected to a bluetooth device. I'm trying to print the connectionState result but the application is not even reaching the first println so I can't check what they may be. I want to enumerate the possible connection states, and then to adjust the UI in response. How can I do this?
val rxBleClient = RxBleClient.create(this.context!!)
val bleDevice = rxBleClient.getBleDevice("34:81:F4:3C:2D:7B")
val disposable = bleDevice.establishConnection(true) // <-- autoConnect flag
.subscribe({
rxBleConnection ->
// All GATT operations are done through the rxBleConnection.
bleDevice.observeConnectionStateChanges()
.subscribe({
connectionState ->
println("Connection State: $connectionState")
if (connectionState != null) {
enableBluetooth.setBackgroundResource(R.drawable.bluetooth_on) // Change image
deviceConnected.setText(R.string.connected_to_hooplight) // Changed text
} else {
enableBluetooth.setBackgroundResource(R.drawable.bluetooth_off) // Change image
deviceConnected.setText(R.string.connect_to_hooplight) // Changed text
}
}, {
throwable ->
Log.d("Error: ", throwable.toString())
})
}, {
throwable ->
// Handle an error here.
Log.d("Error: ", throwable.toString())
})
// When done... dispose and forget about connection teardown :)
disposable.dispose()
There are two things to the above code:
disposable.dispose() should be called when the flow that was subscribed is no longer needed. If the dispose method is called right after subscription nothing will actually happen. That is why even the first println does not show up.
The bleDevice.establishConnection() and bleDevice.observeConnectionStateChanges() are not dependent on each other functionally. Connection does not have to be established to observe changes. Even if one would start observing the changes after the connection is on it will only get info when the connection is closed (because it is the first change since then)
A better way would be to decouple the observing connection changes flow and the actual connection. An example code:
val observingConnectionStateDisposable = bleDevice.observeConnectionStateChanges()
.subscribe(
{ connectionState ->
Log.d("Connection State: $connectionState")
if (connectionState == RxBleConnectionState.CONNECTED) { // fixed the check
enableBluetooth.setBackgroundResource(R.drawable.bluetooth_on) // Change image
deviceConnected.setText(R.string.connected_to_hooplight) // Changed text
} else {
enableBluetooth.setBackgroundResource(R.drawable.bluetooth_off) // Change image
deviceConnected.setText(R.string.connect_to_hooplight) // Changed text
}
},
{ throwable -> Log.d("Error: ", throwable.toString()) }
)
val connectionDisposable = bleDevice.establishConnection(false)
.subscribe(
{ Log.d("connection established") }, // do your thing with the connection
{ throwable -> Log.d("Error on connection: ${throwable}") }
)
I'm updating an app to use RxAndroidBLE, and struggling with how to translate my existing callback pattern into an Rx pattern. In particular, I need to respond to characteristic notifications in different ways depending on the received data, and send a specific write command back to the device (which will then cause the characteristic to be updated, in a loop).
The rationale behind this is that the BLE device I'm integrating with has a special custom characteristic, to which we can send different commands and then listen for data back.
I've read up lots about chaining commands using RxBLE, but none seem to address my particular query, which is how to send a command back to the device on observing a change notification (since the connection itself seems to be out of scope by the time we get to the observable block). What is the "Rx Way" of doing this?
For clarity, this is the entire flow of my BLE service:
scan for devices with a filter on our custom characteristic
connect to a found device
read a couple of standard characteristics (strings), and store these in our data model
if and only if one of the characteristics matches one of an array of strings, proceed to 5. otherwise, dispose of the connection.
subscribe to our custom "control" characteristic ("CC") for change notifications
send command 1 to CC. this should trigger answer 1 to be set in CC, so our handler is called
perform some calculations on answer 1 and save to our model. send command 2 (which includes these modified values, so we can't determine this at compile time) to CC. this should trigger answer 2 in CC.
on receiving answer 2, send command 3, which should trigger answer 3.
on reciving answer 3, parse into an int value. if answer 3 == 0, dispose of the connection - we are done.
answer 3 > 0, so send command 4. this will trigger answer 4.
perform some calculations on answer 4 and store the results in our model
then send command 5, which will actually trigger answer 3 (both commands 5 and 3 trigger answer 3). since we are already subscribed to answer 3, this takes us back to step 9. above - we keep looping until answer 3 is 0 (ie. we have saved all the data).
Edit: I was loathe to share code, as I'm well aware there is no possible way the following will work - but I'm hoping it describes what I'm trying to do even if the syntax won't even compile:
connectedDevice.connectionDisposable = connectedDevice.getRxDevice().establishConnection(false)
.observeOn(AndroidSchedulers.mainThread())
.flatMapSingle(rxBleConnection -> rxBleConnection.readCharacteristic(BATTERY_CHARACTERISTIC_UUID))
.doOnNext(bytes -> {
//store the battery info in our model here
})
.flatMapSingle(rxBleConnection -> rxBleConnection.readCharacteristic(SERIAL_NUMBER_CHARACTERISTIC_UUID))
.doOnNext(bytes -> {
//store the serial number info in our model here
//TODO: how do we only proceed to the subscription if serialNumber is correct?
}
)
.flatMap(rxBleConnection -> rxBleConnection.setupNotification(CUSTOM_CHARACTERISTIC_UUID))
.doOnNext(notificationObservable -> {
// Notification has been set up
rxBleConnection.writeCharacteristic(CUSTOM_CHARACTERISTIC_UUID, COMMAND_1); //we can't do this because rxBleConnection is out of scope!
})
.flatMap(notificationObservable -> notificationObservable) // <-- Notification has been set up, now observe value changes.
.subscribe(
bytes -> {
// Given characteristic has been changes, here is the value.
switch(commandFromBytes(bytes)){
case answer1:
int newCommand = doSomethingWith(bytes);
rxBleConnection.writeCharacteristic(CUSTOM_CHARACTERISTIC_UUID, COMMAND_2 + newCommand);
break;
case answer2:
rxBleConnection.writeCharacteristic(CUSTOM_CHARACTERISTIC_UUID, COMMAND_3);
break;
case answer3:
if(bytes <= 0){
connectedDevice.connectionDisposable.dispose();
}
else{
rxBleConnection.writeCharacteristic(CUSTOM_CHARACTERISTIC_UUID, COMMAND_4);
}
break;
case answer4:
doSomethingLongWindedWith(bytes);
//then
rxBleConnection.writeCharacteristic(CUSTOM_CHARACTERISTIC_UUID, COMMAND_5);
//command 5 will cause answer3 to be notified, so we loop back above
break;
}
},
throwable -> {
// Handle an error here.
}
);
Edit 2: after playing bracket tango for a bit, I think I'm close to a solution here:
connectedDevice.connectionDisposable = connectedDevice.getRxDevice().establishConnection(false)
.observeOn(AndroidSchedulers.mainThread())
.flatMapSingle(rxBleConnection -> rxBleConnection.readCharacteristic(BATTERY_CHARACTERISTIC_UUID)
.doOnNext(bytes -> {
connectedDevice.setBatLevel(bytes);
})
.flatMapSingle(rxBleConnection2 -> rxBleConnection.readCharacteristic(SERIAL_NUMBER_CHARACTERISTIC_UUID))
.doOnNext(bytes -> {
connectedDevice.setSerialNum(bytes);
//we also notify a singleton listener here
}
)
.flatMap(rxBleConnection3 -> {
if (serialNumberIsCorrect(connectedDevice.getSerialNum())) {
rxBleConnection.setupNotification(CUSTOM_CHARACTERISTIC_UUID).subscribe(
bytes -> {
// Given characteristic has been changes, here is the value.
switch (commandFromBytes(bytes)) {
case answer1:
int newCommand = doSomethingWith(bytes);
rxBleConnection.writeCharacteristic(CUSTOM_CHARACTERISTIC_UUID, COMMAND_2 + newCommand);
break;
case answer2:
rxBleConnection.writeCharacteristic(CUSTOM_CHARACTERISTIC_UUID, COMMAND_3);
break;
case answer3:
if (bytes <= 0) {
//we also notify a singleton listener here
connectedDevice.connectionDisposable.dispose();
} else {
rxBleConnection.writeCharacteristic(CUSTOM_CHARACTERISTIC_UUID, COMMAND_4);
}
break;
case answer4:
doSomethingLongWindedWith(bytes);
//then
rxBleConnection.writeCharacteristic(CUSTOM_CHARACTERISTIC_UUID, COMMAND_5);
//command 5 will cause answer3 to be notified, so we loop back above
break;
}
},
throwable -> {
// Handle an error here.
}
);
} else {
connectedDevice.connectionDisposable.dispose();
}
}
.doOnNext(notificationObservable -> {
// Notification has been set up
if (serialNumberIsCorrect(connectedDevice.getSerialNum())) {
rxBleConnection.writeCharacteristic(CUSTOM_CHARACTERISTIC_UUID, COMMAND_1);
}
})
));
The best approach, according to this Jake Wharton's talk would be to construct an Observable that would emit just values that are needed for updating your model.
(example in Kotlin)
We could have these outputs of the Observable:
sealed class ConnectionEvent {
object CloseConnection : ConnectionEvent() // dummy event to notify when the connection can be closed
data class SerialNumber(val byteArray: ByteArray) : ConnectionEvent()
data class BatteryLevel(val byteArray: ByteArray) : ConnectionEvent()
data class Answer4(val byteArray: ByteArray) : ConnectionEvent()
}
And the whole flow could look like this:
bleDevice.establishConnection(false)
.flatMap { connection ->
val batteryLevelSingle = connection.readCharacteristic(batteryLevelCharUuid).map { ConnectionEvent.BatteryLevel(it) as ConnectionEvent }
val serialNumberSingle = connection.readCharacteristic(serialNumberCharUuid).map { ConnectionEvent.SerialNumber(it) }.cache() // cache as the output will be used by the continuation observable as well and we do not want to re-read the serial number
val continuationObservable: Observable<ConnectionEvent> = serialNumberSingle // continuation observable will work if the serial number matches
.flatMapObservable {
when {
it != matchingSerialNumber -> Observable.just(ConnectionEvent.CloseConnection as ConnectionEvent) // close connection if serial does not match
else -> createContinuationObservable(connection) // create flow for getting more data via additional writes and notifications
}
}
Observable.concat( // the actual flow of the whole connection
batteryLevelSingle.toObservable(), // we are starting with getting the battery level and emitting it
serialNumberSingle.toObservable(), // we are getting the serial number and emitting it
continuationObservable // if the serial number matches we continue with notifications and getting more data. otherwise CloseConnection
)
}
.takeWhile { it != ConnectionEvent.CloseConnection } // if the connection is to be closed -> unsubscribe
.subscribe(
{ connectionEvent ->
when(connectionEvent) {
is ConnectionEvent.SerialNumber -> { /* Update model */ }
is ConnectionEvent.BatteryLevel -> { /* Update model */ }
is ConnectionEvent.Answer4 -> { /* Update model */ }
}
},
{ /* handle errors */ }
)
where the write/notification dance is:
private fun createContinuationObservable(connection: RxBleConnection): Observable<ConnectionEvent> {
return connection.setupNotification(customCharUuid)
.flatMap { ccNotifications ->
ccNotifications.flatMap {
when (answerFromBytes(it)) {
answer1 -> connection.writeCharacteristic(customCharUuid, command2FromAnswer1Bytes(it)).ignoreEmissions()
answer2 -> connection.writeCharacteristic(customCharUuid, command3).ignoreEmissions()
answer3 -> when (it.isEmpty()) {
true -> Observable.just(ConnectionEvent.CloseConnection)
else -> connection.writeCharacteristic(customCharUuid, command4).ignoreEmissions()
}
answer4 -> connection.writeCharacteristic(customCharUuid, command5).ignoreEmissions()
.startWith(Observable.just(ConnectionEvent.Answer4(it)))
else -> Observable.error(Exception("Unexpected answer! => ${answerFromBytes(it)}"))
}
}
.startWith(connection.writeCharacteristic(customCharUuid, command1).ignoreEmissions()) // initiate with the command1
}
}
I have used an extension function for more clarity:
fun Single<ByteArray>.ignoreEmissions() = this.toCompletable().toObservable<ConnectionEvent>()
Edit:
I have changed the code a bit to get rid of CloseConnection event and leverage the completions of the observables. So now the outputs look like this:
sealed class ConnectionEvent {
data class SerialNumber(val byteArray: ByteArray) : ConnectionEvent()
data class BatteryLevel(val byteArray: ByteArray) : ConnectionEvent()
data class Answer4(val byteArray: ByteArray) : ConnectionEvent()
}
The main flow:
bleDevice.establishConnection(false)
.map { connection ->
val batteryLevelSingle = connection.readCharacteristic(batteryLevelCharUuid).map { ConnectionEvent.BatteryLevel(it) as ConnectionEvent }
val serialNumberSingle = connection.readCharacteristic(serialNumberCharUuid).map { ConnectionEvent.SerialNumber(it) }.cache() // cache as the output will be used by the continuation observable as well and we do not want to re-read the serial number
val continuationObservable: Observable<ConnectionEvent> = serialNumberSingle // continuation observable will work if the serial number matches
.flatMapObservable {
if (it == matchingSerialNumber) createContinuationObservable(connection) // create flow for getting more data via additional writes and notifications
else Observable.empty() // do not continue if serial number does not match
}
Observable.concat( // the actual flow of the whole connection
batteryLevelSingle.toObservable(), // we are starting with getting the battery level and emitting it
serialNumberSingle.toObservable(), // we are getting the serial number and emitting it
continuationObservable // if the serial number matches we continue with notifications and getting more data. otherwise CloseConnection
)
}
.publish {
// create a Completable from the above Observable.concat()
val dataDownloadCompletable = it.take(1) // take the first emission (there will be only one)
.flatMapCompletable { it.ignoreElements() } // and wait until the first emission completes
it.takeUntil(dataDownloadCompletable.toObservable<Any>()) // when dataDownloadCompletable completes —> unsubscribe from the upstream, mainly .establishConnection() to close it
}
.flatMap { it } // unwrap the above flow
.subscribe(
{ connectionEvent ->
when (connectionEvent) {
is ConnectionEvent.SerialNumber -> { /* Update model */ }
is ConnectionEvent.BatteryLevel -> { /* Update model */ }
is ConnectionEvent.Answer4 -> { /* Update model */ }
}
},
{ /* handle errors */ }
)
Write/notification part:
private fun createContinuationObservable(connection: RxBleConnection): Observable<ConnectionEvent> {
return connection.setupNotification(customCharUuid)
.flatMap { ccNotifications ->
ccNotifications.map { Pair(answerFromBytes(it), it) } // map every response to a pair of <answer, bytes>
.startWith(connection.writeCharacteristic(customCharUuid, command1).ignoreEmissions()) // and start with writing command1 to initiate the data exchange
}
.takeWhile { (answer, bytes) -> !(answer == answer3 && bytes.isEmpty()) } // end the createContinuationObservable on the first answer3 with an empty bytes
.flatMap<ConnectionEvent> { (answer, bytes) ->
when (answer) {
answer1 -> connection.writeCharacteristic(customCharUuid, command2FromAnswer1Bytes(bytes)).ignoreEmissions()
answer2 -> connection.writeCharacteristic(customCharUuid, command3).ignoreEmissions()
answer3 -> connection.writeCharacteristic(customCharUuid, command4).ignoreEmissions()
answer4 -> Observable.just(ConnectionEvent.Answer4(bytes)) // when answer4 is received emit actionable item to update the model
.concatWith(connection.writeCharacteristic(customCharUuid, command5).ignoreEmissions()) // and send the next command5
else -> Observable.error(Exception("Unexpected answer! => $answer"))
}
}
}
And the extension:
fun <T> Single<ByteArray>.ignoreEmissions() = this.toCompletable().toObservable<T>()