背景介紹:
由於第一次用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 Enumeration<CommPortIdentifier> portList; /** * 輸入流 */ private static InputStream inputStream; /** * RS-232的串行口 */ private static SerialPort serialPort; /** * 返回結果 */ private static String res=null; /** * 獲得系統可用的端口名稱列表 * @return 可用端口名稱列表 */ @SuppressWarnings("unchecked") public static void getSystemPort(){ List<String> 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 Enumeration<CommPortIdentifier> portList; /** * 輸入流 */ 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<String> 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; } }