前言
前段時間趕專案的過程中,遇到一個呼叫RS485串列埠通訊的需求,趕完專案因為樓主處理私事,沒來得及完成文章的更新,現在終於可以整理一下當時的demo,記錄下來。
首先說一下大概需求:這個專案是機器視覺方面的,AI演算法通過攝像頭視訊流檢測畫面中的目標事件,比如:火焰、煙霧、人員離崗、吸菸、打手機、車輛超速等,檢測到目標事件後上傳檢測結果到後臺系統,
後臺系統儲存檢測結果並推送結果到前端,這裡用的是SpringBoot整合WebSocket實現前後端互推訊息,感興趣的同學可以看一看,大家多交流。然後就是今天的主題,系統在推送檢測結果到前端的同時,需要觸發
聲光報警器,現有條件就是系統呼叫支援RS485串列埠的繼電器控制電路,進而達到開啟和關閉報警器的目的。
準備工作
說了這麼多可能沒什麼具體的概念,下面先列出需要的硬體裝置及準備工作:
硬體:
USB串列埠轉換器(現在很多主機和筆記本已經沒有485串列埠的介面了,轉換器淘寶可以買到);
RS485繼電器(12V,繼電器模組有8個通道,模組的暫存器有對應8個通道的命令);
聲光報警器(12V);
12V電源轉換器;
電線若干;
驅動:
USB串列埠轉換驅動;
看了這些硬體,感覺樓主是電工是吧?沒錯,樓主確實是自己摸索著連線的,下面上圖:
線路如何接不是本文的重點,用12V的硬體就是因為安全,樓主可以大膽嘗試。。。
接通硬體裝置後,在系統中檢視串列埠名稱,如下圖,可以看到通訊埠名稱是COM1,其實電腦上每個硬體介面都是有固定名稱的,USB插在不同的USB介面上,系統讀取到的通訊埠名稱就是對應介面的名稱,這裡
的埠名稱要記下來,後面編碼要用到。
然後是搬磚前的最後一步準備工作:安裝驅動。樓主的USB串列埠轉換器是在淘寶上買的,商家提供驅動,在電腦上正常安裝驅動即可。
開發實現
首先需要引入rxtx的jar包,Java實現串列埠通訊的依賴,如下:
<dependency> <groupId>org.rxtx</groupId> <artifactId>rxtx</artifactId> <version>2.1.7</version> </dependency>
引入jar包後,就可以搬磚了,大概思路如下:
1、獲取到與串列埠通訊的物件;
2、開啟對應串列埠的埠並建立連線;
3、獲取對應通道的命令併傳送;
4、接收返回的資訊;
5、關閉埠連線。
程式碼如下:
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在主機上的通訊埠名稱,如:COM1、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只是拋磚引玉,有相關開發需求的童鞋可以看一看,參考一下大概的思路。