Przeglądaj źródła

feat(CAN): Add NodeIdHelper for generating CAN node IDs

- Added `NodeIdHelper` to generate 8-bit CAN node IDs in increasing order of set bits and lexicographical order of bit indices.
- `CanReadyPlugin` now uses `NodeIdHelper.scanRange` to iterate through possible node IDs.
- Reduced the timeout for `safeRead` in `CanReadyPlugin` to 100ms.
周文健 1 miesiąc temu
rodzic
commit
a83b3f4360

+ 2 - 7
data/src/main/java/com/grkj/data/hardware/can/CanReadyPlugin.kt

@@ -15,7 +15,6 @@ class CanReadyPlugin : CommPlugin {
     private val logger: Logger = LoggerFactory.getLogger(CanReadyPlugin::class.java)
 
     /** 轮询哪些节点 */
-    private val scanRange: IntRange = 1..6
     private val activeNodes = mutableSetOf<Int>()
 
     /** 周期 */
@@ -60,11 +59,7 @@ class CanReadyPlugin : CommPlugin {
     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) {
+            NodeIdHelper.scanRange { nodeId ->
                 safeRead(CanCommands.Common.getDeviceType(nodeId))?.let {
                     activeNodes.add(nodeId)
                     CanHelper.addNode(nodeId, it.payload[0].toInt())
@@ -121,7 +116,7 @@ class CanReadyPlugin : CommPlugin {
     /** 一次性读取:超时返回 null,不抛异常、不打崩循环 */
     private suspend fun safeRead(
         req: SdoRequest.Read,
-        timeoutMs: Long = 1500
+        timeoutMs: Long = 100
     ): SdoResponse.ReadData? {
         return try {
             withTimeoutOrNull(timeoutMs) {

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

@@ -0,0 +1,81 @@
+package com.grkj.data.hardware.can
+
+/**
+ * NodeId 计算器:
+ * 以“置位个数递增 + 位索引字典序”的顺序枚举 8 位拨码的所有组合。
+ * 例如:
+ *  k=1: 00000001, 00000010, ..., 10000000
+ *  k=2: 00000011, 00000101, ..., 10000001, 00000110, ..., 11000000
+ *  ...
+ *  k=8: 11111111
+ */
+object NodeIdHelper {
+
+    /** 总位数(1..8 对应 bit0..bit7) */
+    private const val MAX_BITS = 8
+
+    /**
+     * 枚举所有非零掩码,按置位个数递增(1..8),
+     * 同一置位个数内按“位索引组合字典序”输出。
+     *
+     * @param emit 对每个掩码调用一次(0x01..0xFF),顺序如上。
+     */
+    suspend fun scanRange(emit: suspend (Int) -> Unit) {
+        // 置位个数 k:1..8
+        for (k in 1..MAX_BITS) {
+            generateKBitMasks(k, MAX_BITS, emit)
+        }
+    }
+
+    /**
+     * 生成固定置位个数 k 的所有掩码(组合字典序)。
+     * 例如 k=2, n=8:
+     * 索引组合 (0,1) → 00000011, (0,2) → 00000101, ..., (6,7) → 11000000
+     */
+    private suspend fun generateKBitMasks(
+        k: Int,
+        n: Int,
+        emit: suspend (Int) -> Unit
+    ) {
+        // 组合索引数组,初始为 [0,1,2,...,k-1]
+        val idx = IntArray(k) { it }
+        while (true) {
+            // 根据当前索引组合生成掩码
+            var mask = 0
+            for (i in 0 until k) {
+                mask = mask or (1 shl idx[i])
+            }
+            emit(mask)
+
+            // 生成下一个组合(典型字典序组合生成算法)
+            var pos = k - 1
+            while (pos >= 0 && idx[pos] == pos + n - k) pos--
+            if (pos < 0) break
+            idx[pos]++
+            for (j in pos + 1 until k) {
+                idx[j] = idx[j - 1] + 1
+            }
+        }
+    }
+
+    /**
+     * 如果你不需要挂起回调,也可以拿到一个 Sequence 方便 for-each。
+     */
+    fun scanRangeSeq(): Sequence<Int> = sequence {
+        for (k in 1..MAX_BITS) {
+            // 复用相同生成逻辑,但以 yield 返回
+            val idx = IntArray(k) { it }
+            while (true) {
+                var mask = 0
+                for (i in 0 until k) mask = mask or (1 shl idx[i])
+                yield(mask)
+
+                var pos = k - 1
+                while (pos >= 0 && idx[pos] == pos + MAX_BITS - k) pos--
+                if (pos < 0) break
+                idx[pos]++
+                for (j in pos + 1 until k) idx[j] = idx[j - 1] + 1
+            }
+        }
+    }
+}