Эх сурвалжийг харах

refactor(蓝牙):
- 优化蓝牙钥匙连接和断开逻辑,增加定时任务检查钥匙信息
- 修复钥匙归还后未断开连接问题
- 修复钥匙取出后未重新连接可用钥匙的问题
- 修复作业流程中获取挂锁异常和步骤确认类型问题
- 修复部分页面在特定条件下未主动连接蓝牙钥匙的问题
- 关闭调试模式

周文健 3 сар өмнө
parent
commit
e045fc6837
28 өөрчлөгдсөн 389 нэмэгдсэн , 219 устгасан
  1. 1 0
      app/build.gradle.kts
  2. 4 1
      app/src/main/AndroidManifest.xml
  3. 5 19
      app/src/main/java/com/grkj/iscs/ISCSApplication.kt
  4. 15 0
      app/src/main/java/com/grkj/iscs/common/GlobalManager.kt
  5. 17 0
      app/src/main/java/com/grkj/iscs/features/login/activity/LoginActivity.kt
  6. 4 0
      app/src/main/java/com/grkj/iscs/features/main/activity/MainActivity.kt
  7. 2 2
      app/src/main/java/com/grkj/iscs/features/main/entity/TodoDataExtension.kt
  8. 4 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/common/WorkflowSettingFragment.kt
  9. 1 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/JobExecuteFragment.kt
  10. 55 3
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/MainViewModel.kt
  11. 7 1
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/home/HomeViewModel.kt
  12. 32 2
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/JobExecuteViewModel.kt
  13. 7 2
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/JobManageHomeViewModel.kt
  14. 6 0
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/MyTodoViewModel.kt
  15. 27 0
      app/src/main/java/com/grkj/iscs/features/splash/activity/SplashActivity.kt
  16. 78 0
      app/src/main/java/com/grkj/iscs/service/CheckKeyInfoTask.kt
  17. 5 0
      data/src/main/java/com/grkj/data/data/CommonConstants.kt
  18. 0 1
      data/src/main/java/com/grkj/data/data/MainDomainData.kt
  19. 0 5
      data/src/main/java/com/grkj/data/logic/IJobTicketLogic.kt
  20. 0 4
      data/src/main/java/com/grkj/data/logic/impl/network/NetworkJobTicketLogic.kt
  21. 4 1
      data/src/main/java/com/grkj/data/logic/impl/standard/HardwareLogic.kt
  22. 0 97
      data/src/main/java/com/grkj/data/logic/impl/standard/JobTicketLogic.kt
  23. 1 1
      shared/src/main/java/com/grkj/shared/config/Constants.kt
  24. 35 16
      ui-base/src/main/java/com/grkj/ui_base/business/BleBusinessManager.kt
  25. 12 11
      ui-base/src/main/java/com/grkj/ui_base/business/ModbusBusinessManager.kt
  26. 64 33
      ui-base/src/main/java/com/grkj/ui_base/utils/ble/BleConnectionManager.kt
  27. 1 19
      ui-base/src/main/java/com/grkj/ui_base/utils/modbus/ModBusController.kt
  28. 2 1
      ui-base/src/main/java/com/grkj/ui_base/utils/modbus/ModBusManager.kt

+ 1 - 0
app/build.gradle.kts

@@ -87,6 +87,7 @@ dependencies {
     implementation(libs.kotlinx.serialization.json)
     implementation(libs.sik.camera)
     implementation("com.github.loper7:DateTimePicker:0.6.3")
+    implementation("com.github.SilverIceKey:SIKCronJob:1.0.5")
     implementation("com.google.dagger:hilt-android:2.56.2")
     ksp("com.google.dagger:hilt-android-compiler:2.56.2")
     kapt("com.github.bingoogolapple.BGABadgeView-Android:compiler:1.2.0")

+ 4 - 1
app/src/main/AndroidManifest.xml

@@ -69,7 +69,10 @@
                 <action android:name="android.media.AUDIO_BECOMEING_NOISY" />
             </intent-filter>
         </receiver>
-
+        <service
+            android:name="com.sik.cronjob.services.TaskService"
+            android:exported="false"
+            android:process=":CronJobService"/>
         <meta-data
             android:name="design_width_in_dp"
             android:value="600" />

+ 5 - 19
app/src/main/java/com/grkj/iscs/ISCSApplication.kt

@@ -6,6 +6,7 @@ import android.app.PendingIntent
 import android.content.Context
 import android.content.Intent
 import android.content.res.Configuration
+import android.util.Log
 import android.util.TypedValue
 import androidx.work.Constraints
 import androidx.work.ExistingPeriodicWorkPolicy
@@ -16,7 +17,9 @@ import com.drake.statelayout.StateConfig
 import com.grkj.data.data.EventConstants
 import com.grkj.data.database.RoomBackupWorker
 import com.grkj.data.di.RepositoryManager
+import com.grkj.iscs.common.GlobalManager
 import com.grkj.iscs.features.splash.activity.SplashActivity
+import com.grkj.iscs.service.CheckKeyInfoTask
 import com.grkj.shared.model.EventBean
 import com.grkj.shared.utils.ArcSoftUtil
 import com.grkj.ui_base.business.ModbusBusinessManager
@@ -32,6 +35,8 @@ import com.scwang.smart.refresh.layout.api.RefreshHeader
 import com.scwang.smart.refresh.layout.api.RefreshLayout
 import com.scwang.smart.refresh.layout.listener.DefaultRefreshFooterCreator
 import com.scwang.smart.refresh.layout.listener.DefaultRefreshHeaderCreator
+import com.sik.cronjob.managers.CronJobManager
+import com.sik.cronjob.managers.CronJobScanner
 import com.sik.sikcore.SIKCore
 import com.sik.sikcore.crash.GlobalCrashCatch
 import com.sik.sikcore.log.LogUtils
@@ -53,19 +58,6 @@ import java.util.concurrent.TimeUnit
 class ISCSApplication : Application() {
     private val logger: Logger = LoggerFactory.getLogger(ISCSApplication::class.java)
 
-    /**
-     * 数据库备份
-     */
-    val backupWork = PeriodicWorkRequestBuilder<RoomBackupWorker>(
-        1, TimeUnit.DAYS
-    )
-        .setConstraints(
-            Constraints.Builder()
-                .setRequiresBatteryNotLow(true)
-                .build()
-        )
-        .build()
-
     /**
      * 程序创建
      */
@@ -95,12 +87,6 @@ class ISCSApplication : Application() {
             RepositoryManager.init(this@ISCSApplication)
         }
         StateConfig.emptyLayout = com.grkj.ui_base.R.layout.layout_empty
-        WorkManager.getInstance(this)
-            .enqueueUniquePeriodicWork(
-                "room_backup_work",
-                ExistingPeriodicWorkPolicy.KEEP,
-                backupWork
-            )
     }
 
     @Subscribe(threadMode = ThreadMode.MAIN)

+ 15 - 0
app/src/main/java/com/grkj/iscs/common/GlobalManager.kt

@@ -0,0 +1,15 @@
+package com.grkj.iscs.common
+
+import com.sik.cronjob.managers.CronJobManager
+import com.sik.sikcore.SIKCore
+
+/**
+ * 全局管理器
+ */
+object GlobalManager {
+
+    /**
+     * 任务管理器
+     */
+    val cronJobManager: CronJobManager by lazy { CronJobManager.getInstance(SIKCore.getApplication()) }
+}

+ 17 - 0
app/src/main/java/com/grkj/iscs/features/login/activity/LoginActivity.kt

@@ -15,17 +15,21 @@ import com.drake.brv.utils.linear
 import com.drake.brv.utils.models
 import com.drake.brv.utils.setup
 import com.google.android.gms.common.internal.service.Common
+import com.grkj.data.data.EventConstants
 import com.grkj.data.data.MainDomainData
 import com.grkj.data.enums.LoginResultEnum
 import com.grkj.data.model.local.LoginMenuEntity
 import com.grkj.iscs.R
+import com.grkj.iscs.common.GlobalManager
 import com.grkj.iscs.databinding.ActivityLoginBinding
 import com.grkj.iscs.databinding.ItemLoginMethodBinding
 import com.grkj.iscs.features.init.activity.SetRemoteServerActivity
 import com.grkj.iscs.features.login.dialog.LoginDialog
 import com.grkj.iscs.features.login.viewmodel.LoginViewModel
 import com.grkj.iscs.features.main.activity.MainActivity
+import com.grkj.iscs.service.CheckKeyInfoTask
 import com.grkj.shared.config.Constants
+import com.grkj.shared.model.EventBean
 import com.grkj.ui_base.base.BaseActivity
 import com.grkj.ui_base.utils.CommonUtils
 import com.grkj.ui_base.utils.event.LoadingEvent
@@ -33,11 +37,14 @@ import com.grkj.ui_base.utils.extension.getAppVersionName
 import com.grkj.shared.utils.extension.toByteArrays
 import com.grkj.shared.utils.extension.toHexStrings
 import com.grkj.ui_base.config.ISCSConfig
+import com.grkj.ui_base.utils.ble.BleConnectionManager
 import com.grkj.ui_base.utils.event.RFIDCardReadEvent
 import com.grkj.ui_base.utils.extension.tip
 import com.grkj.ui_base.utils.fingerprint.FingerprintUtil
 import com.kongzue.dialogx.dialogs.PopTip
+import com.sik.cronjob.managers.CronJobScanner
 import com.sik.sikcore.extension.setDebouncedClickListener
+import com.sik.sikcore.thread.ThreadUtils
 import com.sik.sikimage.ImageConvertUtils
 import dagger.hilt.android.AndroidEntryPoint
 
@@ -215,6 +222,16 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>() {
         })
     }
 
+    override fun onEvent(event: EventBean<Any>) {
+        super.onEvent(event)
+        when (event.code) {
+            EventConstants.EVENT_START_MODBUS_COMPLETE -> {
+                val jobList = CronJobScanner.scanJobs(CheckKeyInfoTask())
+                GlobalManager.cronJobManager.registerJobs(jobList)
+            }
+        }
+    }
+
     override fun onStop() {
         super.onStop()
         cardNo = ""

+ 4 - 0
app/src/main/java/com/grkj/iscs/features/main/activity/MainActivity.kt

@@ -23,6 +23,7 @@ import com.grkj.ui_base.base.BaseActivity
 import com.grkj.ui_base.utils.event.BottomNavVisibilityEvent
 import com.grkj.shared.utils.extension.toByteArrays
 import com.grkj.shared.utils.extension.toHexStrings
+import com.grkj.ui_base.utils.ble.BleConnectionManager
 import com.grkj.ui_base.utils.event.FlashTipEvent
 import com.grkj.ui_base.utils.event.RFIDCardReadEvent
 import com.sik.sikcore.extension.file
@@ -138,6 +139,7 @@ class MainActivity() : BaseActivity<ActivityMainBinding>() {
                 val firstId = binding.navBar.menu[0].itemId
                 binding.navBar.selectedItemId = firstId
                 MainDomainData.fromQuickEntry = false
+                viewModel.checkMyTodoForHandleKey()
             }
             binding.navBar.isVisible = bottomNavDestinations.contains(destination.id)
         }
@@ -175,6 +177,7 @@ class MainActivity() : BaseActivity<ActivityMainBinding>() {
 
     override fun initData() {
         super.initData()
+        viewModel.checkMyTodoForHandleKey()
         viewModel.bleIndicate()
         viewModel.registerStatusListener()
     }
@@ -205,6 +208,7 @@ class MainActivity() : BaseActivity<ActivityMainBinding>() {
      * 退出登录
      */
     private fun logout() {
+        BleConnectionManager.launchDisconnectAllJob()
         viewModel.removeBleIndicate()
         startActivity(Intent(this, LoginActivity::class.java).apply {
             flags = Intent.FLAG_ACTIVITY_NEW_TASK

+ 2 - 2
app/src/main/java/com/grkj/iscs/features/main/entity/TodoDataExtension.kt

@@ -58,7 +58,7 @@ fun TodoStepJoin.toTodoVo(sameTicketStepJoinData: List<TodoStepJoin>): TodoItemV
 fun splitTodoSteps(
     all: List<TodoItemVo>,
     waitLimit: Int = Int.MAX_VALUE        // 限制“批次”数量
-): Int {
+): Triple<MutableList<TodoItemVo>, MutableList<TodoItemVo>, MutableList<TodoItemVo>> {
     val tempTodo = mutableListOf<TodoItemVo>()
     val tempWait = mutableListOf<TodoItemVo>()
     val tempDone = mutableListOf<TodoItemVo>()
@@ -138,7 +138,7 @@ fun splitTodoSteps(
         it.todoStatus = TodoStatusEnum.DONE
     }
     /* —— 结果赋值 —— */
-    return tempTodo.size + tempWait.size
+    return Triple(tempWait, tempTodo, tempDone)
 }
 
 /**

+ 4 - 0
app/src/main/java/com/grkj/iscs/features/main/fragment/common/WorkflowSettingFragment.kt

@@ -97,6 +97,10 @@ class WorkflowSettingFragment : BaseFormFragment<FragmentWorkflowSettingBinding>
                 if (viewModel.currentConfirmType != WorkflowStepConfirmTypeEnum.ROLE_CONFIRM) {
                     viewModel.currentConfirmRole = null
                     viewModel.currentConfirmMember = null
+                    viewModel.currentStep?.confirmRoleCode = null
+                    viewModel.currentStep?.confirmUser = null
+                    binding.stepConfirmRole.text = ""
+                    binding.stepConfirmMember.text = ""
                 }
                 viewModel.currentStep?.confirmType =
                     if (viewModel.currentConfirmType == WorkflowStepConfirmTypeEnum.AUTO_CONFIRM) 1 else 0

+ 1 - 0
app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/JobExecuteFragment.kt

@@ -640,6 +640,7 @@ class JobExecuteFragment : BaseFragment<FragmentJobExecuteBinding>() {
 
     override fun initData() {
         super.initData()
+        viewModel.checkMyTodoForConnectKey().observe(this) {}
         if (!GlobalDataTempStore.getInstance().hasData(DataTransferConstants.KEY_JOB_TICKET_ID)) {
             PopTip.build().tip(R.string.job_lost)
             navController.popBackStack()

+ 55 - 3
app/src/main/java/com/grkj/iscs/features/main/viewmodel/MainViewModel.kt

@@ -1,7 +1,14 @@
 package com.grkj.iscs.features.main.viewmodel
 
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.liveData
+import com.clj.fastble.BleManager
 import com.grkj.data.data.MainDomainData
-import com.grkj.iscs.R
+import com.grkj.data.logic.IJobTicketLogic
+import com.grkj.data.model.local.isMyTodo
+import com.grkj.iscs.features.main.entity.splitTodoSteps
+import com.grkj.iscs.features.main.entity.toTodoVo
+import com.grkj.shared.utils.extension.startsWith
 import com.grkj.ui_base.base.BaseViewModel
 import com.grkj.ui_base.business.BleBusinessManager
 import com.grkj.ui_base.business.ModbusBusinessManager
@@ -9,12 +16,15 @@ import com.grkj.ui_base.config.ISCSConfig
 import com.grkj.ui_base.utils.CommonUtils
 import com.grkj.ui_base.utils.Executor
 import com.grkj.ui_base.utils.ble.BleBean
+import com.grkj.ui_base.utils.ble.BleCmdManager
 import com.grkj.ui_base.utils.ble.BleConnectionManager
+import com.grkj.ui_base.utils.ble.BleConst
 import com.grkj.ui_base.utils.ble.BleIndicateListener
 import com.grkj.ui_base.utils.event.LoadingEvent
 import com.grkj.ui_base.utils.modbus.DeviceConst
 import com.sik.sikcore.thread.ThreadUtils
 import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.delay
 import javax.inject.Inject
 
@@ -22,7 +32,9 @@ import javax.inject.Inject
  * 首页界面模型
  */
 @HiltViewModel
-class MainViewModel @Inject constructor() : BaseViewModel() {
+class MainViewModel @Inject constructor(
+    val jobTicketRepository: IJobTicketLogic
+) : BaseViewModel() {
     /**
      * 蓝牙监听
      */
@@ -42,11 +54,51 @@ class MainViewModel @Inject constructor() : BaseViewModel() {
                         isNeedLoading,
                         prepareDoneCallBack
                     )
+                    when {
+                        // 获取设备当前状态
+                        byteArray.startsWith(BleConst.RSP_CURRENT_STATUS) -> BleCmdManager.handleCurrentStatus(
+                            byteArray
+                        ) {
+                            when (it) {
+                                0x02.toByte() -> {
+                                    checkMyTodoForHandleKey(bleBean.bleDevice.mac)
+                                }
+                            }
+                        }
+                    }
                 }
-
             })
     }
 
+    /**
+     * 获取我的待办的作业
+     */
+    fun checkMyTodoForHandleKey(mac: String? = null): LiveData<Int> {
+        return liveData(Dispatchers.IO) {
+            val userInfo = MainDomainData.userInfo
+            if (userInfo == null) {
+                emit(0) // or emit(false) 根据语义定义
+                return@liveData
+            }
+
+            val userId = userInfo.userId
+            val userName = userInfo.userName
+            val todoItemData = jobTicketRepository.getMyTodoList()
+            val myTodoStepJoin = todoItemData.filter { it.isMyTodo(userId, userName) }
+            val todoItemVos =
+                myTodoStepJoin.map { it.toTodoVo(todoItemData.filter { temp -> it.ticketId == temp.ticketId }) }
+                    .toMutableList()
+            val todoData = splitTodoSteps(todoItemVos, 1)
+            if (BleManager.getInstance().allConnectedDevice.isEmpty() && (todoData.first.any { it.enableLock || it.enableUnlock } || todoData.second.any { it.enableLock || it.enableUnlock })) {
+                BleBusinessManager.connectExistsKey()
+            } else {
+                mac?.let {
+                    BleConnectionManager.launchDisconnectJob(it)
+                }
+            }
+        }
+    }
+
     /**
      * 移除蓝牙监听
      */

+ 7 - 1
app/src/main/java/com/grkj/iscs/features/main/viewmodel/home/HomeViewModel.kt

@@ -2,6 +2,7 @@ package com.grkj.iscs.features.main.viewmodel.home
 
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.liveData
+import com.clj.fastble.BleManager
 import com.grkj.data.data.MainDomainData
 import com.grkj.data.model.dos.WorkflowMode
 import com.grkj.data.model.local.isMyTodo
@@ -15,6 +16,7 @@ import com.grkj.data.logic.IWorkstationLogic
 import com.grkj.iscs.features.main.entity.splitTodoSteps
 import com.grkj.iscs.features.main.entity.toTodoVo
 import com.grkj.ui_base.base.BaseViewModel
+import com.grkj.ui_base.business.BleBusinessManager
 import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.Dispatchers
 import javax.inject.Inject
@@ -140,7 +142,11 @@ class HomeViewModel @Inject constructor(
             todoItemVos =
                 myTodoStepJoin.map { it.toTodoVo(todoItemData.filter { temp -> it.ticketId == temp.ticketId }) }
                     .toMutableList()
-            emit(splitTodoSteps(todoItemVos, 1))
+            val todoData = splitTodoSteps(todoItemVos, 1)
+            if (BleManager.getInstance().allConnectedDevice.isEmpty() && (todoData.first.any { it.enableLock || it.enableUnlock } || todoData.second.any { it.enableLock || it.enableUnlock })) {
+                BleBusinessManager.connectExistsKey()
+            }
+            emit(todoData.first.size + todoData.second.size)
         }
     }
 }

+ 32 - 2
app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/JobExecuteViewModel.kt

@@ -2,6 +2,7 @@ package com.grkj.iscs.features.main.viewmodel.job_manage
 
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.liveData
+import com.clj.fastble.BleManager
 import com.grkj.data.data.MainDomainData
 import com.grkj.data.di.RepositoryManager
 import com.grkj.data.enums.JobTicketStatusEnum
@@ -25,7 +26,10 @@ import com.grkj.data.logic.IWorkflowLogic
 import com.grkj.data.logic.impl.standard.UserLogic
 import com.grkj.data.model.local.TodoStepJoin
 import com.grkj.data.model.local.hasAnyHardwareOperationFunction
+import com.grkj.data.model.local.isMyTodo
 import com.grkj.iscs.R
+import com.grkj.iscs.features.main.entity.splitTodoSteps
+import com.grkj.iscs.features.main.entity.toTodoVo
 import com.grkj.ui_base.base.BaseViewModel
 import com.grkj.ui_base.business.BleBusinessManager
 import com.grkj.ui_base.business.ModbusBusinessManager
@@ -689,12 +693,12 @@ class JobExecuteViewModel @Inject constructor(
                     }
                 }
                 if (workflowStep.enableColock && ticketPoints.all { it.pointStatus == "1" } && ticketUser.filter { it.userRole == RoleEnum.JTCOLOCKER.roleKey }
-                        .any { it.jobStatus != "1" }) {
+                        .any { it.jobStatus == "0" }) {
                     tip = CommonUtils.getStr(R.string.please_do_colock).toString()
                     index = 2
                 }
                 if (workflowStep.enableReleaseColock && ticketUser.filter { it.userRole == RoleEnum.JTCOLOCKER.roleKey }
-                        .any { it.jobStatus != "2" }) {
+                        .none { it.jobStatus == "0" }) {
                     tip =
                         CommonUtils.getStr(com.grkj.ui_base.R.string.please_do_uncolock).toString()
                     index = 2
@@ -798,6 +802,32 @@ class JobExecuteViewModel @Inject constructor(
         }
     }
 
+    /**
+     * 为连接钥匙检查待办
+     */
+    fun checkMyTodoForConnectKey(): LiveData<Int> {
+        return liveData(Dispatchers.IO) {
+            val userInfo = MainDomainData.userInfo
+            if (userInfo == null) {
+                emit(0) // or emit(false) 根据语义定义
+                return@liveData
+            }
+
+            val userId = userInfo.userId
+            val userName = userInfo.userName
+            val todoItemData = jobTicketRepository.getMyTodoList()
+            val myTodoStepJoin = todoItemData.filter { it.isMyTodo(userId, userName) }
+            val todoItemVos =
+                myTodoStepJoin.map { it.toTodoVo(todoItemData.filter { temp -> it.ticketId == temp.ticketId }) }
+                    .toMutableList()
+            val todoData = splitTodoSteps(todoItemVos, 1)
+            if (BleManager.getInstance().allConnectedDevice.isEmpty() && (todoData.first.any { it.enableLock || it.enableUnlock } || todoData.second.any { it.enableLock || it.enableUnlock })) {
+                BleBusinessManager.connectExistsKey()
+            }
+            emit(todoData.first.size + todoData.second.size)
+        }
+    }
+
     /**
      * 是否我的权限可以处理确认待办
      */

+ 7 - 2
app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/JobManageHomeViewModel.kt

@@ -2,6 +2,7 @@ package com.grkj.iscs.features.main.viewmodel.job_manage
 
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.liveData
+import com.clj.fastble.BleManager
 import com.grkj.data.data.MainDomainData
 import com.grkj.data.model.local.isMyTodo
 import com.grkj.data.model.vo.TodoItemVo
@@ -9,6 +10,7 @@ import com.grkj.data.logic.IJobTicketLogic
 import com.grkj.iscs.features.main.entity.splitTodoSteps
 import com.grkj.iscs.features.main.entity.toTodoVo
 import com.grkj.ui_base.base.BaseViewModel
+import com.grkj.ui_base.business.BleBusinessManager
 import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.Dispatchers
 import javax.inject.Inject
@@ -46,8 +48,11 @@ class JobManageHomeViewModel @Inject constructor(
             todoItemVos =
                 myTodoStepJoin.map { it.toTodoVo(todoItemData.filter { temp -> it.ticketId == temp.ticketId }) }
                     .toMutableList()
-
-            emit(splitTodoSteps(todoItemVos, 1))
+            val todoData = splitTodoSteps(todoItemVos, 1)
+            if (BleManager.getInstance().allConnectedDevice.isEmpty() && (todoData.first.any { it.enableLock || it.enableUnlock } || todoData.second.any { it.enableLock || it.enableUnlock })) {
+                BleBusinessManager.connectExistsKey()
+            }
+            emit(todoData.first.size + todoData.second.size)
         }
     }
 }

+ 6 - 0
app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/MyTodoViewModel.kt

@@ -2,6 +2,7 @@ package com.grkj.iscs.features.main.viewmodel.job_manage
 
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.liveData
+import com.clj.fastble.BleManager
 import com.grkj.data.data.DictConstants
 import com.grkj.data.data.MainDomainData
 import com.grkj.data.enums.JobTicketStatusEnum
@@ -18,11 +19,13 @@ import com.grkj.iscs.features.main.entity.actionKey
 import com.grkj.iscs.features.main.entity.findPredecessors
 import com.grkj.iscs.features.main.entity.toTodoVo
 import com.grkj.ui_base.base.BaseViewModel
+import com.grkj.ui_base.business.BleBusinessManager
 import com.grkj.ui_base.business.DataBusiness
 import com.grkj.ui_base.utils.CommonUtils
 import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.Dispatchers
 import javax.inject.Inject
+import kotlin.collections.any
 import kotlin.collections.contains
 
 /**
@@ -76,6 +79,9 @@ class MyTodoViewModel @Inject constructor(
                 myTodoStepJoin.map { it.toTodoVo(todoItemData.filter { temp -> it.ticketId == temp.ticketId }) }
                     .toMutableList()
             splitTodoSteps(todoItemVos, 1)
+            if (BleManager.getInstance().allConnectedDevice.isEmpty() && (waitData.any { it.enableLock || it.enableUnlock } || todoData.any { it.enableLock || it.enableUnlock })) {
+                BleBusinessManager.connectExistsKey()
+            }
             emit(true)
         }
     }

+ 27 - 0
app/src/main/java/com/grkj/iscs/features/splash/activity/SplashActivity.kt

@@ -3,8 +3,14 @@ package com.grkj.iscs.features.splash.activity
 import android.content.Intent
 import android.view.Gravity
 import androidx.activity.viewModels
+import androidx.work.Constraints
+import androidx.work.ExistingPeriodicWorkPolicy
+import androidx.work.PeriodicWorkRequestBuilder
+import androidx.work.WorkManager
 import com.grkj.data.data.MMKVConstants
+import com.grkj.data.database.RoomBackupWorker
 import com.grkj.iscs.R
+import com.grkj.iscs.common.GlobalManager
 import com.grkj.iscs.databinding.ActivitySplashBinding
 import com.grkj.iscs.features.init.activity.InitActivity
 import com.grkj.iscs.features.login.activity.LoginActivity
@@ -15,6 +21,7 @@ import com.kongzue.dialogx.DialogX
 import com.kongzue.dialogx.util.TextInfo
 import com.sik.sikcore.extension.getMMKVData
 import dagger.hilt.android.AndroidEntryPoint
+import java.util.concurrent.TimeUnit
 
 @AndroidEntryPoint
 class SplashActivity : BaseActivity<ActivitySplashBinding>() {
@@ -24,6 +31,13 @@ class SplashActivity : BaseActivity<ActivitySplashBinding>() {
     }
 
     override fun initView() {
+        WorkManager.getInstance(this)
+            .enqueueUniquePeriodicWork(
+                "room_backup_work",
+                ExistingPeriodicWorkPolicy.KEEP,
+                backupWork
+            )
+        GlobalManager.cronJobManager.bindService()
         val dialogXTextInfo = TextInfo()
         val dialogXTitleTextInfo = TextInfo().apply {
             setBold(true)
@@ -60,4 +74,17 @@ class SplashActivity : BaseActivity<ActivitySplashBinding>() {
             }
         }
     }
+
+    /**
+     * 数据库备份
+     */
+    val backupWork = PeriodicWorkRequestBuilder<RoomBackupWorker>(
+        1, TimeUnit.DAYS
+    )
+        .setConstraints(
+            Constraints.Builder()
+                .setRequiresBatteryNotLow(true)
+                .build()
+        )
+        .build()
 }

+ 78 - 0
app/src/main/java/com/grkj/iscs/service/CheckKeyInfoTask.kt

@@ -0,0 +1,78 @@
+package com.grkj.iscs.service
+
+import com.clj.fastble.BleManager
+import com.grkj.shared.utils.extension.startsWith
+import com.grkj.ui_base.business.ModbusBusinessManager
+import com.grkj.ui_base.utils.ble.BleBean
+import com.grkj.ui_base.utils.ble.BleConnectionManager
+import com.grkj.ui_base.utils.ble.BleConst
+import com.grkj.ui_base.utils.ble.BleIndicateListener
+import com.grkj.ui_base.utils.ble.BleUtil
+import com.sik.cronjob.annotations.CronJob
+import com.sik.sikcore.thread.ThreadUtils
+import kotlinx.coroutines.delay
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import kotlin.math.log
+
+/**
+ * 检查钥匙信息任务
+ */
+
+class CheckKeyInfoTask {
+    private val logger: Logger = LoggerFactory.getLogger(this::class.java)
+
+    /**
+     * 是检查信息
+     */
+    private var isCheckInfo: Boolean = false
+
+    /**
+     * 已经检查的钥匙
+     */
+    private var checkedKey: MutableList<String> = mutableListOf()
+
+    /**
+     * 检查钥匙信息
+     */
+    @CronJob(intervalMillis = 30 * 60_000L, initialDelay = 0, runOnMainThread = false)
+    fun checkKeyInfo() {
+        logger.info("开始检查钥匙信息")
+        val existsKey = ModbusBusinessManager.getExistsKey()
+        BleConnectionManager.addBLeIndicateListener(this, object : BleIndicateListener {
+            override fun handleRsp(
+                bleBean: BleBean,
+                byteArray: ByteArray,
+                isNeedLoading: Boolean,
+                prepareDoneCallBack: ((Boolean, BleBean?) -> Unit)?
+            ) {
+                when {
+                    byteArray.startsWith(BleConst.RSP_POWER_STATUS) -> {
+                        if (isCheckInfo) {
+                            BleManager.getInstance().disconnect(bleBean.bleDevice)
+                            BleConnectionManager.deviceList.removeIf { it.bleDevice.mac == bleBean.bleDevice.mac }
+                            logger.info("断开连接检查信息:${bleBean.bleDevice.mac}")
+                            checkedKey.add(bleBean.bleDevice.mac)
+                            if (checkedKey.joinToString(",") == existsKey.joinToString(",") {
+                                    it.mac ?: ""
+                                }) {
+                                isCheckInfo = false
+                            }
+                        }
+                    }
+                }
+            }
+        })
+        checkedKey.clear()
+        isCheckInfo = true
+        ThreadUtils.runOnIO {
+            existsKey.forEach {
+                it.mac?.let {
+                    BleConnectionManager.tryConnectWithOptionalCharge(it)
+                    delay(2000)
+                }
+            }
+        }
+
+    }
+}

+ 5 - 0
data/src/main/java/com/grkj/data/data/CommonConstants.kt

@@ -43,4 +43,9 @@ object CommonConstants {
      * 手机号正则
      */
     const val REGEX_MOBILE = "^1(3\\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\\d|9[0-35-9])\\d{8}$"
+
+    /**
+     * 蓝牙断连时间
+     */
+    const val BLE_DISCONNECT_DELAY_TIME = 60_000L
 }

+ 0 - 1
data/src/main/java/com/grkj/data/data/MainDomainData.kt

@@ -58,6 +58,5 @@ object MainDomainData {
         permissions.clear()
         userCardList.clear()
         userBiometricDataVo.clear()
-        deviceTakeTicketGroupBound.clear()
     }
 }

+ 0 - 5
data/src/main/java/com/grkj/data/logic/IJobTicketLogic.kt

@@ -317,11 +317,6 @@ interface IJobTicketLogic {
      */
     fun getGroupIdByPointIdAndTicketId(pointId: Long, ticketId: Long): Long
 
-    /**
-     * 获取我的待办数量
-     */
-    fun getMyToDoListJobCount(waitLimit: Int = 1): Int
-
     /**
      * 获取我的待办列表
      */

+ 0 - 4
data/src/main/java/com/grkj/data/logic/impl/network/NetworkJobTicketLogic.kt

@@ -253,10 +253,6 @@ class NetworkJobTicketLogic @Inject constructor() : BaseLogic(), IJobTicketLogic
         TODO("Not yet implemented")
     }
 
-    override fun getMyToDoListJobCount(waitLimit: Int): Int {
-        TODO("Not yet implemented")
-    }
-
     override fun getMyTodoList(): List<TodoStepJoin> {
         TODO("Not yet implemented")
     }

+ 4 - 1
data/src/main/java/com/grkj/data/logic/impl/standard/HardwareLogic.kt

@@ -134,7 +134,10 @@ class HardwareLogic @Inject constructor(
                 lockTakeInfo.ticketId?.let { ticketId ->
                     val isJobTicketPoints = jobTicketDao.getJobTicketPointsDataByTicketId(ticketId)
                     val isJobTicketLock = jobTicketDao.getJobTicketLockDataByTicketId(ticketId)
-                        .filter { it.lockId !in isJobTicketPoints.mapNotNull { it.lockId } }
+                        .filter {
+                            it.lockId !in isJobTicketPoints.filter { it.groupId == MainDomainData.deviceTakeTicketGroupBound[ticketId] }
+                                .mapNotNull { it.lockId }
+                        }
                     logger.debug("作业票挂锁信息:${isJobTicketLock.toJson()}-${MainDomainData.deviceTakeTicketGroupBound[ticketId]}")
                     val emptyTicketLockInfo =
                         isJobTicketLock.first { it.groupId == MainDomainData.deviceTakeTicketGroupBound[ticketId] }

+ 0 - 97
data/src/main/java/com/grkj/data/logic/impl/standard/JobTicketLogic.kt

@@ -522,103 +522,6 @@ class JobTicketLogic @Inject constructor(
         }
     }
 
-    override fun getMyToDoListJobCount(waitLimit: Int): Int {
-        val userInfo = MainDomainData.userInfo
-        if (userInfo == null) {
-            return 0
-        }
-
-        val userId = userInfo.userId
-        val userName = userInfo.userName
-        val todoList = getMyTodoList()
-        // 初始化容器
-        val tempTodo = mutableListOf<TodoStepJoin>()
-        val tempWait = mutableListOf<TodoStepJoin>()
-        val tempDone = mutableListOf<TodoStepJoin>()
-
-        // 1. 先按 ticketId 分组
-        val grouped = todoList.filter { it.isMyTodo(userId, userName) }.groupBy { it.ticketId }
-
-        for ((_, steps) in grouped) {
-            var doneSteps =
-                steps.filter {
-                    it.stepStatus == "1" && (!it.enableEndJob && it.ticketStatus in listOf(
-                        JobTicketStatusEnum.SELECT_MEMBER.status,
-                        JobTicketStatusEnum.LOCKING.status,
-                        JobTicketStatusEnum.COLOCKING.status,
-                        JobTicketStatusEnum.UNLOCKING.status,
-                        JobTicketStatusEnum.PROGRESSING.status,
-                    ) || (it.ticketStatus == JobTicketStatusEnum.FINISHED.status) && it.enableEndJob)
-                }
-            val pendingSteps =
-                steps.filter {
-                    it.stepStatus == "0" || (it.stepStatus == "1" && it.enableEndJob && it.ticketStatus in listOf(
-                        JobTicketStatusEnum.SELECT_MEMBER.status,
-                        JobTicketStatusEnum.LOCKING.status,
-                        JobTicketStatusEnum.COLOCKING.status,
-                        JobTicketStatusEnum.UNLOCKING.status,
-                        JobTicketStatusEnum.PROGRESSING.status,
-                    ))
-                }
-
-            /* —— 当前步骤 → todoData —— */
-            val todoStep = pendingSteps
-                .filter { pendingStep ->
-                    steps.filter { it.stepStatus == "0" }
-                        .minByOrNull { it.stepIndex }?.stepIndex == pendingStep.stepIndex || (steps.none {
-                        pendingStep.stepStatus == "0"
-                    } && pendingStep.enableEndJob)
-                }.findPredecessors()
-            tempTodo += todoStep.second
-            doneSteps = doneSteps.toMutableList().apply {
-                addAll(todoStep.third)
-            }
-
-            /* —— 其它待办 → waitData —— */
-            val otherPending = pendingSteps.filter { it !in (todoStep.second + todoStep.third) }
-
-            /* ① 按 (stepIndex, actionKey) 分批 —— */
-            val byBatch = otherPending
-                .groupBy { it.stepIndex to actionKey(it) }             // <─ 新增动作维度
-                .toSortedMap(
-                    compareBy<Pair<Int, Int>> { it.first }          // stepIndex 升序
-                        .thenBy { it.second }                          // actionKey 字典序
-                )
-
-            var pickedBatch = 0
-            for ((_, batchList) in byBatch) {
-                if (pickedBatch >= waitLimit) break
-                if (batchList.isEmpty()) continue
-
-                val action = actionKey(batchList[0])  // 同批次动作一致,取任意一条判断即可
-
-                if (action == 1 || action == 4) {
-                    // ✅ 上锁/解锁 → 同一 stepIndex 下多个 group 要全部保留
-                    tempWait += batchList
-                } else {
-                    // ✅ 其他类型 → 只保留一条代表
-                    tempWait.add(batchList.first())
-                }
-
-                pickedBatch++
-            }
-
-            tempDone += doneSteps
-        }
-        tempTodo.forEach {
-            it.todoStatus = TodoStatusEnum.TODO
-        }
-        tempWait.forEach {
-            it.todoStatus = TodoStatusEnum.WAIT
-            it.previousTodoStepJoin =
-                tempTodo.filter { todoData -> it.ticketId == todoData.ticketId }
-        }
-        tempDone.forEach {
-            it.todoStatus = TodoStatusEnum.DONE
-        }
-        return tempTodo.size + tempWait.size
-    }
-
     /**
      * 获取待办类型
      */

+ 1 - 1
shared/src/main/java/com/grkj/shared/config/Constants.kt

@@ -41,7 +41,7 @@ object Constants {
     const val USER_TYPE_LOCKER = "0"                // 上锁人
     const val USER_TYPE_COLOCKER = "1"              // 共锁人
 
-    const val DEBUG: Boolean = true
+    const val DEBUG: Boolean = false
 
     /*************************  虹软ArcSoft  *************************/
     const val ACTIVE_KEY = "8551-11XJ-913K-J1CN"

+ 35 - 16
ui-base/src/main/java/com/grkj/ui_base/business/BleBusinessManager.kt

@@ -41,6 +41,7 @@ import com.kongzue.dialogx.dialogs.PopTip
 import com.sik.sikcore.SIKCore
 import com.sik.sikcore.data.BeanUtils
 import com.sik.sikcore.date.TimeUtils
+import com.sik.sikcore.extension.toJson
 import com.sik.sikcore.thread.ThreadUtils
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.async
@@ -124,6 +125,7 @@ object BleBusinessManager {
                     // 只能在这里断开,不能全部断开
                     BleManager.getInstance().disconnect(bleBean.bleDevice)
                     BleConnectionManager.deviceList.removeIf { it.bleDevice.mac == bleBean.bleDevice.mac }
+                    logger.info("断开连接发钥匙:${bleBean.bleDevice.mac}")
                     // 打开钥匙卡扣
                     val keyBean = ModBusController.getKeyByMac(bleBean.bleDevice.mac)
                     if (keyBean == null) {
@@ -142,6 +144,7 @@ object BleBusinessManager {
                             bleBean.bleDevice.mac, false, 1
                         )
                         PopTip.build().tip(R.string.take_out_key)
+                        connectExistsKey(listOf(bleBean.bleDevice.mac))
                     }
                 } else {
                     logger.error("切换工作模式失败 : ${bleBean.bleDevice.mac}")
@@ -327,6 +330,7 @@ object BleBusinessManager {
                             DeviceConst.DEVICE_TYPE_LOCK, updateBo?.ticketId
                         )
                     ) {
+                        logger.info("有钥匙待取但是对应的作业票的锁还有")
                         //todo 如果有钥匙待取但是对应的作业票的锁还有的,就不发
                         return
                     }
@@ -342,20 +346,15 @@ object BleBusinessManager {
                                     false, currentModeEvent.bleBean.bleDevice.mac
                                 )
                                 LoadingEvent.sendLoadingEvent()
-                                //连上之后没有工作票要下发就断开 看是否还有设备等待连接并且连接数是否大于等于预期,没有就不断开,有就让路,一般是初始化的时候
-                                if (BleConnectionManager.hasConnectWait() && BleManager.getInstance().allConnectedDevice.size >= BleConst.MAX_KEY_CONNECT_COUNT) {
-                                    BleManager.getInstance()
-                                        .disconnect(currentModeEvent.bleBean.bleDevice)
-                                    BleConnectionManager.deviceList.removeIf { it.bleDevice.mac == currentModeEvent.bleBean.bleDevice.mac }
-                                }
+                                logger.info("作业票数据为空")
                                 return@runOnIO
                             }
                             RepositoryManager.jobTicketRepo.getStepDetail(itBO.ticketId) {
-                                logger.info("步骤数据:${it}")
+                                logger.info("步骤数据:${it.toJson()}")
                                 val step = it?.firstOrNull { it.stepStatus == "0" }
-                                logger.info("当前步骤:${step}")
+                                logger.info("当前步骤:${step.toJson()}")
                                 RepositoryManager.jobTicketRepo.getTicketDetail(itBO.ticketId) { ticketDetail ->
-                                    logger.info("步骤详情:${ticketDetail}")
+                                    logger.info("步骤详情:${ticketDetail.toJson()}")
                                     val role =
                                         ticketDetail?.ticketUserVOList?.filter { it.userRole == RoleEnum.JTLOCKER.roleKey }
                                             ?.find {
@@ -374,8 +373,10 @@ object BleBusinessManager {
                                             currentModeEvent.bleBean.bleDevice.mac,
                                             ticketDetail,
                                             ticketDetail.ticketLockVOList?.filter {
-                                                it.groupId == MainDomainData.deviceTakeTicketGroupBound[itBO.ticketId] && ticketDetail.ticketPointsVOList?.mapNotNull { it.lockId }
-                                                    ?.contains(it.lockId) != true
+                                                it.groupId == MainDomainData.deviceTakeTicketGroupBound[itBO.ticketId] &&
+                                                        ticketDetail.ticketPointsVOList?.filter { it.groupId == MainDomainData.deviceTakeTicketGroupBound[itBO.ticketId] }
+                                                            ?.mapNotNull { it.lockId }
+                                                            ?.contains(it.lockId) != true
                                             }
                                                 ?.map { it.lockNfc }?.toMutableList(),
                                             true
@@ -401,8 +402,6 @@ object BleBusinessManager {
                         )
                         LoadingEvent.sendLoadingEvent()
                         //连上之后没有工作票要下发就断开 看是否还有设备等待连接并且连接数是否大于等于预期,没有就不断开,有就让路,一般是初始化的时候
-                        BleManager.getInstance().disconnect(currentModeEvent.bleBean.bleDevice)
-                        BleConnectionManager.deviceList.removeIf { it.bleDevice.mac == currentModeEvent.bleBean.bleDevice.mac }
                     }
                 }
             }
@@ -535,6 +534,7 @@ object BleBusinessManager {
                             PopTip.build().tip(CommonUtils.getStr(R.string.continue_the_ticket))
                             BleManager.getInstance().disconnect(bleDevice)
                             BleConnectionManager.deviceList.removeIf { it.bleDevice.mac == bleDevice.mac }
+                            logger.info("断开连接归还取消:${bleDevice.mac}")
                             // 打开卡扣,防止初始化的时候选择不处理钥匙导致无法使用
                             val dock = ModBusController.getDockByKeyMac(bleDevice.mac)
                             val keyBean = dock?.getKeyList()?.find { it.mac == bleDevice.mac }
@@ -817,7 +817,12 @@ object BleBusinessManager {
                         true,
                         mac,
                         ticketDetail,
-                        ticketDetail.ticketLockVOList?.filter { it.lockStatus != "2" && it.groupId == MainDomainData.deviceTakeTicketGroupBound[it.ticketId] }
+                        ticketDetail.ticketLockVOList?.filter {
+                            it.groupId == MainDomainData.deviceTakeTicketGroupBound[it.ticketId] &&
+                                    ticketDetail.ticketPointsVOList?.filter { it.groupId == MainDomainData.deviceTakeTicketGroupBound[it.ticketId] }
+                                        ?.mapNotNull { it.lockId }
+                                        ?.contains(it.lockId) != true
+                        }
                             ?.map { it.lockNfc }?.toMutableList(),
                         true
                     )
@@ -834,6 +839,12 @@ object BleBusinessManager {
      * 分配钥匙
      */
     fun handleGiveKey(deviceTakeUpdateBO: DeviceTakeUpdate) {
+        logger.info(
+            "取完锁开始发钥匙1:${
+                ModBusController.getKeyByRfid(deviceTakeUpdateBO.nfc).toJson()
+            }"
+        )
+        logger.info("取完锁开始发钥匙2:${BleConnectionManager.deviceList.toJson()}")
         BleConnectionManager.getBleDeviceByMac(ModBusController.getKeyByRfid(deviceTakeUpdateBO.nfc)?.mac)
             ?.let {
                 BleConnectionManager.getCurrentStatus(
@@ -880,6 +891,7 @@ object BleBusinessManager {
                     }
                 }
             } ?: run {
+            logger.info("根据钥匙的rfid没找到钥匙")
             TipDialog.show(
                 msg = CommonUtils.getStr(R.string.key_take_error_tip).toString(), onConfirmClick = {
                     DeviceExceptionEvent.sendDeviceExceptionEvent(
@@ -892,8 +904,15 @@ object BleBusinessManager {
     /**
      * 连接一把存在的可连接的钥匙
      */
-    fun connectExistsKey(exceptKeyMac: String) {
+    fun connectExistsKey(exceptKeyMac: List<String> = listOf()) {
         ThreadUtils.runOnIO {
+            val connectedDevice = BleManager.getInstance().allConnectedDevice
+            if (connectedDevice.isNotEmpty() && connectedDevice.subtract(exceptKeyMac.toSet())
+                    .isNotEmpty()
+            ) {
+                logger.info("已有钥匙连接")
+                return@runOnIO
+            }
             // —— 串行请求1 & 2 ——
             val slotsPage = DataBusiness.getSlotsPage()
             val keyPage = DataBusiness.getKeyPage()
@@ -913,7 +932,7 @@ object BleBusinessManager {
                     }?.toMutableList() ?: mutableListOf(),
                     (keyPage?.records?.filter { it.exStatus == keyStatusList.find { d -> d.dictLabel == "异常" }?.dictValue }
                         ?.map { it.keyNfc ?: "" }?.toMutableList() ?: mutableListOf()),
-                    mutableListOf(exceptKeyMac)
+                    exceptKeyMac
                 )
             }
         }

+ 12 - 11
ui-base/src/main/java/com/grkj/ui_base/business/ModbusBusinessManager.kt

@@ -144,16 +144,6 @@ object ModbusBusinessManager {
                                 )?.mac?.let {
                                     BleConnectionManager.unregisterConnectListener(it)
                                 }
-                                //待机数不够就再连一把,但不能是原来那把
-                                if (BleManager.getInstance().allConnectedDevice.size < BleConst.MAX_KEY_STAND_BY) {
-                                    ModBusController.getKeyByRfid(
-                                        info.nfc
-                                    )?.mac?.let {
-                                        BleBusinessManager.connectExistsKey(
-                                            it
-                                        )
-                                    }
-                                }
                             } else {
                                 logger.info("更新钥匙取出异常")
                             }
@@ -197,14 +187,15 @@ object ModbusBusinessManager {
                                     return@runOnMain
                                 } else {
                                     logger.info("All locks are taken")
-                                    LoadingEvent.sendLoadingEvent()
                                 }
                                 if (SPUtils.getTicketTakeLockException(info.ticketId)) {
                                     PopTip.build()
                                         .tip(R.string.current_ticket_report_lock_take_exception_tip)
+                                    logger.info("当前作业获取挂锁异常")
                                     return@runOnMain
                                 }
                                 // 检查有无当前工作票的钥匙
+                                logger.info("检查当前作业票的钥匙:${mDeviceTakeList.find { it.deviceType == DeviceConst.DEVICE_TYPE_KEY && it.ticketId == info.ticketId }}")
                                 mDeviceTakeList.find { it.deviceType == DeviceConst.DEVICE_TYPE_KEY && it.ticketId == info.ticketId }
                                     ?.let { itKey ->
                                         LoadingEvent.sendLoadingEvent(
@@ -623,4 +614,14 @@ object ModbusBusinessManager {
         return if (lockData == null) CommonUtils.getStr(R.string.not_in_slot)
             .toString() else "${lockData.row}-${lockData.idx + 1}"
     }
+
+    /**
+     * 获取存在的钥匙
+     */
+    fun getExistsKey(): List<DockBean.KeyBean> {
+        val dockData =
+            ModBusController.dockList.filter { it.type == DeviceConst.DOCK_TYPE_LOCK || it.type == DeviceConst.DOCK_TYPE_KEY || it.type == DeviceConst.DOCK_TYPE_PORTABLE }
+        return dockData.filter { it.type == DeviceConst.DOCK_TYPE_KEY }.flatMap { it.deviceList }
+            .filterIsInstance<DockBean.KeyBean>()
+    }
 }

+ 64 - 33
ui-base/src/main/java/com/grkj/ui_base/utils/ble/BleConnectionManager.kt

@@ -5,6 +5,7 @@ import android.bluetooth.BluetoothGatt
 import com.clj.fastble.BleManager
 import com.clj.fastble.data.BleDevice
 import com.clj.fastble.exception.BleException
+import com.grkj.data.data.CommonConstants
 import com.grkj.shared.utils.extension.startsWith
 import com.grkj.shared.utils.extension.toHexStrings
 import com.grkj.ui_base.R
@@ -37,12 +38,6 @@ object BleConnectionManager {
     // 已连接的蓝牙钥匙集合
     var deviceList: MutableList<BleBean> = mutableListOf()
 
-    /**
-     * 钥匙电量检测
-     */
-    @Volatile
-    var keyPowerDetect: MutableMap<String, Int?> = mutableMapOf()
-
     /**
      * 最大待机连接数,超过则断开最旧设备。
      * 默认为业务常量 MAX_KEY_STAND_BY,可根据需求调整。
@@ -68,6 +63,11 @@ object BleConnectionManager {
      */
     var mExceptionKeyList = mutableListOf<String>()
 
+    /**
+     * 断开连接任务
+     */
+    private val disconnectJob: HashMap<String, String> = hashMapOf()
+
     /**
      * 蓝牙的通信返回监听
      */
@@ -96,8 +96,7 @@ object BleConnectionManager {
                     if (isSuccess) {
                         prepareDoneCallBack?.invoke(true, bleBean)
                         //尝试使用命令作为心跳 ,获取token完成之后就要建立心跳了
-                        tryHeartBeatWithCmd(bleBean.bleDevice)
-                        keyPowerDetect.remove(bleBean.bleDevice.mac)
+                        getBatteryPower(bleBean.bleDevice, true)
                     }
                 }
 
@@ -106,36 +105,65 @@ object BleConnectionManager {
                     val power = byteArray[4].toInt()
                     ModBusController.updateKeyPower(power, bleBean.bleDevice.mac)
                     logger.info("电量(${bleBean.bleDevice.mac}):${power}")
-                    keyPowerDetect[bleBean.bleDevice.mac]?.let {
-                        if (it == 5) {
-                            if (power < 50) {//如果电量小于50就打开仓位充电
-                                ModBusController.controlKeyCharge(true, bleBean.bleDevice.mac) {
-                                    logger.debug("钥匙: ${bleBean.bleDevice.mac} 开始充电")
-                                }
-                            } else {
-                                ModBusController.controlKeyCharge(false, bleBean.bleDevice.mac) {
-                                    logger.debug("钥匙: ${bleBean.bleDevice.mac} 关闭充电")
-                                }
-                            }
-                            keyPowerDetect[bleBean.bleDevice.mac] = 0
-                        }
-                    } ?: { keyPowerDetect[bleBean.bleDevice.mac] = 0 }
-                    ThreadUtils.runOnIODelayed(30 * 1000) {
-                        if (BleManager.getInstance().isConnected(bleBean.bleDevice)) {
-                            //尝试使用命令作为心跳
-                            tryHeartBeatWithCmd(bleBean.bleDevice)
-                        }
-                    }
                 }
             }
         }
     }
 
     /**
-     * 尝试使用命令作为心跳,命令暂未获取电量 没30s发送一次
+     * 启动断连任务
+     */
+    fun launchDisconnectJob(mac: String) {
+        val jobId = ThreadUtils.runOnIODelayed(CommonConstants.BLE_DISCONNECT_DELAY_TIME) {
+            val bleBean = getBleDeviceByMac(mac)
+            bleBean?.bleDevice?.let {
+                BleManager.getInstance().disconnect(it)
+                deviceList.removeIf { it.bleDevice.mac == bleBean.bleDevice.mac }
+                disconnectJob.remove(it.mac)
+                logger.info("断开连接计划单把断开:${it.mac}")
+            }
+        }
+        disconnectJob[mac] = jobId
+    }
+
+    /**
+     * 启动断连所有设备任务
+     */
+    fun launchDisconnectAllJob() {
+        deviceList.forEach { bleBean ->
+            val jobId = ThreadUtils.runOnIODelayed(CommonConstants.BLE_DISCONNECT_DELAY_TIME) {
+                val bleBean =
+                    getBleDeviceByMac(bleBean.bleDevice.mac)
+                bleBean?.bleDevice?.let {
+                    BleManager.getInstance().disconnect(it)
+                    deviceList.removeIf { it.bleDevice.mac == bleBean.bleDevice.mac }
+                    disconnectJob.remove(it.mac)
+                    logger.info("断开连接计划全部断开:${it.mac}")
+                }
+            }
+            disconnectJob[bleBean.bleDevice.mac] = jobId
+        }
+    }
+
+    /**
+     * 取消断连所有设备任务
      */
-    private fun tryHeartBeatWithCmd(bleDevice: BleDevice) {
-        getBatteryPower(bleDevice, true)
+    fun cancelDisconnectAllJob() {
+        disconnectJob.forEach {
+            ThreadUtils.cancel(it.value)
+        }
+        disconnectJob.clear()
+    }
+
+    /**
+     * 取消断连任务
+     */
+    fun cancelDisconnectJob(mac: String) {
+        val jobId = disconnectJob[mac]
+        jobId?.let {
+            ThreadUtils.cancel(it)
+            disconnectJob.remove(jobId)
+        }
     }
 
     /**
@@ -206,6 +234,7 @@ object BleConnectionManager {
     fun unregisterConnectListener(mac: String, bleBean: BleBean? = null) {
         logger.info("蓝牙连接-unregisterConnectListener : $mac")
         connectListeners.removeAll { it.mac == mac }
+        currentConnectingMac.removeAll { it == mac }
     }
 
     /**
@@ -329,9 +358,9 @@ object BleConnectionManager {
             }
 
             override fun onScanning(bleDevice: BleDevice?) {
-                val mac = bleDevice?.mac ?: return
+                val scanMac = bleDevice?.mac ?: return
                 logger.info("蓝牙连接-onScanning:${mac}")
-                if (mac.equals(mac, ignoreCase = true)) {
+                if (scanMac.equals(mac, ignoreCase = true)) {
                     // 找到目标设备,马上停止扫描
                     logger.info("找到目标设备 $mac,停止扫描并尝试连接")
                     BleManager.getInstance().cancelScan()
@@ -372,6 +401,7 @@ object BleConnectionManager {
         ThreadUtils.runOnIO {
             BleManager.getInstance().disconnect(bleDevice)
             deviceList.removeIf { it.bleDevice.mac == bleDevice.mac }
+            logger.info("断开连接连接中断开:${bleDevice.mac}")
             delay(300)
             BleUtil.Companion.instance?.connectBySelect(
                 bleDevice, object : CustomBleGattCallback() {
@@ -401,6 +431,7 @@ object BleConnectionManager {
                         logger.info("蓝牙连接-onConnectSuccess : ${bleDevice?.mac}")
                         bleDevice?.let {
                             deviceList.removeIf { it.bleDevice.mac == bleDevice.mac }
+                            logger.info("移除钥匙")
                             val bleBean = BleBean(it)
                             deviceList.add(bleBean)
 //                            todo 移除异常钥匙

+ 1 - 19
ui-base/src/main/java/com/grkj/ui_base/utils/modbus/ModBusController.kt

@@ -239,24 +239,6 @@ object ModBusController {
                                     //已经初始化完成才会去连接
                                     if (ISCSConfig.isInit) {
                                         controlKeyCharge(true, key.idx, dockBean.addr)
-                                        ThreadUtils.runOnIO {
-                                            delay(500)
-                                            val isConnect =
-                                                BleConnectionManager.tryConnectWithOptionalCharge(
-                                                    keyInfo.macAddress!!
-                                                )
-                                            if (isConnect) {
-                                                val bleBean =
-                                                    BleConnectionManager.getBleDeviceByMac(keyInfo.macAddress)
-                                                bleBean?.let {
-                                                    Executor.delayOnMain(500) {
-                                                        BleConnectionManager.getCurrentStatus(
-                                                            3, it.bleDevice
-                                                        )
-                                                    }
-                                                }
-                                            }
-                                        }
                                     }
                                     controlKeyBuckle(false, key.idx, dockBean.addr)
                                 } else {
@@ -1106,7 +1088,7 @@ object ModBusController {
                 BleManager.getInstance().isConnected(it.mac)
             }    // 主键:在线优先
                 .thenByDescending { BleConnectionManager.getBleDeviceByMac(it.mac)?.token != null } // 次键:有 token 优先
-                .thenByDescending { it.power }                                                // 三级:信号强度越大越前
+                .thenByDescending { it.power }                                                // 三级:电量越高
         )
 
         for (kb in keyList) {

+ 2 - 1
ui-base/src/main/java/com/grkj/ui_base/utils/modbus/ModBusManager.kt

@@ -3,6 +3,7 @@ package com.grkj.ui_base.utils.modbus
 import com.google.gson.Gson
 import com.google.gson.reflect.TypeToken
 import com.grkj.data.data.MMKVConstants
+import com.grkj.shared.config.Constants
 import com.grkj.ui_base.utils.Executor
 import com.grkj.shared.utils.extension.toHexStrings
 import com.sik.sikcore.extension.getMMKVData
@@ -19,7 +20,7 @@ class ModBusManager(
     // 底层串口管理器
     val portManager: PortManager?,
     // 是否输出详细信息
-    val verbose: Boolean = false
+    val verbose: Boolean = Constants.DEBUG
 ) {
     private val logger: Logger = LoggerFactory.getLogger(ModBusManager::class.java)