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.modbus.ModBusController.controlKeyCharge 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 kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext import pub.devrel.easypermissions.AfterPermissionGranted import java.util.LinkedList import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine /** * 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() // 原有回调管理 private val connectListeners = mutableListOf() private var isPreparing: Boolean = false @Volatile private var currentConnectingMac: String? = null /** * 注册连接监听: * - 如果设备已在 deviceList 且拥有 token,立即回调并返回 * - 如果 mac 已在待连接队列或正在连接,忽略重复请求 * - 否则将 mac 添加到队列并触发连接流程 */ fun registerConnectListener( mac: String, connectNow: Boolean = false, callBack: ((Boolean, BleBean?) -> Unit)? = null ) { LogUtil.i("蓝牙连接-开始连接 : $mac") // 已连接且已获取 token deviceList.find { it.bleDevice.mac == mac && BleManager.getInstance().isConnected(mac) && it.token != null }?.let { bean -> LogUtil.i("蓝牙连接-设备已连接") callBack?.invoke(true, bean) return } if (connectNow) { LogUtil.w("蓝牙连接-立即连接 mac: $mac") unregisterConnectListener(mac) } // 重复注册检查 if (connectListeners.any { it.mac == mac } || currentConnectingMac == mac) { LogUtil.w("蓝牙连接-忽略重复注册 mac: $mac") callBack?.invoke(false, null) return } // 加入队列并启动连接 fun checkAndConnect(isDisconnectAll: Boolean = false) { LogUtil.w("蓝牙连接-开始检查连接 mac: $mac") if (BleManager.getInstance().allConnectedDevice.size < maxConnectCount) { connectListeners.add(ConnectListener(mac, callBack)) connectKey() } else { if (connectNow && !isDisconnectAll) { LogUtil.i("蓝牙连接-超过最大连接数,但是需要立即连接,断开所有连接进行连接") BleManager.getInstance().disconnectAllDevice() checkAndConnect(true) } 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?, hideLoading: Boolean = true): Boolean { val canProcess = connectListeners.any { it.mac == mac } if (!canProcess && hideLoading) sendLoadingEventMsg(null, false) return canProcess } /** * 连接钥匙,单个mac走完prepare再进行下一个 */ private fun connectKey() { if (connectListeners.isEmpty()) return if (isPreparing || BleManager.getInstance().allConnectedDevice.size > maxStandbyCount) { LogUtil.i("暂时不能连接:${isPreparing},${BleManager.getInstance().allConnectedDevice.size > maxStandbyCount}") Executor.delayOnMain(1000) { connectKey() } return } val listener = connectListeners.first() currentConnectingMac = listener.mac ThreadUtils.runOnIODelayed(10 * 1000) { isPreparing = false } 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)) { listener.callBack?.invoke(false, null) unregisterConnectListener(listener.mac) } return@runOnMain } // 判断是否仍然待连,防止拿走 // TODO 暂时只处理准备成功 if (connectListeners.contains(listener)) { listener.callBack?.invoke(true, bleBean) unregisterConnectListener(listener.mac) } if (connectListeners.isNotEmpty()) connectKey() } } } /** * @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?) { // 蓝牙未启动重试 LogUtil.i("蓝牙连接-参数:${promptStr}") 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?) { val mac = bleDevice?.mac ?: return LogUtil.i("蓝牙连接-onScanning:$mac") if (mac.equals(mac, ignoreCase = true)) { // 找到目标设备,马上停止扫描 LogUtil.i("找到目标设备 $mac,停止扫描并尝试连接") BleManager.getInstance().cancelScan() // 立刻调用 doConnect,下一步进入连接流程 doConnect(bleDevice, isNeedLoading, prepareDoneCallBack) } } override fun onScanFinished(scanResultList: MutableList?) { 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) ) ) ThreadUtils.runOnIO { BleManager.getInstance().disconnect(bleDevice) delay(300) 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) it.token = null } 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) } }) } } /** * 对单个 MAC 做下面两步: * 1. 先尝试不充电连接,若成功就返回 true; * 2. 否则开启“充电”,等 500ms,再尝试一次连接,连接成功后断电并返回 true;否则返回 false。 */ suspend fun tryConnectWithOptionalCharge(mac: String, withOpenCharge: Boolean = true): Boolean = withContext(Dispatchers.IO) { // -------- 第一次尝试 -------- LogUtil.i("蓝牙连接-第一次尝试") val firstTry = suspendCancellableCoroutine { cont -> // 1. 定义一个 flag,确保只 resume 一次 var isCalled = false BusinessManager.registerConnectListener(mac, true) { isDone, _ -> if (isCalled) { return@registerConnectListener } isCalled = true if (isDone) { // 连接成功后,把电关掉(以防万一) controlKeyCharge(false, mac) { } cont.resume(true) } else { cont.resume(false) } cont.cancel() } } LogUtil.i("蓝牙连接-第一次连接:${firstTry},是否继续尝试上电连接${withOpenCharge}") if (firstTry) { return@withContext true } if (!withOpenCharge) { return@withContext false } // -------- 第二次尝试:先开电,再连 -------- // 开电,并等待回调 suspendCoroutine { unitCont -> controlKeyCharge(false, mac) { ThreadUtils.runOnIO { delay(500) controlKeyCharge(true, mac) { unitCont.resume(Unit) } } } } LogUtil.i("蓝牙连接-开启充电并等待500ms") // 等 500ms 保证硬件电源稳定 delay(500) // 再次注册连接监听 val secondTry = suspendCancellableCoroutine { cont -> var isCalled = false BusinessManager.registerConnectListener(mac, true) { isDone, _ -> if (isCalled) { return@registerConnectListener } isCalled = true // 无论成功或失败,都先把电关掉 controlKeyCharge(false, mac) { } cont.resume(isDone) cont.cancel() } } return@withContext secondTry } /** * 扫描在线的蓝牙钥匙并发送指令关机 */ suspend fun scanOnlineKeyLockMacAndSwitchModeToClose(): Boolean { return suspendCancellableCoroutine { parentCont -> BleUtil.instance?.scan(object : CustomBleScanCallback() { override fun onPrompt(promptStr: String?) { // 蓝牙未启动重试 LogUtil.i("设备录入-参数:${promptStr}") BleManager.getInstance().enableBluetooth() ThreadUtils.runOnMainDelayed(300) { scanOnlineKeyLockMacAndSwitchModeToClose() } } override fun onScanStarted(success: Boolean) { LogUtil.i("设备录入-onScanStarted:${success}") if (!success) { ThreadUtils.runOnMainDelayed(300) { scanOnlineKeyLockMacAndSwitchModeToClose() } } } override fun onScanning(bleDevice: BleDevice?) { val mac = bleDevice?.mac ?: return LogUtil.i("设备录入-onScanning:$mac") } override fun onScanFinished(scanResultList: MutableList?) { val devicesSnapshot = scanResultList?.toList().orEmpty() ThreadUtils.runOnIO { devicesSnapshot.forEach { val connected = tryConnectWithOptionalCharge(it.mac, false) if (connected) { val sendSuccess = sendEmptyTicketJson(it) LogUtil.i("设备录入-发送切换工作模式:${it.mac},${sendSuccess}") } } parentCont.resume(true) } } }) } } /** * 发送空作业票 */ private suspend fun sendEmptyTicketJson(bleDevice: BleDevice): Boolean { return suspendCancellableCoroutine { cont -> BleCmdManager.sendWorkTicket( BusinessManager.generateEmptyTicketSendJson(), bleDevice = bleDevice, callback = object : CustomBleWriteCallback() { override fun onWriteSuccess( current: Int, total: Int, justWrite: ByteArray? ) { ThreadUtils.runOnIO { delay(3000) cont.resume(switchWorkMode(bleDevice)) } } override fun onWriteFailure(exception: BleException?) { ThreadUtils.runOnMainDelayed(300) { cont.resume(sendEmptyTicketJson(bleDevice)) } } }) } } /** * 切换工作模式 */ private suspend fun switchWorkMode(bleDevice: BleDevice): Boolean { return suspendCancellableCoroutine { cont -> BleCmdManager.switchMode( BleConst.STATUS_WORK, bleDevice, object : CustomBleWriteCallback() { override fun onWriteSuccess( current: Int, total: Int, justWrite: ByteArray? ) { BleManager.getInstance().disconnect(bleDevice) ThreadUtils.runOnIO { delay(800) cont.resume(true) } LogUtil.i("设备录入-切换模式发送成功 : ${bleDevice.mac}") } override fun onWriteFailure(exception: BleException?) { LogUtil.e("设备录入-切换模式发送失败 : ${bleDevice.mac}") ThreadUtils.runOnMainDelayed(300) { cont.resume(sendEmptyTicketJson(bleDevice)) } } }) } } /** * 切换待机模式 */ fun switchReadyMode(bleDevice: BleDevice) { BleCmdManager.switchMode( BleConst.STATUS_READY, bleDevice, object : CustomBleWriteCallback() { override fun onWriteSuccess( current: Int, total: Int, justWrite: ByteArray? ) { BleManager.getInstance().disconnect(bleDevice) LogUtil.i("设备录入-切换模式发送成功 : ${bleDevice.mac}") } override fun onWriteFailure(exception: BleException?) { LogUtil.e("设备录入-切换模式发送失败 : ${bleDevice.mac}") } }) } /** * 扫描在线的蓝牙 */ fun scanOnlineKeyLockMac(callback: (MutableList?) -> Unit) { BleUtil.instance?.scan(object : CustomBleScanCallback() { override fun onPrompt(promptStr: String?) { // 蓝牙未启动重试 LogUtil.d("设备录入-参数:${promptStr}") BleManager.getInstance().enableBluetooth() ThreadUtils.runOnMainDelayed(300) { scanOnlineKeyLockMac(callback) } } override fun onScanStarted(success: Boolean) { LogUtil.d("设备录入-onScanStarted:${success}") if (!success) { ThreadUtils.runOnMainDelayed(300) { scanOnlineKeyLockMac(callback) } } } override fun onScanning(bleDevice: BleDevice?) { val mac = bleDevice?.mac ?: return LogUtil.d("设备录入-onScanning:$mac") } override fun onScanFinished(scanResultList: MutableList?) { LogUtil.d("设备录入-扫描完成:$scanResultList") callback(scanResultList) } }) } // 蓝牙连接准备监听 data class ConnectListener( val mac: String, val callBack: ((Boolean, BleBean?) -> Unit)? = null ) }