How the execute flag inside onExecuteWrite callback method is set to true in BluetoothGattServer Class

80 views Asked by At

I'm trying to send an image data from my client BLE app to my peripheral BLE app. I'm trying to do this by splitting the image data and send it over by multiple gatt.writeCharacteristic() calls from the client app. As per my understanding, we can do single write and delayed writes by calling this method. And when I do single writes like sending a single data instead of this image data by splitting it, the single write data is properly received at the other end. But when I try to send multiple data by calling the gatt.writeCharacteristic() method in a loop, I'm getting a 201 error code which is ERROR_GATT_WRITE_REQUEST_BUSY constant as per the android documentation, from the first data sending try itself. But the data is received at the other end, which is in the peripheral app when I checked the logs. As per my understanding, when the data is received at the other end at the peripheral app, at the onCharacteristicWriteRequest(device: BluetoothDevice, requestId: Int, characteristic: BluetoothGattCharacteristic?, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray?) callback method, I'm checking the preparedWrite flag value to see whether it is true or false assuming when the value is true I will cache the data for delayed write, and when that flag is false I will immediately write the data. But on checking the logs, I saw that the preparedWrite flag values are true and the value is properly received at the other end, but the onExecuteWrite(device: BluetoothDevice?, requestId: Int, execute: Boolean) callback method will be executed then. And interestingly the argument execute is set to false in the logs. As per my understanding the execute flag should be true when we try to do the delayed write and the onExecuteWrite(device: BluetoothDevice?, requestId: Int, execute: Boolean) should be called only after receiving all the data that should be cached at the peripheral side before doing the delayed write. If my understanding is correct, then i'm not sure how to set the execute argument in onExecuteWrite(device: BluetoothDevice?, requestId: Int, execute: Boolean) method to true and to make the onExecuteWrite(device: BluetoothDevice?, requestId: Int, execute: Boolean) callback to execute once I cached every single data received from the client side. Please confirm whether my understanding is correct or not. If it is not please explain how to properly deal with this situation.

I'm sending the data by delaying the write process by using a synchronized queue to ensure that multiple calls are not made while a write is happening.

CODE AT MY CLIENT APP SIDE

    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
    private fun sendAlbumArt(gatt: BluetoothGatt,
                            characteristic:BluetoothGattCharacteristic,
                             albumByteArray: ByteArray) {
        Log.e("sendAlbumArt-", "inside sendAlbumArt")
        Log.e("sendAlbumArt-", "The mtuValue allowed is $mtuValue")
        val times = ceil(albumByteArray.size / 
 mtuValue.toDouble()).toInt()
        Log.i("sendAlbumArt", "NUMBER OF BLE PACKETS: $times");
    
        //writeDataSynchronized(gatt, characteristic, albumByteArray)
    
        var start = 0
        var last = 0
        //Thread.sleep(1000)
        for (i in 0 until times) {
            Log.i("sendAlbumArt-", "Inside the for loop to send album art data")
            var end = start + mtuValue
            if (end > albumByteArray.size) {
                end = albumByteArray.size
            }
            val packet: ByteArray = albumByteArray.copyOfRange(start, end)
writeDataSynchronized(gatt, characteristic, packet)
            start += mtuValue
            last++
        }
        if (last == times){
            writeDataSynchronized(gatt, characteristic, ALBUM_ART_STOP)
            Log.i("sendAlbumArt-", "last data is written: ${ALBUM_ART_STOP.toHexString()}")
        }
        /*val executeReliableWrite = gatt.executeReliableWrite()
        if (executeReliableWrite){
            Log.i("sendAlbumArt-", "executeReliableWrite: $executeReliableWrite")
        }else{
            Log.i("sendAlbumArt-", "executeReliableWrite: $executeReliableWrite")
        }*/
        val albumArt = BitmapFactory.decodeByteArray(albumByteArray, 0, albumByteArray.size)
        binding.albumartIv.setImageBitmap(albumArt)
    }


    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
    fun writeDataSynchronized(
        gatt: BluetoothGatt,
        characteristic: BluetoothGattCharacteristic,
        data: ByteArray
    ) {
        Log.e("writeDataSynchronized-", "inside writeDataSynchronized")
        Log.e("writeDataSynchronized-", "Queue size: ${writeQueue.size}")
        synchronized(writeLock) {
            writeQueue.add(data)

            if (!isWriting) {
                Log.e("writeDataSynchronized-", "Queue size: ${writeQueue.size}")
                writeNextData(gatt, characteristic)
            }
        }
    }


    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
    private fun writeNextData(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
        Log.e("writeNextData-", "inside writeNextData")
        synchronized(writeLock) {
            if (!writeQueue.isEmpty()) {
                isWriting = true
                val nextData = writeQueue.poll()
                Log.e("writeNextData-", "data to send to peripheral is: ${nextData.toHexString()}")
                gatt.writeCharacteristic(
                    characteristic,
                    nextData ?: "".toByteArray(),
                    BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
                )
            } else {
                isWriting = false
            }
        }
    }

CODE AT MY PERIPHERAL APP SIDE WHERE WE RECEIVE DATA

override fun onCharacteristicWriteRequest(
            device: BluetoothDevice?,
            requestId: Int,
            characteristic: BluetoothGattCharacteristic?,
            preparedWrite: Boolean,
            responseNeeded: Boolean,
            offset: Int,
            value: ByteArray?
        ) {
           if (!preparedWrite){
              //do something for immediate write
              }else{
                if (characteristic != null) {
                    Log.e("onCharacteristicWriteRequest", "characteristic is: $characteristic")
                    Log.e(
                        "onCharacteristicWriteRequest",
                        "characteristic is: ${characteristic.uuid}"
                    )
                    if (characteristic.uuid == albumArtCharacteristicUuid) {
                        Log.i(
                            "onCharacteristicWriteRequest-AlbumArt",
                            "Album art characteristic value is${value?.toHexString()}"
                        )
                        if (value != null) {
                            //handleIncomingAlbumArtData(value)
                            fillTheBuffer(value)
                        }
                    }
                }
            }
          if (responseNeeded) {
                bluetoothGattServer.sendResponse(
                    device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null
                )
            }
        }

override fun onExecuteWrite(device: BluetoothDevice?, requestId: Int, execute: Boolean) {
            super.onExecuteWrite(device, requestId, execute)
    
            Log.e("onExecuteWrite", "execute: $execute")
            
            if (execute){
                handleIncomingAlbumArtData()
            }
            bluetoothGattServer.sendResponse(
                device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null
            )
            Log.e("onExecuteWrite", "handleIncomingAlbumArtData is not executed successfully")
        }


    private fun fillTheBuffer(value: ByteArray) {
        Log.e("fillTheBuffer", "Inside the method")
        if (value.contentEquals(ALBUM_ART_STOP)){
            Log.e("fillTheBuffer", "Last Frame")
            Log.e("fillTheBuffer_ALBUM_ART_STOP", "buffer Size is: ${buffer.size}")
        }else{
            //buffer.addAll(value.toList())
            buffer.addAll(value.toList())
            Log.e("fillTheBuffer", "buffer Size is: ${buffer.size}")
        }
    }

As you can see in the code I'm executing the write from the client side in a synchronized manner to not flood the server with multiple write requests.

0

There are 0 answers