搭载Android系统的智能硬件,因业务需求,App经常会用到串口通信交互数据。串口通信需自定义指令格式,且需自己处理数据交互过程中分包、黏包的情况。
1.指令格式
格式说明:
字段 | 说明 |
---|---|
STX | 起始符(固定为0x80,1 byte) |
LEN | 数据长度(CMD、DATA的长度,2 bytes) |
CMD | 业务类型(1 byte) |
DATA | 业务参数(TLV格式,可变长度) |
ETX | 结束符(固定为0x81,1 byte) |
CRC | CRC32校验(取高位两个字节,2 bytes) |
2.分包、黏包处理实现
处理逻辑:将每次串口收到的数据追加保存到全局的ByteBuffer中,并每次对ByteBuffer中实际数据长度与字段LEN计算的长度进行比较,截取一条完整有效的指令处理。
public class SerialPortUtils {
/**
* 串口数据
*/
private ByteBuffer byteBuffer;
public void receiveSerialMsg(String msg) {
try {
byte[] receiveBytes = hexStr2bytes(msg);
// 将串口收到的数据追加到ByteBuffer
byteBuffer.put(receiveBytes);
if(byteBuffer.position() >= 3) {
byte[] byteLength = new byte[2];
byteLength[0] = byteBuffer.get(1);
byteLength[1] = byteBuffer.get(2);
// 计算一条完整指令的长度
// 6表示STX(1 byte) + ETX(1 byte) + LEN(2 bytes) + CRC(2 bytes)
int realLen = (byteLength[0] << 8) + byteLength[1] + 6;
//byteBuffer实际长度与LEN字段长度比较,若相等则表示已经接收到一条完整的指令
if(realLen == byteBuffer.position()) {
byte[] effectiveBytes = new byte[realLen];
byteBuffer.flip();
byteBuffer.get(effectiveBytes, 0, realLen);
byteBuffer.clear();
dealEffectiveBytes(effectiveBytes);
} else if(realLen < byteBuffer.position()) { //粘包,截取有效数据,当一次收到多条完整指令时,根据业务场景决定多条指令的解析
int remainLen = byteBuffer.position() - realLen;
byte[] effectiveBytes = new byte[realLen];
byte[] remainBytes = new byte[remainLen];
byteBuffer.flip();
byteBuffer.get(effectiveBytes, 0, realLen);
byteBuffer.get(remainBytes, 0, remainLen); //剩余字节
byteBuffer.clear();
byteBuffer.put(remainBytes); //回填剩余字节,下一次收到数据继续追加接
// 根据业务场景,一次性收到多条完整指令时,只处理第一条指令,其余的舍弃
dealEffectiveBytes(effectiveBytes);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 解析一条完整的指令
private void dealEffectiveBytes(byte[] effectiveBytes) {
Map<String, String> outputMap = new HashMap<>();
//将指令解析结果存到Map中
int result = dataParser(effectiveBytes, outputMap);
}
public byte[] hexStr2bytes(String hex) {
int len = (hex.length() / 2);
byte[] result = new byte[len];
char[] achar = hex.toUpperCase().toCharArray();
for (int i = 0; i < len; i++) {
int pos = i * 2;
result[i] = (byte) (hexChar2byte(achar[pos]) << 4 | hexChar2byte(achar[pos + 1]));
}
return result;
}
//指令中业务参数解析
public int dataParser(byte[] data, Map<String, String> outputMap) {
// 校验STX、ETX
if (data[0] != (byte) 0x80 || data[data.length - 3] != (byte) 0x81) {
return ERROR_STX_ETX;
}
// 校验CRC
if (!checkCRC(data)) {
return ERROR_CRC;
}
int index, curLen = 0;
int dataLen = (data[1] << 8) + data[2];
// 校验长度
if ((dataLen + 6) != data.length) {
return ERROR_LENGTH;
}
byte[] temp = new byte[1];
//解析CMD
temp[0] = data[3];
String strCmd = byte2hex(temp);
outputMap.put("SERIAL_PORT_CMD", strCmd.toUpperCase());
if (data[3] == (byte) 0xA0) { //自定义的CMD
index = 4;
while (curLen < (dataLen - 1)) { //解析DATA,TLV格式
byte type = data[index++];
int length = data[index++];
byte[] value = new byte[length];
for (int i = 0; i < length; i++) {
value[i] = data[index++];
}
if (type == (byte) 0x01) { // 参数:时间
outputMap.put("SERIAL_PORT_TIME", new String(value));
} else if(type == (byte) 0x02) { // 参数:握手数据
outputMap.put("SERIAL_PORT_TEST", new String(value));
}
curLen += length + 2;
}
}
return 0;
}
}
receiveSerialMsg方法,在线程中循环接收到串口数据后调用。