|
|
@@ -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
|
|
|
-
|
|
|
/**
|
|
|
* 提交单个请求任务
|
|
|
*/
|