Răsfoiți Sursa

refactor(更新)
- 初始化锁仓录入的行列修改完成
- 仓位管理的开关、异常记录、异常移除、仓位检测完成

周文健 4 luni în urmă
părinte
comite
f1acc4b0c3
28 a modificat fișierele cu 1026 adăugiri și 165 ștergeri
  1. 4 3
      app/src/main/java/com/grkj/iscs/features/init/model/DockData.kt
  2. 8 5
      app/src/main/java/com/grkj/iscs/features/init/viewmodel/InitDeviceRegistrationKeyAndLockViewModel.kt
  3. 58 0
      app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/SlotsExceptionReportDialog.kt
  4. 1 1
      app/src/main/java/com/grkj/iscs/features/main/fragment/hardware_manage/HardwareManageHomeFragment.kt
  5. 324 105
      app/src/main/java/com/grkj/iscs/features/main/fragment/hardware_manage/SlotsManageFragment.kt
  6. 156 4
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/hardware_manage/SlotsManageViewModel.kt
  7. 12 0
      app/src/main/res/drawable/selectable_input_text_bg.xml
  8. 103 0
      app/src/main/res/layout/dialog_slots_exception_report.xml
  9. 1 1
      app/src/main/res/layout/item_device_registration_key.xml
  10. 1 1
      app/src/main/res/layout/item_device_registration_lock.xml
  11. 55 2
      app/src/main/res/layout/item_device_slot_manage_key.xml
  12. 53 0
      app/src/main/res/layout/item_device_slot_manage_lock.xml
  13. 11 0
      app/src/main/res/values-en/strings.xml
  14. 2 0
      app/src/main/res/values-land/dimens.xml
  15. 11 0
      app/src/main/res/values-zh/strings.xml
  16. 2 0
      app/src/main/res/values/dimens.xml
  17. 11 0
      app/src/main/res/values/strings.xml
  18. 26 1
      data/src/main/java/com/grkj/data/dao/HardwareDao.kt
  19. 8 2
      data/src/main/java/com/grkj/data/database/ISCSDatabase.kt
  20. 35 0
      data/src/main/java/com/grkj/data/repository/IHardwareRepository.kt
  21. 33 2
      data/src/main/java/com/grkj/data/repository/impl/HardwareRepository.kt
  22. 5 0
      ui-base/src/main/java/com/grkj/ui_base/config/ISCSConfig.kt
  23. 28 34
      ui-base/src/main/java/com/grkj/ui_base/utils/modbus/DockBean.kt
  24. 15 0
      ui-base/src/main/java/com/grkj/ui_base/utils/modbus/ModBusCMDHelper.kt
  25. 60 1
      ui-base/src/main/java/com/grkj/ui_base/utils/modbus/ModBusController.kt
  26. 1 1
      ui-base/src/main/res/values-en/strings.xml
  27. 1 1
      ui-base/src/main/res/values-zh/strings.xml
  28. 1 1
      ui-base/src/main/res/values/strings.xml

+ 4 - 3
app/src/main/java/com/grkj/iscs/features/init/model/DockData.kt

@@ -9,20 +9,21 @@ interface DockData {
     /**
      * 钥匙Dock
      */
-    class KeyDock {
+    class KeyDock : DockData {
         val keyData: MutableList<DockBean.KeyBean> = mutableListOf()
     }
 
     /**
      * 挂锁Dock
      */
-    class LockDock {
+    class LockDock : DockData {
         val lockData: MutableList<DockBean.LockBean> = mutableListOf()
     }
+
     /**
      * 便携Dock
      */
-    class PortableDock {
+    class PortableDock : DockData {
         val deviceData: MutableList<DockBean.DeviceBean> = mutableListOf()
     }
 }

+ 8 - 5
app/src/main/java/com/grkj/iscs/features/init/viewmodel/InitDeviceRegistrationKeyAndLockViewModel.kt

@@ -5,7 +5,6 @@ import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.liveData
 import com.clj.fastble.BleManager
 import com.grkj.data.data.MMKVConstants
-import com.grkj.data.model.dos.IsLockCabinet
 import com.grkj.data.model.dos.IsLockCabinetSlots
 import com.grkj.data.repository.IHardwareRepository
 import com.grkj.ui_base.base.BaseViewModel
@@ -200,7 +199,11 @@ class InitDeviceRegistrationKeyAndLockViewModel @Inject constructor(val hardware
     /**
      * 仓位录入挂起任务
      */
-    private fun deviceInputSlotsSuspend(dockBean: DockBean, startIndex: Int = 0): Int {
+    private fun deviceInputSlotsSuspend(
+        dockIndex: Int,
+        dockBean: DockBean,
+        startIndex: Int = 0
+    ): Int {
         var lastIndex = startIndex
         when (dockBean.type) {
             DeviceConst.DOCK_TYPE_KEY -> {
@@ -211,7 +214,7 @@ class InitDeviceRegistrationKeyAndLockViewModel @Inject constructor(val hardware
                     isLockCabinetSlots.cabinetId = MMKVConstants.KEY_LOCK_CABINET_ID.getMMKVData(0)
                     isLockCabinetSlots.slotType = DeviceConst.DEVICE_TYPE_KEY.toString()
                     isLockCabinetSlots.row = dockBean.row.toString()
-                    isLockCabinetSlots.col = (index + 1).toString()
+                    isLockCabinetSlots.col = (dockIndex * 2 + index + 1).toString()
                     isLockCabinetSlots
                 }
                 hardwareRepository.saveCabinetSlots(slots)
@@ -260,8 +263,8 @@ class InitDeviceRegistrationKeyAndLockViewModel @Inject constructor(val hardware
             val dockList = ModBusController.dockList
             val deviceList = dockList.map { it.deviceList }.flatten()
             var startIndex = 0
-            dockList.forEach {
-                startIndex = deviceInputSlotsSuspend(it, startIndex)
+            dockList.sortedBy { it.type }.forEachIndexed { index, dock ->
+                startIndex = deviceInputSlotsSuspend(index, dock, startIndex)
             }
             val lockDevice = deviceList.filter { it.type == DeviceConst.DEVICE_TYPE_LOCK }
                 .filterIsInstance<DockBean.LockBean>().filter { it.newHardware == true }

+ 58 - 0
app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/SlotsExceptionReportDialog.kt

@@ -0,0 +1,58 @@
+package com.grkj.iscs.features.main.dialog.hardware_manage
+
+import android.content.Context
+import android.view.View
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.DialogSlotsExceptionReportBinding
+import com.kongzue.dialogx.dialogs.CustomDialog
+import com.kongzue.dialogx.dialogs.PopTip
+import com.kongzue.dialogx.interfaces.OnBindView
+
+/**
+ * 仓位异常上报
+ */
+class SlotsExceptionReportDialog(
+    val context: Context,
+    val row: Int,
+    val col: Int,
+    val slotType: Int,
+    val onConfirm: (exceptionReason: String) -> Unit
+) :
+    OnBindView<CustomDialog>(R.layout.dialog_slots_exception_report) {
+    private lateinit var binding: DialogSlotsExceptionReportBinding
+    override fun onBind(dialog: CustomDialog, contentView: View) {
+        binding = DialogSlotsExceptionReportBinding.bind(contentView)
+        binding.hardwareInfo.text = context.getString(
+            com.grkj.ui_base.R.string.hardware_info,
+            "${getDeviceTypeStr(slotType)},${context.getString(com.grkj.ui_base.R.string.number)} 行${row},列${col}"
+        )
+        binding.confirmBtn.setOnClickListener {
+            val exceptionReason = binding.exceptionEt.text?.toString()
+            if (exceptionReason?.isEmpty() == true) {
+                PopTip.tip(com.grkj.ui_base.R.string.please_input_exception_reason)
+                return@setOnClickListener
+            }
+            onConfirm(exceptionReason!!)
+            dialog.dismiss()
+        }
+
+        binding.cancelBtn.setOnClickListener {
+            dialog.dismiss()
+        }
+    }
+
+    private fun getDeviceTypeStr(slotType: Int): String {
+        return when (slotType) {
+            0 -> context.getString(com.grkj.ui_base.R.string.hardware_key)
+            1 -> context.getString(com.grkj.ui_base.R.string.hardware_lock)
+            else -> context.getString(com.grkj.ui_base.R.string.hardware_unknown)
+        }
+    }
+
+    companion object {
+        @JvmStatic
+        fun show(context: Context, row: Int, col: Int, slotType: Int, onConfirm: (String) -> Unit) {
+            CustomDialog.show(SlotsExceptionReportDialog(context, row, col, slotType, onConfirm))
+        }
+    }
+}

+ 1 - 1
app/src/main/java/com/grkj/iscs/features/main/fragment/hardware_manage/HardwareManageHomeFragment.kt

@@ -26,7 +26,7 @@ class HardwareManageHomeFragment : BaseFragment<FragmentHardwareManageHomeBindin
     private var menuData: MutableList<MenuItemEntity> = mutableListOf(
         MenuItemEntity(
             0,
-            R.mipmap.icon_data_manage_menu_user_manage,
+            R.mipmap.dock_no_key,
             RoleFunctionalPermissionsEnum.SLOT_MANAGE.description,
             RoleFunctionalPermissionsEnum.SLOT_MANAGE.functionalPermission
         ),

+ 324 - 105
app/src/main/java/com/grkj/iscs/features/main/fragment/hardware_manage/SlotsManageFragment.kt

@@ -1,6 +1,5 @@
 package com.grkj.iscs.features.main.fragment.hardware_manage
 
-import android.view.Gravity
 import android.view.View
 import androidx.core.view.isVisible
 import androidx.fragment.app.viewModels
@@ -8,13 +7,13 @@ import com.drake.brv.BindingAdapter
 import com.drake.brv.annotaion.DividerOrientation
 import com.drake.brv.utils.dividerSpace
 import com.drake.brv.utils.linear
-import com.drake.brv.utils.models
 import com.drake.brv.utils.setup
 import com.google.android.flexbox.AlignItems
 import com.google.android.flexbox.FlexDirection
 import com.google.android.flexbox.FlexboxLayoutManager
 import com.google.android.flexbox.JustifyContent
 import com.grkj.data.data.MainDomainData
+import com.grkj.data.enums.CommonDictDataEnum
 import com.grkj.data.enums.RoleEnum
 import com.grkj.iscs.R
 import com.grkj.iscs.databinding.FragmentSlotsManageBinding
@@ -23,14 +22,17 @@ import com.grkj.iscs.databinding.ItemDeviceRegistrationLockLayoutBinding
 import com.grkj.iscs.databinding.ItemDeviceSlotManageKeyBinding
 import com.grkj.iscs.databinding.ItemDeviceSlotManageLockBinding
 import com.grkj.iscs.features.init.model.DockData
+import com.grkj.iscs.features.main.dialog.hardware_manage.SlotsExceptionReportDialog
 import com.grkj.iscs.features.main.viewmodel.hardware_manage.SlotsManageViewModel
 import com.grkj.ui_base.base.BaseFragment
+import com.grkj.ui_base.config.ISCSConfig
 import com.grkj.ui_base.dialog.TipDialog
+import com.grkj.ui_base.utils.event.LoadingEvent
 import com.grkj.ui_base.utils.modbus.DeviceConst
 import com.grkj.ui_base.utils.modbus.DockBean
 import com.grkj.ui_base.utils.modbus.ModBusController
-import com.kongzue.dialogx.dialogs.PopMenu
 import com.sik.sikcore.extension.setDebouncedClickListener
+import com.sik.sikcore.thread.ThreadUtils
 import dagger.hilt.android.AndroidEntryPoint
 
 /**
@@ -39,20 +41,7 @@ import dagger.hilt.android.AndroidEntryPoint
 @AndroidEntryPoint
 class SlotsManageFragment : BaseFragment<FragmentSlotsManageBinding>() {
     private val viewModel: SlotsManageViewModel by viewModels()
-    private val slotsLongClickMenu: List<String> by lazy {
-        if (MainDomainData.roleKeys?.contains(RoleEnum.ADMIN.roleKey) == true ||
-            MainDomainData.roleKeys?.contains(RoleEnum.SYSCONFIG.roleKey) == true
-        ) {
-            listOf(
-                getString(com.grkj.ui_base.R.string.exception_report),
-                getString(com.grkj.ui_base.R.string.turn_on),
-                getString(com.grkj.ui_base.R.string.turn_off),
-                getString(R.string.detect_slot)
-            )
-        } else {
-            listOf(getString(com.grkj.ui_base.R.string.exception_report))
-        }
-    }
+    private val dockDataList = mutableListOf<DockData>()
 
     override fun getLayoutId(): Int {
         return R.layout.fragment_slots_manage
@@ -85,67 +74,122 @@ class SlotsManageFragment : BaseFragment<FragmentSlotsManageBinding>() {
                     }
                 }
             }
-        }
+        }.models = dockDataList
+        ModBusController.controlAllKeyChargeDown()
+        viewModel.isDestroy = false
     }
 
     /**
      * 显示长按菜单
      */
-    private fun showLongClickMenu(v: View, deviceBean: DockBean.DeviceBean) {
-        PopMenu.show(v, slotsLongClickMenu)
-            .setAlignGravity(Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)
-            .setOverlayBaseView(false)
-            .setOnMenuItemClickListener { dialog, str, index ->
-                when {
-                    //异常上报
-                    str.toString() == getString(com.grkj.ui_base.R.string.exception_report) -> {}
-                    //开
-                    str.toString() == getString(com.grkj.ui_base.R.string.turn_on) -> {
-                        when (deviceBean) {
-                            is DockBean.KeyBean -> {
-                                ModBusController.controlKeyCharge(
-                                    true, deviceBean.idx,
-                                    ModBusController.dockList.find { it.row == deviceBean.row }?.addr
-                                )
-                            }
+    private fun showLongClickMenu(v: View, deviceBean: DockBean.DeviceBean, modelPosition: Int) {
+        SlotsExceptionReportDialog.show(
+            requireContext(),
+            deviceBean.row,
+            modelPosition + 1,
+            deviceBean.type
+        ) {
+            viewModel.tagSlotsException(deviceBean.row, modelPosition + 1, it)
+                .observe(this) {
+                    TipDialog.showSuccess(
+                        msg = getString(R.string.exception_report_success),
+                        onConfirmClick = {
+                            binding.dockRv.adapter?.notifyDataSetChanged()
+                        },
+                        onCancelClick = {
+                            binding.dockRv.adapter?.notifyDataSetChanged()
+                        })
+                }
+        }
+    }
 
-                            is DockBean.LockBean -> {
-                                ModBusController.controlLockBuckle(
-                                    true,
-                                    ModBusController.dockList.find { it.row == deviceBean.row }?.addr,
-                                    deviceBean.idx,
-                                )
+    /**
+     * 仓位检测
+     */
+    private fun detectSlot(deviceBean: DockBean.DeviceBean) {
+        when (deviceBean) {
+            is DockBean.KeyBean -> {
+                LoadingEvent.sendLoadingEvent(getString(R.string.start_detect_key_slot))
+                ModBusController.readKeyRfidStr(deviceBean.addr, deviceBean.idx) { idx, keyRfid ->
+                    ThreadUtils.runOnMain {
+                        viewModel.checkKeyRfidIsNewDevice(keyRfid)
+                            .observe(this@SlotsManageFragment) {
+                                LoadingEvent.sendLoadingEvent(getString(R.string.check_key_is_new_device))
+                                LoadingEvent.sendLoadingEvent(getString(R.string.start_scan_key_mac))
+                                viewModel.detectKeyMac(deviceBean)
+                                    .observe(this@SlotsManageFragment) { keyMac ->
+                                        LoadingEvent.sendLoadingEvent()
+                                        TipDialog.showInfo(
+                                            msg = getString(R.string.check_new_lock_need_register),
+                                            onConfirmClick = {
+                                                viewModel.registerKeyInfo(keyRfid, keyMac)
+                                                    .observe(this@SlotsManageFragment) {
+                                                        if (it) {
+                                                            TipDialog.showSuccess(getString(R.string.register_success))
+                                                        } else {
+                                                            TipDialog.showError(getString(R.string.register_failed))
+                                                        }
+                                                    }
+                                            })
+                                    }
                             }
-
-                            else -> {}
-                        }
                     }
-                    //关
-                    str.toString() == getString(com.grkj.ui_base.R.string.turn_off) -> {
-                        when (deviceBean) {
-                            is DockBean.KeyBean -> {
-                                ModBusController.controlKeyCharge(
-                                    false, deviceBean.idx,
-                                    ModBusController.dockList.find { it.row == deviceBean.row }?.addr
-                                )
-                            }
 
-                            is DockBean.LockBean -> {
-                                ModBusController.controlLockBuckle(
-                                    false,
-                                    ModBusController.dockList.find { it.row == deviceBean.row }?.addr,
-                                    deviceBean.idx,
-                                )
-                            }
+                }
+            }
 
-                            else -> {}
-                        }
+            is DockBean.LockBean -> {
+                LoadingEvent.sendLoadingEvent(getString(R.string.start_detect_lock_slot))
+                ModBusController.readLockRfidStr(deviceBean.addr, deviceBean.idx) { lockRfid ->
+                    ThreadUtils.runOnMain {
+                        LoadingEvent.sendLoadingEvent(getString(R.string.check_lock_is_new_device))
+                        viewModel.checkLockRfidIsNewDevice(lockRfid)
+                            .observe(this@SlotsManageFragment) {
+                                LoadingEvent.sendLoadingEvent()
+                                TipDialog.showInfo(
+                                    msg = getString(R.string.check_new_lock_need_register),
+                                    onConfirmClick = {
+                                        viewModel.registerLockInfo(lockRfid)
+                                            .observe(this@SlotsManageFragment) {
+                                                if (it) {
+                                                    TipDialog.showSuccess(getString(R.string.register_success))
+                                                } else {
+                                                    TipDialog.showError(getString(R.string.register_failed))
+                                                }
+                                            }
+                                    })
+                            }
                     }
-                    //检查仓位
-                    str.toString() == getString(R.string.detect_slot) -> {}
                 }
-                false
             }
+
+            else -> {}
+        }
+    }
+
+    /**
+     * 仓位开关
+     */
+    private fun slotSwitch(isOpen: Boolean, deviceBean: DockBean.DeviceBean) {
+        when (deviceBean) {
+            is DockBean.KeyBean -> {
+                ModBusController.controlKeyBuckle(isOpen, deviceBean.idx, deviceBean.addr)
+                ModBusController.controlKeyCharge(
+                    isOpen, deviceBean.idx,
+                    deviceBean.addr
+                )
+            }
+
+            is DockBean.LockBean -> {
+                ModBusController.controlLockBuckle(
+                    isOpen,
+                    deviceBean.addr,
+                    deviceBean.idx,
+                )
+            }
+
+            else -> {}
+        }
     }
 
     private fun BindingAdapter.BindingViewHolder.onKeyDockRVListBinding(
@@ -163,24 +207,61 @@ class SlotsManageFragment : BaseFragment<FragmentSlotsManageBinding>() {
                 val itemKeyBinding = getBinding<ItemDeviceSlotManageKeyBinding>()
                 val itemKey = getModel<DockBean.KeyBean>()
                 itemKeyBinding.exceptionIv.isVisible =
-                    viewModel.exceptionKeyData.find { it.keyNfc == itemKey.rfid } != null
+                    viewModel.exceptionKeyData.find { it.keyNfc == itemKey.rfid } != null ||
+                            viewModel.exceptionSlotsData.find {
+                                it.row?.toInt() == itemKey.row && it.col?.toInt() == (modelPosition + 1)
+                            }?.status == CommonDictDataEnum.SLOT_STATUS.commonDictRes.find { it.dictLabel == "异常" }?.dictValue
                 itemKeyBinding.ivKey.isSelected = itemKey.isExist
+                itemKeyBinding.switchLayout.isVisible =
+                    MainDomainData.roleKeys?.contains(RoleEnum.ADMIN.roleKey) == true ||
+                            MainDomainData.roleKeys?.contains(RoleEnum.SYSCONFIG.roleKey) == true
+                itemKeyBinding.detect.isVisible =
+                    MainDomainData.roleKeys?.contains(RoleEnum.ADMIN.roleKey) == true ||
+                            MainDomainData.roleKeys?.contains(RoleEnum.SYSCONFIG.roleKey) == true
+                itemKeyBinding.tvTurnOn.setDebouncedClickListener {
+                    slotSwitch(true, itemKey)
+                }
+                itemKeyBinding.tvTurnOff.setDebouncedClickListener {
+                    slotSwitch(false, itemKey)
+                }
+                itemKeyBinding.detect.setDebouncedClickListener {
+                    detectSlot(itemKey)
+                }
                 itemKeyBinding.ivKey.setOnLongClickListener { v ->
-                    showLongClickMenu(v, itemKey)
-                    false
+                    showLongClickMenu(v, itemKey, modelPosition)
+                    true
                 }
                 itemKeyBinding.exceptionIv.setOnClickListener {
                     TipDialog.showError(
                         msg = viewModel.exceptionKeyData.find { it.keyNfc == itemKey.rfid }?.remark
-                            ?: ""
+                            ?: viewModel.exceptionSlotsData.find {
+                                it.row?.toInt() == itemKey.row && it.col?.toInt() == (modelPosition + 1)
+                            }?.remark ?: ""
                     )
                 }
                 itemKeyBinding.exceptionIv.setOnLongClickListener {
-                    viewModel.removeSlotsException(itemKey.row, itemKey.idx)
-                        .observe(this@SlotsManageFragment) {
-                            adapter?.notifyDataSetChanged()
-                        }
-                    false
+                    if (MainDomainData.roleKeys?.contains(RoleEnum.ADMIN.roleKey) == true ||
+                        MainDomainData.roleKeys?.contains(RoleEnum.SYSCONFIG.roleKey) == true
+                    ) {
+                        TipDialog.showInfo(
+                            getString(R.string.do_you_want_to_remove_exception),
+                            onConfirmClick = {
+                                if (viewModel.exceptionSlotsData.any {
+                                        it.row?.toInt() == itemKey.row && it.col?.toInt() == (modelPosition + 1)
+                                    }) {
+                                    viewModel.removeSlotsException(itemKey.row, modelPosition + 1)
+                                        .observe(this@SlotsManageFragment) {
+                                            binding.dockRv.adapter?.notifyDataSetChanged()
+                                        }
+                                } else {
+                                    viewModel.removeDeviceException(itemKey.type, itemKey.rfid)
+                                        .observe(this@SlotsManageFragment) {
+                                            binding.dockRv.adapter?.notifyDataSetChanged()
+                                        }
+                                }
+                            })
+                    }
+                    true
                 }
             }
         }.models = keyDock.keyData
@@ -202,24 +283,61 @@ class SlotsManageFragment : BaseFragment<FragmentSlotsManageBinding>() {
                 val itemLockBinding = getBinding<ItemDeviceSlotManageLockBinding>()
                 val itemLock = getModel<DockBean.LockBean>()
                 itemLockBinding.exceptionIv.isVisible =
-                    viewModel.exceptionLockData.find { it.lockNfc == itemLock.rfid } != null
+                    viewModel.exceptionLockData.find { it.lockNfc == itemLock.rfid } != null ||
+                            viewModel.exceptionSlotsData.find {
+                                it.row?.toInt() == itemLock.row && it.col?.toInt() == (modelPosition + 1)
+                            }?.status == CommonDictDataEnum.SLOT_STATUS.commonDictRes.find { it.dictLabel == "异常" }?.dictValue
                 itemLockBinding.root.isSelected = itemLock.isExist
+                itemLockBinding.switchLayout.isVisible =
+                    MainDomainData.roleKeys?.contains(RoleEnum.ADMIN.roleKey) == true ||
+                            MainDomainData.roleKeys?.contains(RoleEnum.SYSCONFIG.roleKey) == true
+                itemLockBinding.detect.isVisible =
+                    MainDomainData.roleKeys?.contains(RoleEnum.ADMIN.roleKey) == true ||
+                            MainDomainData.roleKeys?.contains(RoleEnum.SYSCONFIG.roleKey) == true
+                itemLockBinding.tvTurnOn.setDebouncedClickListener {
+                    slotSwitch(true, itemLock)
+                }
+                itemLockBinding.tvTurnOff.setDebouncedClickListener {
+                    slotSwitch(false, itemLock)
+                }
+                itemLockBinding.detect.setDebouncedClickListener {
+                    detectSlot(itemLock)
+                }
                 itemLockBinding.root.setOnLongClickListener { v ->
-                    showLongClickMenu(v, itemLock)
-                    false
+                    showLongClickMenu(v, itemLock, modelPosition)
+                    true
                 }
                 itemLockBinding.exceptionIv.setOnClickListener {
                     TipDialog.showError(
                         msg = viewModel.exceptionLockData.find { it.lockNfc == itemLock.rfid }?.remark
-                            ?: ""
+                            ?: viewModel.exceptionSlotsData.find {
+                                it.row?.toInt() == itemLock.row && it.col?.toInt() == (modelPosition + 1)
+                            }?.remark ?: ""
                     )
                 }
                 itemLockBinding.exceptionIv.setOnLongClickListener {
-                    viewModel.removeSlotsException(itemLock.row, itemLock.idx)
-                        .observe(this@SlotsManageFragment) {
-                            adapter?.notifyDataSetChanged()
-                        }
-                    false
+                    if (MainDomainData.roleKeys?.contains(RoleEnum.ADMIN.roleKey) == true ||
+                        MainDomainData.roleKeys?.contains(RoleEnum.SYSCONFIG.roleKey) == true
+                    ) {
+                        TipDialog.showInfo(
+                            getString(R.string.do_you_want_to_remove_exception),
+                            onConfirmClick = {
+                                if (viewModel.exceptionSlotsData.any {
+                                        it.row?.toInt() == itemLock.row && it.col?.toInt() == (modelPosition + 1)
+                                    }) {
+                                    viewModel.removeSlotsException(itemLock.row, modelPosition + 1)
+                                        .observe(this@SlotsManageFragment) {
+                                            binding.dockRv.adapter?.notifyDataSetChanged()
+                                        }
+                                } else {
+                                    viewModel.removeDeviceException(itemLock.type, itemLock.rfid)
+                                        .observe(this@SlotsManageFragment) {
+                                            binding.dockRv.adapter?.notifyDataSetChanged()
+                                        }
+                                }
+                            })
+                    }
+                    true
                 }
             }
         }.models = lockDock.lockData
@@ -242,48 +360,130 @@ class SlotsManageFragment : BaseFragment<FragmentSlotsManageBinding>() {
                     is DockBean.KeyBean -> {
                         val itemKeyBinding = getBinding<ItemDeviceSlotManageKeyBinding>()
                         itemKeyBinding.exceptionIv.isVisible =
-                            viewModel.exceptionKeyData.find { it.keyNfc == itemPortable.rfid } != null
+                            viewModel.exceptionKeyData.find { it.keyNfc == itemPortable.rfid } != null ||
+                                    viewModel.exceptionSlotsData.find {
+                                        it.row?.toInt() == itemPortable.row && it.col?.toInt() == (modelPosition + 1)
+                                    }?.status == CommonDictDataEnum.SLOT_STATUS.commonDictRes.find { it.dictLabel == "异常" }?.dictValue
                         itemKeyBinding.ivKey.isSelected = itemPortable.isExist
+                        itemKeyBinding.switchLayout.isVisible =
+                            MainDomainData.roleKeys?.contains(RoleEnum.ADMIN.roleKey) == true ||
+                                    MainDomainData.roleKeys?.contains(RoleEnum.SYSCONFIG.roleKey) == true
+                        itemKeyBinding.detect.isVisible =
+                            MainDomainData.roleKeys?.contains(RoleEnum.ADMIN.roleKey) == true ||
+                                    MainDomainData.roleKeys?.contains(RoleEnum.SYSCONFIG.roleKey) == true
+                        itemKeyBinding.tvTurnOn.setDebouncedClickListener {
+                            slotSwitch(true, itemPortable)
+                        }
+                        itemKeyBinding.tvTurnOff.setDebouncedClickListener {
+                            slotSwitch(false, itemPortable)
+                        }
+                        itemKeyBinding.detect.setDebouncedClickListener {
+                            detectSlot(itemPortable)
+                        }
                         itemKeyBinding.ivKey.setOnLongClickListener { v ->
-                            showLongClickMenu(v, itemPortable)
-                            false
+                            showLongClickMenu(v, itemPortable, modelPosition)
+                            true
                         }
                         itemKeyBinding.exceptionIv.setOnClickListener {
                             TipDialog.showError(
                                 msg = viewModel.exceptionKeyData.find { it.keyNfc == itemPortable.rfid }?.remark
-                                    ?: ""
+                                    ?: viewModel.exceptionSlotsData.find {
+                                        it.row?.toInt() == itemPortable.row && it.col?.toInt() == (modelPosition + 1)
+                                    }?.remark ?: ""
                             )
                         }
                         itemKeyBinding.exceptionIv.setOnLongClickListener {
-                            viewModel.removeSlotsException(itemPortable.row, itemPortable.idx)
-                                .observe(this@SlotsManageFragment) {
-                                    adapter?.notifyDataSetChanged()
-                                }
-                            false
+                            if (MainDomainData.roleKeys?.contains(RoleEnum.ADMIN.roleKey) == true ||
+                                MainDomainData.roleKeys?.contains(RoleEnum.SYSCONFIG.roleKey) == true
+                            ) {
+                                TipDialog.showInfo(
+                                    getString(R.string.do_you_want_to_remove_exception),
+                                    onConfirmClick = {
+                                        if (viewModel.exceptionSlotsData.any {
+                                                it.row?.toInt() == itemPortable.row && it.col?.toInt() == (modelPosition + 1)
+                                            }) {
+                                            viewModel.removeSlotsException(
+                                                itemPortable.row,
+                                                modelPosition + 1
+                                            ).observe(this@SlotsManageFragment) {
+                                                binding.dockRv.adapter?.notifyDataSetChanged()
+                                            }
+                                        } else {
+                                            viewModel.removeDeviceException(
+                                                itemPortable.type,
+                                                itemPortable.rfid
+                                            ).observe(this@SlotsManageFragment) {
+                                                binding.dockRv.adapter?.notifyDataSetChanged()
+                                            }
+                                        }
+                                    })
+                            }
+                            true
                         }
                     }
 
                     is DockBean.LockBean -> {
                         val itemLockBinding = getBinding<ItemDeviceSlotManageLockBinding>()
                         itemLockBinding.exceptionIv.isVisible =
-                            viewModel.exceptionLockData.find { it.lockNfc == itemPortable.rfid } != null
+                            viewModel.exceptionLockData.find { it.lockNfc == itemPortable.rfid } != null ||
+                                    viewModel.exceptionSlotsData.find {
+                                        it.row?.toInt() == itemPortable.row && it.col?.toInt() == (modelPosition + 1)
+                                    }?.status == CommonDictDataEnum.SLOT_STATUS.commonDictRes.find { it.dictLabel == "异常" }?.dictValue
                         itemLockBinding.root.isSelected = itemPortable.isExist
+                        itemLockBinding.switchLayout.isVisible =
+                            MainDomainData.roleKeys?.contains(RoleEnum.ADMIN.roleKey) == true ||
+                                    MainDomainData.roleKeys?.contains(RoleEnum.SYSCONFIG.roleKey) == true
+                        itemLockBinding.detect.isVisible =
+                            MainDomainData.roleKeys?.contains(RoleEnum.ADMIN.roleKey) == true ||
+                                    MainDomainData.roleKeys?.contains(RoleEnum.SYSCONFIG.roleKey) == true
+                        itemLockBinding.tvTurnOn.setDebouncedClickListener {
+                            slotSwitch(true, itemPortable)
+                        }
+                        itemLockBinding.tvTurnOff.setDebouncedClickListener {
+                            slotSwitch(false, itemPortable)
+                        }
+                        itemLockBinding.detect.setDebouncedClickListener {
+                            detectSlot(itemPortable)
+                        }
                         itemLockBinding.root.setOnLongClickListener { v ->
-                            showLongClickMenu(v, itemPortable)
-                            false
+                            showLongClickMenu(v, itemPortable, modelPosition)
+                            true
                         }
                         itemLockBinding.exceptionIv.setOnClickListener {
                             TipDialog.showError(
                                 msg = viewModel.exceptionLockData.find { it.lockNfc == itemPortable.rfid }?.remark
-                                    ?: ""
+                                    ?: viewModel.exceptionSlotsData.find {
+                                        it.row?.toInt() == itemPortable.row && it.col?.toInt() == (modelPosition + 1)
+                                    }?.remark ?: ""
                             )
                         }
                         itemLockBinding.exceptionIv.setOnLongClickListener {
-                            viewModel.removeSlotsException(itemPortable.row, itemPortable.idx)
-                                .observe(this@SlotsManageFragment) {
-                                    adapter?.notifyDataSetChanged()
-                                }
-                            false
+                            if (MainDomainData.roleKeys?.contains(RoleEnum.ADMIN.roleKey) == true ||
+                                MainDomainData.roleKeys?.contains(RoleEnum.SYSCONFIG.roleKey) == true
+                            ) {
+                                TipDialog.showInfo(
+                                    getString(R.string.do_you_want_to_remove_exception),
+                                    onConfirmClick = {
+                                        if (viewModel.exceptionSlotsData.any {
+                                                it.row?.toInt() == itemPortable.row && it.col?.toInt() == (modelPosition + 1)
+                                            }) {
+                                            viewModel.removeSlotsException(
+                                                itemPortable.row,
+                                                modelPosition + 1
+                                            ).observe(this@SlotsManageFragment) {
+                                                binding.dockRv.adapter?.notifyDataSetChanged()
+                                            }
+                                        } else {
+                                            viewModel.removeDeviceException(
+                                                itemPortable.type,
+                                                itemPortable.rfid
+                                            ).observe(this@SlotsManageFragment) {
+                                                binding.dockRv.adapter?.notifyDataSetChanged()
+                                            }
+                                        }
+                                    })
+                            }
+                            true
                         }
                     }
                 }
@@ -293,14 +493,26 @@ class SlotsManageFragment : BaseFragment<FragmentSlotsManageBinding>() {
 
     override fun initData() {
         super.initData()
-        viewModel.getExceptionData().observe(this) {
-            binding.dockRv.adapter?.notifyDataSetChanged()
-        }
+        getExceptionData()
         viewModel.registerStatusListener {
             getData()
         }
     }
 
+    /**
+     * 获取异常数据
+     */
+    private fun getExceptionData() {
+        viewModel.getExceptionData().observe(this) {
+            binding.dockRv.adapter?.notifyDataSetChanged()
+            if (ISCSConfig.isNetVersion) {
+                binding.root.postDelayed({
+                    getExceptionData()
+                }, 5000)
+            }
+        }
+    }
+
     /**
      * 获取并加载数据
      */
@@ -329,7 +541,14 @@ class SlotsManageFragment : BaseFragment<FragmentSlotsManageBinding>() {
                     deviceData.addAll(it.deviceList.toList())
                 }
             }
-        binding.dockRv.models = newKeyDockList + portableDock + lockDock
+        dockDataList.clear()
+        dockDataList.addAll(newKeyDockList + portableDock + lockDock)
+        binding.dockRv.adapter?.notifyDataSetChanged()
+    }
+
+    override fun onDestroyView() {
+        viewModel.isDestroy = true
+        super.onDestroyView()
     }
 
 }

+ 156 - 4
app/src/main/java/com/grkj/iscs/features/main/viewmodel/hardware_manage/SlotsManageViewModel.kt

@@ -7,10 +7,22 @@ import com.grkj.data.model.dos.IsLock
 import com.grkj.data.model.dos.IsLockCabinetSlots
 import com.grkj.data.repository.IHardwareRepository
 import com.grkj.ui_base.base.BaseViewModel
+import com.grkj.ui_base.business.BleBusinessManager
 import com.grkj.ui_base.business.ModbusBusinessManager
+import com.grkj.ui_base.utils.ble.BleConnectionManager
+import com.grkj.ui_base.utils.modbus.DeviceConst
+import com.grkj.ui_base.utils.modbus.DockBean
+import com.grkj.ui_base.utils.modbus.ModBusController
+import com.sik.sikcore.extension.toJson
+import com.sik.sikcore.thread.ThreadUtils
 import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
 import javax.inject.Inject
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
 
 /**
  * 仓位管理数据模型
@@ -34,6 +46,11 @@ class SlotsManageViewModel @Inject constructor(
      */
     var exceptionKeyData: List<IsKey> = listOf()
 
+    /**
+     * 是否销毁
+     */
+    var isDestroy: Boolean = false
+
     /**
      * 注册监听
      */
@@ -58,6 +75,7 @@ class SlotsManageViewModel @Inject constructor(
             exceptionSlotsData = hardwareRepository.getExceptionSlots()
             exceptionLockData = hardwareRepository.getExceptionLock()
             exceptionKeyData = hardwareRepository.getExceptionKey()
+            logger.info("异常信息:仓位:${exceptionSlotsData.toJson()},挂锁:${exceptionLockData.toJson()},钥匙:${exceptionKeyData.toJson()}")
             emit(true)
         }
     }
@@ -65,9 +83,9 @@ class SlotsManageViewModel @Inject constructor(
     /**
      * 移除锁仓异常
      */
-    fun removeSlotsException(row: Int,col: Int): LiveData<Boolean> {
+    fun removeSlotsException(row: Int, col: Int): LiveData<Boolean> {
         return liveData(Dispatchers.IO) {
-            hardwareRepository.removeSlotsException(row,col)
+            hardwareRepository.removeSlotsException(row, col)
             exceptionSlotsData = hardwareRepository.getExceptionSlots()
             emit(true)
         }
@@ -76,11 +94,145 @@ class SlotsManageViewModel @Inject constructor(
     /**
      * 标记锁仓异常
      */
-    fun tagSlotsException(row: Int,col: Int,remark: String): LiveData<Boolean> {
+    fun tagSlotsException(row: Int, col: Int, remark: String): LiveData<Boolean> {
         return liveData(Dispatchers.IO) {
-            hardwareRepository.tagSlotsException(row,col,remark)
+            hardwareRepository.tagSlotsException(row, col, remark)
             exceptionSlotsData = hardwareRepository.getExceptionSlots()
             emit(true)
         }
     }
+
+    /**
+     * 移除硬件异常
+     */
+    fun removeDeviceException(deviceType: Int, deviceRfid: String?): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            when (deviceType) {
+                DeviceConst.DEVICE_TYPE_KEY -> {
+                    hardwareRepository.removeKeyException(deviceRfid)
+                    exceptionKeyData = hardwareRepository.getExceptionKey()
+                }
+
+                DeviceConst.DEVICE_TYPE_LOCK -> {
+                    hardwareRepository.removeLockException(deviceRfid)
+                    exceptionLockData = hardwareRepository.getExceptionLock()
+                }
+            }
+        }
+    }
+
+    /**
+     * 标记硬件异常
+     */
+    fun tagDeviceException(deviceType: Int, deviceRfid: String?, remark: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            when (deviceType) {
+                DeviceConst.DEVICE_TYPE_KEY -> {
+                    hardwareRepository.tagKeyException(deviceRfid, remark)
+                }
+
+                DeviceConst.DEVICE_TYPE_LOCK -> {
+                    hardwareRepository.tagLockException(deviceRfid, remark)
+                }
+            }
+        }
+    }
+
+    /**
+     * 根据锁RFID检查是否是新设备
+     */
+    fun checkLockRfidIsNewDevice(lockRfid: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            emit(hardwareRepository.getLockInfo(lockRfid) == null)
+        }
+    }
+
+    /**
+     * 注册挂锁
+     */
+    fun registerLockInfo(lockRfid: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            hardwareRepository.saveLockInfo(lockRfid)
+            emit(true)
+        }
+    }
+
+    /**
+     * 注册钥匙
+     */
+    fun registerKeyInfo(keyRfid: String, keyMac: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            hardwareRepository.saveKeyInfo(keyRfid, keyMac)
+            emit(true)
+        }
+    }
+
+    /**
+     * 根据锁RFID检查是否是新设备
+     */
+    fun checkKeyRfidIsNewDevice(keyRfid: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            emit(hardwareRepository.getKeyInfo(keyRfid) == null)
+        }
+    }
+
+    /**
+     * 检查钥匙mac
+     */
+    fun detectKeyMac(keyBean: DockBean.KeyBean): LiveData<String> {
+        return liveData(Dispatchers.IO) {
+            emit(detectNewKeyMac(keyBean))
+        }
+    }
+
+    /**
+     * 检测新钥匙mac地址
+     */
+    private suspend fun detectNewKeyMac(keyBean: DockBean.KeyBean): String {
+        return suspendCancellableCoroutine<String> { cont ->
+            val existsKeyMac =
+                hardwareRepository.getAllKeyInfo().mapNotNull { it.macAddress }.toList()
+            logger.info("设备检测-关闭充电:${keyBean.addr},${keyBean.idx}")
+            ModBusController.controlKeyCharge(false, keyBean.idx, keyBean.addr) {
+                ThreadUtils.runOnIO {
+                    delay(800)
+                    logger.info("设备检测-打开充电:${keyBean.addr},${keyBean.idx}")
+                    ModBusController.controlKeyLockAndCharge(true, keyBean.idx, keyBean.addr) {
+                        ThreadUtils.runOnIO {
+                            delay(3000)
+                            logger.info("设备检测-开始扫描在线蓝牙Mac")
+                            withContext(Dispatchers.Main) {
+                                BleConnectionManager.scanOnlineKeyLockMac(existsKeyMac) { mac ->
+                                    logger.info(
+                                        "设备检测-在线的蓝牙设备:${keyBean.rfid},${mac}"
+                                    )
+                                    if (isDestroy) {
+                                        logger.info(
+                                            "设备检测-界面已销毁"
+                                        )
+                                        return@scanOnlineKeyLockMac
+                                    }
+                                    if (mac == null) {
+                                        logger.info(
+                                            "设备检测-设备mac空:${keyBean.rfid}"
+                                        )
+                                        return@scanOnlineKeyLockMac
+                                    }
+                                    logger.info(
+                                        "设备检测-没有使用过的mac:${keyBean.rfid},${
+                                            mac
+                                        }"
+                                    )
+                                    keyBean.mac = mac
+                                    if (cont.isActive) {
+                                        cont.resume(mac)
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
 }

+ 12 - 0
app/src/main/res/drawable/selectable_input_text_bg.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape android:shape="rectangle">
+            <solid android:color="@color/white" />
+            <corners android:radius="@dimen/selectable_input_radius" />
+            <stroke
+                android:width="@dimen/selectable_input_stroke"
+                android:color="@color/main_color" />
+        </shape>
+    </item>
+</layer-list>

+ 103 - 0
app/src/main/res/layout/dialog_slots_exception_report.xml

@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <RelativeLayout
+        android:layout_width="@dimen/slots_exception_report_width"
+        android:layout_height="wrap_content"
+        android:background="@drawable/common_card_bg">
+
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/tip_dialog_header_height"
+            android:background="@color/common_tip_dialog_info"
+            android:gravity="center_vertical"
+            android:paddingHorizontal="@dimen/common_spacing"
+            android:text="@string/slots_exception_report"
+            android:textColor="@color/white"
+            android:textSize="@dimen/common_btn_text_size" />
+
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/content_layout"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/slots_exception_report_dialog_content_height"
+            android:layout_below="@+id/title"
+            android:layout_marginVertical="@dimen/common_spacing"
+            android:padding="@dimen/common_spacing">
+
+            <TextView
+                android:id="@+id/hardware_info"
+                style="@style/CommonTextView"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginHorizontal="@dimen/common_spacing"
+                android:layout_marginTop="@dimen/common_spacing"
+                android:textColor="@color/black"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                tools:text="@string/hardware_info" />
+
+            <EditText
+                android:id="@+id/exception_et"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_below="@+id/hardware_info"
+                android:layout_centerInParent="true"
+                android:layout_marginHorizontal="@dimen/common_spacing"
+                android:layout_marginVertical="@dimen/common_spacing"
+                android:background="@drawable/selectable_input_text_bg"
+                android:gravity="left|top"
+                android:hint="@string/please_input_exception_reason"
+                android:padding="@dimen/selectable_input_edit_padding"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/hardware_info" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <LinearLayout
+            android:id="@+id/btn_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_below="@+id/content_layout"
+            android:layout_marginRight="@dimen/common_spacing"
+            android:layout_marginBottom="@dimen/common_spacing"
+            android:divider="@drawable/common_divider_normal_space_horizontal"
+            android:gravity="right"
+            android:orientation="horizontal"
+            android:showDividers="middle">
+
+            <TextView
+                android:id="@+id/confirm_btn"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@drawable/common_btn_confirm"
+                android:drawableLeft="@mipmap/icon_confirm"
+                android:drawablePadding="@dimen/common_spacing"
+                android:gravity="center"
+                android:minHeight="@dimen/common_btn_height"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/confirm"
+                android:textColor="@color/white"
+                android:textSize="@dimen/common_btn_text_size" />
+
+            <TextView
+                android:id="@+id/cancel_btn"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@drawable/common_btn_cancel"
+                android:drawableLeft="@mipmap/icon_cancel"
+                android:drawablePadding="@dimen/common_spacing"
+                android:gravity="center"
+                android:minWidth="@dimen/tip_dialog_btn_width"
+                android:minHeight="@dimen/common_btn_height"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/cancel"
+                android:textColor="@color/white"
+                android:textSize="@dimen/common_btn_text_size" />
+        </LinearLayout>
+    </RelativeLayout>
+</layout>

+ 1 - 1
app/src/main/res/layout/item_device_registration_key.xml

@@ -3,7 +3,7 @@
     xmlns:tools="http://schemas.android.com/tools">
 
     <RelativeLayout
-        android:layout_width="match_parent"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:gravity="center">
 

+ 1 - 1
app/src/main/res/layout/item_device_registration_lock.xml

@@ -3,7 +3,7 @@
     xmlns:tools="http://schemas.android.com/tools">
 
     <RelativeLayout
-        android:layout_width="match_parent"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:gravity="center"
         android:orientation="horizontal">

+ 55 - 2
app/src/main/res/layout/item_device_slot_manage_key.xml

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <layout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    xmlns:flex="http://schemas.android.com/apk/res-auto">
+    xmlns:flex="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
 
     <RelativeLayout
         android:layout_width="wrap_content"
@@ -32,5 +32,58 @@
             android:layout_alignBottom="@+id/iv_key"
             android:src="@mipmap/icon_exception"
             android:visibility="gone" />
+
+        <LinearLayout
+            android:id="@+id/switch_layout"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@+id/iv_key"
+            android:layout_alignLeft="@+id/iv_key"
+            android:layout_alignRight="@+id/iv_key"
+            android:gravity="center_horizontal"
+            android:orientation="horizontal"
+            android:padding="2dp"
+            android:visibility="gone">
+
+            <TextView
+                android:id="@+id/tv_turn_on"
+                style="@style/CommonTextView"
+                android:layout_marginRight="@dimen/common_spacing_small"
+                android:layout_weight="1"
+                android:background="@color/common_btn_red_bg"
+                android:padding="2dp"
+                android:text="@string/turn_on" />
+
+            <TextView
+                android:id="@+id/tv_turn_off"
+                style="@style/CommonTextView"
+                android:layout_weight="1"
+                android:background="@color/main_color_dark"
+                android:padding="2dp"
+                android:text="@string/turn_off" />
+
+            <TextView
+                android:id="@+id/tv_read"
+                style="@style/CommonTextView"
+                android:layout_marginLeft="@dimen/common_spacing_small"
+                android:layout_weight="1"
+                android:background="@color/main_color_dark"
+                android:padding="2dp"
+                android:text="@string/turn_read"
+                android:visibility="gone" />
+        </LinearLayout>
+
+        <TextView
+            android:id="@+id/detect"
+            style="@style/CommonTextView"
+            android:layout_below="@+id/switch_layout"
+            android:layout_alignLeft="@+id/switch_layout"
+            android:layout_alignRight="@+id/switch_layout"
+            android:layout_marginTop="@dimen/common_spacing"
+            android:layout_weight="1"
+            android:background="@color/common_btn_red_bg"
+            android:padding="2dp"
+            android:text="@string/detect_slot"
+            android:visibility="gone" />
     </RelativeLayout>
 </layout>

+ 53 - 0
app/src/main/res/layout/item_device_slot_manage_lock.xml

@@ -11,6 +11,7 @@
             android:id="@+id/root"
             android:layout_width="@dimen/init_lock_iv_width"
             android:layout_height="@dimen/init_lock_iv_height"
+            android:layout_centerHorizontal="true"
             android:background="@drawable/dock_lock_selector" />
 
         <ImageView
@@ -21,8 +22,60 @@
             android:layout_alignTop="@+id/root"
             android:layout_alignRight="@+id/root"
             android:layout_alignBottom="@+id/root"
+            android:layout_centerHorizontal="true"
             android:src="@mipmap/icon_exception"
             android:visibility="gone" />
+
+        <LinearLayout
+            android:id="@+id/switch_layout"
+            android:layout_width="150dp"
+            android:layout_height="wrap_content"
+            android:layout_below="@+id/root"
+            android:gravity="center_horizontal"
+            android:orientation="horizontal"
+            android:padding="2dp"
+            android:visibility="gone">
+
+            <TextView
+                android:id="@+id/tv_turn_on"
+                style="@style/CommonTextView"
+                android:layout_marginRight="@dimen/common_spacing_small"
+                android:layout_weight="1"
+                android:background="@color/common_btn_red_bg"
+                android:padding="2dp"
+                android:text="@string/turn_on" />
+
+            <TextView
+                android:id="@+id/tv_turn_off"
+                style="@style/CommonTextView"
+                android:layout_weight="1"
+                android:background="@color/main_color_dark"
+                android:padding="2dp"
+                android:text="@string/turn_off" />
+
+            <TextView
+                android:id="@+id/tv_read"
+                style="@style/CommonTextView"
+                android:layout_marginLeft="@dimen/common_spacing_small"
+                android:layout_weight="1"
+                android:background="@color/main_color_dark"
+                android:padding="2dp"
+                android:text="@string/turn_read"
+                android:visibility="gone" />
+        </LinearLayout>
+
+        <TextView
+            android:id="@+id/detect"
+            style="@style/CommonTextView"
+            android:layout_below="@+id/switch_layout"
+            android:layout_alignLeft="@+id/switch_layout"
+            android:layout_alignRight="@+id/switch_layout"
+            android:layout_marginTop="@dimen/common_spacing"
+            android:layout_weight="1"
+            android:background="@color/common_btn_red_bg"
+            android:padding="2dp"
+            android:text="@string/detect_slot"
+            android:visibility="gone" />
     </RelativeLayout>
 
 </layout>

+ 11 - 0
app/src/main/res/values-en/strings.xml

@@ -398,5 +398,16 @@
     <string name="job_card_scan_tip">Please read the card on the card reader</string>
     <string name="slots_manage_title">Slots manage</string>
     <string name="detect_slot">Detect slot</string>
+    <string name="slots_exception_report">Slots exception report</string>
+    <string name="exception_report_success">Exception report success</string>
+    <string name="check_new_lock_need_register">Detected a new padlock, do you want to register it</string>
+    <string name="register_success">Register success</string>
+    <string name="register_failed">Register failed</string>
+    <string name="start_detect_key_slot">Start checking the key compartment</string>
+    <string name="start_detect_lock_slot">Start detecting padlock compartments</string>
+    <string name="check_lock_is_new_device">Check if the padlock is a new hardware</string>
+    <string name="check_key_is_new_device">Check if the key is new hardware</string>
+    <string name="start_scan_key_mac">Start scanning key information</string>
+    <string name="do_you_want_to_remove_exception">Are you sure to remove and modify the exception</string>
 
 </resources>

+ 2 - 0
app/src/main/res/values-land/dimens.xml

@@ -97,4 +97,6 @@
     <dimen name="icon_add_point_or_member_size">60dp</dimen>
     <dimen name="job_execute_colock_size">59dp</dimen>
     <dimen name="job_execute_tab_icon_size">51dp</dimen>
+    <dimen name="slots_exception_report_width">850dp</dimen>
+    <dimen name="slots_exception_report_dialog_content_height">340dp</dimen>
 </resources>

+ 11 - 0
app/src/main/res/values-zh/strings.xml

@@ -398,5 +398,16 @@
     <string name="job_card_scan_tip">请在读卡器上读卡</string>
     <string name="slots_manage_title">仓位管理</string>
     <string name="detect_slot">检测仓位</string>
+    <string name="slots_exception_report">仓位异常上报</string>
+    <string name="exception_report_success">异常上报成功</string>
+    <string name="check_new_lock_need_register">检测到新挂锁,是否注册</string>
+    <string name="register_success">注册成功</string>
+    <string name="register_failed">注册失败</string>
+    <string name="start_detect_key_slot">开始检测钥匙仓位</string>
+    <string name="start_detect_lock_slot">开始检测挂锁仓位</string>
+    <string name="check_lock_is_new_device">检查挂锁是否为新硬件</string>
+    <string name="check_key_is_new_device">检查钥匙是否为新硬件</string>
+    <string name="start_scan_key_mac">开始扫描钥匙信息</string>
+    <string name="do_you_want_to_remove_exception">是否确认移除改异常</string>
 
 </resources>

+ 2 - 0
app/src/main/res/values/dimens.xml

@@ -97,4 +97,6 @@
     <dimen name="icon_add_point_or_member_size">60dp</dimen>
     <dimen name="job_execute_colock_size">35dp</dimen>
     <dimen name="job_execute_tab_icon_size">30dp</dimen>
+    <dimen name="slots_exception_report_width">500dp</dimen>
+    <dimen name="slots_exception_report_dialog_content_height">200dp</dimen>
 </resources>

+ 11 - 0
app/src/main/res/values/strings.xml

@@ -401,5 +401,16 @@
     <string name="job_card_scan_tip">请在读卡器上读卡</string>
     <string name="slots_manage_title">仓位管理</string>
     <string name="detect_slot">检测仓位</string>
+    <string name="slots_exception_report">仓位异常上报</string>
+    <string name="exception_report_success">异常上报成功</string>
+    <string name="check_new_lock_need_register">检测到新挂锁,是否注册</string>
+    <string name="register_success">注册成功</string>
+    <string name="register_failed">注册失败</string>
+    <string name="start_detect_key_slot">开始检测钥匙仓位,正在读取RFID</string>
+    <string name="start_detect_lock_slot">开始检测挂锁仓位,正在读取RFID</string>
+    <string name="check_lock_is_new_device">检查挂锁是否为新硬件</string>
+    <string name="check_key_is_new_device">检查钥匙是否为新硬件</string>
+    <string name="start_scan_key_mac">开始扫描钥匙信息</string>
+    <string name="do_you_want_to_remove_exception">是否确认移除改异常</string>
 
 </resources>

+ 26 - 1
data/src/main/java/com/grkj/data/dao/HardwareDao.kt

@@ -438,7 +438,7 @@ interface HardwareDao {
     /**
      * 移除仓位异常
      */
-    @Query("update is_lock_cabinet_slots set status = :dictValue where `row` = :row and `col` = :col")
+    @Query("update is_lock_cabinet_slots set status = :dictValue,remark = null where `row` = :row and `col` = :col")
     fun removeSlotsException(row: Int, col: Int, dictValue: String?)
 
     /**
@@ -446,4 +446,29 @@ interface HardwareDao {
      */
     @Query("update is_lock_cabinet_slots set status = :dictValue,remark = :remark where `row` = :row and `col` = :col")
     fun tagSlotsException(row: Int, col: Int, dictValue: String?, remark: String)
+
+    /**
+     * 标记钥匙异常
+     */
+    @Query("update is_key set ex_status = :exStatus,remark = :remark where key_nfc = :rfid")
+    fun tagKeyException(rfid: String?, remark: String, exStatus: String?)
+
+    /**
+     * 标记挂锁异常
+     */
+    @Query("update is_lock set ex_status = :exStatus,remark = :remark where lock_nfc = :rfid")
+    fun tagLockException(rfid: String?, remark: String, exStatus: String?)
+
+    /**
+     * 移除钥匙异常
+     */
+    @Query("update is_key set ex_status = :exStatus,remark = null where key_nfc = :rfid")
+    fun removeKeyException(rfid: String?, exStatus: String?)
+
+    /**
+     * 移除挂锁异常
+     */
+    @Query("update is_lock set ex_status = :exStatus,remark = null where lock_nfc = :rfid")
+    fun removeLockException(rfid: String?, exStatus: String?)
+
 }

+ 8 - 2
data/src/main/java/com/grkj/data/database/ISCSDatabase.kt

@@ -45,6 +45,9 @@ import com.grkj.data.model.dos.WorkflowStep
 import com.grkj.data.model.dos.WorkflowStepTemplate
 import com.grkj.shared.config.Constants
 import com.sik.sikcore.SIKCore
+import kotlinx.coroutines.Dispatchers
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
 
 /**
  * 本地数据库
@@ -63,6 +66,8 @@ import com.sik.sikcore.SIKCore
 )
 abstract class ISCSDatabase : RoomDatabase() {
     companion object {
+        private val logger: Logger = LoggerFactory.getLogger(ISCSDatabase::class.java)
+
         /**
          * 单例
          */
@@ -74,8 +79,9 @@ abstract class ISCSDatabase : RoomDatabase() {
                 if (Constants.DEBUG) {
 //                    fallbackToDestructiveMigration(true)
                 }
-
-            }.createFromAsset("data.db").build()
+            }.createFromAsset("data.db").setQueryCallback(Dispatchers.IO) { sql, args ->
+                logger.debug("SQL:$sql,Args:$args")
+            }.build()
         }
     }
 

+ 35 - 0
data/src/main/java/com/grkj/data/repository/IHardwareRepository.kt

@@ -29,11 +29,21 @@ interface IHardwareRepository {
      */
     fun getLockInfo(rfid: String, callback: (LockInfoRes?) -> Unit)
 
+    /**
+     * 获取锁信息
+     */
+    fun getLockInfo(rfid: String): LockInfoRes?
+
     /**
      * 获取钥匙信息
      */
     fun getKeyInfo(rfid: String, callback: (KeyInfoRes?) -> Unit)
 
+    /**
+     * 获取钥匙信息
+     */
+    fun getKeyInfo(rfid: String): KeyInfoRes?
+
     /**
      * 是否可以归还
      */
@@ -177,6 +187,11 @@ interface IHardwareRepository {
      */
     fun getKeyInfoPage(filterVo: KeyManageFilterVo?, size: Int, offset: Int): List<IsKey>
 
+    /**
+     * 获取所有钥匙信息
+     */
+    fun getAllKeyInfo(): List<IsKey>
+
     /**
      * 获取钥匙分页
      */
@@ -280,4 +295,24 @@ interface IHardwareRepository {
      * 标记锁仓异常
      */
     fun tagSlotsException(row: Int, col: Int, remark: String)
+
+    /**
+     * 标记钥匙异常
+     */
+    fun tagKeyException(rfid: String?, remark: String)
+
+    /**
+     * 标记挂锁异常
+     */
+    fun tagLockException(rfid: String?, remark: String)
+
+    /**
+     * 移除钥匙异常
+     */
+    fun removeKeyException(rfid: String?)
+
+    /**
+     * 移除挂锁异常
+     */
+    fun removeLockException(rfid: String?)
 }

+ 33 - 2
data/src/main/java/com/grkj/data/repository/impl/HardwareRepository.kt

@@ -58,7 +58,7 @@ class HardwareRepository @Inject constructor(
     /**
      * 获取锁信息
      */
-    private fun getLockInfo(rfid: String): LockInfoRes? {
+    override fun getLockInfo(rfid: String): LockInfoRes? {
         val isLock = hardwareDao.getLockInfoByRfid(rfid)
         var lockInfoRes = BeanUtils.copyProperties(isLock, LockInfoRes::class.java)
         logger.info("lockInfo:${isLock},${lockInfoRes}")
@@ -75,7 +75,10 @@ class HardwareRepository @Inject constructor(
         callback(getKeyInfo(rfid))
     }
 
-    private fun getKeyInfo(rfid: String): KeyInfoRes? {
+    /**
+     * 获取钥匙信息
+     */
+    override fun getKeyInfo(rfid: String): KeyInfoRes? {
         val isKey = hardwareDao.getKeyInfoByRfid(rfid)
         var keyInfoRes = BeanUtils.copyProperties(isKey, KeyInfoRes::class.java)
         logger.info("keyInfo:${isKey},${keyInfoRes}")
@@ -291,6 +294,10 @@ class HardwareRepository @Inject constructor(
         )
     }
 
+    override fun getAllKeyInfo(): List<IsKey> {
+        return hardwareDao.getAllKeyData()
+    }
+
     override fun getLockInfoPage(
         filterVo: LockManageFilterVo?,
         size: Int,
@@ -441,4 +448,28 @@ class HardwareRepository @Inject constructor(
             remark
         )
     }
+
+    override fun tagKeyException(rfid: String?, remark: String) {
+        hardwareDao.tagKeyException(
+            rfid, remark,
+            CommonDictDataEnum.KEY_STATUS.commonDictRes.find { it.dictLabel == "异常" }?.dictValue
+        )
+    }
+
+    override fun tagLockException(rfid: String?, remark: String) {
+        hardwareDao.tagLockException(
+            rfid, remark,
+            CommonDictDataEnum.KEY_STATUS.commonDictRes.find { it.dictLabel == "异常" }?.dictValue
+        )
+    }
+
+    override fun removeKeyException(rfid: String?) {
+        hardwareDao.removeKeyException(rfid,
+            CommonDictDataEnum.KEY_STATUS.commonDictRes.find { it.dictLabel == "正常" }?.dictValue)
+    }
+
+    override fun removeLockException(rfid: String?) {
+        hardwareDao.removeLockException(rfid,
+            CommonDictDataEnum.PADLOCK_STATUS.commonDictRes.find { it.dictLabel == "正常" }?.dictValue)
+    }
 }

+ 5 - 0
ui-base/src/main/java/com/grkj/ui_base/config/ISCSConfig.kt

@@ -37,6 +37,11 @@ object ISCSConfig {
      */
     val isWorkstationOn: Boolean get() = MMKVConstants.WORKSTATION_OPEN.getMMKVData(true)
 
+    /**
+     * 是否是联网版
+     */
+    val isNetVersion: Boolean get() = MMKVConstants.SERVER_ADDRESS.getMMKVData("").isNotEmpty()
+
     /**
      * 设计图宽度
      */

+ 28 - 34
ui-base/src/main/java/com/grkj/ui_base/utils/modbus/DockBean.kt

@@ -54,12 +54,12 @@ class DockBean(
                     if (getKeyList().isEmpty()) {
                         deviceList.add(
                             KeyBean(
-                                row, 0, leftHasKey, isLeftCharging, false, null, null
+                                addr, row, 0, leftHasKey, isLeftCharging, false, null, null
                             )
                         )
                         deviceList.add(
                             KeyBean(
-                                row, 1, rightHasKey, isRightCharging, false, null, null
+                                addr, row, 1, rightHasKey, isRightCharging, false, null, null
                             )
                         )
                         return null
@@ -116,7 +116,7 @@ class DockBean(
 
                     if (getLockList().isEmpty()) {
                         for (i in 0 until tempList.size) {
-                            deviceList.add(LockBean(row, i, tempList[i], false, null))
+                            deviceList.add(LockBean(addr, row, i, tempList[i], false, null))
                         }
                     }
 
@@ -158,7 +158,7 @@ class DockBean(
                     }
                     if (getLockList().isEmpty()) {
                         for (i in 0 until tempList.size) {
-                            deviceList.add(LockBean(row, i, tempList[i], false, null))
+                            deviceList.add(LockBean(addr, row, i, tempList[i], false, null))
                         }
                     }
 
@@ -168,7 +168,7 @@ class DockBean(
                     if (getKeyList().isEmpty()) {
                         deviceList.add(
                             KeyBean(
-                                row, 4, isKeyExist, isKeyCharging, false, null, null
+                                addr, row, 4, isKeyExist, isKeyCharging, false, null, null
                             )
                         )
                     }
@@ -181,11 +181,11 @@ class DockBean(
 
                     // TODO 便携柜 list
                     if (getCardList().isEmpty()) {
-                        deviceList.add(CardBean(row, 0, isCardExist))
+                        deviceList.add(CardBean(addr, row, 0, isCardExist))
                     }
 
                     if (getFingerPrintList().isEmpty()) {
-                        deviceList.add(FingerPrintBean(row, 0, isFingerPrintExist))
+                        deviceList.add(FingerPrintBean(addr, row, 0, isFingerPrintExist))
                     }
 
                     val changeList = mutableListOf<DeviceBean>()
@@ -317,7 +317,7 @@ class DockBean(
                     }
                     if (getLockList().isEmpty()) {
                         for (i in 0 until tempList.size) {
-                            deviceList.add(LockBean(row, i, tempList[i], tempList[i], null))
+                            deviceList.add(LockBean(addr, row, i, tempList[i], tempList[i], null))
                         }
                         return null
                     }
@@ -397,15 +397,11 @@ class DockBean(
                                 deviceList.filterIsInstance<SwitchBean>()
                                     .find { it.switchBoardAddr == switchBoardAddr[1] && it.idx == idx }
                                     ?.let {
-                                        it.enabled =
-                                            switchStatus[idx]
+                                        it.enabled = switchStatus[idx]
                                     } ?: run {
                                     deviceList.add(
                                         SwitchBean(
-                                            row,
-                                            idx,
-                                            switchBoardAddr[1],
-                                            switchStatus[idx]
+                                            addr, row, idx, switchBoardAddr[1], switchStatus[idx]
                                         )
                                     )
                                 }
@@ -476,8 +472,7 @@ class DockBean(
      */
     fun getKeyList(): MutableList<KeyBean> {
         return deviceList.filterIsInstance<KeyBean>()
-            .filter { it.type == DeviceConst.DEVICE_TYPE_KEY }
-            .toMutableList()
+            .filter { it.type == DeviceConst.DEVICE_TYPE_KEY }.toMutableList()
     }
 
     /**
@@ -485,8 +480,7 @@ class DockBean(
      */
     fun getLockList(): MutableList<LockBean> {
         return deviceList.filterIsInstance<LockBean>()
-            .filter { it.type == DeviceConst.DEVICE_TYPE_LOCK }
-            .toMutableList()
+            .filter { it.type == DeviceConst.DEVICE_TYPE_LOCK }.toMutableList()
     }
 
     /**
@@ -494,8 +488,7 @@ class DockBean(
      */
     fun getCardList(): MutableList<CardBean> {
         return deviceList.filterIsInstance<CardBean>()
-            .filter { it.type == DeviceConst.DEVICE_TYPE_CARD }
-            .toMutableList()
+            .filter { it.type == DeviceConst.DEVICE_TYPE_CARD }.toMutableList()
     }
 
     /**
@@ -511,8 +504,7 @@ class DockBean(
      */
     fun getSwitchList(): MutableList<SwitchBean> {
         return deviceList.filterIsInstance<SwitchBean>()
-            .filter { it.type == DeviceConst.DEVICE_TYPE_SWITCH }
-            .toMutableList()
+            .filter { it.type == DeviceConst.DEVICE_TYPE_SWITCH }.toMutableList()
     }
 
     override fun toString(): String {
@@ -528,6 +520,7 @@ class DockBean(
      * @param isExist true:有设备 false:无设备
      */
     sealed class DeviceBean(
+        var addr: Byte,
         var type: Int,
         var row: Int,
         var idx: Int,
@@ -536,7 +529,7 @@ class DockBean(
         var newHardware: Boolean = false
     ) {
         override fun toString(): String {
-            return "DeviceBean(type=$type, row=$row, idx=$idx, isExist=$isExist)"
+            return "DeviceBean(addr:$addr, type=$type, row=$row, idx=$idx, isExist=$isExist)"
         }
     }
 
@@ -544,6 +537,7 @@ class DockBean(
      * 钥匙
      */
     class KeyBean(
+        addr: Byte,
         row: Int,
         idx: Int,
         isExist: Boolean,
@@ -553,13 +547,13 @@ class DockBean(
         var mac: String?,
         var isReady: Boolean = false,    // 钥匙是否准备好(连接上且为待机模式)
         var power: Int = 0 //电量
-    ) : DeviceBean(DeviceConst.DEVICE_TYPE_KEY, row, idx, isExist, lockEnabled) {
+    ) : DeviceBean(addr, DeviceConst.DEVICE_TYPE_KEY, row, idx, isExist, lockEnabled) {
         override fun toString(): String {
             return "KeyBean( isCharging=$isCharging, rfid=$rfid, mac=$mac, isReady=$isReady, row=$row, idx=$idx, isExist=$isExist, power=$power)"
         }
 
         fun clone(): KeyBean {
-            return KeyBean(row, idx, isExist, isCharging, lockEnabled, rfid, mac, isReady)
+            return KeyBean(addr, row, idx, isExist, isCharging, lockEnabled, rfid, mac, isReady)
         }
     }
 
@@ -569,18 +563,19 @@ class DockBean(
      * @param rfid 锁具的RFID(仅有关闭锁扣的时候读取并保存,否则为null)
      */
     class LockBean(
+        addr: Byte,
         row: Int,
         idx: Int,
         isExist: Boolean,
         lockEnabled: Boolean = false,
         var rfid: String?,
-    ) : DeviceBean(DeviceConst.DEVICE_TYPE_LOCK, row, idx, isExist, lockEnabled) {
+    ) : DeviceBean(addr, DeviceConst.DEVICE_TYPE_LOCK, row, idx, isExist, lockEnabled) {
         override fun toString(): String {
             return "LockBean(rfid=$rfid, row=$row, idx=$idx, isExist=$isExist)"
         }
 
         fun clone(): LockBean {
-            return LockBean(row, idx, isExist, lockEnabled, rfid)
+            return LockBean(addr, row, idx, isExist, lockEnabled, rfid)
         }
     }
 
@@ -588,29 +583,28 @@ class DockBean(
      * 卡
      */
     class CardBean(
+        addr: Byte,
         row: Int,
         idx: Int,
         isExist: Boolean,
         lockEnabled: Boolean = false,
-    ) : DeviceBean(DeviceConst.DEVICE_TYPE_CARD, row, idx, isExist, lockEnabled)
+    ) : DeviceBean(addr, DeviceConst.DEVICE_TYPE_CARD, row, idx, isExist, lockEnabled)
 
     /**
      * 指纹
      */
     class FingerPrintBean(
+        addr: Byte,
         row: Int,
         idx: Int,
         isExist: Boolean,
         lockEnabled: Boolean = false,
-    ) : DeviceBean(DeviceConst.DEVICE_TYPE_FINGERPRINT, row, idx, isExist, lockEnabled)
+    ) : DeviceBean(addr, DeviceConst.DEVICE_TYPE_FINGERPRINT, row, idx, isExist, lockEnabled)
 
     /**
      * 开关
      */
     class SwitchBean(
-        row: Int,
-        idx: Int,
-        val switchBoardAddr: Byte,
-        var enabled: Boolean
-    ) : DeviceBean(DeviceConst.DEVICE_TYPE_SWITCH, row, idx, true)
+        addr: Byte, row: Int, idx: Int, val switchBoardAddr: Byte, var enabled: Boolean
+    ) : DeviceBean(addr, DeviceConst.DEVICE_TYPE_SWITCH, row, idx, true)
 }

+ 15 - 0
ui-base/src/main/java/com/grkj/ui_base/utils/modbus/ModBusCMDHelper.kt

@@ -135,6 +135,21 @@ object ModBusCMDHelper {
         )
     }
 
+    /**
+     * 操作钥匙/便携式底座钥匙充电关闭,所有
+     */
+    fun generateAllKeyChargeDownCmd(type: Byte): MBFrame {
+        return MBFrame(
+            FRAME_TYPE_WRITE,
+            byteArrayOf(
+                0x00,
+                0x11,
+                (if (type == DeviceConst.DOCK_TYPE_PORTABLE) 0b00100000 else 0b00100010).toByte(),
+                0x00
+            )
+        )
+    }
+
     /**
      * 操作钥匙/便携式底座钥匙充电,一次只操作一个卡扣
      *

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

@@ -12,6 +12,7 @@ import com.grkj.ui_base.utils.ble.BleConnectionManager
 import com.grkj.ui_base.utils.event.ModbusInitCompleteEvent
 import com.grkj.shared.utils.extension.removeLeadingZeros
 import com.grkj.shared.utils.extension.toHexStrings
+import com.grkj.ui_base.business.ModbusBusinessManager
 import com.grkj.ui_base.utils.event.StartModbusEvent
 import com.kongzue.dialogx.dialogs.PopTip
 import com.sik.sikcore.thread.ThreadUtils
@@ -19,6 +20,7 @@ import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import java.util.concurrent.atomic.AtomicInteger
 import java.util.stream.Collectors
+import kotlin.collections.copyOfRange
 
 
 /**
@@ -606,6 +608,27 @@ object ModBusController {
         }
     }
 
+    /**
+     * 读取钥匙RFID字符串
+     */
+    fun readKeyRfidStr(
+        slaveAddress: Byte?, idx: Int, done: ((idx: Int, res: String) -> Unit)? = null
+    ) {
+        slaveAddress?.let {
+            ModBusCMDHelper.generateRfidCmd(idx).let { cmd ->
+                modBusManager?.sendTo(it, cmd) { res ->
+                    if (res.size < 11) {
+                        logger.error("Key rfid error")
+                        return@sendTo
+                    }
+                    val rfid =
+                        res.copyOfRange(3, 11).toHexStrings(false).removeLeadingZeros()
+                    done?.invoke(idx, rfid)
+                }
+            }
+        }
+    }
+
     /**
      * 读取锁具RFID
      */
@@ -619,6 +642,25 @@ object ModBusController {
         }
     }
 
+    /**
+     * 读取锁RFID字符串
+     */
+    fun readLockRfidStr(slaveAddress: Byte?, lockIdx: Int, done: ((res: String) -> Unit)? = null) {
+        slaveAddress?.let {
+            ModBusCMDHelper.generateRfidCmd(lockIdx)?.let { cmd ->
+                modBusManager?.sendTo(it, cmd) { res ->
+                    if (res.size < 11) {
+                        logger.error("Lock rfid error")
+                        return@sendTo
+                    }
+                    val rfid =
+                        res.copyOfRange(3, 11).toHexStrings(false).removeLeadingZeros()
+                    done?.invoke(rfid)
+                }
+            }
+        }
+    }
+
     /**
      * 读便携式底座卡RFID
      */
@@ -733,7 +775,7 @@ object ModBusController {
     /**
      * 打开所有钥匙锁仓并关闭充电
      */
-    fun controlAllKeyBuckleOpen(complete:()-> Unit = {}) {
+    fun controlAllKeyBuckleOpen(complete: () -> Unit = {}) {
         dockList.filter { it.type == DeviceConst.DOCK_TYPE_KEY || it.type == DeviceConst.DOCK_TYPE_PORTABLE }
             .forEach { dock ->
                 dock.type?.let { dockType ->
@@ -746,6 +788,22 @@ object ModBusController {
             }
     }
 
+    /**
+     * 关闭所有钥匙锁仓充电
+     */
+    fun controlAllKeyChargeDown(complete: () -> Unit = {}) {
+        dockList.filter { it.type == DeviceConst.DOCK_TYPE_KEY || it.type == DeviceConst.DOCK_TYPE_PORTABLE }
+            .forEach { dock ->
+                dock.type?.let { dockType ->
+                    ModBusCMDHelper.generateAllKeyChargeDownCmd(dockType).let { cmd ->
+                        modBusManager?.sendTo(dock.addr, cmd) { res ->
+                            complete()
+                        }
+                    }
+                }
+            }
+    }
+
     /**
      * 控制钥匙充电
      */
@@ -1114,6 +1172,7 @@ object ModBusController {
             .flatMapIndexed { row, dock ->
                 dock.getSwitchList().mapIndexed { idx, sw ->
                     DockBean.SwitchBean(
+                        dock.addr,
                         row,                // 来自 flatMapIndexed
                         idx,
                         sw.switchBoardAddr,

+ 1 - 1
ui-base/src/main/res/values-en/strings.xml

@@ -341,7 +341,7 @@
     <string name="ticket_lost">{"msg":"作业票数据丢失啦!","code":500}</string>
     <string name="current_ticket_report_lock_take_exception_tip">current ticket report lock take exception, please return lock</string>
     <string name="please_input_exception_reason">please input exception reason</string>
-    <string name="hardware_info">Hardware Info: %1$s</string>
+    <string name="hardware_info">Hardware Info: %s</string>
     <string name="hardware_unknown">unknown</string>
     <string name="hardware_key">Key</string>
     <string name="hardware_lock">Lock</string>

+ 1 - 1
ui-base/src/main/res/values-zh/strings.xml

@@ -341,7 +341,7 @@
     <string name="ticket_lost">{"msg":"作业票数据丢失啦!","code":500}</string>
     <string name="current_ticket_report_lock_take_exception_tip">当前作业挂锁上报异常,请归还挂锁</string>
     <string name="please_input_exception_reason">请输入异常原因</string>
-    <string name="hardware_info">硬件信息: %1$s</string>
+    <string name="hardware_info">硬件信息: %s</string>
     <string name="hardware_unknown">未知</string>
     <string name="hardware_key">钥匙</string>
     <string name="hardware_lock">挂锁</string>

+ 1 - 1
ui-base/src/main/res/values/strings.xml

@@ -341,7 +341,7 @@
     <string name="ticket_lost">{"msg":"作业票数据丢失啦!","code":500}</string>
     <string name="current_ticket_report_lock_take_exception_tip">当前作业挂锁上报异常,请归还挂锁</string>
     <string name="please_input_exception_reason">请输入异常原因</string>
-    <string name="hardware_info">硬件信息: %1$s</string>
+    <string name="hardware_info">硬件信息: %s</string>
     <string name="hardware_unknown">未知</string>
     <string name="hardware_key">钥匙锁仓</string>
     <string name="hardware_lock">挂锁锁仓</string>