在Android上通过BLE API发送大文件

时间:2018-01-14 14:43:45

标签: android bluetooth kotlin bluetooth-lowenergy android-ble

我已经通过蓝牙LE为发送大型ByteArray创建了BLE发送器类 发送过程的逻辑如下:

  1. 编写描述符以启用有关发送数据的特征的通知
  2. 通过外围设备发送数据发送过程 写入相应的特征(数据大小:块大小: 块数)
  3. 等待外围设备通知数据块0发送数据发送特征
  4. 收到通知后,开始按20字节块(BLE限制)发送第一个块1000字节,其中每个块包含块号和18个字节的数据,发送1000个字节后,发送数据块的校验和
  5. 外围设备通过校验和验证数据并通知描述符以获取下一个块
  6. 我的问题是:有更好的方法吗? 我发现多次写入特性需要一些延迟至少20毫秒。有什么方法可以避免这种情况吗?

    更改了实施而不是20毫秒,我正在等待回调 onCharacteristicWrite Emil建议。并且还改变了准备方法以减少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)
                        }
                    }
    
                }
            }
    
    
        }
    

2 个答案:

答案 0 :(得分:2)

等待20毫秒的事情是什么?使用特征写入泵送数据的首选方法是首先使用“无响应写入”(https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#WRITE_TYPE_NO_RESPONSE),然后执行写入,然后等待onCharacteristicWrite回调,然后立即执行下一次写入。您需要等待onCharacteristicWrite回调,因为API不允许您一次有多个待处理命令/请求。

答案 1 :(得分:0)

我在同一个项目上使用@libinm(OP),并想参考上面的@Emil评论 -

  • @Emil提到“API不允许你一次有多个挂起的命令/请求”,我想知道是否有任何类型的消息缓冲能够提高吞吐量(通过在一个上发送多个消息)单个BLE连接事件)。我知道更轻(嵌入)的BLE堆栈可以为每个连接事件缓冲4/6个消息(TI / Nordic堆栈)。
  • Android BLE中心如何响应每个连接事件(由外围设备发送)的多个消息通知?有没有限制?