Android串口通信,分包/黏包数据解析

搭载Android系统的智能硬件,因业务需求,App经常会用到串口通信交互数据。串口通信需自定义指令格式,且需自己处理数据交互过程中分包、黏包的情况。

1.指令格式
在这里插入图片描述
格式说明:

字段说明
STX起始符(固定为0x80,1 byte)
LEN数据长度(CMD、DATA的长度,2 bytes)
CMD业务类型(1 byte)
DATA业务参数(TLV格式,可变长度)
ETX结束符(固定为0x81,1 byte)
CRCCRC32校验(取高位两个字节,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方法,在线程中循环接收到串口数据后调用。

  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值