QT 绿米QTA协议 (二)AS
几圈年轮 Android 串口通信(一) giuhub demo 地址 https://github.com/MickJson/AndroidUSBSerialPort
一.PowerUpdate
1.1.UI
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="7" android:gravity="center" android:orientation="horizontal"> <ScrollView android:id="@+id/sv_send" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="5" android:background="@drawable/bg_white_stroke"> <TextView android:id="@+id/tv_send" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/receive_hint" android:padding="@dimen/margin_small" android:textColor="@android:color/black" android:textSize="@dimen/text_tv" /> </ScrollView> <ScrollView android:id="@+id/sv_result" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="5" android:background="@drawable/bg_white_stroke"> <TextView android:id="@+id/tv_result" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/receive_hint" android:padding="@dimen/margin_small" android:textColor="@android:color/black" android:textSize="@dimen/text_tv" /> </ScrollView> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="0.5" android:gravity="center" android:orientation="horizontal"> <Button android:id="@+id/btn_start" style="@style/average_vertical_widget" android:text="@string/start_test" /> <Button android:id="@+id/btn_close" style="@style/average_vertical_widget" android:text="@string/close_port" /> </LinearLayout> </LinearLayout>
styles.xml
<resources> <!--font Style--> <style name="average_vertical_widget"> <item name="android:layout_width">0dp</item> <item name="android:layout_height">match_parent</item> <item name="android:layout_weight">1</item> <item name="android:gravity">center</item> <item name="android:textSize">@dimen/text_size</item> <item name="android:textColor">@android:color/black</item> </style> </resources>
1.2.android_serialport_api 串口工具类
package android_serialport_api; import android.util.Log; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; public class SerialPort { private static final String TAG = "SerialPort"; /* * Do not remove or rename the field mFd: it is used by native method close(); */ private FileDescriptor mFd; private FileInputStream mFileInputStream; private FileOutputStream mFileOutputStream; public SerialPort(File device, int baudrate, int dataBits, int stopBits, char parity) throws SecurityException, IOException { /* Check access permission */ if (!device.canRead() || !device.canWrite()) { try { /* Missing read/write permission, trying to chmod the file */ Process su; su = Runtime.getRuntime().exec("/system/bin/su"); String cmd = "chmod 666 " + device.getAbsolutePath() + "\n" + "exit\n"; su.getOutputStream().write(cmd.getBytes()); if ((su.waitFor() != 0) || !device.canRead() || !device.canWrite()) { throw new SecurityException(); } } catch (Exception e) { e.printStackTrace(); throw new SecurityException(); } } mFd = open(device.getAbsolutePath(), baudrate, dataBits, stopBits, parity); if (mFd == null) { Log.e(TAG, "native open returns null"); throw new IOException(); } mFileInputStream = new FileInputStream(mFd); mFileOutputStream = new FileOutputStream(mFd); } // Getters and setters public InputStream getInputStream() { return mFileInputStream; } public OutputStream getOutputStream() { return mFileOutputStream; } public void Write_Rs485_Length(int count) { write_rs485_length(count); } // JNI(调用java本地接口,实现串口的打开和关闭) /**串口有五个重要的参数:串口设备名,波特率,检验位,数据位,停止位 其中检验位一般默认位NONE,数据位一般默认为8,停止位默认为1*/ /** * @param path 串口设备的据对路径 * @param baudrate 波特率 * @param dataBits 数据位 * @param stopBits 停止位 * @param parity 校验位 */ private native static FileDescriptor open(String path, int baudrate, int dataBits, int stopBits, char parity); public native void close(); public native void write_rs485_length(int count); static {//加载jni下的C文件库 Log.i(TAG, "loadLibrary..............gatsby"); System.loadLibrary("serial_port"); } }
二.qtcreator -> AS
2.1.UartTest
package com.lvmi.powerupdate; import android.content.Context; import android.os.Handler; import android.os.Looper; import android.util.Log; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import android_serialport_api.SerialPort; public class UartTest { private final Context mContext; byte[] Save_Data; // 保存的载入数据 byte[] Temp_file_Data; // 临时文件数据 byte[] file_arry; // 文件数据 byte[] Send_Data; // 发送数据 byte Data_Number; // 数据编码 int flag_EOT; // 结束命令标记 int file_ChkData; // 文件校验 int NAck_Cnt; // NAck次数 int Ack_Cnt; // Ack次数 private OnSerialListener mOnSerialListener; public String DEV_TTYS4 = "/dev/ttyS4"; private boolean isOpened = false; private OutputStream mOutputStream; private InputStream mInputStream; private final ExecutorService mThreadPoolExecutor = Executors.newCachedThreadPool(); public UartTest(Context mContext) { super(); this.mContext = mContext; } public void StartUartTest() { openSerialPort(DEV_TTYS4); Btn_Open_File(); Btn_Sand_Data();//启动 } //打开文件 public void Btn_Open_File() { int val; file_arry = getBytesByFile(mContext, "QT573-LVMI-APP.bin"); val = file_arry.length % 128; if (val != 0) { val = 128 - val; //最后一包数据补24个0 byte[] chrVal2 = new byte[val]; for (int i = 0; i < val; i++) { chrVal2[i] = (byte)0; } file_arry = addBytes(file_arry, chrVal2); file_ChkData = Dev_CRC16(file_arry); //must F229 Log.d("gatsby", "file_ChkData -> " + Integer.toHexString(file_ChkData)); } } //启动 public void Btn_Sand_Data() { // 清除 计数 接收文本 保存数据 NAck_Cnt = 0; Ack_Cnt = 0; Save_Data = null; // 开始接收数据 isOpened = true; // 开始发送进入编译环境 Data_Number = 0; // 重新发送计数 flag_EOT = 0; // 发送结束标记位重置 Sand_Data((byte) 0x93); // 发送 } //发送数据 public void Sand_Data(byte cmd) { //StringBuilder ba = new StringBuilder(); byte[] ba = new byte[133]; int ChkData;//0xffff byte val;//0xff String sendData = null; if (cmd == 0x01) { byte[] data; ba[0] = cmd; Log.d("gatsby", "Data_Number ->" + Data_Number); val = (byte) ~Data_Number; ba[1] = (byte) Data_Number; ba[2] = (byte) val; for (int i = 0; i < Send_Data.length; i++) { ba[i + 3] = (byte) Send_Data[i]; } //System.arraycopy(Send_Data, 0, data, 0, Send_Data.length); data = Send_Data; Log.d("gatsby", "data->" + data.length); // 数据校验 ChkData = CRC16(data, 128); Log.d("gatsby", "ChkData->" + ChkData); val = (byte) (ChkData & 0xff); ba[Send_Data.length + 3] = (byte) val; val = (byte) ((ChkData >> 8) & 0xff); ba[Send_Data.length + 4] = (byte) val; //打印看发送数据是否正确 // for (int i = 0; i < ba.length; i++) { // Log.d("gatsby", "i--> " + (i) + " ba ->"+ Integer.toHexString(ba[i] & 0xff)); // } sendSerialPort(ba); if (flag_EOT == 0) { Log.d("gatsby", "Sand Packet " + Data_Number); sendData = "Send Packet " + Data_Number; } // 发送校验 else if (flag_EOT == 1) { Log.d("gatsby", "Sand Check"); sendData = "Send Check"; } if (mOnSerialListener != null) { mOnSerialListener.onSendData(sendData); } } else { byte[] ba2 = new byte[1]; ba2[0] = (byte) cmd; sendSerialPort(ba2); Log.d("gatsby", "cmd ->" + Integer.toHexString(cmd & 0xff)); if (cmd == (byte) 0x93) { Log.d("gatsby", "Sand_Data will be send data "); } else if (cmd == (byte) 0x04) { Log.d("gatsby", "Sand_Data End "); isOpened = false; } } } public void openSerialPort(String devType) { if (!"".equals(devType) && new File(devType).exists()) { Log.d("gatsby", "isOpenedSerialPort"); try { //串口配置:9600 数据位8 起始位1 停止位1 奇校验 SerialPort mSerialPort = new SerialPort(new File(DEV_TTYS4), 9600, 8, 1, 'O'); mOutputStream = mSerialPort.getOutputStream(); mInputStream = mSerialPort.getInputStream(); mThreadPoolExecutor.execute(new ReceiveDataThread()); isOpened = true; } catch (IOException e) { e.printStackTrace(); if (mOnSerialListener != null) { mOnSerialListener.onSerialOpenException(e); } } } } /** * 串口 发送字符串 */ public void Sand_Data_QString(String st) { st += "\r\n"; byte[] ba = st.getBytes(); sendSerialPort(ba); } /** * 串口 发送byte[] */ public void sendSerialPort(byte[] sendbyte) { if (!isOpened) { return; } try { mOutputStream.write(sendbyte); mOutputStream.flush(); } catch (IOException e) { e.printStackTrace(); } } //5s 未接收数据 提示升级失败 Android11 提示过时 换Looper写法 Handler handler = new Handler(Looper.myLooper()); private final Runnable runnable = new Runnable() { @Override public void run() { if (Data_Number == 1) { if (mOnSerialListener != null) { mOnSerialListener.onUpdateError(); } } } }; /** * 串口返回数据内容读取 */ private class ReceiveDataThread extends Thread { @Override public void run() { super.run(); int val;//uint8_t String SendStr = null; String ReciveStr = null; boolean start_flag = false; byte[] ba = new byte[1024]; while (isOpened) { String resSaveData; if (mInputStream != null) { try { int size = mInputStream.read(ba); if (size > 0) { resSaveData = new String(ba, 0, size); Log.d("gatsby", "receiver str = " + resSaveData); Save_Data = resSaveData.getBytes(); if (resSaveData.equals("Apply OTA\r\n")) { Log.d("gatsby", "Apply OTA"); SendStr = "OTA"; start_flag = true; ReciveStr = "Apply OTA"; Sand_Data_QString(SendStr); } else if (resSaveData.equals("Ready\r\n")) { Log.d("gatsby", "Ready"); SendStr = "Start"; ReciveStr = "Ready"; Sand_Data_QString(SendStr); NAck_Cnt = 0; Ack_Cnt = 0; Data_Number = 0; // 重新发送计数 flag_EOT = 0; // 发送结束标记位重置 Temp_file_Data = file_arry; } // NACK else if (Save_Data[0] == 0x15) { NAck_Cnt++; if (Data_Number == 0) { Data_Number = 1; // 首次发送 Log.d("gatsby", "(NAck) Ready Receive"); ReciveStr = "(NAck) Ready Receive"; Send_Data = cutBytes(Temp_file_Data); Sand_Data((byte) 0x01); } else if (Data_Number > 0) { Log.d("gatsby", "(NAck) Ready Packet Error"); ReciveStr = "(NAck) Ready Receive"; Send_Data = cutBytes(Temp_file_Data); Sand_Data((byte) 0x01); } handler.postDelayed(runnable, 6000); } // ACK else if (Save_Data[0] == 0x06) { Ack_Cnt++; Data_Number++; //Log.d("gatsby", "(Ack) Receive ->" + Save_Data[0]); //Log.d("gatsby", "flag_EOT->" + flag_EOT); Log.d("gatsby", "Temp_file_Data aaa->" + Temp_file_Data.length); if (Temp_file_Data.length > 128) { Temp_file_Data = removeBytes(Temp_file_Data, Temp_file_Data.length - 128); Log.d("gatsby", "Temp_file_Data bbb->" + Temp_file_Data.length); // 发送数据 Send_Data = cutBytes(Temp_file_Data); if (Temp_file_Data.length == 128) { Temp_file_Data = removeBytes(Temp_file_Data, 0); } Sand_Data((byte) 0x01); Log.d("gatsby", "(Ack) Receive Packet " + (Data_Number - 1) + " Success"); ReciveStr = "(Ack) Receive Packet " + (Data_Number - 1) + " Success"; } else { // 发送校验 if (flag_EOT == 0) { flag_EOT = 1; val = (byte) (file_ChkData & 0xff); Send_Data[0] = (byte) val; val = (byte) ((file_ChkData >> 8) & 0xff); Send_Data[1] = (byte) val; for (int i = 0; i < 126; i++) { Send_Data[i + 2] = (byte) 0; } Sand_Data((byte) 0x01); Log.d("gatsby", "EOT (Ack) Receive Packet " + (Data_Number - 1) + " Success"); ReciveStr = "(Ack) Receive Packet " + (Data_Number - 1) + " Success"; } // 发送EOT 完成 else if (flag_EOT == 1) { flag_EOT = 2; Sand_Data((byte) 0x04); Log.d("gatsby", "(Ack) Receive Check"); ReciveStr = "(Ack) Receive Check"; } } } //设置监听 if (mOnSerialListener != null) { mOnSerialListener.onReceivedData(ReciveStr); if (start_flag) { mOnSerialListener.onSendData(SendStr); if (SendStr.equals("Start")) { start_flag = false; } } } } } catch (Exception e) { e.printStackTrace(); } } } } } /** * 关闭串口 */ public void closeSerialPort() { try { if (mOutputStream != null) mOutputStream.close(); if (mInputStream != null) mInputStream.close(); isOpened = false; } catch (IOException e) { e.printStackTrace(); } } /** * 设置串口监听 * * @param onSerialListener 串口监听 */ public void setOnSerialListener(OnSerialListener onSerialListener) { this.mOnSerialListener = onSerialListener; } /** * 串口监听 */ public interface OnSerialListener { /** * 串口打开异常 */ void onSerialOpenException(Exception e); /** * 串口数据发送 */ void onSendData(String sendData); /** * 串口数据返回 */ void onReceivedData(String receivedData); /** * 上位机下发后若5S未接收MCU数据 上位机退出升级过程 */ void onUpdateError(); } /** * CRC校验 128字节 */ public int CRC16(byte[] u8pArray, int u8Size) { int val;//0xff int i, j; int CurVal;//0xffff int CrcReg = 0xFFFF; for (i = 0; i < u8Size; i++) { //CurVal = u8pArray[i] << 8; uint8_t val = u8pArray[i] & 0xff; CurVal = val << 8; for (j = 0; j < 8; j++) { if ((short) (CrcReg ^ CurVal) < 0) { CrcReg = (((CrcReg << 1) & 0xffff) ^ 0x1021); } else { CrcReg = (CrcReg << 1) & 0xffff; } CurVal <<= 1; } } return CrcReg; } /** * CRC校验 */ public int Dev_CRC16(byte[] ba) { int val;//0xff int i, j; int CurVal;//0xffff int CrcReg = 0xFFFF; for (i = 0; i < ba.length; i++) { //uint8_t -> int val = ba[i] & 0xff; CurVal = val << 8; for (j = 0; j < 8; j++) { if ((short) (CrcReg ^ CurVal) < 0) { CrcReg = (((CrcReg << 1) & 0xffff) ^ 0x1021); //Log.d("gatsby","file_arry i-> " + i + " CrcReg -> " + CrcReg); } else { CrcReg = (CrcReg << 1) & 0xffff; } CurVal <<= 1; } } return CrcReg; } /** * 两个字节数组合并 */ public byte[] addBytes(byte[] firstData, byte[] secondData) { byte[] mergeData = new byte[firstData.length + secondData.length]; System.arraycopy(firstData, 0, mergeData, 0, firstData.length); System.arraycopy(secondData, 0, mergeData, firstData.length, secondData.length); return mergeData; } /** * 字节数组 截取首128字节 128 */ public byte[] cutBytes(byte[] Data) { byte[] cutData = new byte[128]; System.arraycopy(Data, 0, cutData, 0, cutData.length); return cutData; } /** * 字节数组 移除首128字节 128 */ public byte[] removeBytes(byte[] Data, int removeLength) { byte[] removeData = new byte[removeLength]; System.arraycopy(Data, 128, removeData, 0, removeData.length); return removeData; } /** * 获取assets文件流 转byte数组 */ public byte[] getBytesByFile(Context context, String fileName) { try { InputStream inStream = context.getResources().getAssets().open(fileName); ByteArrayOutputStream outStream = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; while ((len = inStream.read(buffer)) != -1) { outStream.write(buffer, 0, len); } outStream.close(); inStream.close(); return outStream.toByteArray(); } catch (Exception e) { e.printStackTrace(); return null; } } }
MainActivity 回调刷新UI 数据
package com.lvmi.powerupdate; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.ScrollView; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity implements View.OnClickListener, UartTest.OnSerialListener { private ScrollView sv_send,sv_result; private Button btn_start, btn_close; private TextView tv_Send, tv_Result; private UartTest uartTest; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initData(); initView(); } private void initData() { if (uartTest == null) { uartTest = new UartTest(this); uartTest.setOnSerialListener(this); } } private void initView() { sv_send = (ScrollView) findViewById(R.id.sv_send); sv_result = (ScrollView) findViewById(R.id.sv_result); tv_Send = (TextView) findViewById(R.id.tv_send); tv_Result = (TextView) findViewById(R.id.tv_result); btn_start = (Button) findViewById(R.id.btn_start); btn_start.setOnClickListener(this); btn_close = (Button) findViewById(R.id.btn_close); btn_close.setOnClickListener(this); } @Override public void onClick(View view) { switch (view.getId()) { case R.id.btn_start: tv_Send.setText(""); tv_Result.setText(""); btn_start.setEnabled(false); uartTest.StartUartTest(); break; case R.id.btn_close: uartTest.closeSerialPort(); btn_start.setEnabled(true); break; } } @Override public void onSerialOpenException(Exception e) { showData("串口打开失败",tv_Send); } @Override public void onSendData(String sendData) { showData(sendData, tv_Send); if(sendData.equals("Send Check")){ showData("已成功发送完数据包 ", tv_Send); btn_start.setEnabled(true); } sv_send.fullScroll(ScrollView.FOCUS_DOWN); } @Override public void onReceivedData(String receivedData) { showData(receivedData, tv_Result); if(receivedData.equals("(Ack) Receive Check")){ showData("已接受完数据,升级成功 ", tv_Result); } sv_result.fullScroll(ScrollView.FOCUS_DOWN); } @Override public void onUpdateError() { showData("未接收MCU数据,升级失败", tv_Result); } private void showData(final String str, TextView tv) { runOnUiThread(new Runnable() { @Override public void run() { tv.append(str + "\n"); } }); } }