|
@@ -1,21 +1,31 @@
|
|
|
package com.grkj.ui_base.utils.modbus
|
|
package com.grkj.ui_base.utils.modbus
|
|
|
|
|
|
|
|
-import android.serialport.SerialPort
|
|
|
|
|
import androidx.annotation.WorkerThread
|
|
import androidx.annotation.WorkerThread
|
|
|
|
|
+import com.epton.sdk.SerialPort
|
|
|
import com.grkj.data.data.MMKVConstants
|
|
import com.grkj.data.data.MMKVConstants
|
|
|
import com.grkj.ui_base.utils.SPUtils
|
|
import com.grkj.ui_base.utils.SPUtils
|
|
|
|
|
+import com.grkj.ui_base.utils.extension.toHexStrings
|
|
|
import com.kongzue.dialogx.dialogs.PopTip
|
|
import com.kongzue.dialogx.dialogs.PopTip
|
|
|
import com.sik.sikcore.SIKCore
|
|
import com.sik.sikcore.SIKCore
|
|
|
import com.sik.sikcore.extension.getMMKVData
|
|
import com.sik.sikcore.extension.getMMKVData
|
|
|
import com.sik.sikcore.extension.saveMMKVData
|
|
import com.sik.sikcore.extension.saveMMKVData
|
|
|
import com.sik.sikcore.extension.toJson
|
|
import com.sik.sikcore.extension.toJson
|
|
|
import com.sik.sikcore.thread.ThreadUtils
|
|
import com.sik.sikcore.thread.ThreadUtils
|
|
|
|
|
+import kotlinx.coroutines.Dispatchers
|
|
|
|
|
+import kotlinx.coroutines.withContext
|
|
|
|
|
+import kotlinx.coroutines.withTimeout
|
|
|
import org.slf4j.Logger
|
|
import org.slf4j.Logger
|
|
|
import org.slf4j.LoggerFactory
|
|
import org.slf4j.LoggerFactory
|
|
|
import java.io.File
|
|
import java.io.File
|
|
|
import java.io.IOException
|
|
import java.io.IOException
|
|
|
import java.io.InputStream
|
|
import java.io.InputStream
|
|
|
import java.io.OutputStream
|
|
import java.io.OutputStream
|
|
|
|
|
+import java.util.concurrent.Callable
|
|
|
|
|
+import java.util.concurrent.ExecutorService
|
|
|
|
|
+import java.util.concurrent.Executors
|
|
|
|
|
+import java.util.concurrent.TimeUnit
|
|
|
|
|
+import java.util.concurrent.TimeoutException
|
|
|
|
|
+import kotlin.coroutines.cancellation.CancellationException
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 串口通信管理器
|
|
* 串口通信管理器
|
|
@@ -23,6 +33,7 @@ import java.io.OutputStream
|
|
|
class PortManager private constructor(
|
|
class PortManager private constructor(
|
|
|
private val input: InputStream, private val output: OutputStream
|
|
private val input: InputStream, private val output: OutputStream
|
|
|
) {
|
|
) {
|
|
|
|
|
+
|
|
|
// 是否正在监听
|
|
// 是否正在监听
|
|
|
@Volatile
|
|
@Volatile
|
|
|
private var listening = false
|
|
private var listening = false
|
|
@@ -106,7 +117,7 @@ class PortManager private constructor(
|
|
|
try {
|
|
try {
|
|
|
val file = File(if (usb) "/dev/ttyUSB${port}" else "/dev/ttyS${port}")
|
|
val file = File(if (usb) "/dev/ttyUSB${port}" else "/dev/ttyS${port}")
|
|
|
logger.info("连接 port file")
|
|
logger.info("连接 port file")
|
|
|
- SerialPort(file, bps).run {
|
|
|
|
|
|
|
+ SerialPort(file, bps, 0).run {
|
|
|
blocked = false
|
|
blocked = false
|
|
|
logger.info("建立 SerialPort")
|
|
logger.info("建立 SerialPort")
|
|
|
return PortManager(inputStream, outputStream)
|
|
return PortManager(inputStream, outputStream)
|
|
@@ -141,7 +152,7 @@ class PortManager private constructor(
|
|
|
try {
|
|
try {
|
|
|
val file = File(port)
|
|
val file = File(port)
|
|
|
logger.info("连接 port file")
|
|
logger.info("连接 port file")
|
|
|
- SerialPort(file, bps).run {
|
|
|
|
|
|
|
+ SerialPort(file, bps, 0).run {
|
|
|
blocked = false
|
|
blocked = false
|
|
|
logger.info("建立 SerialPort")
|
|
logger.info("建立 SerialPort")
|
|
|
return PortManager(inputStream, outputStream)
|
|
return PortManager(inputStream, outputStream)
|
|
@@ -159,38 +170,93 @@ class PortManager private constructor(
|
|
|
*/
|
|
*/
|
|
|
@WorkerThread
|
|
@WorkerThread
|
|
|
@JvmStatic
|
|
@JvmStatic
|
|
|
- fun detectSlave(baudRate: Int = 115200): String? {
|
|
|
|
|
|
|
+ fun detectSlave(
|
|
|
|
|
+ baudRate: Int = 115200,
|
|
|
|
|
+ perAddrTimeoutMs: Long = 100,
|
|
|
|
|
+ perPortTimeoutMs: Long = 5000 // 每个端口最大 500ms
|
|
|
|
|
+ ): String? {
|
|
|
val devs = File("/dev").listFiles { _, name ->
|
|
val devs = File("/dev").listFiles { _, name ->
|
|
|
name.startsWith("ttyS") || name.startsWith("ttyUSB")
|
|
name.startsWith("ttyS") || name.startsWith("ttyUSB")
|
|
|
}?.map { it.absolutePath } ?: return null
|
|
}?.map { it.absolutePath } ?: return null
|
|
|
- val dockConfig = mutableListOf<DockBean>()
|
|
|
|
|
- devs.forEach { path ->
|
|
|
|
|
- try {
|
|
|
|
|
- dockConfig.clear()
|
|
|
|
|
- SerialPort(File(path), baudRate).let { sp ->
|
|
|
|
|
- val input = sp.inputStream
|
|
|
|
|
- val output = sp.outputStream
|
|
|
|
|
- val slaveInfo = path
|
|
|
|
|
- for (addrInt in 1..0x16) {
|
|
|
|
|
- checkSlave(addrInt, input, output, dockConfig)
|
|
|
|
|
- }
|
|
|
|
|
- checkSlave(0xA1, input, output, dockConfig)
|
|
|
|
|
- if (slaveInfo.isNotEmpty() && dockConfig.isNotEmpty()) {
|
|
|
|
|
- SPUtils.saveDockConfig(SIKCore.getApplication(), dockConfig.toJson())
|
|
|
|
|
- logger.info("扫描到设备:${slaveInfo},从机:${dockConfig}")
|
|
|
|
|
- return slaveInfo
|
|
|
|
|
|
|
+
|
|
|
|
|
+ val executor: ExecutorService = Executors.newCachedThreadPool()
|
|
|
|
|
+ try {
|
|
|
|
|
+ devs.forEach { path ->
|
|
|
|
|
+ val future = executor.submit(Callable<String?> {
|
|
|
|
|
+ val dockConfig = mutableListOf<DockBean>()
|
|
|
|
|
+ SerialPort(File(path), baudRate, 0).let { sp ->
|
|
|
|
|
+ val input = sp.inputStream
|
|
|
|
|
+ val output = sp.outputStream
|
|
|
|
|
+
|
|
|
|
|
+ // 1..0x16
|
|
|
|
|
+ for (addr in 1..0x16) {
|
|
|
|
|
+ tryCheckSlave(
|
|
|
|
|
+ executor,
|
|
|
|
|
+ addr.toInt(),
|
|
|
|
|
+ input,
|
|
|
|
|
+ output,
|
|
|
|
|
+ dockConfig,
|
|
|
|
|
+ perAddrTimeoutMs
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ // 0xA1
|
|
|
|
|
+ tryCheckSlave(
|
|
|
|
|
+ executor,
|
|
|
|
|
+ 0xA1.toInt(),
|
|
|
|
|
+ input,
|
|
|
|
|
+ output,
|
|
|
|
|
+ dockConfig,
|
|
|
|
|
+ perAddrTimeoutMs
|
|
|
|
|
+ )
|
|
|
|
|
+ if (dockConfig.isNotEmpty()) {
|
|
|
|
|
+ MMKVConstants.KEY_DOCK_CONFIG.saveMMKVData(dockConfig.toJson())
|
|
|
|
|
+ return@Callable path
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
+ null
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ val result = future.get(perPortTimeoutMs, TimeUnit.MILLISECONDS)
|
|
|
|
|
+ if (result != null) return result
|
|
|
|
|
+ } catch (e: TimeoutException) {
|
|
|
|
|
+ future.cancel(true)
|
|
|
|
|
+ logger.warn("[$path] 打开/扫描超时 ${perPortTimeoutMs}ms,跳过")
|
|
|
|
|
+ } catch (e: Exception) {
|
|
|
|
|
+ future.cancel(true)
|
|
|
|
|
+ logger.warn("[$path] 扫描出错:${e.message}")
|
|
|
}
|
|
}
|
|
|
- } catch (e: Exception) {
|
|
|
|
|
- logger.warn("扫描 $path 失败:${e.message}")
|
|
|
|
|
}
|
|
}
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ executor.shutdownNow()
|
|
|
}
|
|
}
|
|
|
return null
|
|
return null
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /**
|
|
|
|
|
- * 检查从机
|
|
|
|
|
- */
|
|
|
|
|
|
|
+ private fun tryCheckSlave(
|
|
|
|
|
+ executor: ExecutorService,
|
|
|
|
|
+ addrInt: Int,
|
|
|
|
|
+ input: InputStream,
|
|
|
|
|
+ output: OutputStream,
|
|
|
|
|
+ dockConfig: MutableList<DockBean>,
|
|
|
|
|
+ timeoutMs: Long
|
|
|
|
|
+ ): Boolean {
|
|
|
|
|
+ return try {
|
|
|
|
|
+ val future = executor.submit(Callable {
|
|
|
|
|
+ checkSlave(addrInt, input, output, dockConfig)
|
|
|
|
|
+ dockConfig.any { it.addr.toInt() == addrInt }
|
|
|
|
|
+ })
|
|
|
|
|
+ future.get(timeoutMs, TimeUnit.MILLISECONDS)
|
|
|
|
|
+ } catch (_: TimeoutException) {
|
|
|
|
|
+ logger.warn("addr=$addrInt 检测超时 ${timeoutMs}ms")
|
|
|
|
|
+ false
|
|
|
|
|
+ } catch (e: Exception) {
|
|
|
|
|
+ logger.warn("addr=$addrInt 检测出错:${e.message}")
|
|
|
|
|
+ false
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 原始阻塞版,不变
|
|
|
private fun checkSlave(
|
|
private fun checkSlave(
|
|
|
addrInt: Int,
|
|
addrInt: Int,
|
|
|
input: InputStream,
|
|
input: InputStream,
|
|
@@ -198,12 +264,9 @@ class PortManager private constructor(
|
|
|
dockConfig: MutableList<DockBean>
|
|
dockConfig: MutableList<DockBean>
|
|
|
) {
|
|
) {
|
|
|
val addr = addrInt.toByte()
|
|
val addr = addrInt.toByte()
|
|
|
- val cmdFrame = ModBusCMDHelper.generateReadDeviceTypeCmd()
|
|
|
|
|
- val cmdBytes = cmdFrame.compile(addr)
|
|
|
|
|
|
|
+ val cmd = ModBusCMDHelper.generateReadDeviceTypeCmd().compile(addr)
|
|
|
|
|
+ output.write(cmd); output.flush()
|
|
|
|
|
|
|
|
- output.write(cmdBytes)
|
|
|
|
|
- output.flush()
|
|
|
|
|
- // 简单阻塞读 7 字节
|
|
|
|
|
val buf = ByteArray(7)
|
|
val buf = ByteArray(7)
|
|
|
var received = 0
|
|
var received = 0
|
|
|
while (received < buf.size) {
|
|
while (received < buf.size) {
|
|
@@ -211,16 +274,16 @@ class PortManager private constructor(
|
|
|
if (n <= 0) break
|
|
if (n <= 0) break
|
|
|
received += n
|
|
received += n
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
if (received == buf.size && buf[1] == 0x03.toByte()) {
|
|
if (received == buf.size && buf[1] == 0x03.toByte()) {
|
|
|
- val hi = buf[3].toInt() and 0xFF
|
|
|
|
|
- val lo = buf[4].toInt() and 0xFF
|
|
|
|
|
- val deviceType = (hi shl 8) or lo
|
|
|
|
|
|
|
+ val deviceType = ((buf[3].toInt() and 0xFF) shl 8) or (buf[4].toInt() and 0xFF)
|
|
|
dockConfig.add(
|
|
dockConfig.add(
|
|
|
DockBean(
|
|
DockBean(
|
|
|
- addr,
|
|
|
|
|
- addrInt,
|
|
|
|
|
- dockConfig.count { it.type == deviceType.toByte() } + 1,
|
|
|
|
|
- deviceType.toByte(), deviceList = mutableListOf()
|
|
|
|
|
|
|
+ addr = addr,
|
|
|
|
|
+ row = dockConfig.count(),
|
|
|
|
|
+ col = dockConfig.count { it.type == deviceType.toByte() } + 1,
|
|
|
|
|
+ type = deviceType.toByte(),
|
|
|
|
|
+ deviceList = mutableListOf()
|
|
|
)
|
|
)
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|