之前我写了如何使用串口发送和接收数据,但这只是最原始的一步,下面分享一下我开发基于广大DVR串口通信协议的过程。
概述:
1、每一帧的协议数据必须要是完整的才能用,主要是通过冗余校验位来检测。校验位放在每一帧数据的最后面,它的值是除了它自己之外前面所有数据的总和。每一帧数据的协议头始终是aa开头。
2、广大DVR串口协议要求主机系统每秒都要发送同步状态的请求给到DVR模块,于是在应用必须启动一个定时器来不断发送请求命令,我使用的是Handler来不断循环。此功能号是0x20。
3、一般的回复的数据只有一帧,但是DVR回放列表的数据,却会有6条数据。系统不断从串口读取DVR模块的数据,但是不是每一次都可以读取到完整的数据,而且有时候一次性读取出来的数据还不止一帧数据。大多数情况我们是处理完一条数据就丢弃掉一条数据的信息,但是读取到列表信息的时候,我们需要把列表信息存起来并共同显示到界面。因此我们要做一定的缓存和批量处理的算法。
4、由于处理的数据比较多,缓存buffer又是一个数组,不能处理一点前面的数据,就做一次移除和重新排列,因此使用索引指针的方式来标定数据处理到什么地方了。
代码详解:
1、检验校验位的算法:
private boolean isAppendData(byte[] buffer, int size) {
byte sum = 0;
for(int i = 0; i < buffer.length - 1; ++i) {
sum += buffer[i];
}
return !Integer.toHexString(sum & 255).equals(Integer.toHexString(buffer[size - 1] & 255));
}
返回true代表需要追加,也就是没有检验成功,需要缓存数据。
2、数据接收:
private byte[] mBufferData = new byte[512]; //缓存buffer
private int mBufferSumLength = 0; //缓存中数据的长度
public void setData(byte[] buffer, int size) {
boolean isAppend = this.isAppendData(buffer, size);
if(isAppend) {
if(this.mBufferSumLength + size < 512) {
/**
*拷贝数据到缓存buffer中
*/
System.arraycopy(buffer, 0, this.mBufferData, this.mBufferSumLength, size);
/**
*缓存长度增加
*/
this.mBufferSumLength += size;
/**
*这里表示是0x20数据的反馈,且刚好是一帧的数据。这里是为了快速响应每秒同步状态的数据
*因为这条协议中包含了dvr录制或者回放状态,还有录制或者回放的时间更新
*/
if(this.mBufferData[4] == 32 && this.mBufferSumLength == 14) {
this.dealSingleData();
return;
}
this.dealBufferDataAndResult(false);
} else {
this.dealBufferDataAndResult(true);
}
} else {
/**
*读取的这帧数据是完整的,直接处理,为了防止其他情况发生,需要去检查一下缓存buffer数据
*/
this.dealBufferDataAndResult(true);
this.notifyByteBufferListener(buffer, size);
}
}
3、buffer数据的处理算法
private List<byte[]> mByteList = new ArrayList(); //处理多条数据的集合
int index = 0; //buffer中数据处理的索引,表述数据处理到什么地方了
/**
*true表示是buffer已经溢出的情况,强行清空buffer数据
*/
private void dealBufferDataAndResult(boolean isForceClear) {
this.mByteList.clear();
/**
*必须保证每一帧数据都是从aa开始的
*/
String indexTheLastOne;
while(this.index < this.mBufferSumLength) {
indexTheLastOne = Integer.toHexString(this.mBufferData[this.index] & 255);
if("aa".equals(indexTheLastOne)) {
break;
}
++this.index;
}
/**
*首先保证要处理的数据的索引比buffer存在的数据的总长度小
*虽然现在处理的数据为aa,但是有可能缓存后面已经没有数据,防止不必要的计算
*/
while(this.index < this.mBufferSumLength && this.mBufferData[this.index + 3] != 0) {
indexTheLastOne = Integer.toHexString(this.mBufferData[this.index + 1] & 255);
String indexTheLastTwo = Integer.toHexString(this.mBufferData[this.index + 2] & 255);
/**
*这里继续判断协议的可用性,必须符合下面两个的判断,才能说明这个aa开头的数据是一条协议头,
*而不是前面命令中遗留的aa数据
*/
if(!"4d".equals(indexTheLastOne) || !"44".equals(indexTheLastTwo)) {
break;
}
/**
*这条数据的长度
*/
int lengthValue = this.mBufferData[this.index + 3];
//拷贝出这条数据并再次检验校验位
byte[] tempArr = new byte[lengthValue];
System.arraycopy(this.mBufferData, this.index, tempArr, 0, lengthValue);
boolean isnotPass = this.isAppendData(tempArr, lengthValue);
if(isnotPass) {
break;
}
/**
*校验通过,则把这条完整的协议数据放到list中,在循环检查完整个buffer之后,
*把这个list里的数据回调给监听接口
*/
this.mByteList.add(tempArr);
/**
*处理完一条数据,索引增加
*/
this.index += lengthValue;
Log.i("tf", "cycleone");
}
if(this.mByteList.size() > 0) {
this.notifyByteListListener(this.mByteList);
}
/**
*如果是处理的数据下标已经和整个长度一样,或者是强制清空的,那么就需要清空整个缓存buffer
*/
if(this.index == this.mBufferSumLength || isForceClear) {
this.clearBufferData();
}
}
4、处理单条buffer数据
private void dealSingleData() {
int lengthValue = this.mBufferData[this.index + 3];
byte[] tempArr = new byte[lengthValue];
System.arraycopy(this.mBufferData, this.index, tempArr, 0, lengthValue);
this.notifyByteBufferListener(tempArr, lengthValue);
this.clearBufferData();
}
5、清空buffer和标记
private void clearBufferData() {
for(int i = 0; i < this.mBufferData.length; ++i) {
this.mBufferData[i] = 0;
}
this.mBufferSumLength = 0;
this.index = 0;
}