I have an application that sends and receives data using BLE. Originally it was classical bluetooth but I was tasked with changing the project to BLE.
So far I have succeeded in sending data but not receiving it. The main activity contains multiple fragments. On of those is responsible for sending data where as the other sends a request and then receives a response with the data from the BLE device.
one fragment is called Parameter and the other Memory. Each fragment has a viewmodel and repository as the architecture is based on MVVM. the flow is as follows:
Parameter fragment -> View model -> repository -> DataStore class -> DataStore uses instance from BLEConnectionManager class to send the data of the corresponding parameter. Example of a function in DataStore:
fun sendToolAddressParam(data: Int){
toolAddress = data
var value = Integer.toHexString(data)
if (value.length == 1) value = "0$value"
val message = WriteCommandCodes.TOOL_ADDRESS.value + " " + value + " " + WriteCommandCodes.EXTRA2.value
BleConnectionManager.sendMessage(message)
Timber.i("Payload: $message")
}
There are also functions that request data:
fun requestToolAddress(){
BleConnectionManager.requestReadValues(ReadRequestCodes.TOOL_ADDRESS.value)
}
in the BLE class the functions are written as the following:
fun write(message:String){
val bytes = BigInteger(message.replace("\\s".toRegex(), ""), 16).toByteArray()
Timber.i("Bytes value ---> ${bytes.toHexString()}")
val device = getBleDevice()
// val characteristicRX = getBleCharacteristic() val characteristicRX = bluetoothGattRef.getService(XpressStreamingServiceUUID).getCharacteristic( peripheralRX) writeCharacteristic(device, characteristicRX, bytes) }
fun requestReadValues(requestCode:String){
if(isConnected.value!!){
write(requestCode)
}else{
Timber.e("Make sure that you connected and paired with the desired device.")
}
}
fun sendMessage(message:String){
Timber.i("Check if isConnected = true --> ${isConnected.value}")
if(isConnected.value == true){
write(message)
}else{
Timber.e("Make sure that you connected and paired with the desired device.")
}
}
Now here is my issue I want to receive data from the BLE device after I send the request, the device's documentation when it comes to BLE data exchange is here: https://docs.silabs.com/gecko-os/1/bgx/latest/ble-services
Now I have a function that supposedly receives the incoming messages but this was when classical bluetooth was used.
fun readIncomingMessages(message: String){
when{
message.startsWith(com.brainsocket.milink.data.bluetooth.ReadResponseCodes.KEY_ADDRESS.value) ->{
EventBus.getDefault().post(
ReadKeyAddressEvent(message.substring(com.brainsocket.milink.data.bluetooth.ReadResponseCodes.KEY_ADDRESS.value.length+1, com.brainsocket.milink.data.bluetooth.ReadResponseCodes.KEY_ADDRESS.value.length+3))
)
Timber.i("Message received: $message")
}
message.startsWith(com.brainsocket.milink.data.bluetooth.ReadResponseCodes.TOOL_ADDRESS.value) ->{
EventBus.getDefault().post(
ReadToolAddressEvent(message.substring(com.brainsocket.milink.data.bluetooth.ReadResponseCodes.TOOL_ADDRESS.value.length+1, com.brainsocket.milink.data.bluetooth.ReadResponseCodes.TOOL_ADDRESS.value.length+3))
)
Timber.i("Message received: $message")}
message.startsWith(com.brainsocket.milink.data.bluetooth.ReadResponseCodes.RPM_THRESHOLD.value) ->{
EventBus.getDefault().post(
ReadRPMThresholdEvent(message.substring(com.brainsocket.milink.data.bluetooth.ReadResponseCodes.RPM_THRESHOLD.value.length+1, com.brainsocket.milink.data.bluetooth.ReadResponseCodes.RPM_THRESHOLD.value.length+3))
)
Timber.i("Message received: $message")}
message.startsWith(com.brainsocket.milink.data.bluetooth.ReadResponseCodes.BACKLASH.value) ->{
EventBus.getDefault().post(
ReadBacklashEvent(message.substring(com.brainsocket.milink.data.bluetooth.ReadResponseCodes.BACKLASH.value.length+1, com.brainsocket.milink.data.bluetooth.ReadResponseCodes.BACKLASH.value.length+6))
)
Timber.i("Message received: $message")}
As you can see the Event Bus is used here, it is also used here in the DataStore:
@Subscribe(threadMode = ThreadMode.MAIN)
fun onKeyAddressEvent(event: ReadKeyAddressEvent) {
Timber.i("onKeyAddressEvent: data:${event.data}")
keyAddress = Integer.parseInt(event.data , 16)
EventBus.getDefault().post(ReadMemoryItemsEvent())
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onToolAddressEvent(event: ReadToolAddressEvent) {
Log.d(LOG_TAG, "onToolAddressEvent: data:${event.data}")
when(Integer.parseInt(event.data , 16)){
0 -> toolAddress = 1
1 -> toolAddress = 2
}
EventBus.getDefault().post(ReadMemoryItemsEvent())
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onRPMThresholdEvent(event: ReadRPMThresholdEvent) {
Log.d(LOG_TAG, "onRPMThresholdEvent: data:${event.data}")
rpmThreshold = Integer.parseInt(event.data , 16)
EventBus.getDefault().post(ReadMemoryItemsEvent())
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onReadBacklashEvent(event: ReadBacklashEvent) {
Log.d(LOG_TAG, "onReadBacklashEvent: data:${event.data}")
val data = event.data
backlash = parseGotoPos(data)
EventBus.getDefault().post(ReadMemoryItemsEvent())
}
This is in the repository:
fun getMemoryItems() : List<ModesPosItem> = listOf(
ModesPosItem(value = btDataStore.keyAddress, title = context.getString(R.string.key_address_string)),
ModesPosItem(value = btDataStore.toolAddress, title = context.getString(R.string.tool_address_string)),
ModesPosItem(value = btDataStore.rpmThreshold, title = context.getString(R.string.rpm_threshold_string)),
ModesPosItem(value = btDataStore.backlash, title = context.getString(R.string.backlash_string))
)
This is in the viewmodel:
@Subscribe(threadMode = ThreadMode.MAIN)
fun onReadMemoryItemsEvent(event: ReadMemoryItemsEvent) {
memoryItems.value = repository.getMemoryItems()
Timber.i("Memory Items [tool address, keyAddress, RPM threshold, backlash]: ${memoryItems.value.toString()}")
}
This is in the fragment:
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
}
override fun onStop() {
EventBus.getDefault().unregister(this)
super.onStop()
}
What exactly am I supposed to do to acquire the data from the BLE device?
I made an app that write and read continous stream data throw BLE connection from a bluetooth device.
The Flow i follow is the following:
Going deeper into the connection and using some code:
After you connect to the GATT you call onConnectionStateChange to listen for changes in the gatt connection:
If the GATT is connected succesfully it will discover services.
At this step you can write to the characteristic as follow:
This will let you go on the next step, the onCharacteristicWrite listener:
Writing the characteristic will let you go into the onCharacteristicChanged listener that will give you back the data from the ble device and in which you can use the event bus to use your data.
Where onCharacteristicRead should look like:
Maybe it is not the most efficent way and not the clearest code but it works like a charm.