前段時間趕項目的過程當中,遇到一個調用RS485串口通訊的需求,趕完項目由於樓主處理私事,沒來得及完成文章的更新,如今終於能夠整理一下當時的demo,記錄下來。html
首先說一下大概需求:這個項目是機器視覺方面的,AI算法經過攝像頭視頻流檢測畫面中的目標事件,好比:火焰、煙霧、人員離崗、吸菸、打手機、車輛超速等,檢測到目標事件後上傳檢測結果到後臺系統,前端
後臺系統存儲檢測結果並推送結果到前端,這裏用的是SpringBoot整合WebSocket實現先後端互推消息,感興趣的同窗能夠看一看,你們多交流。而後就是今天的主題,系統在推送檢測結果到前端的同時,須要觸發java
聲光報警器,現有條件就是系統調用支持RS485串口的繼電器控制電路,進而達到打開和關閉報警器的目的。算法
說了這麼多可能沒什麼具體的概念,下面先列出須要的硬件設備及準備工做:後端
硬件:數組
USB串口轉換器(如今不少主機和筆記本已經沒有485串口的接口了,轉換器淘寶能夠買到);安全
RS485繼電器(12V,繼電器模塊有8個通道,模塊的寄存器有對應8個通道的命令);併發
聲光報警器(12V);app
12V電源轉換器;ide
電線若干;
驅動:
USB串口轉換驅動;
看了這些硬件,感受樓主是電工是吧?沒錯,樓主確實是本身摸索着鏈接的,下面上圖:
線路如何接不是本文的重點,用12V的硬件就是由於安全,樓主能夠大膽嘗試。。。
接通硬件設備後,在系統中查看串口名稱,以下圖,能夠看到通訊端口名稱是COM1,其實電腦上每一個硬件接口都是有固定名稱的,USB插在不一樣的USB接口上,系統讀取到的通訊端口名稱就是對應接口的名稱,這裏
的端口名稱要記下來,後面編碼要用到。
而後是搬磚前的最後一步準備工做:安裝驅動。樓主的USB串口轉換器是在淘寶上買的,商家提供驅動,在電腦上正常安裝驅動便可。
首先須要引入rxtx的jar包,Java實現串口通訊的依賴,以下:
<dependency> <groupId>org.rxtx</groupId> <artifactId>rxtx</artifactId> <version>2.1.7</version> </dependency>
引入jar包後,就能夠搬磚了,大概思路以下:
一、獲取到與串口通訊的對象;
二、打開對應串口的端口並創建鏈接;
三、獲取對應通道的命令併發送;
四、接收返回的信息;
五、關閉端口鏈接。
代碼以下:
package com.XXX.utils; import com.databus.Log; import gnu.io.*; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.*; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class RS485Demo extends Thread implements SerialPortEventListener { //單例模式提供鏈接串口的對象 private static RS485Demo getInstance(){ if (cRead == null){ synchronized (RS485Demo.class) { if (cRead == null) { cRead = new RS485Demo(); // 啓動線程來處理收到的數據 cRead.start(); } } } return cRead; } // 封裝十六進制的打開、關閉命令 private static final List<byte[]> onOrderList = Arrays.asList( new byte[]{0x01, 0x05, 0x00, 0x00, (byte) 0xFF, 0x00, (byte) 0x8C, 0x3A}, new byte[]{0x01, 0x05, 0x00, 0x01, (byte) 0xFF, 0x00, (byte) 0xDD, (byte)0xFA}, new byte[]{0x01, 0x05, 0x00, 0x02, (byte) 0xFF, 0x00, (byte) 0x2D, (byte)0xFA}, new byte[]{0x01, 0x05, 0x00, 0x03, (byte) 0xFF, 0x00, (byte) 0x7C, 0x3A}, new byte[]{0x01, 0x05, 0x00, 0x04, (byte) 0xFF, 0x00, (byte) 0xCD,(byte) 0xFB}, new byte[]{0x01, 0x05, 0x00, 0x05, (byte) 0xFF, 0x00, (byte) 0x9C, 0x3B}, new byte[]{0x01, 0x05, 0x00, 0x06, (byte) 0xFF, 0x00, (byte) 0x6C, 0x3B}, new byte[]{0x01, 0x05, 0x00, 0x07, (byte) 0xFF, 0x00, 0x3D, (byte)0xFB}); private static final List<byte[]> offOrderList = Arrays.asList( new byte[]{0x01, 0x05, 0x00, 0x00, 0x00, 0x00, (byte) 0xCD, (byte)0xCA},new byte[]{0x01, 0x05, 0x00, 0x01, 0x00, 0x00, (byte) 0x9C, (byte)0x0A}, new byte[]{0x01, 0x05, 0x00, 0x02, 0x00, 0x00, (byte) 0x6C, (byte)0x0A},new byte[]{0x01, 0x05, 0x00, 0x03, 0x00, 0x00, (byte) 0x3D, (byte)0xCA}, new byte[]{0x01, 0x05, 0x00, 0x04, 0x00, 0x00, (byte) 0x8C, (byte)0x0B},new byte[]{0x01, 0x05, 0x00, 0x05, 0x00, 0x00, (byte) 0xDD, (byte)0xCB}, new byte[]{0x01, 0x05, 0x00, 0x06, 0x00, 0x00, (byte) 0x2D, (byte)0xCB},new byte[]{0x01, 0x05, 0x00, 0x07, 0x00, 0x00, (byte) 0x7C, (byte)0x0B}); // 監聽器,這裏獨立開闢一個線程監聽串口數據 // 串口通訊管理類 static CommPortIdentifier portId; static RS485Demo cRead = null; //USB在主機上的通訊端口名稱,如:COM一、COM2等 static String COMNUM = ""; static Enumeration<?> portList; InputStream inputStream; // 從串口來的輸入流 static OutputStream outputStream;// 向串口輸出的流 static SerialPort serialPort; // 串口的引用 // 堵塞隊列用來存放讀到的數據 private BlockingQueue<String> msgQueue = new LinkedBlockingQueue<String>(); /** * SerialPort EventListene 的方法,持續監聽端口上是否有數據流 */ public void serialEvent(SerialPortEvent event) { switch (event.getEventType()) { case SerialPortEvent.BI: case SerialPortEvent.OE: case SerialPortEvent.FE: case SerialPortEvent.PE: case SerialPortEvent.CD: case SerialPortEvent.CTS: case SerialPortEvent.DSR: case SerialPortEvent.RI: case SerialPortEvent.OUTPUT_BUFFER_EMPTY: break; case SerialPortEvent.DATA_AVAILABLE:// 當有可用數據時讀取數據 byte[] readBuffer = null; int availableBytes = 0; try { availableBytes = inputStream.available(); while (availableBytes > 0) { readBuffer = RS485Demo.readFromPort(serialPort); String needData = printHexString(readBuffer); System.out.println(new Date() + "真實收到的數據爲:-----" + needData); availableBytes = inputStream.available(); msgQueue.add(needData); } } catch (IOException e) { } default: break; } } /** * 從串口讀取數據 * * @param serialPort 當前已創建鏈接的SerialPort對象 * @return 讀取到的數據 */ public static byte[] readFromPort(SerialPort serialPort) { InputStream in = null; byte[] bytes = {}; try { in = serialPort.getInputStream(); // 緩衝區大小爲一個字節 byte[] readBuffer = new byte[1]; int bytesNum = in.read(readBuffer); while (bytesNum > 0) { bytes = concat(bytes, readBuffer); bytesNum = in.read(readBuffer); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (in != null) { in.close(); in = null; } } catch (IOException e) { e.printStackTrace(); } } return bytes; } /** * 經過程序打開COM串口,設置監聽器以及相關的參數 * @return 返回1 表示端口打開成功,返回 0表示端口打開失敗 */ public int startComPort() { // 經過串口通訊管理類得到當前鏈接上的串口列表 try { Log.info("開始獲取串口。。。"); portList = CommPortIdentifier.getPortIdentifiers(); Log.info("獲取串口。。。" + portList); Log.info("獲取串口結果。。。" + portList.hasMoreElements()); while (portList.hasMoreElements()) { // 獲取相應串口對象 Log.info(portList.nextElement()); portId = (CommPortIdentifier) portList.nextElement(); System.out.println("設備類型:--->" + portId.getPortType()); System.out.println("設備名稱:---->" + portId.getName()); // 判斷端口類型是否爲串口 if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) { // 判斷若是COM4串口存在,就打開該串口 // if (portId.getName().equals(portId.getName())) { if (portId.getName().equals(COMNUM)) { try { // 打開串口名字爲COM_4(名字任意),延遲爲1000毫秒 serialPort = (SerialPort) portId.open(portId.getName(), 1000); } catch (PortInUseException e) { System.out.println("打開端口失敗!"); e.printStackTrace(); return 0; } // 設置當前串口的輸入輸出流 try { inputStream = serialPort.getInputStream(); outputStream = serialPort.getOutputStream(); } catch (IOException e) { e.printStackTrace(); return 0; } // 給當前串口添加一個監聽器,serialEvent方法監聽串口返回的數據 try { serialPort.addEventListener(this); } catch (TooManyListenersException e) { e.printStackTrace(); return 0; } // 設置監聽器生效,即:當有數據時通知 serialPort.notifyOnDataAvailable(true); // 設置串口的一些讀寫參數 try { // 比特率、數據位、中止位、奇偶校驗位 serialPort.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); } catch (UnsupportedCommOperationException e) { e.printStackTrace(); return 0; } return 1; } } } }catch (Exception e){ e.printStackTrace(); Log.info(e); return 0; } return 0; } @Override public void run() { // TODO Auto-generated method stub try { System.out.println("--------------任務處理線程運行了--------------"); while (true) { // 若是堵塞隊列中存在數據就將其輸出 try { if (msgQueue.size() > 0) { String vo = msgQueue.peek(); String vos[] = vo.split(" ", -1); //根據返回數據能夠作相應的業務邏輯操做 // getData(vos); // sendOrder(); msgQueue.take(); } }catch (Exception e){ e.printStackTrace(); } } } catch (Exception e) { e.printStackTrace(); } } // 16轉10計算 public long getNum(String num1, String num2) { long value = Long.parseLong(num1, 16) * 256 + Long.parseLong(num2, 16); return value; } // 字節數組轉字符串 private String printHexString(byte[] b) { StringBuffer sbf = new StringBuffer(); 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 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; } //num:偶數啓動報警器,奇數關閉報警器 //commandInfo:偶數打開,奇數關閉;channel:繼電器通道;comNum:串口設備通訊名稱 public static void startRS485(int commandInfo,int channel,String comNum) { try { if(cRead == null){ cRead = getInstance(); } if (!COMNUM.equals(comNum) && null != serialPort){ serialPort.close(); COMNUM = comNum; } int i = 1; if (serialPort == null){ COMNUM = comNum; //打開串口通道並鏈接 i = cRead.startComPort(); } if (i == 1){ Log.info("串口鏈接成功"); try { //根據提供的文檔給出的發送命令,發送16進制數據給儀器 byte[] b; if (commandInfo % 2 == 0) { b = onOrderList.get(channel); }else{ b = offOrderList.get(channel); } System.out.println("發送的數據:" + b); System.out.println("發出字節數:" + b.length); outputStream.write(b); outputStream.flush(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (outputStream != null) { outputStream.close(); } } catch (IOException e) { e.printStackTrace(); } //每次調用完之後關閉串口通道 if (null != cRead){ if (null != serialPort){ serialPort.close(); serialPort = null; } cRead.interrupt(); cRead = null; } } }else{ Log.info("串口鏈接失敗"); return; } }catch (Exception e){ e.printStackTrace(); Log.info("串口鏈接失敗"); } } public static void main(String[] args) { //打開通道1的電路,對應設備名稱COM3 startRS485(0,1,"COM3"); } }
代碼比較繁雜,須要有點耐心才能徹底瞭解,你們能夠從startRS485()函數做爲切入點閱讀代碼。固然,這個demo只是拋磚引玉,有相關開發需求的童鞋能夠看一看,參考一下大概的思路。