Java 串口通信(RS232/485)

Java 串口通信(RS232/485)

  • 一.串口通信页面
  • 二.串口服务实现
    • 1.Java 串口通信配置
      • 1.扩展包和依赖库
      • 2.Pom配置
    • 2.启动类
    • 3.工具包类
      • 1.Common
      • 2.Crc16Modbus
      • 3.SerialUtil
    • 4.WebSocket 配置
      • 1.启动配置
      • 2.监听配置
    • 5.UI交互类
      • 1.串口配置对象
      • 2.串口信息获取接口
      • 3.RS232接口
      • 4.RS485接口
    • 6.串口配置类
      • 1.串口配置
      • 2.RS232串口配置
      • 3.RS232串口监听
      • 4.RS485串口配置
      • 5.RS485串口监听
  • 三.UI代码
  • 四.测试效果
    • 1.串口通信
    • 2.CRC16通信

一.串口通信页面

在这里插入图片描述

Java 实现串口通信,同时通过 WebSocket 与 UI 实时交互传递通信数据

准备工作:

虚拟串口工具:Launch Virtual Serial Port Driver

串口调试助手:SSCOM

在这里插入图片描述

RS485

根据 Modbus 协议,常规485通讯的信息发送形式如下:
地址  功能码 数据信息 校验码
1byte 1byte  nbyte  2byte

在线 CRC检验码计算:CRC 测试链接

二.串口服务实现

1.Java 串口通信配置

1.扩展包和依赖库

RXTXcomm.jar   放入 {JAVA_HOME}/jre/lib/ext
rxtxserial.dll 放入 {JAVA_HOME}/jre/bin

以上两个包可以直接网上下载,注意和JDK版本搭配即可

2.Pom配置

串口通信包:rxtx



    4.0.0

    org.example
    SerialPort
    1.0-SNAPSHOT

    
        8
        8
        UTF-8
    


    
        
            org.springframework.boot
            spring-boot-starter-web
            2.7.4
        
        
            org.projectlombok
            lombok
            1.18.24
        

        
            org.springframework
            spring-websocket
            5.3.27
        
        
            org.rxtx
            rxtx
            2.1.7
        
    

    
        
            nexus-aliyun
            nexus-aliyun
            http://maven.aliyun.com/nexus/content/groups/public/
            
                true
            
            
                false
            
        
    

    
        
            public
            aliyun nexus
            http://maven.aliyun.com/nexus/content/groups/public/
            
                true
            
            
                false
            
        
    


2.启动类

package com.serial.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author
 * @date 2023-07-01 12:41
 * @since 1.8
 */
@SpringBootApplication
public class SerialApplication {

    public static void main(String[] args) {
        SpringApplication.run(SerialApplication.class,args);
    }

}

3.工具包类

1.Common

package com.serial.demo.util;

/**
 * @author
 * @date 2023-07-03 22:17
 * @since 1.8
 */
public class Common {

    public static String HEX_STRING = "0123456789ABCDEF";
    public static final String NONE = "无";
    public static final String ODD = "奇";
    public static final String EVEN = "偶";

    public static final String FORMAT_HEX="HEX";
}

2.Crc16Modbus

CRC16 Modbus Java 实现:计算数据的校验码
package com.serial.demo.util;

/**
 * @author
 * @date 2023-07-04 20:37
 * @since 1.8
 */
public class Crc16Modbus {

    /**
     * CRC 循环冗余校验 即通过生成多项式对原始数据进行计算,将计算结果拼接到数据上一起发送
     *     接收方计算接收到的数据校验接收结果是否准确
     * CRC 即对生成多项式的模二运算
     *
     * 1.预置1个16位的寄存器为十六进制 FFFF(即全为1),称此寄存器为CRC寄存器
     * 2.把第1个8位二进制数据(帧头字节)与 CRC 寄存器的低8位相异或并写回寄存器 高8位数据不变
     * 3.把 CRC 循环右移 高位补 0 取得移出位
     * 4.如果移出位为 0 继续右移 如果移出位为 1 则 CRC 寄存器与多项式 A001(1010 0000 0000 0001)进行异或运算
     * 5.重复步骤 3 和 4 直到右移 8 次
     * 6.重复步骤 2 到 5 进行数据帧下一个字节的处理 直到将数据帧所有字节按上述步骤计算
     * 7.根据需要将寄存器的高、低字节进行交换 得到最终 CRC码
     *
     */

    /**
     * 初始值 CRC-16 寄存器
     */
    private static final int INITIAL_VALUE = 0xFFFF;
    private static final boolean IS_OUT_PUT_OVER_TURN = true;

    /**
     * 原始数据 + CRC码
     *
     * @param hexes 16 进制字符串
     * @return
     */
    public static byte[] getData(String... hexes) {
        byte[] data = new byte[hexes.length];
        int i = 0;
        for (String hex:hexes){
            //先转为数字在转为 byte
            data[i++] = (byte) Integer.parseInt(hex, 16);
        }
        return merge(data);
    }

    /**
     * 原始数据 + CRC码
     *
     * @param data
     * @return
     */
    public static byte[] merge(byte[] data) {
        byte[] crc = getCrc16(data);
        int dLen = data.length;
        int cLen = crc.length;
        byte[] result = new byte[dLen + cLen];
        System.arraycopy(data,0,result,0,dLen);
        System.arraycopy(crc,0,result,dLen,cLen);
        return result;
    }

    /**
     * 基于 CRC16 Modbus 计算校验码
     * CRC 16 Modbus 默认多项式为 x16+x15+x2+1 => 8005 反转即 A001
     *
     * @param data
     * @return
     */
    private static byte[] getCrc16(byte[] data) {
        int len = data.length;
        int crc = INITIAL_VALUE;
        int i, j;
        for (i = 0; i < len; i++) {
            // 把第一个 8 位二进制数据 与 16 位的 CRC寄存器的低 8 位相异或, 把结果放于 CRC寄存器
            crc = ((crc & 0xFF00) | (crc & 0x00FF) ^ (data[i] & 0xFF));
            for (j = 0; j < 8; j++) {
                // 把 CRC 寄存器的内容右移一位(朝低位)用 0 填补最高位, 并检查右移后的移出位
                if ((crc & 0x0001) > 0) {
                    // 如果移出位为 1, CRC寄存器与多项式A001进行异或
                    crc = crc >> 1;
                    crc = crc ^ 0xA001;
                } else {
                    // 如果移出位为 0,再次右移一位
                    crc = crc >> 1;
                }
            }
        }
        return intToBytes(crc);
    }

    /**
     * 将 int 转换成 byte 数组 低位在前 高位在后
     */
    private static byte[] intToBytes(int value)  {
        byte[] src = new byte[2];
        byte hig = (byte) ((value>>8) & 0xFF);
        byte low = (byte) (value & 0xFF);
        if (IS_OUT_PUT_OVER_TURN){
            src[0] = low;
            src[1] = hig;
        } else {
            src[0] = hig;
            src[1] = low;
        }
        return src;
    }

    /**
     * 将字节数组转换成十六进制字符串
     */
    public static String byteTo16String(byte[] data) {
        StringBuffer buffer = new StringBuffer();
        for (byte b : data) {
            byteToHex(buffer,b);
        }
        return buffer.toString().toUpperCase();
    }

    /**
     * 将字节转换成十六进制字符串
     *
     * int 转 byte 对照表
     * [128,255],0,[1,128)
     * [-128,-1],0,[1,128)
     */
    public static void byteToHex(StringBuffer buffer ,byte b) {
        if (b < 0) {
            buffer.append(Integer.toString(b + 256, 16));
        } else if (b == 0) {
            buffer.append("00 ");
        } else if (b > 0 && b <= 15) {
            buffer.append("0" + Integer.toString(b, 16));
        } else if (b > 15) {
            buffer.append(Integer.toString(b, 16));
        }
        buffer.append(" ");
    }
}

3.SerialUtil

package com.serial.demo.util;

import gnu.io.SerialPort;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

/**
 * @author
 * @date 2023-07-03 21:52
 * @since 1.8
 */
public class SerialUtil {


    /**
     * 转为 HEX
     * @param str
     * @return
     */
    public static String toHex(String str){
        StringBuffer sbf = new StringBuffer();
        byte[] b = str.getBytes(StandardCharsets.UTF_8);
        for (int i = 0; i < b.length; i++) {
            String hex = Integer.toHexString(b[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            sbf.append(hex.toUpperCase() + "  ");
        }
        return sbf.toString().trim();
    }

    /**
     *
     * @param hex
     * @return
     */
    public static String toStr(String hex) {
        return new String(hexToByte(hex));
    }

    /**
     * 转 HEX 字节
     * @param hex
     * @return
     */
    public static byte[] hexToByte(String hex){
        hex = hex.toUpperCase().replace(" ","");
        ByteArrayOutputStream bao = new ByteArrayOutputStream(hex.length() / 2);
        // 将每2位16进制整数组装成一个字节
        for (int i = 0; i < hex.length(); i += 2) {
            bao.write((Common.HEX_STRING.indexOf(hex.charAt(i)) << 4 | Common.HEX_STRING.indexOf(hex.charAt(i + 1))));
        }
        return bao.toByteArray();
    }

    /**
     * 获取校验位配置
     * @param checkBit
     * @return
     */
    public static int getParity(String checkBit){
        if (Common.NONE.equals(checkBit)){
            return SerialPort.PARITY_NONE;
        } else if (Common.ODD.equals(checkBit)){
            return SerialPort.PARITY_ODD;
        } else if (Common.EVEN.equals(checkBit)){
            return SerialPort.PARITY_EVEN;
        } else {
            return SerialPort.PARITY_NONE;
        }
    }

    /**
     * 读取数据
     * @param in
     * @return
     */
    public static byte[] readFromPort(InputStream in) {
        byte[] bytes = {};
        try {
            // 缓冲区大小为一个字节
            byte[] readBuffer = new byte[1];
            int bytesNum = in.read(readBuffer);
            while (bytesNum > 0) {
                bytes = concat(bytes, readBuffer);
                bytesNum = in.read(readBuffer);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    in.close();
                    in = null;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return bytes;
    }

    /**
     * 字节转换
     * @param format
     * @param b
     * @return
     */
    public static String printHexString(String format, byte[] b) {
        String result = new String(b);
        if (Common.FORMAT_HEX.equals(format)){
            return SerialUtil.toHex(result);
        }
        return result;
    }

    /**
     * 合并数组
     *
     * @param firstArray  第一个数组
     * @param secondArray 第二个数组
     * @return 合并后的数组
     */
    public static byte[] concat(byte[] firstArray, byte[] secondArray) {
        if (firstArray == null || secondArray == null) {
            if (firstArray != null) {
                return firstArray;
            }
            if (secondArray != null) {
                return secondArray;
            }
            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;
    }
}

4.WebSocket 配置

1.启动配置

package com.serial.demo.socket;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * @author
 * @date 2023-07-02 21:05
 * @since 1.8
 */
@Configuration
public class WebSocketConfig {

    /**
     * 开启 websocket 配置
     * @return
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}

2.监听配置

package com.serial.demo.socket;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author
 * @date 2023-07-02 21:07
 * @since 1.8
 */
@Slf4j
@Component
@ServerEndpoint("/websocket/{sid}")
public class SerialWebSocket {

    /**
     * 缓存通信实例
     */
    private static Map webSocketMap = new ConcurrentHashMap<>(16);

    /**
     * 会话
     */
    private Session session;

    /**
     * 标识
     */
    private String sid;

    /**
     * 建立连接
     * @param sid
     * @param session
     */
    @OnOpen
    public void onOpen(@PathParam("sid") String sid,Session session){
        this.session = session;
        this.sid = sid;
        webSocketMap.put(sid,this);
        //sendMessage(sid,"Hello:");
    }

    /**
     * 关闭连接
     * @param sid
     */
    @OnClose
    public void onClose(@PathParam("sid") String sid){
        try {
            SerialWebSocket socket = webSocketMap.remove(sid);
            if (socket != null){
                socket.session.close();
                socket = null;
            }
        } catch (IOException e) {
            log.error("Close {} exception:",sid,e);
        }
    }

    /**
     * 接收消息
     * @param message
     */
    @OnMessage
    public void onMessage(String message){
        log.info("sid {} msg {}",this.sid,message);
    }

    /**
     * 发送消息
     * @param message
     * @param sid
     */
    public static void sendMessage(String sid,String message){
        SerialWebSocket socket = webSocketMap.get(sid);
        if (socket != null){
            try {
                socket.session.getBasicRemote().sendText(message);
            } catch (IOException e) {
                log.error("Send {} message {} exception:",sid,message,e);
            }
        }
    }

    /**
     * 广播消息
     * @param message
     */
    public static void broadcast(String message){
        for (String sid:webSocketMap.keySet()){
            sendMessage(sid,message);
        }
    }

}

5.UI交互类

1.串口配置对象

package com.serial.demo.entity;

import lombok.Data;

/**
 * @author
 * @date 2023-07-02 22:58
 * @since 1.8
 */
@Data
public class SerialEntity {

    private String portId;
    private int bitRate;
    private int dataBit;
    private int stopBit;
    private String checkBit;
    private String format;

}

2.串口信息获取接口

package com.serial.demo.controller;

import com.serial.demo.config.SerialPortConfig;
import com.serial.demo.util.SerialUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @author
 * @date 2023-07-01 16:37
 * @since 1.8
 */
@CrossOrigin
@RestController
@RequestMapping("/serial")
public class SerialController {

    @Autowired
    SerialPortConfig serial;

    /**
     * 获取端口列表
     * @return
     */
    @GetMapping("/getSerialPortList")
    public List getSerialPortList(){
        return serial.getSerialPortList();
    }

    /**
     * 字符串 转 HEX
     * @return
     */
    @GetMapping("/toHex")
    public String toHex(String str){
        return SerialUtil.toHex(str);
    }

    /**
     * HEX 转 字符串
     * @return
     */
    @GetMapping("/toStr")
    public String toStr(String hex){
        return SerialUtil.toStr(hex);
    }

}

3.RS232接口

package com.serial.demo.controller;

import com.serial.demo.config.Rs232Config;
import com.serial.demo.entity.SerialEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * @author
 * @date 2023-07-03 1:03
 * @since 1.8
 */
@CrossOrigin
@RestController
@RequestMapping("/serial/232")
public class Rs232Controller {

    @Autowired
    Rs232Config rs232Config;

    /**
     * 监听端口
     * @param serial
     */
    @PostMapping("/open")
    public boolean open(@RequestBody SerialEntity serial){
        return rs232Config.openPort(serial);
    }

    /**
     * 获取端口列表
     * @return
     */
    @GetMapping("/close/{portId}")
    public void close(@PathVariable("portId") String portId){
        rs232Config.closePort(portId);
    }

    /**
     * 获取端口列表
     * @return
     */
    @GetMapping("/send/{portId}/{format}/{msg}")
    public void close(@PathVariable("portId") String portId,@PathVariable("format") String format,@PathVariable("msg") String msg){
        rs232Config.sendData(portId,format,msg);
    }

}

4.RS485接口

package com.serial.demo.controller;

import com.serial.demo.config.Rs485Config;
import com.serial.demo.entity.SerialEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * @author
 * @date 2023-07-03 23:33
 * @since 1.8
 */
@CrossOrigin
@RestController
@RequestMapping("/serial/485")
public class Rs485Controller {

    @Autowired
    Rs485Config rs485Config;

    /**
     * 监听端口
     * @param serial
     */
    @PostMapping("/open")
    public boolean open(@RequestBody SerialEntity serial){
        return rs485Config.openPort(serial);
    }

    /**
     * 获取端口列表
     * @return
     */
    @GetMapping("/close/{portId}")
    public void close(@PathVariable("portId") String portId){
        rs485Config.closePort(portId);
    }

    /**
     * 获取端口列表
     * @return
     */
    @GetMapping("/send/{portId}/{format}/{msg}")
    public void close(@PathVariable("portId") String portId,@PathVariable("format") String format,@PathVariable("msg") String msg){
        rs485Config.sendData(portId,format,msg);
    }

}

6.串口配置类

1.串口配置

package com.serial.demo.config;

import gnu.io.CommPortIdentifier;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author
 * @date 2023-07-03 1:01
 * @since 1.8
 */
@Slf4j
@Component
public class SerialPortConfig {

    /**
     * 缓存端口信息
     */
    private static Map serialMap;

    @PostConstruct
    private void init(){
        refreshCom();
    }

    /**
     * 刷新端口
     */
    public void refreshCom(){
        Enumeration portList = CommPortIdentifier.getPortIdentifiers();
        CommPortIdentifier serial;
        Map temp = new ConcurrentHashMap<>(16);
        while (portList.hasMoreElements()){
            serial = portList.nextElement();
            if (serial.getPortType() == CommPortIdentifier.PORT_SERIAL){
                temp.put(serial.getName(),serial);
            }
        }
        serialMap = Collections.unmodifiableMap(temp);
    }

    /**
     * 获取端口列表
     * @return
     */
    public List getSerialPortList(){
        return serialMap.keySet().stream().sorted().collect(Collectors.toList());
    }

    /**
     * 获取串口
     * @return
     */
    public Map getSerialMap(){
        return serialMap;
    }
}

2.RS232串口配置

package com.serial.demo.config;

import com.serial.demo.entity.SerialEntity;
import com.serial.demo.util.Common;
import com.serial.demo.util.SerialUtil;
import gnu.io.CommPortIdentifier;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.UnsupportedCommOperationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.TooManyListenersException;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author
 * @date 2023-07-01 16:22
 * @since 1.8
 */
@Slf4j
@Component
public class Rs232Config {

    private static final int DELAY_TIME = 1000;


    @Autowired
    SerialPortConfig config;

    /**
     * 缓存端口实例
     */
    private Map serialPortMap = new ConcurrentHashMap<>(16);

    /**
     * 监听端口
     * @param serial
     */
    public boolean openPort(SerialEntity serial) {
        String portId = serial.getPortId();
        CommPortIdentifier commPortIdentifier = config.getSerialMap().get(portId);
        if (null != commPortIdentifier){
            SerialPort serialPort = null;
            int bitRate = 0,dataBit = 0,stopBit = 0,parity = 0;
            try {
                serialPort = (SerialPort) commPortIdentifier.open(portId,DELAY_TIME);
                // 设置监听器生效 当有数据时通知
                serialPort.notifyOnDataAvailable(true);
                // 比特率、数据位、停止位、奇偶校验位
                bitRate = serial.getBitRate();
                dataBit = serial.getDataBit();
                stopBit = serial.getStopBit();
                parity = SerialUtil.getParity(serial.getCheckBit());
                serialPort.setSerialPortParams(bitRate, dataBit, stopBit,parity);
            } catch (PortInUseException e) {
                log.error("Open CommPortIdentifier {} Exception:",serial.getPortId(),e );
                return false;
            } catch (UnsupportedCommOperationException e) {
                log.error("Set SerialPortParams BitRate {} DataBit {} StopBit {} Parity {} Exception:",bitRate,dataBit,stopBit,parity,e);
                return false;
            }

            // 设置当前串口的输入输出流
            InputStream input;
            OutputStream output;
            try {
                input = serialPort.getInputStream();
                output = serialPort.getOutputStream();
            } catch (IOException e) {
                log.error("Get serialPort data stream exception:",e);
                return false;
            }

            // 给当前串口添加一个监听器
            try {
                serialPort.addEventListener(new Serial232Listener(input,output,serial.getFormat()));
            } catch (TooManyListenersException e) {
                log.error("Get serialPort data stream exception:",e);
                return false;
            }
            serialPortMap.put(portId,serialPort);
            return true;
        }
        return false;
    }

    /**
     * 关闭端口
     * @param portId
     */
    public void closePort(String portId){
        SerialPort serialPort = serialPortMap.remove(portId);
        if (null != serialPort){
            serialPort.close();
        }
    }

    /**
     * 发送数据
     * @param portId
     * @param format
     * @param message
     */
    public void sendData(String portId,String format,String message){
        SerialPort serialPort = serialPortMap.get(portId);
        if (null == serialPort){
            return;
        }
        OutputStream output = null;
        try {
            byte[] bytes;
            if (Common.FORMAT_HEX.equals(format)){
                bytes = SerialUtil.hexToByte(message);
            } else {
                bytes = message.getBytes(StandardCharsets.UTF_8);
            }
            output = serialPort.getOutputStream();
            output.write(bytes);
            output.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (null != output){
                try {
                    output.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

}

3.RS232串口监听

package com.serial.demo.config;

import com.serial.demo.socket.SerialWebSocket;
import com.serial.demo.util.Crc16Modbus;
import com.serial.demo.util.SerialUtil;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * @author
 * @date 2023-07-01 17:06
 * @since 1.8
 */
public class Serial232Listener implements SerialPortEventListener {

    InputStream inputStream;
    OutputStream outputStream;
    String format;

    public Serial232Listener(InputStream input, OutputStream output, String format){
        inputStream = input;
        outputStream = output;
        this.format = format;
    }

    @Override
    public void serialEvent(SerialPortEvent event) {
        switch (event.getEventType()) {
            case SerialPortEvent.BI:
            case SerialPortEvent.OE:
            case SerialPortEvent.FE:
            case SerialPortEvent.PE:
            case SerialPortEvent.CD:
            case SerialPortEvent.CTS:
            case SerialPortEvent.DSR:
            case SerialPortEvent.RI:
            case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
                break;
            case SerialPortEvent.DATA_AVAILABLE:
                // 当有可用数据时读取数据
                byte[] readBuffer = null;
                int availableBytes = 0;
                try {
                    availableBytes = inputStream.available();
                    while (availableBytes > 0) {
                        readBuffer = SerialUtil.readFromPort(inputStream);
                        String needData = Crc16Modbus.byteTo16String(readBuffer);
                        SerialWebSocket.broadcast(needData);
                        availableBytes = inputStream.available();
                    }
                } catch (IOException e) {
                }
            default:
                break;
        }
    }
}

4.RS485串口配置

package com.serial.demo.config;

import com.serial.demo.entity.SerialEntity;
import com.serial.demo.util.Common;
import com.serial.demo.util.Crc16Modbus;
import com.serial.demo.util.SerialUtil;
import gnu.io.CommPortIdentifier;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.UnsupportedCommOperationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.TooManyListenersException;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author
 * @date 2023-07-03 1:00
 * @since 1.8
 */
@Slf4j
@Component
public class Rs485Config {

    private static final int DELAY_TIME = 1000;


    @Autowired
    SerialPortConfig config;

    /**
     * 缓存端口实例
     */
    private Map serialPortMap = new ConcurrentHashMap<>(16);

    /**
     * 监听端口
     * @param serial
     */
    public boolean openPort(SerialEntity serial) {
        String portId = serial.getPortId();
        CommPortIdentifier commPortIdentifier = config.getSerialMap().get(portId);
        if (null != commPortIdentifier){
            SerialPort serialPort;
            int bitRate = 0,dataBit = 0,stopBit = 0,parity = 0;
            try {
                serialPort = (SerialPort) commPortIdentifier.open(portId,DELAY_TIME);
                // 设置监听器生效 当有数据时通知
                serialPort.notifyOnDataAvailable(true);
                serialPort.setDTR(true);
                serialPort.setRTS(true);
                // 比特率、数据位、停止位、奇偶校验位
                bitRate = serial.getBitRate();
                dataBit = serial.getDataBit();
                stopBit = serial.getStopBit();
                parity = SerialUtil.getParity(serial.getCheckBit());
                serialPort.setSerialPortParams(bitRate, dataBit, stopBit,parity);
            } catch (PortInUseException e) {
                log.error("Open CommPortIdentifier {} Exception:",serial.getPortId(),e );
                return false;
            } catch (UnsupportedCommOperationException e) {
                log.error("Set SerialPortParams BitRate {} DataBit {} StopBit {} Parity {} Exception:",bitRate,dataBit,stopBit,parity,e);
                return false;
            }

            // 设置当前串口的输入输出流
            InputStream input;
            OutputStream output;
            try {
                input = serialPort.getInputStream();
                output = serialPort.getOutputStream();
            } catch (IOException e) {
                log.error("Get serialPort data stream exception:",e);
                return false;
            }

            // 给当前串口添加一个监听器
            try {
                serialPort.addEventListener(new Serial485Listener(input,output,serial.getFormat()));
            } catch (TooManyListenersException e) {
                log.error("Get serialPort data stream exception:",e);
                return false;
            }
            serialPortMap.put(portId,serialPort);
            return true;
        }
        return false;
    }

    /**
     * 关闭端口
     * @param portId
     */
    public void closePort(String portId){
        SerialPort serialPort = serialPortMap.remove(portId);
        if (null != serialPort){
            serialPort.close();
        }
    }

    /**
     * 发送数据
     * @param portId
     * @param format
     * @param message
     */
    public void sendData(String portId,String format,String message){
        SerialPort serialPort = serialPortMap.get(portId);
        if (null == serialPort){
            return;
        }
        OutputStream output = null;
        try {
            byte[] bytes = new byte[0];
            if (Common.FORMAT_HEX.equals(format)){
                bytes = SerialUtil.hexToByte(message);
                bytes = Crc16Modbus.merge(bytes);
            }

            output = serialPort.getOutputStream();
            output.write(bytes);
            output.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (null != output){
                try {
                    output.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

5.RS485串口监听

package com.serial.demo.config;

import com.serial.demo.socket.SerialWebSocket;
import com.serial.demo.util.Crc16Modbus;
import com.serial.demo.util.SerialUtil;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * @author
 * @date 2023-07-03 23:21
 * @since 1.8
 */
public class Serial485Listener implements SerialPortEventListener {

    InputStream inputStream;
    OutputStream outputStream;
    String format;

    public Serial485Listener(InputStream input, OutputStream output, String format){
        inputStream = input;
        outputStream = output;
        this.format = format;
    }

    @Override
    public void serialEvent(SerialPortEvent event) {
        switch (event.getEventType()) {
            case SerialPortEvent.BI:
            case SerialPortEvent.OE:
            case SerialPortEvent.FE:
            case SerialPortEvent.PE:
            case SerialPortEvent.CD:
            case SerialPortEvent.CTS:
            case SerialPortEvent.DSR:
            case SerialPortEvent.RI:
            case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
                break;
            case SerialPortEvent.DATA_AVAILABLE:
                // 当有可用数据时读取数据
                byte[] readBuffer = null;
                int availableBytes = 0;
                try {
                    availableBytes = inputStream.available();
                    while (availableBytes > 0) {
                        readBuffer = SerialUtil.readFromPort(inputStream);
                        String needData = printHexString(readBuffer);
                        SerialWebSocket.broadcast(needData);
                        availableBytes = inputStream.available();
                    }
                } catch (IOException e) {
                }
            default:
                break;
        }
    }

    /**
     * 转为 16 进制字符串
     * @param b
     * @return
     */
    public static String printHexString(byte[] b) {
        return Crc16Modbus.byteTo16String(b);
    }

}

三.UI代码



    
        
        
        
        Serial Communication
        
        
        
        
    
    
        
            
                
串口配置
端口
波特率
数据位
停止位
校验位
操作
RS485
RS485
接收区设置
数据格式
停止显示
WebSocket
发送区设置
自动发送
数据格式
类型
发送
类型转换
STR
HEX

四.测试效果

1.串口通信

ASCII 收数

在这里插入图片描述

ASCII发数

在这里插入图片描述

切换为自动发送后即自动发送当前数据

在这里插入图片描述

Hex 收数

在这里插入图片描述

Hex 发数

在这里插入图片描述

2.CRC16通信

Hex 收数

在这里插入图片描述

Hex 发数

在这里插入图片描述

本文来自网络,不代表协通编程立场,如若转载,请注明出处:https://net2asp.com/901621e1dc.html