|
|
@@ -1,352 +0,0 @@
|
|
|
-package com.iscs.bozzys.utils.ble
|
|
|
-
|
|
|
-import android.annotation.SuppressLint
|
|
|
-import android.app.Application
|
|
|
-import android.bluetooth.BluetoothDevice
|
|
|
-import android.bluetooth.BluetoothGatt
|
|
|
-import android.bluetooth.BluetoothGattCallback
|
|
|
-import android.bluetooth.BluetoothGattCharacteristic
|
|
|
-import android.bluetooth.BluetoothGattDescriptor
|
|
|
-import android.bluetooth.BluetoothManager
|
|
|
-import android.bluetooth.BluetoothProfile
|
|
|
-import android.bluetooth.BluetoothStatusCodes
|
|
|
-import android.bluetooth.le.ScanCallback
|
|
|
-import android.bluetooth.le.ScanFilter
|
|
|
-import android.bluetooth.le.ScanResult
|
|
|
-import android.bluetooth.le.ScanSettings
|
|
|
-import android.os.Build
|
|
|
-import android.os.SystemClock
|
|
|
-import com.iscs.bozzys.utils.LogUtil
|
|
|
-import com.iscs.bozzys.utils.byteArrayToHexString
|
|
|
-import com.iscs.bozzys.utils.hexToByteArray
|
|
|
-import kotlinx.coroutines.CancellableContinuation
|
|
|
-import kotlinx.coroutines.CompletableDeferred
|
|
|
-import kotlinx.coroutines.CoroutineScope
|
|
|
-import kotlinx.coroutines.Dispatchers
|
|
|
-import kotlinx.coroutines.SupervisorJob
|
|
|
-import kotlinx.coroutines.TimeoutCancellationException
|
|
|
-import kotlinx.coroutines.delay
|
|
|
-import kotlinx.coroutines.launch
|
|
|
-import kotlinx.coroutines.suspendCancellableCoroutine
|
|
|
-import kotlinx.coroutines.withContext
|
|
|
-import kotlinx.coroutines.withTimeout
|
|
|
-import java.util.UUID
|
|
|
-import java.util.concurrent.ConcurrentHashMap
|
|
|
-
|
|
|
-/**
|
|
|
- * 蓝牙连接管理器
|
|
|
- */
|
|
|
-class BleManager(
|
|
|
- val app: Application,
|
|
|
- val mac: String = "",
|
|
|
- val mtu: Int = 500,
|
|
|
- val needIndicate: Boolean = true,
|
|
|
- val writeUUID: String = BleProtocol.INDICATE_UUID,
|
|
|
- val serviceUUID: String = BleProtocol.SERVICE_UUID,
|
|
|
-) {
|
|
|
-
|
|
|
- companion object {
|
|
|
- private const val TAG = "BLEManager"
|
|
|
- }
|
|
|
-
|
|
|
- // 蓝牙管理器
|
|
|
- private val bm = app.getSystemService(BluetoothManager::class.java)
|
|
|
-
|
|
|
- // 蓝牙适配器
|
|
|
- private val ba = bm.adapter
|
|
|
-
|
|
|
- // 当前连接的设备
|
|
|
- private var device: BluetoothDevice? = null
|
|
|
-
|
|
|
- // gatt连接对象
|
|
|
- private var gatt: BluetoothGatt? = null
|
|
|
-
|
|
|
- // 连接回调
|
|
|
- private var doneConnect: CancellableContinuation<BleConnectResult>? = null
|
|
|
-
|
|
|
- // 待响应式的请求数据接收池
|
|
|
- private val receiverPool = ConcurrentHashMap<String, CompletableDeferred<BleFrame>>()
|
|
|
-
|
|
|
- // 是否已经连接
|
|
|
- private var isConnected = false
|
|
|
-
|
|
|
- private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
|
|
-
|
|
|
- // 蓝牙扫描回调
|
|
|
- private val scanCallback = object : ScanCallback() {
|
|
|
-
|
|
|
- override fun onScanResult(callbackType: Int, result: ScanResult?) {
|
|
|
- super.onScanResult(callbackType, result)
|
|
|
- LogUtil.i(TAG, "scan() onScanResult() ${result?.device?.address}")
|
|
|
- if (result != null && result.device != null) {
|
|
|
- device = result.device
|
|
|
- // 查找到设备后,停止扫描并且执行连接操作
|
|
|
- stopScan()
|
|
|
- // 执行连接蓝牙设备
|
|
|
- innerConnect()
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- override fun onScanFailed(errorCode: Int) {
|
|
|
- super.onScanFailed(errorCode)
|
|
|
- LogUtil.i(TAG, "scan() onScanFailed() $errorCode")
|
|
|
- // 扫描失败重新执行扫描
|
|
|
- scan()
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 协程方式的连接
|
|
|
- */
|
|
|
- suspend fun connect(device: BluetoothDevice? = null): BleConnectResult = suspendCancellableCoroutine { block ->
|
|
|
- this.doneConnect = block
|
|
|
- if (device == null) {
|
|
|
- // 开始执行扫描连接
|
|
|
- scan()
|
|
|
- } else {
|
|
|
- this.device = device
|
|
|
- innerConnect()
|
|
|
- timeoutDisconnect()
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 超时未连接处理
|
|
|
- */
|
|
|
- private fun timeoutDisconnect() {
|
|
|
- scope.launch {
|
|
|
- delay(5000)
|
|
|
- // 未连接成功执行断开连接操作
|
|
|
- if (!isConnected) {
|
|
|
- if (!(this@BleManager.doneConnect?.isCompleted ?: false)) {
|
|
|
- this@BleManager.doneConnect?.resume(BleConnectResult(false), null)
|
|
|
- }
|
|
|
- release()
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 查找设备
|
|
|
- */
|
|
|
- @SuppressLint("MissingPermission")
|
|
|
- private fun scan() {
|
|
|
- if (ba == null || !ba.isEnabled) {
|
|
|
- // 蓝牙不可用
|
|
|
- LogUtil.e(TAG, "scan() bluetooth unused")
|
|
|
- if (!(this.doneConnect?.isCompleted ?: false)) this.doneConnect?.resume(BleConnectResult(false), null)
|
|
|
- return
|
|
|
- }
|
|
|
- LogUtil.i(TAG, "scan() bluetooth started")
|
|
|
- // 获取扫描对象
|
|
|
- val scan = ba.bluetoothLeScanner
|
|
|
- // 设置扫描过滤
|
|
|
- val filters = ArrayList<ScanFilter>()
|
|
|
- if (mac.isNotEmpty()) filters.add(ScanFilter.Builder().setDeviceAddress(mac).build())
|
|
|
- // 设置蓝牙扫描频率高 SCAN_MODE_LOW_LATENCY
|
|
|
- // 设置蓝牙扫描频率中 SCAN_MODE_BALANCED
|
|
|
- val settings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
|
|
|
- scan.startScan(filters, settings, scanCallback)
|
|
|
- }
|
|
|
-
|
|
|
- @SuppressLint("MissingPermission")
|
|
|
- fun stopScan() {
|
|
|
- try {
|
|
|
- ba.bluetoothLeScanner.stopScan(scanCallback)
|
|
|
- } catch (e: Exception) {
|
|
|
- LogUtil.e(TAG, "stopScan() stop scan failed $e")
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 连接设备,内部方法
|
|
|
- */
|
|
|
- @SuppressLint("MissingPermission")
|
|
|
- private fun innerConnect() {
|
|
|
- try {
|
|
|
- gatt = device?.connectGatt(app, false, object : BluetoothGattCallback() {
|
|
|
-
|
|
|
- @SuppressLint("MissingPermission")
|
|
|
- override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
|
|
|
- if (newState == BluetoothProfile.STATE_CONNECTED) {
|
|
|
- LogUtil.i(TAG, "$gatt gattCallback onConnectionStateChange() gatt connected")
|
|
|
- gatt.discoverServices()
|
|
|
- } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
|
|
- LogUtil.i(TAG, "$gatt gattCallback onConnectionStateChange() gatt disconnected")
|
|
|
- isConnected = false
|
|
|
- if (!(this@BleManager.doneConnect?.isCompleted ?: false)) {
|
|
|
- this@BleManager.doneConnect?.resume(BleConnectResult(false), null)
|
|
|
- }
|
|
|
- release()
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @SuppressLint("MissingPermission")
|
|
|
- override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
|
|
|
- if (status == BluetoothGatt.GATT_SUCCESS) {
|
|
|
- LogUtil.i(TAG, "gattCallback onServicesDiscovered() find services success")
|
|
|
- // 执行设置mtu操作
|
|
|
- gatt.requestMtu(mtu)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- override fun onMtuChanged(gatt: BluetoothGatt?, mtu: Int, status: Int) {
|
|
|
- super.onMtuChanged(gatt, mtu, status)
|
|
|
- LogUtil.i(TAG, "gattCallback onMtuChanged() mtu $mtu")
|
|
|
- Thread {
|
|
|
- val indicateOk = enableIndicate()
|
|
|
- SystemClock.sleep(1000)
|
|
|
- if (needIndicate) {
|
|
|
- isConnected = indicateOk
|
|
|
- if (!(this@BleManager.doneConnect?.isCompleted ?: false)) {
|
|
|
- this@BleManager.doneConnect?.resume(BleConnectResult(indicateOk), null)
|
|
|
- }
|
|
|
- } else {
|
|
|
- isConnected = false
|
|
|
- if (!(this@BleManager.doneConnect?.isCompleted ?: false)) {
|
|
|
- this@BleManager.doneConnect?.resume(BleConnectResult(true), null)
|
|
|
- }
|
|
|
- }
|
|
|
- }.start()
|
|
|
- }
|
|
|
-
|
|
|
- @SuppressLint("MissingPermission")
|
|
|
- override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray, status: Int) {
|
|
|
- super.onCharacteristicRead(gatt, characteristic, value, status)
|
|
|
- LogUtil.i(TAG, "gattCallback onCharacteristicRead()")
|
|
|
- }
|
|
|
-
|
|
|
- override fun onDescriptorRead(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int, value: ByteArray) {
|
|
|
- super.onDescriptorRead(gatt, descriptor, status, value)
|
|
|
- LogUtil.i(TAG, "gattCallback onDescriptorRead() data write success")
|
|
|
- }
|
|
|
-
|
|
|
- override fun onCharacteristicChanged(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?) {
|
|
|
- super.onCharacteristicChanged(gatt, characteristic)
|
|
|
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) return
|
|
|
- LogUtil.w(TAG, "gattCallback <--- ${characteristic?.value?.byteArrayToHexString(" ")}")
|
|
|
- val data = characteristic?.value ?: byteArrayOf()
|
|
|
- var key = ""
|
|
|
- receiverPool.forEach { item ->
|
|
|
- if (data.byteArrayToHexString().startsWith(item.key.split("_")[1])) {
|
|
|
- // 找到指定响应体
|
|
|
- key = item.key
|
|
|
- return@forEach
|
|
|
- }
|
|
|
- }
|
|
|
- if (key.isNotEmpty()) {
|
|
|
- val deferred = receiverPool.remove(key)
|
|
|
- if (deferred != null && !deferred.isCompleted) {
|
|
|
- val spl = key.split("_")
|
|
|
- val rspCodeLen = spl[1].length / 2
|
|
|
- deferred.complete(BleFrame(spl[0].hexToByteArray(), data.copyOfRange(rspCodeLen, data.size), spl[1].hexToByteArray()))
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray) {
|
|
|
- super.onCharacteristicChanged(gatt, characteristic, value)
|
|
|
- LogUtil.w(TAG, "gattCallback <--- ${value.byteArrayToHexString(" ")}")
|
|
|
- var key = ""
|
|
|
- receiverPool.forEach { item ->
|
|
|
- if (value.byteArrayToHexString().startsWith(item.key.split("_")[1])) {
|
|
|
- // 找到指定响应体
|
|
|
- key = item.key
|
|
|
- return@forEach
|
|
|
- }
|
|
|
- }
|
|
|
- if (key.isNotEmpty()) {
|
|
|
- val deferred = receiverPool.remove(key)
|
|
|
- if (deferred != null && !deferred.isCompleted) {
|
|
|
- val spl = key.split("_")
|
|
|
- val rspCodeLen = spl[1].length / 2
|
|
|
- deferred.complete(BleFrame(spl[0].hexToByteArray(), value.copyOfRange(rspCodeLen, value.size), spl[1].hexToByteArray()))
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }, BluetoothDevice.TRANSPORT_LE)
|
|
|
- LogUtil.d(TAG, "$gatt gatt created")
|
|
|
- } catch (e: Exception) {
|
|
|
- LogUtil.e(TAG, "connect() connect to device ${device?.address} failed $e")
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 断开连接
|
|
|
- */
|
|
|
- @SuppressLint("MissingPermission")
|
|
|
- fun disconnect() {
|
|
|
- device?.let { gatt?.disconnect() }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 资源释放
|
|
|
- */
|
|
|
- @SuppressLint("MissingPermission")
|
|
|
- private fun release() {
|
|
|
- try {
|
|
|
- // 执行断开连接操作
|
|
|
- gatt?.close()
|
|
|
- } finally {
|
|
|
- gatt = null
|
|
|
- device = null
|
|
|
- doneConnect = null
|
|
|
- isConnected = false
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 是否使能Indicate
|
|
|
- */
|
|
|
- @SuppressLint("MissingPermission")
|
|
|
- private fun enableIndicate(uuid: String = writeUUID): Boolean {
|
|
|
- gatt?.getService(UUID.fromString(serviceUUID))?.getCharacteristic(UUID.fromString(uuid))?.let {
|
|
|
- // 开启通知
|
|
|
- val open = gatt?.setCharacteristicNotification(it, true)
|
|
|
- LogUtil.i(TAG, "enableIndicate() open notification $open")
|
|
|
- val descriptor = it.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"))
|
|
|
- LogUtil.i(TAG, "enableIndicate() get descriptor $descriptor")
|
|
|
- if (open != true || descriptor == null) return false
|
|
|
-
|
|
|
- // 写入desc
|
|
|
- val enable = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
|
- gatt?.writeDescriptor(descriptor, BluetoothGattDescriptor.ENABLE_INDICATION_VALUE) == BluetoothStatusCodes.SUCCESS
|
|
|
- } else {
|
|
|
- descriptor.value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
|
|
|
- gatt?.writeDescriptor(descriptor)
|
|
|
- }
|
|
|
- LogUtil.i(TAG, "enableIndicate() indicate enable $enable")
|
|
|
- return enable == true
|
|
|
- }
|
|
|
- return false
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 写数据,带响应
|
|
|
- */
|
|
|
- @SuppressLint("MissingPermission")
|
|
|
- suspend fun writeByResponse(frame: BleFrame) = withContext(Dispatchers.IO) {
|
|
|
- val deferred = CompletableDeferred<BleFrame>()
|
|
|
- val writeUUID = if (frame.writeUUID.isNotEmpty()) UUID.fromString(frame.writeUUID) else UUID.fromString(writeUUID)
|
|
|
- receiverPool["${frame.reqCode.byteArrayToHexString()}_${frame.rspCode.byteArrayToHexString()}"] = deferred
|
|
|
- LogUtil.w(TAG, "gattCallback ---> ${frame.reqCode.byteArrayToHexString(" ")} ${frame.data.byteArrayToHexString(" ")}")
|
|
|
- // 发送数据方法兼容处理
|
|
|
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
|
- gatt?.getService(UUID.fromString(serviceUUID))?.getCharacteristic(writeUUID)?.let {
|
|
|
- gatt?.writeCharacteristic(it, frame.reqCode + frame.data, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
|
|
|
- }
|
|
|
- } else {
|
|
|
- val characteristic = gatt?.getService(UUID.fromString(serviceUUID))?.getCharacteristic(writeUUID)
|
|
|
- characteristic?.value = frame.reqCode + frame.data
|
|
|
- characteristic?.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
|
|
|
- gatt?.writeCharacteristic(characteristic)
|
|
|
- }
|
|
|
- try {
|
|
|
- withTimeout(1000) { deferred.await() }
|
|
|
- } catch (_: TimeoutCancellationException) {
|
|
|
- BleFrame(byteArrayOf(), byteArrayOf(), byteArrayOf())
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
-}
|