最近有个需求是这样子的,客户购买了我们这边的室内可视分机,
客户DIY自己的软件,我们这边需要提供RS485串口通讯demo演示以及sdk集成。
大致需求是这样子,话不多说,往下看。
一、SDK部分
1、拷贝libxxx.so文件到armeabi-v7a目录下
拷贝.so库(用来进行485串口通讯的)到,libs/armeabi-v7a目录下,.so文件名称根据自己项目具体的功能模块命名名称。
2、删除res目录下所有的资源文件
3、build.gradle(Module:serialport485)
apply plugin: 'com.android.library'
android {
compileSdkVersion 26
defaultConfig {
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'consumer-rules.pro'
ndk {
// 设置支持的SO库架构
abiFilters 'armeabi', 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
}
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
configurations.all {
resolutionStrategy.force 'com.android.support:support-annotations:25.3.1'
}
}
4、serialport485的Library库,AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.leecore.serialport" />
5、串口通讯实现
串口通讯类,静态块加载.so文件,打开/关闭串口,获取输入流/输出流,定义JNI封装接口等等。
/*
* Copyright 2009 Cedric Priscal
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.leecore.serialport;
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;
/**
* @description: 串口通讯
* @version: v1.0
* @author: yeyl
* @date: 2020/12/22 18:46
* @history:
*/
public class SerialPort {
static {
System.loadLibrary("xxx");
}
private static final String TAG = "SerialPort";
private FileDescriptor mFd;
private FileInputStream mFileInputStream;
private FileOutputStream mFileOutputStream;
public SerialPort(File device, int baudrate, int flags) 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) {
throw new SecurityException();
}
}
mFd = serialPortOpen(device.getAbsolutePath(), baudrate, flags);
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 close() {
try {
mFileInputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
mFileOutputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
serialPortClose();
}
/**
* 打开串口
*
* @param path 设备路径
* @param baudrate 波特率
* @param flags 标志位
*/
private native FileDescriptor serialPortOpen(String path, int baudrate, int flags);
/**
* 串口关闭
*/
private native void serialPortClose();
}
6、打包成aar文件依赖方式
编译aar方式一:
编译aar方式二:
aar文件无debug和release版本之分,使用哪个都是可以的,编译aar结果:
二、DEMO部分
1、拷贝serialport485.aar文件到libs目录下
serialport485.aar文件名称是被修改后的
2、build.gradle(Module:app)
依赖aar方式一:
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.xxx.phone.demo"
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
ndk {
// 设置支持的SO库架构
abiFilters 'armeabi', 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar','*.aar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
configurations.all {
resolutionStrategy.force 'com.android.support:support-annotations:25.3.1'
}
}
依赖aar方式二:
apply plugin: 'com.android.application'
android {
...//此处省略代码
repositories {
flatDir {
dirs 'libs'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation(name: 'serialport485', ext: 'aar')
...//此处省略代码
}
3、MainActivity.java
比如打开/关闭串口API、读取/写入数据API等,串口通讯485接口说明,都已在此类中实现了。
package com.xxx.phone.demo;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;
import com.leecore.serialport.SerialPort;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidParameterException;
/**
* @description: 串口通讯485,打开/关闭串口、读取/写入数据演示
* @version: v1.0
* @author: yeyl
* @date: 2020/12/23 17:20
* @history:
*/
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private SerialPort mSerialPort;
private OutputStream mOutputStream;
private InputStream mInputStream;
/**
* 波特率
*/
private static final int BAUDRATE = 9600;
/**
* 串口路径
*/
private static final String UART_PATH = "/dev/uart_dev/UART_485";
/**
* 读取到的数据
*/
private TextView mTvReadData;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTvReadData = findViewById(R.id.tv_main_read_data);
findViewById(R.id.btn_main_open).setOnClickListener(this);
findViewById(R.id.btn_main_close).setOnClickListener(this);
findViewById(R.id.btn_main_read_data).setOnClickListener(this);
findViewById(R.id.btn_main_write_data).setOnClickListener(this);
}
/**
* 字节数组转16进制字符串
*
* @param buf
* @return
*/
public static String bytesToHexString(byte[] buf) {
final String HEX = "0123456789ABCDEF";
if (buf == null) {
return "";
}
StringBuffer result = new StringBuffer(2 * buf.length);
for (int i = 0; i < buf.length; i++) {
result.append(HEX.charAt((buf[i] >> 4) & 0x0f)).append(HEX.charAt(buf[i] & 0x0f));
}
return result.toString();
}
/**
* 读取数据请求
*/
private void readDataRequest() {
if (mInputStream == null) {
return;
}
try {
if (mInputStream.available() > 0) {
byte[] response = new byte[mInputStream.available()];
mInputStream.read(response);
mTvReadData.setText(bytesToHexString(response));
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 写入数据请求
*/
private void writeDataRequest() {
if (mOutputStream == null) {
return;
}
byte[] data = new byte[3];
data[0] = 97;
data[1] = 98;
data[2] = 99;
try {
mOutputStream.write(data);
mOutputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_main_open:
init(UART_PATH, BAUDRATE);
break;
case R.id.btn_main_close:
closeSerialPort();
break;
case R.id.btn_main_read_data:
readDataRequest();
break;
case R.id.btn_main_write_data:
writeDataRequest();
break;
default:
break;
}
}
/**
* 初始化串口
*
* @param uartPath 串口路径
* @param baudrate 波特率
*/
public void init(String uartPath, int baudrate) {
try {
mSerialPort = getSerialPort(uartPath, baudrate);
mInputStream = mSerialPort.getInputStream();
mOutputStream = mSerialPort.getOutputStream();
} catch (SecurityException e) {
mSerialPort = null;
} catch (IOException e) {
mSerialPort = null;
} catch (InvalidParameterException e) {
}
}
/**
* 根据设备串口路径以及波特率设置串口
*
* @param path 串口路径
* @param baudrate 波特率
* @return 打开的串口
* @throws SecurityException
* @throws IOException
* @throws InvalidParameterException
*/
private SerialPort getSerialPort(String path, int baudrate) throws SecurityException, IOException, InvalidParameterException {
if (mSerialPort == null) {
/* Check parameters */
if ((path.length() == 0) || (baudrate == -1)) {
throw new InvalidParameterException();
}
/* Open the serial port */
mSerialPort = new SerialPort(new File(path), baudrate, 0);
}
return mSerialPort;
}
/**
* 关闭串口
*/
public void closeSerialPort() {
if (mSerialPort != null) {
mSerialPort.close();
mSerialPort = null;
}
mInputStream = null;
mOutputStream = null;
}
}
4、activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context="com.xxx.phone.demo.MainActivity">
<Button
android:id="@+id/btn_main_open"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="打开串口" />
<Button
android:id="@+id/btn_main_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="关闭串口" />
<Button
android:id="@+id/btn_main_read_data"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="读取数据" />
<Button
android:id="@+id/btn_main_write_data"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="写入数据" />
<TextView
android:id="@+id/tv_main_read_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp" />
</LinearLayout>
三、串口调试部分
1、demo截图
2、串口调试软件截图
写入(发送)数据:
读取(接收)数据:
串口调试软件输入97 98 99数据完,点击手动发送,接着回到Modbus485Demo界面中点击读取数据,界面左下角显示979899数据,验证结果是正确的。