在之前的文章中,我们讨论了使用单例模式的SerialPortEventListener类。然而,这种模式在某些情况下并不理想,因为它会导致重复创建监听器,从而无法正确获取串口返回的数据。那么,如何实现SerialPortEventListener的复用呢?
首先,我们需要了解什么是SerialPortEventListener类。SerialPortEventListener是一个用于监听串口事件的类,可以接收串口事件通知,并在事件发生时执行相应的操作。例如,当有数据可读时,它可以帮助我们进行数据缓存和处理。
setListenerToSerialPort函数用于建立监听,前端使用一个定时器不断地请求receiveDataTest()来获取数据,而后端则不断返回数据。但是,这个方法有一个问题,就是后端会重复创建监听器,导致每次都拿不到一个监听器的数据。
一、失败方案
原本获取串口返回的信息是这样写的:
串口监听工具
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.*; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * Created by Yeats * 串口工具类 */ public class SerialPortTool { /** * slf4j 日志记录器 */ private static final Logger logger = LoggerFactory.getLogger(SerialPortTool.class); /** * 查找电脑上所有可用 com 端口 * * @return 可用端口名称列表,没有时 列表为空 */ public static final ArrayList<String> findSystemAllComPort() { /** * getPortIdentifiers:获得电脑主板当前所有可用串口 */ Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers(); ArrayList<String> portNameList = new ArrayList<>(); /** * 将可用串口名添加到 List 列表 */ while (portList.hasMoreElements()) { String portName = portList.nextElement().getName();//名称如 COM1、COM2.... portNameList.add(portName); } return portNameList; } /** * 打开电脑上指定的串口 * * @param portName 端口名称,如 COM1,为 null 时,默认使用电脑中能用的端口中的第一个 * @param b 波特率(baudrate),如 9600 * @param d 数据位(datebits),如 SerialPort.DATABITS_8 = 8 * @param s 停止位(stopbits),如 SerialPort.STOPBITS_1 = 1 * @param p 校验位 (parity),如 SerialPort.PARITY_NONE = 0 * @return 打开的串口对象,打开失败时,返回 null */ public static final SerialPort openComPort(String portName, int b, int d, int s, int p) { CommPort commPort = null; try { //当没有传入可用的 com 口时,默认使用电脑中可用的 com 口中的第一个 if (portName == null || "".equals(portName)) { List<String> comPortList = findSystemAllComPort(); if (comPortList != null && comPortList.size() > 0) { portName = comPortList.get(0); } } logger.info("开始打开串口:portName=" + portName + ",baudrate=" + b + ",datebits=" + d + ",stopbits=" + s + ",parity=" + p); //通过端口名称识别指定 COM 端口 CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName); /** * open(String TheOwner, int i):打开端口 * TheOwner 自定义一个端口名称,随便自定义即可 * i:打开的端口的超时时间,单位毫秒,超时则抛出异常:PortInUseException if in use. * 如果此时串口已经被占用,则抛出异常:gnu.io.PortInUseException: Unknown Application */ commPort = portIdentifier.open(portName, 5000); /** * 判断端口是不是串口 * public abstract class SerialPort extends CommPort */ if (commPort instanceof SerialPort) { SerialPort serialPort = (SerialPort) commPort; /** * 设置串口参数:setSerialPortParams( int b, int d, int s, int p ) * b:波特率(baudrate) * d:数据位(datebits),SerialPort 支持 5,6,7,8 * s:停止位(stopbits),SerialPort 支持 1,2,3 * p:校验位 (parity),SerialPort 支持 0,1,2,3,4 * 如果参数设置错误,则抛出异常:gnu.io.UnsupportedCommOperationException: Invalid Parameter * 此时必须关闭串口,否则下次 portIdentifier.open 时会打不开串口,因为已经被占用 */ serialPort.setSerialPortParams(b, d, s, p); logger.info("打开串口 " + portName + " 成功..."); return serialPort; } else { logger.error("当前端口 " + commPort.getName() + " 不是串口..."); } } catch (NoSuchPortException e) { e.printStackTrace(); } catch (PortInUseException e) { logger.warn("串口 " + portName + " 已经被占用,请先解除占用..."); e.printStackTrace(); } catch (UnsupportedCommOperationException e) { logger.warn("串口参数设置错误,关闭串口,数据位[5-8]、停止位[1-3]、验证位[0-4]..."); e.printStackTrace(); if (commPort != null) {//此时必须关闭串口,否则下次 portIdentifier.open 时会打不开串口,因为已经被占用 commPort.close(); } } logger.error("打开串口 " + portName + " 失败..."); return null; } /** * 往串口发送数据 * * @param serialPort 串口对象 * @param orders 待发送数据 */ public static void sendDataToComPort(SerialPort serialPort, byte[] orders) { OutputStream outputStream = null; try { if (serialPort != null) { outputStream = serialPort.getOutputStream(); outputStream.write(orders); outputStream.flush(); logger.info("往串口 " + serialPort.getName() + " 发送数据:" + Arrays.toString(orders) + " 完成..."); } else { logger.error("gnu.io.SerialPort 为null,取消数据发送..."); } } catch (IOException e) { e.printStackTrace(); } finally { if (outputStream != null) { try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * 从串口读取数据 * * @param serialPort 要读取的串口 * @return 读取的数据 */ public static byte[] getDataFromComPort(SerialPort serialPort) { InputStream inputStream = null; byte[] data = null; try { if (serialPort != null) { inputStream = serialPort.getInputStream(); // 等待数据接收完成 Thread.sleep(500); // 获取可读取的字节数 int availableBytes = inputStream.available(); if (availableBytes > 0) { data = new byte[availableBytes]; int readBytes = inputStream.read(data); logger.info("从串口 " + serialPort.getName() + " 接收到数据:" + Arrays.toString(data) + " 完成..."); } else { logger.warn("从串口 " + serialPort.getName() + " 接收到空数据..."); } } else { logger.error("gnu.io.SerialPort 为null,取消数据接收..."); } } catch (IOException | InterruptedException e) { e.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } return data; } /** * 关闭串口 * * @param serialPort 待关闭的串口对象 */ public static void closeComPort(SerialPort serialPort) { if (serialPort != null) { serialPort.close(); logger.info("关闭串口 " + serialPort.getName()); } } /** * 16进制字符串转十进制字节数组 * 这是常用的方法,如某些硬件的通信指令就是提供的16进制字符串,发送时需要转为字节数组再进行发送 * * @param strSource 16进制字符串,如 "455A432F5600",每两位对应字节数组中的一个10进制元素 * 默认会去除参数字符串中的空格,所以参数 "45 5A 43 2F 56 00" 也是可以的 * @return 十进制字节数组, 如 [69, 90, 67, 47, 86, 0] */ public static byte[] hexString2Bytes(String strSource) { if (strSource == null || "".equals(strSource.trim())) { System.out.println("hexString2Bytes 参数为空,放弃转换."); return null; } strSource = strSource.replace(" ", ""); int l = strSource.length() / 2; byte[] ret = new byte[l]; for (int i = 0; i < l; i++) { ret[i] = Integer.valueOf(strSource.substring(i * 2, i * 2 + 2), 16).byteValue(); } return ret; } /** * 给串口设置监听 * * @param serialPort serialPort 要读取的串口 * @param listener SerialPortEventListener监听对象 * @throws TooManyListenersException 监听对象太多 */ public static void setListenerToSerialPort(SerialPort serialPort, SerialPortEventListener listener) throws TooManyListenersException { //给串口添加事件监听 serialPort.addEventListener(listener); //串口有数据监听 serialPort.notifyOnDataAvailable(true); //中断事件监听 serialPort.notifyOnBreakInterrupt(true); } }
Controller层
@GetMapping("/open") public String openSerialPort(@RequestParam() String port) throws NoSuchPortException, PortInUseException, UnsupportedCommOperationException, TooManyListenersException { port = "COM" + port; log.debug(port); SerialPort serialPort = remoteService.openSerialPortTest(port); return "Serial Port Opened"; } @PostMapping("/send") public String sendData(@RequestBody String data) throws TooManyListenersException, UnsupportedCommOperationException, NoSuchPortException, PortInUseException { remoteService.sendData(data); return "Data Sent"; } @GetMapping("/receive") public String receiveData() throws TooManyListenersException { return remoteService.getCurrentListener().getData(); } @GetMapping("/close") public String closeSerialPort() throws TooManyListenersException, NoSuchPortException, PortInUseException, UnsupportedCommOperationException { remoteService.closeSerialPort(); return "Serial Port Closed"; }
MySerialPortEventListener
public class MySerialPortEventListener implements SerialPortEventListener { private RedisCache redisCache; private Queue<String> dataQueue = new LinkedList<>(); private String latestData = ""; public MySerialPortEventListener(SerialPort serialPort, int eventType) throws TooManyListenersException { this.serialPort = serialPort; this.eventType = eventType; this.redisCache = new RedisCache(); //给串口添加事件监听 serialPort.addEventListener(this); //串口有数据监听 serialPort.notifyOnDataAvailable(true); //中断事件监听 serialPort.notifyOnBreakInterrupt(true); } private SerialPort serialPort; private int eventType; private static ConcurrentHashMap<Long, MySerialPortEventListener> instanceMap = new ConcurrentHashMap<>(); public static synchronized MySerialPortEventListener getInstance(SerialPort serialPort, int eventType) throws TooManyListenersException { long threadId = Thread.currentThread().getId(); MySerialPortEventListener instance = instanceMap.get(threadId); if (instance == null) { instance = new MySerialPortEventListener(serialPort, eventType); instanceMap.put(threadId, instance); } return instance; } public static ConcurrentHashMap<Long, MySerialPortEventListener> getInstanceMap() { return instanceMap; } @Override public void serialEvent(SerialPortEvent arg0) { if (arg0.getEventType() == SerialPortEvent.DATA_AVAILABLE) { // 数据通知 byte[] bytes = SerialPortTool.getDataFromComPort(serialPort); System.out.println("收到的数据长度:" + bytes.length); String str = new String(bytes); latestData = str; System.out.println("收到的数据:" + str); eventType = arg0.getEventType(); // 将事件类型赋值给eventType } } public String getData() { if (latestData != null) { String data = latestData; latestData = null; // 清空最新数据,避免重复获取 return data; } else { return ""; } } public int getEventType() { return eventType; } public SerialPort getSerialPort() { return serialPort; } }
impl
private SerialPort serialPort; @Override public SerialPort openSerialPort(String port) { serialPort = SerialPortTool.openComPort(port, 9600, 8, 1, 0); // 创建新线程来处理串口的打开操作 Thread thread = new Thread(new Runnable() { @Override public void run() { try { MySerialPortEventListener listener = MySerialPortEventListener.getInstance(serialPort, 1); } catch (TooManyListenersException e) { e.printStackTrace(); } } }); thread.start(); return serialPort; } @Override public void sendData(String senData) { String data = senData; // 获取当前线程的MySerialPortEventListener实例 MySerialPortEventListener listener = getCurrentListener(); if (listener != null) { SerialPortTool.sendDataToComPort(listener.getSerialPort(), data.getBytes()); } } @Override public SerialPort getSerialPort() { // 获取当前线程的MySerialPortEventListener实例 MySerialPortEventListener listener = getCurrentListener(); if (listener != null) { return listener.getSerialPort(); } else { return null; } } @Override // 获取当前线程的MySerialPortEventListener实例 public MySerialPortEventListener getCurrentListener() { long threadId = Thread.currentThread().getId(); MySerialPortEventListener listener = MySerialPortEventListener.getInstanceMap().get(threadId); return listener; } // 关闭串口测试 @Override public void closeSerialPort() { SerialPortTool.closeComPort(serialPort); }
现在我把发送数据,设置监听,获取数据单独拿出来了,但是获取不到数据了,我已经把listener对象作为一个成员变量保存在RemoteService类中了。
问题是每次请求数据时都使用不到同一个SerialPortEventListener对象,所以拿不到串口返回的数据
if (arg0.getEventType() == SerialPortEvent.DATA_AVAILABLE) { // 数据通知 byte[] bytes = SerialPortTool.getDataFromComPort(serialPort); System.out.println("收到的数据长度:" + bytes.length); System.out.println("收到的数据:" + new String(bytes));
因为 getCurrentListener() 方法返回了 null,导致在 receiveDataTest() 方法中调用 getData() 方法时出现了 NullPointerException。这可能是因为你没有在第二个用户访问 COM6 时创建一个新的 MySerialPortEventListener 实例,而是继续使用了第一个用户的实例,导致数据被覆盖或丢失。
二、成功方案
在这个示例中,我们在后端定义了一个MySerialPortEventListener类,每个用户访问串口时创建一个新的 MySerialPortEventListener 实例,并将其存储在一个 Map 中,以便在后续的请求中使用。这样每个用户都有自己的监听器实例,互不干扰。
串口监听工具
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.*; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * * 串口工具类 */ public class SerialPortTool { private static final Logger logger = LoggerFactory.getLogger(SerialPortTool.class);//slf4j 日志记录器 /** * 查找电脑上所有可用 com 端口 * * @return 可用端口名称列表,没有时 列表为空 */ public static final ArrayList<String> findSystemAllComPort() { /** * getPortIdentifiers:获得电脑主板当前所有可用串口 */ Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers(); ArrayList<String> portNameList = new ArrayList<>(); /** * 将可用串口名添加到 List 列表 */ while (portList.hasMoreElements()) { String portName = portList.nextElement().getName();//名称如 COM1、COM2.... portNameList.add(portName); } return portNameList; } /** * 打开电脑上指定的串口 * * @param portName 端口名称,如 COM1,为 null 时,默认使用电脑中能用的端口中的第一个 * @param b 波特率(baudrate),如 9600 * @param d 数据位(datebits),如 SerialPort.DATABITS_8 = 8 * @param s 停止位(stopbits),如 SerialPort.STOPBITS_1 = 1 * @param p 校验位 (parity),如 SerialPort.PARITY_NONE = 0 * @return 打开的串口对象,打开失败时,返回 null */ public static final SerialPort openComPort(String portName, int b, int d, int s, int p) { CommPort commPort = null; try { //当没有传入可用的 com 口时,默认使用电脑中可用的 com 口中的第一个 // if (portName == null || "".equals(portName)) { // List<String> comPortList = findSystemAllComPort(); // if (comPortList != null && comPortList.size() > 0) { // portName = comPortList.get(0); // } // } logger.info("开始打开串口:portName=" + portName + ",baudrate=" + b + ",datebits=" + d + ",stopbits=" + s + ",parity=" + p); //通过端口名称识别指定 COM 端口 CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName); /** * open(String TheOwner, int i):打开端口 * TheOwner 自定义一个端口名称,随便自定义即可 * i:打开的端口的超时时间,单位毫秒,超时则抛出异常:PortInUseException if in use. * 如果此时串口已经被占用,则抛出异常:gnu.io.PortInUseException: Unknown Application */ commPort = portIdentifier.open(portName, 5000); /** * 判断端口是不是串口 * public abstract class SerialPort extends CommPort */ if (commPort instanceof SerialPort) { SerialPort serialPort = (SerialPort) commPort; /** * 设置串口参数:setSerialPortParams( int b, int d, int s, int p ) * b:波特率(baudrate) * d:数据位(datebits),SerialPort 支持 5,6,7,8 * s:停止位(stopbits),SerialPort 支持 1,2,3 * p:校验位 (parity),SerialPort 支持 0,1,2,3,4 * 如果参数设置错误,则抛出异常:gnu.io.UnsupportedCommOperationException: Invalid Parameter * 此时必须关闭串口,否则下次 portIdentifier.open 时会打不开串口,因为已经被占用 */ serialPort.setSerialPortParams(b, d, s, p); logger.info("打开串口 " + portName + " 成功..."); return serialPort; } else { logger.error("当前端口 " + commPort.getName() + " 不是串口..."); return null; } } catch (NoSuchPortException e) { e.printStackTrace(); } catch (PortInUseException e) { logger.warn("串口 " + portName + " 已经被占用,请先解除占用..."); e.printStackTrace(); } catch (UnsupportedCommOperationException e) { logger.warn("串口参数设置错误,关闭串口,数据位[5-8]、停止位[1-3]、验证位[0-4]..."); e.printStackTrace(); if (commPort != null) {//此时必须关闭串口,否则下次 portIdentifier.open 时会打不开串口,因为已经被占用 commPort.close(); } } logger.error("打开串口 " + portName + " 失败..."); return null; } /** * 往串口发送数据 * * @param serialPort 串口对象 * @param orders 待发送数据 * @return */ public static String sendDataToComPort(SerialPort serialPort, byte[] orders) { OutputStream outputStream = null; try { if (serialPort != null) { outputStream = serialPort.getOutputStream(); outputStream.write(orders); outputStream.flush(); logger.info("往串口 " + serialPort.getName() + " 发送数据:" + Arrays.toString(orders) + " 完成"); return "往串口 " + serialPort.getName() + " 发送数据:" + Arrays.toString(orders) + " 完成"; } else { logger.error("gnu.io.SerialPort 为null,取消数据发送..."); return "串口为空,取消数据发送"; } } catch (IOException e) { e.printStackTrace(); } finally { if (outputStream != null) { try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } return null; } /** * 向串口发送数据 */ public static String sendDataToPort(SerialPort serialPort, byte[] data) { OutputStream outputStream = null; try { if (serialPort != null) { outputStream = serialPort.getOutputStream(); outputStream.write(data); outputStream.flush(); logger.info("Successfully sent data to serial port " + serialPort.getName() + ": " + Arrays.toString(data)); return "Successfully sent data to serial port " + serialPort.getName(); } else { logger.error("Failed to send data to serial port: serial port is null"); return null; } } catch (IOException e) { logger.error("Failed to send data to serial port " + serialPort.getName() + ": " + e.getMessage()); } finally { if (outputStream != null) { try { outputStream.close(); } catch (IOException e) { logger.error("Failed to close output stream for serial port " + serialPort.getName() + ": " + e.getMessage()); } } } return null; } /** * 从串口获取数据 */ public static byte[] getDataFromPort(SerialPort serialPort) { InputStream inputStream = null; byte[] data = null; try { if (serialPort != null) { inputStream = serialPort.getInputStream(); // 等待数据接收完成 Thread.sleep(500); // 获取可读取的字节数 int availableBytes = inputStream.available(); if (availableBytes > 0) { data = new byte[availableBytes]; int readBytes = inputStream.read(data); logger.info("从串口 " + serialPort.getName() + " 接收到数据:" + Arrays.toString(data) + " 完成..."); } else { logger.warn("从串口 " + serialPort.getName() + " 接收到空数据..."); } } else { logger.error("gnu.io.SerialPort 为null,取消数据接收..."); } } catch (IOException | InterruptedException e) { e.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } return data; } /** * 关闭串口 * * @param serialPort 待关闭的串口对象 */ public static void closeSerialPort(SerialPort serialPort) { if (serialPort != null) { serialPort.close(); logger.info("Successfully closed serial port " + serialPort.getName()); } else { logger.error("Failed to close serial port: serial port is null"); } } /** * 16进制字符串转十进制字节数组 * 这是常用的方法,如某些硬件的通信指令就是提供的16进制字符串,发送时需要转为字节数组再进行发送 * * @param strSource 16进制字符串,如 "455A432F5600",每两位对应字节数组中的一个10进制元素 * 默认会去除参数字符串中的空格,所以参数 "45 5A 43 2F 56 00" 也是可以的 * @return 十进制字节数组, 如 [69, 90, 67, 47, 86, 0] */ public static byte[] hexString2Bytes(String strSource) { if (strSource == null || "".equals(strSource.trim())) { System.out.println("hexString2Bytes 参数为空,放弃转换."); return null; } strSource = strSource.replace(" ", ""); int l = strSource.length() / 2; byte[] ret = new byte[l]; for (int i = 0; i < l; i++) { ret[i] = Integer.valueOf(strSource.substring(i * 2, i * 2 + 2), 16).byteValue(); } return ret; } /** * 给串口设置监听 */ public static void setListenerToSerialPort(SerialPort serialPort, SerialPortEventListener listener) throws TooManyListenersException { // serialPort.removeEventListener(); //给串口添加事件监听 serialPort.addEventListener(listener); //串口有数据监听 serialPort.notifyOnDataAvailable(true); //中断事件监听 serialPort.notifyOnBreakInterrupt(true); } }
Controller层
@Slf4j @RestController @RequestMapping("/remote/remote") public class RemoteController { @Autowired private IRemoteService remoteService; /** * 打开串口 */ @GetMapping("/open") public AjaxResult openSerialPort(@RequestParam() String port) { String portName = "COM" + port; try { SerialPort serialPort = remoteService.openSerialPort(portName); if (serialPort == null) { return AjaxResult.error("打开串口失败"); } return AjaxResult.success("打开串口成功"); } catch (Exception e) { return AjaxResult.error("打开串口失败: " + e.getMessage()); } } /** * 向串口发送数据 */ @GetMapping("/send") public AjaxResult sendDataToPort(@RequestParam() String data, @RequestParam() String port) throws TooManyListenersException, UnsupportedCommOperationException, NoSuchPortException, PortInUseException { String portName = "COM" + port; try { String result = remoteService.sendDataToPort(data, portName); if (result != null) { return AjaxResult.success("发送数据成功"); } else { return AjaxResult.error("发送数据失败"); } } catch (Exception e) { return AjaxResult.error("发送数据失败: " + e.getMessage()); } } /** * 从串口接收数据 */ @GetMapping("/receive") public AjaxResult receiveDataFromPort(@RequestParam() String port) { String portName = "COM" + port; try { String data = remoteService.receiveDataFromPort(portName); return AjaxResult.success(data); } catch (Exception e) { return AjaxResult.error("接收串口数据失败: " + e.getMessage()); } } /** * 关闭串口 */ @GetMapping("/close") public AjaxResult closeSerialPort(@RequestParam() String port) { String portName = "COM" + port; try { remoteService.closeSerialPort(portName); return AjaxResult.success("关闭串口成功"); } catch (Exception e) { return AjaxResult.warn(e.getMessage()); } } }
MySerialPortEventListener
package com.ruoyi.remote.burn; import gnu.io.SerialPort; import gnu.io.SerialPortEvent; import gnu.io.SerialPortEventListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.TooManyListenersException; /** * 串口监听器 * * @author Yeats */ public class MySerialPortEventListener implements SerialPortEventListener { private String latestData = ""; private SerialPort serialPort; private int eventType; private static final Logger logger = LoggerFactory.getLogger(SerialPortTool.class);//slf4j 日志记录器 /** * 构造串口监听器 * * @param serialPort * @param eventType * @throws TooManyListenersException */ public MySerialPortEventListener(SerialPort serialPort, int eventType) throws TooManyListenersException { this.serialPort = serialPort; this.eventType = eventType; //给串口添加事件监听 serialPort.addEventListener(this); //串口有数据监听 serialPort.notifyOnDataAvailable(true); //中断事件监听 serialPort.notifyOnBreakInterrupt(true); } /** * 串口监听事件 * * @param arg0 * @throws TooManyListenersException */ @Override public void serialEvent(SerialPortEvent arg0) { if (arg0.getEventType() == SerialPortEvent.DATA_AVAILABLE) { // 数据通知 byte[] bytes = SerialPortTool.getDataFromPort(serialPort); String str = new String(bytes); latestData = str; eventType = arg0.getEventType(); // 将事件类型赋值给eventType logger.info("收到的数据长度:" + bytes.length); logger.info("收到的数据:" + str); } } /** * 获取最新数据 * * @return 最新数据 */ public String getData() { if (latestData != null) { // 清空最新数据,避免重复获取 String data = " " + latestData; latestData = null; return data; } else { return ""; } } /** * 获取串口对象 * * @return 串口对象 */ public SerialPort getSerialPort() { return serialPort; } }
impl
@Service public class RemoteServiceImpl implements IRemoteService { private static final Logger logger = LoggerFactory.getLogger(SerialPortTool.class);//slf4j 日志记录器 /** * 查找电脑上所有可用 com 端口 * * @return 可用端口名称列表,没有时 列表为空 */ public static final ArrayList<String> findSystemAllComPort() { /** * getPortIdentifiers:获得电脑主板当前所有可用串口 */ Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers(); ArrayList<String> portNameList = new ArrayList<>(); /** * 将可用串口名添加到 List 列表 */ while (portList.hasMoreElements()) { String portName = portList.nextElement().getName();//名称如 COM1、COM2.... portNameList.add(portName); } return portNameList; } private Map<String, MySerialPortEventListener> listenerMap = new HashMap<>(); /** * 打开串口 */ @Override public SerialPort openSerialPort(String portName) { SerialPort serialPort = SerialPortTool.openComPort(portName, 9600, 8, 1, 0); if (serialPort == null) { throw new RuntimeException("打开串口 " + portName + " 失败"); } // 创建新线程来处理串口的打开操作 Thread thread = new Thread(() -> { try { MySerialPortEventListener listener = new MySerialPortEventListener(serialPort, 1); listenerMap.put(portName, listener); } catch (TooManyListenersException e) { e.printStackTrace(); } }); thread.start(); return serialPort; } /** * 向串口发送数据 */ @Override public String sendDataToPort(String senData, String portName) { MySerialPortEventListener listener = listenerMap.get(portName); if (listener != null) { return SerialPortTool.sendDataToPort(listener.getSerialPort(), senData.getBytes()); } else { throw new RuntimeException("发送串口失败,未找到该串口"); } } /** * 从串口接收数据 */ @Override public String receiveDataFromPort(String portName) throws RuntimeException { MySerialPortEventListener listener = listenerMap.get(portName); if (listener != null) { return listener.getData(); } else { return ""; } } /** * 关闭串口 */ @Override public void closeSerialPort(String portName) throws RuntimeException { MySerialPortEventListener listener = listenerMap.get(portName); if (listener != null) { SerialPortTool.closeSerialPort(listener.getSerialPort()); listenerMap.remove(portName); } else { throw new RuntimeException("当前串口已关闭"); } } }
import gnu.io.SerialPort; public interface IRemoteService { /** * 打开串口 */ public SerialPort openSerialPort(String portName); /** * 向串口发送数据 */ public String sendDataToPort(String senData, String portName); /** * 从串口接收数据 */ public String receiveDataFromPort(String portName); /** * 关闭串口 */ public void closeSerialPort(String portName); }
前端Api
// 发送数据到串口 export function sendDataToPort(data) { return request({ url: '/remote/remote/send', method: 'get', params: data, }) } // 接收数据从串口 export function receiveDataFromPort(data) { return request({ url: '/remote/remote/receive', method: 'get', params: data, }) } // 打开串口 export function openSerialPort(data) { return request({ url: '/remote/remote/open', method: 'get', params: data, }) } // 关闭串口 export function closeSerialPort(data) { return request({ url: '/remote/remote/close', method: 'get', params: data, }) }
vue前端
data() { return { sendDataString: "", receivedDataString: "", timerId: null, }; }, methods: { // 在openSerialPort方法中调用后端接口 openSerialPort() { let serialPort = { port: this.port51 }; // 调用后端接口打开串口 openSerialPort(serialPort).then((response) => { // 如果成功打开串口,则开启定时器 if (response.code == 200) { this.$message.success(response.msg); this.startTimer(); } else { this.$message.error(response.msg); } }); }, // 关闭串口 closeSerialPort() { let serialPort = { port: this.port51 }; this.stopTimer(); closeSerialPort(serialPort).then((response) => { if (response.code == 200) { this.sendDataString = ""; this.receivedDataString = ""; this.$message.success(response.msg); } else { this.$message.error(response.msg); } }); }, // 清空接收数据 clearReceive() { this.receivedDataString = ""; }, // 发送数据 sendDataToPort() { this.isSending = true; if (!this.sendDataString || this.sendDataString.trim().length === 0) { this.$message.warning("发送数据不能为空"); this.isSending = false; } else { let serialPort = { data: this.sendDataString, port: this.port51 }; sendDataToPort(serialPort).then((response) => { if (response.code == 200) { this.isSending = false; this.$message.success(response.msg); } else { this.isSending = false; this.$message.error(response.msg); } }); } }, // 定义一个函数,用于开启定时器 startTimer() { // 如果定时器已经开启,则直接返回 if (this.timerId) { return; } let serialPort = { port: this.port51 }; // 开启定时器,每隔100毫秒请求一次数据 this.timerId = setInterval(() => { receiveDataFromPort(serialPort).then((response) => { if (response.code == 200) { // 将接收到的数据存储到全局变量中 this.receivedDataString += response.msg; this.isOpenPort = true; this.isOpening = false; } else { this.$message.error(response.msg); } }); }, 1000); }, // 定义一个函数,用于停止定时器 stopTimer() { // 如果定时器已经停止,则直接返回 if (!this.timerId) { return; } clearInterval(this.timerId); this.timerId = null; }, // 在beforeDestroy生命周期钩子中清除定时器 beforeDestroy() { this.closeSerialPort(); }, },