Kaynağa Gözat

feat: Add CAN bus communication and device models

- Add CAN bus communication helper `CanHelper` for connecting, reading, and writing data.
- Add device models `DeviceModel` and `CanDeviceConst` for different hardware types.
- Add `CanReadyPlugin` for polling device status and notifying listeners.
- Add `DeviceParseStatus` for parsing device status data.
- Integrate CAN communication into the application lifecycle.
- Update CAN command definitions and data packing.
- Remove debug logging and test code.
周文健 2 ay önce
ebeveyn
işleme
cdb9dfca87

+ 4 - 0
app/src/main/java/com/grkj/iscs_mc/ISCSMCApplication.kt

@@ -10,6 +10,7 @@ import com.drake.statelayout.StateConfig
 import com.grkj.data.common.EventConstants
 import com.grkj.data.di.LogicManager
 import com.grkj.data.hardware.can.CanHelper
+import com.grkj.data.hardware.can.CanReadyPlugin
 import com.grkj.data.hardware.can.CustomCanConfig
 import com.grkj.data.local.database.DbReadyGate
 import com.grkj.iscs_mc.features.splash.activity.SplashActivity
@@ -94,6 +95,9 @@ class ISCSMCApplication : Application() {
             DbReadyGate.await()
             LogicManager.init(this@ISCSMCApplication)
             CanHelper.connect()
+            CanHelper.addDeviceChangeListener(this) {
+                logger.info("有设备更新:${it}")
+            }
         }
     }
 

+ 0 - 9
app/src/main/java/com/grkj/iscs_mc/features/login/fragment/LoginFragment.kt

@@ -133,15 +133,6 @@ class LoginFragment : BaseFragment<FragmentLoginBinding>() {
             FaceEngine.getActiveDeviceInfo(requireContext(), activeDeviceInfo)
             ShellUtils.execCmd("echo ${activeDeviceInfo.deviceInfo} > /sdcard/iscs/activeDeviceInfo.txt")
         }
-        binding.titleEn.setDebouncedClickListener {
-            ThreadUtils.runOnIO {
-                val rsp = runBlocking(Dispatchers.IO) {
-                    CanHelper.writeTo(CanCommands.forDevice(1).controlLatch(0, 1)) {
-                        logger.info("CAN返回:${it}")
-                    }
-                }
-            }
-        }
         binding.tecSupport.setOnClickListener {
             currentSupportClickTime++
             if (currentSupportClickTime > 10) {

+ 90 - 17
data/src/main/java/com/grkj/data/hardware/can/CanCommand.kt

@@ -28,15 +28,82 @@ object CanCommands {
         WRITE_ERROR = 0x80
     )
 
+    /**
+     * 指令 主索引
+     */
+    object Command {
+        /**
+         * 设备类型
+         */
+        const val DEVICE_TYPE = 0x6000
+
+        /**
+         * 版本号
+         */
+        const val VERSION = 0x6003
+
+        /**
+         * 状态
+         */
+        const val STATUS = 0x6010
+
+        /**
+         * 控制状态
+         */
+        const val CONTROL_REG = 0x6011
+
+        /**
+         * 物资柜存储状态
+         */
+        const val STORAGE_REG = 0x6012
+
+        /**
+         * 照明/消毒
+         */
+        const val LIGHT_REG = 0x6015
+
+        /**
+         * 灯带控制
+         */
+        const val LED_STRIP_CONTROL = 0x6016
+
+        /**
+         * 温度
+         */
+        const val TEMPERATURE = 0x6017
+
+        /**
+         * 湿度
+         */
+        const val HUMIDITY = 0x6018
+
+        /**
+         * 左钥匙rfid/挂锁RFID/钥匙柜RFID
+         */
+        const val RFID = 0x6020
+
+        /**
+         * 右钥匙RFID
+         */
+        const val RIGHT_KEY_RFID = 0x6024
+
+    }
+
     // ========= 通用区:所有节点都能用 =========
     object Common {
         /** 版本 (R) → 0x6003/0x00, 4B: HW主,HW子,SW主,SW子 */
         fun getDeviceVersion(nodeId: Int): SdoRequest.Read =
-            SdoRequest.Read(nodeId, 0x6003, 0x00)
+            SdoRequest.Read(nodeId, Command.VERSION, 0x00)
 
         /** 大多数设备复用的状态寄存器 (R) → 0x6010/0x00, 2B */
         fun getStatus(nodeId: Int): SdoRequest.Read =
-            SdoRequest.Read(nodeId, 0x6010, 0x00)
+            SdoRequest.Read(nodeId, Command.STATUS, 0x00)
+
+        /**
+         * 获取设备类型
+         */
+        fun getDeviceType(nodeId: Int): SdoRequest.Read =
+            SdoRequest.Read(nodeId, Command.DEVICE_TYPE, 0x00)
     }
 
     /**
@@ -51,14 +118,14 @@ object CanCommands {
 
         /** 控制/状态 (R/W) 0x6011/0x00, 2B:写仅置相关位,其余写0;读回含工作位 */
         fun readControlReg(): SdoRequest.Read =
-            SdoRequest.Read(nodeId, 0x6011, 0x00)
+            SdoRequest.Read(nodeId, Command.CONTROL_REG, 0x00)
 
         /** 设置左右卡扣(bit0=左卡扣,bit4=右卡扣) */
         fun setLatch(left: Boolean? = null, right: Boolean? = null): SdoRequest.Write {
             var v = 0
             if (left != null) v = v or ((if (left) 1 else 0) shl 0)
             if (right != null) v = v or ((if (right) 1 else 0) shl 4)
-            return SdoRequest.Write(nodeId, 0x6011, 0x00, shortLE(0b0001_0001, v), 2)
+            return SdoRequest.Write(nodeId, Command.CONTROL_REG, 0x00, shortLE(v, 0b0001_0001), 2)
         }
 
         /** 设置左右充电(bit1=左充电,bit5=右充电) */
@@ -66,7 +133,7 @@ object CanCommands {
             var v = 0
             if (leftOn != null) v = v or ((if (leftOn) 1 else 0) shl 1)
             if (rightOn != null) v = v or ((if (rightOn) 1 else 0) shl 5)
-            return SdoRequest.Write(nodeId, 0x6011, 0x00, shortLE(0b0010_0010, v), 2)
+            return SdoRequest.Write(nodeId, Command.CONTROL_REG, 0x00, shortLE(v, 0b0010_0010), 2)
         }
 
         /** 单侧卡扣语法糖:keySlotId: 0左/1右;status: 0解锁/1锁住 */
@@ -76,42 +143,48 @@ object CanCommands {
             val bit = if (keySlotId == 0) 0 else 4
             return SdoRequest.Write(
                 nodeId,
-                0x6011,
+                Command.CONTROL_REG,
                 0x00,
-                shortLE(1 shl bit, (status and 1) shl bit),
+                shortLE((status and 1) shl bit, 1 shl bit),
                 2
             )
         }
 
         /** 左/右 RFID (R) 4B 小端(常见地址) */
-        fun getLeftRfid(): SdoRequest.Read = SdoRequest.Read(nodeId, 0x6020, 0x00)
-        fun getRightRfid(): SdoRequest.Read = SdoRequest.Read(nodeId, 0x6024, 0x00)
+        fun getLeftRfid(): SdoRequest.Read = SdoRequest.Read(nodeId, Command.RFID, 0x00)
+        fun getRightRfid(): SdoRequest.Read = SdoRequest.Read(nodeId, Command.RIGHT_KEY_RFID, 0x00)
 
         // ---- FiveLock / KeyCabinet(常见 1..5 位同构写法,寄存器通常与 0x6011 兼容) ----
 
         /** 一次写入 5 位控制(低5位有效),适配 5路/柜体同构 */
         fun setLatchBits_1to5(bits01to05: Int): SdoRequest.Write {
             val v = bits01to05 and 0b1_1111
-            return SdoRequest.Write(nodeId, 0x6011, 0x00, shortLE(0b1_1111, v), 2)
+            return SdoRequest.Write(nodeId, Command.CONTROL_REG, 0x00, shortLE(v, 0b1_1111), 2)
         }
 
         /** 单位控制(1..5) */
         fun controlOne_1to5(slotIndex1to5: Int, locked: Boolean): SdoRequest.Write {
             require(slotIndex1to5 in 1..5) { "slotIndex must be 1..5" }
             val v = (if (locked) 1 else 0) shl (slotIndex1to5 - 1)
-            return SdoRequest.Write(nodeId, 0x6011, 0x00, shortLE(1 shl (slotIndex1to5 - 1), v), 2)
+            return SdoRequest.Write(
+                nodeId,
+                Command.CONTROL_REG,
+                0x00,
+                shortLE(v, 1 shl (slotIndex1to5 - 1)),
+                2
+            )
         }
 
         /** 1..5 位 RFID 常见映射:0x6020..0x6024 */
         fun getSlotRfid_1to5(slotIndex1to5: Int): SdoRequest.Read {
             require(slotIndex1to5 in 1..5) { "slotIndex must be 1..5" }
-            return SdoRequest.Read(nodeId, 0x6020 + (slotIndex1to5 - 1), 0x00)
+            return SdoRequest.Read(nodeId, Command.RFID, 0x00 + (slotIndex1to5 - 1))
         }
 
         // ---- MaterialCabinet(RGB/温湿度扩展) ----
 
         /** RGB 状态灯 (R/W) 0x6016/0x00, 4B: B[0..7],G[8..15],R[16..23], 模式[24..26], 时间[27..29], 单位[30], 锁定[31] */
-        fun getRgb(): SdoRequest.Read = SdoRequest.Read(nodeId, 0x6016, 0x00)
+        fun getRgb(): SdoRequest.Read = SdoRequest.Read(nodeId, Command.LED_STRIP_CONTROL, 0x00)
 
         fun setRgb(
             r: Int, g: Int, b: Int,      // 0..255
@@ -133,12 +206,12 @@ object CanCommands {
             v = v or ((tt and 0x07) shl 27)
             v = v or ((if (secondsUnit) 1 else 0) shl 30)
             v = v or ((if (lockControl) 1 else 0) shl 31)
-            return SdoRequest.Write(nodeId, 0x6016, 0x00, intLE(v), 4)
+            return SdoRequest.Write(nodeId, Command.LED_STRIP_CONTROL, 0x00, intLE(v), 4)
         }
 
         /** 温湿度(常见扩展) */
-        fun getTemperature(): SdoRequest.Read = SdoRequest.Read(nodeId, 0x6017, 0x00)
-        fun getHumidity(): SdoRequest.Read = SdoRequest.Read(nodeId, 0x6018, 0x00)
+        fun getTemperature(): SdoRequest.Read = SdoRequest.Read(nodeId, Command.TEMPERATURE, 0x00)
+        fun getHumidity(): SdoRequest.Read = SdoRequest.Read(nodeId, Command.HUMIDITY, 0x00)
 
         // ---- 通用状态 ----
         fun getStatus(): SdoRequest.Read = Common.getStatus(nodeId)
@@ -147,7 +220,7 @@ object CanCommands {
 
     // ========= Byte 打包工具(LE) =========
     private fun shortLE(control: Int, target: Int): ByteArray =
-        byteArrayOf((target and 0xFF).toByte(), (control and 0xFF).toByte())
+        byteArrayOf((control and 0xFF).toByte(), (target and 0xFF).toByte())
 
     private fun intLE(v: Int): ByteArray = byteArrayOf(
         (v and 0xFF).toByte(),

+ 81 - 0
data/src/main/java/com/grkj/data/hardware/can/CanDeviceConst.kt

@@ -0,0 +1,81 @@
+package com.grkj.data.hardware.can
+
+/**
+ * 硬件常量
+ */
+object CanDeviceConst {
+    /**
+     * 电子钥匙底座
+     */
+    const val DEVICE_KEY_DOCK = 0
+
+    /**
+     * 5路挂锁底座
+     */
+    const val DEVICE_LOCK_DOCK = 1
+
+    /**
+     * 钥匙柜控制板
+     */
+    const val DEVICE_KEY_CABINET_CONTROL_BOARD = 2
+
+    /**
+     * 物资柜主控制板
+     */
+    const val DEVICE_MATERIAL_CABINET_CONTROL_BOARD = 3
+
+    /**
+     * 设备变化,初始化
+     */
+    const val DEVICE_CHANGE_INIT = 1
+
+    /**
+     * 设备变化,钥匙归还
+     */
+    const val DEVICE_CHANGE_KEY_RETURN = 1 shl 1
+
+    /**
+     * 设备变化,钥匙取出
+     */
+    const val DEVICE_CHANGE_KEY_TAKE = 1 shl 2
+
+    /**
+     * 设备变化,上锁
+     */
+    const val DEVICE_CHANGE_LOCKED = 1 shl 3
+
+    /**
+     * 设备变化,解锁
+     */
+    const val DEVICE_CHANGE_UNLOCK = 1 shl 4
+
+    /**
+     * 设备变化,左门打开
+     */
+    const val DEVICE_CHANGE_MATERIAL_LEFT_DOOR_OPEN = 1 shl 5
+
+    /**
+     * 设备变化,右门打开
+     */
+    const val DEVICE_CHANGE_MATERIAL_RIGHT_DOOR_OPEN = 1 shl 6
+
+    /**
+     * 设备变化,左门关闭
+     */
+    const val DEVICE_CHANGE_MATERIAL_LEFT_DOOR_CLOSED = 1 shl 7
+
+    /**
+     * 设备变化,右门关闭
+     */
+    const val DEVICE_CHANGE_MATERIAL_RIGHT_DOOR_CLOSED = 1 shl 8
+
+    /**
+     * 设备变化,锁归还
+     */
+    const val DEVICE_CHANGE_LOCK_RETURN = 1 shl 9
+
+    /**
+     * 设备变化,锁取出
+     */
+    const val DEVICE_CHANGE_LOCK_TAKE = 1 shl 10
+}

+ 98 - 5
data/src/main/java/com/grkj/data/hardware/can/CanHelper.kt

@@ -28,12 +28,96 @@ object CanHelper {
     /**
      * 节点对应的设备类型列表
      */
-    private val nodeMap: HashMap<Int, CanCommands.GenericCommands> = hashMapOf()
+    private val nodeMap: HashMap<Int, Int> = hashMapOf()
 
     /**
-     * 扫描节点范围
+     * 设备数据
      */
-    private val scanRange: IntRange = 1..2
+    private val deviceData: HashMap<Int, List<DeviceModel>> = hashMapOf()
+
+    /**
+     * 设备变更监听器
+     */
+    private val deviceChangeListeners: HashMap<Any, (List<DeviceModel>) -> Unit> = hashMapOf()
+
+    /**
+     * 设备状态监听器
+     */
+    private val deviceStatusListener: CanReadyPlugin.DeviceStatusListener =
+        object : CanReadyPlugin.DeviceStatusListener {
+
+            override fun deviceStatus(nodeId: Int, index: Int, statusData: ByteArray) {
+                when (nodeMap[nodeId]) {
+                    CanDeviceConst.DEVICE_KEY_DOCK -> {
+                        DeviceParseStatus.parseKeyDockStatus(nodeId, index, statusData)
+                    }
+
+                    CanDeviceConst.DEVICE_LOCK_DOCK -> {
+                        DeviceParseStatus.parseLockDockStatus(nodeId, index, statusData)
+
+                    }
+
+                    CanDeviceConst.DEVICE_KEY_CABINET_CONTROL_BOARD -> {
+                        DeviceParseStatus.parseKeyCabinetControlBoardStatus(
+                            nodeId,
+                            index,
+                            statusData
+                        )
+
+                    }
+
+                    CanDeviceConst.DEVICE_MATERIAL_CABINET_CONTROL_BOARD -> {
+                        DeviceParseStatus.parseMaterialCabinetControlBoardStatus(
+                            nodeId,
+                            index,
+                            statusData
+                        )
+
+                    }
+                }
+            }
+        }
+
+    /**
+     * 添加设备变更监听器
+     */
+    fun addDeviceChangeListener(key: Any, listener: (List<DeviceModel>) -> Unit) {
+        deviceChangeListeners.put(key, listener)
+    }
+
+    /**
+     * 移除设备变更监听器
+     */
+    fun removeDeviceChangeListener(key: Any) {
+        deviceChangeListeners.remove(key)
+    }
+
+    /**
+     * 根据节点id获取设备
+     */
+    fun getDeviceByNodeId(nodeId: Int): List<DeviceModel> {
+        return deviceData[nodeId] ?: emptyList()
+    }
+
+    /**
+     * 根据设备类型获取设备
+     */
+    fun getDeviceByDeviceType(deviceType: Int): List<DeviceModel> {
+        return deviceData.flatMap { it.value }.filter { it.deviceType == deviceType }
+    }
+
+    /**
+     * 更新硬件数据
+     */
+    fun updateDeviceData(nodeId: Int, deviceData: List<DeviceModel>) {
+        this.deviceData[nodeId] = deviceData
+        if (deviceData.any { it.deviceChange != 0 }) {
+            deviceChangeListeners.forEach {
+                it.value.invoke(deviceData.filter { it.deviceChange != 0 })
+            }
+            deviceData.forEach { it.deviceChange = 0 }
+        }
+    }
 
     /**
      * 连接
@@ -44,6 +128,7 @@ object CanHelper {
         // 绑定配置(每个节点一个 deviceId,建议 "can0@<nodeId>")
         ProtocolManager.bindDeviceConfig(CustomCanConfig.instance)
         canProtocol.registerConfig(CustomCanConfig.instance)
+        CanReadyPlugin.registerDeviceStatusListener(this, deviceStatusListener)
         // 连接
         ProtocolManager.connect(CustomCanConfig.instance.deviceId)
     }
@@ -51,14 +136,21 @@ object CanHelper {
     /**
      * 根据设备类型获取节点id
      */
-    fun getNodeIdByDeviceType(deviceType: CanCommands.GenericCommands): List<Int> {
+    fun getNodeIdByDeviceType(deviceType: Int): List<Int> {
         return nodeMap.filter { it.value == deviceType }.map { it.key }
     }
 
+    /**
+     * 添加节点
+     */
+    fun addNode(nodeId: Int, deviceType: Int) {
+        nodeMap.put(nodeId, deviceType)
+    }
+
     /**
      * 读取
      */
-    fun readFrom(req: SdoRequest.Read, callback: (SdoResponse.ReadData) -> Unit) {
+    fun readFrom(req: SdoRequest.Read, callback: (SdoResponse.ReadData?) -> Unit) {
         scope.launch(Dispatchers.IO) {
             runCatching {
                 ProtocolManager.getProtocol(CustomCanConfig.instance.deviceId)
@@ -67,6 +159,7 @@ object CanHelper {
                 callback(rsp.toSdoResponse() as SdoResponse.ReadData)
             }.onFailure {
                 logger.info("读取失败:${it}")
+                callback(null)
             }
         }
     }

+ 158 - 0
data/src/main/java/com/grkj/data/hardware/can/CanReadyPlugin.kt

@@ -0,0 +1,158 @@
+package com.grkj.data.hardware.can
+
+import com.sik.comm.core.model.ProtocolState
+import com.sik.comm.core.plugin.CommPlugin
+import com.sik.comm.core.plugin.PluginScope
+import com.sik.comm.impl_can.SdoRequest
+import com.sik.comm.impl_can.SdoResponse
+import kotlinx.coroutines.*
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+class CanReadyPlugin : CommPlugin {
+    private val logger: Logger = LoggerFactory.getLogger(CanReadyPlugin::class.java)
+
+    /** 轮询哪些节点 */
+    private val scanRange: IntRange = 1..6
+    private val activeNodes = mutableSetOf<Int>()
+
+    /** 周期 */
+    private val pollMsStatus = 200L   // 每节点读 0x6010 的间隔
+
+    /** 协程域 & 单个轮询任务 */
+    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
+    private var pollJob: Job? = null
+
+    companion object {
+
+        /**
+         * 设备状态监听
+         */
+        private val deviceStatusListener: HashMap<Any, DeviceStatusListener> = hashMapOf()
+
+        /**
+         * 注册监听
+         */
+        fun registerDeviceStatusListener(key: Any, listener: DeviceStatusListener) {
+            deviceStatusListener[key] = listener
+        }
+
+        /**
+         * 移除监听
+         */
+        fun unRegisterDeviceStatusListener(key: Any) {
+            deviceStatusListener.remove(key)
+        }
+    }
+
+    override fun onStateChanged(scopeP: PluginScope) {
+        super.onStateChanged(scopeP)
+        when (scopeP.state) {
+            ProtocolState.READY -> startPollingSingleLoop()
+            ProtocolState.DISCONNECTED -> stopPolling()
+            else -> Unit
+        }
+    }
+
+    /** 单协程,逐节点轮询 */
+    private fun startPollingSingleLoop() {
+        pollJob?.cancel()
+        pollJob = scope.launch {
+            logger.info("CAN poll single-loop start, nodes={}", scanRange)
+            // 每个节点独立的 RFID 读取节流时间戳
+            val lastRfidAt = LongArray(scanRange.last + 1) { 0L }
+
+            for (nodeId in scanRange) {
+                safeRead(CanCommands.Common.getDeviceType(nodeId))?.let {
+                    activeNodes.add(nodeId)
+                    CanHelper.addNode(nodeId, it.payload[0].toInt())
+                }
+            }
+            while (isActive) {
+                for (nodeId in activeNodes) {
+                    try {
+                        val cmds = CanCommands.forDevice(nodeId)
+
+                        // 1) 读状态 0x6010/00 (2B)
+                        safeRead(cmds.getStatus())?.let { rd ->
+                            deviceStatusListener.forEach {
+                                it.value.deviceStatus(
+                                    nodeId,
+                                    rd.index,
+                                    rd.payload
+                                )
+                            }
+                        }
+                        safeRead(cmds.readControlReg())?.let { rd ->
+                            deviceStatusListener.forEach {
+                                it.value.deviceStatus(
+                                    nodeId,
+                                    rd.index,
+                                    rd.payload
+                                )
+                            }
+                        }
+
+                    } catch (t: Throwable) {
+                        // 单个节点出错不影响整体循环
+                        logger.warn("poll node={} error: {}", nodeId, t.toString())
+                    }
+
+                    // 给总线/固件留点缝,避免贴脸轰
+                    delay(pollMsStatus)
+                }
+            }
+        }.also { job ->
+            job.invokeOnCompletion { e ->
+                if (e != null) logger.warn("single-loop completed with error: {}", e.toString())
+                else logger.info("single-loop stopped.")
+            }
+        }
+    }
+
+    private fun stopPolling() {
+        pollJob?.cancel()
+        pollJob = null
+        logger.info("CAN poll stopped.")
+    }
+
+    /** 一次性读取:超时返回 null,不抛异常、不打崩循环 */
+    private suspend fun safeRead(
+        req: SdoRequest.Read,
+        timeoutMs: Long = 1500
+    ): SdoResponse.ReadData? {
+        return try {
+            withTimeoutOrNull(timeoutMs) {
+                suspendCoroutine { cont ->
+                    CanHelper.readFrom(req) { rsp -> cont.resume(rsp) }
+                }
+            }.also { rsp ->
+                if (rsp == null) {
+                    logger.debug(
+                        "read timeout node={} idx=0x{} sub=0x{}",
+                        req.nodeId, req.index.toString(16), req.subIndex.toString(16)
+                    )
+                }
+            }
+        } catch (t: Throwable) {
+            logger.warn(
+                "read exception node={} idx=0x{} sub=0x{} : {}",
+                req.nodeId, req.index.toString(16), req.subIndex.toString(16), t.toString()
+            )
+            null
+        }
+    }
+
+
+    /**
+     * 设备状态监听
+     */
+    interface DeviceStatusListener {
+        /**
+         * 设备状态
+         */
+        fun deviceStatus(nodeId: Int, index: Int, statusData: ByteArray)
+    }
+}

+ 0 - 3
data/src/main/java/com/grkj/data/hardware/can/CanSendDelayInterceptor.kt

@@ -34,10 +34,7 @@ class CanSendDelayInterceptor(
             val waitMs = max(0L, minIntervalMs - elapsed)
 
             if (waitMs > 0) {
-                logger.info("发送间隔 ${elapsed}ms < ${minIntervalMs}ms,延迟 ${waitMs}ms")
                 delay(waitMs)
-            } else {
-                logger.info("发送间隔 ${elapsed}ms,>= ${minIntervalMs}ms,直接发送")
             }
 
             // 发送

+ 13 - 0
data/src/main/java/com/grkj/data/hardware/can/CustomCanConfig.kt

@@ -1,6 +1,7 @@
 package com.grkj.data.hardware.can
 
 import com.sik.comm.core.interceptor.CommInterceptor
+import com.sik.comm.core.plugin.CommPlugin
 import com.sik.comm.impl_can.CanConfig
 
 /**
@@ -16,7 +17,19 @@ class CustomCanConfig : CanConfig(
         val instance by lazy { CustomCanConfig() }
     }
 
+    /**
+     * 发送延迟拦截器
+     */
     private val canSendDelayInterceptor = CanSendDelayInterceptor()
+
+    /**
+     * 状态读取插件
+     */
+    private val canReadyPlugin = CanReadyPlugin()
+
     override val additionalInterceptors: List<CommInterceptor>
         get() = super.additionalInterceptors + canSendDelayInterceptor
+
+    override val additionalPlugins: List<CommPlugin>
+        get() = super.additionalPlugins + canReadyPlugin
 }

+ 101 - 0
data/src/main/java/com/grkj/data/hardware/can/DeviceModel.kt

@@ -0,0 +1,101 @@
+package com.grkj.data.hardware.can
+
+/**
+ * 设备模型
+ */
+sealed class DeviceModel {
+    /**
+     * 节点id
+     */
+    var nodeId: Int = 0
+
+    /**
+     * 设备类型
+     */
+    var deviceType: Int = 0
+
+    /**
+     * 设备id
+     * 钥匙0代表左,1代表右
+     */
+    var id: Int = 0
+
+    /**
+     * 是否锁定
+     */
+    var locked: Boolean = false
+
+    /**
+     * 是否为新设备
+     */
+    var newHardware: Boolean = false
+
+    /**
+     * rfid
+     */
+    var rfid: String = ""
+
+    /**
+     * 是否存在钥匙
+     */
+    var isExist: Boolean = false
+
+    /**
+     * 设备变化
+     */
+    var deviceChange: Int = 0
+
+
+    /**
+     * 双钥匙
+     */
+    class DeviceKey : DeviceModel() {
+        /**
+         * MAC地址
+         */
+        var mac: String = ""
+
+        /**
+         * 是否在充电
+         */
+        var isCharging: Boolean = false
+
+        /**
+         * 电量
+         */
+        var power: Int = 0
+        override fun toString(): String {
+            return "DeviceKey(mac='$mac', isCharging=$isCharging, power=$power)"
+        }
+
+    }
+
+    /**
+     * 通用设备
+     */
+    class CommonDevice : DeviceModel()
+
+    /**
+     * 物资柜设备
+     */
+    class MaterialDevice : DeviceModel() {
+        /**
+         * 左门是否上锁
+         */
+        var leftDoorLocked: Boolean = false
+
+        /**
+         * 右门是否上锁
+         */
+        var rightDoorLocked: Boolean = false
+        override fun toString(): String {
+            return "MaterialDevice(leftDoorLocked=$leftDoorLocked, rightDoorLocked=$rightDoorLocked)"
+        }
+
+
+    }
+
+    override fun toString(): String {
+        return "DeviceModel(nodeId=$nodeId, deviceType=$deviceType, id=$id, locked=$locked, newHardware=$newHardware, rfid='$rfid', isExist=$isExist, deviceChange=$deviceChange)"
+    }
+}

+ 207 - 0
data/src/main/java/com/grkj/data/hardware/can/DeviceParseStatus.kt

@@ -0,0 +1,207 @@
+package com.grkj.data.hardware.can
+
+import com.sik.sikcore.bit.BitTypeUtils
+
+/**
+ * 设备状态转换
+ */
+object DeviceParseStatus {
+    /**
+     * 钥匙仓位状态转换
+     */
+    fun parseKeyDockStatus(nodeId: Int, index: Int, statusData: ByteArray) {
+        val deviceModel = CanHelper.getDeviceByNodeId(nodeId)
+        val leftKeyModel: DeviceModel.DeviceKey =
+            (deviceModel.getOrNull(0) ?: DeviceModel.DeviceKey().apply {
+                this.nodeId = nodeId
+                this.deviceType = CanDeviceConst.DEVICE_KEY_DOCK
+                this.id = 0
+                this.deviceChange = CanDeviceConst.DEVICE_CHANGE_KEY_TAKE
+            }) as DeviceModel.DeviceKey
+        val rightKeyModel: DeviceModel.DeviceKey =
+            (deviceModel.getOrNull(1) ?: DeviceModel.DeviceKey().apply {
+                this.nodeId = nodeId
+                this.deviceType = CanDeviceConst.DEVICE_KEY_DOCK
+                this.id = 1
+                this.deviceChange = CanDeviceConst.DEVICE_CHANGE_KEY_TAKE
+            }) as DeviceModel.DeviceKey
+        when (index) {
+            CanCommands.Command.STATUS -> {
+                require(statusData.size == 2) { "Status payload size must is 2" }
+                val leftKeyData = statusData[0]
+                val rightKeyData = statusData[1]
+                val leftKeyExists = ((leftKeyData.toInt() shr 0) and 1) == 1
+                if (leftKeyModel.isExist != leftKeyExists) {
+                    leftKeyModel.deviceChange = BitTypeUtils.addType(
+                        leftKeyModel.deviceChange,
+                        if (leftKeyExists) CanDeviceConst.DEVICE_CHANGE_KEY_RETURN else CanDeviceConst.DEVICE_CHANGE_KEY_TAKE
+                    )
+                }
+                leftKeyModel.isExist = leftKeyExists
+                leftKeyModel.isCharging = ((leftKeyData.toInt() shr 1) and 1) == 1
+                val rightKeyExists = ((rightKeyData.toInt() shr 0) and 1) == 1
+                if (rightKeyModel.isExist != rightKeyExists) {
+                    rightKeyModel.deviceChange = BitTypeUtils.addType(
+                        rightKeyModel.deviceChange,
+                        if (rightKeyExists) CanDeviceConst.DEVICE_CHANGE_KEY_RETURN else CanDeviceConst.DEVICE_CHANGE_KEY_TAKE
+                    )
+                }
+                rightKeyModel.isExist = rightKeyExists
+                rightKeyModel.isCharging = ((rightKeyData.toInt() shr 1) and 1) == 1
+            }
+
+            CanCommands.Command.CONTROL_REG -> {
+                val keyData = statusData[0]
+                val leftKeyLocked = ((keyData.toInt() shr 0) and 1) == 1
+                if (leftKeyModel.locked != leftKeyLocked) {
+                    leftKeyModel.deviceChange = BitTypeUtils.addType(
+                        leftKeyModel.deviceChange,
+                        if (leftKeyLocked) CanDeviceConst.DEVICE_CHANGE_LOCKED else CanDeviceConst.DEVICE_CHANGE_UNLOCK
+                    )
+                }
+                leftKeyModel.locked = leftKeyLocked
+                val rightKeyLocked = ((keyData.toInt() shr 4) and 1) == 1
+                if (rightKeyModel.locked != rightKeyLocked) {
+                    rightKeyModel.deviceChange = BitTypeUtils.addType(
+                        rightKeyModel.deviceChange,
+                        if (rightKeyLocked) CanDeviceConst.DEVICE_CHANGE_LOCKED else CanDeviceConst.DEVICE_CHANGE_UNLOCK
+                    )
+                }
+                rightKeyModel.locked = rightKeyLocked
+            }
+        }
+        CanHelper.updateDeviceData(nodeId, listOf(leftKeyModel, rightKeyModel))
+    }
+
+    /**
+     * 锁仓状态转换
+     */
+    fun parseLockDockStatus(nodeId: Int, index: Int, statusData: ByteArray) {
+        var deviceModel = CanHelper.getDeviceByNodeId(nodeId)
+        if (deviceModel.isEmpty()) {
+            deviceModel = mutableListOf<DeviceModel.CommonDevice>()
+            for (i in 0 until 5) {
+                deviceModel.add(DeviceModel.CommonDevice().apply {
+                    this.nodeId = nodeId
+                    this.deviceType = CanDeviceConst.DEVICE_LOCK_DOCK
+                    this.id = i + 1
+                    this.deviceChange = CanDeviceConst.DEVICE_CHANGE_KEY_TAKE
+                })
+            }
+        }
+        when (index) {
+            CanCommands.Command.STATUS -> {
+                deviceModel.forEach {
+                    val deviceExists = ((statusData[0].toInt() shr (it.id - 1)) and 1) == 1
+                    if (it.isExist != deviceExists) {
+                        it.deviceChange = BitTypeUtils.addType(
+                            it.deviceChange,
+                            if (deviceExists) CanDeviceConst.DEVICE_CHANGE_LOCK_RETURN else CanDeviceConst.DEVICE_CHANGE_LOCK_TAKE
+                        )
+                    }
+                    it.isExist = deviceExists
+                }
+            }
+
+            CanCommands.Command.CONTROL_REG -> {
+                deviceModel.forEach {
+                    val deviceLocked = ((statusData[0].toInt() shr (it.id - 1)) and 1) == 1
+                    if (it.locked != deviceLocked) {
+                        it.deviceChange = BitTypeUtils.addType(
+                            it.deviceChange,
+                            if (deviceLocked) CanDeviceConst.DEVICE_CHANGE_LOCKED else CanDeviceConst.DEVICE_CHANGE_UNLOCK
+                        )
+                    }
+                    it.locked = deviceLocked
+                }
+            }
+        }
+        CanHelper.updateDeviceData(nodeId, deviceModel)
+    }
+
+    /**
+     * 钥匙柜控制板状态转换
+     */
+    fun parseKeyCabinetControlBoardStatus(nodeId: Int, index: Int, statusData: ByteArray) {
+        var deviceModel = CanHelper.getDeviceByNodeId(nodeId)
+        if (deviceModel.isEmpty()) {
+            deviceModel = mutableListOf<DeviceModel.DeviceKey>()
+            for (i in 0 until 5) {
+                deviceModel.add(DeviceModel.DeviceKey().apply {
+                    this.nodeId = nodeId
+                    this.deviceType = CanDeviceConst.DEVICE_KEY_CABINET_CONTROL_BOARD
+                    this.id = i + 1
+                    this.deviceChange = CanDeviceConst.DEVICE_CHANGE_KEY_TAKE
+                })
+            }
+        }
+        when (index) {
+            CanCommands.Command.STATUS -> {
+                deviceModel.forEach {
+                    val keyExists = ((statusData[0].toInt() shr (it.id - 1)) and 1) == 1
+                    if (it.isExist != keyExists) {
+                        it.deviceChange = BitTypeUtils.addType(
+                            it.deviceChange,
+                            if (keyExists) CanDeviceConst.DEVICE_CHANGE_KEY_RETURN else CanDeviceConst.DEVICE_CHANGE_KEY_TAKE
+                        )
+                    }
+                    it.isExist = keyExists
+                }
+            }
+
+            CanCommands.Command.CONTROL_REG -> {
+                deviceModel.forEach {
+                    val keyLocked = ((statusData[0].toInt() shr (it.id - 1)) and 1) == 1
+                    if (it.locked != keyLocked) {
+                        it.deviceChange = BitTypeUtils.addType(
+                            it.deviceChange,
+                            if (keyLocked) CanDeviceConst.DEVICE_CHANGE_LOCKED else CanDeviceConst.DEVICE_CHANGE_UNLOCK
+                        )
+                    }
+                    it.locked = ((statusData[0].toInt() shr (it.id - 1)) and 1) == 1
+                }
+            }
+        }
+        CanHelper.updateDeviceData(nodeId, deviceModel)
+    }
+
+    /**
+     * 物资柜控制板状态转换
+     */
+    fun parseMaterialCabinetControlBoardStatus(nodeId: Int, index: Int, statusData: ByteArray) {
+        var deviceModel = CanHelper.getDeviceByNodeId(nodeId)
+        if (deviceModel.isEmpty()) {
+            deviceModel = mutableListOf<DeviceModel.MaterialDevice>()
+            deviceModel.add(DeviceModel.MaterialDevice().apply {
+                this.nodeId = nodeId
+                this.deviceType = CanDeviceConst.DEVICE_MATERIAL_CABINET_CONTROL_BOARD
+                this.id = 0
+                this.deviceChange = CanDeviceConst.DEVICE_CHANGE_KEY_TAKE
+            })
+        }
+        when (index) {
+            CanCommands.Command.CONTROL_REG -> {
+                deviceModel.filterIsInstance<DeviceModel.MaterialDevice>().forEach {
+                    val leftDoorLocked = ((statusData[0].toInt() shr 0) and 1) == 0
+                    if (it.leftDoorLocked != leftDoorLocked) {
+                        it.deviceChange = BitTypeUtils.addType(
+                            it.deviceChange,
+                            if (leftDoorLocked) CanDeviceConst.DEVICE_CHANGE_MATERIAL_LEFT_DOOR_OPEN else CanDeviceConst.DEVICE_CHANGE_MATERIAL_LEFT_DOOR_CLOSED
+                        )
+                    }
+                    it.leftDoorLocked = leftDoorLocked
+                    val rightDoorLocked = ((statusData[0].toInt() shr 4) and 1) == 0
+                    if (it.leftDoorLocked != rightDoorLocked) {
+                        it.deviceChange = BitTypeUtils.addType(
+                            it.deviceChange,
+                            if (rightDoorLocked) CanDeviceConst.DEVICE_CHANGE_MATERIAL_RIGHT_DOOR_OPEN else CanDeviceConst.DEVICE_CHANGE_MATERIAL_RIGHT_DOOR_CLOSED
+                        )
+                    }
+                    it.rightDoorLocked = rightDoorLocked
+                }
+            }
+        }
+        CanHelper.updateDeviceData(nodeId, deviceModel)
+    }
+
+}

+ 0 - 1
data/src/main/java/com/grkj/data/local/database/PresetData.kt

@@ -23,7 +23,6 @@ object PresetData {
     val presetSysRole: List<SysRole> = run {
         val presetSysRole =
             SIKCore.getApplication().readJsonFromAssets("$ASSETS_DIR/preset_sys_role.json")
-        logger.info("预设数据:$presetSysRole")
         gson.fromJson(presetSysRole, object : TypeToken<List<SysRole>>() {}.type)
     }
 }