赞
踩
记录一下使用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>
BootRXTXApplication.java
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
可以看到控制台
对应的接收端
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。