package com.grkj.iscs.modbus import com.grkj.iscs.extentions.crc16 import com.grkj.iscs.extentions.toHexStrings import com.grkj.iscs.util.Executor import com.grkj.iscs.util.jvmSeconds import com.grkj.iscs.util.log.LogUtil import java.util.concurrent.LinkedBlockingQueue /** * modbus 最小发送间隔(150豪秒) */ // TODO 超时的可能也是用的这个,看情况是否增加到500 const val MODBUS_MIN_SEND_INTERVAL = 150_000_000 /** * 最大从机数量 */ const val MODBUS_MAX_SLAVE_COUNT = 16 /** * 从机状态:未变化 */ const val MODBUS_SLAVE_STATUS__NO_CHANGE = -1 /** * 从机状态:已经满溢 */ const val MODBUS_SLAVE_STATUS__FULL = 0b00100000 /** * ModBus 协议管理器 */ class ModBusManager( // 从机数量 var slaveCount: Int, // 串口管理器 val portManager: PortManager, // 是否输出详细信息 val verbose: Boolean = false ) { @Volatile private var running = true /** * 正在发送的任务 */ @Volatile private var sending: FrameTask? = null /** * 等待发送队列 */ private val pendings = LinkedBlockingQueue() /** * 线程锁 */ private val lock = Any() private var thread: Thread? = null init { portManager.listen { res -> if (verbose) { LogUtil.i("接收:${res.toHexStrings()}") } synchronized(lock) { sending?.run { if (match(res) && running) { done?.let { it(res) } sending = null } else { LogUtil.w("响应: ${res.toHexStrings()}未匹配, running:${running}") } } } } } /** * 发送队列的消息 */ private fun takePendingToSend() { if (sending == null) { sending = pendings.take() } if (!running) { return } sending?.run { waitIfNecessary() } synchronized(lock) { sending?.run { if (shouldSend()) { if (portManager.send(req)) { afterSent() if (verbose) { LogUtil.i("发送:${req.toHexStrings()}") } } else { // Tip.toast("无法与主控板通讯") LogUtil.w("无法与主控板通讯") } } else { LogUtil.i("未响应: ${req.toHexStrings()}") // 放弃处理,回调空数据 done?.let { it(byteArrayOf()) } sending = null // onFrameTimeout() } } } } private var timeouts = 0 private var lastTimeout = 0L private fun onFrameTimeout() { val now = jvmSeconds() if (now - lastTimeout > 10) { timeouts = 0 } // 如果连续超时达到 15 次,则重建 Modbus 连接 if (++timeouts > 15) { // EventBus.getDefault().post(ConfigEvent()) } lastTimeout = now } /** * 循环发送给所有从机 * @param frame 发送报文 * @param listener 每轮发送完后的数据监听 * @param delayMills 每轮发送的间隔 */ fun repeatSendToAll( frame: MBFrame, interrupt: (() -> List)? = null, listener: (res: List) -> Unit, delayMills: Long ): ModBusManager { val keep = interrupt?.invoke()?.run { !this[0] } ?: false if (keep) { sendToAll(frame) { if (running) { listener(it) Executor.delayOnIO({ if (running) { repeatSendToAll(frame, interrupt, listener, delayMills) } }, delayMills) } } } else { Executor.delayOnIO({ if (running) { repeatSendToAll(frame, interrupt, listener, delayMills) } }, delayMills) } return this } /** * 发送给所有从机 * @param frame 发送报文 * @param done 所有从机都发送完成后的回调 */ fun sendToAll(frame: MBFrame, done: ((res: List) -> Unit)? = null) { if (slaveCount == 0) { done?.let { it(listOf()) } return } sendUp(0, frame, done, ArrayList()) } private fun sendUp( index: Int, frame: MBFrame, done: ((res: List) -> Unit)?, resList: ArrayList ) { sendTo(index, frame) { res -> resList.add(res) if (index >= slaveCount - 1) { // 已经发送完 if (running) { done?.let { it(resList) } } } else { // 发送给下一个从机 sendUp(index + 1, frame, done, resList) } } } /** * 发送给序号为 index 的从机 * @param index 从机序号 * @param frame 发送报文 * @param done 完成回调 */ fun sendTo( index: Int, frame: MBFrame, allowRetransmission: Boolean = true, minSendIntervalNanoSeconds: Int = MODBUS_MIN_SEND_INTERVAL, done: ((res: ByteArray) -> Unit)? = null ) { if (slaveCount <= 0) { LogUtil.i("sendTo($index), slaveCount为0, 返回空数据") done?.invoke(byteArrayOf()) return } if (index < 0 || index >= slaveCount) { throw IllegalArgumentException("index [${index}] out of bound [${slaveCount}]") } val task = FrameTask(frame.compile(index), done) task.allowRetransmission = allowRetransmission task.minSendInterval = minSendIntervalNanoSeconds pendings.add(task) } fun start() { thread = Thread { while (running) { try { takePendingToSend() } catch (e: InterruptedException) { } } } thread?.isDaemon = true thread?.start() } fun isRunning(): Boolean { return running } fun stop() { running = false thread?.interrupt() portManager.close() } /** * 生成锁具卡扣开关指令 */ fun generateLockBuckleCmd(isOpen: Boolean, lockIndex: Int): MBFrame { var str = "" val idx = lockIndex - (lockIndex / 8) * 8 for (i in 7 downTo 0) { str += if (i == idx) { "1" } else { "0" } } // 第三位是 是否响应,第四位是操作哪个,操作默认全是0或者1,使用第三位响应来进行操作 return MBFrame( FRAME_TYPE_WRITE, byteArrayOf(0x00, if (lockIndex in 0..7) 0x11 else 0x12, str.toInt(2).toByte(), if (isOpen) 0x00 else 0xFF.toByte() ) ) } fun generateKeyRfidCmd(isLeft: Boolean): MBFrame { return MBFrame( FRAME_TYPE_READ, byteArrayOf(0x00, if (isLeft) 0x20 else 0x24, 0x00, 0x04) ) } /** * 生成钥匙底座灯光指令 * * @param leftAction、rightAction 0:保持当前状态 1:点亮 2:熄灭 默认0 */ fun generateKeyLightCmd(leftAction: Int = 0, rightAction: Int = 0): MBFrame { return MBFrame( FRAME_TYPE_WRITE, byteArrayOf(0x00, 0x15, leftAction.toByte(), rightAction.toByte()) ) } /** * 操作钥匙底座卡扣,一次只操作一个卡扣 * * @param isOpen true:开操作 false:关操作 * @param isLeft true:左卡扣 false:右卡扣 */ fun generateKeyBuckleCmd(isOpen: Boolean, isLeft: Boolean): MBFrame { return MBFrame( FRAME_TYPE_WRITE, byteArrayOf(0x00, 0x11, if (isLeft) 0b10000000.toByte() else 0b00001000, if (isOpen) 0x00 else 0xFF.toByte()) ) } /** * 生成锁具 RFID 读指令 * * @param lockIdx 0-9 */ fun generateLockRfidCmd(lockIdx: Int): MBFrame { return MBFrame( FRAME_TYPE_READ, byteArrayOf(0x00, (0x20 + lockIdx * 4).toByte(), 0x00, 0x04) ) } } class FrameTask( val req: ByteArray, val done: ((res: ByteArray) -> Unit)? // 响应回调 ) { /** * 是否允许重发 */ var allowRetransmission: Boolean = true /** * 上次发送时间 */ var lastSent: Long = 0 /** * 已发送次数 */ var sentCount = 0 /** * 最小发送间隔 */ var minSendInterval: Int = MODBUS_MIN_SEND_INTERVAL fun waitIfNecessary() { val interval = System.nanoTime() - lastSent if (interval < minSendInterval) { Thread.sleep((minSendInterval - interval) / 1000_000) } } fun shouldSend(): Boolean { return if (allowRetransmission) { sentCount < 3 } else { sentCount < 1 } } fun afterSent() { sentCount++ lastSent = System.nanoTime() } /** * 判断 res 是否是 frame 的响应 */ fun match(res: ByteArray): Boolean { // 从机地址 和 功能码 必须相同 if (res.size < 5 || req[0] != res[0] || req[1] != res[1]) { return false } // 报文2 的 CRC校验得正确 val crc16 = res.crc16(0, res.size - 2) return crc16[0] == res[res.size - 2] && crc16[1] == res[res.size - 1] } } private const val FRAME_TYPE_READ: Byte = 0x03 private const val FRAME_TYPE_WRITE: Byte = 0x06 private const val FRAME_TYPE_SCANNER: Byte = 0x43 const val FRAME_TYPE_WRITE_MULTI: Byte = 0x10 const val FRAME_TYPE_WRITE_FILE: Byte = 0x15 /** * ModBus 数据帧 */ class MBFrame( // 类型 val type: Byte, // 数据域:D1 和 D2 val data: ByteArray ) { /** * @param index 从机序号 */ fun compile(index: Int): ByteArray { val bytes = ByteArray(4 + data.size) // TODO 从机开始地址0x01 // bytes[0] = (0x80 + index).toByte() bytes[0] = (1 + index).toByte() bytes[1] = type for (i in data.indices) { bytes[2 + i] = data[i] } val crc16 = bytes.crc16(0, bytes.size - 2) bytes[bytes.size - 2] = crc16[0] bytes[bytes.size - 1] = crc16[1] return bytes // val cmd = byteArrayOf((1 + index).toByte()) + byteArrayOf(type) + data // return cmd + cmd.crc16() } companion object { /** * 读取设备类型 */ val READ_DEVICE_TYPE = MBFrame( FRAME_TYPE_READ, byteArrayOf(0x00, 0x00, 0x00, 0x01) ) /** * 读钥匙/锁具底座状态 */ val READ_STATUS = MBFrame( FRAME_TYPE_READ, byteArrayOf(0x00, 0x10, 0x00, 0x01) ) /** * 读卡扣状态(钥匙、锁的0-7) */ val READ_BUCKLE_STATUS = MBFrame( FRAME_TYPE_READ, byteArrayOf(0x00, 0x11, 0x00, 0x01) ) /** * 读卡扣状态(锁的9、10) */ val READ_LOCK_BUCKLE_EXTRA_STATUS = MBFrame( FRAME_TYPE_READ, byteArrayOf(0x00, 0x12, 0x00, 0x01) ) } }