Java使用RXTX進行串口SerialPort通訊

RXTX簡介

RXTX是一個提供串口和並口通信的開源java類庫,由該項目發佈的文件均遵循LGPL協議。
RXTX項目提供了Windows,Linux,Mac os X,Solaris操作系統下的兼容javax.comm串口通訊包API的實現,爲其他開發人員在此類系統下開發串口應用提供了相當的方便。
RXTX的使用上與sun提供的comm.jar基本相同,編程時最明顯的不同是要包含的包名由javax.comm.改成了gnu.io.

RxtxAPI 的核心是抽象的CommPort類(用於描述一個被底層系統支持的端口的抽象類,它包含一些高層的IO控制方法,這些方法對於所有不同的通訊端口來說是通用的)及其兩個子類:SerialPort類和ParallePort類。其中,SerialPort類是用於串口通信的類,ParallePort類是用於並行口通信的類。CommPort類還提供了常規的通信模式和方法,例如:getInputStream( )方法和getOutputStream( )方法,專用於與端口上的設備進行通信。
然而,這些類的構造方法都被有意的設置爲非公有的(non-public)。所以,不能直接構造對象,而是先通過靜態的CommPortIdentifer.getPortIdentifiers()獲得端口列表,再從這個端口列表中選擇所需要的端口,並調用CommPortIdentifer對象的Open( )方法,這樣,就能得到一個CommPort對象。當然,還要將這個CommPort對象的類型轉換爲某個非抽象的子類,表明是特定的通訊設備,該子類可以是SerialPort類和ParallePort類中的一個。下面將分別對CommPortIdentifier類,串口類SerialPort進行詳細的介紹。

接口
 
CommDriver可負載設備(the loadable device)驅動程序接口的一部分
 
CommPortOwnershipListener傳遞各種通訊端口的所有權事件
 
ParallelPortEventListener傳遞並行端口事件
 
SerialPortEventListener傳遞串行端口事件
 
類
 
CommPort通訊端口
 
CommPortIdentifier通訊端口管理
 
ParallelPort並行通訊端口
 
ParallelPortEvent並行端口事件
 
SerialPortRS-232串行通訊端口
 
SerialPortEvent 串行端口事件
 
異常類
 
NoSuchPortException當驅動程序不能找到指定端口時拋出
 
PortInUseException當碰到指定端口正在使用中時拋出
 
UnsupportedCommOperationException驅動程序不允許指定操作時拋出
 
CommPortIdentifier類
 
這個類主要用於對通信端口進行管理和設置,是對端口進行訪問控制的核心類,主要包括以下方法:
 
addPortName(String,int, CommDriver) 添加端口名到端口列表裏
 
addPortOwnershipListener(CommPortOwnershipListener)添加端口擁有的監聽器
 
removePortOwnershipListener(CommPortOwnershipListener)移除端口擁有的監聽器
 
getCurrentOwner()獲取當前佔有端口的對象或應用程序
 
getName()獲取端口名稱
 
getPortIdentifier(CommPort)獲取指定打開的端口的CommPortIdentifier類型對象
 
getPortIdentifier(String)獲取以參數命名的端口的CommPortIdentifier類型對象
 
getPortIdentifiers()獲取系統中的端口列表
 
getPortType()獲取端口的類型
 
isCurrentlyOwned()判斷當前端口是否被佔用
 
open(FileDescriptor)用文件描述的類型打開端口
 
open(String,int) 打開端口,兩個參數:程序名稱,延遲時間(毫秒數)
 
SerialPort類
 
這個類用於描述一個RS-232串行通信端口的底層接口,它定義了串口通信所需的最小功能集。通過它,用戶可以直接對串口進行讀、寫及設置工作。
 
SerialPort類中關於串口參數的靜態成員變量說明:
 
DATABITS_5 數據位爲5
 
DATABITS_6 數據位爲6
 
DATABITS_7 數據位爲7
 
DATABITS_8 數據位爲8
 
PARITY_NONE 空格檢驗
 
PARITY_ODD 奇檢驗
 
PARITY_EVEN 偶檢驗
 
PARITY_MARK 標記檢驗
 
PARITY_SPACE 無檢驗
 
STOPBITS_1 停止位爲1
 
STOPBITS_2 停止位爲2
 
STOPBITS_1_5 停止位爲1.5
 
 
 
SerialPort類中關於串口參數的方法說明:
 
getBaudRate()得到波特率
 
getParity()得到檢驗類型
 
getDataBits()得到數據位數
 
getStopBits()得到停止位數
 
setSerialPortParams(int,int, int, int) 設置串口參數依次爲(波特率,數據位,停止位,奇偶檢驗)
 
SerialPort類中關於事件的靜態成員變量說明:
 
BI Break interrupt 通訊中斷
 
FE Framing error 幀錯誤
 
CD Carrier detect 載波偵聽
 
OE Overrun error 溢位錯誤
 
CTS Clear to send 清除發送
 
PE Parity error 奇偶檢驗錯誤
 
DSR Data set ready 數據設備準備好
 
RI Ring indicator 響鈴偵測
 
DATA_AVAILABLE 串口中的可用數據
 
OUTPUT_BUFFER_EMPTY 輸出緩衝區已清空
 
 
 
SerialPort類中關於事件的方法說明:
 
isCD()是否有載波
 
isCTS()是否清除以傳送
 
isDSR()數據是否備妥
 
isDTR()是否數據端備妥
 
isRI()是否響鈴偵測
 
isRTS()是否要求傳送
 
addEventListener(SerialPortEventListener)向SerialPort對象中添加串口事件監聽器
 
removeEventListener()移除SerialPort對象中的串口事件監聽器
 
notifyOnBreakInterrupt(boolean)設置中斷事件true有效,false無效
 
notifyOnCarrierDetect(boolean)設置載波監聽事件true有效,false無效
 
notifyOnCTS(boolean)設置清除發送事件true有效,false無效
 
notifyOnDataAvailable(boolean)設置串口有數據的事件true有效,false無效
 
notifyOnDSR(boolean)設置數據備妥事件true有效,false無效
 
notifyOnFramingError(boolean)設置發生錯誤事件true有效,false無效
 
notifyOnOutputEmpty(boolean)設置發送緩衝區爲空事件true有效,false無效
 
notifyOnParityError(boolean)設置發生奇偶檢驗錯誤事件true有效,false無效
 
notifyOnRingIndicator(boolean)設置響鈴偵測事件true有效,false無效
 
getEventType()得到發生的事件類型返回值爲int型
 
sendBreak(int)設置中斷過程的時間,參數爲毫秒值
 
setRTS(boolean)設置或清除RTS位
 
setDTR(boolean)設置或清除DTR位
 
SerialPort中的其他常用方法說明:
 
close()關閉串口
 
getOutputStream()得到OutputStream類型的輸出流
 
getInputStream()得到InputStream類型的輸入流

準備工作

  • RXTX包:rxtx-2.2pre2-bins.zip
  • 串口虛擬工具:vspd.exe
  • 串口調試工具:amcktszs_v2.4.0.0.exe

安裝RXTX包

解壓rxtx-2.2pre2-bins.zip,將RXTXcomm.jar加入項目依賴庫裏,對應操作的系統的rxtxSerial.dll和rxtxParallel.dll文件放入jdk的bin目錄下

 

前提條件:maven已經加入環境變量中
mvn install:install-file -DgroupId=gnu.io -DartifactId=RXTXcomm -Dversion=1.0 -Dpackaging=jar -Dfile=E:\Work\Yotrio\libs\RXTXcomm.jar

工具創建一對虛擬串口

Image.png

打開串口調試工具,配置串口參數

Image [2].png

樣例Demo

封裝串口讀寫工具類

 


/**
 * 模塊名稱:projects-parent com.yotrio.common
 * 功能說明:串口服務類,提供打開、關閉串口,讀取、發送串口數據等服務(採用單例設計模式)
 * <br>
 * 開發人員:Wangyq
 * 創建時間: 2018-09-20 10:05
 * 系統版本:1.0.0
 **/

public class SerialPortUtil {

    private static SerialPortUtil serialPortUtil = null;

    static {
        //在該類被ClassLoader加載時就初始化一個SerialTool對象
        if (serialPortUtil == null) {
            serialPortUtil = new SerialPortUtil();
        }
    }

    //私有化SerialTool類的構造方法,不允許其他類生成SerialTool對象
    private SerialPortUtil() {
    }

    /**
     * 獲取提供服務的SerialTool對象
     *
     * @return serialPortUtil
     */
    public static SerialPortUtil getSerialPortUtil() {
        if (serialPortUtil == null) {
            serialPortUtil = new SerialPortUtil();
        }
        return serialPortUtil;
    }


    /**
     * 查找所有可用端口
     *
     * @return 可用端口名稱列表
     */
    public static final ArrayList<String> findPort() {
        //獲得當前所有可用串口
        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 波特率
     * @param databits 數據位
     * @param parity   校驗位(奇偶位)
     * @param stopbits 停止位
     * @return 串口對象
     * @throws SerialPortParameterFailure 設置串口參數失敗
     * @throws NotASerialPort             端口指向設備不是串口類型
     * @throws NoSuchPort                 沒有該端口對應的串口設備
     * @throws PortInUse                  端口已被佔用
     */
    public static final SerialPort openPort(String portName, int baudrate, int databits, int parity, int stopbits) throws SerialPortParameterFailure, NotASerialPort, NoSuchPort, PortInUse {

        try {
            //通過端口名識別端口
            CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName);

            //打開端口,並給端口名字和一個timeout(打開操作的超時時間)
            CommPort commPort = portIdentifier.open(portName, 2000);

            //判斷是不是串口
            if (commPort instanceof SerialPort) {

                SerialPort serialPort = (SerialPort) commPort;
                try {
                    //設置一下串口的波特率等參數
                    serialPort.setSerialPortParams(baudrate, databits, stopbits, parity);
                } 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();
        }
    }

    /**
     * 關閉串口
     *
     * @param serialPort 待關閉的串口對象
     */
    public static void closePort(SerialPort serialPort) {
        if (serialPort != null) {
            serialPort.close();
            serialPort = null;
        }
    }

    /**
     * 往串口發送數據
     *
     * @param serialPort 串口對象
     * @param order      待發送數據
     * @throws SendDataToSerialPortFailure        向串口發送數據失敗
     * @throws SerialPortOutputStreamCloseFailure 關閉串口對象的輸出流出錯
     */
    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();
            }
        }
    }

    /**
     * 從串口讀取數據
     *
     * @param serialPort 當前已建立連接的SerialPort對象
     * @return 讀取到的數據
     * @throws ReadDataFromSerialPortFailure     從串口讀取數據時出錯
     * @throws SerialPortInputStreamCloseFailure 關閉串口對象輸入流出錯
     */
    public static byte[] readFromPort(SerialPort serialPort) throws ReadDataFromSerialPortFailure, SerialPortInputStreamCloseFailure {

        InputStream in = null;
        byte[] bytes = null;

        try {
            in = serialPort.getInputStream();
            int bufflenth = in.available();        //獲取buffer裏的數據長度

            while (bufflenth != 0) {
                bytes = new byte[bufflenth];    //初始化byte數組爲buffer中數據的長度
                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;

    }

    /**
     * 添加監聽器
     *
     * @param port     串口對象
     * @param listener 串口監聽器
     * @throws TooManyListeners 監聽類對象過多
     */
    public static void addListener(SerialPort port, SerialPortEventListener listener) throws TooManyListeners {

        try {
            //給串口添加監聽器
            port.addEventListener(listener);
            //設置當有數據到達時喚醒監聽接收線程
            port.notifyOnDataAvailable(true);
            //設置當通信中斷時喚醒中斷線程
            port.notifyOnBreakInterrupt(true);
        } catch (TooManyListenersException e) {
            throw new TooManyListeners();
        }
    }

    /**
     * 刪除監聽器
     *
     * @param port     串口對象
     * @param listener 串口監聽器
     * @throws TooManyListeners 監聽類對象過多
     */
    public static void removeListener(SerialPort port, SerialPortEventListener listener) {
        //刪除串口監聽器
        port.removeEventListener();
    }

}

整合websocket獲取並推送給前臺頁面實時顯示

 

@ServerEndpoint(value = "/websocket") //接受websocket請求路徑
@Component
public class PoundWebSocket {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 保存所有在線socket連接
     */
    private static Map<String, PoundWebSocket> webSocketMap = new LinkedHashMap<>();

    /**
     * 記錄當前在線數目
     */
    private static int count = 0;

    /**
     * 當前連接(每個websocket連入都會創建一個MyWebSocket實例
     */
    private Session session;

    /**
     * 創建監聽串口
     */
    private static SerialPort serialPort = null;

    /**
     * 創建監聽器
     */
    private static SerialPortEventListener serialPortEventListener = null;

    /**
     * 監聽串口
     */
    private static String PORT_NAME;

    /**
     * 監聽串口波特率
     */
    private static int BAUD_RATE;

    /**
     * 數據位
     */
    private static int DATA_BITS;

    /**
     * 停止位
     */
    private static int STOP_BITS;

    /**
     * 奇偶位
     */
    private static int PARITY;

    /**
     * 地磅型號
     */
    private static String MODEL;

    private static IPoundInfoService poundInfoService;

    private static ApplicationContext applicationContext;

    public static void setApplicationContext(ApplicationContext applicationContext) {
        PoundWebSocket.applicationContext = applicationContext;
    }

    private static StringBuffer stringBuffer = new StringBuffer();

    /**
     * 處理連接建立
     *
     * @param session
     */
    @OnOpen
    public void onOpen(Session session) {
        if (poundInfoService == null) {
            poundInfoService = applicationContext.getBean(IPoundInfoService.class);
        }
        //獲取地磅信息
        PoundInfo poundInfo = poundInfoService.findOne();
        PORT_NAME = poundInfo.getSerialPort();
        BAUD_RATE = poundInfo.getBaudRate();
        MODEL = poundInfo.getModel();
        DATA_BITS = poundInfo.getDataBits() != null ? poundInfo.getDataBits() : SerialPort.DATABITS_8;
        STOP_BITS = poundInfo.getStopBits() != null ? poundInfo.getStopBits() : SerialPort.STOPBITS_1;
        PARITY = poundInfo.getParity() != null ? poundInfo.getParity() : SerialPort.PARITY_NONE;

        this.session = session;
        webSocketMap.put(session.getId(), this);
        addCount();
//        logger.info("新的連接加入:{}", session.getId());
        try {
            //確保串口已被關閉,未關閉會導致重新監聽串口失敗
            if (serialPort != null) {
                SerialPortUtil.closePort(serialPort);
                serialPort = null;
            }
            //創建串口 COM5位串口名稱 9600波特率
            if (serialPort == null && StringUtils.isNotEmpty(PORT_NAME) && StringUtils.isNotEmpty(MODEL)) {
                serialPort = SerialPortUtil.openPort(PORT_NAME, BAUD_RATE, DATA_BITS, PARITY, STOP_BITS);
//                logger.info("創建串口:{}", serialPort);
                //設置串口監聽
                SerialPortUtil.addListener(serialPort, new SerialPortEventListener() {

                    @Override
                    public void serialEvent(SerialPortEvent serialPortEvent) {
                        if (serialPortEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
                            try {
                                //讀取串口數據
                                byte[] bytes = SerialPortUtil.readFromPort(serialPort);

                                //根據型號解析字符串
                                switch (MODEL) {
                                    case PoundConstant.MODEL_XK_3190:
                                        parsingString1(bytes);
                                        break;
                                    case PoundConstant.MODEL_XK_3190_10:
                                        parsingString2(bytes);
                                        break;
                                    case PoundConstant.MODEL_D_2008:
                                        parsingString1(bytes);
                                        break;
                                    case PoundConstant.MODEL_DK_3230_D_6:
                                        parsingString3(bytes);
                                        break;
                                    case PoundConstant.MODEL_D_2009_F:
                                        parsingString4(bytes);
                                        break;
                                    default:
                                        String value = String.valueOf(Integer.valueOf(new String(bytes, "GB2312")) - RandomUtil.randomInt(1000, 10000));
                                        sendMessageToAll(value);
                                }

//                                System.out.println("收到的數據:" + new String(bytes, "GB2312") + "----" + new Date());

                            } catch (ReadDataFromSerialPortFailure readDataFromSerialPortFailure) {
                                logger.error(readDataFromSerialPortFailure.toString());
                            } catch (SerialPortInputStreamCloseFailure serialPortInputStreamCloseFailure) {
                                logger.error(serialPortInputStreamCloseFailure.toString());
                            } catch (UnsupportedEncodingException e) {
                                logger.error(e.toString());
                            } catch (IOException e) {
                                logger.error(e.toString());
                            }
                        }
                    }
                });
            }
        } catch (SerialPortParameterFailure serialPortParameterFailure) {
            logger.error(serialPortParameterFailure.toString());
        } catch (NotASerialPort notASerialPort) {
            logger.error(notASerialPort.toString());
        } catch (NoSuchPort noSuchPort) {
            logger.error(noSuchPort.toString());
        } catch (PortInUse portInUse) {
            logger.error(portInUse.toString());
        } catch (TooManyListeners tooManyListeners) {
            logger.error(tooManyListeners.toString());
        }
    }

    /**
     * 解析字符串 方法1
     *
     * @param bytes 獲取的字節碼
     */
    private void parsingString1(byte[] bytes) {
        StringBuffer sb = new StringBuffer();
        //將ASCII碼轉成字符串
        for (int i = 0; i < bytes.length; i++) {
            sb.append((char) Integer.parseInt(String.valueOf(bytes[i])));
        }

        //解析字符串
        String[] strs = sb.toString().trim().split("\\+");
        int weight = 0;
        for (int j = 0; j < strs.length; j++) {
            if (strs[j].trim().length() >= 6) {
                weight = Integer.parseInt(strs[j].trim().substring(0, 6));
                //發送數據
                sendMessageToAll(String.valueOf(weight));
                break;
            }
        }
    }

    /**
     * 解析字符串 方法2
     *
     * @param bytes 獲取的字節碼
     */
    private void parsingString2(byte[] bytes) {
        StringBuffer sb = new StringBuffer();
        //將ASCII碼轉成字符串
        for (int i = 0; i < bytes.length; i++) {
            sb.append((char) Integer.parseInt(String.valueOf(bytes[i])));
        }
        //解析字符串
        String[] strs = sb.toString().trim().split("\\+");
        double weight = 0;
        for (int j = 0; j < strs.length; j++) {
            if (strs[j].trim().length() >= 6) {
                weight = Double.parseDouble(strs[j].trim().substring(0, 6)) / 10;
                //發送數據
                sendMessageToAll(String.valueOf(weight));
                break;
            }
        }
    }

    /**
     * 解析字符串 方法3
     *
     * @param bytes 獲取的字節碼
     */
    private void parsingString3(byte[] bytes) {
        StringBuffer sb = new StringBuffer();
        //將ASCII碼轉成字符串
        for (int i = 0; i < bytes.length; i++) {
            sb.append((char) Integer.parseInt(String.valueOf(bytes[i])));
        }

//        logger.info("sb:" + sb.toString());
        sb.reverse();

        //解析字符串
        String[] strs = sb.toString().trim().split("\\=");
        double weight = 0;
        for (int j = 0; j < strs.length; j++) {
            if (strs[j].trim().length() >= 6) {
                weight = Double.parseDouble(strs[j].trim());
                //發送數據
                sendMessageToAll(String.valueOf(weight));
                break;
            }
        }
    }

    /**
     * 解析字符串 方法3
     *
     * @param bytes 獲取的字節碼
     */
    private void parsingString4(byte[] bytes) {
        StringBuffer sb = new StringBuffer();
        //將ASCII碼轉成字符串
        for (int i = 0; i < bytes.length; i++) {
            sb.append((char) Integer.parseInt(String.valueOf(bytes[i])));
        }

//        logger.info("sb:" + sb.reverse());
        //字符串反轉
        sb.reverse();

        //解析字符串
        String[] strs = sb.toString().trim().split("\\=");
        int weight = 0;
        for (int j = 0; j < strs.length; j++) {
            if (strs[j].trim().length() >= 6) {
                weight = Integer.parseInt(strs[j].trim().substring(0, 6));
                //發送數據
                sendMessageToAll(String.valueOf(weight));
                break;
            }
        }
    }

    /**
     * 接受消息
     *
     * @param message
     * @param session
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        logger.info("收到客戶端{}消息:{}", session.getId(), message);
        try {
            this.sendMessage(message);
        } catch (Exception e) {
            logger.error(e.toString());
        }
    }

    /**
     * 處理錯誤
     *
     * @param error
     * @param session
     */
    @OnError
    public void onError(Throwable error, Session session) {
        logger.info("發生錯誤{},{}", session.getId(), error.getMessage());
    }

    /**
     * 處理連接關閉
     */
    @OnClose
    public void onClose() {
        webSocketMap.remove(this.session.getId());
        reduceCount();
        logger.info("連接關閉:{}", this.session.getId());

        //連接關閉後關閉串口,下一次打開連接重新監聽串口
        if (serialPort != null) {
            SerialPortUtil.closePort(serialPort);
            serialPort = null;
        }
    }

    /**
     * 羣發消息
     *
     * @param message
     */
    public void sendMessageToAll(String message) {
        for (int i = 0; i < webSocketMap.size(); i++) {
            try {
//                logger.info("session:id=" + session.getId());
                this.session.getBasicRemote().sendText(message);
            } catch (IOException e) {
                logger.error(e.getMessage());
            }
        }
    }

    /**
     * 發送消息
     *
     * @param message
     * @throws IOException
     */
    public void sendMessage(String message) throws IOException {
//        logger.info("session:id=" + session.getId());
        this.session.getBasicRemote().sendText(message);
    }

    //廣播消息
    public static void broadcast() {
        PoundWebSocket.webSocketMap.forEach((k, v) -> {
            try {
                v.sendMessage("這是一條測試廣播");
            } catch (Exception e) {
            }
        });
    }

    //獲取在線連接數目
    public static int getCount() {
        return count;
    }

    //操作count,使用synchronized確保線程安全
    public static synchronized void addCount() {
        PoundWebSocket.count++;
    }

    public static synchronized void reduceCount() {
        PoundWebSocket.count--;
    }
}



作者:小土豆哥哥
鏈接:
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章