前言
今天是居家隔离的第六天,又是一个非常奥利给的一天。早晨10点睁开了我朦胧的双眼皮的大眼睛,然后打了个大大的哈欠,伸伸老腰。大喊一声,起床。鹅鹅鹅鹅鹅鹅~估计又要有人笑出了声。
看居委会通知,今天下午1点开始又一轮核酸检测,呃呵,没搞错吧,外面在下大雨诶!经过我再三的确认以后,确实是现在开始又一轮的检测,然后我就去做核酸检测了。那雨水真的“大珠小珠落玉盘”一样,落在我的衣服上面。经过我一个小时的排队,终于做完了核算,带来的结果就是,我的鞋子全湿啦!感觉都可以养鱼了。
快速接入
在我的上一篇文章中已经给出了快速接入的方案,这里奉上传送门。
目标
SerialPortKit 截止目前开源也已有数日,根据大多朋友的反馈呢,都还挺不错,当然,在这段时间内,我也没有闲着,又是奋笔疾驰抓紧时间修复了些许 Bug,经过我不懈努力(水的一批)版本已经迭代到了 1.0.4-SNAPSHOT,早日争取搞个 Release版本,看样子,小欢子确实是挺给力的哈!不过吧,也有朋友反馈说,在使用过程中也遇到了一些困难,让能不能给一个好点的 Demo 示例拿来参考参考。我当然毅然决然的给否定了(弱弱的接了下来啦~),起初呢,我是这样子想的,这也是我的锅,为啥就不能“送人送到家,送佛送到西”,然后,我就在发文前两天,仔细琢磨了一下子,就开始整理好用的 Demo 示例。
思路
我是一个三流的“程序猿”,每天都在努力的码代码,搬砖。也接入过很多的SDK,每天都在与各种各样的三方SDK打交道,经常性的不是这问题就是那问题。也遇到过SDK大版本的升级,然后,“我C”这么多的爆红,项目中的每个文件我都需要修改一遍,好烦呀!给我整的痛不欲生。
后来通过我认真的思考,二十三种设计模式中选择代理模式、单例模式进行把SDK进行隔离,这样子的好处就是,当SDK升级导致API大变化的时候,或者是更换SDK的时候,都可以非常快速的完成更换。(关公面前刷大刀啦~)
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 哦!