How to receive data from BLE device using greenbot EventBus in my code?

380 Views Asked by At

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?

1

There are 1 best solutions below

2
antonioaugello On

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:

  1. Connect Gatt;
  2. Discover Services;
  3. Write To Characteristic;
  4. Subscribe to Notification;
  5. Read Characteristic from notification --> Here the EventBus post() with your data package;

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:

private val gattCallback = object : BluetoothGattCallback() {
        override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
val deviceAddress = gatt.device.address
            if (status == BluetoothGatt.GATT_SUCCESS) {
                if (newState == BluetoothProfile.STATE_CONNECTED) {
                    Log.w("BluetoothGattCallback", "Successfully connected to $deviceAddress")
                    // NOW DISCOVER SERVICES
                    gatt.discoverServices()

                } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                    Log.w("BluetoothGattCallback", "Successfully disconnected from $deviceAddress")
                }
            } else {
                Log.w(
                    "BluetoothGattCallback",
                    "Error $status encountered for $deviceAddress! Disconnecting..."
                ) 
        }

If the GATT is connected succesfully it will discover services.

At this step you can write to the characteristic as follow:

override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
    with(gatt) {
        Log.w(
            "BluetoothGattCallback",
            "Discovered ${services.size} services for ${device.address}"
        )

        val msg = byteArrayOf(0x00.toByte())
        val newcharacteristic = gatt!!.getService(dataUUID_service).getCharacteristic(
            dataUUID_characteristic
        )
        newcharacteristic!!.value = msg
        gatt!!.writeCharacteristic(newcharacteristic)        
    }
}

This will let you go on the next step, the onCharacteristicWrite listener:

override fun onCharacteristicWrite(
            gatt: BluetoothGatt,
            characteristic: BluetoothGattCharacteristic,
            status: Int
        ) {

            val characteristic = gatt.getService(dataUUID_service).getCharacteristic(
            dataUUID_characteristic
        )
        gatt.setCharacteristicNotification(characteristic, true)
        val descriptor = characteristic!!.getDescriptor(descriptor_UUID)
        descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
        if (descriptor != null) {
            descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
            gatt.writeDescriptor(descriptor)
        }
        }

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.

override fun onCharacteristicChanged(
            gatt: BluetoothGatt,
            charac: BluetoothGattCharacteristic
        ) {
            super.onCharacteristicChanged(gatt, charac)
            // Log.d("CHARAC", "Characteristic Changed")
            onCharacteristicRead(gatt, charac, BluetoothGatt.GATT_SUCCESS)
        }

Where onCharacteristicRead should look like:

override fun onCharacteristicRead(
            gatt: BluetoothGatt,
            characteristic: BluetoothGattCharacteristic,
            status: Int
        ) {
            with(characteristic) {
                when (status) {
                    BluetoothGatt.GATT_SUCCESS -> {
                        Log.i("BluetoothGattCallback","Read characteristic $uuid:\n${value.toHexString()}" )


                    // value is the read value from ble device
                    // HERE YOU HANDE YOUR EVENT BUS, example:

                    val eventData: deviceListener = deviceListener(value)
                    EventBus.getDefault().post(eventData)


                   }
                    BluetoothGatt.GATT_READ_NOT_PERMITTED -> {
                        Log.e("BluetoothGattCallback", "Read not permitted for $uuid!")
                    }
                    else -> {
                        Log.e(
                            "BluetoothGattCallback",
                            "Characteristic read failed for $uuid, error: $status"
                        )
                    }
                }
            }
        }

Maybe it is not the most efficent way and not the clearest code but it works like a charm.