注意:1.JSerialComm库的发送和响应数据有两种方式,第一是监听器,第二是轮询模式。此文章使用的是第二种方式,即每处理一个事件,都需要调用响应和发送的方法。
2.JSerialComm库发送和响应数据不适用inputStream和OutPutStream流,它是通过WriteBytes和readBytes来发送和响应数据的。
1.POM文件的导入
<dependency> <groupId>com.fazecast</groupId> <artifactId>jSerialComm</artifactId> <version>2.10.5</version> </dependency>
2.写一个实体类
@Component @AllArgsConstructor @NoArgsConstructor @Data public class SerialEntity { @ApiModelProperty(value = "端口名称") private String PortName = "COM9"; @ApiModelProperty(value = "请求指令") private String powerOffCommand; private int BAUDRATE = 2400; private int DATALENGTH = 8; private int STOPBIT = 1; private String PARITY = "NONE" @ApiModelProperty(name="过载故障") private char Overloadwarning; @ApiModelProperty(name="过温故障") private char Overtemperature; @ApiModelProperty(name="电池过低故障") private char Batterylow; @ApiModelProperty(name="省电模式名称") private String Batterymode; @ApiModelProperty(name="剩余电量") private String BatteryVoltage; @ApiModelProperty(name="经纬度") private String Latitude; @ApiModelProperty(name="经纬度") private String longitude; private int BAUDRATE1 = 115200; }
3.common工具类
package com.ruoyi.equipment.serialportcommon; 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"; }
4. SerialUtil配置类
package com.ruoyi.equipment.serialportcommon; import com.fazecast.jSerialComm.SerialPort; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class SerialUtil { private SerialPort serialPort; /** * 转为 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.NO_PARITY; } else if (common.ODD.equals(checkBit)) { return SerialPort.ODD_PARITY; } else if (common.EVEN.equals(checkBit)) { return SerialPort.EVEN_PARITY; } else { return SerialPort.NO_PARITY; } } public static byte[] readFromPort(SerialPort serialPort) { serialPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 100, 0); byte[] bytes = {}; try { // 缓冲区大小为一个字节 byte[] readBuffer = new byte[1]; int bytesNum = serialPort.readBytes(readBuffer,readBuffer.length); System.out.println("readBytes:"+bytesNum); while (bytesNum > 0) { bytes = concat(bytes, readBuffer); bytesNum = serialPort.readBytes(readBuffer,readBuffer.length); } } catch (Exception e) { e.printStackTrace(); } return bytes; } 此方法是基于经纬度接口接受数据的方法 public static byte[] readFromPort1(SerialPort serialPort) { serialPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 1000, 0); byte[] bytes = {}; try { // 缓冲区大小为一个字节 byte[] readBuffer = new byte[1]; int bytesNum = serialPort.readBytes(readBuffer,readBuffer.length); System.out.println("readBytes:"+bytesNum); for (int i =0 ; i < 64;i++) { bytes = concat(bytes, readBuffer); bytesNum = serialPort.readBytes(readBuffer, readBuffer.length); } } catch (Exception 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; } }
5.RsConfig功能实现类
代码阐述:
(1):打开串口并且设置对应的波特率等参
(2):关闭串口
(3):发送命令
(4):响应数据:readdata方法将获取到的ASCII码转为为字符串,取第7,8,12个字符
readdata1方法将获取到的ASCII码转为字符串,并按空格分割保存在数组,取 第1个数组
readdata2同上
readdata4方法获取经纬度信息,注意经纬度接收到的数据是一直显示,所以
获取到你想要的数据时就关闭串口,再对获取到的数据进行处理。
@Slf4j @Component public class Rs232Config { // 定义一个常量,用于表示延迟时间,单位为毫秒 /* private static final int DELAY_TIME = 1000;*/ private SerialUtil serialUtil; /** * 缓存端口实例 * 使用ConcurrentHashMap以支持并发访问 */ private Map<String, SerialPort> serialPortMap = new ConcurrentHashMap<>(16); public boolean closePort(SerialEntity serial) { // 从map中移除并获取对应的SerialPort实例 SerialPort serialPort = serialPortMap.remove(serial.getPortName()); if (null != serialPort) { // 关闭串口 serialPort.closePort(); System.out.println("关闭串口成功"); } return false; } public boolean openPort(SerialEntity serial) { if (serial == null) { log.error("serial is null"); return false; } String portName = serial.getPortName(); System.out.println("端口号: " + portName); // 尝试打开串口 SerialPort serialPort = SerialPort.getCommPort(portName); System.out.println("串口一打开"+serialPort); if (serialPort.isOpen()) { System.err.println("Error: Port is already open"); return false; } // 从serial对象中获取串口参数(这里假设serial对象包含了这些参数) int baudRate = serial.getBAUDRATE(); int dataBits = serial.getDATALENGTH(); int stopBits = serial.getSTOPBIT(); // 假设有一个转换方法 int parity = SerialUtil.getParity(serial.getPARITY());// 假设有一个转换方法 // 打开串口并设置参数 serialPort.setComPortParameters(baudRate, dataBits, stopBits, parity); serialPortMap.put(portName, serialPort); return true; } public boolean sendData(String portId, String message) { // 假设 serialPortMap 是一个存储了 SerialPort 对象的 Map SerialPort serialPort = serialPortMap.get(portId); System.out.println("serialPort: " + serialPort); // 检查串口是否已经打开 if (serialPort == null || !serialPort.openPort()) { throw new RuntimeException("Serial port not found for ID: " + portId); } try { byte[] bytes = message.getBytes(StandardCharsets.UTF_8); // 使用 JSerialComm 的 writeBytes 方法发送数据 int numBytesWritten = serialPort.writeBytes(bytes, bytes.length); System.out.println(numBytesWritten); // 检查是否成功发送了所有字节 if (numBytesWritten == bytes.length) { } else { // 没有成功发送所有字节,可以根据需要处理这种情况 } } catch (Exception e) { throw new RuntimeException(e); } return true; }public SerialEntity readData(String portId) { SerialEntity serial = new SerialEntity(); SerialPort serialPort = serialPortMap.get(portId); byte[] bytes = serialUtil.readFromPort(serialPort); System.out.println("bytes" + bytes); System.out.println("bytes: "); // 创建一个StringBuilder来构建字符串,这比使用String连接更高效 StringBuilder sb = new StringBuilder(); for (byte b : bytes) { // 直接将字节转换为字符(假设是ASCII编码) char asciiChar = (char) b; sb.append(asciiChar); } System.out.println("sb: " + sb); // 将StringBuilder转换为String并打印 String resultString = sb.toString(); System.out.println("Received string: " + resultString); String[] strArray = resultString.split("\\s+"); char seven = resultString.charAt(7); char eight = resultString.charAt(8); char tweleve = resultString.charAt(12); serial.setBatterylow(seven); serial.setOverloadwarning(eight); serial.setOvertemperature(tweleve); System.out.println(serial.getBatterylow()); System.out.println(serial.getOverloadwarning()); System.out.println(serial.getOvertemperature()); return serial; } public SerialEntity readData1(String portId) { SerialEntity serial = new SerialEntity(); SerialPort serialPort = serialPortMap.get(portId); byte[] bytes = serialUtil.readFromPort(serialPort); System.out.println("bytes" + bytes); System.out.println("bytes: "); // 创建一个StringBuilder来构建字符串,这比使用String连接更高效 StringBuilder sb = new StringBuilder(); for (byte b : bytes) { // 直接将字节转换为字符(假设是ASCII编码) char asciiChar = (char) b; sb.append(asciiChar); } // 将StringBuilder转换为String并打印 String resultString = sb.toString(); System.out.println("Received string: " + resultString); String[] strArray = resultString.split("\\s+"); // 检查数组长度以确保有足够的部分 if (strArray.length > 1) { // 获取第二个部分(索引为1) String firstPart = strArray[2]; serial.setBatteryVoltage(firstPart); System.out.println(serial.getBatteryVoltage()); } else { System.out.println("Not enough parts in the string."); } return serial; } public SerialEntity readData2(String portId) { SerialEntity serial = new SerialEntity(); SerialPort serialPort = serialPortMap.get(portId); byte[] bytes = serialUtil.readFromPort(serialPort); System.out.println("bytes" + bytes); System.out.println("bytes: "); // 创建一个StringBuilder来构建字符串,这比使用String连接更高效 StringBuilder sb = new StringBuilder(); for (byte b : bytes) { // 直接将字节转换为字符(假设是ASCII编码) char asciiChar = (char) b; sb.append(asciiChar); } // 将StringBuilder转换为String并打印 String resultString = sb.toString(); System.out.println("Received string: " + resultString); String[] strArray = resultString.split("\\s+"); // 检查数组长度以确保有足够的部分 if (strArray.length > 0) { // 获取第二个部分(索引为1) String firstPart = strArray[0]; serial.setBatterymode(firstPart); System.out.println(serial.getBatterymode()); } else { System.out.println("Not enough parts in the string."); } return serial; } public SerialEntity readData4(String portId) { SerialEntity serial = new SerialEntity(); SerialPort serialPort = serialPortMap.get(portId); byte[] bytes = serialUtil.readFromPort1(serialPort); System.out.println("bytes" + bytes); System.out.println("bytes: "); // 创建一个StringBuilder来构建字符串,这比使用String连接更高效 StringBuilder sb = new StringBuilder(); serialPort.closePort(); for (byte b : bytes) { // 直接将字节转换为字符(假设是ASCII编码) char asciiChar = (char) b; sb.append(asciiChar); } // 将StringBuilder转换为String并打印 String resultString = sb.toString(); System.out.println("Received string: " + resultString); String[] strArray = resultString.split(","); System.out.println("strArray: " + strArray); // 检查数组长度以确保有足够的部分 if (strArray.length > 1) { // 获取第二个部分(索引为1) String firstPart = strArray[3]; serial.setLatitude(firstPart); String twoPart = strArray[5]; serial.setLongitude(twoPart); /* String two = strArray[6]; serial.setBatteryVoltage(two); */ System.out.println(serial.getLatitude()); System.out.println(serial.getLongitude()); } else { System.out.println("Not enough parts in the string."); } return serial; }
}
6.UpsManager类,逻辑处理类
package com.ruoyi.equipment.config; import com.ruoyi.equipment.entity.SerialEntity; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @Slf4j @Component public class UPSManager { // 使用Spring的自动装配特性注入Rs232Config的实例 private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); @Autowired private Rs232Config rs232Config; // 关闭UPS连接的端口,并发送关机命令 public boolean closePort(SerialEntity serial) { try { if (rs232Config != null) { rs232Config.openPort(serial); boolean closeport = rs232Config.sendData(serial.getPortName(), serial.getPowerOffCommand()); System.out.println("closeport" + closeport); boolean portclosed = rs232Config.closePort(serial); if (portclosed) { System.out.println("已发送指令"); } } else { System.out.println("rs232Config is null"); return false; } } catch (RuntimeException e) { e.printStackTrace(); return false; } return true; } public boolean openPort(SerialEntity serial) { if (serial == null) { System.out.println("SerialEntity 对象为空,无法打开端口"); return false; } if (rs232Config == null) { System.out.println("rs232Config 对象为空,无法操作端口" + rs232Config); return false; } boolean portOpened = rs232Config.openPort(serial); if (portOpened) { rs232Config.sendData(serial.getPortName(), serial.getPowerOffCommand()); System.out.println("已发送指令"); } else { System.out.println("端口未打开"); return false; } rs232Config.closePort(serial); return true; } // 获取UPS的当前状态 public SerialEntity CurrentStatus(SerialEntity serial) { SerialEntity SS = new SerialEntity(); rs232Config.openPort(serial); boolean m= rs232Config.sendData(serial.getPortName(), serial.getPowerOffCommand()); System.out.println("发送命令成功"+m); System.out.println("222222"); System.out.println( rs232Config.sendData(serial.getPortName(), serial.getPowerOffCommand())); SS = rs232Config.readData(serial.getPortName()); rs232Config.closePort(serial); return SS; } public SerialEntity ReaminBatteryTime (SerialEntity serial) { SerialEntity SS = new SerialEntity(); // 设置UPS电池剩余时间请求命令 /* statusRequestCommand = "F\r\n";*/ try { Thread.sleep(1200); rs232Config.openPort(serial); rs232Config.sendData(serial.getPortName(), serial.getPowerOffCommand()); SS = rs232Config.readData1(serial.getPortName()); rs232Config.closePort(serial); // 这里只是模拟了一个成功的请求,并返回true } catch (InterruptedException e) { System.out.println("线程被中断"); } return SS; } public SerialEntity CurrentMode (SerialEntity serial) { SerialEntity SS = new SerialEntity(); try { Thread.sleep(1800); // 设置UPS当前模式请求命令 rs232Config.openPort(serial); rs232Config.sendData(serial.getPortName(), serial.getPowerOffCommand()); SS = rs232Config.readData2(serial.getPortName()); rs232Config.closePort(serial); // 这里只是模拟了一个成功的请求,并返回true return SS; } catch (InterruptedException e) { throw new RuntimeException(e); } } //设置几分钟后 打开串口 public boolean openPortAfterMinutes (SerialEntity serial){ scheduler.schedule(() -> { try { /* UPSManager upsManager = new UPSManager(); */ if (serial == null) { System.out.println("SerialEntity 对象为空,无法打开端口"); } if (rs232Config == null) { System.out.println("rs232Config 对象为空,无法操作端口" + rs232Config); // 注意:这里也不需要 return false } boolean portOpened = rs232Config.openPort(serial); if (portOpened) { rs232Config.sendData(serial.getPortName(), serial.getPowerOffCommand()); System.out.println("已发送指令"); } else { System.out.println("端口未打开"); // 注意:这里也不需要 return false } rs232Config.closePort(serial); // 注意:其他代码逻辑... // 注意:这里不需要 return true,因为 Runnable 没有返回值 } catch (Exception e) { e.printStackTrace(); // 处理异常 } }, serial.getMinutes(), TimeUnit.MINUTES); // 返回 true 表示操作已成功安排(但不一定已完成) return true; } //设置几分钟后关闭串口 public boolean closePortAfterMinutes (SerialEntity serial ){ scheduler.schedule(() -> { try { /* UPSManager upsManager = new UPSManager();*/ try { if (rs232Config != null) { rs232Config.openPort(serial); boolean closeport = rs232Config.sendData(serial.getPortName(), serial.getPowerOffCommand()); System.out.println("closeport" + closeport); boolean portclosed = rs232Config.closePort(serial); if (portclosed) { System.out.println("已发送指令"); } } else { System.out.println("rs232Config is null"); } } catch (RuntimeException e) { e.printStackTrace(); } // 在 minutes 分钟后执行 closePort } catch (Exception e) { e.printStackTrace(); // 处理异常 } }, serial.getMinutes(), TimeUnit.MINUTES); // 返回 true 表示操作已成功安排(但不一定已完成) return true; } public SerialEntity latitude(SerialEntity serial) { SerialEntity SS = new SerialEntity(); rs232Config.openPort1(serial); boolean m= rs232Config.sendData(serial.getPortName(), serial.getPowerOffCommand()); System.out.println("发送命令成功"+m); System.out.println("222222"); System.out.println( rs232Config.sendData(serial.getPortName(), serial.getPowerOffCommand())); SS = rs232Config.readData4(serial.getPortName()); rs232Config.closePort(serial); return SS; } }
7.Rs232Controller请求处理类
package com.ruoyi.web.controller.equipment; import com.ruoyi.common.utils.ResponseObj; import com.ruoyi.equipment.config.UPSManager; import com.ruoyi.equipment.entity.SerialEntity; import io.swagger.annotations.Api; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; /*它用于处理与RS-232串口通信相关的HTTP请求*/ @RestController @RequestMapping("/UPS/RS232") @Api(tags = "UPS接入企业端") public class Rs232Controller { @Autowired UPSManager upsManager; @PostMapping("/open") public ResponseObj open(@RequestBody SerialEntity serial) { return ResponseObj.success(upsManager.openPort(serial)); } @PostMapping("/close") public ResponseObj close(@RequestBody SerialEntity serial) { /* serialController.getSerialPortList();*/ return ResponseObj.success(upsManager.closePort(serial)); } @PostMapping("/CurrentStatus") public ResponseObj CurrentStatus(@RequestBody SerialEntity serial) throws IOException{ return ResponseObj.success(upsManager.CurrentStatus(serial)); } @PostMapping("/ReaminBatteryTime") public ResponseObj ReaminBatteryTime(@RequestBody SerialEntity serial) { return ResponseObj.success(upsManager.ReaminBatteryTime(serial)); } @PostMapping("/CurrentMode") public ResponseObj CurrentMode(@RequestBody SerialEntity serial) { return ResponseObj.success(upsManager.CurrentMode(serial)); } @PostMapping("/latitude") public ResponseObj latitude(@RequestBody SerialEntity serial) { return ResponseObj.success(upsManager.latitude(serial)); } @PostMapping("/time") public ResponseObj Time(@RequestBody SerialEntity serial) { return ResponseObj.success(upsManager.openPortAfterMinutes(serial)); } @PostMapping("/time2") public ResponseObj Time2(@RequestBody SerialEntity serial) { return ResponseObj.success( upsManager.closePortAfterMinutes(serial)); } }