소스 검색

feat(硬件): 新增硬件模式切换及相关功能
- 新增硬件模式(Modbus/CAN)切换功能,支持在初始化页面和设置页面进行配置
- 新增`StartListenerEvent`用于启动硬件监听服务
- `IHardwareHelper`接口新增`connectAndAddListener`、`allSlotOn`、`allSlotOff`方法
- `ModBusHardwareHelper`和`CanHardwareHelper`实现新增接口方法
- 仓位管理页面新增全仓位开启和关闭功能
- 优化RFID标签删除逻辑,修复已使用标签仍可删除的问题
- 优化DAO层查询,增加`includeDescendants`参数,支持查询下级区域数据
- 设置页面增加开发者选项,可配置是否包含下级区域数据
- 修复Modbus初始化时,无钥匙仓位无法完成初始化的问题
- 新增中英文国际化文本

周文健 2 달 전
부모
커밋
f18787f6fe
27개의 변경된 파일637개의 추가작업 그리고 220개의 파일을 삭제
  1. 25 8
      app/src/main/assets/i18n/en-US.json
  2. 15 0
      app/src/main/assets/i18n/zh-CN.json
  3. 5 1
      app/src/main/java/com/grkj/iscs/ISCSApplication.kt
  4. 23 2
      app/src/main/java/com/grkj/iscs/features/init/fragment/InitWelcomeFragment.kt
  5. 8 0
      app/src/main/java/com/grkj/iscs/features/login/activity/LoginActivity.kt
  6. 1 1
      app/src/main/java/com/grkj/iscs/features/main/fragment/hardware_manage/RfidTokenManageFragment.kt
  7. 6 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/hardware_manage/SlotsManageFragment.kt
  8. 25 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/user_info/SettingsFragment.kt
  9. 19 1
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/hardware_manage/SlotsManageViewModel.kt
  10. 7 3
      app/src/main/java/com/grkj/iscs/features/splash/activity/SplashActivity.kt
  11. 39 0
      app/src/main/res/layout/fragment_init_welcome.xml
  12. 33 1
      app/src/main/res/layout/fragment_settings.xml
  13. 50 11
      app/src/main/res/layout/fragment_slots_manage.xml
  14. 5 0
      data/src/main/java/com/grkj/data/config/ISCSConfig.kt
  15. 26 10
      data/src/main/java/com/grkj/data/dao/HardwareDao.kt
  16. 21 15
      data/src/main/java/com/grkj/data/dao/IsSopDao.kt
  17. 47 46
      data/src/main/java/com/grkj/data/dao/IsolationPointDao.kt
  18. 62 24
      data/src/main/java/com/grkj/data/dao/JobTicketDao.kt
  19. 33 34
      data/src/main/java/com/grkj/data/dao/UserDao.kt
  20. 4 0
      data/src/main/java/com/grkj/data/data/EventConstants.kt
  21. 5 0
      data/src/main/java/com/grkj/data/data/MMKVConstants.kt
  22. 31 6
      data/src/main/java/com/grkj/data/hardware/IHardwareHelper.kt
  23. 46 2
      data/src/main/java/com/grkj/data/hardware/can/CanHardwareHelper.kt
  24. 59 53
      data/src/main/java/com/grkj/data/hardware/modbus/ModBusController.kt
  25. 15 0
      data/src/main/java/com/grkj/data/hardware/modbus/ModBusHardwareHelper.kt
  26. 25 0
      data/src/main/java/com/grkj/data/utils/event/StartListenerEvent.kt
  27. 2 2
      data/src/main/java/com/grkj/data/utils/event/StartModbusEvent.kt

+ 25 - 8
app/src/main/assets/i18n/en-US.json

@@ -3923,11 +3923,12 @@
     "key": "export_selected_backup_file_confirm",
     "type": "text",
     "value": "Launch the path selector, select and click on the bottom right corner to choose 'Export'"
-  },"header_pulling": {
-  "key": "header_pulling",
-  "type": "text",
-  "value": "Pull down to refresh"
-},
+  },
+  "header_pulling": {
+    "key": "header_pulling",
+    "type": "text",
+    "value": "Pull down to refresh"
+  },
   "header_refreshing": {
     "key": "header_refreshing",
     "type": "text",
@@ -4046,7 +4047,7 @@
   "data_export": {
     "key": "data_export",
     "type": "text",
-    "value": "Data export"
+    "value": "Data Export"
   },
   "data_export_tip": {
     "key": "data_export",
@@ -4071,12 +4072,12 @@
   "data_export_success_tip": {
     "key": "data_export_success_tip",
     "type": "text",
-    "value": "Data export completed. Please select a folder and click the bottom right button to save."
+    "value": "Data Export completed. Please select a folder and click the bottom right button to save."
   },
   "data_export_error": {
     "key": "data_export_error",
     "type": "text",
-    "value": "Data export failed."
+    "value": "Data Export failed."
   },
   "user": {
     "key": "user",
@@ -4123,9 +4124,25 @@
     "type": "text",
     "value": "Password"
   },
+  "init_hardware_mode": {
+    "key": "hardware_mode",
+    "type": "text",
+    "value": "Hardware Mode"
+  },
   "password_hint": {
     "key": "password_hint",
     "type": "text",
     "value": "6–20: letters/numbers/symbols"
+  },
+  "all_slot_turn_on": {
+    "key": "all_slot_turn_on",
+    "type": "text",
+    "value": "All slot turn on"
+  },
+  "all_slot_turn_off": {
+    "key": "all_slot_turn_off",
+    "type": "text",
+    "value": "All slot turn off"
+    "
   }
 }

+ 15 - 0
app/src/main/assets/i18n/zh-CN.json

@@ -4124,6 +4124,11 @@
     "type": "text",
     "value": "硬件模式(模式修改保存需要重启应用)"
   },
+  "init_hardware_mode": {
+    "key": "hardware_mode",
+    "type": "text",
+    "value": "硬件模式"
+  },
   "password": {
     "key": "password",
     "type": "text",
@@ -4133,5 +4138,15 @@
     "key": "password_hint",
     "type": "text",
     "value": "6-20位:字母/数字/符号"
+  },
+  "all_slot_turn_on": {
+    "key": "all_slot_turn_on",
+    "type": "text",
+    "value": "全仓位开"
+  },
+  "all_slot_turn_off": {
+    "key": "all_slot_turn_off",
+    "type": "text",
+    "value": "全仓位关"
   }
 }

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

@@ -108,7 +108,6 @@ class ISCSApplication : Application() {
         StateConfig.emptyLayout = com.grkj.ui_base.R.layout.layout_empty
         ThreadUtils.runOnIO {
             DbReadyGate.await()
-            HardwareBusinessManager.registerMainListener()
             LogicManager.init(this@ISCSApplication)
             initImageLoader()
         }
@@ -140,6 +139,11 @@ class ISCSApplication : Application() {
                     ModBusController.initDevicesStatus()
                 }
             }
+            EventConstants.EVENT_START_LISTENER -> {
+                ThreadUtils.runOnIO {
+                    HardwareBusinessManager.registerMainListener()
+                }
+            }
 
             EventConstants.EVENT_RESTART_APP -> {
                 scheduleRestart(this)

+ 23 - 2
app/src/main/java/com/grkj/iscs/features/init/fragment/InitWelcomeFragment.kt

@@ -1,11 +1,14 @@
 package com.grkj.iscs.features.init.fragment
 
+import androidx.core.view.isVisible
 import androidx.fragment.app.viewModels
 import com.grkj.data.data.MMKVConstants
+import com.grkj.data.enums.HardwareMode
 import com.grkj.iscs.R
 import com.grkj.iscs.databinding.FragmentInitWelcomeBinding
 import com.grkj.iscs.features.common.dialog.ChangeLangDialog
 import com.grkj.iscs.features.init.viewmodel.InitDeviceRegistrationKeyAndLockViewModel
+import com.grkj.iscs.features.main.dialog.TextDropDownDialog
 import com.grkj.shared.utils.i18n.I18nManager
 import com.grkj.shared.utils.i18n.LanguageEntry
 import com.grkj.shared.utils.i18n.LanguageRegistry
@@ -14,6 +17,7 @@ import com.grkj.ui_base.base.BaseFragment
 import com.grkj.ui_base.utils.CommonUtils
 import com.kongzue.dialogx.dialogs.PopTip
 import com.sik.sikcore.extension.getMMKVData
+import com.sik.sikcore.extension.saveMMKVData
 import com.sik.sikcore.extension.setDebouncedClickListener
 import dagger.hilt.android.AndroidEntryPoint
 import java.util.Locale
@@ -39,8 +43,25 @@ class InitWelcomeFragment : BaseFragment<FragmentInitWelcomeBinding>() {
                 PopTip.tip("清除成功")
             }
         }
-        if (MMKVConstants.KEY_PORT_CONFIG.getMMKVData("").isEmpty()) {
-            binding.startBtn.text = CommonUtils.getStr("detect_port")
+        if (MMKVConstants.KEY_HARDWARE_MODE.getMMKVData("").isEmpty()) {
+            binding.hardwareModeLayout.isVisible = true
+            binding.hardwareMode.text = HardwareMode.MODBUS.name
+            var hardwareMode = HardwareMode.MODBUS.name
+            binding.hardwareMode.setDebouncedClickListener {
+                val hardwareModeData = HardwareMode.values()
+                    .map { TextDropDownDialog.SimpleTextDropDownEntity(dataText = it.name) }
+                TextDropDownDialog.showSingle(hardwareModeData, binding.hardwareMode) {
+                    binding.hardwareMode.text = it.getShowText()
+                    hardwareMode = it.getShowText()
+                }
+            }
+            binding.startBtn.setDebouncedClickListener {
+                MMKVConstants.KEY_HARDWARE_MODE.saveMMKVData(hardwareMode)
+                HardwareMode.getCurrentHardwareMode().connectAndAddListener()
+                binding.startBtn.text = CommonUtils.getStr("detect_port")
+                binding.hardwareMode.setDebouncedClickListener {}
+                binding.startBtn.setDebouncedClickListener {}
+            }
         } else {
             viewModel.createLockCabinetData().observe(this) {
 

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

@@ -9,6 +9,8 @@ import android.widget.LinearLayout
 import androidx.activity.viewModels
 import androidx.core.view.isVisible
 import androidx.core.widget.ImageViewCompat
+import com.arcsoft.face.FaceEngine
+import com.arcsoft.face.model.ActiveDeviceInfo
 import com.drake.brv.BindingAdapter
 import com.drake.brv.annotaion.DividerOrientation
 import com.drake.brv.utils.divider
@@ -45,6 +47,7 @@ import com.grkj.ui_base.utils.extension.getAppVersionName
 import com.grkj.ui_base.utils.extension.serialNo
 import com.grkj.ui_base.utils.fingerprint.FingerprintUtil
 import com.sik.sikcore.extension.setDebouncedClickListener
+import com.sik.sikcore.shell.ShellUtils
 import com.sik.sikimage.ImageConvertUtils
 import dagger.hilt.android.AndroidEntryPoint
 import java.util.Locale
@@ -102,6 +105,11 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>() {
                 onLoginMenuBinding(this)
             }
         }
+        binding.titleCn.setDebouncedClickListener {
+            val activeDeviceInfo = ActiveDeviceInfo()
+            FaceEngine.getActiveDeviceInfo(this, activeDeviceInfo)
+            ShellUtils.execCmd("echo ${activeDeviceInfo.deviceInfo} > /sdcard/iscs/activeDeviceInfo.txt")
+        }
         binding.version.setOnLongClickListener {
             startActivity(Intent(this, SetRemoteServerActivity::class.java))
             true

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

@@ -163,7 +163,7 @@ class RfidTokenManageFragment : BaseFragment<FragmentRfidTokenManageBinding>() {
         if (viewModel.rfidTokenManageDataList.none { it.isSelected }) {
             showToast(CommonUtils.getStr("please_select_rfid_token")); return
         }
-        if (viewModel.rfidTokenManageDataList.map { it.rfidId }
+        if (viewModel.rfidTokenManageDataList.filter { it.isSelected }.map { it.rfidId }
                 .any { it in viewModel.inUseRfidIds }) {
             showToast(CommonUtils.getStr("selected_rfid_in_use").toString())
             return

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

@@ -81,6 +81,12 @@ class SlotsManageFragment : BaseFragment<FragmentSlotsManageBinding>() {
         ) {
             ISCSConfig.isDeviceRegistration = true
         }
+        binding.tvAllSlotTurnOn.setDebouncedClickListener {
+            viewModel.allSlotTurnOn().observe(this){}
+        }
+        binding.tvAllSlotTurnOff.setDebouncedClickListener {
+            viewModel.allSlotTurnOff().observe(this){}
+        }
         HardwareMode.getCurrentHardwareMode().controlAllKeyChargeDown()
         viewModel.isDestroy = false
     }

+ 25 - 0
app/src/main/java/com/grkj/iscs/features/main/fragment/user_info/SettingsFragment.kt

@@ -1,6 +1,8 @@
 package com.grkj.iscs.features.main.fragment.user_info
 
+import androidx.core.view.isVisible
 import com.google.android.gms.common.internal.service.Common
+import com.grkj.data.config.ISCSConfig
 import com.grkj.data.data.CommonConstants
 import com.grkj.data.data.MMKVConstants
 import com.grkj.data.enums.HardwareMode
@@ -25,6 +27,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding>() {
      * 硬件模式修改
      */
     private var hardwareModeChanged: Boolean = false
+    private var devClickTime = 0
     override fun getLayoutId(): Int {
         return R.layout.fragment_settings
     }
@@ -33,6 +36,12 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding>() {
         binding.back.setDebouncedClickListener {
             navController.popBackStack()
         }
+        binding.settingsTitle.setDebouncedClickListener {
+            devClickTime++
+            if (devClickTime % 10 == 0) {
+                showDevSettings()
+            }
+        }
         binding.maxFingerprintInsert.setText(
             "${
                 MMKVConstants.KEY_MAX_FINGERPRINT_INSERT.getMMKVData(
@@ -79,6 +88,15 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding>() {
         }
     }
 
+    /**
+     * 显示开发设置
+     */
+    private fun showDevSettings() {
+        binding.includeDescendantTv.isVisible = true
+        binding.includeDescendant.isVisible = true
+        binding.includeDescendant.setText("${ISCSConfig.includeDescendants}")
+    }
+
     /**
      * 检查数据
      */
@@ -95,6 +113,13 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding>() {
             showToast(CommonUtils.getStr("please_input_auto_logout_time_correct"))
             return false
         }
+        var includeDescendant = binding.includeDescendant.text.toString()
+        if (includeDescendant.isEmpty()) {
+            includeDescendant = "0"
+        }
+        MMKVConstants.KEY_INCLUDE_DESCENDANTS.saveMMKVData(
+            includeDescendant.toInt()
+        )
         return true
     }
 }

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

@@ -225,7 +225,7 @@ class SlotsManageViewModel @Inject constructor(
                                 mac
                             }"
                         )
-                        HardwareMode.getCurrentHardwareMode().updateKeyMac(keyBean.rfid,mac)
+                        HardwareMode.getCurrentHardwareMode().updateKeyMac(keyBean.rfid, mac)
                         if (cont.isActive) {
                             cont.resume(mac)
                         }
@@ -251,4 +251,22 @@ class SlotsManageViewModel @Inject constructor(
             emit(true)
         }
     }
+
+    /**
+     * 全仓位开
+     */
+    fun allSlotTurnOn(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO){
+            HardwareMode.getCurrentHardwareMode().allSlotOn()
+        }
+    }
+
+    /**
+     * 全仓位关
+     */
+    fun allSlotTurnOff(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO){
+            HardwareMode.getCurrentHardwareMode().allSlotOff()
+        }
+    }
 }

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

@@ -8,6 +8,8 @@ import com.grkj.data.data.MMKVConstants
 import com.grkj.data.database.BackupScheduler
 import com.grkj.data.database.DbReadyGate
 import com.grkj.data.database.ISCSDatabase
+import com.grkj.data.enums.HardwareMode
+import com.grkj.data.utils.event.StartListenerEvent
 import com.grkj.iscs.R
 import com.grkj.iscs.common.GlobalManager
 import com.grkj.iscs.databinding.ActivitySplashBinding
@@ -19,7 +21,6 @@ import com.grkj.shared.utils.i18n.I18nManager
 import com.grkj.shared.utils.i18n.LanguageRegistry
 import com.grkj.shared.utils.i18n.LanguageStore
 import com.grkj.ui_base.base.BaseActivity
-import com.grkj.ui_base.utils.event.StartModbusEvent
 import com.kongzue.dialogx.DialogX
 import com.kongzue.dialogx.util.TextInfo
 import com.scwang.smart.refresh.footer.ClassicsFooter
@@ -30,7 +31,6 @@ import dagger.hilt.android.AndroidEntryPoint
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
 import java.util.Locale
 
 @AndroidEntryPoint
@@ -77,7 +77,11 @@ class SplashActivity : BaseActivity<ActivitySplashBinding>() {
         lifecycleScope.launch {
             BackupScheduler.applySaved(this@SplashActivity)
             DbReadyGate.await()
-            StartModbusEvent.sendStartModbusEvent()
+            if (MMKVConstants.KEY_HARDWARE_MODE.getMMKVData("").isNotEmpty()) {
+                StartListenerEvent.sendStartListenerEvent()
+                HardwareMode.getCurrentHardwareMode()
+                    .connectAndAddListener()
+            }
             viewModel.checkPresetData().observe(this@SplashActivity) {
                 viewModel.checkSysMenuAndRole().observe(this@SplashActivity) {
                     val isAppInit = MMKVConstants.APP_INIT.getMMKVData(false)

+ 39 - 0
app/src/main/res/layout/fragment_init_welcome.xml

@@ -70,6 +70,45 @@
             android:textSize="@dimen/iscs_text_md"
             app:i18nKey='@{"start_tip"}' />
 
+        <LinearLayout
+            android:id="@+id/hardware_mode_layout"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_above="@+id/start_tip"
+            android:layout_centerHorizontal="true"
+            android:visibility="gone"
+            android:layout_marginBottom="@dimen/iscs_space_2"
+            android:orientation="horizontal">
+
+            <TextView
+                android:id="@+id/hardware_mode_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:formRole="label"
+                app:i18nKey='@{"init_hardware_mode"}'
+                app:markPosition="start"
+                app:required="true" />
+
+            <TextView
+                android:id="@+id/hardware_mode"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:background="@drawable/bg_common_input"
+                android:drawableRight="@mipmap/icon_drop_down"
+                android:maxLines="1"
+                android:minWidth="@dimen/add_to_map_input_min_width"
+                android:paddingHorizontal="@dimen/iscs_space_2"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:formRole="field" />
+        </LinearLayout>
+
+
         <TextView
             android:id="@+id/start_btn"
             android:layout_width="wrap_content"

+ 33 - 1
app/src/main/res/layout/fragment_settings.xml

@@ -25,6 +25,7 @@
                 app:skinSrc='@{"icon_settings.png"}' />
 
             <TextView
+                android:id="@+id/settings_title"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_marginLeft="@dimen/iscs_space_2"
@@ -148,7 +149,6 @@
                 android:layout_marginTop="@dimen/iscs_space_2"
                 android:background="@drawable/bg_common_input"
                 android:drawableRight="@mipmap/icon_drop_down"
-                android:inputType="number"
                 android:maxLines="1"
                 android:minWidth="@dimen/add_to_map_input_min_width"
                 android:paddingHorizontal="@dimen/iscs_space_2"
@@ -158,6 +158,38 @@
                 android:textSize="@dimen/iscs_text_md"
                 app:formRole="field" />
 
+            <TextView
+                android:id="@+id/include_descendant_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/hardware_mode"
+                android:layout_marginTop="@dimen/iscs_space_4"
+                android:text="是否包含下级"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                android:visibility="gone"
+                app:formRole="label"
+                app:markPosition="start"
+                app:required="true" />
+
+            <EditText
+                android:id="@+id/include_descendant"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_below="@+id/include_descendant_tv"
+                android:layout_alignLeft="@+id/include_descendant_tv"
+                android:layout_marginTop="@dimen/iscs_space_2"
+                android:background="@drawable/bg_common_input"
+                android:maxLines="1"
+                android:minWidth="@dimen/add_to_map_input_min_width"
+                android:paddingHorizontal="@dimen/iscs_space_2"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                android:visibility="gone"
+                app:formRole="field" />
+
             <TextView
                 android:id="@+id/confirm"
                 android:layout_width="wrap_content"

+ 50 - 11
app/src/main/res/layout/fragment_slots_manage.xml

@@ -21,17 +21,17 @@
                 android:layout_width="@dimen/title_icon_size"
                 android:layout_height="@dimen/title_icon_size"
                 android:tint="?attr/colorPrimary"
-                app:skinSrc='@{"slot.svg"}'/>
+                app:skinSrc='@{"slot.svg"}' />
 
             <TextView
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_marginLeft="@dimen/iscs_space_2"
                 android:layout_weight="1"
-                app:i18nKey='@{"slots_manage_title"}'
                 android:textColor="?attr/colorTextPrimary"
                 android:textSize="@dimen/iscs_text_md"
-                android:textStyle="bold" />
+                android:textStyle="bold"
+                app:i18nKey='@{"slots_manage_title"}' />
 
             <TextView
                 android:id="@+id/back"
@@ -41,14 +41,14 @@
                 android:layout_marginLeft="@dimen/iscs_space_2"
                 android:background="@drawable/common_btn_secondary"
                 android:drawableLeft="@mipmap/icon_back"
-                android:drawableTint="?attr/colorPrimary"
                 android:drawablePadding="@dimen/iscs_space_2"
+                android:drawableTint="?attr/colorPrimary"
                 android:gravity="center"
                 android:minHeight="@dimen/common_btn_height"
                 android:paddingHorizontal="@dimen/iscs_space_4"
-                app:i18nKey='@{"back"}'
                 android:textColor="?attr/colorTextPrimary"
-                android:textSize="@dimen/iscs_text_md" />
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"back"}' />
         </LinearLayout>
 
         <View
@@ -61,21 +61,60 @@
             android:layout_height="match_parent"
             android:layout_margin="@dimen/iscs_space_4">
 
-            <androidx.recyclerview.widget.RecyclerView
-                android:id="@+id/dock_rv"
+            <LinearLayout
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
-                android:layout_gravity="center" />
+                android:orientation="vertical">
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="horizontal">
+
+                    <LinearLayout
+                        android:id="@+id/switch_layout"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="@dimen/iscs_space_2"
+                        android:gravity="right"
+                        android:orientation="horizontal"
+                        android:padding="2dp">
+
+                        <TextView
+                            android:id="@+id/tv_all_slot_turn_on"
+                            style="@style/CommonTextView"
+                            android:layout_marginRight="@dimen/iscs_space_1"
+                            android:background="?attr/colorStatusRed"
+                            android:padding="2dp"
+                            app:i18nKey='@{"all_slot_turn_on"}' />
+
+                        <TextView
+                            android:id="@+id/tv_all_slot_turn_off"
+                            style="@style/CommonTextView"
+                            android:layout_marginLeft="@dimen/iscs_space_2"
+                            android:background="?attr/colorBlue80"
+                            android:padding="2dp"
+                            app:i18nKey='@{"all_slot_turn_off"}' />
+                    </LinearLayout>
+                </LinearLayout>
+
+                <androidx.recyclerview.widget.RecyclerView
+                    android:id="@+id/dock_rv"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:layout_gravity="center" />
+            </LinearLayout>
+
             <TextView
                 android:id="@+id/loading_tip"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:background="?attr/colorContainerBg"
+                android:gravity="center"
                 android:textColor="?attr/colorTextPrimary"
-                app:i18nKey='@{"loading_device"}'
                 android:textSize="@dimen/iscs_text_xl"
                 android:textStyle="bold"
-                android:gravity="center"/>
+                app:i18nKey='@{"loading_device"}' />
         </FrameLayout>
     </LinearLayout>
 </layout>

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

@@ -22,6 +22,11 @@ object ISCSConfig {
      */
     var isDeviceRegistration: Boolean = false
 
+    /**
+     * 是否可以查看区域下的节点
+     */
+    val includeDescendants: Int get() = MMKVConstants.KEY_INCLUDE_DESCENDANTS.getMMKVData(0)
+
     /**
      * 是否可以初始化设备
      */

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

@@ -5,6 +5,7 @@ import androidx.room.Insert
 import androidx.room.OnConflictStrategy
 import androidx.room.Query
 import androidx.room.Update
+import com.grkj.data.config.ISCSConfig
 import com.grkj.data.model.dos.IsIsolationPoint
 import com.grkj.data.model.dos.IsJobCard
 import com.grkj.data.model.dos.IsKey
@@ -469,14 +470,16 @@ interface HardwareDao {
     /**
      * 根据区域统计所有点位
      */
-    @Query(
-        """
-        select count(1) 
-        from is_isolation_point iip
-        where (:workstationId IS NULL OR trim(:workstationId) = '' OR iip.workstation_id = :workstationId)
-    """
-    )
-    fun getAllPointCount(workstationId: Long?): Int
+    @Query("""
+    select count(1)
+    from is_isolation_point p
+    left join is_workstation w on w.workstation_id = p.workstation_id
+    where (:workstationId is null 
+           or p.workstation_id = :workstationId
+           or (:includeDescendants = 1 and (','||ifnull(w.ancestors,'')||',') like '%,'||:workstationId||',%'))
+""")
+    fun getAllPointCount(workstationId: Long?, includeDescendants: Int = ISCSConfig.includeDescendants): Int
+
 
     /**
      * 所有点位
@@ -522,8 +525,21 @@ interface HardwareDao {
     /**
      * 根据区域id获取点位数据
      */
-    @Query("select * from is_isolation_point where del_flag = '0' and workstation_id = :workstationId")
-    fun getPointDataByWorkstationId(workstationId: Long): List<IsIsolationPoint>
+    @Query("""
+    select * 
+        from is_isolation_point iip
+        left join is_workstation w on w.workstation_id = iip.workstation_id
+        where iip.del_flag = '0'
+          and (
+                iip.workstation_id = :workstationId
+                or (:includeDescendants = 1 and (','||ifnull(w.ancestors,'')||',') like '%,'||:workstationId||',%')
+              )
+""")
+    fun getPointDataByWorkstationId(
+        workstationId: Long,
+        includeDescendants: Int = ISCSConfig.includeDescendants
+    ): List<IsIsolationPoint>
+
 
     /**
      * 根据rfidId获取rfid数据

+ 21 - 15
data/src/main/java/com/grkj/data/dao/IsSopDao.kt

@@ -6,6 +6,7 @@ import androidx.room.OnConflictStrategy
 import androidx.room.Query
 import androidx.room.TypeConverters
 import androidx.room.Update
+import com.grkj.data.config.ISCSConfig
 import com.grkj.data.converters.Converters
 import com.grkj.data.model.dos.IsSop
 import com.grkj.data.model.dos.IsSopGroup
@@ -44,21 +45,26 @@ interface IsSopDao {
     /**
      * 根据岗位id获取sop数据
      */
-    @Query(
-        """
-        select sop_id as sopId,
-            `is`.sop_name as sopName,
-            `is`.sop_type as sopType,
-            `is`.mode_id as modeId,
-            `is`.workstation_id as workstationId,
-            iw.workstation_name as workstationName,
-            `is`.del_flag as delFlag 
-            from is_sop `is` 
-            left join is_workstation iw on `is`.workstation_id = iw.workstation_id
-            where `is`.workstation_id = :workstationId
-    """
-    )
-    fun getSopDataByWorkstationId(workstationId: Long): List<SopManageVo>
+    @Query("""
+    select sop_id as sopId,
+           `is`.sop_name as sopName,
+           `is`.sop_type as sopType,
+           `is`.mode_id as modeId,
+           `is`.workstation_id as workstationId,
+           iw.workstation_name as workstationName,
+           `is`.del_flag as delFlag
+    from is_sop `is`
+    left join is_workstation iw on `is`.workstation_id = iw.workstation_id
+    where (
+            `is`.workstation_id = :workstationId
+            or (:includeDescendants = 1 and (','||ifnull(iw.ancestors,'')||',') like '%,'||:workstationId||',%')
+          )
+    """)
+    fun getSopDataByWorkstationId(
+        workstationId: Long,
+        includeDescendants: Int = ISCSConfig.includeDescendants
+    ): List<SopManageVo>
+
 
     /**
      * 根据sopId获取sop点位数据

+ 47 - 46
data/src/main/java/com/grkj/data/dao/IsolationPointDao.kt

@@ -5,6 +5,7 @@ import androidx.room.Insert
 import androidx.room.OnConflictStrategy
 import androidx.room.Query
 import androidx.room.Update
+import com.grkj.data.config.ISCSConfig
 import com.grkj.data.model.dos.IsIsolationPoint
 import com.grkj.data.model.vo.JobPointVo
 import com.grkj.data.model.vo.PointDetailVO
@@ -30,37 +31,30 @@ interface IsolationPointDao {
     /**
      * 获取隔离点数据
      */
-    @Query(
-        """
-        select iip.point_id as pointId,
-        iip.point_name as pointName,
-        iip.remark as pointFunction,
-        iw.workstation_name as workstationName,
-        iw.workstation_id as workstationId,
-        irt.rfid as rfidToken,
-        irt.rfid_id as rfidId,
-        iip.power_type as powerType
+    @Query("""
+select iip.point_id as pointId,
+       iip.point_name as pointName,
+       iip.remark as pointFunction,
+       iw.workstation_name as workstationName,
+       iw.workstation_id as workstationId,
+       irt.rfid as rfidToken,
+       irt.rfid_id as rfidId,
+       iip.power_type as powerType
         from is_isolation_point iip
         left join is_rfid_token irt on iip.rfid_id = irt.rfid_id
-        left join is_workstation iw on iip.workstation_id=iw.workstation_id
-        WHERE iip.del_flag = 0
-
-          -- 只有当 nickname 不为 NULL 且非空才做模糊匹配
-          AND (:pointName       IS NULL OR trim(:pointName) = '' OR iip.point_name       LIKE '%' || :pointName       || '%')
-    
-          -- 只有当 cardCode 不为 NULL 且非空才做模糊匹配
-          AND (:pointFunction       IS NULL OR trim(:pointFunction) = '' OR iip.remark       LIKE '%' || :pointFunction       || '%')
-    
-          -- 只有当 workstationName 不为 NULL 且非空才做模糊匹配
-          AND (:powerType IS NULL OR trim(:powerType) = '' OR iip.power_type LIKE '%' || :powerType || '%')
-          AND (:workstation IS NULL OR trim(:workstation) = '' OR iw.workstation_id = :workstation)
-          -- 只有当 workstationName 不为 NULL 且非空才做模糊匹配
-          AND (:rfidTag IS NULL OR trim(:rfidTag) = '' OR irt.rfid LIKE '%' || :rfidTag || '%')
-    
-    
-        LIMIT :size OFFSET :offset
-    """
-    )
+        left join is_workstation iw on iip.workstation_id = iw.workstation_id
+        where iip.del_flag = 0
+          and (:pointName is null or trim(:pointName) = '' or iip.point_name like '%'||:pointName||'%')
+          and (:pointFunction is null or trim(:pointFunction) = '' or iip.remark like '%'||:pointFunction||'%')
+          and (:powerType is null or trim(:powerType) = '' or iip.power_type like '%'||:powerType||'%')
+          and (
+                :workstation is null or trim(:workstation) = '' 
+                or iw.workstation_id = :workstation
+                or (:includeDescendants = 1 and (','||ifnull(iw.ancestors,'')||',') like '%,'||:workstation||',%')
+              )
+          and (:rfidTag is null or trim(:rfidTag) = '' or irt.rfid like '%'||:rfidTag||'%')
+        limit :size offset :offset
+""")
     fun getPointManageData(
         pointName: String?,
         pointFunction: String?,
@@ -68,9 +62,11 @@ interface IsolationPointDao {
         workstation: Long?,
         rfidTag: String?,
         size: Int,
-        offset: Int
+        offset: Int,
+        includeDescendants: Int = ISCSConfig.includeDescendants
     ): List<PointManageVo>
 
+
     /**
      * 根据隔离点id删除隔离点
      */
@@ -80,24 +76,29 @@ interface IsolationPointDao {
     /**
      * 获取所有隔离点数据区域区分
      */
-    @Query(
-        """
-        select iip.point_id as pointId,
-        iip.point_name as pointName,
-        iip.remark as pointFunction,
-        iw.workstation_name as workstationName,
-        iw.workstation_id as workstationId,
-        irt.rfid as rfidToken,
-        irt.rfid_id as rfidId,
-        iip.power_type as powerType
+    @Query("""
+select iip.point_id as pointId,
+       iip.point_name as pointName,
+       iip.remark as pointFunction,
+       iw.workstation_name as workstationName,
+       iw.workstation_id as workstationId,
+       irt.rfid as rfidToken,
+       irt.rfid_id as rfidId,
+       iip.power_type as powerType
         from is_isolation_point iip
         left join is_rfid_token irt on iip.rfid_id = irt.rfid_id
-        left join is_workstation iw on iip.workstation_id=iw.workstation_id
-        WHERE iip.del_flag = 0
-        AND iw.workstation_id = :workstationId
-    """
-    )
-    fun getAllPointManageDataWithWorkstationId(workstationId: Long): MutableList<PointManageVo>
+        left join is_workstation iw on iip.workstation_id = iw.workstation_id
+        where iip.del_flag = 0
+          and (
+                iw.workstation_id = :workstationId
+                or (:includeDescendants = 1 and (','||ifnull(iw.ancestors,'')||',') like '%,'||:workstationId||',%')
+              )
+""")
+    fun getAllPointManageDataWithWorkstationId(
+        workstationId: Long,
+        includeDescendants: Int = ISCSConfig.includeDescendants
+    ): MutableList<PointManageVo>
+
 
     /**
      * 根据点位nfc获取点位数据

+ 62 - 24
data/src/main/java/com/grkj/data/dao/JobTicketDao.kt

@@ -7,6 +7,7 @@ import androidx.room.Query
 import androidx.room.Transaction
 import androidx.room.TypeConverters
 import androidx.room.Update
+import com.grkj.data.config.ISCSConfig
 import com.grkj.data.converters.Converters
 import com.grkj.data.model.dos.IsJobTicket
 import com.grkj.data.model.dos.IsJobTicketGroup
@@ -348,14 +349,27 @@ interface JobTicketDao {
     /**
      * 获取工作中的作业数量
      */
-    @Query(
-        "select count(1) from is_job_ticket where ticket_status in ('1','2','3','4','7') " +
-                "AND (:workstationId IS NULL OR trim(:workstationId) = '' OR workstation_id = :workstationId) " +
-                "AND (:modeId IS NULL OR trim(:modeId) = '' OR mode_id = :modeId) " +
-                "AND (ex_status is NULL or ex_status not in (:exStatusList)) " +
-                "and del_flag = 0"
-    )
-    fun getInProgressJobSize(workstationId: Long?, modeId: Long?, exStatusList: List<String>): Int
+    @Query("""
+    select count(1)
+    from is_job_ticket ijt
+    left join is_workstation w on w.workstation_id = ijt.workstation_id
+    where ijt.ticket_status in ('1','2','3','4','7')
+      and (:modeId is null or trim(:modeId) = '' or ijt.mode_id = :modeId)
+      and (ex_status is null or ex_status not in (:exStatusList))
+      and (
+            :workstationId is null or trim(:workstationId) = ''
+            or ijt.workstation_id = :workstationId
+            or (:includeDescendants = 1 and (','||ifnull(w.ancestors,'')||',') like '%,'||:workstationId||',%')
+          )
+      and ijt.del_flag = 0
+    """)
+    fun getInProgressJobSize(
+        workstationId: Long?,
+        modeId: Long?,
+        exStatusList: List<String>,
+        includeDescendants: Int = ISCSConfig.includeDescendants
+    ): Int
+
 
     /**
      * 获取工作中的作业数量
@@ -373,10 +387,25 @@ interface JobTicketDao {
     /**
      * 获取所有作业数量
      */
-    @Query(
-        "select count(1) from is_job_ticket where (:workstationId IS NULL OR trim(:workstationId) = '' OR workstation_id = :workstationId) " + "AND (:startTime IS NULL OR trim(:startTime) = '' OR (create_time >= :startTime OR update_time >= :startTime)) " + "AND (:endTime IS NULL OR trim(:endTime) = '' OR (create_time <= :endTime OR update_time <= :endTime)) and del_flag = 0"
-    )
-    fun getAllJobSize(workstationId: Long?, startTime: String?, endTime: String?): Int
+    @Query("""
+    select count(1)
+    from is_job_ticket ijt
+    left join is_workstation w on w.workstation_id = ijt.workstation_id
+    where (
+            :workstationId is null or trim(:workstationId) = ''
+            or ijt.workstation_id = :workstationId
+            or (:includeDescendants = 1 and (','||ifnull(w.ancestors,'')||',') like '%,'||:workstationId||',%')
+          )
+      and (:startTime is null or trim(:startTime) = '' or (ijt.create_time >= :startTime or ijt.update_time >= :startTime))
+      and (:endTime   is null or trim(:endTime)   = '' or (ijt.create_time <= :endTime   or ijt.update_time <= :endTime))
+      and ijt.del_flag = 0
+    """)
+    fun getAllJobSize(
+        workstationId: Long?,
+        startTime: String?,
+        endTime: String?,
+        includeDescendants: Int = ISCSConfig.includeDescendants
+    ): Int
 
     /**
      * 获取锁定中的点位
@@ -612,18 +641,27 @@ interface JobTicketDao {
     /**
      * 获取使用中的硬件数量
      */
-    @Query(
-        """
-        select count(1) from is_job_ticket_points ijtp
-        left join is_job_ticket ijt on ijtp.ticket_id = ijt.ticket_id
-        where ijt.ticket_status in ('1','2','3','4','7')
-        and (:workflowModeId IS NULL OR trim(:workflowModeId) = '' OR ijt.mode_id = :workflowModeId)
-        and (:workstationId IS NULL OR trim(:workstationId) = '' OR ijt.workstation_id = :workstationId)
-        and ijt.del_flag = 0
-        group by ijtp.lock_id
-    """
-    )
-    fun getUsedHardwareCount(workstationId: Long?, workflowModeId: Long?): Int
+    @Query("""
+    select count(1)
+    from is_job_ticket_points ijtp
+    left join is_job_ticket ijt on ijtp.ticket_id = ijt.ticket_id
+    left join is_workstation w on w.workstation_id = ijt.workstation_id
+    where ijt.ticket_status in ('1','2','3','4','7')
+      and (:workflowModeId is null or trim(:workflowModeId) = '' or ijt.mode_id = :workflowModeId)
+      and (
+            :workstationId is null or trim(:workstationId) = ''
+            or ijt.workstation_id = :workstationId
+            or (:includeDescendants = 1 and (','||ifnull(w.ancestors,'')||',') like '%,'||:workstationId||',%')
+          )
+      and ijt.del_flag = 0
+    group by ijtp.lock_id
+    """)
+    fun getUsedHardwareCount(
+        workstationId: Long?,
+        workflowModeId: Long?,
+        includeDescendants: Int = ISCSConfig.includeDescendants
+    ): Int
+
 
     /**
      * 根据sopId获取作业

+ 33 - 34
data/src/main/java/com/grkj/data/dao/UserDao.kt

@@ -6,6 +6,7 @@ import androidx.room.OnConflictStrategy
 import androidx.room.Query
 import androidx.room.TypeConverters
 import androidx.room.Update
+import com.grkj.data.config.ISCSConfig
 import com.grkj.data.converters.Converters
 import com.grkj.data.model.dos.SysUserCharacteristicDo
 import com.grkj.data.model.dos.SysUserDo
@@ -175,40 +176,38 @@ interface UserDao {
     /**
      * 获取所有用户数据
      */
-    @Query(
-        """
-        SELECT
-          su.user_id                         AS userId,
-          su.nick_name                       AS nickName,
-          su.user_name                       AS userName,
-          su.password,
-              su.avatar,
-    
-          -- 聚合所有卡号(如果没卡则结果里 cardCodes 会是 NULL 或空)
-          GROUP_CONCAT(DISTINCT ijc.card_code)       AS cardCodes,
-    
-          GROUP_CONCAT(DISTINCT sr.role_id)           AS roleIds,
-          GROUP_CONCAT(DISTINCT sr.role_name)         AS roleNames,
-          GROUP_CONCAT(DISTINCT sr.role_key)          AS roleKeys,
-          GROUP_CONCAT(DISTINCT iw.workstation_id)    AS workstationIds,
-          GROUP_CONCAT(DISTINCT iw.workstation_name)  AS workstationNames,
-    
-          su.status                            AS status
-        FROM sys_user su
-        LEFT JOIN sys_user_role sur ON su.user_id = sur.user_id
-        LEFT JOIN sys_role sr       ON sur.role_id = sr.role_id
-        LEFT JOIN is_job_card ijc   ON ijc.user_id = su.user_id
-        LEFT JOIN is_user_workstation iuw ON iuw.user_id = su.user_id
-        LEFT JOIN is_workstation iw ON iw.workstation_id = iuw.workstation_id
-        WHERE su.del_flag = 0
-        AND iw.workstation_id = :workstationId
-        GROUP BY
-          su.user_id,
-          su.nick_name,
-          su.status
-    """
-    )
-    fun getAllUserDataWithWorkstationId(workstationId: Long): List<UserManageVo>
+    @Query("""
+    SELECT
+      su.user_id AS userId,
+      su.nick_name AS nickName,
+      su.user_name AS userName,
+      su.password,
+      su.avatar,
+      GROUP_CONCAT(DISTINCT ijc.card_code) AS cardCodes,
+      GROUP_CONCAT(DISTINCT sr.role_id) AS roleIds,
+      GROUP_CONCAT(DISTINCT sr.role_name) AS roleNames,
+      GROUP_CONCAT(DISTINCT sr.role_key) AS roleKeys,
+      GROUP_CONCAT(DISTINCT iw.workstation_id) AS workstationIds,
+      GROUP_CONCAT(DISTINCT iw.workstation_name) AS workstationNames,
+      su.status AS status
+    FROM sys_user su
+    LEFT JOIN sys_user_role sur ON su.user_id = sur.user_id
+    LEFT JOIN sys_role sr ON sur.role_id = sr.role_id
+    LEFT JOIN is_job_card ijc ON ijc.user_id = su.user_id
+    LEFT JOIN is_user_workstation iuw ON iuw.user_id = su.user_id
+    LEFT JOIN is_workstation iw ON iw.workstation_id = iuw.workstation_id
+    WHERE su.del_flag = 0
+      AND (
+            iw.workstation_id = :workstationId
+            OR (:includeDescendants = 1 AND (','||ifnull(iw.ancestors,'')||',') LIKE '%,'||:workstationId||',%')
+          )
+    GROUP BY su.user_id,su.nick_name,su.status
+    """)
+    fun getAllUserDataWithWorkstationId(
+        workstationId: Long,
+        includeDescendants: Int = ISCSConfig.includeDescendants
+    ): List<UserManageVo>
+
 
     /**
      * 更新用户数据

+ 4 - 0
data/src/main/java/com/grkj/data/data/EventConstants.kt

@@ -73,6 +73,10 @@ object EventConstants {
      * 刷新头像
      */
     const val EVENT_REFRESH_AVATAR_CODE: Int = 100_000_014
+    /**
+     * 启动监听
+     */
+    const val EVENT_START_LISTENER = 100_000_015
 
     //---------------------------作业票------------------------
     const val EVENT_GET_TICKET_STATUS: Int = 100_001_001

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

@@ -69,4 +69,9 @@ object MMKVConstants {
      * 硬件模式
      */
     const val KEY_HARDWARE_MODE = "key_hardware_mode"
+
+    /**
+     * 是否能查看区域下级目录
+     */
+    const val KEY_INCLUDE_DESCENDANTS = "key_include_descendants"
 }

+ 31 - 6
data/src/main/java/com/grkj/data/hardware/IHardwareHelper.kt

@@ -6,6 +6,12 @@ import com.grkj.data.model.res.CabinetSlotsRecord
  * 硬件操作帮助类
  */
 interface IHardwareHelper {
+
+    /**
+     * 连接并监听
+     */
+    fun connectAndAddListener()
+
     /**
      * 根据钥匙的RFID获取MAC地址
      */
@@ -81,12 +87,13 @@ interface IHardwareHelper {
         isOpen: Boolean,
         idx: Int,
         slaveAddress: Int?,
-        done: ((res: ByteArray) -> Unit)? = null)
+        done: ((res: ByteArray) -> Unit)? = null
+    )
 
     /**
      * 控制钥匙仓位根据mac地址
      */
-    fun controlKeyBuckle(isOpen: Boolean,mac: String,done: ((ByteArray) -> Unit)? = null)
+    fun controlKeyBuckle(isOpen: Boolean, mac: String, done: ((ByteArray) -> Unit)? = null)
 
     /**
      * 控制仓位充电
@@ -95,7 +102,8 @@ interface IHardwareHelper {
         isOpen: Boolean,
         idx: Int,
         slaveAddress: Int?,
-        done: ((res: ByteArray) -> Unit)? = null)
+        done: ((res: ByteArray) -> Unit)? = null
+    )
 
     /**
      * 根据mac地址开关充电
@@ -113,7 +121,9 @@ interface IHardwareHelper {
         isOpen: Boolean,
         slaveAddress: Int?,
         lockIdxList: Int,
-        done: ((res: ByteArray) -> Unit)? = null)
+        done: ((res: ByteArray) -> Unit)? = null
+    )
+
     /**
      * 批量控制锁仓
      */
@@ -121,12 +131,17 @@ interface IHardwareHelper {
         isOpen: Boolean,
         slaveAddress: Int?,
         lockIdxList: MutableList<Int>,
-        done: ((res: ByteArray) -> Unit)? = null)
+        done: ((res: ByteArray) -> Unit)? = null
+    )
 
     /**
      * 读取钥匙的rfid
      */
-    fun readKeyRfidStr(slaveAddress: Int?, idx: Int, done: ((idx: Int, res: String) -> Unit)? = null)
+    fun readKeyRfidStr(
+        slaveAddress: Int?,
+        idx: Int,
+        done: ((idx: Int, res: String) -> Unit)? = null
+    )
 
     /**
      * 读取挂锁rfid
@@ -177,4 +192,14 @@ interface IHardwareHelper {
      * 更新钥匙mac
      */
     fun updateKeyMac(rfid: String, mac: String)
+
+    /**
+     * 全仓位开
+     */
+    fun allSlotOn()
+
+    /**
+     * 全仓位关
+     */
+    fun allSlotOff()
 }

+ 46 - 2
data/src/main/java/com/grkj/data/hardware/can/CanHardwareHelper.kt

@@ -1,22 +1,28 @@
 package com.grkj.data.hardware.can
 
+import android.app.Application
 import com.grkj.data.hardware.DockData
 import com.grkj.data.hardware.IHardwareHelper
 import com.grkj.data.hardware.modbus.DeviceConst
 import com.grkj.data.model.res.CabinetSlotsRecord
+import com.grkj.data.utils.event.StartListenerEvent
 import com.grkj.shared.utils.extension.toHexFromLe
-import com.grkj.shared.utils.face.arcsoft.CameraHelper
 import com.grkj.shared.utils.i18n.I18nManager
 import com.sik.comm.impl_can.toCommMessage
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
-import kotlin.math.log
+import java.lang.ref.WeakReference
 
 /**
  * Can硬件读写帮助类
  */
 class CanHardwareHelper : IHardwareHelper {
     private val logger: Logger = LoggerFactory.getLogger(CanHardwareHelper::class.java)
+    override fun connectAndAddListener() {
+        CanHelper.connect()
+        StartListenerEvent.sendStartListenerEvent()
+    }
+
     override fun getKeyMacByRfid(rfid: String): String? {
         return CanHelper.getKeyByRfid(rfid)?.mac
     }
@@ -75,6 +81,44 @@ class CanHardwareHelper : IHardwareHelper {
         CanHelper.getKeyByRfid(rfid)?.mac = mac
     }
 
+    override fun allSlotOn() {
+        getDockData().forEach {
+            val req = when (it.type) {
+                CanDeviceConst.DEVICE_KEY_DOCK -> {
+                    CanCommands.forDevice(it.addr).setLatch(true, true)
+                }
+
+                CanDeviceConst.DEVICE_LOCK_DOCK -> {
+                    CanCommands.forDevice(it.addr).setLatchBits_1to5(0b1_1111, true)
+                }
+
+                else -> null
+            }
+            req?.let {
+                CanHelper.writeTo(it)
+            }
+        }
+    }
+
+    override fun allSlotOff() {
+        getDockData().forEach {
+            val req = when (it.type) {
+                CanDeviceConst.DEVICE_KEY_DOCK -> {
+                    CanCommands.forDevice(it.addr).setLatch(true, true)
+                }
+
+                CanDeviceConst.DEVICE_LOCK_DOCK -> {
+                    CanCommands.forDevice(it.addr).setLatchBits_1to5(0b1_1111, true)
+                }
+
+                else -> null
+            }
+            req?.let {
+                CanHelper.writeTo(it)
+            }
+        }
+    }
+
     override fun controlKeyBuckle(
         isOpen: Boolean,
         mac: String,

+ 59 - 53
data/src/main/java/com/grkj/data/hardware/modbus/ModBusController.kt

@@ -212,65 +212,71 @@ object ModBusController {
         logger.info("initKey : $dockList")
         dockList.filter { it.type == DeviceConst.DOCK_TYPE_KEY || it.type == DeviceConst.DOCK_TYPE_PORTABLE }
             .forEach { dockBean ->
-                dockBean.getKeyList().forEach { key ->
-                    if (key.isExist) {
-                        logger.info("initKey : ${dockBean.addr} : ${key.idx == 0}")
-                        readKeyRfid(dockBean.addr, key.idx) { idx, res ->
-                            if (res.size < 11) {
-                                logger.error("Key rfid error")
-                                return@readKeyRfid
-                            }
-                            val rfid =
-                                res.copyOfRange(3, 11).toHexStrings(false).removeLeadingZeros()
-                            logger.info("初始化钥匙 RFID : $rfid")
-                            // 更新rfid
-                            updateKeyRfid(dockBean.addr, key.idx, rfid)
-                            // 蓝牙准备操作
-                            LogicManager.hardwareLogic.getKeyInfo(rfid) { keyInfo ->
-                                logger.info("getKeyInfo : $rfid - ${keyInfo?.macAddress}")
-                                updateKeyNewHardware(
-                                    dockBean.addr,
-                                    key.idx,
-                                    keyInfo == null || keyInfo.keyNfc?.isEmpty() == true || keyInfo.macAddress?.isEmpty() == true
-                                )
-                                if (keyInfo != null && !keyInfo.macAddress.isNullOrEmpty()) {
-                                    // 更新mac
-                                    updateKeyMac(dockBean.addr, key.idx, keyInfo.macAddress!!)
-                                    //已经初始化完成才会去连接
-                                    if (ISCSConfig.isInit) {
-                                        controlKeyCharge(true, key.idx, dockBean.addr)
-                                    }
-                                    controlKeyBuckle(false, key.idx, dockBean.addr)
-                                } else {
-                                    if (ISCSConfig.isInit) {
-                                        ToastEvent.sendToastEvent(I18nManager.t("get_key_info_fail"))
+                if (dockBean.getKeyList().isEmpty()){
+                    ISCSConfig.canInitDevice = true
+                    ModbusInitCompleteEvent.sendModbusInitCompleteEvent()
+                }else{
+                    dockBean.getKeyList().forEach { key ->
+                        if (key.isExist) {
+                            logger.info("initKey : ${dockBean.addr} : ${key.idx == 0}")
+                            readKeyRfid(dockBean.addr, key.idx) { idx, res ->
+                                if (res.size < 11) {
+                                    logger.error("Key rfid error")
+                                    return@readKeyRfid
+                                }
+                                val rfid =
+                                    res.copyOfRange(3, 11).toHexStrings(false).removeLeadingZeros()
+                                logger.info("初始化钥匙 RFID : $rfid")
+                                // 更新rfid
+                                updateKeyRfid(dockBean.addr, key.idx, rfid)
+                                // 蓝牙准备操作
+                                LogicManager.hardwareLogic.getKeyInfo(rfid) { keyInfo ->
+                                    logger.info("getKeyInfo : $rfid - ${keyInfo?.macAddress}")
+                                    updateKeyNewHardware(
+                                        dockBean.addr,
+                                        key.idx,
+                                        keyInfo == null || keyInfo.keyNfc?.isEmpty() == true || keyInfo.macAddress?.isEmpty() == true
+                                    )
+                                    if (keyInfo != null && !keyInfo.macAddress.isNullOrEmpty()) {
+                                        // 更新mac
+                                        updateKeyMac(dockBean.addr, key.idx, keyInfo.macAddress!!)
+                                        //已经初始化完成才会去连接
+                                        if (ISCSConfig.isInit) {
+                                            controlKeyCharge(true, key.idx, dockBean.addr)
+                                        }
+                                        controlKeyBuckle(false, key.idx, dockBean.addr)
+                                    } else {
+                                        if (ISCSConfig.isInit) {
+                                            ToastEvent.sendToastEvent(I18nManager.t("get_key_info_fail"))
+                                        }
+                                        controlKeyBuckle(true, key.idx, dockBean.addr)
                                     }
-                                    controlKeyBuckle(true, key.idx, dockBean.addr)
+
                                 }
-                                val isKeyReady =
-                                    dockList.filter { it.type == DeviceConst.DOCK_TYPE_KEY || it.type == DeviceConst.DOCK_TYPE_PORTABLE }
+                            }
+                        } else {
+                            controlKeyBuckle(true, key.idx, dockBean.addr)
+                        }
+                        val isKeyReady =
+                            dockList.filter { it.type == DeviceConst.DOCK_TYPE_KEY || it.type == DeviceConst.DOCK_TYPE_PORTABLE }
+                                .all {
+                                    it.deviceList.filter { it.type == DeviceConst.DEVICE_TYPE_KEY }
+                                        .filterIsInstance<DockBean.KeyBean>()
+                                        .filter { it.isExist }
                                         .all {
-                                            it.deviceList.filter { it.type == DeviceConst.DEVICE_TYPE_KEY }
-                                                .filterIsInstance<DockBean.KeyBean>()
-                                                .filter { it.isExist }
-                                                .all {
-                                                    logger.info("钥匙信息:${it.rfid}")
-                                                    it.rfid != null
-                                                }
+                                            logger.info("钥匙信息:${it.rfid}")
+                                            it.rfid != null
                                         }
-                                logger.info("钥匙是否准备完毕:${isKeyReady},${ISCSConfig.isInit}")
-                                if (isKeyReady && ISCSConfig.isInit) {
-                                    ISCSConfig.canInitDevice = true
-                                    logger.info("发送初始化完成事件")
-                                    ModbusInitCompleteEvent.sendModbusInitCompleteEvent()
-                                } else if (isKeyReady) {
-                                    ISCSConfig.canInitDevice = true
-                                    ModbusInitCompleteEvent.sendModbusInitCompleteEvent()
                                 }
-                            }
+                        logger.info("钥匙是否准备完毕:${isKeyReady},${ISCSConfig.isInit}")
+                        if (isKeyReady && ISCSConfig.isInit) {
+                            ISCSConfig.canInitDevice = true
+                            logger.info("发送初始化完成事件")
+                            ModbusInitCompleteEvent.sendModbusInitCompleteEvent()
+                        } else if (isKeyReady) {
+                            ISCSConfig.canInitDevice = true
+                            ModbusInitCompleteEvent.sendModbusInitCompleteEvent()
                         }
-                    } else {
-                        controlKeyBuckle(true, key.idx, dockBean.addr)
                     }
                 }
             }

+ 15 - 0
data/src/main/java/com/grkj/data/hardware/modbus/ModBusHardwareHelper.kt

@@ -3,6 +3,7 @@ package com.grkj.data.hardware.modbus
 import com.grkj.data.hardware.DockData
 import com.grkj.data.hardware.IHardwareHelper
 import com.grkj.data.model.res.CabinetSlotsRecord
+import com.grkj.data.utils.event.StartModbusEvent
 import com.grkj.shared.utils.i18n.I18nManager
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
@@ -12,6 +13,10 @@ import org.slf4j.LoggerFactory
  */
 class ModBusHardwareHelper : IHardwareHelper {
     private val logger: Logger = LoggerFactory.getLogger(ModBusHardwareHelper::class.java)
+    override fun connectAndAddListener() {
+        StartModbusEvent.sendStartModbusEvent()
+    }
+
     override fun getKeyMacByRfid(rfid: String): String? {
         return ModBusController.getKeyByRfid(
             rfid
@@ -88,6 +93,16 @@ class ModBusHardwareHelper : IHardwareHelper {
         }
     }
 
+    override fun allSlotOff() {
+        ModBusController.controlAllKeyChargeDown()
+        ModBusController.controlAllLockBuckles(false)
+    }
+
+    override fun allSlotOn() {
+        ModBusController.controlAllKeyBuckleOpen()
+        ModBusController.controlAllLockBuckles(true)
+    }
+
     override fun updateKeyMac(rfid: String, mac: String) {
         ModBusController.getKeyByRfid(rfid)?.mac = mac
     }

+ 25 - 0
data/src/main/java/com/grkj/data/utils/event/StartListenerEvent.kt

@@ -0,0 +1,25 @@
+package com.grkj.data.utils.event
+
+import com.grkj.data.data.EventConstants
+import com.grkj.shared.model.EventBean
+import com.grkj.shared.utils.event.EventHelper
+
+/**
+ * 启动监听
+ */
+class StartListenerEvent() {
+
+    companion object {
+        /**
+         * 发送启动监听事件
+         */
+        @JvmStatic
+        fun sendStartListenerEvent() {
+            val startListenerEvent = StartListenerEvent()
+            val startListenerEventBean = EventBean<StartListenerEvent>(
+                EventConstants.EVENT_START_LISTENER, startListenerEvent
+            )
+            EventHelper.sendEvent(startListenerEventBean)
+        }
+    }
+}

+ 2 - 2
ui-base/src/main/java/com/grkj/ui_base/utils/event/StartModbusEvent.kt → data/src/main/java/com/grkj/data/utils/event/StartModbusEvent.kt

@@ -1,7 +1,7 @@
-package com.grkj.ui_base.utils.event
+package com.grkj.data.utils.event
 
-import com.grkj.shared.model.EventBean
 import com.grkj.data.data.EventConstants
+import com.grkj.shared.model.EventBean
 import com.grkj.shared.utils.event.EventHelper
 
 /**