基于Java編寫串口通信工具
最近一門課要求編寫一個(gè)上位機(jī)串口通信工具,我基于Java編寫了一個(gè)帶有圖形界面的簡(jiǎn)單串口通信工具,下面詳述一下過程,供大家參考 ^_^
一:
首先,你需要下載一個(gè)額外的支持Java串口通信操作的jar包,由于java.comm比較老了,而且不支持64位系統(tǒng),這里推薦Rxtx這個(gè)jar包(32位/64位均支持)。
官方下載地址:http://fizzed.com/oss/rxtx-for-java (注:可能需要FQ才能下載)
不能FQ的童鞋,可以在這里下載:
http://xiazai.jb51.net/201612/yuanma/javamfzrxtx(jb51.net).rar(32位)
http://xiazai.jb51.net/201612/yuanma/javamfzrxtx(jb51.net).rar(64位)
二:
下載解壓jar包并在 Java Build Path 下引入:
捕獲
注:如果運(yùn)行過程中拋出 java.lang.UnsatisfiedLinkError 錯(cuò)誤,請(qǐng)將rxtx解壓包中的 rxtxParallel.dll,rxtxSerial.dll 這兩個(gè)文件復(fù)制到 C:\Windows\System32 目錄下即可解決該錯(cuò)誤。
三:
關(guān)于該jar包的使用,我寫了一個(gè)SerialTool.java類,該類提供關(guān)于串口通信的各簡(jiǎn)單服務(wù),代碼如下(注意該類位于 serialPort 包里):
package serialPort; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Enumeration; import java.util.TooManyListenersException; import gnu.io.CommPort; import gnu.io.CommPortIdentifier; import gnu.io.NoSuchPortException; import gnu.io.PortInUseException; import gnu.io.SerialPort; import gnu.io.SerialPortEventListener; import gnu.io.UnsupportedCommOperationException; import serialException.*; /** * 串口服務(wù)類,提供打開、關(guān)閉串口,讀取、發(fā)送串口數(shù)據(jù)等服務(wù)(采用單例設(shè)計(jì)模式) * @author zhong * */ public class SerialTool { private static SerialTool serialTool = null; static { //在該類被ClassLoader加載時(shí)就初始化一個(gè)SerialTool對(duì)象 if (serialTool == null) { serialTool = new SerialTool(); } } //私有化SerialTool類的構(gòu)造方法,不允許其他類生成SerialTool對(duì)象 private SerialTool() {} /** * 獲取提供服務(wù)的SerialTool對(duì)象 * @return serialTool */ public static SerialTool getSerialTool() { if (serialTool == null) { serialTool = new SerialTool(); } return serialTool; } /** * 查找所有可用端口 * @return 可用端口名稱列表 */ public static final ArrayList<String> findPort() { //獲得當(dāng)前所有可用串口 Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers(); ArrayList<String> portNameList = new ArrayList<>(); //將可用串口名添加到List并返回該List while (portList.hasMoreElements()) { String portName = portList.nextElement().getName(); portNameList.add(portName); } return portNameList; } /** * 打開串口 * @param portName 端口名稱 * @param baudrate 波特率 * @return 串口對(duì)象 * @throws SerialPortParameterFailure 設(shè)置串口參數(shù)失敗 * @throws NotASerialPort 端口指向設(shè)備不是串口類型 * @throws NoSuchPort 沒有該端口對(duì)應(yīng)的串口設(shè)備 * @throws PortInUse 端口已被占用 */ public static final SerialPort openPort(String portName, int baudrate) throws SerialPortParameterFailure, NotASerialPort, NoSuchPort, PortInUse { try { //通過端口名識(shí)別端口 CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName); //打開端口,并給端口名字和一個(gè)timeout(打開操作的超時(shí)時(shí)間) CommPort commPort = portIdentifier.open(portName, 2000); //判斷是不是串口 if (commPort instanceof SerialPort) { SerialPort serialPort = (SerialPort) commPort; try { //設(shè)置一下串口的波特率等參數(shù) serialPort.setSerialPortParams(baudrate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); } catch (UnsupportedCommOperationException e) { throw new SerialPortParameterFailure(); } //System.out.println("Open " + portName + " sucessfully !"); return serialPort; } else { //不是串口 throw new NotASerialPort(); } } catch (NoSuchPortException e1) { throw new NoSuchPort(); } catch (PortInUseException e2) { throw new PortInUse(); } } /** * 關(guān)閉串口 * @param serialport 待關(guān)閉的串口對(duì)象 */ public static void closePort(SerialPort serialPort) { if (serialPort != null) { serialPort.close(); serialPort = null; } } /** * 往串口發(fā)送數(shù)據(jù) * @param serialPort 串口對(duì)象 * @param order 待發(fā)送數(shù)據(jù) * @throws SendDataToSerialPortFailure 向串口發(fā)送數(shù)據(jù)失敗 * @throws SerialPortOutputStreamCloseFailure 關(guān)閉串口對(duì)象的輸出流出錯(cuò) */ public static void sendToPort(SerialPort serialPort, byte[] order) throws SendDataToSerialPortFailure, SerialPortOutputStreamCloseFailure { OutputStream out = null; try { out = serialPort.getOutputStream(); out.write(order); out.flush(); } catch (IOException e) { throw new SendDataToSerialPortFailure(); } finally { try { if (out != null) { out.close(); out = null; } } catch (IOException e) { throw new SerialPortOutputStreamCloseFailure(); } } } /** * 從串口讀取數(shù)據(jù) * @param serialPort 當(dāng)前已建立連接的SerialPort對(duì)象 * @return 讀取到的數(shù)據(jù) * @throws ReadDataFromSerialPortFailure 從串口讀取數(shù)據(jù)時(shí)出錯(cuò) * @throws SerialPortInputStreamCloseFailure 關(guān)閉串口對(duì)象輸入流出錯(cuò) */ public static byte[] readFromPort(SerialPort serialPort) throws ReadDataFromSerialPortFailure, SerialPortInputStreamCloseFailure { InputStream in = null; byte[] bytes = null; try { in = serialPort.getInputStream(); int bufflenth = in.available(); //獲取buffer里的數(shù)據(jù)長(zhǎng)度 while (bufflenth != 0) { bytes = new byte[bufflenth]; //初始化byte數(shù)組為buffer中數(shù)據(jù)的長(zhǎng)度 in.read(bytes); bufflenth = in.available(); } } catch (IOException e) { throw new ReadDataFromSerialPortFailure(); } finally { try { if (in != null) { in.close(); in = null; } } catch(IOException e) { throw new SerialPortInputStreamCloseFailure(); } } return bytes; } /** * 添加監(jiān)聽器 * @param port 串口對(duì)象 * @param listener 串口監(jiān)聽器 * @throws TooManyListeners 監(jiān)聽類對(duì)象過多 */ public static void addListener(SerialPort port, SerialPortEventListener listener) throws TooManyListeners { try { //給串口添加監(jiān)聽器 port.addEventListener(listener); //設(shè)置當(dāng)有數(shù)據(jù)到達(dá)時(shí)喚醒監(jiān)聽接收線程 port.notifyOnDataAvailable(true); //設(shè)置當(dāng)通信中斷時(shí)喚醒中斷線程 port.notifyOnBreakInterrupt(true); } catch (TooManyListenersException e) { throw new TooManyListeners(); } } }
注:該類方法中 throw 的 Exception 都是我自定義的 Exception,之所以這么做是為了方便在主程序中進(jìn)行相應(yīng)處理,下面貼其中一個(gè)Exception出來(lái)給大家做下說(shuō)明:
(注意我所有自定義的 Exception 都放在 serialException 包里)
package serialException; public class SerialPortParameterFailure extends Exception { /** * */ private static final long serialVersionUID = 1L; public SerialPortParameterFailure() {} @Override public String toString() { return "設(shè)置串口參數(shù)失??!打開串口操作未完成!"; } }
每個(gè)自定義的Exception類我都重寫了它的 toString() 方法,便于主程序捕捉到該Exception后打印對(duì)應(yīng)的錯(cuò)誤信息
其中在serialException包里還有一個(gè)專門將接收到的Exception對(duì)象內(nèi)的錯(cuò)誤信息提取出來(lái)轉(zhuǎn)換成字符串并返回的類,代碼如下:
package serialException; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; /** * 負(fù)責(zé)將傳入的Exception中的錯(cuò)誤信息提取出來(lái)并轉(zhuǎn)換成字符串; * @author zhong * */ public class ExceptionWriter { /** * 將Exception中的錯(cuò)誤信息封裝到字符串中并返回該字符串 * @param e 包含錯(cuò)誤的Exception * @return 錯(cuò)誤信息字符串 */ public static String getErrorInfoFromException(Exception e) { StringWriter sw = null; PrintWriter pw = null; try { sw = new StringWriter(); pw = new PrintWriter(sw); e.printStackTrace(pw); return "\r\n" + sw.toString() + "\r\n"; } catch (Exception e2) { return "出錯(cuò)啦!未獲取到錯(cuò)誤信息,請(qǐng)檢查后重試!"; } finally { try { if (pw != null) { pw.close(); } if (sw != null) { sw.close(); } } catch (IOException e1) { e1.printStackTrace(); } } } }
四:
主程序類的使用,Client.java里含有程序的入口地址(main方法),它的作用是顯示一個(gè)歡迎界面并調(diào)用DataView.java這個(gè)類進(jìn)行實(shí)際的串口數(shù)據(jù)顯示。
Client.java代碼如下:
package serialPort; import java.awt.Color; import java.awt.FlowLayout; import java.awt.Font; import java.awt.Frame; import java.awt.Graphics; import java.awt.GridLayout; import java.awt.Image; import java.awt.Label; import java.awt.Panel; import java.awt.Toolkit; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.JOptionPane; import serialException.ExceptionWriter; /** * 主程序 * @author zhong * */ public class Client extends Frame{ /** * */ private static final long serialVersionUID = 1L; /** * 程序界面寬度 */ public static final int WIDTH = 800; /** * 程序界面高度 */ public static final int HEIGHT = 620; /** * 程序界面出現(xiàn)位置(橫坐標(biāo)) */ public static final int LOC_X = 200; /** * 程序界面出現(xiàn)位置(縱坐標(biāo)) */ public static final int LOC_Y = 70; Color color = Color.WHITE; Image offScreen = null; //用于雙緩沖 //設(shè)置window的icon(這里我自定義了一下Windows窗口的icon圖標(biāo),因?yàn)閷?shí)在覺得哪個(gè)小咖啡圖標(biāo)不好看 = =) Toolkit toolKit = getToolkit(); Image icon = toolKit.getImage(Client.class.getResource("computer.png")); //持有其他類 DataView dataview = new DataView(this); //主界面類(顯示監(jiān)控?cái)?shù)據(jù)主面板) /** * 主方法 * @param args // */ public static void main(String[] args) { new Client().launchFrame(); } /** * 顯示主界面 */ public void launchFrame() { this.setBounds(LOC_X, LOC_Y, WIDTH, HEIGHT); //設(shè)定程序在桌面出現(xiàn)的位置 this.setTitle("CDIO工程項(xiàng)目"); //設(shè)置程序標(biāo)題 this.setIconImage(icon); this.setBackground(Color.white); //設(shè)置背景色 this.addWindowListener(new WindowAdapter() { //添加對(duì)窗口狀態(tài)的監(jiān)聽 public void windowClosing(WindowEvent arg0) { //當(dāng)窗口關(guān)閉時(shí) System.exit(0); //退出程序 } }); this.addKeyListener(new KeyMonitor()); //添加鍵盤監(jiān)聽器 this.setResizable(false); //窗口大小不可更改 this.setVisible(true); //顯示窗口 new Thread(new RepaintThread()).start(); //開啟重畫線程 } /** * 畫出程序界面各組件元素 */ public void paint(Graphics g) { Color c = g.getColor(); g.setFont(new Font("微軟雅黑", Font.BOLD, 40)); g.setColor(Color.black); g.drawString("歡迎使用上位機(jī)實(shí)時(shí)監(jiān)控系統(tǒng)", 45, 190); g.setFont(new Font("微軟雅黑", Font.ITALIC, 26)); g.setColor(Color.BLACK); g.drawString("Version:1.0 Powered By:ZhongLei", 280, 260); g.setFont(new Font("微軟雅黑", Font.BOLD, 30)); g.setColor(color); g.drawString("————點(diǎn)擊Enter鍵進(jìn)入主界面————", 100, 480); //使文字 "————點(diǎn)擊Enter鍵進(jìn)入主界面————" 黑白閃爍 if (color == Color.WHITE) color = Color.black; else if (color == color.BLACK) color = Color.white; } /** * 雙緩沖方式重畫界面各元素組件 */ public void update(Graphics g) { if (offScreen == null) offScreen = this.createImage(WIDTH, HEIGHT); Graphics gOffScreen = offScreen.getGraphics(); Color c = gOffScreen.getColor(); gOffScreen.setColor(Color.white); gOffScreen.fillRect(0, 0, WIDTH, HEIGHT); //重畫背景畫布 this.paint(gOffScreen); //重畫界面元素 gOffScreen.setColor(c); g.drawImage(offScreen, 0, 0, null); //將新畫好的畫布“貼”在原畫布上 } /* * 內(nèi)部類形式實(shí)現(xiàn)對(duì)鍵盤事件的監(jiān)聽 */ private class KeyMonitor extends KeyAdapter { public void keyReleased(KeyEvent e) { int keyCode = e.getKeyCode(); if (keyCode == KeyEvent.VK_ENTER) { //當(dāng)監(jiān)聽到用戶敲擊鍵盤enter鍵后執(zhí)行下面的操作 setVisible(false); //隱去歡迎界面 dataview.setVisible(true); //顯示監(jiān)測(cè)界面 dataview.dataFrame(); //初始化監(jiān)測(cè)界面 } } } /* * 重畫線程(每隔250毫秒重畫一次) */ private class RepaintThread implements Runnable { public void run() { while(true) { repaint(); try { Thread.sleep(250); } catch (InterruptedException e) { //重畫線程出錯(cuò)拋出異常時(shí)創(chuàng)建一個(gè)Dialog并顯示異常詳細(xì)信息 String err = ExceptionWriter.getErrorInfoFromException(e); JOptionPane.showMessageDialog(null, err, "錯(cuò)誤", JOptionPane.INFORMATION_MESSAGE); System.exit(0); } } } } }
運(yùn)行截圖:
注:實(shí)際運(yùn)行過程中最下面的“點(diǎn)擊Enter鍵進(jìn)入主界面”有一個(gè)一閃一閃的效果(是通過每隔一段時(shí)間重畫一次界面,讓這句話以白黑兩色反復(fù)交替出現(xiàn)實(shí)現(xiàn)的),雙緩沖方式利于解決重畫時(shí)界面閃爍的問題(如果不使用雙緩沖方式的話相當(dāng)于每次重畫時(shí)是在舊界面上一點(diǎn)一點(diǎn)畫上新東西,而雙緩沖實(shí)質(zhì)上是通過先在內(nèi)存中直接畫好一張新界面圖,然后一次性直接用新界面覆蓋掉舊界面)
DataView.java代碼如下:(該類用于實(shí)時(shí)顯示串口數(shù)據(jù))
簡(jiǎn)單說(shuō)明:
硬件設(shè)備每隔一段時(shí)間通過串口發(fā)送一次數(shù)據(jù)到計(jì)算機(jī),該串口工具成功連接至硬件設(shè)備并添加監(jiān)聽后,會(huì)在每次接收到數(shù)據(jù)時(shí)解析數(shù)據(jù)并更新界面;
你在使用時(shí)很可能需求跟我不一樣,該類僅供參考,實(shí)際使用中你很可能需要重新制作數(shù)據(jù)顯示界面以及數(shù)據(jù)解析方式
package serialPort; import java.awt.Button; import java.awt.Choice; import java.awt.Color; import java.awt.Font; import java.awt.Frame; import java.awt.Graphics; import java.awt.Image; import java.awt.Label; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.List; import java.util.TooManyListenersException; import javax.swing.JOptionPane; import gnu.io.SerialPort; import gnu.io.SerialPortEvent; import gnu.io.SerialPortEventListener; import serialException.*; /** * 監(jiān)測(cè)數(shù)據(jù)顯示類 * @author Zhong * */ public class DataView extends Frame { /** * */ private static final long serialVersionUID = 1L; Client client = null; private List<String> commList = null; //保存可用端口號(hào) private SerialPort serialPort = null; //保存串口對(duì)象 private Font font = new Font("微軟雅黑", Font.BOLD, 25); private Label tem = new Label("暫無(wú)數(shù)據(jù)", Label.CENTER); //溫度 private Label hum = new Label("暫無(wú)數(shù)據(jù)", Label.CENTER); //濕度 private Label pa = new Label("暫無(wú)數(shù)據(jù)", Label.CENTER); //壓強(qiáng) private Label rain = new Label("暫無(wú)數(shù)據(jù)", Label.CENTER); //雨量 private Label win_sp = new Label("暫無(wú)數(shù)據(jù)", Label.CENTER); //風(fēng)速 private Label win_dir = new Label("暫無(wú)數(shù)據(jù)", Label.CENTER); //風(fēng)向 private Choice commChoice = new Choice(); //串口選擇(下拉框) private Choice bpsChoice = new Choice(); //波特率選擇 private Button openSerialButton = new Button("打開串口"); Image offScreen = null; //重畫時(shí)的畫布 //設(shè)置window的icon Toolkit toolKit = getToolkit(); Image icon = toolKit.getImage(DataView.class.getResource("computer.png")); /** * 類的構(gòu)造方法 * @param client */ public DataView(Client client) { this.client = client; commList = SerialTool.findPort(); //程序初始化時(shí)就掃描一次有效串口 } /** * 主菜單窗口顯示; * 添加Label、按鈕、下拉條及相關(guān)事件監(jiān)聽; */ public void dataFrame() { this.setBounds(client.LOC_X, client.LOC_Y, client.WIDTH, client.HEIGHT); this.setTitle("CDIO工程項(xiàng)目"); this.setIconImage(icon); this.setBackground(Color.white); this.setLayout(null); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent arg0) { if (serialPort != null) { //程序退出時(shí)關(guān)閉串口釋放資源 SerialTool.closePort(serialPort); } System.exit(0); } }); tem.setBounds(140, 103, 225, 50); tem.setBackground(Color.black); tem.setFont(font); tem.setForeground(Color.white); add(tem); hum.setBounds(520, 103, 225, 50); hum.setBackground(Color.black); hum.setFont(font); hum.setForeground(Color.white); add(hum); pa.setBounds(140, 193, 225, 50); pa.setBackground(Color.black); pa.setFont(font); pa.setForeground(Color.white); add(pa); rain.setBounds(520, 193, 225, 50); rain.setBackground(Color.black); rain.setFont(font); rain.setForeground(Color.white); add(rain); win_sp.setBounds(140, 283, 225, 50); win_sp.setBackground(Color.black); win_sp.setFont(font); win_sp.setForeground(Color.white); add(win_sp); win_dir.setBounds(520, 283, 225, 50); win_dir.setBackground(Color.black); win_dir.setFont(font); win_dir.setForeground(Color.white); add(win_dir); //添加串口選擇選項(xiàng) commChoice.setBounds(160, 397, 200, 200); //檢查是否有可用串口,有則加入選項(xiàng)中 if (commList == null || commList.size()<1) { JOptionPane.showMessageDialog(null, "沒有搜索到有效串口!", "錯(cuò)誤", JOptionPane.INFORMATION_MESSAGE); } else { for (String s : commList) { commChoice.add(s); } } add(commChoice); //添加波特率選項(xiàng) bpsChoice.setBounds(526, 396, 200, 200); bpsChoice.add("1200"); bpsChoice.add("2400"); bpsChoice.add("4800"); bpsChoice.add("9600"); bpsChoice.add("14400"); bpsChoice.add("19200"); bpsChoice.add("115200"); add(bpsChoice); //添加打開串口按鈕 openSerialButton.setBounds(250, 490, 300, 50); openSerialButton.setBackground(Color.lightGray); openSerialButton.setFont(new Font("微軟雅黑", Font.BOLD, 20)); openSerialButton.setForeground(Color.darkGray); add(openSerialButton); //添加打開串口按鈕的事件監(jiān)聽 openSerialButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { //獲取串口名稱 String commName = commChoice.getSelectedItem(); //獲取波特率 String bpsStr = bpsChoice.getSelectedItem(); //檢查串口名稱是否獲取正確 if (commName == null || commName.equals("")) { JOptionPane.showMessageDialog(null, "沒有搜索到有效串口!", "錯(cuò)誤", JOptionPane.INFORMATION_MESSAGE); } else { //檢查波特率是否獲取正確 if (bpsStr == null || bpsStr.equals("")) { JOptionPane.showMessageDialog(null, "波特率獲取錯(cuò)誤!", "錯(cuò)誤", JOptionPane.INFORMATION_MESSAGE); } else { //串口名、波特率均獲取正確時(shí) int bps = Integer.parseInt(bpsStr); try { //獲取指定端口名及波特率的串口對(duì)象 serialPort = SerialTool.openPort(commName, bps); //在該串口對(duì)象上添加監(jiān)聽器 SerialTool.addListener(serialPort, new SerialListener()); //監(jiān)聽成功進(jìn)行提示 JOptionPane.showMessageDialog(null, "監(jiān)聽成功,稍后將顯示監(jiān)測(cè)數(shù)據(jù)!", "提示", JOptionPane.INFORMATION_MESSAGE); } catch (SerialPortParameterFailure | NotASerialPort | NoSuchPort | PortInUse | TooManyListeners e1) { //發(fā)生錯(cuò)誤時(shí)使用一個(gè)Dialog提示具體的錯(cuò)誤信息 JOptionPane.showMessageDialog(null, e1, "錯(cuò)誤", JOptionPane.INFORMATION_MESSAGE); } } } } }); this.setResizable(false); new Thread(new RepaintThread()).start(); //啟動(dòng)重畫線程 } /** * 畫出主界面組件元素 */ public void paint(Graphics g) { Color c = g.getColor(); g.setColor(Color.black); g.setFont(new Font("微軟雅黑", Font.BOLD, 25)); g.drawString(" 溫度: ", 45, 130); g.setColor(Color.black); g.setFont(new Font("微軟雅黑", Font.BOLD, 25)); g.drawString(" 濕度: ", 425, 130); g.setColor(Color.black); g.setFont(new Font("微軟雅黑", Font.BOLD, 25)); g.drawString(" 壓強(qiáng): ", 45, 220); g.setColor(Color.black); g.setFont(new Font("微軟雅黑", Font.BOLD, 25)); g.drawString(" 雨量: ", 425, 220); g.setColor(Color.black); g.setFont(new Font("微軟雅黑", Font.BOLD, 25)); g.drawString(" 風(fēng)速: ", 45, 310); g.setColor(Color.black); g.setFont(new Font("微軟雅黑", Font.BOLD, 25)); g.drawString(" 風(fēng)向: ", 425, 310); g.setColor(Color.gray); g.setFont(new Font("微軟雅黑", Font.BOLD, 20)); g.drawString(" 串口選擇: ", 45, 410); g.setColor(Color.gray); g.setFont(new Font("微軟雅黑", Font.BOLD, 20)); g.drawString(" 波特率: ", 425, 410); } /** * 雙緩沖方式重畫界面各元素組件 */ public void update(Graphics g) { if (offScreen == null) offScreen = this.createImage(Client.WIDTH, Client.HEIGHT); Graphics gOffScreen = offScreen.getGraphics(); Color c = gOffScreen.getColor(); gOffScreen.setColor(Color.white); gOffScreen.fillRect(0, 0, Client.WIDTH, Client.HEIGHT); //重畫背景畫布 this.paint(gOffScreen); //重畫界面元素 gOffScreen.setColor(c); g.drawImage(offScreen, 0, 0, null); //將新畫好的畫布“貼”在原畫布上 } /* * 重畫線程(每隔30毫秒重畫一次) */ private class RepaintThread implements Runnable { public void run() { while(true) { //調(diào)用重畫方法 repaint(); //掃描可用串口 commList = SerialTool.findPort(); if (commList != null && commList.size()>0) { //添加新掃描到的可用串口 for (String s : commList) { //該串口名是否已存在,初始默認(rèn)為不存在(在commList里存在但在commChoice里不存在,則新添加) boolean commExist = false; for (int i=0; i<commChoice.getItemCount(); i++) { if (s.equals(commChoice.getItem(i))) { //當(dāng)前掃描到的串口名已經(jīng)在初始掃描時(shí)存在 commExist = true; break; } } if (commExist) { //當(dāng)前掃描到的串口名已經(jīng)在初始掃描時(shí)存在,直接進(jìn)入下一次循環(huán) continue; } else { //若不存在則添加新串口名至可用串口下拉列表 commChoice.add(s); } } //移除已經(jīng)不可用的串口 for (int i=0; i<commChoice.getItemCount(); i++) { //該串口是否已失效,初始默認(rèn)為已經(jīng)失效(在commChoice里存在但在commList里不存在,則已經(jīng)失效) boolean commNotExist = true; for (String s : commList) { if (s.equals(commChoice.getItem(i))) { commNotExist = false; break; } } if (commNotExist) { //System.out.println("remove" + commChoice.getItem(i)); commChoice.remove(i); } else { continue; } } } else { //如果掃描到的commList為空,則移除所有已有串口 commChoice.removeAll(); } try { Thread.sleep(30); } catch (InterruptedException e) { String err = ExceptionWriter.getErrorInfoFromException(e); JOptionPane.showMessageDialog(null, err, "錯(cuò)誤", JOptionPane.INFORMATION_MESSAGE); System.exit(0); } } } } /** * 以內(nèi)部類形式創(chuàng)建一個(gè)串口監(jiān)聽類 * @author zhong * */ private class SerialListener implements SerialPortEventListener { /** * 處理監(jiān)控到的串口事件 */ public void serialEvent(SerialPortEvent serialPortEvent) { switch (serialPortEvent.getEventType()) { case SerialPortEvent.BI: // 10 通訊中斷 JOptionPane.showMessageDialog(null, "與串口設(shè)備通訊中斷", "錯(cuò)誤", JOptionPane.INFORMATION_MESSAGE); break; case SerialPortEvent.OE: // 7 溢位(溢出)錯(cuò)誤 case SerialPortEvent.FE: // 9 幀錯(cuò)誤 case SerialPortEvent.PE: // 8 奇偶校驗(yàn)錯(cuò)誤 case SerialPortEvent.CD: // 6 載波檢測(cè) case SerialPortEvent.CTS: // 3 清除待發(fā)送數(shù)據(jù) case SerialPortEvent.DSR: // 4 待發(fā)送數(shù)據(jù)準(zhǔn)備好了 case SerialPortEvent.RI: // 5 振鈴指示 case SerialPortEvent.OUTPUT_BUFFER_EMPTY: // 2 輸出緩沖區(qū)已清空 break; case SerialPortEvent.DATA_AVAILABLE: // 1 串口存在可用數(shù)據(jù) //System.out.println("found data"); byte[] data = null; try { if (serialPort == null) { JOptionPane.showMessageDialog(null, "串口對(duì)象為空!監(jiān)聽失敗!", "錯(cuò)誤", JOptionPane.INFORMATION_MESSAGE); } else { data = SerialTool.readFromPort(serialPort); //讀取數(shù)據(jù),存入字節(jié)數(shù)組 //System.out.println(new String(data)); // 自定義解析過程,你在實(shí)際使用過程中可以按照自己的需求在接收到數(shù)據(jù)后對(duì)數(shù)據(jù)進(jìn)行解析 if (data == null || data.length < 1) { //檢查數(shù)據(jù)是否讀取正確 JOptionPane.showMessageDialog(null, "讀取數(shù)據(jù)過程中未獲取到有效數(shù)據(jù)!請(qǐng)檢查設(shè)備或程序!", "錯(cuò)誤", JOptionPane.INFORMATION_MESSAGE); System.exit(0); } else { String dataOriginal = new String(data); //將字節(jié)數(shù)組數(shù)據(jù)轉(zhuǎn)換位為保存了原始數(shù)據(jù)的字符串 String dataValid = ""; //有效數(shù)據(jù)(用來(lái)保存原始數(shù)據(jù)字符串去除最開頭*號(hào)以后的字符串) String[] elements = null; //用來(lái)保存按空格拆分原始字符串后得到的字符串?dāng)?shù)組 //解析數(shù)據(jù) if (dataOriginal.charAt(0) == '*') { //當(dāng)數(shù)據(jù)的第一個(gè)字符是*號(hào)時(shí)表示數(shù)據(jù)接收完成,開始解析 dataValid = dataOriginal.substring(1); elements = dataValid.split(" "); if (elements == null || elements.length < 1) { //檢查數(shù)據(jù)是否解析正確 JOptionPane.showMessageDialog(null, "數(shù)據(jù)解析過程出錯(cuò),請(qǐng)檢查設(shè)備或程序!", "錯(cuò)誤", JOptionPane.INFORMATION_MESSAGE); System.exit(0); } else { try { //更新界面Label值 /*for (int i=0; i<elements.length; i++) { System.out.println(elements[i]); }*/ //System.out.println("win_dir: " + elements[5]); tem.setText(elements[0] + " ℃"); hum.setText(elements[1] + " %"); pa.setText(elements[2] + " hPa"); rain.setText(elements[3] + " mm"); win_sp.setText(elements[4] + " m/s"); win_dir.setText(elements[5] + " °"); } catch (ArrayIndexOutOfBoundsException e) { JOptionPane.showMessageDialog(null, "數(shù)據(jù)解析過程出錯(cuò),更新界面數(shù)據(jù)失??!請(qǐng)檢查設(shè)備或程序!", "錯(cuò)誤", JOptionPane.INFORMATION_MESSAGE); System.exit(0); } } } } } } catch (ReadDataFromSerialPortFailure | SerialPortInputStreamCloseFailure e) { JOptionPane.showMessageDialog(null, e, "錯(cuò)誤", JOptionPane.INFORMATION_MESSAGE); System.exit(0); //發(fā)生讀取錯(cuò)誤時(shí)顯示錯(cuò)誤信息后退出系統(tǒng) } break; } } } }
運(yùn)行截圖:
整個(gè)項(xiàng)目源碼打包下載:http://xiazai.jb51.net/201612/yuanma/javaserialMonitor(jb51.net).rar
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(20)
下面小編就為大家?guī)?lái)一篇Java基礎(chǔ)的幾道練習(xí)題(分享)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧,希望可以幫到你2021-07-07Java中do-while循環(huán)的使用方法及注意事項(xiàng)詳解
這篇文章主要介紹了Java中do-while循環(huán)的使用方法及注意事項(xiàng)的相關(guān)資料,在Java編程中,do-while循環(huán)是一種基本的循環(huán)控制結(jié)構(gòu),它至少執(zhí)行一次循環(huán)體,然后根據(jù)條件判斷是否繼續(xù),文中將用法介紹的非常詳細(xì),需要的朋友可以參考下2024-10-10Mybatis中ResultMap解決屬性名和數(shù)據(jù)庫(kù)字段名不一致問題
我們Pojo類的屬性名和數(shù)據(jù)庫(kù)中的字段名不一致的現(xiàn)象時(shí)有發(fā)生,本文就詳細(xì)的介紹一下Mybatis中ResultMap解決屬性名和數(shù)據(jù)庫(kù)字段名不一致問題,感興趣的可以了解一下2021-10-10springboot接口參數(shù)為L(zhǎng)ist的問題
這篇文章主要介紹了springboot接口參數(shù)為L(zhǎng)ist的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11Java調(diào)用接口如何獲取json數(shù)據(jù)解析后保存到數(shù)據(jù)庫(kù)
這篇文章主要介紹了Java調(diào)用接口如何獲取json數(shù)據(jù)解析后保存到數(shù)據(jù)庫(kù)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03Spring Security6配置方法(廢棄WebSecurityConfigurerAdapter)
本文主要介紹了Spring Security6配置方法(廢棄WebSecurityConfigurerAdapter),就像文章標(biāo)題所說(shuō)的,SpringSecurity已經(jīng)廢棄了繼承WebSecurityConfigurerAdapter的配置方式,下面就來(lái)詳細(xì)的介紹一下,感興趣的可以了解一下2023-12-12mybatis中的擴(kuò)展實(shí)現(xiàn)源碼解析
這篇文章主要介給大家紹了關(guān)于mybatis中擴(kuò)展實(shí)現(xiàn)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-01-01