Отправка большого файла через BLE API на Android

Я создал класс отправителя BLE для отправки большого ByteArray через Bluetooth LE. Логика процесса отправки:

  1. Напишите дескриптор, чтобы включить уведомление о характеристиках, которые отправляют данные
  2. Уведомлять периферию о процессе передачи данных посредством записи в соответствующие характеристики (Размер данных: размер блока: количество фрагментов)
  3. Подождите, пока периферийное устройство не сообщит для блока 0 для отправки данных.
  4. По полученному уведомлению начало отправки первого блока 1000 байтов блоками по 20 байтов (ограничение BLE), где каждый блок содержит номер блока и 18 байтов данных, после отправки 1000 байт, отправьте блок контрольной суммы для отправленных данных
  5. Периферийные проверяют данные по контрольной сумме и уведомляют дескриптор для следующего фрагмента

Мой вопрос: есть ли лучший подход? Я обнаружил, что характеристики записи несколько раз требуют некоторой задержки не менее 20 миллисекунд. Есть ли способ избежать этого?

Изменена реализация вместо 20 миллисов , я жду обратного вызова на CharacteristicWrite, как сообщил Эмиль . и также изменил метод подготовки, чтобы уменьшить время вычисления между 18 байтами блоков отправляет:

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) } } } } } 

Solutions Collecting From Web of "Отправка большого файла через BLE API на Android"

Что это за ожидание 20 мс? Предпочтительным способом накачки данных с использованием записи характеристик является сначала использование «Write Without Response» ( https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#WRITE_TYPE_NO_RESPONSE ), затем выполните запись, а затем подождите обратный вызов onCharacteristicWrite, а затем немедленно выполните следующую запись. Вам нужно дождаться обратного вызова onCharacteristicWrite, поскольку API не позволяет одновременно иметь несколько ожидающих команд / запросов.