ソースを参照

feat(CAN总线): 新增CAN总线硬件数据处理
- 新增CAN总线常量类`CanDeviceConst`
- 新增CAN总线数据模型类`DeviceModel`
- 新增CAN总线数据解析类`DeviceParseStatus`
- 新增CAN总线服务启动插件`CanReadyPlugin`

周文健 2 ヶ月 前
コミット
f69cc23860

+ 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
+}

+ 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)
+    }
+}

+ 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)
+    }
+
+}