Android串口丢包重发的重传协议,一种流式重传协议

本文介绍了Android系统下实现串口通信的重传协议,包括协议流程、数据包结构和关键代码解释。通过设置StartFlag、Header、Payload和Checksum确保数据完整。协议提供send()和flush()接口,以及错误处理机制,当接收错误时能从错误帧开始重发数据,确保数据传输的可靠性。
摘要由CSDN通过智能技术生成

前言

最近在做一款产品,主要功能是android 接收到音频数据通过串口实时发送MCU处理,要求延迟小,速度快,传输稳定性有保障。我们都知道串口是一种不可信任性传输。不能保障传输数据完整性,在产品测试过程中也发现低概率性出现数据丢包现象。所以需要设计一种支持流式可靠性传输协议。
产品需要Android串口跟MCU串口通信传输音频流,每次传输大概1M左右,MCU接收到音频后需要立马处理,所以需要尽可能快接收数据处理,不能接收每一帧会回传校验结果。只有等到有错误帧才返回给Android 请求重发错误帧,如果一次性传输1M,体积过大,时间过程长,如果数据有误需要重传1M数据,延时性太大。所以拆包每次发送10K数据发送。
所以设计了一种流式传输且支持重传的串口协议,支持传输数据大小是1B ~ 1G , 传输数据帧大小10K,小于10K按照实际大小发送,大于10K则拆包发送。比如100K分成10包发送。正常发送不必回传结果,只有错误才回传错误帧的index,比如发送到低5包出错,则回传给Android 第5帧错误。Android重新从5包发送数据。

在这里插入图片描述
协议大概流程图如图所示。
既然是数据传输必然有传输格式协议,使用简单的TLV协议即可。
传输协议简介如下:

包结构

在这里插入图片描述
Header:包头
Payload:有效载荷,Module_id+Msg_id+Data
Checksum:校验和,校验数据为Header + Payload
在这里插入图片描述
StartFlag:固定值0xFFAA5500,每一个数据包固定的开始序列
Handle:未使用,默认0
Version:0x01 协议版本,第一个版本
Length: payload区域的长度+ Checksum字段
Opcode:
Opcode 方法定义 描述
0x01 request Request需要和response配合使用,每个request都必须有一个response的应答。Request可以由主 设备(主机)发起,也可以由从设备(翻译机、副机)发起。
0x02 response 回复对应的request的response
0x03 indication 主设备推送消息给从设备
0x04 notification 从设备推送消息给主设备
下面就是代码讲解:
协议对我提供两个接口 send() 和 flush 两个接口。和一个回调接口OnSendCallback。
已一个传输文件为例
send 将文件保存到缓存中,此时并没有发送。//发送缓存大小可修改,当前定义4M
flush 将缓存文件拆包通过串口发送 ,是真正的发送文件

先放demo 的文件


package com.dfxh.wang.serialport_test;

import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.media.MediaPlayer;
import android.os.BatteryManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static android.text.InputType.TYPE_CLASS_NUMBER;

public class MainActivity extends AppCompatActivity implements SerialPortUtils.OnSendCallback,View.OnClickListener {
   

    // Used to load the 'native-lib' library on application startup.
    static {
   
        System.loadLibrary("native-lib");
    }
    private final String TAG = "MainActivity";

    private int looptime = 100;
    private Context mContext;
    private boolean flag = true;
    private String mFileName = "/sdcard/receive.bin";
    private String mSendFileName = "/sdcard/send.bin";
    private String mSendFileName1 = "/sdcard/test.txt";
    String path ="/storage/emulated/0/testFolder/testFile";
    private CustomServiceDataHelper mDtaHelper = new CustomServiceDataHelper();
    public static SerialPortUtils serialPortUtils;
    private static Lock lock = new ReentrantLock();
    private long receiveFileLength = 0;
    private ExecutorService singleThreadExecutor;
    private int send_index = 1;
    private int sendtime = 0;
    BatteryManager batteryManager;
    private boolean charging = false;
    private boolean no_enough_space_flag = false;
    private Handler mHandler = new Handler() {
   
        @Override
        public void handleMessage(Message msg) {
   
            switch (msg.what) {
   
                case 2://通知发送方错误帧id
                //    serialPortUtils.sendErorPackIndex();
                    break;
                case 3://通知发送方错误帧id
                    serialPortUtils.sendErorPackIndex();
                    break;
                case 7:
                    //重新发送错误数据包
                    short index = (short) msg.arg1;
                    ReSendErrorData(index);
                    Log.d(TAG, "mHandler  resend  data index= " + index);
                    break;
                case 8:
                    Log.d("SendData", "mHandler send new data  =====");
                    if ((sendtime <1000)&&(!no_enough_space_flag)){
   
                        Log.d(TAG, "send file time : " + sendtime);
                       // startSendBinFile1(sendtime);
                        startSendBinFile();
                        sendtime++;
                    }else {
   
                        Log.d(TAG, "no_enough_space_flag: " + no_enough_space_flag);
                    }
                    break;
                case 11:
                     led_red_on();
                    break;
                case 12:
                    led_green_on();
                    break;
            }
        }
    };
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
   
        super.onCreate(savedInstanceState);
        mContext = this;
        setContentView(R.layout.activity_main);
        Log.e(TAG,"MainActivity  onCreate ====================");
        TextView tv = findViewById(R.id.lv);
        serialPortUtils = new SerialPortUtils(mHandler,this);
        serialPortUtils.setSendCallback(this);
        singleThreadExecutor = Executors.newSingleThreadExecutor();
        Button open_bt = (Button) findViewById(R.id.open_bt);
        open_bt.setOnClickListener(this);
        Button send_bt = (Button) findViewById(R.id.send_bt);
        send_bt.setOnClickListener(this);
        Button close_bt = (Button) findViewById(R.id.close);
        close_bt.setOnClickListener(this);
        IntentFilter mIntentFilter = new IntentFilter();
        mIntentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
        mIntentFilter.addAction(Intent.ACTION_BATTERY_LOW);
        mIntentFilter.addAction(Intent.ACTION_BATTERY_OKAY);
        mIntentFilter.addAction(Intent.ACTION_POWER_CONNECTED);
        mIntentFilter.addAction(Intent.ACTION_POWER_DISCONNECTED);
        registerReceiver(mIntentReceiver, mIntentFilter);

        batteryManager = (BatteryManager)getSystemService(BATTERY_SERVICE);
        //new StatuThread().start();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
   
            // Android M Permission check
            if (this.checkSelfPermission(Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
   
                requestPermissions(new String[]{
   Manifest.permission.READ_PHONE_STATE}, 1);
            }
        }
    }
    public void unregister() {
   
        if (mIntentReceiver != null) {
   
            this.unregisterReceiver(mIntentReceiver);
        }
    }

    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
   
        @Override
        public void onReceive(Context context, Intent intent) {
   
            if (intent != null) {
   
                int status = 0;
                String action = intent.getAction();
                int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
                int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
                float batteryPct = level / (float)scale;
                Log.e(TAG, "level --> " + level);
                switch (action) {
   
                    case Intent.ACTION_BATTERY_CHANGED://电量发生改变
                        Log.e(TAG, "BatteryBroadcastReceiver --> onReceive--> ACTION_BATTERY_CHANGED");
                        //Toast.makeText(mContext,"电量变化",Toast.LENGTH_SHORT).show();
                        if(charging){
   
                            if(level < 95){
   
                                status = 11;
                            }else{
   
                                status = 12;
                            }
                        }else {
   
                            if(0 < level && level < 10){
   
                                status = 11;
                            }else{
   
                                status = 12;
                            }
                        }
                        Toast.makeText(mContext,"充电状态 " + charging + "值: " + level,Toast.LENGTH_SHORT).show();
                        break;
                    case Intent.ACTION_BATTERY_LOW://电量低
                        Log.e(TAG, "BatteryBroadcastReceiver --> onReceive--> ACTION_BATTERY_LOW");
                        status = 9;
                        break;
                    case Intent.ACTION_BATTERY_OKAY://电量充满
                        Log.e(TAG, "BatteryBroadcastReceiver --> onReceive--> ACTION_BATTERY_OKAY");
                        status = 12;
                        break;
                    case Intent.ACTION_POWER_CONNECTED://接通电源
                        Log.e(TAG, "BatteryBroadcastReceiver --> onReceive--> ACTION_POWER_CONNECTED");
                        if(level < 95){
   
                            status = 11;
                        }else{
   
                            status = 12;
                        }
                        charging = true;
                        Toast.makeText(mContext,"充电中,电量是" + level,Toast.LENGTH_SHORT).show();
                        break;
                    case Intent.ACTION_POWER_DISCONNECTED://拔出电源
                        Log.e(TAG, "BatteryBroadcastReceiver --> onReceive--> ACTION_POWER_DISCONNECTED");
                        charging = false;
                        if(0 < level && level < 10){
   
                            status = 11;
                        }else{
   
                            status = 12;
                        }
                       // Toast.makeText(mContext,"未充电" + level,Toast.LENGTH_SHORT).show();
                        break;
                }
                Message msg = new Message();
                msg.what = status;
                mHandler.sendMessage(msg);
            }
        }
    };
    @Override
    protected void onResume() {
   
        super.onResume();
        serialPortUtils.openSerialPort();
    }
    private void  readBinfile(){
   
        File file = new File(mSendFileName);
        FileInputStream fileInputStream = null;
        try {
   
            byte[] buf = new byte[1024*512];
            int length = -1;
            fileInputStream = new FileInputStream(file);
            while(((length = fileInputStream.read(buf)) != -1)&&(no_enough_space_flag!=true)){
   
                if(serialPortUtils.send(buf,length)==false){
   
                    no_enough_space_flag = true;
                    break;
                }
                // PrintfInfo(buf,length,"buf");
                Log.d("SendData", "send_index= " + send_index);
                Log.d("SendData", toHexString(buf,length));
                send_index++;
                serialPortUtils.flush();
            }
            fileInputStream.close();
           // serialPortUtils.sendFileEndFlag();
            send_index =0;
            Log.d("SendData", "发送完毕=====");
            Thread.sleep(3000);
        }catch (Exception e){
   
            e.printStackTrace();
        }

    }

    @Override
    public void OnResult(int ret) {
   

    }

    @Override
    public void Onread(CommonResponseData helper) {
   
        Log.d(TAG, "Onread  Opcode=====" + helper.getOpcode());
        Log.d(TAG, "Onread  ModuleId=====" + helper.getModuleId());
        Log.d(TAG, 
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

九霄的爸爸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值