Java串口通信技术探究3:RXTX库线程 优化系统性能的SerialPortEventListener类

目录

  • 一、失败方案
    • 串口监听工具
    • Controller层
    • MySerialPortEventListener
    • impl
  • 二、成功方案
    • 串口监听工具
    • Controller层
    • MySerialPortEventListener
    • impl
    • 前端Api

在之前的文章中,我们讨论了使用单例模式的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;}@Overridepublic 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;@Overridepublic SerialPort openSerialPort(String port) {serialPort = SerialPortTool.openComPort(port, 9600, 8, 1, 0);// 创建新线程来处理串口的打开操作Thread thread = new Thread(new Runnable() {@Overridepublic void run() {try {MySerialPortEventListener listener = MySerialPortEventListener.getInstance(serialPort, 1);} catch (TooManyListenersException e) {e.printStackTrace();}}});thread.start();return serialPort;}@Overridepublic void sendData(String senData) {String data = senData;// 获取当前线程的MySerialPortEventListener实例MySerialPortEventListener listener = getCurrentListener();if (listener != null) {SerialPortTool.sendDataToComPort(listener.getSerialPort(), data.getBytes());}}@Overridepublic 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;}// 关闭串口测试@Overridepublic 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 {@Autowiredprivate 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*/@Overridepublic 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(); // 将事件类型赋值给eventTypelogger.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<>();/*** 打开串口*/@Overridepublic 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;}/*** 向串口发送数据*/@Overridepublic 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("发送串口失败,未找到该串口");}}/*** 从串口接收数据*/@Overridepublic String receiveDataFromPort(String portName) throws RuntimeException {MySerialPortEventListener listener = listenerMap.get(portName);if (listener != null) {return listener.getData();} else {return "";}}/*** 关闭串口*/@Overridepublic 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();},},

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/674788.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【代码随想录26】332.重新安排行程 51.N皇后 37.解数独

目录 332.重新安排行程题目描述参考代码 51.N皇后题目描述参考代码 37.解数独题目描述参考代码 332.重新安排行程 题目描述 给你一份航线列表 tickets &#xff0c;其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 所有这些机…

09-错误处理

上一篇&#xff1a;08-常用集合(容器) 在软件中&#xff0c;错误是一个不争的事实&#xff0c;因此 Rust 提供了许多功能来处理出错的情况。在许多情况下&#xff0c;Rust 要求您在编译代码之前承认出错的可能性并采取一些措施。这一要求可确保您在将代码部署到生产环境之前发现…

JRebel激活-nginx版本

nginx转发流量&#xff08;代替其他网上说的那个工具&#xff09; proxy_pass http://idea.lanyus.com; 工具激活 填写内容说明&#xff1a; 第一行的激活网址是&#xff1a;http://127.0.0.1:8888/ 正确的GUID。GUID 可以通过专门的网站来生成&#xff08;点击打开&#…

kettle控件-复制记录到结果/ 从结果获取记录的使用

在数据采集过程中&#xff0c;遇到对方数据传送不及时的情况&#xff0c;导致数据漏采集&#xff0c;需要手工反复补采。为了解决这一问题&#xff0c;可以利用kettle的复制记录到结果/从结果获取记录控件。 job的整个流程如下&#xff1a; 设置变量&#xff1a; 创建目录: ge…

STM32输出PWM波控制180°舵机

时间记录&#xff1a;2024/2/8 一、PWM介绍 &#xff08;1&#xff09;脉冲宽度调制 &#xff08;2&#xff09;占空比&#xff1a;高电平时间占整个周期时间的比例 &#xff08;3&#xff09;STM32通过定时器实现PWM时具有两种模式 PWM1模式&#xff1a;向上计数模式下&…

软件测试工程师——缺陷(一篇足以)

目录 定义 缺陷的类型 缺陷的严重程度 缺陷的状态 缺陷的根源 ​缺陷的来源 缺陷的起源 缺陷的生命周期 缺陷的识别 缺陷报告模板 编写缺陷报告的目的 缺陷报告编写的准则 缺陷描述的准则 定义 1. 软件未实现产品说明书中所提及的功能 2. 软件实现了产品说明书中…

Python入门知识点分享——(十九)私有属性和方法

上文我们介绍了面向对象的基础知识&#xff0c;了解了类和对象的联系和语法&#xff0c;这次我们就紧接着来介绍面向对象中的私有特点——私有属性和私有方法。 私有属性&#xff0c;顾名思义是指不能在类的外部被使用或直接访问的属性。私有属性严格意义上来说并不能算做第三…

第63讲个人中心用户信息动态显示实现

个人中心页面实现 &#xff08;补充前面的取消按钮逻辑&#xff09; 个人中心用户信息动态显示实现 index.wxml <view class"user_info"><!-- 用户背景信息开始 --><view class"user_info_bg"><view class"user_info_wrap&q…

echarts图表插件

图表组件 ECharts&#xff0c;全称为Enterprise Charts&#xff0c;是一个使用JavaScript实现的开源可视化库。它主要用于数据可视化领域&#xff0c;能够方便地创建出直观、交互性强的图表。ECharts由百度团队开发&#xff0c;目前是Apache的顶级项目之一。ECharts支持的图表…

JVM调优(Window下)

1、编写代码&#xff0c;像下面代码这样&#xff0c;产生OOM&#xff0c; private static final Integer K 1024;/*** 死循环&#xff0c;验证JVM调优* return*/GetMapping(value "/deadLoop")public void deadLoop(){int size K * K * 8;List<byte[]> lis…

Java后端技术助力,党员学习平台更稳定

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

C++奇淫巧计:如何快速观察内存分配

需求 在不严肃的场景下&#xff0c;你想测试、跟踪自己的代码的内存分配&#xff0c;粗略评估有无错误的、意外的行为&#xff0c;怎么做&#xff1f; 代码 很简单&#xff0c;直接重写 new 操作符。 #include <iostream>static int malloc_count 0;void* operator …

38. C++ 引用的本质

1. C 引用的本质 1.1 引用的底层实现方式 引用被称为变量的别名&#xff0c;它不能脱离被引用对象独立存在&#xff0c;这是在高级语言层面的概念和理解&#xff0c;并未揭示引用的实现方式。常见错误说法是“引用“自身不是一个变量&#xff0c;甚至编译器可以不为引用分配空…

Day 41 | 动态规划 343. 整数拆分 、 96.不同的二叉搜索树

343. 整数拆分 题目 文章讲解 视频讲解 思路&#xff1a;不需要考虑正整数为1的情况。 dp[i]表示正整数i拆分后结果的最大乘积&#xff0c;递推公式中 j 表示拆分的正整数&#xff0c;最大不会超过 i-j &#xff0c;否则会轮回。dp[i-j]是正整数 i-j 拆分后结果最大乘积。 c…

堆排及时间复杂度分析

箴言: 初始阶段&#xff0c;不需要去纠结那一种更优美&#xff0c;非要找出那一种是最好的&#xff0c;其实能解决问题的就是好办法。 一&#xff0c;常见排序时间复杂度 冒泡快排归并堆排桶排时间O(n^2)O(nlogn)O(nlogn)O(nlogn)kn空间O(1)O(1)O(nlogn)O(1)kn 二&#xff…

Dijkstra求最短路(一) 朴素版本-算法基础-数据结构(二)

给定一个 n 个点 m 条边的有向图&#xff0c;图中可能存在重边和自环&#xff0c;所有边权均为正值。 请你求出 1 号点到 n 号点的最短距离&#xff0c;如果无法从 1 号点走到 n 号点&#xff0c;则输出 −1。 输入格式 第一行包含整数 n 和 m。 接下来 m 行每行包含三个整数…

Linux介绍和命令使用

目录 目录 一、Linux简介 1.1 主流操作系统 1.2 Linux 发展历史 1.3 Linux系统版本 二、Linux安装 三、Linux 目录结构 四、Linux常用命令 4.1 基础常用命令说明 4.2 Linux 命令使用技巧 4.3 Linux 命令格式 4.4 进阶重点常用命令 4.4.1 拷贝移动命令 4.4.2 打包…

[AIGC] 开源流程引擎哪个好,如何选型?

开源流程引擎是指一种自动化的工作流解决方案&#xff0c;它可以帮助你管理和协调你的业务流程和决策。但是&#xff0c;在开源世界里&#xff0c;有许多不同的流程引擎可以选择。因此&#xff0c;如何选择适合你的开源流程引擎&#xff0c;是一个具有挑战性和价值的话题。 文章…

AI嵌入式K210项目(26)-二维码识别

文章目录 前言一、什么是二维码&#xff1f;二、实验准备三、实验过程四、API接口总结 前言 本章介绍基于机器视觉实现二维码识别&#xff0c;主要包含两个过程&#xff0c;首先检测图像中是否有二维码&#xff0c;如果有则框出并打印二维码信息&#xff1b; 一、什么是二维码…

STM32——LCD(1)认识

目录 一、初识LCD 1. LCD介绍 2. 显示器的分类 3. 像素 4. LED和OLED显示器 5. 显示器的基本参数 &#xff08;1&#xff09;像素 &#xff08;2&#xff09;分辨率 &#xff08;3&#xff09;色彩深度 &#xff08;4&#xff09;显示器尺寸 &#xff08;5&#xff…