|
|
@@ -6,22 +6,20 @@ import kotlin.math.max
|
|
|
import kotlin.math.min
|
|
|
|
|
|
/**
|
|
|
- * 按设备类型分组的 CAN 指令集
|
|
|
+ * CAN 指令集(无类型探测版)
|
|
|
*
|
|
|
- * 使用方式:
|
|
|
- * 1) 先调用 Common.getDeviceType(nodeId) 发读请求,拿到设备类型原始2字节;
|
|
|
- * 2) 用 Common.parseDeviceType(payload) 得到 DeviceType;
|
|
|
- * 3) 调用 CanCommands.forDevice(nodeId, deviceType) 获得对应命令集,然后用里面的方法发 SDO。
|
|
|
+ * - 不再依赖设备类型读取;直接按节点拿到一个“通用命令集”
|
|
|
+ * - 同时提供 EKeyDock / FiveLock / KeyCabinet / MaterialCabinet 的方法
|
|
|
+ * - 你按实际设备只调用相关的方法即可;调用不支持的寄存器会返回 Abort/超时,但不会阻塞分发
|
|
|
*/
|
|
|
object CanCommands {
|
|
|
- /**
|
|
|
- * SDO 协议指令集:
|
|
|
- */
|
|
|
+
|
|
|
+ /** SDO 协议指令集(修正读响应常量) */
|
|
|
val sdoDialect: SdoDialect = SdoDialect(
|
|
|
READ = 0x40,
|
|
|
- READ_1B = 0x2F,
|
|
|
- READ_2B = 0x4B,
|
|
|
- READ_4B = 0x23,
|
|
|
+ READ_1B = 0x4F, // ✅ 读1B响应
|
|
|
+ READ_2B = 0x4B, // ✅ 读2B响应
|
|
|
+ READ_4B = 0x43, // ✅ 读4B响应
|
|
|
READ_ERROR = 0x80,
|
|
|
WRITE_1B = 0x2F,
|
|
|
WRITE_2B = 0x2B,
|
|
|
@@ -30,95 +28,45 @@ object CanCommands {
|
|
|
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
|
|
|
- }
|
|
|
- }
|
|
|
+ /** 大多数设备复用的状态寄存器 (R) → 0x6010/0x00, 2B */
|
|
|
+ fun getStatus(nodeId: Int): SdoRequest.Read =
|
|
|
+ SdoRequest.Read(nodeId, 0x6010, 0x00)
|
|
|
}
|
|
|
|
|
|
- /** 各设备命令集统一接口(可按需加通用方法) */
|
|
|
- interface CommandSet {
|
|
|
- val nodeId: Int
|
|
|
-
|
|
|
- /** 设备当前状态(大多数设备复用 0x6010/0x00, 2B),具体位义由各实现说明 */
|
|
|
- fun getStatus(): SdoRequest.Read
|
|
|
-
|
|
|
- /** 获取版本通用接口 */
|
|
|
- fun getVersion(): SdoRequest.Read = Common.getDeviceVersion(nodeId)
|
|
|
- }
|
|
|
+ /**
|
|
|
+ * 统一返回“通用命令集”(不做类型判断)
|
|
|
+ */
|
|
|
+ fun forDevice(nodeId: Int): GenericCommands = GenericCommands(nodeId)
|
|
|
|
|
|
- // ========= 电子钥匙底座(左右位) =========
|
|
|
+ /** 通用命令集:把各家寄存器方法都放这(按需调用) */
|
|
|
+ class GenericCommands(val nodeId: Int) {
|
|
|
|
|
|
- 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)
|
|
|
+ // ---- EKeyDock(左右位)/ 以及很多板子兼容的 0x6011 语义 ----
|
|
|
|
|
|
- /** 读/写 控制/状态 (R/W) 0x6011/0x00, 2B(写:仅置相关位,其余位写0) */
|
|
|
+ /** 控制/状态 (R/W) 0x6011/0x00, 2B:写仅置相关位,其余写0;读回含工作位 */
|
|
|
fun readControlReg(): SdoRequest.Read =
|
|
|
SdoRequest.Read(nodeId, 0x6011, 0x00)
|
|
|
|
|
|
- /** 设置左右卡扣(写 0x6011) */
|
|
|
+ /** 设置左右卡扣(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(v), 2)
|
|
|
+ return SdoRequest.Write(nodeId, 0x6011, 0x00, shortLE(0b0001_0001, v), 2)
|
|
|
}
|
|
|
|
|
|
- /** 设置左右充电(写 0x6011) */
|
|
|
+ /** 设置左右充电(bit1=左充电,bit5=右充电) */
|
|
|
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)
|
|
|
+ return SdoRequest.Write(nodeId, 0x6011, 0x00, shortLE(0b0010_0010, v), 2)
|
|
|
}
|
|
|
|
|
|
/** 单侧卡扣语法糖:keySlotId: 0左/1右;status: 0解锁/1锁住 */
|
|
|
@@ -126,74 +74,41 @@ object CanCommands {
|
|
|
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)
|
|
|
+ return SdoRequest.Write(
|
|
|
+ nodeId,
|
|
|
+ 0x6011,
|
|
|
+ 0x00,
|
|
|
+ shortLE(1 shl bit, (status and 1) shl bit),
|
|
|
+ 2
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
- /** 左/右 RFID (R) 4B 小端 */
|
|
|
+ /** 左/右 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)
|
|
|
+ // ---- FiveLock / KeyCabinet(常见 1..5 位同构写法,寄存器通常与 0x6011 兼容) ----
|
|
|
|
|
|
- /** 一次写入 5 位控制(低5位有效) */
|
|
|
- fun setLatchBits(bits01to05: Int): SdoRequest.Write {
|
|
|
+ /** 一次写入 5 位控制(低5位有效),适配 5路/柜体同构 */
|
|
|
+ fun setLatchBits_1to5(bits01to05: Int): SdoRequest.Write {
|
|
|
val v = bits01to05 and 0b1_1111
|
|
|
- return SdoRequest.Write(nodeId, 0x6011, 0x00, shortLE(v), 2)
|
|
|
+ return SdoRequest.Write(nodeId, 0x6011, 0x00, shortLE(0b1_1111, v), 2)
|
|
|
}
|
|
|
|
|
|
/** 单位控制(1..5) */
|
|
|
- fun controlOne(slotIndex1to5: Int, locked: Boolean): SdoRequest.Write {
|
|
|
+ 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(v), 2)
|
|
|
+ return SdoRequest.Write(nodeId, 0x6011, 0x00, shortLE(1 shl (slotIndex1to5 - 1), v), 2)
|
|
|
}
|
|
|
|
|
|
- /** 各位 RFID (R) 0x6020..0x6024 /0x00, 4B 小端 */
|
|
|
- fun getSlotRfid(slotIndex1to5: Int): SdoRequest.Read {
|
|
|
+ /** 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)
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- // ========= 钥匙柜控制板(通常与 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)
|
|
|
+ // ---- 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)
|
|
|
@@ -221,20 +136,18 @@ object CanCommands {
|
|
|
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)
|
|
|
+ // ---- 通用状态 ----
|
|
|
+ fun getStatus(): SdoRequest.Read = Common.getStatus(nodeId)
|
|
|
+ fun getVersion(): SdoRequest.Read = Common.getDeviceVersion(nodeId)
|
|
|
}
|
|
|
|
|
|
// ========= Byte 打包工具(LE) =========
|
|
|
- private fun shortLE(v: Int): ByteArray =
|
|
|
- byteArrayOf((v and 0xFF).toByte(), ((v ushr 8) and 0xFF).toByte())
|
|
|
+ private fun shortLE(control: Int, target: Int): ByteArray =
|
|
|
+ byteArrayOf((target and 0xFF).toByte(), (control and 0xFF).toByte())
|
|
|
|
|
|
private fun intLE(v: Int): ByteArray = byteArrayOf(
|
|
|
(v and 0xFF).toByte(),
|