经过大量翻阅文章总结出springboot连接串口最可行的方法!希望能帮到大家( •̀ ω •́ )✧
使用Rxtx实现串口通信
1、配置pom.xml
<!-- 串口内容读取 -->
<dependency>
<groupId>org.bidib.jbidib.org.qbang.rxtx</groupId>
<artifactId>rxtxcomm</artifactId>
<version>2.2</version>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
2、创建串口类
@Slf4j
@Data
public class SerialPortEntity {
private String portName;//端口号
private Integer baudrate;//波特率
private int databits;//数据位
private String databitsStr;//数据位字符串
private int stopbits;//停止位
private String stopbitsStr;//停止位字符串
private int paritybit;//校验位
private String paritybitStr;//校验位字符串
private SerialPort serialPort = null; //串口会话
public SerialPortEntity(String portName, Integer baudrate, String paritybit, String databits, String stopbits){
this.portName = portName;
this.baudrate = baudrate;
this.databitsStr = databits;
this.stopbitsStr = stopbits;
this.paritybitStr = paritybit;
//校验位
switch (paritybit) {
case "NONE":
this.paritybit = SerialPort.PARITY_NONE;
break;
case "ODD":
this.paritybit = SerialPort.PARITY_ODD;
break;
case "EVEN":
this.paritybit = SerialPort.PARITY_EVEN;
break;
case "MARK":
this.paritybit = SerialPort.PARITY_MARK;
break;
case "SPACE":
this.paritybit = SerialPort.PARITY_SPACE;
break;
}
//数据位
switch (databits) {
case "5":
this.databits = SerialPort.DATABITS_5;
break;
case "6":
this.databits = SerialPort.DATABITS_6;
break;
case "7":
this.databits = SerialPort.DATABITS_7;
break;
case "8":
this.databits = SerialPort.DATABITS_8;
break;
}
//停止位
switch (stopbits) {
case "1":
this.stopbits = SerialPort.STOPBITS_1;
break;
case "1.5":
this.stopbits = SerialPort.STOPBITS_1_5;
break;
case "2":
this.stopbits = SerialPort.STOPBITS_2;
break;
}
}
// 连接串口
public SerialPort connect(){
// 查看所有串口
SerialPortUtil serialPortUtil = SerialPortUtil.getSerialPortUtil();
List<String> portList = serialPortUtil.findPort();
log.info("发现全部端口: "+portList);
log.info("尝试打开端口:"+portName+" ....");
// 打开指定端口
SerialPort serialPort = serialPortUtil.openPort(portName,baudrate,databits,paritybit,stopbits);
SerialPortListener listener = new SerialPortListener();
listener.setSerialPort(serialPort);
setSerialPort(serialPort);
serialPortUtil.addListener(serialPort,listener);
return serialPort;
}
// 关闭串口
public void close(){
SerialPortUtil serialPortUtil = SerialPortUtil.getSerialPortUtil();
if(serialPort != null) serialPortUtil.closePort(serialPort);
else log.info("请先调用connect方法!");
}
// 发送指令
public void send(byte[] code){
SerialPortUtil serialPortUtil = SerialPortUtil.getSerialPortUtil();
if(serialPort != null) serialPortUtil.sendToPort(serialPort,code);
else log.info("请先调用connect方法!");
}
}
3、创建监听器
@Slf4j
@Data
public class SerialPortListener implements SerialPortEventListener {
private SerialPort serialPort = null;
@Override
public void serialEvent(SerialPortEvent serialPortEvent) {
switch (serialPortEvent.getEventType()){
// 串口存在有效数据
case SerialPortEvent.DATA_AVAILABLE:
byte[] bytes = SerialPortUtil.getSerialPortUtil().readFromPort(serialPort);
log.info("===========start===========");
log.info(new Date() + "【读到的字符】:-----" + Arrays.toString(bytes));
log.info(new Date() + "【字节数组转16进制字符串】:-----" + ModBusUtils.bytes2HexString(bytes));
log.info("===========end===========");
break;
// 2.输出缓冲区已清空
case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
log.error("输出缓冲区已清空");
break;
// 3.清除待发送数据
case SerialPortEvent.CTS:
log.error("清除待发送数据");
break;
// 4.待发送数据准备好了
case SerialPortEvent.DSR:
log.error("待发送数据准备好了");
break;
// 10.通讯中断
case SerialPortEvent.BI:
log.error("与串口设备通讯中断");
break;
default:
break;
}
}
}
4、创建串口工具类
@Slf4j
public class SerialPortUtil {
private static SerialPortUtil serialPortUtil = null;
static {
serialPortUtil = new SerialPortUtil();
}
private SerialPortUtil(){
}
/**
* 获取提供服务的SerialTool对象
* @return serialPortUtil
*/
public static SerialPortUtil getSerialPortUtil(){
if(serialPortUtil == null){
serialPortUtil = new SerialPortUtil();
}
return serialPortUtil;
}
/**
* 查找所有可用端口
* @return 可用端口名称列表
*/
public ArrayList<String> findPort() {
// 获得当前所有可用串口
Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers();
ArrayList<String> portNameList = new ArrayList<>();
// 将可用串口名添加到List并返回该List
while (portList.hasMoreElements()) {
String portName = portList.nextElement().getName();
portNameList.add(portName);
}
return portNameList;
}
/**
* 打开串口
* @param portName 端口名称
* @param baudrate 波特率 9600
* @param databits 数据位 8
* @param parity 校验位(奇偶位) NONE :0
* @param stopbits 停止位 1
* @return 串口对象
*/
public SerialPort openPort(String portName, int baudrate, int databits, int parity, int stopbits) {
try {
// 通过端口名识别端口
CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName);
// 打开端口,并给端口名字和一个timeout(打开操作的超时时间)
CommPort commPort = portIdentifier.open(portName, 5000);
// 判断是不是串口
if (commPort instanceof SerialPort) {
SerialPort serialPort = (SerialPort) commPort;
// 设置一下串口的波特率等参数
serialPort.setSerialPortParams(baudrate, databits, stopbits, parity);
log.info("打开串口 " + portName + " 成功 !");
return serialPort;
} else {
log.error("不是串口");
}
} catch (NoSuchPortException e1) {
log.error("没有找到端口");
e1.printStackTrace();
} catch (PortInUseException e2) {
log.error("端口被占用");
e2.printStackTrace();
} catch (UnsupportedCommOperationException e) {
e.printStackTrace();
}
return null;
}
/**
* 关闭串口
* @param serialPort 待关闭的串口对象
*/
public void closePort(SerialPort serialPort) {
if (serialPort != null) {
serialPort.close();
}
}
/**
* 往串口发送数据
* @param serialPort 串口对象
*/
public void sendToPort(SerialPort serialPort, byte[] bytes) {
OutputStream out = null;
try {
out = serialPort.getOutputStream();
out.write(bytes);
out.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 从串口读取数据
* @param serialPort 当前已建立连接的SerialPort对象
* @return 读取到的数据
*/
public byte[] readFromPort(SerialPort serialPort) {
InputStream in = null;
byte[] bytes = null;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
in = serialPort.getInputStream();
// 获取buffer里的数据长度
int bufferlength = in.available();
while (bufferlength != 0) {
// 初始化byte数组为buffer中数据的长度
bytes = new byte[bufferlength];
in.read(bytes);
bufferlength = in.available();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return bytes;
}
/**
* 添加监听器
* @param port 串口对象
* @param listener 串口监听器
*/
public void addListener(SerialPort port, SerialPortEventListener listener) {
try {
// 给串口添加监听器
port.addEventListener(listener);
// 设置当有数据到达时唤醒监听接收线程
port.notifyOnDataAvailable(true);
// 设置当通信中断时唤醒中断线程
port.notifyOnBreakInterrupt(true);
} catch (TooManyListenersException e) {
log.error("太多监听器");
e.printStackTrace();
}
}
/**
* 删除监听器
*
* @param port 串口对象
* @param listener 串口监听器
*/
public void removeListener(SerialPort port, SerialPortEventListener listener) {
// 删除串口监听器
port.removeEventListener();
}
/**
* 设置串口的Listener
*
* @param serialPort
* @param listener
*/
public static void setListenerToSerialPort(SerialPort serialPort, SerialPortEventListener listener) {
try {
// 给串口添加事件监听
serialPort.addEventListener(listener);
} catch (TooManyListenersException e) {
e.printStackTrace();
}
// 串口有数据监听
serialPort.notifyOnDataAvailable(true);
// 中断事件监听
serialPort.notifyOnBreakInterrupt(true);
}
}
5、Modbus工具类
public class ModBusUtils {
public static char byteToASCLL(byte b){
return (char) b;
}
/*
* 字节数组转16进制字符串
*/
public static String bytes2HexString(byte[] b) {
String r = "";
for (int i = 0; i < b.length; i++) {
String hex = Integer.toHexString(b[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
r += hex.toUpperCase()+" ";
}
return r;
}
6、测试类
@SpringBootTest
@Slf4j
public class RxtxTest {
@Test
public void SerialDemo(){
SerialPortEntity serialPortEntity = new SerialPortEntity("COM4",19200,"NONE","8","1");
try {
SerialPort serialPort = serialPortEntity.connect();
serialPortEntity.send(new byte[]{1,3,0,38,0,1,101,(byte)193});
Thread.sleep(10000);
serialPortEntity.close();
}catch (Exception e){
serialPortEntity.close();
e.printStackTrace();
}
}
}
7、遇到java.lang.NoClassDefFoundError: Could not initialize class gnu.io.RXTXVersion
问题的小伙伴可以安装两个DLL文件到 JDK目录/jre/bin下来解决此报错。
文件下载链接:https://download.csdn.net/download/qq_37281772/88356664
好啦!现在来测试一下( •̀ ω •́ )✧~
准备调试工具
串口调试工具:http://www.zlmcu.com/document/com_debug_tools.html
虚拟串口工具 :https://download.csdn.net/download/qq_37281772/87449453
操作步骤
1、打开虚拟串口工具,创建虚拟串口对,我使用的是COM4和COM5(端口可自定义)
2、打开串口调试工具,打开COM5串口(因为上面测试代码打开了COM4)
3、启动Test代码
COM4给COM5下发指令
COM5给COM4下发指令
萌新第一次写文,如文章有误,欢迎指出哈ヾ(>▽<)o*
参考文献:
1、Netty介绍:https://www.cnblogs.com/MrRightZhao/p/11925307.html
2、Netty集成串口RXTX编程:https://www.jianshu.com/p/9e9747dedc3d
3、Java使用Netty实现Modbus-RTU通信协议:https://blog.csdn.net/qq_40042416/article/details/128457288
4、springboot+netty+串口:https://blog.csdn.net/csweldn520/article/details/116661871
5、java利用socket通信实现Modbus-RTU通信协议:https://blog.csdn.net/qq_40042416/article/details/115672989