Sticky packet processing for BLE Bluetooth packet reception in android

foreword

In android, the BLE feature reads and writes a maximum length of 20 bytes at a time. For a frame structure larger than 20 bytes that is continuously sent for a long time, if the Android terminal receives it, we need to reframe it (that is, how to deal with the problem of sticky packets and packet loss). For how to deal with this problem, we first need to do a good job in designing the frame structure. A relatively complete frame should include the frame header, frame length, frame serial number, and frame end. This information is used to determine whether to drop frames and reframe them.
Take a Bluetooth receiving project we did recently, we designed each frame to be 128 bytes. It is specifically defined as:

/**
 * Created by sks on 2017/12/1.
 * /**
 * @author sks
 * *  定义帧的结构
 *
 *
 *       帧头:4个字节   ,  0xbebebebe
 *       帧计数:1个字节      
 *      帧长度:1个字节     128
 *      sessionId: 4个字节
 *      heart_array: 心电数组  100字节
 *      breath_array:呼吸数组  10字节
 *      temperature_value:体温  2字节
 *      heart_value: 心率  1字节
 *      breath_value: 呼吸率 1字节
 *      power_level:电池标志位  1字节
 *      reserved_array: 保留字节数组  1字节
 *      帧尾:   2字节   0xaaaa
 *
 *
 */

public class EcgMsg {

    /**
     *
     */
    public EcgMsg() {
        super();
    }

    private byte[] frameHeader;
    private int frameNum;   //一个字节
    private int frameLength;
    private byte[] sessionID;
    private byte[] heart_array;
    private byte[] breath_array;
    private float temperature_value;
    private int heart_value;
    private int breath_value;
    private int power_level;
    private byte[] reserved_array;

    public EcgMsg(byte[] frameHeader, int frameNum, int frameLength, byte[] sessionID, byte[] heart_array, byte[] breath_array, float temperature_value, int heart_value, int breath_value, int power_level, byte[] reserved_array) {
        this.frameHeader = frameHeader;
        this.frameNum = frameNum;
        this.frameLength = frameLength;
        this.sessionID = sessionID;
        this.heart_array = heart_array;
        this.breath_array = breath_array;
        this.temperature_value = temperature_value;
        this.heart_value = heart_value;
        this.breath_value = breath_value;
        this.power_level = power_level;
        this.reserved_array = reserved_array;
    }

    public byte[] getFrameHeader() {
        return frameHeader;
    }

    public void setFrameHeader(byte[] frameHeader) {
        this.frameHeader = frameHeader;
    }

    public int getFrameNum() {
        return frameNum;
    }

    public void setFrameNum(int frameNum) {
        this.frameNum = frameNum;
    }

    public int getFrameLength() {
        return frameLength;
    }

    public void setFrameLength(int frameLength) {
        this.frameLength = frameLength;
    }

    public byte[] getSessionID() {
        return sessionID;
    }

    public void setSessionID(byte[] sessionID) {
        this.sessionID = sessionID;
    }

    public byte[] getHeart_array() {
        return heart_array;
    }

    public void setHeart_array(byte[] heart_array) {
        this.heart_array = heart_array;
    }

    public byte[] getBreath_array() {
        return breath_array;
    }

    public void setBreath_array(byte[] breath_array) {
        this.breath_array = breath_array;
    }

    public float getTemperature_value() {
        return temperature_value;
    }

    public void setTemperature_value(float temperature_value) {
        this.temperature_value = temperature_value;
    }

    public int getHeart_value() {
        return heart_value;
    }

    public void setHeart_value(int heart_value) {
        this.heart_value = heart_value;
    }

    public int getBreath_value() {
        return breath_value;
    }

    public void setBreath_value(int breath_value) {
        this.breath_value = breath_value;
    }

    public int getPower_level() {
        return power_level;
    }

    public void setPower_level(int power_level) {
        this.power_level = power_level;
    }

    public byte[] getReserved_array() {
        return reserved_array;
    }

    public void setReserved_array(byte[] reserved_array) {
        this.reserved_array = reserved_array;
    }

    @Override
    public String toString() {
        return "EcgMsg{" +
                "frameHeader=" + Arrays.toString(frameHeader) +
                ", frameNum=" + frameNum +
                ", frameLength=" + frameLength +
                ", sessionID=" + Arrays.toString(sessionID) +
                ", heart_array=" + Arrays.toString(heart_array) +
                ", breath_array=" + Arrays.toString(breath_array) +
                ", temperature_value=" + temperature_value +
                ", heart_value=" + heart_value +
                ", breath_value=" + breath_value +
                ", power_level=" + power_level +
                ", reserved_array=" + Arrays.toString(reserved_array) +
                '}';
    }
}

The front end of the hardware adopts a serial port to Bluetooth module, and it is received through Android Bluetooth. It can be found that the receiving method is 128 bytes divided into 7 times, that is, 20*6+8. During the transmission process, one of these 7 receptions may be lost, so the number we receive may be
- the frame header cannot be found, that is, the number sent for the first time in the 7 receptions is lost;
- the frame tail cannot be found , that is, the last reception of 8 bytes fails
- a certain reception fails in the middle, in this case, the frame header and the end of the frame are in, but the intermediate data is lost
- a combination of the above conditions.
For the above cases, we have to reframe after receiving, all must be considered.
In java, the tcp framework often uses the netty library, which can automatically perform tcp packet processing (handling sticky packets) for us through the encapsulated method. Its core is to define a core class: buffer ByteBuf, which is more powerful than Java's native NIO bytebuffer. Therefore, we can consider borrowing netty's ByteBuf buffer for framing for our bluetooth reception.

Introduction to Netty's Bytebuf class

ByteBuf maintains a byte array, and its internal format is:

 *      +-------------------+------------------+------------------+
 *      | discardable bytes |  readable bytes  |  writable bytes  |
 *      |                   |     (CONTENT)    |                  |
 *      +-------------------+------------------+------------------+
 *      |                   |                  |                  |
 *      0      <=      readerIndex   <=   writerIndex    <=    capacity

Compared with the original ByteBuffer, it maintains two pointers, one is the read pointer and the other is the write pointer, while the original ByteBuffer only maintains one pointer, you need to call the flip method to switch the read and write status, which is not easy for users Management and maintenance

When reading, the readable range is the subscript range [readerIndex, writeIndex), and the writable range is [writerIndex, capacity-1], but the discardable range will become relatively useless, neither reading nor can Write

So we can use the discardReadBytes method to recycle the memory space, after the recycling is like this:

 *      +------------------+--------------------------------------+
 *      |  readable bytes  |    writable bytes (got more space)   |
 *      +------------------+--------------------------------------+
 *      |                  |                                      |
 * readerIndex (0) <= writerIndex (decreased)        <=        capacity

Another important function of bytebuf is to mark Mark and reset reset.

private int markedReaderIndex;//标记读索引
private int markedWriterIndex;//标记写索引

Through the reset method, the read pointer can be returned to the marked read index position

accomplish

First define the Bytebuf variable and initialize the buffer size

 private ByteBuf buffer= Unpooled.buffer(1024*1000);

Suppose that in the callback method of Bluetooth receiving data, the number received each time is obtained by the following code:

  final byte[] data = characteristic.getValue();

For the continuous data array, it needs to be sent to the buffer and then framed

        if (data != null && data.length > 0) {
            buffer.writeBytes(data);     //将蓝牙收到的byte数组放入bytebuf缓冲区中
         //   Log.i("tag", "缓冲区大小" + buffer.readableBytes() + "");

            //重新组帧
            while (buffer.readableBytes() > MyApplication.FRAME_LENGTH) {     //判断缓冲区大小大于一帧长度,就    
   可以进行解帧
                ByteBuf bufTemp = buffer.readBytes(1);    //先取第一个字节,判断是不是帧头的第一个字节0xbe
                byte[] bytesTemp = new byte[1];
                bufTemp.readBytes(bytesTemp);
                if (bytesTemp[0] == (byte) 0xbe) {        //判断第一个字节是不是0xbe,如果不是,直接丢弃,如果是,则进入if判断
                    buffer.markReaderIndex();     //取后三个数时候,考虑如果第1个是0xbe,但是后面三个不是0xbe,这个时候需要进行回滚操作
                    ByteBuf bufTemp1 = buffer.readBytes(3);   //如果第一个字节是0xbe,就再取后三个字节,继续对帧头进行判断
                    byte[] bytesTemp1 = new byte[3];
                    bufTemp1.readBytes(bytesTemp1);
                    if (bytesTemp1[0] == (byte) 0xbe && bytesTemp1[1] == (byte) 0xbe && bytesTemp1[2] == (byte) 0xbe) {      //如果后三位是0xbebebe,说明找到了帧头,就需要取一帧的后续部分(帧长-帧头)=128-4=124个字节
                            ByteBuf bufTemp2 = buffer.readBytes(MyApplication.FRAME_LENGTH - 4);
                            byte[] bytesTemp2 = new byte[MyApplication.FRAME_LENGTH - 4];
                            bufTemp2.readBytes(bytesTemp2);
                           //取出帧的后续部分,还需要判断帧尾是不是0xaa,0xaa;如果不是0xaaaa,说明这个帧不完整,即需要重新进入第二个字节搜索帧头
                            if (bytesTemp2[bytesTemp2.length-2]!=(byte)0xaa&&bytesTemp2[bytesTemp2.length-1]!=(byte)0xaa){
                                buffer.resetReaderIndex();   //指针回滚,回滚到只是取出第一个数
                                continue;       //重新进入while循环
                             }
                   //         查看帧计数
                            new_frame_index=bytesTemp2[0]&0xff;
                            if (new_frame_index!=(old_frame_index+1)||((new_frame_index==0)&&(old_frame_index!=255))){
                                    error_Frame_count++;
                                   log2Flie.log2FileFun("log","帧计数错误"+"error_Frame_count-->"+error_Frame_count+";old_frame-->"+old_frame_index+";new_frame-->"+new_frame_index);
                            }
                            old_frame_index=new_frame_index;
                            //重新组帧
                            byte[] bytesTemp3 = new byte[MyApplication.FRAME_LENGTH];
                            for (int i = 0; i < 4; i++) {
                                bytesTemp3[i] = (byte) 0xbe;
                            }
                            System.arraycopy(bytesTemp2, 0, bytesTemp3, 4, bytesTemp2.length);
                            //如果是,则放入list
                            synchronized (byteListInbuf) {
                                byteListInbuf.add(bytesTemp3);     //放入byteListInbuf链表的帧shiw

                            }
                            buffer.discardReadBytes();   //将取出来的这一帧数据在buffer的内存进行清除,释放内存

                    }else{       //第一个字节是0xbe,后三个字节不是0xbebebe情况
                            buffer.resetReaderIndex();   //指针回滚,回滚到只是取出第一个数
                            continue;
                        }
                }
            }
        }

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325901165&siteId=291194637