在Build.gradle(:project)下导入jitpack库
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
在新版gradle中,在settings.gradle下导入jitpack库
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
maven { url "https://jitpack.io" }
}
}
在Build.gradle(:module)中添加:
dependencies {
implementation 'com.github.BASS-HY:EasySerial:1.0.0'
}
1.创建一个永久接收的串口(串口开启失败返回Null);
演示:不做自定义返回数据的处理;
A). 创建一个串口,串口返回的数据默认为ByteArray类型;
val port = EasySerialBuilder.createKeepReceivePort("/dev/ttyS4", BaudRate.B4800)
我们还可以这样创建串口,串口返回的数据默认为ByteArray类型;
val port = EasySerialBuilder.createKeepReceivePort(
"/dev/ttyS4",
BaudRate.B4800, DataBit.CS8, StopBit.B1,
Parity.NONE, 0, FlowCon.NONE
)
B). 设置串口每次从数据流中读取的最大字节数,默认为64个字节;
注意:必须在调用[addDataCallBack]之前设置,否则设置无效;
port.setMaxReadSize(64)
C).设置数据的读取间隔;
port.setReadInterval(100)
D). 监听串口返回的数据;
第一种写法;须注意,此回调处于协程之中;
val dataCallBack = port.addDataCallBack {
//处理项目逻辑;
// 此处示范将串口数据转化为16进制字符串;
val hexString = it.conver2HexString()
Log.d(tag, "接收到串口数据:$hexString")
}
port.addDataCallBack(dataCallBack)
第二种写法;须注意,此回调处于协程之中;
val dataCallBack = object : EasyReceiveCallBack {
override suspend fun receiveData(dataList: List) {
//处理项目逻辑;
//此处示范将串口数据转化为16进制字符串;
if (dataList.isEmpty()) return
val hexString = dataList.last().conver2HexString()
Log.d(tag, "接收到串口数据:$hexString")
}
}
port.addDataCallBack(dataCallBack)
E). 移除串口监听;
port.removeDataCallBack(dataCallBack)
F). 关闭串口;
使用完毕关闭串口,关闭串口须在作用域中关闭,关闭时会阻塞当前协程,直到关闭处理完成;
这个过程并不会耗费太长时间,一般为1ms-4ms;
CoroutineScope(Dispatchers.IO).launch { port.close() }
2. 创建一个永久接收的串口(串口开启失败返回Null);
演示:自定义回调的数据类型,在接收到串口数据后对数据进行一次处理,再将数据返回给串口数据监听者;
A). 创建一个串口,串口返回的数据类型,我们自定义为String类型;
val port = EasySerialBuilder.createKeepReceivePort("/dev/ttyS4", BaudRate.B4800)
我们还可以这样创建串口,串口返回的数据类型,我们自定义为String类型;
val port = EasySerialBuilder.createKeepReceivePort(
"/dev/ttyS4",
BaudRate.B4800, DataBit.CS8, StopBit.B1,
Parity.NONE, 0, FlowCon.NONE
)
B). 设置串口每次从数据流中读取的最大字节数,默认为64个字节;
注意:必须在调用[addDataCallBack]之前设置,否则设置无效;
port.setMaxReadSize(64)
C).设置数据的读取间隔;
port.setReadInterval(100)
D).因为我们设置数据返回类型不再是默认的ByteArray类型,所以我们需要设置自定义的数据解析规则;
port.setDataHandle(CustomEasyPortDataHandle())
接下来我们创建一个自定义解析类,并将其命令为CustomEasyPortDataHandle;
class CustomEasyPortDataHandle : EasyPortDataHandle() {
private val stringList = mutableListOf()//用于记录数据
private val stringBuilder = StringBuilder()//用于记录数据
private val pattern = Pattern.compile("(AT)(.*?)(\r\n)")//用于匹配数据
/**
* 数据处理方法
*
* @param byteArray 串口收到的原始数据
* @return 返回自定义处理后的数据,此数据将被派发到各个监听者
*
*
* 我们可以在这里做很多事情,比如有时候串口返回的数据并不是完整的数据,
* 它可能有分包返回的情况,我们需要自行凑成一个完整的数据后再返回给监听者,
* 在数据不完整的时候我们直接返回空数据集给监听者,告知他们这不是一个完整的数据;
*
* 在这里我们做个演示,假设数据返回是以AT开头,换行符为结尾的数据是正常的数据;
*
*/
override suspend fun portData(byteArray: ByteArray): List {
//清除之前记录的匹配成功的数据
stringList.clear()
//将串口数据转为16进制字符串
val hexString = byteArray.conver2HexString()
//记录本次读取到的串口数据
stringBuilder.append(hexString)
while (true) {//循环匹配,直到匹配完所有的数据
//寻找记录中符合规则的数据
val matcher = pattern.matcher(stringBuilder)
//没有寻找到符合规则的数据,则返回Null
if (!matcher.find()) break
//寻找到符合规则的数据,记录匹配成功的数据,并将其从StringBuilder中删除
val group = matcher.group()
stringList.add(group)
stringBuilder.delete(matcher.start(), matcher.end())
}
//返回记录的匹配成功的数据
return stringList.toList()
}
/**
* 串口关闭时会回调此方法
* 如果您需要,可重写此方法,在此方法中做释放资源的操作
*/
override fun close() {
stringBuilder.clear()
stringList.clear()
}
}
E). 监听串口返回的数据;
此时,我们监听到的数据为我们一开始设置的String?类型;
val dataCallBack = object : EasyReceiveCallBack<String> {
override suspend fun receiveData(dataList: List<String>) {
//返回的数据集内没有数据,则表明没有匹配成功的数据;
//我们这里不处理没有匹配成功的情况;
if (dataList.isEmpty()) return
//处理项目逻辑;
//此处演示直接将转化后的数据类型打印出来;
dataList.forEach { Log.d(tag, "接收到串口数据:$it") }
}
}
F). 移除串口监听,关闭串口与之前的一致;
port.addDataCallBack(dataCallBack)//添加串口数据监听
port.removeDataCallBack(dataCallBack)//移除串口数据监听
CoroutineScope(Dispatchers.IO).launch { port.close() }//关闭串口
A). 创建一个发送后再接收的串口(串口开启失败返回Null);
val port = EasySerialBuilder.createWaitRspPort("/dev/ttyS4", BaudRate.B4800)
我们还可以这样创建串口:
val port = EasySerialBuilder.createWaitRspPort(
"/dev/ttyS4",
BaudRate.B4800, DataBit.CS8, StopBit.B1,
Parity.NONE, 0, FlowCon.NONE
)
B). 设置串口每次从数据流中读取的最大字节数,默认为64个字节;
port.setMaxReadSize(64)
C).设置数据的读取间隔;
port.setReadInterval(100)
D). 接下来演示发送串口命令的3种方法
1. 调用写入函数,并阻塞等待200ms,阻塞完成之后将会返回此期间接收到的串口数据;
需要注意的是,此方法需要在协程作用域中调用;
val rspBean : WaitResponseBean = port.writeWaitRsp(orderByteArray1)
此外,我们也可以在调用此函数时指定等待时长,此处我们演示等待500ms:
val rspBean : WaitResponseBean = port.writeWaitRsp(orderByteArray1, timeOut = 500)
还可以,指定本次请求接收的数据的最大字节数,如下示例:
val rspBean : WaitResponseBean = port.writeWaitRsp(orderByteArray1, bufferSize = 64)
rspBean
是一个封装的数据类,我们来讲解一下:
rspBean.bytes
这是串口返回的数据,此字节数组的大小为调用写入时指定的bufferSize的大小;
需要注意的是,字节数组内的字节并不全是串口返回的数据;
默认的bufferSize大小为64,我们假设串口返回了4个字节的数据,那么其余的60个字节都是0;
那我们怎么知道实际收到了多少个字节呢?这就需要用到数据类内的另一个数据:
rspBean.size
这是串口实际读取的字节长度,所以我们取串口返回的实际字节数组可以这样取:
val portBytes = rspBean.bytes.copyOf(rspBean.size)
插句题外话,我们也提供了直接将读取到的字节转为16进制字符串的方法:
val hexString = rspBean.bytes.conver2HexString(rspBean.size)
2. 有时候,我们可能需要连续向串口输出命令,并等待其返回,对此我们也提供了便捷的方案:
val rspBeanList : MutableList<WaitResponseBean> = port.writeAllWaitRsp(orderByteArray1, orderByteArray2, orderByteArray3)
或者是指定每个请求的超时时间:
val rspBeanList : MutableList<WaitResponseBean> = port.writeAllWaitRsp(200, orderByteArray1, orderByteArray2, orderByteArray3)
又或者,即指定每个请求的超时时间,也指定每个请求接收数据的最大字节数:
val rspBeanList : MutableList<WaitResponseBean> = port.writeAllWaitRsp(200, 64, orderByteArray1, orderByteArray2, orderByteArray3)
同样的,这些方法也是需要在协程作用域中调用;
我们来讲解一下函数参数:
第1个参数:单个写入命令的阻塞等待时长,注意,这是单个的,并非所有命令的总阻塞时长;
第2个参数:一个数组类型,即我们想写入并等待返回的所有指令集;
rspBeanList
为多个WaitResponseBean
的集合,我们在上面已经讲解了WaitResponseBean
,此处不再赘述;集合中的数据与请求是一一对应:
val rspBean1 : WaitResponseBean = rspBeanList[0]//orderByteArray1
val rspBean2 : WaitResponseBean = rspBeanList[1]//orderByteArray2
val rspBean3 : WaitResponseBean = rspBeanList[2]//orderByteArray3
3. 在同一个串口中,我们有些需要等待串口的数据返回,有些是不需要的,在不需要串口数据返回的情况下,我们可以直接调用写入即可:
port.write(orderByteArray1)
相同的,此方法必须在协程作用域中调用;
E). 关闭串口:
使用完毕关闭串口,关闭串口须在协程作用域中关闭,关闭时会阻塞当前协程,直到关闭处理完成;
这个过程并不会耗费太长时间,一般为1ms-4ms;
CoroutineScope(Dispatchers.IO).launch { port.close() }
1. 获取串口对象:
每一个串口只会创建一个实例,我们在内部缓存了串口实例,即一处创建,到处可取;如果此串口还未创建,则将获取到Null;
val serialPort: BaseEasySerialPort? = EasySerialBuilder.get("dev/ttyS4")
获取到实例后,我们仅可以调用close()方法关闭串口;
此方法必须在协程作用域中调用;
CoroutineScope(Dispatchers.IO).launch { serialPort.close() }
如果你明确知道当前串口属于哪种类型,那么你可以进行类型强转后使用更多特性。如:
val easyWaitRspPort = serialPort.cast2WaitRspPort()
CoroutineScope(Dispatchers.IO).launch {
val rspBean = easyWaitRspPort.writeWaitRsp("00 FF AA".conver2ByteArray())
}
或者是:
val keepReceivePort = serialPort.cast2KeepReceivePort<ByteArray>()
keepReceivePort.write("00 FF AA".conver2ByteArray())
2. 设置串口不读取字节数:
如果你发现,串口无法收到数据,但是可正常写入数据,使用串口调试工具可正常收发,那么你应当试试如下将串口设置为无法读取字节数:
EasySerialBuilder.addNoAvailableDevicePath("dev/ttyS4")
设置完后再开启串口,否则设置不生效;也可以直接这么写:
EasySerialBuilder.addNoAvailableDevicePath("dev/ttyS4")
.createWaitRspPort("dev/ttyS4", BaudRate.B4800)
对于addNoAvailableDevicePath()
方法,需要讲解一下内部串口数据读取的实现了;
在读取数据时,会先调用inputStream.available()
来判断流中有多少个可读字节,但在部分串口中,即使有数据,
available()
读取到的依旧是0,这就导致了无法读取到数据的情况;
当调用addNoAvailableDevicePath()
后, 我们将不再判断流中的可读字节数,而是直接调用inputStream.read()
方法;
当你使用此方法后,请勿重复开启\关闭串口, 因为这样可能会导致串口无法再工作;
3. 获取本设备所有的串口名称:
val allDevicesPath: MutableList<String> = EasySerialFinderUtil.getAllDevicesPath()
allDevicesPath.forEach { Log.d(tag, "串口名称: $it") }
4. 判断当前是否有串口正在使用:
val hasPortWorking: Boolean = EasySerialBuilder.hasPortWorking()
5. 串口日志打印开关:
是否打印串口通信日志 true为打印日志,false为不打印;
建议在Release版本中不打印串口日志;
打印的日志的 tag = “EasyPort”;
EasySerialBuilder.isShowLog(true)
6. 数据转化:
A). 16进制字符串转为字节数组:
val hexString = "00 FF CA FA"
//将16进制字符串 转为 字节数组
val hexByteArray1 = hexString.conver2ByteArray()
//将16进制字符串从第0位截取到第4位("00 FF") 转为 字节数组
val hexByteArray2 = hexString.conver2ByteArray(4)
//将16进制字符串从第2位截取到第4位(" FF") 转为 字节数组
val hexByteArray3 = hexString.conver2ByteArray(2, 4)
B). 字节数组转为16进制字符串:
val byteArray = byteArrayOf(0, -1, 10)// 此字节数组=="00FF0A"
//将字节数组 转为 16进制字符串
val hexStr1 = byteArray.conver2HexString()//结果为:"00FF0A"
//将字节数组取1位 转为 16进制字符串
val hexStr2 = byteArray.conver2HexString(1)//结果为:"00"
//将字节数组取2位 转为 16进制字符串 并设置字母为小写
val hexStr3 = byteArray.conver2HexString(2, false)//结果为:"00ff"
//将字节数组取第2位到第3位 转为 16进制字符串 并设置字母为小写
val hexStr4 = byteArray.conver2HexString(1, 2, false)//结果为:"ff0a"
//将字节数组取第0位 转为 16进制字符串 并设置字母为小写
val hexStr5 = byteArray.conver2HexString(0, 0, false)//结果为:"00"
//将字节数组 转为 16进制字符串 16进制之间用空格分隔
val hexStr6 = byteArray.conver2HexStringWithBlank()//结果为:"00 FF 0A"
//将字节数组取2位 转为 16进制字符串 16进制之间用空格分隔
val hexStr7 = byteArray.conver2HexStringWithBlank(2)//结果为:"00 FF"
//将字节数组取2位 转为 16进制字符串 并设置字母为小写
val hexStr8 = byteArray.conver2HexStringWithBlank(2, false)//结果为:"00 ff"
//将字节数组取第2位到第3位 转为 16进制字符串 并设置字母为小写
val hexStr9 = byteArray.conver2HexStringWithBlank(1, 2, false)//结果为:"ff 0a"
//将字节数组取第2位 转为 16进制字符串 并设置字母为小写
val hexStr10 = byteArray.conver2HexStringWithBlank(1, 1, false)//结果为:"ff"
C). 字节数组转为字符数组:
val byteArray2 = byteArrayOf('H'.code.toByte(), 'A'.code.toByte(), 'H'.code.toByte(), 'A'.code.toByte())
//将字节数组 转为 字符数组
val charArray1 = byteArray2.conver2CharArray()//即:"HAHA"
//将字节数组取1位 转为 字符数组
val charArray2 = byteArray2.conver2CharArray(1)//即:"H"
//将字节数组取第2位到第3位 转为 字符数组
val charArray3 = byteArray2.conver2CharArray(2, 3)//即:"HA"
//将字节数组第2位 转为 字符数组
val charArray4 = byteArray2.conver2CharArray(2, 2)//即:"H"
D). 字节数组计算CRC值:
val byteArray3 = byteArrayOf(1, 3, 0, 0, 0, 9)
//计算字节数组的CRC值:
val crc1 = byteArray3.getCRC()
//将字节数组取2位,计算CRC值:
val crc2 = byteArray3.getCRC(2)
//将字节数组取第2位到第3位,计算CRC值:
val crc3 = byteArray3.getCRC(2, 3)
//将crc转为字节的计算示例:
val highByte = (crc1 and 0xFF).toByte()//高位
val lowByte = (crc1 shr 8 and 0xFF).toByte()//低位
github地址 如果有什么意见和建议都可以在github或者在本文章中向我提出噢,我也会一直完善此项目的;
此项目移植于谷歌官方串口库android-serialport-api ,以及GeekBugs-Android-SerialPort ,综合了这俩个库的C代码,对其进行的封装;
文章地址:
https://blog.csdn.net/weixin_45379305/article/details/127719263?spm=1001.2014.3001.5501