package com.grkj.iscs_mars //todo 所有蓝牙包替换com.clj. -> com.clj. import android.bluetooth.BluetoothGattCharacteristic import android.content.Context import android.content.Intent import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.MutableLiveData import com.google.gson.Gson import com.grkj.iscs_mars.ble.BleBean import com.grkj.iscs_mars.ble.BleCmdManager import com.grkj.iscs_mars.ble.BleConnectionManager import com.grkj.iscs_mars.ble.BleConst import com.grkj.iscs_mars.ble.BleConst.STATUS_READY import com.grkj.iscs_mars.ble.BleConst.STATUS_WORK import com.grkj.iscs_mars.ble.BleReturnDispatcher import com.grkj.iscs_mars.ble.BleSendDispatcher import com.grkj.iscs_mars.ble.BleUtil import com.grkj.iscs_mars.ble.CustomBleWriteCallback import com.grkj.iscs_mars.enums.NoKeyReason import com.grkj.iscs_mars.extentions.removeLeadingZeros import com.grkj.iscs_mars.extentions.serialNo import com.grkj.iscs_mars.extentions.startsWith import com.grkj.iscs_mars.extentions.toHexStrings import com.grkj.iscs_mars.modbus.DockBean import com.grkj.iscs_mars.modbus.ModBusController import com.grkj.iscs_mars.modbus.ModBusController.dockList import com.grkj.iscs_mars.modbus.ModBusController.getOneKey import com.grkj.iscs_mars.model.Constants.USER_TYPE_LOCKER import com.grkj.iscs_mars.model.DeviceConst import com.grkj.iscs_mars.model.DeviceConst.DEVICE_TYPE_CARD import com.grkj.iscs_mars.model.DeviceConst.DEVICE_TYPE_FINGERPRINT import com.grkj.iscs_mars.model.DeviceConst.DEVICE_TYPE_KEY import com.grkj.iscs_mars.model.DeviceConst.DEVICE_TYPE_LOCK import com.grkj.iscs_mars.model.DeviceConst.DOCK_TYPE_ELEC_LOCK_BOARD import com.grkj.iscs_mars.model.DeviceConst.DOCK_TYPE_KEY import com.grkj.iscs_mars.model.DeviceConst.DOCK_TYPE_LOCK import com.grkj.iscs_mars.model.DeviceConst.DOCK_TYPE_PORTABLE import com.grkj.iscs_mars.model.DictAndSystemConstants import com.grkj.iscs_mars.model.ISCSDomainData import com.grkj.iscs_mars.model.bo.DeviceTakeUpdateBO import com.grkj.iscs_mars.model.bo.UpdateKeyReturnBO import com.grkj.iscs_mars.model.bo.WorkTicketGetBO import com.grkj.iscs_mars.model.bo.WorkTicketSendBO import com.grkj.iscs_mars.model.bo.WorkTicketSendBO.LockListBO import com.grkj.iscs_mars.model.eventmsg.CurrentModeMsg import com.grkj.iscs_mars.model.eventmsg.DeviceExceptionMsg import com.grkj.iscs_mars.model.eventmsg.DeviceTakeUpdateMsg import com.grkj.iscs_mars.model.eventmsg.LoadingMsg import com.grkj.iscs_mars.model.eventmsg.MsgEvent import com.grkj.iscs_mars.model.eventmsg.MsgEventConstants import com.grkj.iscs_mars.model.eventmsg.MsgEventConstants.MSG_EVENT_CURRENT_MODE import com.grkj.iscs_mars.model.eventmsg.MsgEventConstants.MSG_EVENT_DEVICE_EXCEPTION import com.grkj.iscs_mars.model.eventmsg.MsgEventConstants.MSG_EVENT_DEVICE_TAKE_UPDATE import com.grkj.iscs_mars.model.eventmsg.MsgEventConstants.MSG_EVENT_LOADING import com.grkj.iscs_mars.model.eventmsg.MsgEventConstants.MSG_EVENT_SWITCH_COLLECTION_UPDATE import com.grkj.iscs_mars.model.eventmsg.MsgEventConstants.MSG_EVENT_SWITCH_MODE import com.grkj.iscs_mars.model.eventmsg.MsgEventConstants.MSG_EVENT_UPDATE_TICKET_PROGRESS import com.grkj.iscs_mars.model.eventmsg.SwitchModeMsg import com.grkj.iscs_mars.model.eventmsg.UpdateTicketProgressMsg import com.grkj.iscs_mars.model.vo.dict.CommonDictRespVO import com.grkj.iscs_mars.model.vo.hardware.CabinetSlotsRespVo import com.grkj.iscs_mars.model.vo.hardware.SwitchListReqVO import com.grkj.iscs_mars.model.vo.key.KeyPageRespVO import com.grkj.iscs_mars.model.vo.lock.LockPageRespVO import com.grkj.iscs_mars.model.vo.lock.LockTakeUpdateReqVO import com.grkj.iscs_mars.model.vo.ticket.LockPointUpdateReqVO import com.grkj.iscs_mars.model.vo.ticket.TicketDetailRespVO import com.grkj.iscs_mars.service.CheckKeyInfoTask import com.grkj.iscs_mars.util.ActivityUtils import com.grkj.iscs_mars.util.CommonUtils import com.grkj.iscs_mars.util.Executor import com.grkj.iscs_mars.util.NetApi import com.grkj.iscs_mars.util.SPUtils import com.grkj.iscs_mars.util.ToastUtils import com.grkj.iscs_mars.util.log.LogUtil import com.grkj.iscs_mars.view.activity.LoginActivity import com.grkj.iscs_mars.view.base.BaseActivity import com.grkj.iscs_mars.view.dialog.TipDialog import com.huyuhui.fastble.data.BleDevice import com.huyuhui.fastble.exception.BleException import com.sik.cronjob.managers.CronJobScanner import com.sik.sikandroid.activity.ActivityTracker import com.sik.sikcore.SIKCore import com.sik.sikcore.date.TimeUtils import com.sik.sikcore.thread.ThreadUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext import kotlin.coroutines.resume /** * 业务层管理 */ object BusinessManager { // 消息总线 val mEventBus = MutableLiveData() @JvmStatic @Volatile // 已连接的蓝牙钥匙集合 var deviceList: MutableList = mutableListOf() // Modbus数据页面监听 class DeviceListener( val key: Any, val callBack: (DockBean) -> Unit ) private val listeners = ArrayList() private var initListener: (() -> Unit)? = null // 归还设备是否需要登录 var NEED_AUTH = true // 归还设备是否需要登录及角色验证 var CAN_RETURN = true get() { val loginUser = SPUtils.getLoginUser(MyApplication.instance!!.applicationContext!!) return (NEED_AUTH && loginUser != null) || !NEED_AUTH } // 设备待取列表(需要报给后台的列表,等实际取完再上报) @JvmStatic val mDeviceTakeList = mutableListOf() // 是否是测试人员登录的 var isTestMode = false // 有问题的钥匙的列表 - rfid var mExceptionKeyList = mutableListOf() /** * 检查钥匙任务 */ var checkKeyInfoTask: CheckKeyInfoTask = CheckKeyInfoTask() /** * 初始化消息总线 */ fun initMsgEventBus() { mEventBus.observeForever { LogUtil.i("msgEvent : $it") when (it.code) { // loading消息 MSG_EVENT_LOADING -> { Executor.runOnMain { val loadingMsg = it.data as LoadingMsg (ActivityUtils.currentActivity() as BaseActivity<*>).handleLoading( loadingMsg.isShow, loadingMsg.loadingText ) } } // 设备取出 MSG_EVENT_DEVICE_TAKE_UPDATE -> { handleDeviceTake(it.data as DeviceTakeUpdateMsg) } MsgEventConstants.MSG_EVENT_INIT_KEY_COMPLETE -> { val job = CronJobScanner.scanJobs(checkKeyInfoTask) MyApplication.cronJobManager.registerJobs(job) } // 钥匙当前模式 MSG_EVENT_CURRENT_MODE -> { handleCurrentMode(it.data as CurrentModeMsg) } // 钥匙切换模式结果 MSG_EVENT_SWITCH_MODE -> { when ((it.data as SwitchModeMsg).job) { // 工作模式 1 -> { if (it.data.res == 1) { // 只能在这里断开,不能全部断开 BleSendDispatcher.scheduleDisconnect(it.data.bleBean.bleDevice.mac) // 打开钥匙卡扣 val keyBean = ModBusController.getKeyByMac(it.data.bleBean.bleDevice.mac) if (keyBean == null) { sendEventMsg( MsgEvent( MSG_EVENT_LOADING, LoadingMsg(false, "未找到钥匙信息", false) ) ) ToastUtils.tip(R.string.key_not_exists) } else { sendLoadingEventMsg(CommonUtils.getStr(R.string.take_out_key_tip)) val dock = ModBusController.getDockByKeyMac(it.data.bleBean.bleDevice.mac) keyBean.isReady = false ModBusController.controlKeyBuckle( true, keyBean.isLeft, dock?.addr ) ModBusController.updateKeyReadyStatus( it.data.bleBean.bleDevice.mac, false, 1 ) ToastUtils.tip(R.string.take_out_key) ThreadUtils.runOnIO { checkMyTodoForHandleKey(it.data.bleBean.bleDevice.mac) } } } else { LogUtil.e("切换工作模式失败 : ${it.data.bleBean.bleDevice.mac}") Executor.delayOnMain(500) { switchWorkMode(it.data.bleBean.bleDevice, false) } } } // 待机模式 2 -> { if (it.data.res == 1) { ModBusController.updateKeyReadyStatus( it.data.bleBean.bleDevice.mac, true, 2 ) // 延时再次获取当前状态,触发handleCurrentMode里工作票下发状态检查 Executor.delayOnMain(500) { getCurrentStatus(1, it.data.bleBean.bleDevice) } } else { LogUtil.e("切换待机模式失败 : ${it.data.bleBean.bleDevice.mac}") Executor.delayOnMain(500) { switchReadyMode(it.data.bleBean.bleDevice) } } } } } MSG_EVENT_SWITCH_COLLECTION_UPDATE -> { ThreadUtils.runOnIO { val switchListReqVOS = ModBusController.getSwitchData().map { SwitchListReqVO( it.idx.toString(), if (it.enabled) "1" else "0", TimeUtils.nowString(TimeUtils.DEFAULT_DATE_HOUR_MIN_SEC_FORMAT) ) } NetApi.updateSwitchList(switchListReqVOS) { LogUtil.i("开关更新完成") } } } } } } /** * 检查我的待办 */ suspend fun checkMyTodoForHandleKey(mac: String? = null, delayMillis: Long = 60_0000): Boolean { if (!CAN_RETURN) { return false } val result = suspendCancellableCoroutine { cont -> NetApi.getMySelfState { if (it) { LogUtil.i("蓝牙连接-存在上锁解锁待办") if (BleSendDispatcher.canConnect()) { LogUtil.i("蓝牙连接-发送队列可以连接") mac?.let { connectExistsKey(listOf(it)) } } } else { LogUtil.i("蓝牙连接-没有待办数据,延迟断开所有发送连接") BleSendDispatcher.disconnectAll(delayMillis) } if (cont.isActive) { cont.resume(it) } } } return result } /** * 连接一把存在的可连接的钥匙 */ fun connectExistsKey(exceptKeyMac: List = listOf()) { ThreadUtils.runOnIO { // —— 串行请求1 & 2 —— val slotsPage = getSlotsPage() // —— 并行加载字典(或按需串行也行) —— val slotStatus = async { fetchDict(DictAndSystemConstants.KEY_SLOT_STATUS) } val keyStatus = async { fetchDict(DictAndSystemConstants.KEY_KEY_STATUS) } val slotType = async { fetchDict(DictAndSystemConstants.KEY_SLOT_TYPE) } // 等待字典加载完成 val slotStatusList = slotStatus.await() val keyStatusList = keyStatus.await() val slotTypeList = slotType.await() withContext(Dispatchers.Default) { val keyPage = withContext(Dispatchers.IO) { getKeyPage() } getOneKey( slotsPage?.records?.filter { it.slotType == slotTypeList.find { d -> d.dictLabel == "钥匙" }?.dictValue && it.status == slotStatusList.find { d -> d.dictLabel == "异常" }?.dictValue }?.toMutableList() ?: mutableListOf(), (keyPage?.records?.filter { it.exStatus == keyStatusList.find { d -> d.dictLabel == "异常" }?.dictValue } ?.map { it.keyNfc ?: "" }?.toMutableList() ?: mutableListOf()), exceptKeyMac ) } } } /****************************************** ModBus ******************************************/ /** * 链接底座 */ fun connectDock(isNeedInit: Boolean = false) { ModBusController.interruptReadTrashBinStatus(false) ModBusController.start(MyApplication.instance!!.applicationContext) ModBusController.unregisterListener(MyApplication.instance!!.applicationContext) if (isNeedInit) { ModBusController.initDevicesStatus() } } /** * 断开底座链接 */ fun disconnectDock() { ModBusController.stop() } /** * 注册状态监听 */ fun registerStatusListener(key: Any, listener: (DockBean) -> Unit) { listeners.add(DeviceListener(key, listener)) } /** * 注册初始化监听 */ fun registerInitListener(listener: () -> Unit) { this.initListener = listener } /** * 取消注册初始化监听 */ fun unRegisterInitListener() { this.initListener = null } /** * 取消注册状态监听 */ fun unregisterListener(key: Any) { val it = listeners.iterator() while (it.hasNext()) { if (it.next().key == key) { it.remove() } } } /** * 总的监听,做预处理,其余的所有监听均使用本监听处理后的数据,只允许调用一次 */ fun registerMainListener() { ModBusController.registerStatusListener(this) { res -> deviceStatusHandle(res) } } /** * 硬件状态 * 1、检测到有钥匙 * 2、上锁 * 3、开启充电 * 4、蓝牙连接 * 5、蓝牙数据通讯 */ private fun deviceStatusHandle(res: Any) { LogUtil.i("硬件状态:${(res as List).map { it.toHexStrings() }}") if (res.isEmpty() || res.any { it.isEmpty() }) { var tipStr = CommonUtils.getStr(R.string.no_response_board_exists) + " : " val addressList = mutableListOf() ModBusController.modBusManager?.mSlaveAddressList?.forEach { itDock -> if (res.none { it.isNotEmpty() && it[0] == itDock }) { addressList.add("0x${String.format("%02X", itDock)}") } } tipStr += addressList ToastUtils.tip(tipStr) } res.forEachIndexed { index, bytes -> val dockBean = ModBusController.updateStatus(bytes) ?: return@forEachIndexed ModBusController.isInitReady = true when (dockBean.type) { DOCK_TYPE_KEY -> { if (!CAN_RETURN) { return@forEachIndexed } dockBean.getKeyList().forEach { keyBean -> deviceKeyHandler(dockBean, keyBean) } } DOCK_TYPE_LOCK -> { if (!CAN_RETURN) { return@forEachIndexed } dockBean.getLockList().forEach { lockBean -> deviceLockHandler(dockBean, lockBean) } } DOCK_TYPE_ELEC_LOCK_BOARD -> { // TODO 占位 } DOCK_TYPE_PORTABLE -> { // TODO 便携式待完善 dockBean.deviceList.forEach { deviceBean -> if (deviceBean.isExist) { when (deviceBean.type) { DEVICE_TYPE_KEY -> { if (!CAN_RETURN) { return@forEachIndexed } deviceKeyHandler(dockBean, deviceBean as DockBean.KeyBean) } DEVICE_TYPE_LOCK -> { if (!CAN_RETURN) { return@forEachIndexed } deviceLockHandler(dockBean, deviceBean as DockBean.LockBean) } DEVICE_TYPE_CARD -> { ModBusController.readPortalCaseCardRfid(dockBean.addr) { res -> if (res.size < 11) { LogUtil.e("Portal Case card rfid error") return@readPortalCaseCardRfid } val rfid = res.copyOfRange(3, 11).toHexStrings(false) .removeLeadingZeros() LogUtil.i("卡片RFID : $rfid") } } DEVICE_TYPE_FINGERPRINT -> { } } } } } DeviceConst.DOCK_TYPE_COLLECT -> { ModBusController.switchStatus(bytes) {} } } Executor.delayOnMain(200) { if (!ISCSDomainData.isDeviceRegistration) { listeners.forEach { it.callBack(dockBean) } } } } Executor.delayOnMain(200) { if (ISCSDomainData.isDeviceRegistration) { initListener?.invoke() } } } /** * 挂锁处理 */ private fun deviceLockHandler( dockBean: DockBean, lockBean: DockBean.LockBean ) { if (lockBean.isExist) { ModBusController.readLockRfid(dockBean.addr, lockBean.idx) { res -> if (res.size < 11) { LogUtil.e("Lock rfid error") return@readLockRfid } val rfid = res.copyOfRange(3, 11).toHexStrings(false).removeLeadingZeros() ModBusController.updateLockRfid( dockBean.addr, lockBean.idx, rfid ) ThreadUtils.runOnIO { val lockStatusReq = async { fetchDict(DictAndSystemConstants.KEY_PAD_LOCK_STATUS) } val slotStatus = async { fetchDict(DictAndSystemConstants.KEY_SLOT_STATUS) } val slotType = async { fetchDict(DictAndSystemConstants.KEY_SLOT_TYPE) } val slotsPageReq = async { getSlotsPage() } var lockStatus = lockStatusReq.await() val slotsPage = slotsPageReq.await() val slotStatusList = slotStatus.await() val slotTypeList = slotType.await() NetApi.getIsLockPage { lockData -> //锁rfid未异常正常请求锁数据,关锁 if (rfid in (lockData?.records?.filter { it.exStatus == lockStatus.find { it.dictLabel == "异常" }?.dictValue } ?.map { it.lockNfc }?.toMutableList() ?: mutableListOf())) { ToastUtils.tip( MyApplication.instance?.applicationContext!!.getString( R.string.lock_exception_tag ) ) } else if (slotsPage?.records?.filter { it.slotType == slotTypeList.find { d -> d.dictLabel == "锁" }?.dictValue && it.status == slotStatusList.find { d -> d.dictLabel == "异常" }?.dictValue } ?.find { it.row?.toInt() == dockBean.row && (lockBean.idx + 1) == it.col?.toInt() } != null) { ToastUtils.tip( MyApplication.instance?.applicationContext!!.getString( R.string.slot_exception_tag ) ) } else { NetApi.getLockInfo(rfid) { if (it != null) { NetApi.getLockStateByNfc(rfid) { if (it != 1) { ModBusController.controlLockBuckle( false, dockBean.addr, lockBean.idx ) { itRst -> if (itRst.isNotEmpty()) { // 上报锁具信息 NetApi.updateLockReturn( rfid, MyApplication.instance!!.serialNo() ) {} } } } else { ToastUtils.tip(CommonUtils.getStr(R.string.ticket_not_finish_can_not_return_lock)) } } } } } } } } } else { LogUtil.i("挂锁取出-:${lockBean.rfid}") sendEventMsg( MsgEvent( MSG_EVENT_DEVICE_TAKE_UPDATE, DeviceTakeUpdateMsg(DEVICE_TYPE_LOCK, lockBean.rfid) ) ) } } private fun deviceKeyHandler(dockBean: DockBean, keyBean: DockBean.KeyBean) { if (keyBean.isExist) { // 放回钥匙,读取rfid ModBusController.readKeyRfid( dockBean.addr, if (keyBean.isLeft) 0 else 1 ) { isLeft, res -> if (!ISCSDomainData.isDeviceRegistration) { ModBusController.controlKeyCharge( true, keyBean.isLeft, dockBean.addr ) } if (res.size < 11) { LogUtil.e("Key rfid error") return@readKeyRfid } val rfid = res.copyOfRange(3, 11).toHexStrings(false).removeLeadingZeros() ThreadUtils.runOnIO { val slotStatus = async { fetchDict(DictAndSystemConstants.KEY_SLOT_STATUS) } val slotType = async { fetchDict(DictAndSystemConstants.KEY_SLOT_TYPE) } val slotsPageReq = async { getSlotsPage() } val keyStatusReq = async { fetchDict(DictAndSystemConstants.KEY_KEY_STATUS) } val keyPageReq = async { getKeyPage() } var keyStatus = keyStatusReq.await() var keyData = keyPageReq.await() val slotsPage = slotsPageReq.await() val slotStatusList = slotStatus.await() val slotTypeList = slotType.await() //锁钥匙未异常正常请求锁数据,关锁 if (rfid in (keyData?.records?.filter { it.exStatus == keyStatus.find { it.dictLabel == "异常" }?.dictValue } ?.map { it.keyNfc }?.toMutableList() ?: mutableListOf())) { ToastUtils.tip( MyApplication.instance?.applicationContext!!.getString( R.string.key_exception_tag ) ) } else if (slotsPage?.records?.filter { it.slotType == slotTypeList.find { d -> d.dictLabel == "钥匙" }?.dictValue && it.status == slotStatusList.find { d -> d.dictLabel == "异常" }?.dictValue } ?.find { it.row?.toInt() == dockBean.row && it.col?.toInt() == (dockBean.col + (if (keyBean.isLeft) 0 else 1) * 2) } != null) { ToastUtils.tip( MyApplication.instance?.applicationContext!!.getString( R.string.slot_exception_tag ) ) } else { ModBusController.updateKeyRfid( dockBean.addr, keyBean.isLeft, rfid ) // 放回钥匙,上锁 ModBusController.controlKeyBuckle( false, keyBean.isLeft, dockBean.addr ) { NetApi.getKeyInfo(rfid) { ModBusController.updateKeyNewHardware( dockBean.addr, true, it == null ) if (it != null && !it.macAddress.isNullOrEmpty()) { ModBusController.updateKeyMac( dockBean.addr, keyBean.isLeft, it.macAddress ) ModBusController.updateKeyReadyStatus( it.macAddress, false, 5 ) } else { LogUtil.e("Get key info fail : $rfid") if (!ISCSDomainData.isDeviceRegistration) { ToastUtils.tip(R.string.get_key_info_fail) } ModBusController.controlKeyBuckle( true, keyBean.isLeft, dockBean.addr ) } } } } } } } else if (!keyBean.isCharging) {//增加充电判断,防止无线充电干扰锁仓状态导致判断为取出 // 移出待连监听集合,防止connectKey循环失败 keyBean.mac?.let { unregisterConnectListener(it) } sendEventMsg( MsgEvent( MSG_EVENT_DEVICE_TAKE_UPDATE, DeviceTakeUpdateMsg(DEVICE_TYPE_KEY, keyBean.rfid) ) ) } } /** * 更新所有锁仓状态 */ fun updateAllBuckleStatus(done: () -> Unit) { ModBusController.updateAllBuckleStatus(done) } /** * 更新开关状态 */ fun updateSwitchStatus(done: () -> Unit) { ModBusController.updateSwitchStatus(done) } /** * 钥匙归还提示确认弹窗,当前策略:作业票未完成是否强制上传数据 */ private fun showKeyReturnUploadDataForceDialog(onConfirm: () -> Unit, onCancel: () -> Unit) { val ctx = ActivityUtils.currentActivity() as BaseActivity<*> val dlg = TipDialog(ctx) dlg.setTip(ctx.getString(R.string.key_return_force_tip)) dlg.setType(TipDialog.TYPE_ALL) // 加个选择判断,如果是直接取消弹框而不是点击“确定”,当成确定 var state = 0 dlg.setConfirmListener { state = 1 onConfirm.invoke() } dlg.setOnCancelListener { state = 2 onCancel.invoke() } dlg.setOnDismissListener { if (state == 0) { onCancel.invoke() } } dlg.show() } /** * 钥匙归还提示确认弹框,当前策略:作业票未完成禁止归还钥匙 */ private fun showKeyReturnDialog(onConfirm: () -> Unit) { val ctx = ActivityUtils.currentActivity() as BaseActivity<*> val dlg = TipDialog(ctx) dlg.setTip(ctx.getString(R.string.key_return_tip)) dlg.setType(TipDialog.TYPE_CONFIRM) // 加个选择判断,如果是直接取消弹框而不是点击“确定”,当成确定 var state = 0 dlg.setConfirmListener { state = 1 onConfirm.invoke() } dlg.setOnDismissListener { if (state == 0) { onConfirm.invoke() } } dlg.show() } fun readLockBuckleStatus() { // TODO slaveIdx暂时写死,调试用 ModBusController.readBuckleStatus(true, 0) { type, res -> LogUtil.i("单slave卡扣状态 : $type - ${res.toHexStrings()}") when (type) { 0 -> { val isLeftLock = (res[4].toInt() shr 0) and 0x1 == 1 val isRightLock = (res[4].toInt() shr 4) and 0x1 == 1 LogUtil.i("锁具底座卡扣状态 : $isLeftLock - $isRightLock") } 1 -> { val tempList = mutableListOf() for (i in 0..7) { tempList.add((res[4].toInt() shr i) and 0x1 == 1) } LogUtil.i("锁具底座卡扣1-8状态 : $tempList") } 2 -> { val lock9Status = (res[4].toInt() shr 0) and 0x1 == 1 val lock10Status = (res[4].toInt() shr 1) and 0x1 == 1 LogUtil.i("锁具底座卡扣9、10状态 : $lock9Status - $lock10Status") } } } } fun readKeyBuckleStatus() { // TODO slaveIdx暂时写死,调试用 ModBusController.readBuckleStatus(false, 1) { type, res -> LogUtil.i("单slave卡扣状态 : $type - ${res.toHexStrings()}") // TODO 待验证 when (type) { 0 -> { val isLeftLock = (res[4].toInt() shr 0) and 0x1 == 1 val isRightLock = (res[4].toInt() shr 4) and 0x1 == 1 LogUtil.i("钥匙底座卡扣状态 : $isLeftLock - $isRightLock") } 1 -> { val tempList = mutableListOf() for (i in 0..7) { tempList.add((res[4].toInt() shr i) and 0x1 == 1) } LogUtil.i("锁具底座卡扣1-8状态 : $tempList") } 2 -> { val lock9Status = (res[4].toInt() shr 0) and 0x1 == 1 val lock10Status = (res[4].toInt() shr 1) and 0x1 == 1 LogUtil.i("锁具底座卡扣9、10状态 : $lock9Status - $lock10Status") } } } } // 1. 把 NetApi.get…Page 包成 suspend 函数 private suspend fun getSlotsPage(): CabinetSlotsRespVo? = suspendCancellableCoroutine { cont -> NetApi.getIsLockCabinetSlotsPage { slots -> cont.resume(slots) } } private suspend fun getLocksPage(): LockPageRespVO? = suspendCancellableCoroutine { cont -> NetApi.getIsLockPage { locks -> cont.resume(locks) } } private suspend fun getKeyPage(): KeyPageRespVO? = suspendCancellableCoroutine { cont -> NetApi.getIsKeyPage { keys -> cont.resume(keys) cont.cancel() } } // 2. 把原本同步的字典查询留在 IO 线程 private suspend fun fetchDict(key: String): List = withContext(Dispatchers.IO) { @Suppress("UNCHECKED_CAST") NetApi.getDictData(key) as List } // 3. 重写 checkEquipCount fun checkEquipCount( needLockCount: Int, isNeedKey: Boolean, callBack: (Pair?, MutableMap>) -> Unit ) { // 你可以改成接收 CoroutineScope 或者直接在全局 Scope 启动 ThreadUtils.runOnMain { sendLoadingEventMsg(MyApplication.instance?.applicationContext!!.getString(R.string.check_key_and_lock)) try { // —— 串行请求1 & 2 —— val slotsPage = getSlotsPage() val locksPage = getLocksPage() // —— 并行加载字典(或按需串行也行) —— val lockStatus = async { fetchDict(DictAndSystemConstants.KEY_PAD_LOCK_STATUS) } val slotStatus = async { fetchDict(DictAndSystemConstants.KEY_SLOT_STATUS) } val slotType = async { fetchDict(DictAndSystemConstants.KEY_SLOT_TYPE) } val keyStatus = async { fetchDict(DictAndSystemConstants.KEY_KEY_STATUS) } // 等待字典加载完成 val lockStatusList = lockStatus.await() val slotStatusList = slotStatus.await() val slotTypeList = slotType.await() val keyStatusList = keyStatus.await() // —— 在 Default 线程做计算密集操作 —— val lockMap = withContext(Dispatchers.Default) { ModBusController.getLocks( needLockCount, slotsPage?.records?.filter { it.slotType == slotTypeList.find { d -> d.dictLabel == "锁" }?.dictValue && it.status == slotStatusList.find { d -> d.dictLabel == "异常" }?.dictValue }?.toMutableList() ?: mutableListOf(), locksPage?.records?.filter { it.exStatus == lockStatusList.find { d -> d.dictLabel == "异常" }?.dictValue } ?.map { it.lockNfc ?: "" }?.toMutableList() ?: mutableListOf() ) } // —— 如果需钥匙,再请求并计算 —— var keyPair: Pair? = null if (isNeedKey) { val keyPage = withContext(Dispatchers.IO) { getKeyPage() } keyPair = withContext(Dispatchers.Default) { getOneKey( slotsPage?.records?.filter { it.slotType == slotTypeList.find { d -> d.dictLabel == "钥匙" }?.dictValue && it.status == slotStatusList.find { d -> d.dictLabel == "异常" }?.dictValue }?.toMutableList() ?: mutableListOf(), keyPage?.records?.filter { it.exStatus == keyStatusList.find { d -> d.dictLabel == "异常" }?.dictValue } ?.map { it.keyNfc ?: "" }?.toMutableList() ?: mutableListOf() ) } } // —— 全部计算完毕,在主线程一次性回调 —— callBack(keyPair, lockMap) } catch (e: Exception) { // 根据需求处理异常,或把异常信息也通过 callback 返回 sendLoadingEventMsg(null, false) e.printStackTrace() ToastUtils.tip("检查设备异常:${e.message}") } } } /****************************************** 蓝牙 ******************************************/ /******************************************蓝牙通用准备******************************************/ /** * 注册连接监听 */ fun registerConnectListener( mac: String, connectNow: Boolean = false, isSend: Boolean = true, callBack: (( Boolean, BleBean? ) -> Unit)? = null ) { BleConnectionManager.registerConnectListener(mac, connectNow, isSend, callBack) } /** * 连接监听反注册 */ fun unregisterConnectListener(mac: String, bleBean: BleBean? = null) { BleConnectionManager.unregisterConnectListener(mac, bleBean) } /******************************************蓝牙通用准备结束******************************************/ fun getBleDeviceByMac(mac: String?): BleBean? { return deviceList.find { it.bleDevice.mac == mac } } fun getBleBeanByRfid(nfc: String?): BleBean? { nfc ?: return null ModBusController.getKeyByRfid(nfc)?.mac?.let { itMac -> return getBleDeviceByMac(itMac) } return null } /** * 下发工作票 */ private fun sendTicketBusiness( isLock: Boolean, mac: String, ticketDetail: TicketDetailRespVO, lockList: MutableList?, activity: AppCompatActivity, isNeedLoading: Boolean = false, ) { if (BleSendDispatcher.isConnected(mac)) { val bleBean = getBleDeviceByMac(mac) if (bleBean == null) { LogUtil.e("sendTicketBusiness fail : $mac, bleBean is null") return } // 单bleBean json赋值 bleBean.retryCount = 0 bleBean.ticketSend = generateTicketSendJson(isLock, ticketDetail, lockList) bleBean.ticketSend?.let { itJson -> sendTicketWithRetry(itJson, bleBean.bleDevice, isNeedLoading) } } else { ThreadUtils.runOnIODelayed(1000) { sendTicketBusiness(isLock, mac, ticketDetail, lockList, activity, isNeedLoading) } } } /** * 带重试的下发工作票,重试次数3,间隔500ms */ private fun sendTicketWithRetry( json: String, bleDevice: BleDevice, isNeedLoading: Boolean = false, maxRetries: Int = 3, delayMillis: Long = 500 ) { var retryCount = 0 fun attemptSend() { sendTicket(json, bleDevice, isNeedLoading) { sendRst -> if (!sendRst && retryCount < maxRetries) { retryCount++ // 等待一段时间后再次尝试 Executor.delayOnMain(delayMillis) { LogUtil.i("Retry attempt, mac : ${bleDevice.mac}, retryCount : $retryCount") attemptSend() } } } } attemptSend() } /** * 读取工作票完成情况 */ private fun getTicketStatusBusiness( mac: String, isNeedLoading: Boolean = false ) { LogUtil.i("检查钥匙连接情况") if (BleReturnDispatcher.isConnected(mac)) { getBleDeviceByMac(mac)?.bleDevice?.let { LogUtil.i("开始读取作业票") BleReturnDispatcher.busy(mac) getTicketStatusWithRetry( it, isNeedLoading ) } } else { //如果连接在待发列表则断开连接,重新使用归还队列连接之后检查 if (BleSendDispatcher.isConnected(mac)) { BleSendDispatcher.scheduleDisconnect(mac) BleReturnDispatcher.submit(mac) { getTicketStatusBusiness(mac, isNeedLoading) } } if (isNeedLoading) sendEventMsg( MsgEvent( MSG_EVENT_LOADING, LoadingMsg(false, null, false) ) ) } } private fun getTicketStatusWithRetry( bleDevice: BleDevice, isNeedLoading: Boolean = false, maxRetries: Int = 3, delayMillis: Long = 500 ) { var retryCount = 0 fun attemptSend() { getTicketStatus(bleDevice, isNeedLoading) { sendRst -> if (!sendRst && retryCount < maxRetries) { retryCount++ // 等待一段时间后再次尝试 Executor.delayOnMain(delayMillis) { LogUtil.i("Retry attempt, mac : ${bleDevice.mac}, retryCount : $retryCount") attemptSend() } } } } attemptSend() } private fun sendTicket( jsonStr: String, bleDevice: BleDevice, isNeedLoading: Boolean = false, processCallback: ((Boolean) -> Unit)? = null ) { if (isNeedLoading) sendEventMsg( MsgEvent( MSG_EVENT_LOADING, LoadingMsg(true, CommonUtils.getStr(R.string.start_to_send_ticket), null) ) ) BleCmdManager.sendWorkTicket( jsonStr, bleDevice = bleDevice, callback = object : CustomBleWriteCallback() { override fun onWriteSuccess( bleDevice: BleDevice, characteristic: BluetoothGattCharacteristic, current: Int, total: Int, justWrite: ByteArray, data: ByteArray ) { LogUtil.i("sendTicket success") if (isNeedLoading) sendEventMsg( MsgEvent( MSG_EVENT_LOADING, LoadingMsg(true, CommonUtils.getStr(R.string.sending_ticket), null) ) ) } override fun onWriteFailure( bleDevice: BleDevice, characteristic: BluetoothGattCharacteristic?, exception: BleException, current: Int, total: Int, justWrite: ByteArray?, data: ByteArray?, isTotalFail: Boolean ) { LogUtil.e("sendTicket fail : ${bleDevice.mac}") if (isNeedLoading) sendEventMsg( MsgEvent( MSG_EVENT_LOADING, LoadingMsg(false, CommonUtils.getStr(R.string.send_ticket_fail), null) ) ) processCallback?.invoke(false) } }) } /** * 生成下发工作票Json * * @param vo 工作票详情 */ private fun generateTicketSendJson( isLock: Boolean, vo: TicketDetailRespVO, lockList: MutableList? ): String { LogUtil.i("generateTicketSendJson : $lockList") val bo = WorkTicketSendBO( cardNo = SPUtils.getLoginUser(MyApplication.instance!!.applicationContext)?.userCardList?.get( 0 ), ) CommonUtils.getDiffHours(vo.ticketEndTime)?.let { bo.effectiveTime = it } // 有配置则用配置,没用则填充默认密码 bo.password = SPUtils.getLoginUser(MyApplication.instance!!.applicationContext)?.keyCode ?: "123456" val dataBO = WorkTicketSendBO.DataBO( taskCode = vo.ticketId.toString(), codeId = 1 ) val taskList = ArrayList() vo.ticketPointsVOList?.filter { it.pointStatus == if (isLock) "0" else "1" } ?.let { itList -> itList.forEach { pointVO -> if (vo.noUnlockTicketPointsVOSet?.any { it.pointId == pointVO.pointId } == true && !isLock) { return@forEach } val task = WorkTicketSendBO.DataBO.DataListBO( dataId = pointVO.pointId?.toInt(), equipRfidNo = pointVO.pointNfc, equipName = pointVO.pointName, target = if (isLock) 0 else 1 ) if (!isLock) { task.infoRfidNo = pointVO.lockNfc } pointVO.prePointId?.let { task.prevId = it.toInt() } // TODO partCode待补充 taskList.add(task) } } dataBO.dataList = taskList bo.data = mutableListOf(dataBO) if (isLock) { // TODO 挂锁数组 if (!lockList.isNullOrEmpty()) { bo.lockList = mutableListOf() lockList.forEachIndexed { index, s -> if (s.isNullOrEmpty()) { LogUtil.w("Lock nfc is null or empty") return@forEachIndexed } bo.lockList?.add(LockListBO(index + 1, s)) } } } // TODO partList 待补充 val jsonStr = Gson().toJson(bo) LogUtil.i("json : $jsonStr") return jsonStr } /** * 生成下空发工作票Json * * @param vo 工作票详情 */ fun generateEmptyTicketSendJson(): String { // 构造一个所有字段都为空/默认值的 WorkTicketSendBO val bo = WorkTicketSendBO( cardNo = "", // 空卡号 effectiveTime = 0, // 默认有效时长 password = "" // 空密码 ).apply { // data 列表留空 data = mutableListOf() // lockList 留空(如果字段非空,再设置为 emptyList()) lockList = mutableListOf() } // 转成 JSON 并返回 val jsonStr = Gson().toJson(bo) LogUtil.i("generateEmptyTicketJson: $jsonStr") return jsonStr } fun handleRsp( bleBean: BleBean, byteArray: ByteArray, isNeedLoading: Boolean = false, prepareDoneCallBack: ((Boolean, BleBean?) -> Unit)? ) { // TODO Token校验 // val len = byteArray[2].toInt() // val token = byteArray.copyOfRange(len + 7, len + 11) // if (token.contentEquals(bleBean.token)) { // LogUtil.i("Token is right") // } else { // LogUtil.e("Token is wrong") // } when { // 获取令牌 byteArray.startsWith(BleConst.RSP_GET_TOKEN) -> BleCmdManager.handleToken( bleBean.bleDevice, byteArray ) { isSuccess -> if (isSuccess) { //尝试使用命令作为心跳 ,获取token完成之后就要建立心跳了 BleCmdManager.getPower( bleBean.bleDevice.mac, null ) } } // 工作模式切换 byteArray.startsWith(BleConst.RSP_SWITCH_MODE) -> { handleSwitchModeResult(byteArray, isNeedLoading) { res, job -> sendEventMsg( MsgEvent( MSG_EVENT_SWITCH_MODE, SwitchModeMsg(job.toInt(), res.toInt(), bleBean) ) ) } } // 工作票下发 byteArray.startsWith(BleConst.RSP_SEND_WORK_TICKET) -> handleWorkTicketResult( bleBean, byteArray, isNeedLoading ) // 获取设备当前状态 byteArray.startsWith(BleConst.RSP_CURRENT_STATUS) -> BleCmdManager.handleCurrentStatus( byteArray ) { sendEventMsg(MsgEvent(MSG_EVENT_CURRENT_MODE, CurrentModeMsg(bleBean, it))) } // 获取设备工作票完成情况 byteArray.startsWith(BleConst.RSP_WORK_TICKET_RESULT) && byteArray[3] == 0x02.toByte() -> handleTicketStatus( bleBean.bleDevice, byteArray, isNeedLoading ) byteArray.startsWith(BleConst.RSP_POWER_STATUS) -> { val power = byteArray[4].toInt() ModBusController.updateKeyPower(power, bleBean.bleDevice.mac) LogUtil.i("电量(${bleBean.bleDevice.mac}):${power}") prepareDoneCallBack?.invoke(true, bleBean) if (power < 50) {//如果电量小于50就打开仓位充电 ModBusController.controlKeyCharge(true, bleBean.bleDevice.mac) { LogUtil.i("钥匙: ${bleBean.bleDevice.mac} 开始充电") } } } } } /** * 工作模式切换结果 * job : 0x01:工作模式 0x02:待机模式 * res : 0x01:成功 0x02:失败 */ private fun handleSwitchModeResult( byteArray: ByteArray, isNeedLoading: Boolean = false, callBack: ((Byte, Byte) -> Unit)? = null ) { BleCmdManager.handleSwitchModeResult(byteArray) { job, res -> if (res == 0x01.toByte() && job == 0x01.toByte()) { LogUtil.i("切换工作模式成功") if (isNeedLoading) sendEventMsg( MsgEvent( MSG_EVENT_LOADING, LoadingMsg(false, "切换工作模式成功", null) ) ) } else if (res == 0x01.toByte() && job == 0x02.toByte()) { LogUtil.i("切换待机模式成功") if (isNeedLoading) sendEventMsg( MsgEvent( MSG_EVENT_LOADING, LoadingMsg(false, "切换待机模式成功", null) ) ) } else { LogUtil.e("切换模式失败 : ${job.toInt()} - ${res.toInt()}") if (isNeedLoading) sendEventMsg( MsgEvent( MSG_EVENT_LOADING, LoadingMsg(false, null, null) ) ) } callBack?.invoke(res, job) } } /** * 工作票下发结果 * res:0x00:成功 0x01:失败 0x02:传输超时 0x0D:当前IDX超出范围 0x0E:当前数据CRC校验失败 0x14:JSON结构错误 0x63:未知错误 */ private fun handleWorkTicketResult( bleBean: BleBean, byteArray: ByteArray, isNeedLoading: Boolean = false ) { BleCmdManager.handleWorkTicketResult(bleBean, byteArray) { isSuccess, rst -> if (isNeedLoading) sendEventMsg( MsgEvent( MSG_EVENT_LOADING, LoadingMsg(false, null, null) ) ) if (isSuccess) { // 下发完毕,切换工作模式 LogUtil.i("工作票下发完毕") if (isNeedLoading) sendEventMsg( MsgEvent( MSG_EVENT_LOADING, LoadingMsg(true, "切换钥匙为工作模式", null) ) ) Executor.delayOnIO(800) { //切换到工作模式 switchWorkMode(bleBean.bleDevice, isNeedLoading) } } else { sendLoadingEventMsg(null, false) if (bleBean.retryCount < 3) { Executor.delayOnMain(500) { bleBean.retryCount++ sendLoadingEventMsg(MyApplication.instance!!.getString(R.string.start_to_send_ticket)) sendTicketWithRetry(bleBean.ticketSend!!, bleBean.bleDevice, isNeedLoading) } } else { ToastUtils.tip(R.string.send_ticket_fail) LogUtil.e("Send ticket fail") ModBusController.getKeyByMac(bleBean.bleDevice.mac)?.let { itKey -> mDeviceTakeList.removeIf { it.deviceType == DEVICE_TYPE_KEY && it.nfc == itKey.rfid } } } } } } /** * 获取当前钥匙的状态 */ fun getCurrentStatus( from: Int, bleDevice: BleDevice, retryCount: Int = 0, timeoutCallBack: ((Boolean) -> Unit)? = null ) { LogUtil.i("getCurrentStatus - ${bleDevice.mac} - from : $from") var isTimeout = true // 加1秒防止早于onWriteFailure开始处理导致多次处理 Executor.delayOnMain((BleUtil.OPERATE_TIMEOUT + 1).toLong()) { if (isTimeout) { LogUtil.e("getCurrentStatus timeout : mac = ${bleDevice.mac}, retryCount = $retryCount") if (retryCount > 0) { if (from != 4) { val canConnect = BleSendDispatcher.canConnect() LogUtil.i("发送队列是否可以连接:${canConnect}") if (BleSendDispatcher.isConnected(bleDevice.mac)) { BleSendDispatcher.scheduleDisconnect(bleDevice.mac) } BleSendDispatcher.submit(bleDevice.mac) { getCurrentStatus( from, bleDevice, retryCount - 1, timeoutCallBack ) } } else { BleReturnDispatcher.clearNoBusyConnectedDevice() BleReturnDispatcher.scheduleDisconnect(bleDevice.mac) BleReturnDispatcher.submit(bleDevice.mac) { getCurrentStatus( from, bleDevice, retryCount - 1, timeoutCallBack ) } } } else { BleSendDispatcher.scheduleDisconnect(bleDevice.mac) BleReturnDispatcher.scheduleDisconnect(bleDevice.mac) ModBusController.getKeyByMac(bleDevice.mac)?.rfid?.let { addExceptionKey(it) timeoutCallBack?.invoke(true) } } } } BleCmdManager.getCurrentStatus(bleDevice, object : CustomBleWriteCallback() { override fun onWriteSuccess( bleDevice: BleDevice, characteristic: BluetoothGattCharacteristic, current: Int, total: Int, justWrite: ByteArray, data: ByteArray ) { LogUtil.i("getCurrentStatus success : ${bleDevice.mac}") isTimeout = false timeoutCallBack?.invoke(false) } override fun onWriteFailure( bleDevice: BleDevice, characteristic: BluetoothGattCharacteristic?, exception: BleException, current: Int, total: Int, justWrite: ByteArray?, data: ByteArray?, isTotalFail: Boolean ) { LogUtil.i("getCurrentStatus fail : ${bleDevice.mac}") isTimeout = false Executor.delayOnMain(1000) { getCurrentStatus( from, bleDevice, retryCount - 1, timeoutCallBack = timeoutCallBack ) } } }) } /** * 获取电池电量 */ fun getBatteryPower(bleDevice: BleDevice) { LogUtil.i("获取电池电量:${bleDevice.mac}") BleCmdManager.getPower(bleDevice.mac, object : CustomBleWriteCallback() { override fun onWriteSuccess( bleDevice: BleDevice, characteristic: BluetoothGattCharacteristic, current: Int, total: Int, justWrite: ByteArray, data: ByteArray ) { LogUtil.i("发送获取电池电量命令成功:${bleDevice.mac}") } override fun onWriteFailure( bleDevice: BleDevice, characteristic: BluetoothGattCharacteristic?, exception: BleException, current: Int, total: Int, justWrite: ByteArray?, data: ByteArray?, isTotalFail: Boolean ) { ThreadUtils.runOnIODelayed(500) { LogUtil.i("发送获取电池电量命令失败:${bleDevice.mac}") getBatteryPower(bleDevice) } } }) } /** * 切换工作模式 */ private fun switchWorkMode(bleDevice: BleDevice, isNeedLoading: Boolean = false) { LogUtil.i("switchWorkMode - ${bleDevice.mac}") BleCmdManager.switchMode(STATUS_WORK, bleDevice, object : CustomBleWriteCallback() { override fun onWriteSuccess( bleDevice: BleDevice, characteristic: BluetoothGattCharacteristic, current: Int, total: Int, justWrite: ByteArray, data: ByteArray ) { LogUtil.i("switch mode work success : ${bleDevice.mac}") } override fun onWriteFailure( bleDevice: BleDevice, characteristic: BluetoothGattCharacteristic?, exception: BleException, current: Int, total: Int, justWrite: ByteArray?, data: ByteArray?, isTotalFail: Boolean ) { LogUtil.e("switch mode work fail : ${exception?.code} - ${exception?.description}") Executor.delayOnMain(500) { switchWorkMode(bleDevice, isNeedLoading) } } }) } /** * 处理工作票完成情况 */ private fun handleTicketStatus( bleDevice: BleDevice, byteArray: ByteArray, isNeedLoading: Boolean = false ) { BleCmdManager.handleTicketStatus(bleDevice, byteArray) { ticketJson -> if (ticketJson.isNullOrEmpty()) { return@handleTicketStatus } if (isNeedLoading) sendEventMsg( MsgEvent( MSG_EVENT_LOADING, LoadingMsg(false, "工作票完成状态读取完成", null) ) ) LogUtil.i("Get ticket status complete : ${bleDevice.mac}") BleReturnDispatcher.removeBusy(bleDevice.mac) // TD:Ticket Done if (isNeedLoading) sendEventMsg( MsgEvent( MSG_EVENT_LOADING, LoadingMsg(false, "TD$ticketJson}", true) ) ) val workTicketGetBO = try { Gson().fromJson(ticketJson, WorkTicketGetBO::class.java) } catch (e: Exception) { null } if (workTicketGetBO == null) { ToastUtils.tip(R.string.ticket_data_error) return@handleTicketStatus } // 判断workTicketGetBO里是否有未完成的 ThreadUtils.runOnIO { val finishedStatus = workTicketGetBO.hasFinished() LogUtil.i("作业票结束情况:${finishedStatus}") if (finishedStatus.first) { Executor.delayOnMain(500) { handleKeyReturn(bleDevice, workTicketGetBO, finishedStatus.second) //检查钥匙是否代取,如果是的话给钥匙 val keyBean = ModBusController.getKeyByMac(bleDevice.mac) mDeviceTakeList.find { it.deviceType == DEVICE_TYPE_KEY && it.nfc == keyBean?.rfid } ?.let { itKey -> LogUtil.i("检查到存在待取钥匙:${itKey}") sendLoadingEventMsg( MyApplication.instance?.applicationContext!!.getString( R.string.ble_connecting ) ) handleGiveKey(itKey) } } } else { // 当前策略:作业票未完成禁止归还钥匙 withContext(Dispatchers.Main) { showKeyReturnUploadDataForceDialog({ handleKeyReturn(bleDevice, workTicketGetBO, finishedStatus.second) //检查钥匙是否代取,如果是的话给钥匙 val keyBean = ModBusController.getKeyByMac(bleDevice.mac) mDeviceTakeList.find { it.deviceType == DEVICE_TYPE_KEY && it.nfc == keyBean?.rfid } ?.let { itKey -> sendLoadingEventMsg( MyApplication.instance?.applicationContext!!.getString( R.string.ble_connecting ) ) handleGiveKey(itKey) } }) { sendLoadingEventMsg(null, false) ToastUtils.tip(R.string.continue_the_ticket) BleReturnDispatcher.scheduleDisconnect(bleDevice.mac) // 打开卡扣,防止初始化的时候选择不处理钥匙导致无法使用 val dock = ModBusController.getDockByKeyMac(bleDevice.mac) val keyBean = dock?.getKeyList()?.find { it.mac == bleDevice.mac } keyBean?.let { ModBusController.controlKeyBuckle(true, keyBean.isLeft, dock.addr) } } } } } } } /** * ticketFinished主要是后端的作业票是否已经结束,结束了,就直接修改状态就好了 */ private fun handleKeyReturn( bleDevice: BleDevice, workTicketGetBO: WorkTicketGetBO?, ticketFinished: Boolean ) { val dock = ModBusController.getDockByKeyMac(bleDevice.mac) val keyBean = dock?.getKeyList()?.find { it.mac == bleDevice.mac } keyBean?.let { ModBusController.controlKeyBuckle(false, keyBean.isLeft, dock.addr) } if (ticketFinished) { mDeviceTakeList.removeIf { it.nfc == keyBean?.rfid } switchReadyMode(bleDevice) } else { // 上报隔离点状态 val keyNfc = ModBusController.getKeyByMac(bleDevice.mac)?.rfid ?: "key rfid lost" workTicketGetBO?.data?.forEach { data -> val updateList = mutableListOf() data.dataList?.filter { it.closed == 1 && it.status == it.target } ?.forEach { dataListDTO -> val updateVO = LockPointUpdateReqVO( data.taskCode?.toLong(), dataListDTO.infoRfidNo, dataListDTO.equipRfidNo, keyNfc, dataListDTO.target, dataListDTO.status ) updateList.add(updateVO) } sendLoadingEventMsg(null, false) if (CAN_RETURN) { //如果需要上报的点位数据为空则直接上报钥匙归还 if (updateList.isEmpty()) { mDeviceTakeList.removeIf { it.nfc == keyBean?.rfid } // 上报钥匙归还 keyReturn(data.taskCode?.toLong()!!, keyNfc) switchReadyMode(bleDevice) return@forEach } // 上报点位钥匙绑定 NetApi.updateLockPointBatch(updateList) { isSuccess, msg, code -> LogUtil.i("还锁操作:${isSuccess},${msg},${code}") if (isSuccess) { // 上报钥匙归还 keyReturn(data.taskCode?.toLong()!!, keyNfc) data.taskCode?.toLong()?.let { sendEventMsg( MsgEvent( MSG_EVENT_UPDATE_TICKET_PROGRESS, UpdateTicketProgressMsg(it) ) ) } // 确认归还,切换为待机模式 switchReadyMode(bleDevice) } else { ThreadUtils.runOnMain { // 当前策略:作业票未完成禁止归还钥匙 showKeyReturnDialog { sendLoadingEventMsg(null, false) ToastUtils.tip(R.string.continue_the_ticket) BleReturnDispatcher.scheduleDisconnect(bleDevice.mac) // 打开卡扣,防止初始化的时候选择不处理钥匙导致无法使用 if (workTicketGetBO.data?.all { it.dataList?.all { it.closed == 1 } == true } == true) { workTicketGetBO.data?.firstOrNull()?.taskCode?.toLong() ?.let { checkStepAndTicketDetailThenSendTicket( it, bleDevice.mac ) } } else { val dock = ModBusController.getDockByKeyMac(bleDevice.mac) val keyBean = dock?.getKeyList()?.find { it.mac == bleDevice.mac } keyBean?.let { ModBusController.controlKeyBuckle( true, keyBean.isLeft, dock.addr ) } } } } SPUtils.clearUpdateKeyReturn(MyApplication.instance!!) SPUtils.clearUpdateLockPoint(MyApplication.instance!!) } } } else { SPUtils.saveUpdateLockPoint(MyApplication.instance!!, updateList) SPUtils.saveUpdateKeyReturn( MyApplication.instance!!, UpdateKeyReturnBO(data.taskCode?.toLong()!!, keyNfc!!) ) // 保存待发数据,切换为待机模式 switchReadyMode(bleDevice) } } } } /** * 钥匙归还 */ private fun keyReturn(ticketId: Long, keyNfc: String) { NetApi.updateKeyReturn( ticketId, keyNfc, MyApplication.instance!!.serialNo() ) { isSuccess, msg, code -> if (!isSuccess) { if (msg == MyApplication.instance?.applicationContext!!.getString( R.string.ticket_lost ) ) { sendEventMsg( MsgEvent( MsgEventConstants.MSG_EVENT_TICKET_FINISHED, null ) ) } SPUtils.clearUpdateKeyReturn(MyApplication.instance!!) ToastUtils.tip(R.string.key_return_success) } else { ToastUtils.tip(R.string.key_return_success) } } } /** * 处理虚拟钥匙取出,如果作业的全部点位已经上锁更新钥匙的状态使用 */ fun handleVirtualKeyGive(taskCode: Long, keyNfc: String, done: () -> Unit) { // 上报钥匙归还 NetApi.updateKeyTake(taskCode, keyNfc, MyApplication.instance!!.serialNo()) { isSuccess -> if (isSuccess) { done() } } } /** * 处理虚拟钥匙归还,如果作业的全部点位已经上锁更新钥匙的状态使用 */ fun handleVirtualKeyReturn(taskCode: Long, keyNfc: String, done: () -> Unit) { // 上报钥匙归还 NetApi.updateKeyReturn( taskCode, keyNfc, MyApplication.instance!!.serialNo() ) { isSuccess, msg, code -> if (!isSuccess) { if (msg == MyApplication.instance?.applicationContext!!.getString( R.string.ticket_lost ) ) { sendEventMsg( MsgEvent( MsgEventConstants.MSG_EVENT_TICKET_FINISHED, null ) ) } SPUtils.clearUpdateKeyReturn(MyApplication.instance!!) } else { done() sendEventMsg( MsgEvent( MSG_EVENT_UPDATE_TICKET_PROGRESS, UpdateTicketProgressMsg(taskCode) ) ) } } } fun switchReadyMode(bleDevice: BleDevice) { BleCmdManager.switchMode(STATUS_READY, bleDevice, object : CustomBleWriteCallback() { override fun onWriteSuccess( bleDevice: BleDevice, characteristic: BluetoothGattCharacteristic, current: Int, total: Int, justWrite: ByteArray, data: ByteArray ) { LogUtil.i("switch mode ready success : ${bleDevice.mac}") } override fun onWriteFailure( bleDevice: BleDevice, characteristic: BluetoothGattCharacteristic?, exception: BleException, current: Int, total: Int, justWrite: ByteArray?, data: ByteArray?, isTotalFail: Boolean ) { LogUtil.e("switch mode ready fail : ${bleDevice.mac}") Executor.delayOnMain(300) { switchReadyMode(bleDevice) } } }) } /** * 获取工作票完成情况 */ private fun getTicketStatus( bleDevice: BleDevice, isNeedLoading: Boolean = false, processCallback: ((Boolean) -> Unit)? = null ) { if (isNeedLoading) sendEventMsg( MsgEvent( MSG_EVENT_LOADING, LoadingMsg(true, "开始获取工作票", null) ) ) BleCmdManager.getTicketStatus(bleDevice, object : CustomBleWriteCallback() { override fun onWriteSuccess( bleDevice: BleDevice, characteristic: BluetoothGattCharacteristic, current: Int, total: Int, justWrite: ByteArray, data: ByteArray ) { if (isNeedLoading) sendEventMsg( MsgEvent( MSG_EVENT_LOADING, LoadingMsg(false, "工作票获取成功", null) ) ) LogUtil.i("getTicketStatus success") } override fun onWriteFailure( bleDevice: BleDevice, characteristic: BluetoothGattCharacteristic?, exception: BleException, current: Int, total: Int, justWrite: ByteArray?, data: ByteArray?, isTotalFail: Boolean ) { if (isNeedLoading) sendEventMsg( MsgEvent( MSG_EVENT_LOADING, LoadingMsg(false, "工作票获取失败", false) ) ) processCallback?.invoke(false) LogUtil.e("getTicketStatus fail") } }) } /** * 添加待更新取出状态的设备 */ fun addDeviceTake(deviceType: Int, ticketId: Long, nfc: String?) { LogUtil.i("addDeviceTake : $deviceType - $ticketId - $nfc") mDeviceTakeList.removeIf { it.deviceType == deviceType && it.nfc == nfc } mDeviceTakeList.add(DeviceTakeUpdateBO(deviceType, ticketId, nfc!!)) } fun removeDeviceTake(deviceType: Int, nfc: String?) { LogUtil.i("removeDeviceTake : $deviceType - $nfc") mDeviceTakeList.removeIf { it.deviceType == deviceType && it.nfc == nfc } } private fun handleDeviceTake(deviceTakeUpdateBO: DeviceTakeUpdateMsg, rfid: String? = null) { LogUtil.i("$deviceTakeUpdateBO") when (deviceTakeUpdateBO.deviceType) { // 钥匙 0 -> { mDeviceTakeList.find { it.deviceType == DEVICE_TYPE_KEY && it.nfc == deviceTakeUpdateBO.nfc } ?.let { info -> if (mDeviceTakeList.any { it.deviceType == DeviceConst.DEVICE_TYPE_LOCK && it.ticketId == info.ticketId }) { LogUtil.i("存在未取出的挂锁,不继续操作") return } sendLoadingEventMsg(null, false) NetApi.updateKeyTake( info.ticketId, info.nfc, MyApplication.instance?.serialNo()!! ) { isSuccess -> if (isSuccess) { mDeviceTakeList.removeIf { it.deviceType == DEVICE_TYPE_KEY && it.nfc == info.nfc && it.ticketId == info.ticketId } sendEventMsg( MsgEvent( MSG_EVENT_UPDATE_TICKET_PROGRESS, UpdateTicketProgressMsg(info.ticketId) ) ) //钥匙取出之后重新再连一把钥匙待机 ModBusController.getKeyByRfid( info.nfc )?.mac?.let { unregisterConnectListener(it) } //待机数不够就再连一把,但不能是原来那把 ModBusController.getKeyByRfid( info.nfc )?.mac?.let { ThreadUtils.runOnIO { checkMyTodoForHandleKey(it) } } } } } ?: sendLoadingEventMsg(null, false) } // 挂锁 1 -> { mDeviceTakeList.find { it.deviceType == DEVICE_TYPE_LOCK && it.nfc == deviceTakeUpdateBO.nfc } ?.let { info -> NetApi.updateLockTake( mutableListOf( LockTakeUpdateReqVO( info.ticketId, info.nfc, MyApplication.instance?.serialNo()!! ) ) ) { isSuccess -> Executor.runOnMain { if (isSuccess == false) { LogUtil.e("Lock take report fail") ToastUtils.tip(R.string.lock_take_report_fail) SPUtils.saveTicketTakeLockException(info.ticketId) mDeviceTakeList.removeIf { it.deviceType == DEVICE_TYPE_LOCK && it.nfc == info.nfc } mDeviceTakeList.removeIf { it.deviceType == DEVICE_TYPE_KEY && it.ticketId == info.ticketId } sendLoadingEventMsg(null, false) return@runOnMain } // 检查是不是要发钥匙了 mDeviceTakeList.removeIf { it.deviceType == DEVICE_TYPE_LOCK && it.nfc == info.nfc } // 检查当前工作票是否取完挂锁 if (mDeviceTakeList.any { it.deviceType == DEVICE_TYPE_LOCK && it.ticketId == info.ticketId }) { LogUtil.i("Waiting all locks to take out") sendLoadingEventMsg( MyApplication.instance?.applicationContext?.getString( R.string.take_out_lock_tip, mDeviceTakeList.count { it.deviceType == DEVICE_TYPE_LOCK && it.ticketId == info.ticketId }) ) ToastUtils.tip(R.string.take_out_rest_locks) return@runOnMain } else { LogUtil.i("All locks are taken") sendLoadingEventMsg(null, false) } if (SPUtils.getTicketTakeLockException(info.ticketId)) { ToastUtils.tip(R.string.current_ticket_report_lock_take_exception_tip) return@runOnMain } // 检查有无当前工作票的钥匙 mDeviceTakeList.find { it.deviceType == DEVICE_TYPE_KEY && it.ticketId == info.ticketId } ?.let { itKey -> sendLoadingEventMsg( MyApplication.instance?.applicationContext!!.getString( R.string.ble_connecting ) ) handleGiveKey(itKey) } } } } } } } /** * 分配钥匙 */ private fun handleGiveKey(deviceTakeUpdateBO: DeviceTakeUpdateBO) { getBleDeviceByMac(ModBusController.getKeyByRfid(deviceTakeUpdateBO.nfc)?.mac)?.let { getCurrentStatus( 2, getBleDeviceByMac(ModBusController.getKeyByRfid(deviceTakeUpdateBO.nfc)?.mac)!!.bleDevice ) { if (!it) { return@getCurrentStatus } LogUtil.w("handleGiveKey timeout") removeDeviceTake(DEVICE_TYPE_KEY, deviceTakeUpdateBO.nfc) checkEquipCount(0, true) { keyPair, lockMap -> if (keyPair == null) { ThreadUtils.runOnMain { val tipDialog = TipDialog(SIKCore.getApplication()) tipDialog.setTip( SIKCore.getApplication().getString(R.string.key_take_error_tip) ) tipDialog.setConfirmListener { tipDialog.dismiss() sendEventMsg( MsgEvent( MSG_EVENT_DEVICE_EXCEPTION, DeviceExceptionMsg(DEVICE_TYPE_KEY, deviceTakeUpdateBO.nfc) ) ) } tipDialog.show() } } else { addDeviceTake( DEVICE_TYPE_KEY, deviceTakeUpdateBO.ticketId, keyPair.second?.rfid!! ) handleGiveKey( DeviceTakeUpdateBO( DEVICE_TYPE_KEY, deviceTakeUpdateBO.ticketId, keyPair.second?.rfid!! ) ) } } } } ?: run { ThreadUtils.runOnMain { ActivityTracker.getCurrentActivity()?.let { val tipDialog = TipDialog(it) tipDialog.setTip( SIKCore.getApplication().getString(R.string.key_take_error_tip) ) tipDialog.setConfirmListener { tipDialog.dismiss() sendEventMsg( MsgEvent( MSG_EVENT_DEVICE_EXCEPTION, DeviceExceptionMsg(DEVICE_TYPE_KEY, deviceTakeUpdateBO.nfc) ) ) } tipDialog.show() } } } } /** * 根据当前模式进行处理 */ private fun handleCurrentMode(currentModeMsg: CurrentModeMsg) { when (currentModeMsg.mode) { // 工作模式 0x01.toByte() -> { // 读工作票 getTicketStatusBusiness(currentModeMsg.bleBean.bleDevice.mac) } // 待机模式 0x02.toByte() -> { // 根据情况看是否需要下发工作票 ModBusController.getKeyByMac(currentModeMsg.bleBean.bleDevice.mac)?.let { key -> // 判断是否有待取的钥匙 val updateBo = mDeviceTakeList.find { it.deviceType == DEVICE_TYPE_KEY && key.rfid == it.nfc } if (mDeviceTakeList.any { it.deviceType == DEVICE_TYPE_LOCK && it.ticketId == updateBo?.ticketId }) { //todo 如果有钥匙待取但是对应的作业票的锁还有的,就不发 return } updateBo?.let { itBO -> if (BleReturnDispatcher.isConnected(currentModeMsg.bleBean.bleDevice.mac)) { LogUtil.i("当前钥匙在归还队列,断开连接") BleReturnDispatcher.scheduleDisconnect(currentModeMsg.bleBean.bleDevice.mac) } if (BleSendDispatcher.isConnected(currentModeMsg.bleBean.bleDevice.mac)) { LogUtil.i("当前钥匙已在发送队列连接") checkAndSendTicket(currentModeMsg, itBO) } else { LogUtil.i("当前钥匙未加入发送队列,连接后发送") BleSendDispatcher.submit(currentModeMsg.bleBean.bleDevice.mac) { checkAndSendTicket(currentModeMsg, itBO) } } } ?: let { ModBusController.updateKeyReadyStatus( currentModeMsg.bleBean.bleDevice.mac, true, 4 ) sendLoadingEventMsg(null, false) BleReturnDispatcher.scheduleDisconnect(currentModeMsg.bleBean.bleDevice.mac) //连上之后没有工作票要下发就断开 看是否还有设备等待连接,没有就不断开,有就让路,一般是初始化的时候 ThreadUtils.runOnIO { checkMyTodoForHandleKey(currentModeMsg.bleBean.bleDevice.mac) } } } ?: let { ModBusController.updateKeyReadyStatus( currentModeMsg.bleBean.bleDevice.mac, true, 4 ) sendLoadingEventMsg(null, false) BleReturnDispatcher.scheduleDisconnect(currentModeMsg.bleBean.bleDevice.mac) //连上之后没有工作票要下发就断开 看是否还有设备等待连接,没有就不断开,有就让路,一般是初始化的时候 ThreadUtils.runOnIO { checkMyTodoForHandleKey(currentModeMsg.bleBean.bleDevice.mac) } } } // 故障模式 0x03.toByte() -> { // TODO 上报? ToastUtils.tip( "${currentModeMsg.bleBean.bleDevice.mac} : " + "${CommonUtils.getStr(R.string.key_is_in_failure_mode)}" ) } } } /** * 检查并下发作业票 */ private fun checkAndSendTicket(currentModeMsg: CurrentModeMsg, itBO: DeviceTakeUpdateBO) { NetApi.getStepDetail(itBO.ticketId) { var step = 0 it?.filter { it.stepStatus == "1" } ?.maxByOrNull { it.stepIndex!! }?.stepIndex?.let { step = it } NetApi.getTicketDetail(itBO.ticketId) { ticketDetail, _ -> if (ticketDetail == null) { return@getTicketDetail } val role = ticketDetail?.ticketUserVOList?.find { it.userId == SPUtils.getLoginUser(MyApplication.instance?.applicationContext!!)?.userId && it.userType == USER_TYPE_LOCKER } if (role == null) { ToastUtils.tip(R.string.you_are_not_locker_tip) return@getTicketDetail } if (step == 4) { // 上锁工作票 sendTicketBusiness( true, currentModeMsg.bleBean.bleDevice.mac, ticketDetail, ticketDetail.ticketLockVOList?.filter { ticketDetail.ticketPointsVOList?.mapNotNull { it.lockId } ?.contains(it.lockId) != true }?.map { it.lockNfc }?.toMutableList(), ActivityUtils.currentActivity() as BaseActivity<*>, true ) } else if (step == 7) { // 解锁工作票 sendTicketBusiness( false, currentModeMsg.bleBean.bleDevice.mac, ticketDetail, null, ActivityUtils.currentActivity() as BaseActivity<*>, true ) } } } } /** * 检查步骤和作业票详情并且下发作业票 */ private fun checkStepAndTicketDetailThenSendTicket(ticketId: Long, mac: String) { NetApi.getStepDetail(ticketId) { var step = 0 it?.filter { it.stepStatus == "1" }?.maxByOrNull { it.stepIndex!! }?.stepIndex?.let { step = it } NetApi.getTicketDetail(ticketId) { ticketDetail, _ -> if (ticketDetail == null) { return@getTicketDetail } val role = ticketDetail?.ticketUserVOList?.find { it.userId == SPUtils.getLoginUser(MyApplication.instance?.applicationContext!!)?.userId && it.userType == USER_TYPE_LOCKER } if (role == null) { ToastUtils.tip(R.string.you_are_not_locker_tip) return@getTicketDetail } if (step == 4) { // 上锁工作票 sendTicketBusiness( true, mac, ticketDetail, ticketDetail.ticketLockVOList?.filter { ticketDetail.ticketPointsVOList?.mapNotNull { it.lockId } ?.contains(it.lockId) != true }?.map { it.lockNfc }?.toMutableList(), ActivityUtils.currentActivity() as BaseActivity<*>, true ) } else if (step == 7) { // 解锁工作票 sendTicketBusiness( false, mac, ticketDetail, null, ActivityUtils.currentActivity() as BaseActivity<*>, true ) } } } } fun submitKeyData(context: Context) { if (!CAN_RETURN) return val updateList = SPUtils.getUpdateLockPoint(context) if (updateList.isNotEmpty()) { NetApi.updateLockPointBatch(updateList) { isSuccess, msg, code -> LogUtil.i("submitKeyData还锁操作:${isSuccess},${msg},${code}") if (isSuccess || code == 500) { SPUtils.clearUpdateLockPoint(context) SPUtils.clearUpdateKeyReturn(context) } } } val returnList = SPUtils.getUpdateKeyReturn(context).filter { it.keyNfc.isNotEmpty() }.toMutableList() if (returnList.isEmpty()) { return } val itemsToRemove = returnList.toList() var count = 0 itemsToRemove.forEach { itData -> NetApi.updateKeyReturn( itData.ticketId, itData.keyNfc, context.serialNo() ) { isSuccess, msg, code -> count++ if (!isSuccess) { if (msg == MyApplication.instance?.applicationContext!!.getString( R.string.ticket_lost ) ) { sendEventMsg( MsgEvent( MsgEventConstants.MSG_EVENT_TICKET_FINISHED, null ) ) } SPUtils.clearUpdateKeyReturn(MyApplication.instance!!) } } } } fun sendLoadingEventMsg(str: String?, isShow: Boolean = true) { sendEventMsg(MsgEvent(MSG_EVENT_LOADING, LoadingMsg(isShow, str, false))) } fun logout(context: Context) { reConnectKey() NetApi.logout() // 关所有有设备的卡扣 dockList.filter { it.type == DOCK_TYPE_LOCK || it.type == DOCK_TYPE_PORTABLE } .forEach { dockBean -> val hasLockIdxList = dockBean.getLockList().filter { it.isExist }.map { it.idx } as MutableList val noLockIdxList = dockBean.getLockList().filter { !it.isExist }.map { it.idx } as MutableList ModBusController.controlLockBuckle(false, dockBean.addr, hasLockIdxList) ModBusController.controlLockBuckle(true, dockBean.addr, noLockIdxList) } dockList.filter { it.type == DOCK_TYPE_KEY || it.type == DOCK_TYPE_PORTABLE } .forEach { dockBean -> dockBean.getKeyList().forEach { key -> if (key.isExist) { NetApi.getKeyInfo(key.rfid.toString()) { if (it != null && !it.macAddress.isNullOrEmpty()) { ModBusController.updateKeyMac( dockBean.addr, key.isLeft, it.macAddress ) ModBusController.controlKeyBuckle( false, key.isLeft, dockBean.addr ) } else { ModBusController.controlKeyBuckle( true, key.isLeft, dockBean.addr ) } } } else { ModBusController.controlKeyBuckle(true, key.isLeft, dockBean.addr) } } } BleSendDispatcher.disconnectAll(60_0000L) sendLoadingEventMsg(null, false) context.startActivity(Intent(context, LoginActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }) } /** * 钥匙重新连接,清除内部作业票,清除所有代取设备重新分配 */ private fun reConnectKey() { val keyList = mDeviceTakeList.filter { it.deviceType == DEVICE_TYPE_KEY } // 不拿的设备不归你,下次登录重新按需分配 // 尽早clear,防止触发handleCurrentMode导致重新下发作业票 mDeviceTakeList.clear() // 连接后直接切换待机模式,让钥匙作业票失效并且重新准备完毕 keyList.forEach { val mac = ModBusController.getKeyByRfid(it.nfc)?.mac if (mac == null) { NetApi.getKeyInfo(it.nfc) { keyInfo -> keyInfo?.macAddress?.let { itMac -> registerConnectListener(itMac, isSend = true) { isDone, bleBean -> if (isDone && bleBean != null) { switchReadyMode(bleBean.bleDevice) } } } } } else { registerConnectListener(mac, isSend = true) { isDone, bleBean -> if (isDone && bleBean != null) { switchReadyMode(bleBean.bleDevice) } } } } } /** * 强制使用setValue,防止postValue造成数据丢失 */ fun sendEventMsg(msgEvent: MsgEvent) { Executor.runOnMain { mEventBus.value = msgEvent } } fun addExceptionKey(rfid: String) { LogUtil.w("addExceptionKey: $rfid") if (mExceptionKeyList.contains(rfid)) { return } mExceptionKeyList.add(rfid) } fun removeExceptionKey(key: String) { LogUtil.i("removeExceptionKey: $key") mExceptionKeyList.remove(key) } }