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

前言

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

Android串口丢包重发的重传协议,一种流式重传协议_第1张图片
协议大概流程图如图所示。
既然是数据传输必然有传输格式协议,使用简单的TLV协议即可。
传输协议简介如下:

包结构

在这里插入图片描述
Header:包头
Payload:有效载荷,Module_id+Msg_id+Data
Checksum:校验和,校验数据为Header + Payload
Android串口丢包重发的重传协议,一种流式重传协议_第2张图片
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, "Onread  MsgId=====" + helper.getMsgId());
        if(helper.getData() != null){
            Log.d(TAG, "Onread=====" + toHexString(helper.getData(),helper.getData().length));
        }
    }

    class ReadFileThread implements Runnable{
        int num;
        public ReadFileThread(int num) {
            super();
            this.num = num;
        }
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+":"+num);
            try {
                readBinfile();
            }catch (Exception e){
                e.printStackTrace();
            }
            Message msg = new Message();
            msg.what = 8;
            Log.d("SendData", "ReadFileThread 发送= " + sendtime);
            mHandler.sendMessage(msg);
        }
    }
    private void  startSendData(){
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                String str = "1111111111";
                byte[] bytes = str.getBytes();
              // serialPortUtils.sendData(9,0,2,3);
                serialPortUtils.sendData(9,bytes.length,2,3,bytes);
            }
        };
        singleThreadExecutor.execute(runnable);
    }


    private void  startSendFile(){
        send_index =0;
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                File file = new File(mSendFileName1);
                FileInputStream fileInputStream = null;
                try {
                    byte[] buf = new byte[512];
                    int length = -1;
                    String line = null;
                    FileReader fr = new FileReader(mSendFileName1);
                    BufferedReader bufr = new BufferedReader(fr);
                    while((line = bufr.readLine()) != null){
                        Log.d(TAG, "line= " + line);
                        byte[] tt = line.getBytes();
                        Log.d(TAG, toHexString(tt,tt.length));
                        serialPortUtils.send(tt,tt.length);
                        // PrintfInfo(buf,length,"buf");
                        // Log.d("SendData", "send_index= " + send_index);
                        //Log.d("SendData", toHexString(buf,length));
                        //send_index++;
                    }
                    serialPortUtils.flush();
                    bufr.close();
                    fr.close();
                    Thread.sleep(1);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        };
        singleThreadExecutor.execute(runnable);
    }

    private void  startSendBinFile(){
        send_index =0;
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                File file = new File(mSendFileName);
                FileInputStream fileInputStream = null;
                try {
                    byte[] buf = new byte[1024*512];
                  //  byte[] buf = new byte[512];
                    int length = -1;
                    fileInputStream = new FileInputStream(file);
                    Log.d("SendData", "no_enough_space_flag + " + no_enough_space_flag);
                    while(((length = fileInputStream.read(buf)) != -1)&&(no_enough_space_flag!=true)){
                  //  if(((length = fileInputStream.read(buf)) != -1)&&(no_enough_space_flag!=true)){
                        if(serialPortUtils.send(buf,length)==false){
                            no_enough_space_flag = true;
                           // return;
                            break;
                        }
                         //PrintfInfo(buf,length,"bufggg");
                        Log.d("SendData", "send_index= " + send_index);
                     //   Log.d("SendData", toHexString(buf,length));
                        send_index++;
                        serialPortUtils.flush();
                    }
                    fileInputStream.close();
                    if(serialPortUtils.send_err_index == -1){
                        Log.d("SendData", "============================");
                        serialPortUtils.sendFileEndFlag();
                    }
                    send_index =0;
                    Log.d("SendData", "发送完毕=====");
                    Thread.sleep(500);
                    Message msg = new Message();
                    msg.what = 8;
                    if((no_enough_space_flag!=true)&&(serialPortUtils.send_err_index == -1)){
                        Log.d("SendData", "startSendBinFile send new frame=====");
                    //    mHandler.sendMessage(msg);
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }

            }
        };
        singleThreadExecutor.execute(runnable);
    }

    public  String toHexString(byte[] byteArray,int len) {
        final StringBuilder hexString = new StringBuilder("");
        if (byteArray == null || len <= 0)
            return null;
        for (int i = 0; i < len; i++) {
            int v = byteArray[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                hexString.append(0);
            }
            hexString.append(hv);
        }
        return hexString.toString().toLowerCase();
    }
    private class StatuThread extends Thread {
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        @Override
        public void run() {
            super.run();
            while (flag){
                int battery = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
                Log.d(TAG, "battery ============================ " + battery);
                if(!charging){
                    Log.d(TAG, "send battery: " + battery);
                    int status = 0;
                    if(battery > 10 ){
                        status = 12;
                    }else{
                        status = 11;
                    }

                    Message msg = new Message();
                    msg.what = status;
                    mHandler.sendMessage(msg);
                }
                try {
                    Thread.sleep(10000);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }
    private void ReSendErrorData(short index){
        final short i = index;
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    Log.d(TAG, "ReSendErrorData index===== " + i);
                    serialPortUtils.reSendPackect(i);
                    serialPortUtils.sendFileEndFlag();
                    Thread.sleep(500);
                    Message msg = new Message();
                    msg.what = 8;
                    Log.d("PPTV", "ReSendErrorData reSend_Err_Frame_Index===== " + serialPortUtils.send_err_index);
                    if(serialPortUtils.send_err_index == -1){
                        Log.d("SendData", "ReSendErrorData send new frame=====");
                       // mHandler.sendMessage(msg);
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        };
        singleThreadExecutor.execute(runnable);
    }
    public static long convertBytesToLong(byte[] b) {
        ByteBuffer buf = ByteBuffer.wrap(b);
        return buf.getLong();
    }

    private void stopsend(){
        no_enough_space_flag = true;
        serialPortUtils.clearstatus();
    }
    boolean check_md5(){
        File receive = new File(mFileName);
        if(!receive.exists()){
            Log.d(TAG,"file not exist");
            return true;
        }
        File send = new File("/sdcard/send.bin");
        if(!getFileMD5(receive).equals(getFileMD5(send))){
            Log.d("check_md5","false");
            //start_play_thread(this);
            return true;
        }
        Log.d("check_md5","success");
        return true;
    }

    public static String getFileMD5(File file) {
        if (!file.isFile()) {
            return null;
        }
        MessageDigest digest = null;
        FileInputStream in = null;
        byte buffer[] = new byte[1024];
        int len;
        try {
            digest = MessageDigest.getInstance("MD5");
            in = new FileInputStream(file);
            while ((len = in.read(buffer, 0, 1024)) != -1) {
                digest.update(buffer, 0, len);
            }
            in.close();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        BigInteger bigInt = new BigInteger(1, digest.digest());
        return bigInt.toString(16);
    }
    public void led_off(){

        JSONObject send_json = new JSONObject();
        try {
            send_json.put("Power_Led_Red","off");
            send_json.put("Power_Led_Green","off");
            send_json.put("Translate_Led_Green","off");
            send_json.put("Bt_Led_Blue","off");
            byte[] data = send_json.toString().getBytes();
            serialPortUtils.sendData(1,data.length,17,1,data);
            // byte[] send = mDtaHelper.getSendData(1,data.length,17,1,data);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    public void led_red_on(){
        JSONObject send_json = new JSONObject();
        try {
            send_json.put("Power_Led_Green","off");
            send_json.put("Power_Led_Red","on");
            byte[] data = send_json.toString().getBytes();
            serialPortUtils.sendData(1,data.length,17,1,data);
        }catch (JSONException e) {
            e.printStackTrace();
        }
    }
    public void led_red_off(){
        JSONObject send_json = new JSONObject();
        try {
            send_json.put("Power_Led_Red","off");
            byte[] data = send_json.toString().getBytes();
            serialPortUtils.sendData(1,data.length,17,1,data);
        }catch (JSONException e) {
            e.printStackTrace();
        }
    }
    public void led_green_on(){
        JSONObject send_json = new JSONObject();
        try {
            send_json.put("Power_Led_Red","off");
            send_json.put("Power_Led_Green","on");
            byte[] data = send_json.toString().getBytes();
            serialPortUtils.sendData(1,data.length,17,1,data);
        }catch (JSONException e) {
            e.printStackTrace();
        }
    }
    public void led_green_off(){
        JSONObject send_json = new JSONObject();
        try {
            send_json.put("Power_Led_Green","off");
            byte[] data = send_json.toString().getBytes();
            serialPortUtils.sendData(1,data.length,17,1,data);
        }catch (JSONException e) {
            e.printStackTrace();
        }
    }
    public void led_on(){

        JSONObject send_json = new JSONObject();
        try {
            send_json.put("Power_Led_Red","on");
            send_json.put("Power_Led_Green","on");
            send_json.put("Translate_Led_Green","on");
            send_json.put("Bt_Led_Blue","on");
            byte[] data = send_json.toString().getBytes();
            //  byte[] send = mDtaHelper.getSendData(1,data.length,17,1,data);
            serialPortUtils.sendData(1,data.length,17,1,data);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.open_bt:
                no_enough_space_flag = false;
              //  startSendBinFile();
              //  startSendData();
                //startSendFile();
             //   led_on();
                led_red_on();

                break;
            case R.id.send_bt:
               // stopsend();
                led_green_on();
                break;
            case R.id.close:
              //  stopsend();
            //    delete("/sdcard",false,".bin");
                led_off();
                break;
            default:
        }
    }
    public  void delete(String dirPath, boolean isPrefix, String regEx) {
        singleThreadExecutor.execute(new DeleteRunnable(dirPath, isPrefix, regEx));
    }
    public void PrintfInfo(byte[] data,int len,String str) {
        System.out.format(  str + " data is ====\n");
        for (int i = 0; i < len; i++) {
            System.out.format("%02x", data[i]);
        }
        System.out.format("\n");
        System.out.format(str +" end ============\n");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        serialPortUtils.closeSerialPort();
    }
}

send 函数实现如下,主要功能就是保存数据到mOutputBuffer 缓存

 public synchronized boolean send(byte [] data,int len){
        streamMode = true;
        if(mOutputBuffer.remaining() > len){
            mOutputBuffer.put(data,0,len);
            Log.e(TAG,"position after send msg : " + mOutputBuffer.position());
            return true;
        }else{
            Log.e(TAG," not enough space,stop");
            return  false;
        }
    }

而mOutputBuffer 实现是在串口初始化中



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;
             
            }
        }
    };
    @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();
   
        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);
            }
        }
    }
    @Override
    protected void onResume() {
        super.onResume();
        serialPortUtils.openSerialPort();
    }
  
    @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, "Onread  MsgId=====" + helper.getMsgId());
        if(helper.getData() != null){
            Log.d(TAG, "Onread=====" + toHexString(helper.getData(),helper.getData().length));
        }
    }

    private void  startSendBinFile(){
        send_index =0;
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                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,"bufggg");
                        Log.d("SendData", "send_index= " + send_index);
                     //   Log.d("SendData", toHexString(buf,length));
                        send_index++;
                        serialPortUtils.flush();
                    }
                    fileInputStream.close();
                    if(serialPortUtils.send_err_index == -1){
                        Log.d("SendData", "============================");
                        serialPortUtils.sendFileEndFlag();
                    }
                    send_index =0;
                    Log.d("SendData", "发送完毕=====");
                    Thread.sleep(500);
                    Message msg = new Message();
                    msg.what = 8;
                    if((no_enough_space_flag!=true)&&(serialPortUtils.send_err_index == -1)){
                        Log.d("SendData", "startSendBinFile send new frame=====");
                    //    mHandler.sendMessage(msg);
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }

            }
        };
        singleThreadExecutor.execute(runnable);
    }

   protected void onResume() {
        super.onResume();
        serialPortUtils.openSerialPort();
    }

    private void ReSendErrorData(short index){
        final short i = index;
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    Log.d(TAG, "ReSendErrorData index===== " + i);
                    serialPortUtils.reSendPackect(i);
                    serialPortUtils.sendFileEndFlag();
                    Thread.sleep(500);
                    Message msg = new Message();
                    msg.what = 8;
                    Log.d("PPTV", "ReSendErrorData reSend_Err_Frame_Index===== " + serialPortUtils.send_err_index);
                    if(serialPortUtils.send_err_index == -1){
                        Log.d("SendData", "ReSendErrorData send new frame=====");
                       // mHandler.sendMessage(msg);
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        };
        singleThreadExecutor.execute(runnable);
    }
  
    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.open_bt:
                no_enough_space_flag = false;
                startSendBinFile();      
                break;
            default:
        }
    }
   
}
    

串口初始化

serialPortUtils.openSerialPort();

    public SerialPort openSerialPort() {
        try {
            serialPort = new SerialPort(new File(path), baudrate, 0, 1000);
            threadStatus = false; //线程状态
            mOutputBuffer = ByteBuffer.allocateDirect(DEF_OUT_BUF_SIZE);//发送缓存
            mInputBuffer = ByteBuffer.allocateDirect(DEF_INPUT_BUF_SIZE);//接收缓存
            inputStream = serialPort.getInputStream();//串口输入流
            outputStream = serialPort.getOutputStream();//串口输出流
            mInputStream = new BufferedInputStream(inputStream,1024*1024*4);//串口输入缓存
            mOutputStream = new BufferedOutputStream(outputStream,1024*1024*4);//串口输出缓存
            mReadThread = new ReadThread();
            mReadThread.start();//接收线程,用于接收MCU发送信息
        } catch (IOException e) {
            Log.e(TAG, "openSerialPort: 打开串口异常:" + e.toString());
            return serialPort;
        }
        Log.d(TAG, "openSerialPort: 打开串口");
        return serialPort;
    }

这里已已发送文件测试如何处理发送数据。这里测试发送1000次。都发送成功,说协议很稳定

    private void  startSendBinFile(){//发送测试文件
        send_index =0;
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                File file = new File(mSendFileName);
                FileInputStream fileInputStream = null;
                try {
                    byte[] buf = new byte[1024*512];//每次读取512K
               
                    int length = -1;
                    fileInputStream = new FileInputStream(file);
                    Log.d("SendData", "no_enough_space_flag + " + no_enough_space_flag);
                    while(((length = fileInputStream.read(buf)) != -1)&&(no_enough_space_flag!=true))
                        if(serialPortUtils.send(buf,length)==false){//读取到mOutputBuffer 缓存中
                            no_enough_space_flag = true;
                    
                            break;
                        }
               
                        send_index++;
                        serialPortUtils.flush(); //发送文件
                    }
                    fileInputStream.close();
                    if(serialPortUtils.send_err_index == -1){
                        Log.d("SendData", "============================");
                        serialPortUtils.sendFileEndFlag();
                    }
                    send_index =0;
                    Log.d("SendData", "发送完毕=====");
                    Thread.sleep(500);
                    Message msg = new Message();
                    msg.what = 8;
                    if((no_enough_space_flag!=true)&&(serialPortUtils.send_err_index == -1)){
                        Log.d("SendData", "startSendBinFile send new frame=====");
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }

            }
        };
        singleThreadExecutor.execute(runnable);
    }

申请一个单独的线程池用于发送文件,单独线程池好处是可以防止多个线程写串口,防止串口数据串码

    public synchronized boolean send(byte [] data,int len){
        streamMode = true;
        if(mOutputBuffer.remaining() > len){
            mOutputBuffer.put(data,0,len); //数据放到缓存中
            Log.e(TAG,"position after send msg : " + mOutputBuffer.position());
            return true;
        }else{
            Log.e(TAG," not enough space,stop");
            return  false;
        }
    }
 public  void flush(){
        Log.d(TAG, "flush: data position: " + mOutputBuffer.position());
        mOutputBuffer.flip();
        send_buff_index = 0;
        OutputDataLen = mOutputBuffer.limit();
        Log.d(TAG, "flip: position: " + mOutputBuffer.position());
        Log.d(TAG, "flip: limit: " + mOutputBuffer.limit());
        int total =  mOutputBuffer.limit()/FRAME_SIZE;
        if(mOutputBuffer.limit()%FRAME_SIZE > 0){
            total++;
        }
        Log.d(TAG, "total: " + total);
        while(mOutputBuffer.hasRemaining()){
            if(send_err_index > -1){//这个数据值代表错误帧index,一旦有错误帧就会被改变。再接收线程中被改变
                break;
            }
            int left = mOutputBuffer.remaining();
            int datlen = FRAME_SIZE;
            if(left < FRAME_SIZE ){
                datlen = left;
            }
            byte[] bytes = new byte[datlen];
            mOutputBuffer.get(bytes);
            Log.e(TAG, "flush send_buff_index=" + send_buff_index);

            sendSerialData(bytes,bytes.length,send_buff_index++);
            try {
             //   Thread.sleep(1000);
            }catch (Exception e){
                e.printStackTrace();
            }
       }
    }

组包数据

 private synchronized void sendSerialData(byte[] bytes,int length,short index){
        //byte[] send  = mDtaHelper.PackectData(7,length,1,index,bytes);
        byte[] send  = mDtaHelper.PackectData(streamOpcode,index,length,streamMouduleId,streamMsgId,bytes);
        sendSerialPort(send);
    }
 public byte[] PackectData(int opcode, short index ,int length, int module_id, int msg_id, byte[] payload) {
        byte[] data = new byte[length + HEAD_LENGTH + CustomServiceDataHelper.PAYLOAD_MINIMUM_SIZE];
        setStartFlag(0xFFAA5500);        // start flag:4
        setIndex((short) index);
        setHandle((short)0);                    // handle:2, now fill 0
        setVersion((byte)1);             // version:1
        setOpcode((byte)opcode);         // opcode:1
        setLength(length+CustomServiceDataHelper.PAYLOAD_MINIMUM_SIZE);               // length:4, length=header+payload
        setModuleId((byte)module_id);    // module id:1
        setMsgId((byte)msg_id);          // message id:1
        setData(payload, length);
        DataSerialize(data);             // payload(include checksum)
        return data;
    }

按照之前通信协议进行组包。然后通过串口发送
下面就是Android串口接收MCU返回信息,如返回错误。就冲洗您发送数据

 /**
     * 单开一线程,来读数据
     */
    private class ReadThread extends Thread {
        @Override
        public void run() {
            super.run();
            mInputBuffer.clear();
            int len;
            int sleepTime = 0;
            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_MORE_FAVORABLE);
            //判断进程是否在运行,更安全的结束进程
            while (!threadStatus){
                try {
                    if ((mInputStream.available() > 20)||((  mInputStream.available() > 0)&&(sleepTime >3))) {//超过20字节读取数据
                        sleepTime = 0;
                        byte[] mRecvBuffer = new byte[10240];
                    
                        len = mInputStream.read(mRecvBuffer);
                       
                        receoveProcessZmodem(mRecvBuffer,len);//解析数据
                    }else if(sleepTime >200000){//超时清空接收buffer
                        Log.d(TAG,"clear buff");
                        mUsedlen = 0;
                        sleepTime =0;
                        mSendCallback.OnResult(clear_recv_buff);
                    }else {
                      //  Log.d(TAG,"没有数据休息一下");
                        sleepTime++;
                        Thread.sleep(1);
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
            }

        }
    }

private void receoveProcessZmodem(byte[] buffer, int size){
int ret = dataDeserialize1(buffer, size,mReceiveDtaHelper);
}

public int dataDeserialize1(byte[] array, int readlen,CommonResponseData helper) {
        Log.d(TAG, "dataDeserialize1 | pre mUsedlen: " + mUsedlen);
        Log.d(TAG, "recv array  | readlen: " + readlen );
        int errcode = receiveComplete;
        boolean flag = true;
        try {
            System.arraycopy(array, 0, mParseDate, mUsedlen, readlen);//接收到数据拷贝到处理buff mParseDate中
        } catch (Exception ex) {
            mParseDate = new byte[mParseDate.length];
            mUsedlen = 0;
            flag = false;
            errcode = noComplete;
        }
        mUsedlen += readlen;
        if (mUsedlen - HEAD_LENGTH < 0) {
            /*数据不足*/
            Log.d(TAG, "return mUsedlen1 " + mUsedlen);
            return noComplete;
        }
        ByteArrayInStream in = new ByteArrayInStream(mParseDate);
        Log.d(TAG, "mParseDate:" + toHexString(mParseDate,mUsedlen));
        while (flag) {
            switch (gemCommFSM) {
                case E_FIND_HEADER:
                    Log.d(TAG, "------goto  E_FIND_HEADER====");
                    int index = findHeader(mParseDate, mUsedlen);
                    if (index == -1) {//没有找到帧头,下次再找
                        flag = false;
                        errcode = noComplete;
                        gemCommFSM = State.E_FIND_HEADER;
                    }else{
                        if (index > 0) {
                            System.arraycopy(mParseDate, index, mParseDate, 0, mUsedlen - index);
                             mUsedlen -= index;
                        }
                        try {
                            mStartFlag = in.readInt();
                            helper.setStartFlag(mStartFlag);
                            mIndex = in.readShort();
                            helper.setIndex(mIndex);
                            mHandle = in.readShort();
                            helper.setHandle(mHandle);
                            mVersion = in.readByte();
                            helper.setVersion(mVersion);
                            mOpcode = in.readByte();
                            helper.setOpcode(mOpcode);
                            mLength = in.readInt();
                            helper.setLength(mLength);
                        } catch (IOException e) {
                            Log.d(TAG, e.getMessage().toString());
                        }
                        gemCommFSM = State.E_CHECK_LENGTH;
                    }
                    break;
                case E_CHECK_LENGTH:
                    Log.d(TAG, "------goto  E_CHECK_LENGTH====");
                    if (mLength > mParseDate.length) {
                        //数据丢包了,丢弃该包
                        mParseDate = new byte[mParseDate.length];
                        Log.e(TAG, "return give up: " + mUsedlen + ", mLength = " + mLength);
                        mUsedlen = 0;
                        flag = false;
                        errcode = lengthError;
                        gemCommFSM = State.E_FIND_HEADER;
                    }else if (mUsedlen - HEAD_LENGTH < mLength) {
                        /*数据不足*/
                        Log.d(TAG, "return mUsedlen2 " + mUsedlen + ", mLength = " + mLength);
                        flag = false;
                        errcode = noComplete;
                        gemCommFSM = State.E_FIND_HEADER;
                    }else {
                        try {
                            mModuleId = in.readByte();
                            helper.setModuleId(mModuleId);
                            mMsgId = in.readByte();
                            helper.setMsgId(mMsgId);
                            if (mLength - PAYLOAD_MINIMUM_SIZE > 0) {
                                mData = new byte[mLength - PAYLOAD_MINIMUM_SIZE];
                                mData = in.readBytes(mLength - PAYLOAD_MINIMUM_SIZE);
                                helper.setData(mData);//数据拷贝到helper中,做为回调数据结构用
                                mDataLen = mData.length;
                            }
                            mChecksum = in.readByte();
                            helper.setChecksum(mChecksum);
                        }catch (Exception e){
                            Log.d(TAG, e.getMessage().toString());
                        }
                        gemCommFSM = State.E_CHECK_CRC;
                    }
                    break;
                case E_CHECK_CRC:
                    recv_buff_index=helper.getIndex();
                    Log.d(TAG, "------goto  E_CHECK_CRC====");
                    if((recv_err_index > -1)&&(recv_buff_index !=recv_err_index)){
                        Log.d(TAG, "------stop recv data  " + recv_buff_index);
                        Log.d(TAG, "------stop recv data is " + toHexString(helper.getData(),helper.getData().length));
                        System.arraycopy(mParseDate, in.mIndex, mParseDate, 0, mUsedlen - in.mIndex);
                        mUsedlen -= in.mIndex;
                        in.mIndex =0;
                        gemCommFSM = State.E_FIND_HEADER;
                    }else{
                        int resultCheckSum = crc8_atm(mParseDate, in.mIndex - 1);
                        if (mChecksum != resultCheckSum) {
                            Log.d(TAG, "data occur mutation!");
                            Log.d(TAG, "show crc error data: " + toHexString(mData,mData.length));
                            flag = false;
                            System.arraycopy(mParseDate, in.mIndex, mParseDate, 0, mUsedlen - in.mIndex);
                            mUsedlen -= in.mIndex;
                            in.mIndex =0;
                            errcode = crcError;
                            gemCommFSM = State.E_FIND_HEADER;
                        }else{
                            gemCommFSM = State.E_RCV_COMPLETE;
                        }
                    }
                    break;
                case E_RCV_COMPLETE:       
                    System.arraycopy(mParseDate, in.mIndex, mParseDate, 0, mUsedlen - in.mIndex);
                    mUsedlen -= in.mIndex;
                    in.mIndex =0;
                    Log.d(TAG, "return mUsedlen3 " + mUsedlen);
                    if (mLength - PAYLOAD_MINIMUM_SIZE > 0) {
                    }
                    pareRecvSerialData(helper);
                    gemCommFSM = State.E_FIND_HEADER;
                    break;
                default:
                    Log.d(TAG, "------goto  default====");
                    flag = false;
                    mUsedlen =0;
                    errcode = UnkonwnErr;
                    gemCommFSM = State.E_FIND_HEADER;
            }
        }
        return errcode;
    }

pareRecvSerialData处理MCU发送回来的错误原因以及错误帧id

    private void pareRecvSerialData(CommonResponseData dataHelper){
        Log.d(TAG,"pareRecvSerialData frame index ===" + dataHelper.getIndex());
        Log.d(TAG,"pareRecvSerialData  dataHelper.getOpcode()===" + dataHelper.getOpcode());
        if(dataHelper.getOpcode() == 8){ //回复帧类型
            int moduleid = dataHelper.getModuleId();
            if(moduleid == 1){
                int total = dataHelper.getMsgId();
                Log.e(TAG,"pareRecvSerialData data length: " + total);
            }else if(moduleid == 2){
                short errIndex = dataHelper.getIndex();//错误帧index
                if((errIndex == send_err_index)&&(sendRepeatTime ==2)){//超过两次就禁止重发
                    Log.e(TAG,"pareRecvSerialData  send over 2 time ,stop send: " + errIndex);
                    send_err_index = -1;
                    send_stop=true;
                    sendRepeatTime = 0;
                    mOutputBuffer.clear();
                }else {//通知有数据错误
                    sendRepeatTime++;
                    send_err_index = dataHelper.getIndex();//此标志会停止flush函数发送数据。只有不等于-1 就停止发送
                    sendHandlerMsg(7,send_err_index);
                }
            }
        }
    }

mainactivity 接收到有数据错误后

     case 7:
                    //重新发送错误数据包
                    short index = (short) msg.arg1;
                    ReSendErrorData(index);
                    Log.d(TAG, "mHandler  resend  data index= " + index);
                    break;
 private void ReSendErrorData(short index){
        final short i = index;
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    Log.d(TAG, "ReSendErrorData index===== " + i);
                    serialPortUtils.reSendPackect(i);
                    Thread.sleep(500);
                    Message msg = new Message();
                    msg.what = 8;
               
                    if(serialPortUtils.send_err_index == -1){
                       mHandler.sendMessage(msg);
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        };
        singleThreadExecutor.execute(runnable);
    }

重发数据会从错误帧开始重新发送数据。

public synchronized void  reSendPackect( short index){
        Log.e(TAG,"reSendPackect start==========--------------------------- " + index);
        send_buff_index = index;
        int position = send_buff_index*FRAME_SIZE;   
        mOutputBuffer.position(position);
        mOutputBuffer.limit(OutputDataLen);
        while (mOutputBuffer.hasRemaining()){
            if(send_stop){
                break;
            }
            int left = mOutputBuffer.remaining();
            Log.e(TAG,"reSendPackect remaining" + left);
            int datlen = FRAME_SIZE;
            if(left < FRAME_SIZE ){
                datlen = left;
            }
            byte[] bytes = new byte[datlen];
            mOutputBuffer.get(bytes);
               sendSerialData(bytes,bytes.length,send_buff_index++);
        }
        send_stop = false;
        mOutputBuffer.clear();
    }

完成数据重发。。。有时间把整个app 工程贡献出来,有需要的可以私信找我要

你可能感兴趣的:(android)