Эх сурвалжийг харах

refactor(Modbus):
- `ModBusManager`, `ModBusController`: 重构底层通信实现,移除旧的`PortManager`,改为使用新的`HardwareClient`硬件抽象层进行串口通信。
- `ModBusManager`:
- 将原先基于`while`循环和`synchronized`锁的发送/接收逻辑,改造为基于`Kotlin Coroutine`和`Channel`的异步任务模型,提升了代码的健壮性和可维护性。
- 优化了半双工通信的时序控制,增加了帧间延迟(`interFrameGapMs`)和收发转换延迟(`turnaroundDelayMs`)。
- 移除了`isRunning`方法,由`HardwareClient`管理连接状态。
- `ModBusController`:
- 适配`ModBusManager`的重构,移除`unregisterListener`和`isRunning`等冗余方法。

chore(调试):
- `Constants.kt`: 将`DEBUG`常量设置为`true`,以启用调试模式。
- `LoginActivity`: 注释掉了HLK人脸识别后端相关的初始化代码。

周文健 6 сар өмнө
parent
commit
da82188dd9

+ 8 - 23
data/src/main/java/com/grkj/data/hardware/modbus/ModBusController.kt

@@ -9,12 +9,14 @@ import com.grkj.data.hardware.ble.BleSendDispatcher
 import com.grkj.data.net.res.CabinetSlotsRecord
 import com.grkj.data.utils.event.ModbusInitCompleteEvent
 import com.grkj.data.utils.event.ToastEvent
+import com.grkj.hardware.sdk.HardwareClient
 import com.grkj.shared.utils.extension.isPureZero
 import com.grkj.shared.utils.extension.removeLeadingZeros
 import com.grkj.shared.utils.extension.toHexFromLe
 import com.grkj.shared.utils.extension.toHexStrings
 import com.grkj.shared.utils.i18n.I18nManager
 import com.huyuhui.fastble.BleManager
+import com.sik.sikcore.SIKCore
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import java.util.concurrent.atomic.AtomicInteger
@@ -70,9 +72,10 @@ object ModBusController {
     @ExperimentalUnsignedTypes
     fun start() {
         modBusManager?.stop()
-        PortManager.openCtrlBord()?.let { pm ->
-            return@let ModBusManager(pm, false)
-        }.also { modBusManager = it }?.start()
+        logger.info("开始初始化通信")
+        HardwareClient(SIKCore.getApplication()).let { client ->
+            return@let ModBusManager(client, false)
+        }.also { modBusManager = it }.start()
     }
 
     /**
@@ -102,25 +105,6 @@ object ModBusController {
         listeners.add(StatusListener(key, listener, LISTENER_TYPE_STATUS))
     }
 
-    /**
-     * 取消注册监听器
-     */
-    fun unregisterListener(key: Any) {
-        val it = listeners.iterator()
-        while (it.hasNext()) {
-            if (it.next().key == key) {
-                it.remove()
-            }
-        }
-    }
-
-    /**
-     * 引擎是否运行中
-     */
-    fun isRunning(): Boolean? {
-        return modBusManager?.isRunning()
-    }
-
     /*****************************************************************************************/
 
     /**
@@ -225,7 +209,8 @@ object ModBusController {
                                     logger.error("Lock rfid error")
                                     return@readKeyRfid
                                 }
-                                val rfid = res.copyOfRange(3, 11).toHexStrings(false).removeLeadingZeros()
+                                val rfid =
+                                    res.copyOfRange(3, 11).toHexStrings(false).removeLeadingZeros()
                                 logger.info("初始化钥匙 RFID : $rfid")
                                 // 更新rfid
                                 updateKeyRfid(dockBean.addr, key.idx, rfid)

+ 98 - 56
data/src/main/java/com/grkj/data/hardware/modbus/ModBusManager.kt

@@ -3,18 +3,25 @@ package com.grkj.data.hardware.modbus
 import com.google.gson.Gson
 import com.google.gson.reflect.TypeToken
 import com.grkj.data.data.MMKVConstants
+import com.grkj.hardware.HwConfig
+import com.grkj.hardware.IHardwareChannel
+import com.grkj.hardware.sdk.HardwareClient
+import com.grkj.hardware.sdk.protocol.ProtocolId
 import com.grkj.shared.config.Constants
 import com.grkj.shared.utils.extension.toHexStrings
 import com.sik.sikcore.extension.getMMKVData
 import com.sik.sikcore.thread.ThreadUtils
-import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.isActive
 import kotlinx.coroutines.launch
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
+import kotlin.coroutines.cancellation.CancellationException
+import kotlin.math.log
 
 /**
  * ModBus 协议管理器(协程版)
@@ -22,46 +29,43 @@ import org.slf4j.LoggerFactory
  */
 class ModBusManager(
     // 底层串口管理器
-    val portManager: PortManager?,
+    val client: HardwareClient,
     // 是否输出详细信息
     val verbose: Boolean = Constants.DEBUG
 ) {
     private val logger: Logger = LoggerFactory.getLogger(ModBusManager::class.java)
 
-    @Volatile
-    private var running = true
-
-    /** 正在发送的任务 */
-    @Volatile
-    private var sending: FrameTask? = null
-
     /** 等待发送队列 */
     private val pendings = Channel<FrameTask>(Channel.UNLIMITED)
 
-    /** 锁保护 sending */
-    private val lock = Any()
+    /**
+     * 作用域
+     */
+    protected val scope = CoroutineScope(Dispatchers.Default + Job())
+
+    private var sending: FrameTask? = null
+
+    private var running: Boolean = false
 
     private var job: Job? = null
 
     /** 从机地址池 */
     var mSlaveAddressList = mutableListOf<Byte>()
 
+    /**
+     * 通道
+     */
+    private var channel: IHardwareChannel? = null
+
+    // 时序
+    private val turnaroundDelayMs = 2L
+    private val interFrameGapMs = 2L
+    private val defaultTimeoutMs = 520L
+
+    @Volatile
+    private var txWorkerStarted = false
+
     init {
-        // 串口监听,回调在单独线程中执行
-        portManager?.listen { res ->
-            if (verbose) logger.debug("接收:${res.toHexStrings()}")
-            synchronized(lock) {
-                sending?.run {
-                    if (match(res) && running) {
-                        done?.invoke(res)
-                        sending = null
-                    } else {
-                        logger.warn("响应: ${res.toHexStrings()} 未匹配, running: $running")
-                    }
-                }
-            }
-        }
-        portManager?.clearGarbageSafe()
         // 初始化地址池
         val dockConfig = MMKVConstants.KEY_DOCK_CONFIG.getMMKVData("[]")
         logger.info("基座配置: ${dockConfig}")
@@ -76,26 +80,51 @@ class ModBusManager(
     /**
      * 从队列中取任务并发送
      */
-    private suspend fun takePendingToSend() {
-        if (sending == null) {
-            sending = pendings.receive()
-        }
-        if (!running) return
-        sending?.waitIfNecessary()
-        synchronized(lock) {
-            sending?.run {
-                if (shouldSend()) {
-                    if (portManager?.send(req) == true) {
-                        afterSent()
-                        if (verbose) logger.debug("发送:${req.toHexStrings()}")
-                    } else {
-                        logger.warn("无法与主控板通讯")
+    private fun takePendingToSend() {
+        if (txWorkerStarted) return
+        txWorkerStarted = true
+
+        scope.launch(Dispatchers.IO) {
+            try {
+                for (req in pendings) {
+                    // 提前中断检查(避免无意义等待)
+                    if (!running || !isActive) break
+
+                    // 本次要发送的就是这条,明确绑定,别混着用 sending?.run 和外层 req
+                    sending = req
+                    try {
+                        // 半双工节拍:先 turnaround 再 IFG
+                        if (turnaroundDelayMs > 0) delay(turnaroundDelayMs)
+                        if (interFrameGapMs > 0) delay(interFrameGapMs)
+
+                        if (!running || !isActive) break
+
+                        // 只判断这条是否应发
+                        if (sending?.shouldSend() == true) {
+                            val ok = (channel?.write(sending!!.req, 0, sending!!.req.size) == true)
+                            if (ok) {
+                                sending?.afterSent()
+                                if (verbose) logger.debug("发送:${sending!!.req.toHexStrings()}")
+                            } else {
+                                logger.warn("无法与主控板通讯")
+                                // 失败反馈给上层(按你现有约定用空数组)
+                                sending?.done?.invoke(byteArrayOf())
+                            }
+                        } else {
+                            logger.info("未响应: ${sending?.req?.toHexStrings()}")
+                            sending?.done?.invoke(byteArrayOf())
+                        }
+
+                        // 发送后再给个帧间隔,避免挤占下一帧
+                        if (interFrameGapMs > 0) delay(interFrameGapMs)
+                    } finally {
+                        // 无论成功/失败/异常都清空,保证下一条能被消费
                     }
-                } else {
-                    logger.info("未响应: ${req.toHexStrings()}")
-                    done?.invoke(byteArrayOf())
-                    sending = null
                 }
+            } catch (ce: CancellationException) {
+                // 协程取消正常退出
+            } finally {
+                txWorkerStarted = false
             }
         }
     }
@@ -104,14 +133,32 @@ class ModBusManager(
      * 开始通信(协程替代线程)
      */
     fun start() {
-        job = CoroutineScope(Dispatchers.IO).launch {
-            while (running) {
-                try {
-                    takePendingToSend()
-                } catch (_: CancellationException) {
-                    break
+        job = scope.launch {
+            val bindOK = client.bind()
+            if (!bindOK) {
+                logger.info("服务启动失败")
+                return@launch
+            }
+            val cfg = HwConfig(
+                transportUri = "serial://?dev=/dev/ttyS4&baud=115200&data_bits=8&stop_bits=1&parity=N",
+                codec = ProtocolId.RAW,
+                readTimeoutMs = 500
+            )
+            channel = client.open(cfg) { sessionId, payload ->
+                // 串口监听,回调在单独线程中执行
+                if (verbose) logger.debug("接收:${payload.toHexStrings()}")
+                sending?.run {
+                    if (match(payload) && running) {
+                        done?.invoke(payload)
+                        sending = null
+                    } else {
+                        logger.warn("响应: ${payload.toHexStrings()} 未匹配, running: $running")
+                        sending?.done?.invoke(byteArrayOf())
+                    }
                 }
             }
+            running = true
+            takePendingToSend()
         }
     }
 
@@ -121,14 +168,9 @@ class ModBusManager(
     fun stop() {
         running = false
         job?.cancel()
-        portManager?.close()
+        client.unbind()
     }
 
-    /**
-     * 是否运行
-     */
-    fun isRunning(): Boolean = running
-
     /**
      * 提交单个请求任务
      */

+ 0 - 1
iscs_lock/src/main/AndroidManifest.xml

@@ -76,7 +76,6 @@
                 <action android:name="android.media.AUDIO_BECOMEING_NOISY" />
             </intent-filter>
         </receiver>
-
         <meta-data
             android:name="design_width_in_dp"
             android:value="600" />

+ 2 - 2
iscs_lock/src/main/java/com/grkj/iscs/features/login/activity/LoginActivity.kt

@@ -76,9 +76,9 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>() {
 
     override fun initView() {
         // ② 建一个 HLK 客户端(串口或你现有的 Modbus 封装)
-        val hlk = Hlk223Client(Hlk223Config.getProtocol(), "HLK-223")
+//        val hlk = Hlk223Client(Hlk223Config.getProtocol(), "HLK-223")
         // ③ 想切到 HLK 路线(但保持对外 API 不变)
-        FaceUtil.enableHlkBackend(hlk)
+//        FaceUtil.enableHlkBackend(hlk)
         //todo 模拟器不支持 测试用,直接创建管理员账号
         FaceUtil.checkActiveStatus(SIKCore.getApplication())
         FaceUtil.initEngine(SIKCore.getApplication())

BIN
shared/libs/hardware-sdk-release.aar


+ 1 - 1
shared/src/main/java/com/grkj/shared/config/Constants.kt

@@ -42,7 +42,7 @@ object Constants {
     const val USER_TYPE_LOCKER = "0"                // 上锁人
     const val USER_TYPE_COLOCKER = "1"              // 共锁人
 
-    const val DEBUG: Boolean = false
+    const val DEBUG: Boolean = true
 
     /**
      * 语言地区过滤