记录一下使用SpringBoot+RXTXcomm实现Java串口通信,使用Java语言开发串口,对串口进行读写操作。
案例源码:SpringBoot+RXTXcomm实现Java串口通信 读取串口数据以及发送数据
RXTXcomm.jar这个包支持的系统较多,但是更新太慢,在win系统下使用没有问题,但是在centos的工控机系统里使用读取和发送有问题,至今没能解决,报错的日志也记录一下
serial port com start success
#
# A fatal error has been detected by the Java Runtime Environment:
#
# SIGSEGV (0xb) at pc=0x00007f5e636f75da, pid=18871, tid=0x00007f5e635ee700
#
# JRE version: Java(TM) SE Runtime Environment (8.0_144-b01) (build 1.8.0_144-b01)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.144-b01 mixed mode linux-amd64 compressed oops)
# Problematic frame:
# C [librxtxSerial.so+0x75da] Java_gnu_io_RXTXPort_nativeDrain+0xea
#
# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# An error report file with more information is saved as:
# /home/hs_err_pid18871.log
#
# If you would like to submit a bug report, please visit:
# http://bugreport.java.com/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#
已放弃
[root@localhost home]#
Java HotSpot(TM) Server VM warning: You have loaded library /home/jdk18/jre/lib/i386/librxtxSerial.so which might have disabled stack guard. The VM will try to fix the stack guard now.
It's highly recommended that you fix the library with 'execstack -c <libfile>', or link it with '-z noexecstack'.
java.lang.UnsatisfiedLinkError: /home/jdk18/jre/lib/i386/librxtxSerial.so: /home/jdk18/jre/lib/i386/librxtxSerial.so: 错误 ELF 类: ELFCLASS64 (Possible cause: architecture word width mismatch) thrown while loading gnu.io.RXTXCommDriver
15:33:11.580 spring-boot-logging [main] INFO o.s.b.a.l.ConditionEvaluationReportLoggingListener -
19:26:03.323 spring-boot-logging [main] INFO c.z.d.serialport.SerialPortManager - open serial port success:/dev/ttyS1
serial port com start success
19:26:15.326 spring-boot-logging [Thread-5] INFO c.z.data.serialport.SerialPortThread - stepCount--200
#
# A fatal error has been detected by the Java Runtime Environment:
#
# SIGSEGV (0xb) at pc=0x00007ffa604a7733, pid=17020, tid=0x00007ffa6019e700
#
# JRE version: Java(TM) SE Runtime Environment (8.0_144-b01) (build 1.8.0_144-b01)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.144-b01 mixed mode linux-amd64 compressed oops)
# Problematic frame:
# C [librxtxSerial.so+0x7733] Java_gnu_io_RXTXPort_nativeDrain+0xc3
#
# Core dump written. Default location: /usr/local/core or core.17020
#
# An error report file with more information is saved as:
# /usr/local/hs_err_pid17020.log
#
# If you would like to submit a bug report, please visit:
# http://bugreport.java.com/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#
已放弃(吐核)
因此如果要使用RXTXcomm.jar这个串口包,建议在win系统下使用更好一些,其他系统使用可能出现莫名其妙的问题,如果非要在linux(centos)系统使用,推荐使用jSerialComm.jar
<dependency>
<groupId>com.fazecast</groupId>
<artifactId>jSerialComm</artifactId>
<version>2.9.2</version>
</dependency>
这里记录使用SpringBoot加RXTXcomm在win10系统下的使用
mfz-rxtx-2.2-20081207-win-x64的下载地址
http://fizzed.com/oss/rxtx-for-java
根据自己的系统选择对应的下载包
从readme.txt得知编译的环境特别旧了,将来的使用定然是越来越少
win-x86, win-x64, ia64
-----------------------------------------------
Built using Microsoft Visual C++ 2008 - not MinGW. The
x86 and x64 versions are native and do not rely on
any other non-standard windows libraries. Just drop
in the compiled .dlls that are specific to the version
of Java you run. If you installed the 64-bit version
of the JDK, then install the x64 build.
I've tested the x86 and x64 version with Windows 2008,
2003, and Vista SP1.
linux-i386 & linux-x86_64
-----------------------------------------------
Built using CentOS 5.2 and gcc 4.1.2.
Just drop in the compiled .dlls that are specific to
the version of Java you run. If you installed the 64-bit
version of the JDK, then install the x64 build.
I've tested the x86 and x64 versions with x86 and x64
versions of CentOS 5.0 and 5.2.
根据文档,先将rxtxSerial.dll和rxtxParallel.dll放在指定的目录内
我自己电脑的JAVA_HOME
那么文件存放位置
至于 RXTXcomm.jar包不放在文档里的位置,放在具体的项目内引用(根据个人喜好来,我直接按照文档的方式去放,发现不起作用)
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>boot.example.mfz.rxtx</groupId>
<artifactId>boot-example-serial-port-mfz-rxtx-2.0.5</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>boot-example-serial-port-mfz-rxtx-2.0.5</name>
<description>Demo project for Spring Boot</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>gnu.io</groupId>
<artifactId>RXTXcomm</artifactId>
<version>2.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/libs/jar/RXTXcomm.jar</systemPath>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.example.BootRXTXApplication</mainClass>
<includeSystemScope>true</includeSystemScope><!--外部进行打包-->
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
这里有个点儿
<systemPath>${project.basedir}/libs/jar/RXTXcomm.jar</systemPath>
package com.example;
import com.example.serialport.SerialPortManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.List;
@SpringBootApplication
@EnableScheduling
@EnableAsync
public class BootRXTXApplication implements CommandLineRunner {
private final Logger log = LoggerFactory.getLogger(this.getClass());
public static void main(String[] args) throws IOException {
SpringApplication.run(BootRXTXApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
try{
List<String> portList = SerialPortManager.getSerialPortList();
if(!portList.isEmpty()){
log.info(portList.toString());
SerialPortManager.connectSerialPort();
}
} catch (Exception e){
log.error("获取串口信息失败");
}
}
@PreDestroy
public void destroy() {
SerialPortManager.closeSerialPort();
System.exit(0);
}
}
测试往串口发数据
SerialPortSendController.java
package com.example.controller;
import com.example.serialport.ConvertHexStrAndStrUtils;
import com.example.serialport.SerialPortManager;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SerialPortSendController {
// http://localhost:8781/sendTest?message=mywmyyhtw
@GetMapping(value = "/sendTest")
@ResponseBody
public String sendStringTopic(@RequestParam(name="message",required = true) String message) throws Exception {
SerialPortManager.sendSerialPortData(ConvertHexStrAndStrUtils.strToHexStr(message));
return "success";
}
}
字符串以及16进制以及字节之间的转换工具类ConvertHexStrAndStrUtils.java
package com.example.serialport;
import java.nio.charset.StandardCharsets;
public class ConvertHexStrAndStrUtils {
private static final char[] HEXES = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
public static String bytesToHexStr(byte[] bytes) {
if (bytes == null || bytes.length == 0) {
return null;
}
StringBuilder hex = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
hex.append(HEXES[(b >> 4) & 0x0F]);
hex.append(HEXES[b & 0x0F]);
}
return hex.toString().toUpperCase();
}
public static byte[] hexStrToBytes(String hex) {
if (hex == null || hex.length() == 0) {
return null;
}
char[] hexChars = hex.toCharArray();
byte[] bytes = new byte[hexChars.length / 2]; // 如果 hex 中的字符不是偶数个, 则忽略最后一个
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) Integer.parseInt("" + hexChars[i * 2] + hexChars[i * 2 + 1], 16);
}
return bytes;
}
public static String strToHexStr(String str) {
StringBuilder sb = new StringBuilder();
byte[] bs = str.getBytes();
int bit;
for (int i = 0; i < bs.length; i++) {
bit = (bs[i] & 0x0f0) >> 4;
sb.append(HEXES[bit]);
bit = bs[i] & 0x0f;
sb.append(HEXES[bit]);
}
return sb.toString().trim();
}
public static String hexStrToStr(String hexStr) {
//能被16整除,肯定可以被2整除
byte[] array = new byte[hexStr.length() / 2];
try {
for (int i = 0; i < array.length; i++) {
array[i] = (byte) (0xff & Integer.parseInt(hexStr.substring(i * 2, i * 2 + 2), 16));
}
hexStr = new String(array, StandardCharsets.UTF_8);
} catch (Exception e) {
e.printStackTrace();
return "";
}
return hexStr;
}
}
串口工具类 我这里默认写死COM1 波特率9600
SerialPortManager.java
package com.example.serialport;
import gnu.io.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.TooManyListenersException;
import java.util.concurrent.TimeUnit;
public class SerialPortManager {
private static final Logger log = LoggerFactory.getLogger(SerialPortManager.class);
public static String SERIAL_PORT_NUMBER = "COM1";
public static final int SERIAL_BAUD_RATE = 9600;
public static volatile long SERIAL_CALLBACK_TIME = System.currentTimeMillis()/1000;
public static volatile boolean SERIAL_PORT_STATE = false;
public static volatile SerialPort SERIAL_PORT_OBJECT = null;
// 获得系统可用的端口名称列表
@SuppressWarnings("unchecked")
public static List<String> getSerialPortList() {
List<String> systemPorts = new ArrayList<>();
//获得系统可用的端口
Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers();
while (portList.hasMoreElements()) {
String portName = portList.nextElement().getName();//获得端口的名字
systemPorts.add(portName);
}
return systemPorts;
}
public static void connectSerialPort(){
try {
closeSerialPort();
TimeUnit.MILLISECONDS.sleep(4000);
if(openSerialPort()){
System.out.println("serial port com start success");
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
// 打开串口 设置中断和监听事件
public static boolean openSerialPort() {
try {
CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(SERIAL_PORT_NUMBER);
CommPort commPort = portIdentifier.open(SERIAL_PORT_NUMBER, 3000);
if(commPort == null){return false;}
//判断是不是串口
if (commPort instanceof SerialPort) {
SerialPort serialPort = (SerialPort) commPort;
//设置串口参数(波特率,数据位8,停止位1,校验位无)
serialPort.setSerialPortParams(SERIAL_BAUD_RATE, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
// 当有数据到达时唤醒数据接收线程
serialPort.notifyOnDataAvailable(true);
// 当串口连接中断时唤醒中断线程
serialPort.notifyOnBreakInterrupt(true);
serialPort.notifyOnCarrierDetect(true);
serialPort.notifyOnDSR(true);
// 添加串口监听事件
serialPort.addEventListener(new SerialPortListener(new SerialPortCallback()));
SerialPortManager.SERIAL_PORT_OBJECT = serialPort;
SerialPortManager.SERIAL_PORT_STATE = true;
log.info("open serial port success:" + SERIAL_PORT_NUMBER);
return true;
} else {
//是其他类型的端口
throw new NoSuchPortException();
}
} catch (NoSuchPortException e) {
log.error("not find serial port:" + e.getMessage());
} catch (PortInUseException e) {
log.error("the serial port used:" + e.getMessage());
} catch (UnsupportedCommOperationException e) {
log.error("open others serial port:" + e.getMessage());
} catch (TooManyListenersException e) {
log.error("the more listener this serial port:"+ e.getMessage());
}
return false;
}
// 关闭串口
public static void closeSerialPort() {
SERIAL_PORT_STATE = false;
if (SERIAL_PORT_OBJECT != null) {
SERIAL_PORT_OBJECT.close();
SERIAL_PORT_OBJECT = null;
log.info("serial port close");
}
}
// 向串口发送数据
public static void sendSerialPortData(String data) {
OutputStream outputStream = null;
try {
outputStream = SERIAL_PORT_OBJECT.getOutputStream();
outputStream.write(ConvertHexStrAndStrUtils.hexStrToBytes(data));
outputStream.flush();
log.info("send data success:"+data);
} catch (IOException e) {
log.error("read data exception:"+e.getMessage());
} finally {
try {
outputStream.close();
} catch (IOException e) {
log.error("read data inputStream close error:"+ e.getMessage());
}
}
}
// 从串口读取数据
public static byte[] readSerialPortData() {
InputStream in = null;
byte[] bytes = {};
try {
TimeUnit.MILLISECONDS.sleep(200);
in = SERIAL_PORT_OBJECT.getInputStream();
byte[] readBuffer = new byte[1];
int bytesNum = in.read(readBuffer);
while (bytesNum > 0) {
bytes = concat(bytes, readBuffer);
bytesNum = in.read(readBuffer);
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return bytes;
}
public static byte[] concat(byte[] firstArray, byte[] secondArray) {
if (firstArray == null || secondArray == null) {
return null;
}
byte[] bytes = new byte[firstArray.length + secondArray.length];
System.arraycopy(firstArray, 0, bytes, 0, firstArray.length);
System.arraycopy(secondArray, 0, bytes, firstArray.length, secondArray.length);
return bytes;
}
}
SerialPortListener.java
package com.example.serialport;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SerialPortListener implements SerialPortEventListener {
private final Logger log = LoggerFactory.getLogger(this.getClass());
private final SerialPortCallback serialPortCallback;
public SerialPortListener(SerialPortCallback serialPortCallback) {
this.serialPortCallback = serialPortCallback;
}
public void serialEvent(SerialPortEvent serialPortEvent) {
log.warn("SerialPortTestListener:"+serialPortEvent.getEventType());
SerialPortManager.SERIAL_CALLBACK_TIME = System.currentTimeMillis()/1000;
if (serialPortEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
if (serialPortCallback != null) {
serialPortCallback.dataAvailable();
}
}
}
}
从串口接收数据的SerialPortCallback.java
package com.example.serialport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SerialPortCallback {
private final Logger log = LoggerFactory.getLogger(this.getClass());
public void dataAvailable() {
try {
//throw new Exception();
byte[] data = SerialPortManager.readSerialPortData();
String s = ConvertHexStrAndStrUtils.bytesToHexStr(data);
log.info("rev--data:"+s);
} catch (Exception e) {
log.error(e.toString());
}
}
}
定时器SerialPortTimer.java
package com.example.serialport;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
@Service
public class SerialPortTimer {
@Async
@Scheduled(cron = "0 0/5 * * * ?")
public void timeSerialPortScheduled() throws IOException, InterruptedException {
long now = System.currentTimeMillis();
DateFormat format = new SimpleDateFormat("yyyy-MM-dd:HH:mm:ss");
System.out.println("timeSerialPortScheduled--"+format.format(now));
long interval = 2 * 60 * 60;
long rebootInterval = 24 * 60 * 60;
long difference = now/1000 - SerialPortManager.SERIAL_CALLBACK_TIME;
// 当2个小时内收不到串口数据重启串口
if(difference > interval){
SerialPortManager.connectSerialPort();
}
// 当24小时内都还是收不到串口数据重启系统
if(difference > rebootInterval){
try {
String osName = System.getProperty("os.name");
if(osName.startsWith("Windows")) {
Runtime.getRuntime().exec("shutdown -r -t 0 -f");
} else if(osName.startsWith("Linux")){
Runtime.getRuntime().exec("reboot");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
代码结构
│ pom.xml
│
├─doc
│ │ mfz-rxtx-2.2-20081207-win-x64.zip
│ │ mfz-rxtx-2.2-20081207-win-x86.zip
│ │
│ └─mfz-rxtx-2.2-20081207-win-x64
│ BuildProperties.txt
│ Install.txt
│ Readme.txt
│ ReleaseNotes.txt
│ RXTXcomm.jar
│ rxtxParallel.dll
│ rxtxSerial.dll
│
├─libs
│ └─jar
│ jna.jar
│ RXTXcomm.jar
│
├─src
│ ├─main
│ │ ├─java
│ │ │ └─com
│ │ │ └─example
│ │ │ │ BootRXTXApplication.java
│ │ │ │
│ │ │ ├─controller
│ │ │ │ SerialPortSendController.java
│ │ │ │
│ │ │ └─serialport
│ │ │ ConvertHexStrAndStrUtils.java
│ │ │ SerialPortCallback.java
│ │ │ SerialPortListener.java
│ │ │ SerialPortManager.java
│ │ │ SerialPortTimer.java
│ │ │
│ │ └─resources
│ │ application.properties
│ │ logback-spring.xml
│ │
│ └─test
│ └─java
│ └─com
│ └─example
│ BootRXTXApplicationTest.java
│
接收数据测试 通过com2 向 com1发送数据 那么就算SpringBoot串口接收数据
可以看到控制台有打印数据
发送数据测试
http://localhost:8781/sendTest?message=mywmyyhtw