基於RXTXcomm的串列埠通訊及異常修復
阿新 • • 發佈:2020-08-17
串列埠通訊同步顯示及異常修復
依賴第三方jar包:RXTXcomm.jar (下載見文末連結)
一、程式碼分析:
step_1: 獲取埠
/** * 檢測並獲取當前裝置所有的可用埠(此處可包括USB埠和藍芽埠) * @return 返回包含所有可用埠的名稱的列表(如COM4、COM6等) * 可將返回的列表依次輸出以檢視 * 當然也可以通過‘裝置管理器-埠’來檢視可用埠 */ public ArrayList<String> findPorts() { // 呼叫jar包內的getPortIdentifiers函式,獲得當前所有可用埠的列舉 Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers(); ArrayList<String> portNameList = new ArrayList<String>(); // 將可用埠名新增到List並返回該List while (portList.hasMoreElements()) { String portName = portList.nextElement().getName(); portNameList.add(portName); } return portNameList; }
step_2: 開啟串列埠
/** * 通過上一步獲取的埠名來開啟串列埠並設定串列埠引數 * @param portName 埠名 * @param baudrate 波特率(需與電子秤的波特率一致,一般為9600,建議作為final巨集觀常量放在程式開頭) * @return 返回開啟的串列埠,若非串列埠則返回null * @throws PortInUseException 當埠已被佔用時丟擲異常 */ public SerialPort openPort(String portName, int baudrate) throws PortInUseException { try { // 通過埠名識別埠 CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName); // 開啟埠,並給埠名字和一個timeout(開啟操作的超時時間) CommPort commPort = portIdentifier.open(portName, 2000); // 判斷埠是不是串列埠 if (commPort instanceof SerialPort) { SerialPort serialPort = (SerialPort) commPort; try { // 設定一下串列埠的波特率等引數 // 資料位:8 // 停止位:1 // 校驗位:None serialPort.setSerialPortParams(baudrate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); } catch (UnsupportedCommOperationException e) { e.printStackTrace(); } return serialPort; } } catch (NoSuchPortException e1) { e1.printStackTrace(); } return null; }
step_3: 新增串列埠事件監聽
/** * 為開啟的串列埠新增資料到達事件監聽、通訊中斷監聽 * @param serialPort 已開啟的串列埠 * @param listener 監聽器 */ public void addListener(SerialPort serialPort, DataAvailableListener listener) { try { /** * 給串列埠新增監聽器 * 函式addEventListener為jar包自帶函式 * 函式addEventListener的引數listener必須為SerialPortEventListener型別 * 所以DataAvailableListener必須實現SerialPortEventListener介面 */ serialPort.addEventListener(listener); // 設定當有資料到達時喚醒監聽接收執行緒 serialPort.notifyOnDataAvailable(true); // 設定當通訊中斷時喚醒中斷執行緒 serialPort.notifyOnBreakInterrupt(true); } catch (TooManyListenersException e) { e.printStackTrace(); } catch (NullPointerException e) { e.printStackTrace(); } } /** * 自定義監聽器,實現jar包中定義的SerialPortEventListener介面,並覆寫serialEvent方法 */ public class DataAvailableListener implements SerialPortEventListener { @Override public void serialEvent(SerialPortEvent serialPortEvent) { /** * 總共有10類事件可以監聽 * 此處只對兩類事件進行了反應和處理 */ switch (serialPortEvent.getEventType()) { case SerialPortEvent.DATA_AVAILABLE: //接收到資料事件 byte[] data; try { if (mSerialport == null) { System.out.println("串列埠物件為空,監聽失敗!"); } else { // 讀取串列埠資料 data = readFromPort(mSerialport); // 將ASCII碼陣列轉化為對應的字串 String text = new String(data); // 去除不必要的字元 text = text.replaceAll(" ", ""); text = text.replaceAll("\r", ""); text = text.replaceAll("\n", ""); text = text.replaceAll("\t", ""); if (text.length() > 0) { //將處理後的重量資訊列印輸出 System.out.println(text); } } } catch (Exception e) { System.out.println(e.toString()); // 發生讀取錯誤時顯示錯誤資訊後退出系統 System.exit(0); } break; case SerialPortEvent.OUTPUT_BUFFER_EMPTY: // 2.輸出緩衝區已清空 break; case SerialPortEvent.CTS: // 3.清除待發送資料 break; case SerialPortEvent.DSR: // 4.待發送資料準備好了 break; case SerialPortEvent.RI: // 5.振鈴指示 break; case SerialPortEvent.CD: // 6.載波檢測 break; case SerialPortEvent.OE: // 7.溢位(溢位)錯誤 break; case SerialPortEvent.PE: // 8.奇偶校驗錯誤 break; case SerialPortEvent.FE: // 9.幀錯誤 break; case SerialPortEvent.BI: // 10.通訊中斷 System.out.println("與串列埠裝置通訊中斷"); System.exit(0); break; default: break; } } }
step_4: 從串列埠讀取收到的資料
/**
* 從串列埠中按位元組讀取收到的資料
* @param serialPort 開啟後有資料傳達的串列埠
* @return 以位元組陣列的形式返回收到的資料資訊
*/
public byte[] readFromPort(SerialPort serialPort) {
InputStream in = null;
byte[] bytes = {};// 採用位元組陣列儲存傳來的ASCII碼值,方便之後轉化為字串
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);//將讀取到的二進位制資料存於readBuffer並返回讀取到的位元組數
}//按照位元組將資料加入到位元組陣列中
} catch (IOException e) {
restart(); //捕獲異常並讀取
} finally {
try {
if (in != null) {
in.close();
in = null;
}
} catch (IOException e) {
restart(); //捕獲異常並讀取
}
}
return bytes;
}
/**
* 將兩位元組數組合併為同一個
* @param firstArray
* @param secondArray
* @return 返回合併後的位元組陣列
*/
public byte[] concat(byte[] firstArray, byte[] secondArray) {
if (firstArray == null || secondArray == null) {
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;
}
主函式:
private SerialPort mSerialport = null;
private final int BAUDRATE = 9600;// 波特率,預設為9600
public static void main(String[] args) {
String commName = null;
if (findPorts().size() > 0) {
// 獲取埠名稱,預設取第一個埠
commName = findPorts().get(0); // step_1
}
if (commName == null) {// 說明不存在可用埠
System.out.println("沒有搜尋到有效埠!");
} else {
try {
mSerialport = openPort(commName, BAUDRATE); // step_2
if (mSerialport != null) {
System.out.println("串列埠已開啟");
}
} catch (PortInUseException e) {
System.out.println("串列埠已被佔用!");
}
// 新增串列埠監聽
addListener(mSerialport, new DataAvailableListener()); // step_3、step_4
}
}
二、問題與解決
1、問題描述
在程式執行約2~3分鐘後會按照一定週期出現如下異常,並中斷執行
2、原因分析
主要是兩種錯誤:
-
第一個是 IOException 異常,是在呼叫 readFromPort 函式從串列埠讀取資料的過程中,從更底層被丟擲後在 readFromPort 函式中被捕獲的。
-
第二個 Error 也是從底層的.c檔案中出的錯,右側的亂碼 "�ܾ����ʡ�" 翻譯成 GBK 編碼後是 "拒絕訪問" 。
-
可見這些錯誤來自於jar包的底層程式碼,於是有兩種解決思路:
-
- 除錯修改jar包的內部程式碼
-
- 考慮用於串列埠通訊的其他java解決方案,不用RXTX
-
- 採用一些上層操作掩蓋底層報錯
-
3、解決辦法
因為無意間發現當出現以上報錯使執行中斷時,如果能關閉串列埠然後再次開啟串列埠,此時又能成功接收到資料並顯示,雖然之後還會繼續出現報錯,但是每次報錯都能通過對串列埠的重啟來解決,同時考慮到報錯具有一定的週期性,因此考慮新建一個執行緒來週期性地對埠進行重啟,具體程式碼如下:
private Thread restartThread = new Thread(new RestartThread());
public void restart() { //在 step_4 的 readFromPort 函式中捕獲 IOException 後執行
if (!restartThread.isAlive() || restartThread.isInterrupted()) {
restartThread.start();
}
}
class RestartThread implements Runnable {
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
closeSerialPort();
try {
Thread.sleep(400);
} catch (InterruptedException e) {
e.printStackTrace();
}
openSerialPort();
}
}
}
public void closeSerialPort() {
if (mSerialport != null) {
mSerialport.close();
}
mSerialport = null;
}
public void openSerialPort() {
String commName = null;
if (findPorts().size() > 0) {
// 獲取埠名稱,預設取第一個埠
commName = findPorts().get(0); // step_1
}
if (commName == null) {// 說明不存在可用埠
System.out.println("沒有搜尋到有效埠!");
} else {
try {
mSerialport = openPort(commName, BAUDRATE); // step_2
if (mSerialport != null) {
System.out.println("串列埠已開啟");
}
} catch (PortInUseException e) {
System.out.println("串列埠已被佔用!");
}
// 新增串列埠監聽
addListener(mSerialport, new DataAvailableListener()); // step_3、step_4
}
}
並將主函式修改為:
private SerialPort mSerialport = null;
private final int BAUDRATE = 9600;// 波特率,預設為9600
public static void main(String[] args) {
openSerialPort();
}
4、效果分析
採用這種方式,當遇到第一個IOException時,就會按照一定的週期重啟重新整理視窗,可以看到控制檯在不斷的重新整理,雖然時常會出現Error,但並不會影響資料的顯示,串列埠仍然會正常的接受並將資料顯示在控制檯,因此,在我們的實際應用中,我們不需要關注控制檯的輸出,只需要將重量的資料傳達給我們所需要的顯示的前端,這樣一來,前端仍然能正常顯示資料,後端控制檯的報錯異常就這樣被掩蓋了。
原始碼及jar包連結:
https://gitee.com/LarryHawkingYoung/RXTXcomm_SerialPort_BugResolved.git