|
|
@@ -0,0 +1,482 @@
|
|
|
+package com.grkj.iscs.ble
|
|
|
+
|
|
|
+import android.bluetooth.BluetoothGatt
|
|
|
+import androidx.appcompat.app.AppCompatActivity
|
|
|
+import com.clj.fastble.BleManager
|
|
|
+import com.clj.fastble.data.BleDevice
|
|
|
+import com.clj.fastble.exception.BleException
|
|
|
+import com.grkj.iscs.BusinessManager
|
|
|
+import com.grkj.iscs.BusinessManager.deviceList
|
|
|
+import com.grkj.iscs.BusinessManager.getBleDeviceByMac
|
|
|
+import com.grkj.iscs.BusinessManager.getCurrentStatus
|
|
|
+import com.grkj.iscs.BusinessManager.isTestMode
|
|
|
+import com.grkj.iscs.BusinessManager.removeExceptionKey
|
|
|
+import com.grkj.iscs.BusinessManager.sendEventMsg
|
|
|
+import com.grkj.iscs.BusinessManager.sendLoadingEventMsg
|
|
|
+import com.grkj.iscs.R
|
|
|
+import com.grkj.iscs.extentions.toHexStrings
|
|
|
+import com.grkj.iscs.modbus.ModBusController
|
|
|
+import com.grkj.iscs.model.Constants.PERMISSION_REQUEST_CODE
|
|
|
+import com.grkj.iscs.model.eventmsg.LoadingMsg
|
|
|
+import com.grkj.iscs.model.eventmsg.MsgEvent
|
|
|
+import com.grkj.iscs.model.eventmsg.MsgEventConstants.MSG_EVENT_LOADING
|
|
|
+import com.grkj.iscs.util.ActivityUtils
|
|
|
+import com.grkj.iscs.util.CommonUtils
|
|
|
+import com.grkj.iscs.util.Executor
|
|
|
+import com.grkj.iscs.util.log.LogUtil
|
|
|
+import com.grkj.iscs.view.base.BaseActivity
|
|
|
+import com.sik.sikcore.activity.ActivityTracker
|
|
|
+import com.sik.sikcore.thread.ThreadUtils
|
|
|
+import pub.devrel.easypermissions.AfterPermissionGranted
|
|
|
+import java.util.LinkedList
|
|
|
+
|
|
|
+/**
|
|
|
+ * BLE 连接管理工具:保持原有扫描、连接、监听、取 Token 流程,
|
|
|
+ * 并增加“最大待机数”功能,超出后断开最旧连接,保证同时在线设备数不超过阈值。
|
|
|
+ */
|
|
|
+object BleConnectionManager {
|
|
|
+ /**
|
|
|
+ * 最大待机连接数,超过则断开最旧设备。
|
|
|
+ * 默认为业务常量 MAX_KEY_STAND_BY,可根据需求调整。
|
|
|
+ */
|
|
|
+ @Volatile
|
|
|
+ var maxStandbyCount: Int = BleConst.MAX_KEY_STAND_BY
|
|
|
+
|
|
|
+ @Volatile
|
|
|
+ var maxConnectCount: Int = BleConst.MAX_KEY_CONNECT_COUNT
|
|
|
+
|
|
|
+
|
|
|
+ // 按连接完成顺序维护待机队列
|
|
|
+ private val standbyQueue = LinkedList<String>()
|
|
|
+
|
|
|
+ // 原有回调管理
|
|
|
+ private val connectListeners = mutableListOf<ConnectListener>()
|
|
|
+ private var isPreparing: Boolean = false
|
|
|
+
|
|
|
+ @Volatile
|
|
|
+ private var currentConnectingMac: String? = null
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 注册连接监听:
|
|
|
+ * - 如果设备已在 deviceList 且拥有 token,立即回调并返回
|
|
|
+ * - 如果 mac 已在待连接队列或正在连接,忽略重复请求
|
|
|
+ * - 否则将 mac 添加到队列并触发连接流程
|
|
|
+ */
|
|
|
+ fun registerConnectListener(mac: String, callBack: ((Boolean, BleBean?) -> Unit)? = null) {
|
|
|
+ LogUtil.i("registerConnectListener : $mac")
|
|
|
+ // 已连接且已获取 token
|
|
|
+ deviceList.find { it.bleDevice.mac == mac && it.token != null }?.let { bean ->
|
|
|
+ callBack?.invoke(true, bean)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // 重复注册检查
|
|
|
+ if (connectListeners.any { it.mac == mac } || currentConnectingMac == mac) {
|
|
|
+ LogUtil.w("忽略重复注册 mac: $mac")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // 加入队列并启动连接
|
|
|
+ fun checkAndConnect() {
|
|
|
+ if (BleManager.getInstance().allConnectedDevice.size < maxConnectCount) {
|
|
|
+ connectListeners.add(ConnectListener(mac, callBack))
|
|
|
+ connectKey()
|
|
|
+ } else {
|
|
|
+ ThreadUtils.runOnIODelayed(500) {
|
|
|
+ checkAndConnect()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ checkAndConnect()
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 连接监听反注册
|
|
|
+ */
|
|
|
+ fun unregisterConnectListener(mac: String, bleBean: BleBean? = null) {
|
|
|
+ LogUtil.i("unregisterConnectListener : $mac")
|
|
|
+ connectListeners.removeAll { it.mac == mac }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查是否能进行蓝牙连接准备的下一步,防止未准备完但是已经取消订阅
|
|
|
+ */
|
|
|
+ private fun checkProcess(mac: String?): Boolean {
|
|
|
+ val canProcess = connectListeners.any { it.mac == mac }
|
|
|
+ if (!canProcess) sendLoadingEventMsg(null, false)
|
|
|
+ return canProcess
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 连接钥匙,单个mac走完prepare再进行下一个
|
|
|
+ */
|
|
|
+ private fun connectKey() {
|
|
|
+ if (connectListeners.isEmpty()) return
|
|
|
+ if (isPreparing || BleManager.getInstance().allConnectedDevice.size >= maxStandbyCount) {
|
|
|
+ Executor.delayOnMain(1000) { connectKey() }
|
|
|
+ return
|
|
|
+ }
|
|
|
+ val listener = connectListeners.first()
|
|
|
+ currentConnectingMac = listener.mac
|
|
|
+ isPreparing = true
|
|
|
+ if (ActivityTracker.getCurrentActivity() == null) {
|
|
|
+ LogUtil.w("Ignore connectKey : ${listener.mac} no current activity")
|
|
|
+ isPreparing = false
|
|
|
+ currentConnectingMac = null
|
|
|
+ return
|
|
|
+ }
|
|
|
+ prepareBle(
|
|
|
+ listener.mac, ActivityUtils.currentActivity() as BaseActivity<*>, false
|
|
|
+ ) { isDone, bleBean ->
|
|
|
+ Executor.runOnMain {
|
|
|
+ isPreparing = false
|
|
|
+ currentConnectingMac = null
|
|
|
+ if (!isDone) {
|
|
|
+ // 判断是否仍然待连,防止拿走;移到末尾,防止循环影响
|
|
|
+ if (checkProcess(listener.mac)) {
|
|
|
+ unregisterConnectListener(listener.mac)
|
|
|
+ Executor.delayOnMain(2000) {
|
|
|
+ registerConnectListener(
|
|
|
+ listener.mac,
|
|
|
+ listener.callBack
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return@runOnMain
|
|
|
+ }
|
|
|
+ // 判断是否仍然待连,防止拿走
|
|
|
+ // TODO 暂时只处理准备成功
|
|
|
+ if (connectListeners.contains(listener)) {
|
|
|
+ listener.callBack?.invoke(true, bleBean)
|
|
|
+ unregisterConnectListener(listener.mac)
|
|
|
+ }
|
|
|
+// bleBean?.bleDevice?.mac?.let { mac ->
|
|
|
+// addToStandby(mac)
|
|
|
+// }
|
|
|
+ if (connectListeners.isNotEmpty()) connectKey()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加到待机队列并断开最旧超出连接
|
|
|
+ */
|
|
|
+ private fun addToStandby(mac: String) {
|
|
|
+ synchronized(standbyQueue) {
|
|
|
+ standbyQueue.addLast(mac)
|
|
|
+ while (standbyQueue.size > maxStandbyCount) {
|
|
|
+ val oldMac = standbyQueue.removeFirst()
|
|
|
+ deviceList.find { it.bleDevice.mac == oldMac }?.let { oldBean ->
|
|
|
+ LogUtil.i("断开最旧待机设备: $oldMac")
|
|
|
+ BleManager.getInstance().disconnect(oldBean.bleDevice)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param loadingCallBack 是否显示loading、loading文字、流程是否结束
|
|
|
+ * @param prepareDoneCallBack 蓝牙连接是否成功、蓝牙连接对象
|
|
|
+ */
|
|
|
+ private fun prepareBle(
|
|
|
+ mac: String,
|
|
|
+ activity: AppCompatActivity,
|
|
|
+ isNeedLoading: Boolean = false,
|
|
|
+ prepareDoneCallBack: ((Boolean, BleBean?) -> Unit)?
|
|
|
+ ) {
|
|
|
+ if (!checkProcess(mac)) {
|
|
|
+ LogUtil.e("Prepare is canceled : $mac")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ Executor.runOnMain {
|
|
|
+ CommonUtils.checkBlePermission(activity) {
|
|
|
+ doScanBle(mac, isNeedLoading, prepareDoneCallBack)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @AfterPermissionGranted(PERMISSION_REQUEST_CODE)
|
|
|
+ private fun doScanBle(
|
|
|
+ mac: String,
|
|
|
+ isNeedLoading: Boolean = false,
|
|
|
+ prepareDoneCallBack: ((Boolean, BleBean?) -> Unit)?
|
|
|
+ ) {
|
|
|
+ LogUtil.i("doScanBle:$mac")
|
|
|
+ if (!checkProcess(mac)) {
|
|
|
+ LogUtil.e("Prepare is canceled : $mac")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (isNeedLoading) sendEventMsg(
|
|
|
+ MsgEvent(
|
|
|
+ MSG_EVENT_LOADING, LoadingMsg(true, "正在扫描设备...", null)
|
|
|
+ )
|
|
|
+ )
|
|
|
+ BleUtil.instance?.scan(object : CustomBleScanCallback() {
|
|
|
+ override fun onPrompt(promptStr: String?) {
|
|
|
+ // 蓝牙未启动重试
|
|
|
+ BleManager.getInstance().enableBluetooth()
|
|
|
+ doScanBle(mac, isNeedLoading, prepareDoneCallBack)
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onScanStarted(success: Boolean) {
|
|
|
+ LogUtil.i("onScanStarted:${success}")
|
|
|
+ if (!success) {
|
|
|
+ if (isNeedLoading) sendEventMsg(
|
|
|
+ MsgEvent(
|
|
|
+ MSG_EVENT_LOADING, LoadingMsg(false, null, null)
|
|
|
+ )
|
|
|
+ )
|
|
|
+ prepareDoneCallBack?.invoke(false, null)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onScanning(bleDevice: BleDevice?) {
|
|
|
+ LogUtil.i("onScanning:${bleDevice?.mac}")
|
|
|
+ bleDevice?.let {
|
|
|
+ doConnect(it, isNeedLoading, prepareDoneCallBack)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onScanFinished(scanResultList: MutableList<BleDevice>?) {
|
|
|
+ LogUtil.i("onScanFinished: $mac - ${scanResultList?.none { it.mac == mac }}")
|
|
|
+ if (isNeedLoading) sendEventMsg(
|
|
|
+ MsgEvent(
|
|
|
+ MSG_EVENT_LOADING, LoadingMsg(false, null, null)
|
|
|
+ )
|
|
|
+ )
|
|
|
+ // 没有扫描到
|
|
|
+ if (scanResultList?.none { it.mac == mac } == true) {
|
|
|
+ LogUtil.w("$mac is not scanned")
|
|
|
+ prepareDoneCallBack?.invoke(false, null)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 连接蓝牙设备
|
|
|
+ */
|
|
|
+ private fun doConnect(
|
|
|
+ bleDevice: BleDevice,
|
|
|
+ isNeedLoading: Boolean = false,
|
|
|
+ prepareDoneCallBack: ((Boolean, BleBean?) -> Unit)?
|
|
|
+ ) {
|
|
|
+ LogUtil.i("doConnect : ${bleDevice.mac}")
|
|
|
+ if (!checkProcess(bleDevice.mac)) {
|
|
|
+ LogUtil.e("Prepare is canceled : ${bleDevice.mac}")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (isNeedLoading) sendEventMsg(
|
|
|
+ MsgEvent(
|
|
|
+ MSG_EVENT_LOADING,
|
|
|
+ LoadingMsg(true, CommonUtils.getStr(R.string.ble_connecting), null)
|
|
|
+ )
|
|
|
+ )
|
|
|
+ BleManager.getInstance().disconnect(bleDevice)
|
|
|
+ BleUtil.instance?.connectBySelect(
|
|
|
+ bleDevice, object : CustomBleGattCallback() {
|
|
|
+ override fun onPrompt(promptStr: String?) {
|
|
|
+ if (isNeedLoading) sendEventMsg(
|
|
|
+ MsgEvent(
|
|
|
+ MSG_EVENT_LOADING, LoadingMsg(false, promptStr, null)
|
|
|
+ )
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onStartConnect() {}
|
|
|
+
|
|
|
+ override fun onConnectFail(bleDevice: BleDevice?, exception: BleException?) {
|
|
|
+ if (isNeedLoading) sendEventMsg(
|
|
|
+ MsgEvent(
|
|
|
+ MSG_EVENT_LOADING,
|
|
|
+ LoadingMsg(false, CommonUtils.getStr(R.string.ble_connect_fail), false)
|
|
|
+ )
|
|
|
+ )
|
|
|
+ LogUtil.e("onConnectFail : ${bleDevice?.mac} - ${exception?.description}")
|
|
|
+ prepareDoneCallBack?.invoke(false, null)
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onConnectSuccess(
|
|
|
+ bleDevice: BleDevice?, gatt: BluetoothGatt?, status: Int
|
|
|
+ ) {
|
|
|
+ if (isNeedLoading) sendEventMsg(
|
|
|
+ MsgEvent(
|
|
|
+ MSG_EVENT_LOADING, LoadingMsg(false, null, null)
|
|
|
+ )
|
|
|
+ )
|
|
|
+ LogUtil.i("onConnectSuccess : ${bleDevice?.mac}")
|
|
|
+ bleDevice?.let {
|
|
|
+ deviceList.removeIf { it.bleDevice.mac == bleDevice.mac }
|
|
|
+ val bleBean = BleBean(it)
|
|
|
+ deviceList.add(bleBean)
|
|
|
+ removeExceptionKey(it.mac)
|
|
|
+ // 设置MTU
|
|
|
+ Executor.delayOnMain(200) {
|
|
|
+ if (!checkProcess(bleDevice.mac)) {
|
|
|
+ LogUtil.e("Prepare is canceled : ${bleDevice.mac}")
|
|
|
+ return@delayOnMain
|
|
|
+ }
|
|
|
+ BleUtil.instance?.setMtu(it)
|
|
|
+ }
|
|
|
+ // 监听
|
|
|
+ Executor.delayOnMain(500) {
|
|
|
+ indicate(bleBean, isNeedLoading, prepareDoneCallBack)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onDisConnected(
|
|
|
+ isActiveDisConnected: Boolean,
|
|
|
+ device: BleDevice?,
|
|
|
+ gatt: BluetoothGatt?,
|
|
|
+ status: Int
|
|
|
+ ) {
|
|
|
+ if (isNeedLoading) sendEventMsg(
|
|
|
+ MsgEvent(
|
|
|
+ MSG_EVENT_LOADING, LoadingMsg(false, null, false)
|
|
|
+ )
|
|
|
+ )
|
|
|
+ LogUtil.i("onDisConnected : ${device?.mac} - $isActiveDisConnected")
|
|
|
+ getBleDeviceByMac(device?.mac)?.let {
|
|
|
+ deviceList.remove(it)
|
|
|
+ }
|
|
|
+ bleDevice.mac?.let { itMac ->
|
|
|
+ unregisterConnectListener(itMac)
|
|
|
+ }
|
|
|
+ if (!isActiveDisConnected) {
|
|
|
+ // 测试模式下不重连
|
|
|
+ if (isTestMode) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // 断开和重连之间最好间隔一段时间,否则可能会出现长时间连接不上的情况
|
|
|
+ Executor.delayOnMain(300) {
|
|
|
+ registerConnectListener(bleDevice.mac) { isDone, bleBean ->
|
|
|
+ if (isDone && bleBean != null) {
|
|
|
+ Executor.delayOnMain(300) {
|
|
|
+ getCurrentStatus(6, bleBean.bleDevice)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ ModBusController.updateKeyReadyStatus(bleDevice.mac, false, 3)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 监听蓝牙设备
|
|
|
+ */
|
|
|
+ private fun indicate(
|
|
|
+ bleBean: BleBean?,
|
|
|
+ isNeedLoading: Boolean = false,
|
|
|
+ prepareDoneCallBack: ((Boolean, BleBean?) -> Unit)?
|
|
|
+ ) {
|
|
|
+ if (!checkProcess(bleBean?.bleDevice?.mac)) {
|
|
|
+ LogUtil.e("Prepare is canceled : ${bleBean?.bleDevice?.mac}")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (isNeedLoading) sendEventMsg(
|
|
|
+ MsgEvent(
|
|
|
+ MSG_EVENT_LOADING, LoadingMsg(true, "开始监听...", null)
|
|
|
+ )
|
|
|
+ )
|
|
|
+ bleBean?.let {
|
|
|
+ var isIndicateSuccess = false
|
|
|
+ BleUtil.instance?.indicate(
|
|
|
+ it.bleDevice, indicateCallback = object : CustomBleIndicateCallback() {
|
|
|
+ override fun onPrompt(promptStr: String?) {
|
|
|
+ LogUtil.i("indicate onPrompt : $promptStr")
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onConnectPrompt(promptStr: String?) {
|
|
|
+ LogUtil.i("indicate onConnectPrompt : $promptStr")
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onDisConnectPrompt(promptStr: String?) {
|
|
|
+ LogUtil.i("indicate onDisConnectPrompt : $promptStr")
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onIndicateSuccess() {
|
|
|
+ LogUtil.i("onIndicateSuccess")
|
|
|
+ isIndicateSuccess = true
|
|
|
+ getToken(bleBean, isNeedLoading, prepareDoneCallBack)
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onIndicateFailure(exception: BleException?) {
|
|
|
+ if (isNeedLoading) sendEventMsg(
|
|
|
+ MsgEvent(
|
|
|
+ MSG_EVENT_LOADING, LoadingMsg(false, null, false)
|
|
|
+ )
|
|
|
+ )
|
|
|
+ LogUtil.e("onIndicateFailure : ${bleBean.bleDevice.mac} - ${exception?.description}")
|
|
|
+ Executor.delayOnIO(500) {
|
|
|
+ if (isIndicateSuccess) {
|
|
|
+ return@delayOnIO
|
|
|
+ }
|
|
|
+ prepareDoneCallBack?.invoke(false, null)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onCharacteristicChanged(data: ByteArray?) {
|
|
|
+ LogUtil.i("onCharacteristicChanged : ${data?.toHexStrings()}")
|
|
|
+ data?.let { itData ->
|
|
|
+ BusinessManager.handleRsp(
|
|
|
+ it,
|
|
|
+ itData,
|
|
|
+ isNeedLoading,
|
|
|
+ prepareDoneCallBack
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取蓝牙钥匙token
|
|
|
+ */
|
|
|
+ private fun getToken(
|
|
|
+ bleBean: BleBean?,
|
|
|
+ isNeedLoading: Boolean = false,
|
|
|
+ prepareDoneCallBack: ((Boolean, BleBean?) -> Unit)?
|
|
|
+ ) {
|
|
|
+ if (!checkProcess(bleBean?.bleDevice?.mac)) {
|
|
|
+ LogUtil.e("Prepare is canceled : ${bleBean?.bleDevice?.mac}")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (isNeedLoading) sendEventMsg(
|
|
|
+ MsgEvent(
|
|
|
+ MSG_EVENT_LOADING, LoadingMsg(true, "开始获取token...", null)
|
|
|
+ )
|
|
|
+ )
|
|
|
+ bleBean?.let {
|
|
|
+ BleCmdManager.getToken(it.bleDevice.mac, object : CustomBleWriteCallback() {
|
|
|
+ override fun onWriteSuccess(current: Int, total: Int, justWrite: ByteArray?) {
|
|
|
+ if (isNeedLoading) sendEventMsg(
|
|
|
+ MsgEvent(
|
|
|
+ MSG_EVENT_LOADING, LoadingMsg(false, "token获取成功", null)
|
|
|
+ )
|
|
|
+ )
|
|
|
+ LogUtil.i("getToken success : ${bleBean.bleDevice.mac}")
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onWriteFailure(exception: BleException?) {
|
|
|
+ if (isNeedLoading) sendEventMsg(
|
|
|
+ MsgEvent(
|
|
|
+ MSG_EVENT_LOADING, LoadingMsg(false, "token获取失败", false)
|
|
|
+ )
|
|
|
+ )
|
|
|
+ LogUtil.e("getToken fail : ${bleBean.bleDevice.mac}")
|
|
|
+ prepareDoneCallBack?.invoke(false, null)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 蓝牙连接准备监听
|
|
|
+ data class ConnectListener(
|
|
|
+ val mac: String,
|
|
|
+ val callBack: ((Boolean, BleBean?) -> Unit)? = null
|
|
|
+ )
|
|
|
+}
|