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");
}
});
}
}

浙公网安备 33010602011771号