Java 串口通信(RS232/485)
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/extrxtxserial.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;@SpringBootApplicationpublic class SerialApplication { public static void main(String[] args) { SpringApplication.run(SerialApplication.class,args); }}
3.工具包类
1.Common
package com.serial.demo.util;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;public class Crc16Modbus { private static final int INITIAL_VALUE = 0xFFFF; private static final boolean IS_OUT_PUT_OVER_TURN = true; 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); } 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; } 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); } 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(); } 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;public class SerialUtil { 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(); } public static String toStr(String hex) { return new String(hexToByte(hex)); } 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(); } 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; } } 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; } 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; } 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;@Configurationpublic class WebSocketConfig { @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;@Slf4j@Component@ServerEndpoint("/websocket/{sid}")public class SerialWebSocket { private static Map webSocketMap = new ConcurrentHashMap<>(16); private Session session; private String sid; @OnOpen public void onOpen(@PathParam("sid") String sid,Session session){ this.session = session; this.sid = sid; webSocketMap.put(sid,this); //sendMessage(sid,"Hello:"); } @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); } } @OnMessage public void onMessage(String message){ log.info("sid {} msg {}",this.sid,message); } 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); } } } 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;@Datapublic 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;@CrossOrigin@RestController@RequestMapping("/serial")public class SerialController { @Autowired SerialPortConfig serial; @GetMapping("/getSerialPortList") public List getSerialPortList(){ return serial.getSerialPortList(); } @GetMapping("/toHex") public String toHex(String str){ return SerialUtil.toHex(str); } @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.*;@CrossOrigin@RestController@RequestMapping("/serial/232")public class Rs232Controller { @Autowired Rs232Config rs232Config; @PostMapping("/open") public boolean open(@RequestBody SerialEntity serial){ return rs232Config.openPort(serial); } @GetMapping("/close/{portId}") public void close(@PathVariable("portId") String portId){ rs232Config.closePort(portId); } @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.*;@CrossOrigin@RestController@RequestMapping("/serial/485")public class Rs485Controller { @Autowired Rs485Config rs485Config; @PostMapping("/open") public boolean open(@RequestBody SerialEntity serial){ return rs485Config.openPort(serial); } @GetMapping("/close/{portId}") public void close(@PathVariable("portId") String portId){ rs485Config.closePort(portId); } @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;@Slf4j@Componentpublic 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); } public List getSerialPortList(){ return serialMap.keySet().stream().sorted().collect(Collectors.toList()); } 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;@Slf4j@Componentpublic class Rs232Config { private static final int DELAY_TIME = 1000; @Autowired SerialPortConfig config; private Map serialPortMap = new ConcurrentHashMap<>(16); 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; } public void closePort(String portId){ SerialPort serialPort = serialPortMap.remove(portId); if (null != serialPort){ serialPort.close(); } } 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;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;@Slf4j@Componentpublic class Rs485Config { private static final int DELAY_TIME = 1000; @Autowired SerialPortConfig config; private Map serialPortMap = new ConcurrentHashMap<>(16); 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; } public void closePort(String portId){ SerialPort serialPort = serialPortMap.remove(portId); if (null != serialPort){ serialPort.close(); } } 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;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; } } public static String printHexString(byte[] b) { return Crc16Modbus.byteTo16String(b); }}
Serial Communication
1.串口通信
ASCII 收数
ASCII发数
切换为自动发送后即自动发送当前数据
Hex 收数
Hex 发数
2.CRC16通信
Hex 收数
Hex 发数
来源地址:https://blog.csdn.net/weixin_42176639/article/details/131544863