|
|
@@ -0,0 +1,242 @@
|
|
|
+package com.iscs.comm.manager
|
|
|
+
|
|
|
+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.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.comm.entity.BleConnectResult
|
|
|
+import com.iscs.comm.entity.BleFrame
|
|
|
+import com.iscs.comm.extension.byteArrayToHexString
|
|
|
+import com.iscs.comm.protocol.BLEProtocol
|
|
|
+import com.iscs.comm.utils.ISCSLog
|
|
|
+import kotlinx.coroutines.CancellableContinuation
|
|
|
+import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
|
+import kotlinx.coroutines.suspendCancellableCoroutine
|
|
|
+import java.util.UUID
|
|
|
+
|
|
|
+/**
|
|
|
+ * 蓝牙连接管理器
|
|
|
+ */
|
|
|
+class BLEManager(
|
|
|
+ val app: Application,
|
|
|
+ val mac: String = "",
|
|
|
+ val mtu: Int = 500,
|
|
|
+ val needIndicate: Boolean = true,
|
|
|
+ val indicateUUID: 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 scanCallback = object : ScanCallback() {
|
|
|
+
|
|
|
+ override fun onScanResult(callbackType: Int, result: ScanResult?) {
|
|
|
+ super.onScanResult(callbackType, result)
|
|
|
+ ISCSLog.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)
|
|
|
+ ISCSLog.i(TAG, "scan() onScanFailed() $errorCode")
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 蓝牙Gatt连接回调
|
|
|
+ */
|
|
|
+ @OptIn(ExperimentalCoroutinesApi::class)
|
|
|
+ private val gattCallback = object : BluetoothGattCallback() {
|
|
|
+
|
|
|
+ override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
|
|
|
+ if (newState == BluetoothProfile.STATE_CONNECTED) {
|
|
|
+ ISCSLog.i(TAG, "gattCallback onConnectionStateChange() gatt connected")
|
|
|
+ gatt.discoverServices()
|
|
|
+ } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
|
|
+ ISCSLog.i(TAG, "gattCallback onConnectionStateChange() gatt disconnected")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
|
|
|
+ if (status == BluetoothGatt.GATT_SUCCESS) {
|
|
|
+ ISCSLog.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)
|
|
|
+ ISCSLog.i(TAG, "gattCallback onMtuChanged() mtu $mtu")
|
|
|
+ Thread {
|
|
|
+ val indicateOk = enableIndicate()
|
|
|
+ SystemClock.sleep(2000)
|
|
|
+ if (needIndicate) {
|
|
|
+ this@BLEManager.doneConnect?.resume(BleConnectResult(indicateOk), null)
|
|
|
+ } else {
|
|
|
+ this@BLEManager.doneConnect?.resume(BleConnectResult(true), null)
|
|
|
+ }
|
|
|
+ }.start()
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray, status: Int) {
|
|
|
+ super.onCharacteristicRead(gatt, characteristic, value, status)
|
|
|
+ ISCSLog.i(TAG, "gattCallback onCharacteristicRead() ${gatt.readCharacteristic(characteristic)} ${value.contentToString()}")
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
|
|
|
+ super.onCharacteristicWrite(gatt, characteristic, status)
|
|
|
+ ISCSLog.i(TAG, "gattCallback onCharacteristicWrite() data write success ${characteristic.value.byteArrayToHexString(" ")}")
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onCharacteristicChanged(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?) {
|
|
|
+ super.onCharacteristicChanged(gatt, characteristic)
|
|
|
+ ISCSLog.i(TAG, "gattCallback onCharacteristicChanged() get notification data ${characteristic?.value?.byteArrayToHexString(" ")}")
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray) {
|
|
|
+ super.onCharacteristicChanged(gatt, characteristic, value)
|
|
|
+ ISCSLog.i(TAG, "gattCallback onCharacteristicChanged() get notification data ${value.byteArrayToHexString(" ")}")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 协程方式的连接
|
|
|
+ */
|
|
|
+ suspend fun connect(): BleConnectResult = suspendCancellableCoroutine { block ->
|
|
|
+ this.doneConnect = block
|
|
|
+ // 开始执行扫描连接
|
|
|
+ scan()
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查找设备
|
|
|
+ */
|
|
|
+ @SuppressLint("MissingPermission")
|
|
|
+ private fun scan() {
|
|
|
+ if (ba == null || !ba.isEnabled) {
|
|
|
+ // 蓝牙不可用
|
|
|
+ ISCSLog.e(TAG, "scan() bluetooth unused")
|
|
|
+ this.doneConnect?.resume(BleConnectResult(false), null)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // 获取扫描对象
|
|
|
+ val scan = ba.bluetoothLeScanner
|
|
|
+ // 设置扫描过滤
|
|
|
+ val filters = ArrayList<ScanFilter>()
|
|
|
+ if (mac.isNotEmpty()) filters.add(ScanFilter.Builder().setDeviceAddress(mac).build())
|
|
|
+ // 设置蓝牙扫描频率高
|
|
|
+ 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) {
|
|
|
+ ISCSLog.e(TAG, "stopScan() stop scan failed $e")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 连接设备,内部方法
|
|
|
+ */
|
|
|
+ private fun innerConnect() {
|
|
|
+ // context -> app
|
|
|
+ // autoConnect -> false 避免设备连接异常
|
|
|
+ try {
|
|
|
+ gatt = device?.connectGatt(app, false, gattCallback, BluetoothDevice.TRANSPORT_LE)
|
|
|
+ } catch (e: Exception) {
|
|
|
+ ISCSLog.e(TAG, "connect() connect to device ${device?.address} failed $e")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 断开连接
|
|
|
+ */
|
|
|
+ fun disconnect() {
|
|
|
+ device?.let { gatt?.close() }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 是否使能Indicate
|
|
|
+ */
|
|
|
+ private fun enableIndicate(): Boolean {
|
|
|
+ gatt?.getService(UUID.fromString(serviceUUID))?.getCharacteristic(UUID.fromString(indicateUUID))?.let {
|
|
|
+ // 开启通知
|
|
|
+ val open = gatt?.setCharacteristicNotification(it, true)
|
|
|
+ ISCSLog.i(TAG, "enableIndicate() open notification $open")
|
|
|
+ val descriptor = it.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"))
|
|
|
+ ISCSLog.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)
|
|
|
+ } else {
|
|
|
+ descriptor.value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
|
|
|
+ gatt?.writeDescriptor(descriptor)
|
|
|
+ }
|
|
|
+ ISCSLog.i(TAG, "enableIndicate() indicate enable $enable")
|
|
|
+ return enable == true
|
|
|
+ }
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 写数据,带响应
|
|
|
+ */
|
|
|
+ fun writeByResponse(frame: BleFrame) {
|
|
|
+ // 发送数据方法兼容处理
|
|
|
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
|
+ gatt?.getService(UUID.fromString(serviceUUID))?.getCharacteristic(UUID.fromString(indicateUUID))?.let {
|
|
|
+ ISCSLog.i(TAG, "writeByResponse() write characteristic find result ${it.uuid}")
|
|
|
+ gatt?.writeCharacteristic(it, frame.reqCode + frame.data, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ val characteristic = gatt?.getService(UUID.fromString(serviceUUID))?.getCharacteristic(UUID.fromString(indicateUUID))
|
|
|
+ ISCSLog.i(TAG, "writeByResponse() write characteristic find result ${characteristic?.uuid}")
|
|
|
+ characteristic?.value = frame.reqCode + frame.data
|
|
|
+ characteristic?.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
|
|
|
+ gatt?.writeCharacteristic(characteristic)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|