SpringBoot使用RXTX连接串口教程及遇到的坑总结

SpringBoot使用RXTX连接串口教程及遇到的坑总结

  • 本文主要记录了本人在近期使用串口操作设备时的流程以及踩到的坑,希望能够帮助到有需要运用JAVA去操作串口需求的朋友,避免像我一样踩了很多坑才成功连接上。

一、所用环境及依赖

  • java: jdk11
  • maven: apache-maven-3.5.3
  • sprintboot:2.7.6
  • rxtx: 2.1.7
  • operate system: windows10

二、部署流程

2.1 下载RXTXComm包

  1. 可以选择从官网下载
  2. 也直接下载我网盘的分享文件网盘文件,提取码:sjsf
  3. 下载完成后把压缩包里的RXTXcomm.jar、rxtxParallel.dll、rxtxSerial.dll复制出来
    在这里插入图片描述

2.2 部署RXTXComm包

  1. 由于我的Springboot工程使用的是Maven仓库,因此需要通过命令先把RXTXcomm.jar导入到自己Maven的Repository里面(Maven的具体使用方式本文不做详解)
  2. 打开CMD,输入以下命令,导入RXTXcomm.jar
/*
 * DgroupId: pom.xml中映射的groupId
 * DartifactId: pom.xml中映射的artifactId
 * Dversion: pom.xml中映射的version
 * Dpackaging: 加载的是jar包
 * Dfile: RXTXcomm.jar包的路径,建议使用绝对路径
 */ 
mvn install:install-file-DgroupId=gnu.io -DartifactId=rxtx -Dversion=2.1.7 -Dpackaging=jar -Dfile="E:\RXTXcomm.jar"
  1. 运行后若在Maven的repository中存在下面的文件夹及文件,就代表导入成功了
    在这里插入图片描述
  2. RXTXcomm.jar导入成功后,需要把解压到的rxtxParallel.dll、rxtxSerial.dll文件复制到JAVA_HOME/bin路径下或者C:/Windows/System32/路径下(JAVA_HOME的配置方法很简单,自行百度即可)
  3. 最后在pom.xml中输入以下命令引用依赖即可完成RXTXcomm依赖导入到项目中
<dependency>

	<groupId>gnu.io</groupId>
	<artifactId>rxtx</artifactId>
	<version>2.1.7</version>

</dependency>

三、编写串口使用程序

  • 为了更有效、更清晰地使用RXTXcomm,我编写了以下几个文件,分享出来供参考
  • 我的工程目录如下
    在这里插入图片描述

3.1 编写RXTXConfig.java

  1. 首先在application.yml中添加下面的配置
rxtx-config:
  portName: COM13  # 串口名
  baudRate: 9600  # 波特率
  parityBit: EVEN  # 检验位
  dataBits: 8  # 数据位
  stopBits: 1  # 停止位
  1. config目录下建立RXTXConfig.java,代码如下
package com.mbtxtq.app.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "rxtx-config")
public class RXTXConfig {

    private String portName;
    private Integer baudRate;
    private String parityBit;
    private String dataBits;
    private String stopBits;

    public String getPortName() {

        return portName;

    }

    public void setPortName(String portName) {

        this.portName = portName;

    }

    public Integer getBaudRate() {

        return baudRate;

    }

    public void setBaudRate(Integer baudRate) {

        this.baudRate = baudRate;

    }

    public String getParityBit() {

        return parityBit;

    }

    public void setParityBit(String parityBit) {

        this.parityBit = parityBit;

    }

    public String getDataBits() {

        return dataBits;

    }

    public void setDataBits(String dataBits) {

        this.dataBits = dataBits;

    }

    public String getStopBits() {

        return stopBits;

    }

    public void setStopBits(String stopBits) {

        this.stopBits = stopBits;

    }

}

3.2 编写实体类SerialPortEntity

  • 在entity目录下建立SerialPortEntity.java,代码如下
package com.mbtxtq.app.entity;

import com.mbtxtq.app.listener.SerialPortListener;
import com.mbtxtq.app.utils.SerialPortUtil;
import gnu.io.SerialPort;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.List;

@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.3 编写监听器SerialPortListener

  • 在listener目录下建立SerialPortEntity.java,代码如下
package com.mbtxtq.app.listener;

import com.mbtxtq.app.utils.SerialPortUtil;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.Date;

@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;

        }

    }

}

3.4 编写工具类SerialPortUtil

  • 在utils目录下建立SerialPortUtil.java,代码如下
package com.mbtxtq.app.utils;

import gnu.io.*;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.TooManyListenersException;

@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);

//            如果jdk大于1.8版本的话不要把out.flush()打开,会报错
//            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);

    }

}

3.5 编写枚举类WarningLightEnum

  • 在enums目录下建立WarningLightEnum.java,代码如下
package com.mbtxtq.app.enums;

public enum WarningLightEnum {

    ALL_CLOSE("全关", new byte[]{1, 5, 0, 0, 0, 0, (byte) 205, (byte) 202}),

    RED_LIGHT_OPEN("红灯亮", new byte[]{1, 5, 0, 1, (byte) 255, 0, (byte) 221, (byte) 250});

    private String codeType;
    private byte[] codeList;

    private WarningLightEnum(String codeType, byte[] codeList) {

        this.codeType = codeType;
        this.codeList = codeList;

    }

    public String getCodeType() {

        return codeType;

    }

    public void setCodeType(String codeType) {

        this.codeType = codeType;

    }

    public byte[] getCodeList() {

        return codeList;

    }

    public void setCodeList(byte[] codeList) {

        this.codeList = codeList;

    }

}

3.6 编写测试类TestController

  • 在controller目录下建立TestController.java,代码如下
package com.mbtxtq.app.controller.test;

import com.mbtxtq.app.config.RXTXConfig;
import com.mbtxtq.app.entity.main_scout_process.SerialPortEntity;
import com.mbtxtq.app.enums.WarningLightEnum;

import gnu.io.SerialPort;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@Controller
@CrossOrigin(origins = "*")
@ResponseBody
@RequestMapping("/target/feature/extract")
public class TestController {

    @Autowired
    private RXTXConfig rxtxConfig;

    @PostMapping("test")
    public void test(@RequestBody Map<String, Object> params) {

		String useType = (String) params.get("use_type");

        byte[] codeList1 = warningLightEnum.ALL_CLOSE.getCodeList();
        byte[] codeList2 = WarningLightEnum.RED_LIGHT_OPEN.getCodeList();

		// 初始化实体类SerialPortEntity,参数使用RXTXConfig中的参数,可自行调整
        SerialPortEntity serialPortEntity = new SerialPortEntity(
                this.rxtxConfig.getPortName(), this.rxtxConfig.getBaudRate(),
                this.rxtxConfig.getParityBit(), this.rxtxConfig.getDataBits(), this.rxtxConfig.getStopBits()
        );

        try {

            SerialPort serialPort = serialPortEntity.connect();

            System.out.println(serialPort);

			if (useType.equals("close")) {

				serialPortEntity.send(codeList1);
				
			} else if (useType.equals("start")) {

				serialPortEntity.send(codeList2);
			
			}

//            Thread.sleep(10000);

            serialPortEntity.close();

        } catch (Exception e){

            e.printStackTrace();

            serialPortEntity.close();

        }

    }

}

  • 通过postman获取前端代码请求test的api即可调用串口的测试接口,成功连接!

四、所踩到的坑

  1. 复制rxtxParallel.dll、rxtxSerial.dll这两个文件很重要!没有复制或者复制错目录会导致报以下的错
java.lang.UnsatisfiedLinkError: no rxtxSerial64 in java.library.path thrown while loading gnu.io.RXTXCommDriver
Exception in thread "AWT-EventQueue-0" java.lang.NoClassDefFoundError: Could not initialize class gnu.io.RXTXVersion
	at gnu.io.CommPortIdentifier.<clinit>(CommPortIdentifier.java:123)
  1. 如果你使用的是jdk1.8以上的版本的话,SerialPortUtil中sendToPort方法一定不要加out.flush,否则会报错。这个地方卡了我大半天,后来才找出来。
  2. 当使用java调用串口时,不要使用其他串口测试工具,否则会出现报错
  • 6
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
Spring Boot使用RXTX进行串口通信,可以按照以下步骤进行: 1. 添加RXTX依赖 在pom.xml文件中添加以下依赖: ```xml <dependency> <groupId>org.rxtx</groupId> <artifactId>rxtx</artifactId> <version>2.2</version> </dependency> ``` 2. 配置串口信息 在application.properties文件中添加以下配置信息: ```properties serial.port.name=COM1 # 串口名称 serial.port.baudrate=9600 # 波特率 serial.port.databits=8 # 数据位 serial.port.stopbits=1 # 停止位 serial.port.parity=0 # 校验位 ``` 3. 创建SerialPortService类 创建一个SerialPortService类,用于打开、关闭串口,发送和接收数据。代码如下: ```java @Service public class SerialPortService { private SerialPort serialPort; public void open(String portName, int baudRate, int dataBits, int stopBits, int parity) throws Exception { CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName); if (portIdentifier.isCurrentlyOwned()) { throw new Exception("Port is currently in use"); } else { CommPort commPort = portIdentifier.open(this.getClass().getName(), 2000); if (commPort instanceof SerialPort) { serialPort = (SerialPort) commPort; serialPort.setSerialPortParams(baudRate, dataBits, stopBits, parity); } else { throw new Exception("Only serial ports are supported"); } } } public void close() { if (serialPort != null) { serialPort.close(); } } public void send(String data) throws Exception { if (serialPort != null) { OutputStream outputStream = serialPort.getOutputStream(); outputStream.write(data.getBytes()); outputStream.flush(); } else { throw new Exception("Serial port is not open"); } } public String receive() throws Exception { if (serialPort != null) { InputStream inputStream = serialPort.getInputStream(); byte[] buffer = new byte[1024]; int len = inputStream.read(buffer); return new String(buffer, 0, len); } else { throw new Exception("Serial port is not open"); } } } ``` 4. 创建Controller类 创建一个Controller类,用于处理HTTP请求和WebSocket请求。代码如下: ```java @RestController public class SerialPortController { @Autowired private SerialPortService serialPortService; @GetMapping("/open") public String open() throws Exception { serialPortService.open( Environment.getProperty("serial.port.name"), Integer.parseInt(Environment.getProperty("serial.port.baudrate")), Integer.parseInt(Environment.getProperty("serial.port.databits")), Integer.parseInt(Environment.getProperty("serial.port.stopbits")), Integer.parseInt(Environment.getProperty("serial.port.parity")) ); return "Serial port is open"; } @GetMapping("/close") public String close() { serialPortService.close(); return "Serial port is closed"; } @GetMapping("/send") public String send(@RequestParam String data) throws Exception { serialPortService.send(data); return "Data is sent"; } @MessageMapping("/receive") @SendTo("/topic/receive") public String receive() throws Exception { return serialPortService.receive(); } } ``` 5. 创建WebSocket配置类 创建一个WebSocket配置类,用于配置WebSocket。代码如下: ```java @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("/topic"); registry.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws").withSockJS(); } } ``` 6. 创建WebSocket客户端 创建一个WebSocket客户端,用于向服务器发送WebSocket请求和接收服务器的响应。代码如下: ```javascript var stompClient = null; function connect() { var socket = new SockJS('/ws'); stompClient = Stomp.over(socket); stompClient.connect({}, function (frame) { console.log('Connected: ' + frame); stompClient.subscribe('/topic/receive', function (message) { console.log('Received: ' + message.body); }); }); } function send() { var data = document.getElementById('data').value; stompClient.send('/app/send', {}, data); } ``` 7. 创建HTML页面 创建一个HTML页面,用于测试串口通信。代码如下: ```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Serial Port Test</title> <script src="/webjars/sockjs-client/1.5.0/dist/sockjs.min.js"></script> <script src="/webjars/stomp-websocket/2.3.3/dist/stomp.min.js"></script> <script src="/js/app.js"></script> </head> <body onload="connect()"> <input type="text" id="data"> <button onclick="send()">Send</button> </body> </html> ```
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Nikolas06

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值