我们的程序收发十六进制命令,假设采用16个字节,一个包头(1字节),一个控制(命令字1字节),一个具体命令(数据13字节),一个校验位(1字节),由于java本身对内部控制的应用较少,所以这个前后总共花费了五六个小时才搞定,特此记录一下。
这个程序还是有一些缺点的,就是它并不是一次性的返回所有数据,我尝试了很多方式都不行,最后通过判断的方式只接受一条完整有效的命令。
类型转换
java的操作一般都是对字符串或数组的操作为主,这里的转换有两种方式:
数组
对数组的操作,就是定义一个16字节数组,然后对每一个字节单独赋值。
优点很明显,就是可以准确的控制到每一个字节,校验比对都很方便
缺点也同样明显,当命令较多时,编写不易,而且校验起来很麻烦。
byte[] buffer = new byte[16];
buffer[0] = (byte) 0xFD;
...
字符串
字符串是java中最常用的,优缺点也同样明显,但我还是用字符串了
首先命令得是确定值,因为字符串的修改,并不如数组那样方便,为了方便,我们在每个字节中加个空格,方便查看:
String commandStr = "FD E1 01 00 00 00 00 00 00 00 00 00 00 00 00 E2";
然后就是把它转换为byte数组,但是字符串有个问题,就是每次读取的是一个char,比如FD,会被分成两个部分,那么在编写转换类的时候,就需要把它们合成一个。
- 一个字节是8位, 那么我们首先取得F,然后左移4位,放在高4位的位置,低4位归零,D通过转换后放在第四位的位置上,这样就合并为一个完整的字节了:高4位 0x0f0,低4位 0x ff
- 对位置来说,我们每次需要取得两位进行合并,所以每次需要索引到字符串中对应的位置,下面的代码中封装为toByte方法
- 将Byte数组转换为hexString就太特么方便了,一下就 体现出java那强大的特性
// 十六进制字符串转二进制数组
public static byte[] hexStringToByte(String hex) {
hex = hex.replace(" ", "");
int len = (hex.length() / 2);
byte[] result = new byte[len];
char[] achar = hex.toCharArray();
for (int i = 0; i < len; i++) {
int pos = i * 2;
result[i] = (byte) (toByte(achar[pos]) << 4 | toByte(achar[pos + 1]));
}
return result;
}
//获取char的索引
private static int toByte(char c) {
return (byte) "0123456789ABCDEF".indexOf(c);
}
// byte数组转换为十六进制字符串,用java提供的转换类即可
public static final String bytesToHexString(byte[] bArray) {
StringBuffer sb = new StringBuffer(bArray.length);
String sTemp;
for (int i = 0; i < bArray.length; i++) {
sTemp = Integer.toHexString(0xFF & bArray[i]);
if (sTemp.length() < 2)
sb.append(0);
sb.append(sTemp.toUpperCase());
}
return sb.toString();
}
对SerialPortActivity的修改
之前对串口的封装及代码已经在 关于串口编程的总结 中了
对于read的无数次测试,最后我发现,还是没找到一次性读取的办法,如果有大神有好的办法可留言告诉我一声,万分感谢!!!
基本每次都读取一个字符的东西,但是有个问题,就是并不是所有的都是一个字节,有可能超出1个字节,为了防止这种事情发生,最好限定每次只读一个字节
size = mInputStream.read(buffer, 0, 1);
对应用类的修改
- 为了防止越界,还是把抽象方法加上一个size比较好,虽然已经是稳定的1个字节了..
- 判断包头是否正确
- 拷贝数组到全局变量,并线性偏移
@Override
protected void onDataReceived(byte[] buffer, int size) {
try {
if ((mSize == 0) && (buffer[0] == (byte) 0xFD)) {
System.arraycopy(buffer, 0, mBuffer, mSize, size);
mSize++;
} else if (mSize > 0) {
System.arraycopy(buffer, 0, mBuffer, mSize, size);
mSize++;
} else {
L.e("初始值不是FD " + ConvertUtils.bytesToHexString(buffer));
}
Handler handler = new Handler(Looper.getMainLooper());
handler.post(runnable);
} catch (ArrayIndexOutOfBoundsException e) {
L.e("error: over 16 bit.");
}
}
对于runnable来说,收到的消息同样要进行处理:
- 当runnable收到数据时,我们可预知命令大小是16个字节的,用全局size判断是否收取完整即可
- 包头 已经判断成功了,收取完整后通过约定的规则去判定校验位是否完整
- 对全局变量归零
if (mSize == 16) {
byte checkBit = (byte) (mBuffer[1] + mBuffer[2]);
if (checkBit == mBuffer[15]) {
// do something..
L.e("校验成功");
}
String mString = ConvertUtils.bytesToHexString(mBuffer);
L.e("command:" + mString);
mSize = 0;
mBuffer = new byte[16];
}