Java串口通信 RXTX 解决过程
背景介绍:
由于第一次用Java与硬件通信,网上查了许多资料,在这进行整理,便于以后学习。本人串口测试是USB串口设备连接电脑,在设备管理器中找到端口名称(也可以通过一些虚拟串口工具模拟)。
下面主要简述获取串口消息返回值的一些问题,在最下面将会附上完整代码。
准备工作:
RXTX包:mfz-rxtx-2.2-20081207-win-x64.zip,解压,RXTXcomm.jar加入项目依赖库里,rxtxParallel.dll和rxtxSerial.dll放入jdk的bin目录下(我使用的jdk1.8)
RXTX工具类编写:
编写基础方法:获取可用端口名,开启端口,发送命令,接受命令,关闭端口
import gnu.io.*; import javax.sound.midi.SoundbankResource; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.TooManyListenersException; import static com.why.rxtx.utill.HexadecimalUtil.get16NumAdd0; import static com.why.rxtx.utill.HexadecimalUtil.hexStringToByteArray; import static com.why.rxtx.utill.OrderUtil.retuenLogOrder; import static com.why.rxtx.utill.OrderUtil.send; /** * 使用rxtx连接串口工具类 */ public class RXTXUtil { private static final String DEMONAME = "串口测试"; /** * 检测系统中可用的端口 */ private CommPortIdentifier portId; /** * 获得系统可用的端口名称列表 */ private static EnumerationportList; /** * 输入流 */ private static InputStream inputStream; /** * RS-232的串行口 */ private static SerialPort serialPort; /** * 返回结果 */ private static String res=null; /** * 获得系统可用的端口名称列表 * @return 可用端口名称列表 */ @SuppressWarnings("unchecked") public static void getSystemPort(){ List systemPorts = new ArrayList<>(); //获得系统可用的端口 portList = CommPortIdentifier.getPortIdentifiers(); while(portList.hasMoreElements()) { String portName = portList.nextElement().getName();//获得端口的名字 systemPorts.add(portName); } } /** * 开启串口 * @param serialPortName 串口名称 * @param baudRate 波特率 * @return 串口对象 */ public static void openSerialPort(String serialPortName,int baudRate) { try { //通过端口名称得到端口 CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(serialPortName); //打开端口,(自定义名字,打开超时时间) CommPort commPort = portIdentifier.open(serialPortName, 5000); //判断是不是串口 if (commPort instanceof SerialPort) { serialPort = (SerialPort) commPort; //设置串口参数(波特率,数据位8,停止位1,校验位无) serialPort.setSerialPortParams(baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); // System.out.println("开启串口成功,串口名称:"+serialPortName); }else { //是其他类型的端口 throw new NoSuchPortException(); } } catch (NoSuchPortException e) { e.printStackTrace(); } catch (PortInUseException e) { e.printStackTrace(); } catch (UnsupportedCommOperationException e) { e.printStackTrace(); } } /** * 向串口发送数据 * @param order 发送的命令 */ public static void sendData( String order) { //16进制表示的字符串转换为字节数组 byte[] data =hexStringToByteArray(order); OutputStream os = null; try { os = serialPort.getOutputStream();//获得串口的输出流 os.write(data); os.flush(); } catch (IOException e) { e.printStackTrace(); } finally { //关闭流操作 try { if (os != null) { os.close(); os = null; } } catch (IOException e) { e.printStackTrace(); } } } /** * 从串口读取数据 * @return 读取的数据 */ public static String readData() { //保存串口返回信息 StringBuffer res=new StringBuffer(40); InputStream is = null; byte[] bytes = null; try { is = serialPort.getInputStream();//获得串口的输入流 int bufflenth = is.available();//获得数据长度 while (bufflenth != 0) { bytes = new byte[bufflenth];//初始化byte数组 is.read(bytes); bufflenth = is.available(); } if(bytes!=null) { for (int i = 0; i < bytes.length; i++) { //转换成16进制数(FF) res.append(get16NumAdd0((bytes[i]&0xff)+"",2)); } } System.out.println("res: "+res.toString()); } catch (IOException e) { e.printStackTrace(); } finally { try { if (is != null) { is.close(); is = null; } } catch(IOException e) { e.printStackTrace(); } } return res.toString(); } /** * 关闭串口 * */ public static void closeSerialPort() { if(serialPort != null) { serialPort.close(); //System.out.println("关闭了串口:"+serialPort.getName()); serialPort = null; } } }
在一些基本方法写完后,便得开始测试串口通信了
于是就开始编写测试代码了
/** * 串口命令执行 * @param order 命令 * @param portName 端口名 * @param baudRate 波特率 * @return * @throws UnsupportedEncodingException */ public synchronized static String executeOrder(String order,String portName,int baudRate) { String str=""; if (serialPort==null) { openSerialPort(portName, baudRate); } //发送消息 sendData(order); //接收消息 String str=readData(); return res; }
很遗憾上面代码输入命令,端口号等等后,返回结果一直是null, 突然间发现和以前写读写io流还是有一定区别的。原因在于,io流是有文件在那可以随时读写,而串口需要等待返回信息。
然后便开始改写代码。通过上网查询,发现有一个端口监听器,于是有了下面代码
/** * 串口命令执行 * @param order 命令 * @param portName 端口名 * @param baudRate 波特率 * @return * @throws UnsupportedEncodingException */ public synchronized static String executeOrder(String order,String portName,int baudRate) { String str=""; if (serialPort==null) { openSerialPort(portName, baudRate); } //发送消息 sendData(order); //设置监听 setListenerToSerialPort( new SerialPortEventListener(){ @Override public void serialEvent(SerialPortEvent serialPortEvent) { if(serialPortEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) {//数据通知 String str=readData(); res=returnCheck(order,str); } } }); return res; } /** * 给串口设置监听 * @param listener */ public static void setListenerToSerialPort( SerialPortEventListener listener) { try { //给串口添加事件监听 serialPort.addEventListener(listener); } catch (TooManyListenersException e) { e.printStackTrace(); } serialPort.notifyOnDataAvailable(true);//串口有数据监听 serialPort.notifyOnBreakInterrupt(true);//中断事件监听 }
经过再次执行,发现返回的依旧是null,于是在监听器里打印串口消息输出,发现是先返回 null,然后再打印串口消息。于是得出结论监听器是异步的,但是我们需要返回值去判断是否执行成功,
显然这不符合要求。于是再做修改,需要把这异步返回值接收到。于是有了下面两种方式:
第一种:放弃监听器,使用循环去读取串口返回信息,通过循环次数设置最大请求时间
/** * 串口命令执行 * @param order 命令 * @param portName 端口名 * @param baudRate 波特率 * @return * @throws UnsupportedEncodingException */ public synchronized static String executeOrder(String order,String portName,int baudRate) { String str=""; if (serialPort==null) { openSerialPort(portName, baudRate); } //发送消息 sendData(order); //代替监听 for (int i = 0; i <1000; i++) { str=readData(); str=returnCheck(order,str); if (str!=null){ return str; }else { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } } }
第二种:使用监听,通过循环判断是否有串口消息返回,与第一种本质还是相同的
/** * 串口命令执行 * @param order 命令 * @param portName 端口名 * @param baudRate 波特率 * @return * @throws UnsupportedEncodingException */ public synchronized static String executeOrder(String order,String portName,int baudRate) { String str=""; if (serialPort==null) { openSerialPort(portName, baudRate); } //发送消息 sendData(order); //设置监听 setListenerToSerialPort( new SerialPortEventListener(){ @Override public void serialEvent(SerialPortEvent serialPortEvent) { if(serialPortEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) {//数据通知 String str=readData(); res=returnCheck(order,str); } } }); //监听是异步请求,如果要获取监听里的内容做返回值, // 可以通过循环去延迟来获取返回值, // 设置最大延迟防止死循环 long startTime = System.currentTimeMillis(); while (res==null){ try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } long endTime = System.currentTimeMillis(); if ((endTime-startTime)/1000.0>20){//最长20s返回 return res; } } return res; }
到此串口消息通信就已解决了,下面附上完整代码,包括一些使用到的进制转换方法。
package com.along.outboundmanage.utill; import gnu.io.*; import javax.sound.midi.SoundbankResource; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.TooManyListenersException; import static com.why.rxtx.utill.HexadecimalUtil.get16NumAdd0; import static com.why.rxtx.utill.HexadecimalUtil.hexStringToByteArray; /** * 使用rxtx连接串口工具类 */ public class RXTXUtil { private static final String DEMONAME = "串口测试"; /** * 检测系统中可用的端口 */ private CommPortIdentifier portId; /** * 获得系统可用的端口名称列表 */ private static EnumerationportList; /** * 输入流 */ private static InputStream inputStream; /** * RS-232的串行口 */ private static SerialPort serialPort; /** * 返回结果 */ private static String res=null; /** * 串口命令执行 * @param order 命令 * @param portName 端口名 * @param baudRate 波特率 * @return * @throws UnsupportedEncodingException */ public synchronized static String executeOrder(String order,String portName,int baudRate) { String str=""; if (serialPort==null) { openSerialPort(portName, baudRate); } //发送消息 sendData(order); //代替监听 /*for (int i = 0; i <1000; i++) { str=readData(); if (str!=null){ return str; }else { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } }*/ //设置监听 setListenerToSerialPort( new SerialPortEventListener(){ @Override public void serialEvent(SerialPortEvent serialPortEvent) { if(serialPortEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) {//数据通知 String str=readData(); res=returnCheck(order,str); } } }); //监听是异步请求,如果要获取监听里的内容做返回值, // 可以通过循环去延迟来获取返回值, // 设置最大延迟防止死循环 long startTime = System.currentTimeMillis(); while (res==null){ try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } long endTime = System.currentTimeMillis(); if ((endTime-startTime)/1000.0>20){//最长20s返回 return res; } } return res; } /** * 获得系统可用的端口名称列表 * @return 可用端口名称列表 */ @SuppressWarnings("unchecked") public static void getSystemPort(){ List systemPorts = new ArrayList<>(); //获得系统可用的端口 portList = CommPortIdentifier.getPortIdentifiers(); while(portList.hasMoreElements()) { String portName = portList.nextElement().getName();//获得端口的名字 systemPorts.add(portName); } } /** * 开启串口 * @param serialPortName 串口名称 * @param baudRate 波特率 * @return 串口对象 */ public static void openSerialPort(String serialPortName,int baudRate) { try { //通过端口名称得到端口 CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(serialPortName); //打开端口,(自定义名字,打开超时时间) CommPort commPort = portIdentifier.open(serialPortName, 5000); //判断是不是串口 if (commPort instanceof SerialPort) { serialPort = (SerialPort) commPort; //设置串口参数(波特率,数据位8,停止位1,校验位无) serialPort.setSerialPortParams(baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); // System.out.println("开启串口成功,串口名称:"+serialPortName); }else { //是其他类型的端口 throw new NoSuchPortException(); } } catch (NoSuchPortException e) { e.printStackTrace(); } catch (PortInUseException e) { e.printStackTrace(); } catch (UnsupportedCommOperationException e) { e.printStackTrace(); } } /** * 向串口发送数据 * @param order 发送的命令 */ public static void sendData( String order) { //16进制表示的字符串转换为字节数组 byte[] data =hexStringToByteArray(order); OutputStream os = null; try { os = serialPort.getOutputStream();//获得串口的输出流 os.write(data); os.flush(); } catch (IOException e) { e.printStackTrace(); } finally { //关闭流操作 try { if (os != null) { os.close(); os = null; } } catch (IOException e) { e.printStackTrace(); } } } /** * 从串口读取数据 * @return 读取的数据 */ public static String readData() { //保存串口返回信息 StringBuffer res=new StringBuffer(40); InputStream is = null; byte[] bytes = null; try { is = serialPort.getInputStream();//获得串口的输入流 int bufflenth = is.available();//获得数据长度 while (bufflenth != 0) { bytes = new byte[bufflenth];//初始化byte数组 is.read(bytes); bufflenth = is.available(); } if(bytes!=null) { for (int i = 0; i < bytes.length; i++) { //转换成16进制数(FF) res.append(get16NumAdd0((bytes[i]&0xff)+"",2)); } } System.out.println("res: "+res.toString()); } catch (IOException e) { e.printStackTrace(); } finally { try { if (is != null) { is.close(); is = null; } } catch(IOException e) { e.printStackTrace(); } } return res.toString(); } /** * 关闭串口 * */ public static void closeSerialPort() { if(serialPort != null) { serialPort.close(); //System.out.println("关闭了串口:"+serialPort.getName()); serialPort = null; } } /** * 给串口设置监听 * @param listener */ public static void setListenerToSerialPort( SerialPortEventListener listener) { try { //给串口添加事件监听 serialPort.addEventListener(listener); } catch (TooManyListenersException e) { e.printStackTrace(); } serialPort.notifyOnDataAvailable(true);//串口有数据监听 serialPort.notifyOnBreakInterrupt(true);//中断事件监听 } }
import org.thymeleaf.util.StringUtils; /** * 进制转换 * */ public class HexadecimalUtil { /** * 十六进制转十进制 * * @param num * @return */ public static Integer get10HexNum(String num) { if (num.contains("0X")) { num = num.replace("0X", ""); } return Integer.parseInt(num.substring(0), 16); } /** * 十进制转十六进制 * * @param num * @return */ public static String get16Num(Object num) { return Integer.toHexString(Integer.parseInt(num + "")); } /** * 十进制转十六进制,设置长度,不足补0 * * @param num * @return */ public static String get16NumAdd0(String num, int len) { String str = Integer.toHexString(Integer.parseInt(num)).toUpperCase(); String res = ""; if (len >= str.length()) { res = StringUtils.repeat("0", (len - str.length())) + str; } else { return str; } return res; } //num & 0xff public static int low8(Object num) { return Integer.parseInt(num + "") & 0xff; } //获取高四位 public static int getHeight4(byte data) { int height; height = ((data & 0xf0) >> 4); return height; } /** * 16进制表示的字符串转换为字节数组 * * @param hexString 16进制表示的字符串 * @return byte[] 字节数组 */ public static byte[] hexStringToByteArray(String hexString) { hexString = hexString.replaceAll(" ", ""); int len = hexString.length(); byte[] bytes = new byte[len / 2]; for (int i = 0; i < len; i += 2) { // 两位一组,表示一个字节,把这样表示的16进制字符串,还原成一个字节 bytes[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character .digit(hexString.charAt(i + 1), 16)); } return bytes; } }