Android串口通讯 | SerialPortKit | 快速接入使用 | 告别繁琐的思考逻辑

674 阅读4分钟

前言

今天是居家隔离的第六天,又是一个非常奥利给的一天。早晨10点睁开了我朦胧的双眼皮的大眼睛,然后打了个大大的哈欠,伸伸老腰。大喊一声,起床。鹅鹅鹅鹅鹅鹅~估计又要有人笑出了声。

image.png

看居委会通知,今天下午1点开始又一轮核酸检测,呃呵,没搞错吧,外面在下大雨诶!经过我再三的确认以后,确实是现在开始又一轮的检测,然后我就去做核酸检测了。那雨水真的“大珠小珠落玉盘”一样,落在我的衣服上面。经过我一个小时的排队,终于做完了核算,带来的结果就是,我的鞋子全湿啦!感觉都可以养鱼了。

快速接入

在我的上一篇文章中已经给出了快速接入的方案,这里奉上传送门。

Android串口通讯 | 专注业务

目标

SerialPortKit 截止目前开源也已有数日,根据大多朋友的反馈呢,都还挺不错,当然,在这段时间内,我也没有闲着,又是奋笔疾驰抓紧时间修复了些许 Bug,经过我不懈努力(水的一批)版本已经迭代到了 1.0.4-SNAPSHOT,早日争取搞个 Release版本,看样子,小欢子确实是挺给力的哈!不过吧,也有朋友反馈说,在使用过程中也遇到了一些困难,让能不能给一个好点的 Demo 示例拿来参考参考。我当然毅然决然的给否定了(弱弱的接了下来啦~),起初呢,我是这样子想的,这也是我的锅,为啥就不能“送人送到家,送佛送到西”,然后,我就在发文前两天,仔细琢磨了一下子,就开始整理好用的 Demo 示例。

4_c35c12e8dde50e76faab4a3c6f22b3df.gif

思路

我是一个三流的“程序猿”,每天都在努力的码代码,搬砖。也接入过很多的SDK,每天都在与各种各样的三方SDK打交道,经常性的不是这问题就是那问题。也遇到过SDK大版本的升级,然后,“我C”这么多的爆红,项目中的每个文件我都需要修改一遍,好烦呀!给我整的痛不欲生。

后来通过我认真的思考,二十三种设计模式中选择代理模式、单例模式进行把SDK进行隔离,这样子的好处就是,当SDK升级导致API大变化的时候,或者是更换SDK的时候,都可以非常快速的完成更换。(关公面前刷大刀啦~)

SerialPortKit 传送门

5_2fa89685278b6040bc90c916ee5b5d61.gif

SerialPortProxy

顾名思义,核心目的就是做代理 SerialPortKit 的实例。当然,包括初始化也都在代理中完成,用到 SerialPortKit 的时候才会进行其初始化。

class SerialPortProxy {

    companion object {
        private const val TAG = "SerialPortProxy"
    }

    private lateinit var serialPortManager: SerialPortManager

    private var isInitSuccess = false

    private val isInit
        get() = isInitSuccess

    private fun initSdk() {
        searchAllDevices()

        serialPortManager = SerialPortKit.newBuilder(ContextProvider.appContext)
            // 设备地址
            .path("/dev/ttyS0")
            // 波特率
            .baudRate(115200)
            // Byte数组最大接收内存
            .maxSize(1024)
            // 发送失败重试次数
            .retryCount(2)
            // 发送一次指令,最多接收几次设备发送的数据,局部接收次数优先级高
            .receiveMaxCount(1)
            // 是否按照 maxSize 内存进行接收
            .isReceiveMaxSize(false)
            // 是否显示吐司
            .isShowToast(true)
            // 是否Debug模式,Debug模式会输出Log
            .debug(BuildConfig.DEBUG)
            // 是否自定义校验下位机发送的数据正确性,把校验好的Byte数组装入WrapReceiverData
            .isCustom(true, DataConvertUtil.customProtocol())
            // 校验发送指令与接收指令的地址位,相同则为一次正常的通讯
            .addressCheckCall(DataConvertUtil.addressCheckCall())
            .build()
            .get()

        isInitSuccess = true
    }

    private fun reInitSdk() {
        if (!isInit) {
            initSdk()
        }
    }

    fun searchAllDevices() {
        try {
            val serialPortFinder = SerialPortFinder()
            serialPortFinder.allDevices.forEach {
                Log.d(TAG, "搜索到的串口信息为: $it")
            }
        } catch (e: Exception) {
            Log.d(TAG, "initSerialPort: ", e)
        }
    }

    val portManager: SerialPortManager
        get() {
            reInitSdk()
            return serialPortManager
        }
}

SerialPortHelper

当然,光代理指定是不可以的,肯定还需要一个助手呀,助手这就来了,核心就是为了解决部分不需要的一些回调,进行充分的解耦。让发送指令单单就是发送指令,而让业务只专注它需要的数据回调。

暴露SDK接口

object SerialPortHelper {
    private val mProxy = SerialPortProxy()

    /**
     * 暴露SDK
     */
    val portManager: SerialPortManager
        get() = mProxy.portManager

    /**
     * 内部使用,默认开启串口
     */
    private val serialPortManager: SerialPortManager
        get() {
            // 默认开启串口
            if (!mProxy.portManager.isOpenDevice) {
                mProxy.portManager.open()
            }
            return portManager
        }
}

封装发送指令

object SerialPortHelper {

    /**
     * 读取设备版本信息
     *
     * @param listener 监听回调
     */
    fun readVersion(listener: OnReadVersionListener?) {
        val sends: ByteArray = SenderManager.getSender().sendReadVersion()
        val isSuccess: Boolean =
            serialPortManager.send(
                WrapSendData(sends, 3000, 300, 1),
                object : OnDataReceiverListener {
    
                    override fun onSuccess(data: WrapReceiverData) {
                        // xxx
                    }
    
                    override fun onFailed(wrapSendData: WrapSendData, msg: String) {
                        Log.e(TAG, "onFailed: $msg")
                    }
    
                    override fun onTimeOut() {
                        Log.d(TAG, "onTimeOut: 发送数据或者接收数据超时")
                    }
                })
        printLog(isSuccess, sends)
    }
}

检查回调数据是否符合要求

/**
 * 检测回调数据是否符合要求
 *
 * @param buffer 回调数据
 * @return true 符合要求 false 数据命令未通过校验
 */
private fun checkCallData(buffer: ByteArray): Boolean {
    val tempData = bytes2HexString(buffer)
    Log.i(TAG, "receive serialPort data :$tempData")
    return buffer[0] == SerialCommandProtocol.baseStart[0] && SerialCommandProtocol.checkHex(
        buffer
    )
}

校验数据进行组装分析

// 设备版本信息解析
override fun onSuccess(data: WrapReceiverData) {
    val buffer: ByteArray = data.data
    // 校验回调数据是否符合要求
    if (checkCallData(buffer)) {
        val serializeId: Int =
            ((buffer[7] and 0xFF.toByte()).toInt() shl 24) + ((buffer[8] and 0xFF.toByte()).toInt() shl 16) + ((buffer[9] and 0xFF.toByte()).toInt() shl 8) + (buffer[10] and 0xFF.toByte())
        listener?.let {
            // 如果使用默认 Task 发送指令,则回调线程为子线程,需要用户自行切主线程
            runOnUiThread {
                listener.onResult(
                    DeviceVersionModel(
                        String.format("%s", serializeId),
                        String.format("v %s.%s", buffer[3], buffer[4]),
                        String.format("v %s.%s", buffer[5], buffer[6])
                    )
                )
            }
        }
    }
}

工具类使用

打开串口

if (!SerialPortHelper.portManager.isOpenDevice) {
    val open = SerialPortHelper.portManager.open()
    Log.d(TAG, "串口打开${if (open) "成功" else "失败"}")
}

关闭串口

val close = SerialPortHelper.portManager.close()
Log.d(TAG, "串口关闭${if (close) "成功" else "失败"}")

发送数据

SerialPortHelper.portManager.send(WrapSendData(
    SenderManager.getSender().sendStartDetect()
),
    object : OnDataReceiverListener {
        override fun onSuccess(data: WrapReceiverData) {
            Log.d(TAG, "响应数据:${TypeConversion.bytes2HexString(data.data)}")
        }

        override fun onFailed(wrapSendData: WrapSendData, msg: String) {
            Log.e(
                TAG,
                "发送数据: ${TypeConversion.bytes2HexString(wrapSendData.sendData)}, $msg"
            )
        }

        override fun onTimeOut() {
            Log.e(TAG, "发送或者接收超时")
        }
    })

切换串口

val switchDevice = SerialPortHelper.portManager.switchDevice(path = "/dev/ttyS1")
Log.d(TAG, "串口切换${if (switchDevice) "成功" else "失败"}")

切换波特率

val switchDevice = SerialPortHelper.portManager.switchDevice(baudRate = 9600)
Log.d(TAG, "波特率切换${if (switchDevice) "成功" else "失败"}")

统一监听

在使用过程中,有情况为下位机给客户端进行发送数据,增加统一监听则可以收到下位机发来的指令,不经过地址校验,但仍通过指令是否合格校验。

override fun onResume() {
    super.onResume()
    // 增加统一监听回调
    SerialPortHelper.portManager.addDataPickListener(onDataPickListener)
}

override fun onPause() {
    super.onPause()
    // 移除统一监听回调
    SerialPortHelper.portManager.removeDataPickListener(onDataPickListener)
}

private val onDataPickListener: OnDataPickListener = object : OnDataPickListener {
    override fun onSuccess(data: WrapReceiverData) {
        Log.d(TAG, "统一响应数据:${TypeConversion.bytes2HexString(data.data)}")
    }
}

读取版本示例

以为为建议使用封装格式,进行隔离SDK以及指令处理,而避免直接在项目中直接使用,造成臃肿

SerialPortHelper.readVersion(object : OnReadVersionListener {
    override fun onResult(deviceVersionModel: DeviceVersionModel) {
        Log.d(TAG, "onResult: $deviceVersionModel")
    }
})

小结

最近已经快脱虚啦!给大家供上好用的示例 Demo,已经熬了我好长时间啦~若是有帮助到你的话,还希望多多给个 start,菜鸡给这献丑了。写的不好不要嘲笑我,若是你有更好的方案的话,还建议提 pr 哦!

SerialPortKit 再次奉上传送门