Reputation: 81
I have created BLE sender class for the sending large ByteArray via Bluetooth LE The logic of the send process following:
My Question is: is there any better approach? I have found that writing characteristics multiple times requires some delay of at least 20 milliseconds. Is there any way to avoid this?
Changed the implementation instead of 20 millis, I'm waiting for a callback onCharacteristicWrite as Emil advised. and Also changed the prepare method to decrease calculation time between 18bytes blocks sends:
class BluetoothLEDataSender(
val characteristicForSending: BluetoothGattCharacteristic,
val characteristicForNotifyDataSend: BluetoothGattCharacteristic,
private val config: BluetoothLESenderConfiguration = BluetoothLESenderConfiguration(),
val bluetoothLeService: WeakReference<BluetoothLeService>) : HandlerThread("BluetoothLEDataSender") {
data class BluetoothLESenderConfiguration(val sendingIntervalMillis: Long = 20L, val chunkSize: Int = 1000, val retryForFailureInSeconds: Long = 3)
private val toaster by lazy { Toast.makeText(bluetoothLeService.get()!!,"",Toast.LENGTH_SHORT) }
companion object {
val ACTION_DATA_SEND_FINISHED = "somatix.com.bleplays.ACTION_DATA_SEND_FINISHED"
val ACTION_DATA_SEND_FAILED = "somatix.com.bleplays.ACTION_DATA_SEND_FAILED"
}
lateinit var dataToSend: List<BlocksQueue>
val messageHandler by lazy { SenderHandler()}
var currentIndex = 0
public fun notifyDataState(receivedChecksum: String) {
val msg = Message()
msg.arg1 = receivedChecksum.toInt()
messageHandler.sendMessage(msg)
}
inner class BlocksQueue(val initialCapacity:Int):ArrayBlockingQueue<ByteArray>(initialCapacity)
inner class BlockSendingTask:Runnable{
override fun run() {
executeOnUiThread({ toaster.setText("Executing block: $currentIndex")
toaster.show()})
sendNext()
}
}
public fun sendMessage(messageByteArray: ByteArray) {
start()
dataToSend = prepareSending(messageByteArray)
bluetoothLeService.get()?.setEnableNotification(characteristicForSending,true)
val descriptor = characteristicForSending.getDescriptor(DESCRIPTOR_CONFIG_UUID)
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
bluetoothLeService.get()?.writeDescriptor(descriptor)
characteristicForNotifyDataSend.value = "${messageByteArray.size}:${config.chunkSize}:${dataToSend.size}".toByteArray()
toaster.setText(String(characteristicForNotifyDataSend.value))
toaster.show()
messageHandler.postDelayed({bluetoothLeService.get()?.writeCharacteristic(characteristicForNotifyDataSend)}, config.sendingIntervalMillis)
}
private fun prepareSending(messageByteArray: ByteArray): ArrayList<BlocksQueue> {
with(config)
{
var chunksNumber = messageByteArray.size / config.chunkSize
chunksNumber = if (messageByteArray.size == chunksNumber * config.chunkSize) chunksNumber else chunksNumber + 1
val chunksArray = ArrayList<BlocksQueue>()
(0 until chunksNumber).mapTo(chunksArray) {
val start = it * chunkSize
val end = if ((start + chunkSize) > messageByteArray.size) messageByteArray.size else start + chunkSize
val sliceArray = messageByteArray.sliceArray(start until end)
listOfCheckSums.add(sliceArray.checkSum())
var capacity = sliceArray.size / 18
capacity = if(sliceArray.size - capacity*18 == 0) capacity else capacity + 1
//Add place for checksum
val queue = BlocksQueue(capacity+1)
for(i in 0 until capacity){
val start1 = i *18
val end1 = if((start1 + 18)<sliceArray.size) start1 +18 else sliceArray.size
queue.add(sliceArray.sliceArray(start1 until end1))
}
queue.add(sliceArray.checkSum().toByteArray())
queue
}
return chunksArray
}
}
fun sendNext(){
val currentChunk = dataToSend.get(currentIndex)
val peek = currentChunk.poll()
if(peek != null)
{
if(currentChunk.initialCapacity > currentBlock+1)
{
val indexByteArray = if(currentBlock>9) "$currentBlock".toByteArray() else "0${currentBlock}".toByteArray()
characteristicForSending.value = indexByteArray + peek
}
else{
characteristicForSending.value = peek
}
bluetoothLeService.get()?.writeCharacteristic(characteristicForSending)
currentBlock++
}
else
{
Log.i(TAG, "Finished chunk $currentIndex")
currentBlock = 0
}
}
private val TAG= "BluetoothLeService"
@SuppressLint("HandlerLeak")
inner class SenderHandler:Handler(looper){
private var failureCheck:FailureCheck? = null
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
currentIndex = msg.arg1
if(currentIndex < dataToSend.size)
{
if (currentIndex!= 0 && failureCheck != null)
{
removeCallbacks(failureCheck)
}
failureCheck = FailureCheck(currentIndex)
post(BlockSendingTask())
postDelayed(failureCheck,TimeUnit.MILLISECONDS.convert(config.retryForFailureInSeconds,TimeUnit.SECONDS))
}
else {
if (currentIndex!= 0 && failureCheck != null)
{
removeCallbacks(failureCheck)
}
val intent= Intent(ACTION_DATA_SEND_FINISHED)
bluetoothLeService.get()?.sendBroadcast(intent)
}
}
private inner class FailureCheck(val index:Int):Runnable{
override fun run() {
if (index==currentIndex){
val intent= Intent(ACTION_DATA_SEND_FAILED)
bluetoothLeService.get()?.sendBroadcast(intent)
}
}
}
}
}
Upvotes: 4
Views: 5440
Reputation: 75
I work with @libinm (OP) on the same project and would like to refer to @Emil comment above -
Upvotes: 1
Reputation: 18517
What's this thing about waiting 20 ms? The preferred way to pump data using characteristic writes is to first use "Write Without Response" (https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#WRITE_TYPE_NO_RESPONSE), then perform a Write, then wait for the onCharacteristicWrite callback and then immediately perform the next Write. You need to wait for the onCharacteristicWrite callback since the API doesn't allow you to have multiple pending commands/requests at a time.
Upvotes: 4