|
|
@@ -0,0 +1,248 @@
|
|
|
+package com.grkj.data.hardware.can
|
|
|
+
|
|
|
+import com.sik.comm.impl_can.SdoDialect
|
|
|
+import com.sik.comm.impl_can.SdoRequest
|
|
|
+import kotlin.math.max
|
|
|
+import kotlin.math.min
|
|
|
+
|
|
|
+/**
|
|
|
+ * 按设备类型分组的 CAN 指令集
|
|
|
+ *
|
|
|
+ * 使用方式:
|
|
|
+ * 1) 先调用 Common.getDeviceType(nodeId) 发读请求,拿到设备类型原始2字节;
|
|
|
+ * 2) 用 Common.parseDeviceType(payload) 得到 DeviceType;
|
|
|
+ * 3) 调用 CanCommands.forDevice(nodeId, deviceType) 获得对应命令集,然后用里面的方法发 SDO。
|
|
|
+ */
|
|
|
+object CanCommands {
|
|
|
+ /**
|
|
|
+ * SDO 协议指令集:
|
|
|
+ */
|
|
|
+ val sdoDialect: SdoDialect = SdoDialect(
|
|
|
+ READ = 0x40,
|
|
|
+ READ_1B = 0x2F,
|
|
|
+ READ_2B = 0x4B,
|
|
|
+ READ_4B = 0x23,
|
|
|
+ READ_ERROR = 0x80,
|
|
|
+ WRITE_1B = 0x2F,
|
|
|
+ WRITE_2B = 0x2B,
|
|
|
+ WRITE_4B = 0x23,
|
|
|
+ WRITE_ACK = 0x60,
|
|
|
+ WRITE_ERROR = 0x80
|
|
|
+ )
|
|
|
+
|
|
|
+ // ========= 公共区:所有设备通用 =========
|
|
|
+ object Common {
|
|
|
+ /** 设备类型 (R) → 0x6000/0x00, 2B */
|
|
|
+ fun getDeviceType(nodeId: Int): SdoRequest.Read =
|
|
|
+ SdoRequest.Read(nodeId, 0x6000, 0x00)
|
|
|
+
|
|
|
+ /** 解析设备类型(小端 16 位,低字节为主) */
|
|
|
+ fun parseDeviceType(payload: ByteArray): DeviceType {
|
|
|
+ if (payload.isEmpty()) return DeviceType.Unknown
|
|
|
+ val v =
|
|
|
+ if (payload.size >= 2) ((payload[1].toInt() and 0xFF) shl 8) or (payload[0].toInt() and 0xFF)
|
|
|
+ else (payload[0].toInt() and 0xFF)
|
|
|
+ return DeviceType.fromCode(v)
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 版本 (R) → 0x6003/0x00, 4B: HW主,HW子,SW主,SW子 */
|
|
|
+ fun getDeviceVersion(nodeId: Int): SdoRequest.Read =
|
|
|
+ SdoRequest.Read(nodeId, 0x6003, 0x00)
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========= 工厂:按类型返回对应命令集 =========
|
|
|
+ fun forDevice(nodeId: Int): CommandSet = when (CanHelper.getDeviceType(nodeId)) {
|
|
|
+ DeviceType.EKeyDock -> EKeyDockCommands(nodeId)
|
|
|
+ DeviceType.FiveLockDock -> FiveLockDockCommands(nodeId)
|
|
|
+ DeviceType.KeyCabinet -> KeyCabinetCommands(nodeId)
|
|
|
+ DeviceType.MaterialCabinet -> MaterialCabinetCommands(nodeId)
|
|
|
+ DeviceType.Unknown -> UnknownCommands(nodeId)
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========= 类型定义 =========
|
|
|
+
|
|
|
+ /** 文档里的主控板类型(0/1/2/3),其余视为 Unknown */
|
|
|
+ enum class DeviceType(val code: Int) {
|
|
|
+ EKeyDock(0), // 电子钥匙底座(左右位)
|
|
|
+ FiveLockDock(1), // 5路挂锁底座(1..5位)
|
|
|
+ KeyCabinet(2), // 钥匙柜控制板(多为5位同构,也可扩)
|
|
|
+ MaterialCabinet(3), // 物资柜主控(支持RGB状态灯等)
|
|
|
+ Unknown(-1);
|
|
|
+
|
|
|
+ companion object {
|
|
|
+ fun fromCode(code: Int): DeviceType = when (code) {
|
|
|
+ 0 -> EKeyDock
|
|
|
+ 1 -> FiveLockDock
|
|
|
+ 2 -> KeyCabinet
|
|
|
+ 3 -> MaterialCabinet
|
|
|
+ else -> Unknown
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 各设备命令集统一接口(可按需加通用方法) */
|
|
|
+ interface CommandSet {
|
|
|
+ val nodeId: Int
|
|
|
+
|
|
|
+ /** 设备当前状态(大多数设备复用 0x6010/0x00, 2B),具体位义由各实现说明 */
|
|
|
+ fun getStatus(): SdoRequest.Read
|
|
|
+
|
|
|
+ /** 获取版本通用接口 */
|
|
|
+ fun getVersion(): SdoRequest.Read = Common.getDeviceVersion(nodeId)
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========= 电子钥匙底座(左右位) =========
|
|
|
+
|
|
|
+ class EKeyDockCommands(override val nodeId: Int) : CommandSet {
|
|
|
+ /** 状态 (R) 0x6010/0x00, 2B
|
|
|
+ * bit0:左卡扣, bit1:左充电, bit4:右卡扣, bit5:右充电,
|
|
|
+ * bit8/9/12/13 工作位标识等(读侧用)
|
|
|
+ */
|
|
|
+ override fun getStatus(): SdoRequest.Read =
|
|
|
+ SdoRequest.Read(nodeId, 0x6010, 0x00)
|
|
|
+
|
|
|
+ /** 读/写 控制/状态 (R/W) 0x6011/0x00, 2B(写:仅置相关位,其余位写0) */
|
|
|
+ fun readControlReg(): SdoRequest.Read =
|
|
|
+ SdoRequest.Read(nodeId, 0x6011, 0x00)
|
|
|
+
|
|
|
+ /** 设置左右卡扣(写 0x6011) */
|
|
|
+ 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(v), 2)
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 设置左右充电(写 0x6011) */
|
|
|
+ fun setCharge(leftOn: Boolean? = null, rightOn: Boolean? = null): SdoRequest.Write {
|
|
|
+ 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(v), 2)
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 单侧卡扣语法糖:keySlotId: 0左/1右;status: 0解锁/1锁住 */
|
|
|
+ fun controlLatch(keySlotId: Int, status: Int): SdoRequest.Write {
|
|
|
+ require(keySlotId in 0..1) { "keySlotId must be 0(left)/1(right)" }
|
|
|
+ require(status == 0 || status == 1) { "status must be 0/1" }
|
|
|
+ val bit = if (keySlotId == 0) 0 else 4
|
|
|
+ return SdoRequest.Write(nodeId, 0x6011, 0x00, shortLE((status and 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)
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========= 5 路挂锁底座(1..5位) =========
|
|
|
+
|
|
|
+ class FiveLockDockCommands(override val nodeId: Int) : CommandSet {
|
|
|
+ /** 锁位状态 (R) 0x6010/0x00, 2B: bit0..bit4 对应 1..5 号位已锁 */
|
|
|
+ override fun getStatus(): SdoRequest.Read =
|
|
|
+ SdoRequest.Read(nodeId, 0x6010, 0x00)
|
|
|
+
|
|
|
+ /** 控制/状态 (R/W) 0x6011/0x00, 2B:写 bit0..bit4 控制 1..5 号位 */
|
|
|
+ fun readControlReg(): SdoRequest.Read =
|
|
|
+ SdoRequest.Read(nodeId, 0x6011, 0x00)
|
|
|
+
|
|
|
+ /** 一次写入 5 位控制(低5位有效) */
|
|
|
+ fun setLatchBits(bits01to05: Int): SdoRequest.Write {
|
|
|
+ val v = bits01to05 and 0b1_1111
|
|
|
+ return SdoRequest.Write(nodeId, 0x6011, 0x00, shortLE(v), 2)
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 单位控制(1..5) */
|
|
|
+ fun controlOne(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(v), 2)
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 各位 RFID (R) 0x6020..0x6024 /0x00, 4B 小端 */
|
|
|
+ fun getSlotRfid(slotIndex1to5: Int): SdoRequest.Read {
|
|
|
+ require(slotIndex1to5 in 1..5) { "slotIndex must be 1..5" }
|
|
|
+ return SdoRequest.Read(nodeId, 0x6020 + (slotIndex1to5 - 1), 0x00)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========= 钥匙柜控制板(通常与 FiveLock 类似,保留扩展位) =========
|
|
|
+
|
|
|
+ class KeyCabinetCommands(override val nodeId: Int) : CommandSet {
|
|
|
+ /** 状态 (R) 0x6010/0x00(实现与 FiveLock 类似,具体位义按柜体定义) */
|
|
|
+ override fun getStatus(): SdoRequest.Read =
|
|
|
+ SdoRequest.Read(nodeId, 0x6010, 0x00)
|
|
|
+
|
|
|
+ /** 控制/状态 (R/W) 0x6011/0x00,低5位通常对应 1..5 号位 */
|
|
|
+ fun setLatchBits(bits01to05: Int): SdoRequest.Write =
|
|
|
+ SdoRequest.Write(nodeId, 0x6011, 0x00, shortLE(bits01to05 and 0b1_1111), 2)
|
|
|
+
|
|
|
+ fun controlOne(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(v), 2)
|
|
|
+ }
|
|
|
+
|
|
|
+ fun getSlotRfid(slotIndex1to5: Int): SdoRequest.Read {
|
|
|
+ require(slotIndex1to5 in 1..5) { "slotIndex must be 1..5" }
|
|
|
+ return SdoRequest.Read(nodeId, 0x6020 + (slotIndex1to5 - 1), 0x00)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========= 物资柜主控(RGB 状态灯等) =========
|
|
|
+
|
|
|
+ class MaterialCabinetCommands(override val nodeId: Int) : CommandSet {
|
|
|
+ /** 柜体状态 (R) 如需另定寄存器再扩展;暂复用 0x6010 */
|
|
|
+ override fun getStatus(): SdoRequest.Read =
|
|
|
+ SdoRequest.Read(nodeId, 0x6010, 0x00)
|
|
|
+
|
|
|
+ /** 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 setRgb(
|
|
|
+ r: Int, g: Int, b: Int, // 0..255
|
|
|
+ mode: Int, // 0关/1常亮/2闪烁/3呼吸/4流水
|
|
|
+ timeStep: Int, // 0..7(实际=+1)
|
|
|
+ secondsUnit: Boolean, // false=100ms, true=1000ms
|
|
|
+ lockControl: Boolean // 是否锁定主控权
|
|
|
+ ): SdoRequest.Write {
|
|
|
+ val rb = clamp8(b)
|
|
|
+ val gg = clamp8(g)
|
|
|
+ val rr = clamp8(r)
|
|
|
+ val mm = clamp3(mode)
|
|
|
+ val tt = clamp3(timeStep)
|
|
|
+ var v = 0
|
|
|
+ v = v or (rb and 0xFF)
|
|
|
+ v = v or ((gg and 0xFF) shl 8)
|
|
|
+ v = v or ((rr and 0xFF) shl 16)
|
|
|
+ v = v or ((mm and 0x07) shl 24)
|
|
|
+ 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)
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 板载温湿度 (R) 2B,小端;温度有符号/10,湿度无符号/10 */
|
|
|
+ fun getTemperature(): SdoRequest.Read = SdoRequest.Read(nodeId, 0x6017, 0x00)
|
|
|
+ fun getHumidity(): SdoRequest.Read = SdoRequest.Read(nodeId, 0x6018, 0x00)
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========= 未知类型占位,防空指针 =========
|
|
|
+ class UnknownCommands(override val nodeId: Int) : CommandSet {
|
|
|
+ override fun getStatus(): SdoRequest.Read =
|
|
|
+ SdoRequest.Read(nodeId, 0x6010, 0x00)
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========= Byte 打包工具(LE) =========
|
|
|
+ private fun shortLE(v: Int): ByteArray =
|
|
|
+ byteArrayOf((v and 0xFF).toByte(), ((v ushr 8) and 0xFF).toByte())
|
|
|
+
|
|
|
+ private fun intLE(v: Int): ByteArray = byteArrayOf(
|
|
|
+ (v and 0xFF).toByte(),
|
|
|
+ ((v ushr 8) and 0xFF).toByte(),
|
|
|
+ ((v ushr 16) and 0xFF).toByte(),
|
|
|
+ ((v ushr 24) and 0xFF).toByte()
|
|
|
+ )
|
|
|
+
|
|
|
+ private fun clamp8(x: Int) = min(255, max(0, x))
|
|
|
+ private fun clamp3(x: Int) = min(7, max(0, x))
|
|
|
+}
|