记录一下使用SpringBoot+jSerialComm实现Java串口通信,使用Java语言开发串口,对串口进行读写操作,在win和linux系统都是可以的,有一点好处是不需要导入额外的文件。
案例demo源码:SpringBoot+jSerialComm实现Java串口通信 读取串口数据以及发送数据
之前使用RXTXcomm实现Java串口通信,这种方式对linux(centos)的支持效果不好还有些问题 但在win下面使用还不错,原文地址:SpringBoot+RXTXcomm实现Java串口通信 读取串口数据以及发送数据
不需要额外导入文件 比如dll 只需要导入对应的包
com.fazecast jSerialComm 2.9.2
pom.xml
org.springframework.boot spring-boot-starter-parent 2.0.5.RELEASE 4.0.0 boot.example.jSerialComm boot-example-jSerialComm-2.0.5 0.0.1-SNAPSHOT boot-example-jSerialComm-2.0.5 http://maven.apache.org UTF-8 UTF-8 1.8 org.springframework.boot spring-boot-starter-web com.fazecast jSerialComm 2.9.2 io.springfox springfox-swagger2 2.9.2 com.github.xiaoymin swagger-bootstrap-ui 1.9.2 org.springframework.boot spring-boot-maven-plugin boot.example.SerialPortApplication true repackage
SerialPortApplication启动类
package boot.example; import boot.example.serialport.SerialPortManager; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; import javax.annotation.PreDestroy; import java.io.IOException; /** * 蚂蚁舞 */ @SpringBootApplication @EnableScheduling @EnableAsync public class SerialPortApplication implements CommandLineRunner { public static void main(String[] args) throws IOException { SpringApplication.run(SerialPortApplication.class, args); } @Override public void run(String... args) throws Exception { try{ // win SerialPortManager.connectSerialPort("COM1"); // linux centos //SerialPortManager.connectSerialPort("ttyS1"); } catch (Exception e){ System.out.println(e.toString()); } } @PreDestroy public void destroy() { SerialPortManager.closeSerialPort(); } }
SwaggerConfig
package boot.example; import com.google.common.base.Predicates; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; /** * 蚂蚁舞 */ @Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket createRestApi(){ return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select() .apis(RequestHandlerSelectors.any()).paths(PathSelectors.any()) .paths(Predicates.not(PathSelectors.regex("/error.*"))) .paths(PathSelectors.regex("/.*")) .build().apiInfo(apiInfo()); } private ApiInfo apiInfo(){ return new ApiInfoBuilder() .title("SpringBoot+jSerialComm实现Java串口通信 读取串口数据以及发送数据") .description("测试接口") .version("0.01") .build(); } }
SerialPortController
package boot.example.controller; import boot.example.serialport.ConvertHexStrAndStrUtils; import boot.example.serialport.SerialPortManager; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 蚂蚁舞 */ @Controller @RequestMapping("/serialPort") public class SerialPortController { @GetMapping("/list") @ResponseBody public ListlistPorts() { List portList = SerialPortManager.getSerialPortList(); if(!portList.isEmpty()){ return portList; } return null; } @PostMapping("/send/{hexData}") @ResponseBody public String sendPorts(@PathVariable("hexData") String hexData) { if (SerialPortManager.SERIAL_PORT_STATE){ SerialPortManager.sendSerialPortData(ConvertHexStrAndStrUtils.hexStrToBytes(hexData)); return "success"; } return "fail"; } }
ConvertHexStrAndStrUtils
package boot.example.serialport; import java.nio.charset.StandardCharsets; /** * 蚂蚁舞 */ public class ConvertHexStrAndStrUtils { private static final char[] HEXES = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; public static String bytesToHexStr(byte[] bytes) { if (bytes == null || bytes.length == 0) { return null; } StringBuilder hex = new StringBuilder(bytes.length * 2); for (byte b : bytes) { hex.append(HEXES[(b >> 4) & 0x0F]); hex.append(HEXES[b & 0x0F]); } return hex.toString().toUpperCase(); } public static byte[] hexStrToBytes(String hex) { if (hex == null || hex.length() == 0) { return null; } char[] hexChars = hex.toCharArray(); byte[] bytes = new byte[hexChars.length / 2]; // 如果 hex 中的字符不是偶数个, 则忽略最后一个 for (int i = 0; i < bytes.length; i++) { bytes[i] = (byte) Integer.parseInt("" + hexChars[i * 2] + hexChars[i * 2 + 1], 16); } return bytes; } public static String strToHexStr(String str) { StringBuilder sb = new StringBuilder(); byte[] bs = str.getBytes(); int bit; for (int i = 0; i < bs.length; i++) { bit = (bs[i] & 0x0f0) >> 4; sb.append(HEXES[bit]); bit = bs[i] & 0x0f; sb.append(HEXES[bit]); } return sb.toString().trim(); } public static String hexStrToStr(String hexStr) { //能被16整除,肯定可以被2整除 byte[] array = new byte[hexStr.length() / 2]; try { for (int i = 0; i < array.length; i++) { array[i] = (byte) (0xff & Integer.parseInt(hexStr.substring(i * 2, i * 2 + 2), 16)); } hexStr = new String(array, StandardCharsets.UTF_8); } catch (Exception e) { e.printStackTrace(); return ""; } return hexStr; } }
SerialPortManager
package boot.example.serialport; import com.fazecast.jSerialComm.SerialPort; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** * 蚂蚁舞 */ public class SerialPortManager { public static final int SERIAL_BAUD_RATE = 115200; public static volatile boolean SERIAL_PORT_STATE = false; public static volatile SerialPort SERIAL_PORT_OBJECT = null; //查找所有可用端口 public static ListgetSerialPortList() { // 获得当前所有可用串口 SerialPort[] serialPorts = SerialPort.getCommPorts(); List portNameList = new ArrayList (); // 将可用串口名添加到List并返回该List for(SerialPort serialPort:serialPorts) { System.out.println(serialPort.getSystemPortName()); portNameList.add(serialPort.getSystemPortName()); } //去重 portNameList = portNameList.stream().distinct().collect(Collectors.toList()); return portNameList; } // 连接串口 public static void connectSerialPort(String portName){ try { SerialPort serialPort = SerialPortManager.openSerialPort(portName, SERIAL_BAUD_RATE); TimeUnit.MILLISECONDS.sleep(2000); //给当前串口对象设置监听器 serialPort.addDataListener(new SerialPortListener(new SerialPortCallback())); if(serialPort.isOpen()) { SERIAL_PORT_OBJECT = serialPort; SERIAL_PORT_STATE = true; System.out.println(portName+"-- start success"); } } catch (InterruptedException ex) { ex.printStackTrace(); } } // 打开串口 public static SerialPort openSerialPort(String portName, Integer baudRate) { SerialPort serialPort = SerialPort.getCommPort(portName); if (baudRate != null) { serialPort.setBaudRate(baudRate); } if (!serialPort.isOpen()) { //开启串口 serialPort.openPort(1000); }else{ return serialPort; } serialPort.setFlowControl(SerialPort.FLOW_CONTROL_DISABLED); serialPort.setComPortParameters(baudRate, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY); serialPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_BLOCKING | SerialPort.TIMEOUT_WRITE_BLOCKING, 1000, 1000); return serialPort; } // 关闭串口 public static void closeSerialPort() { if (SERIAL_PORT_OBJECT != null && SERIAL_PORT_OBJECT.isOpen()){ SERIAL_PORT_OBJECT.closePort(); SERIAL_PORT_STATE = false; SERIAL_PORT_OBJECT = null; } } // 发送字节数组 public static void sendSerialPortData(byte[] content) { if (SERIAL_PORT_OBJECT != null && SERIAL_PORT_OBJECT.isOpen()){ SERIAL_PORT_OBJECT.writeBytes(content, content.length); } } // 读取字节数组 public static byte[] readSerialPortData() { if (SERIAL_PORT_OBJECT != null && SERIAL_PORT_OBJECT.isOpen()){ byte[] reslutData = null; try { if (!SERIAL_PORT_OBJECT.isOpen()){return null;}; int i=0; while (SERIAL_PORT_OBJECT.bytesAvailable() > 0 && i++ < 5) Thread.sleep(20); byte[] readBuffer = new byte[SERIAL_PORT_OBJECT.bytesAvailable()]; int numRead = SERIAL_PORT_OBJECT.readBytes(readBuffer, readBuffer.length); if (numRead > 0) { reslutData = readBuffer; } } catch (InterruptedException e) { e.printStackTrace(); } return reslutData; } return null; } }
SerialPortListener
package boot.example.serialport; import com.fazecast.jSerialComm.SerialPort; import com.fazecast.jSerialComm.SerialPortDataListener; import com.fazecast.jSerialComm.SerialPortEvent; /** * 蚂蚁舞 */ public class SerialPortListener implements SerialPortDataListener { private final SerialPortCallback serialPortCallback; public SerialPortListener(SerialPortCallback serialPortCallback) { this.serialPortCallback = serialPortCallback; } @Override public int getListeningEvents() { //必须是return这个才会开启串口工具的监听 return SerialPort.LISTENING_EVENT_DATA_AVAILABLE; } @Override public void serialEvent(SerialPortEvent serialPortEvent) { if (serialPortCallback != null) { serialPortCallback.dataAvailable(); } } }
SerialPortCallback
package boot.example.serialport; import java.text.SimpleDateFormat; import java.util.Date; /** * 蚂蚁舞 */ public class SerialPortCallback { public void dataAvailable() { try { //当前监听器监听到的串口返回数据 back byte[] back = SerialPortManager.readSerialPortData(); System.out.println("back-"+(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()))+"--"+ConvertHexStrAndStrUtils.bytesToHexStr(back)); String s = ConvertHexStrAndStrUtils.bytesToHexStr(back); System.out.println("rev--data:"+s); //throw new Exception(); } catch (Exception e) { System.out.println(e.toString()); } } }
项目结构
demo使用的波特率是115200 其他参数就默认的就好,一般只有波特率改动
启动项目和启动com工具(项目和com之间使用的是com1和com2虚拟串口 虚拟串口有工具的,比如Configure Virtual Serial Port Driver)
可以看到com1和com2都已经在使用 应用程序用的com1端口 com工具用的com2端口,这样的虚拟串口工具可以模拟调试使用的 应用程序通过com1向com2发送数据 ,com工具通过com2向com1的应用程序发送数据,全双工双向的,如此可以测试了。
访问地址
http://localhost:24810/doc.html
查看当前的串口 win系统下的两个虚拟串口
通过虚拟串口发送数据到com工具
通过com工具查看收到的数据已经发送数据给应用程序
控制台收到数据
记录着,将来用得着。