Java comm串口通信Utils类

原文链接:https://www.iteye.com/blog/hwei199-2251685

Java comm串口通信Utils类

javax.comm包提供了java原生的串口通信API,实际中用到的场景很多,例如很多设备的控制信号都是通过串口进行控制的,只要向指定串口发送指定消息,就可以控制设备或读取设备信息,例如读取温度传感器信息、控制自动贩卖机出货等等。

使用javax.comm进行串口通信大概分为以下几个步骤:

1、选择一个可利用串口如COM1,得到一个CommPortIdentifier类。

2、设置初始化参数(波特率、数据位、停止位、校验位),利用CommPortIdentifier.open()方法得到一个SerialPort。

3、利用SerialPort.getOutputStream得到串口输出流,向串口写入数据。

4、利用SerialPort.addEventListener(SerialPortEventListener listener)为串口添加监听事件,当串口返回数据时,在SerialPortEventListener监听器的public void serialEvent(SerialPortEvent arg0)方法中,通过SerialPort.getInputStream得到串口输入流来读取响应数据。

这里提供一个串口通信Utils类。由于串口的通信机制,

SerialPortEventListener监听器每接收到8个字节时会调用一次serialEvent,因此在数据未读取完毕时需要追加读入字节数组,这样才能取到完整的返回字节数组。

源码如下,代码中的Log记录类和自定义Exception类请自行替换后通过编译。
Util类 SerialPortCommUtil:

package com.ktvm.common.serial;  
  
import java.io.BufferedInputStream;  
import java.io.BufferedOutputStream;  
import java.io.IOException;  
import java.io.InputStream;  
import java.io.OutputStream;  
import java.util.Enumeration;  
import java.util.Timer;  
import java.util.TimerTask;  
  
import javax.comm.CommPortIdentifier;  
import javax.comm.SerialPort;  
import javax.comm.SerialPortEvent;  
import javax.comm.SerialPortEventListener;  
  
import com.ktvm.common.exception.KtVendExceptoin;  
import com.ktvm.common.util.LogUtil;  
import com.ktvm.common.util.SysConstant;  
  
/** 
 * @author weih535 
 * 温度监控器 
 */  
public class SerialPortCommUtil implements SerialPortEventListener{  
    private SerialPort serialPort;  
    private InputStream inputStream;  
    private OutputStream outputStream;  
    private CommPortIdentifier commPort;  
    private int timeout = 2000; // open 端口时的等待时间  
    /**串口响应时间*/  
    private int responseTime;  
    private String portName;    //端口名称  
    private int baudRate;       //波特率  
    private int dataBits=8;     //数据位  
    private int stopBit=1;      //停止位  
    private int verifyBit=0;    //检验位  
      
    private int totalDataLen = 0;   //串口返回字节数据长度  
      
    private int readlength = 0;  
    private boolean initTest = false;  
      
    private int callbackTimes; //串口通信当前回调次数  
    byte[] readBuffer = null;  
    private boolean enableLog = false;  
    private boolean isWatting = false; //是否正在等待串口数据返回  
    private boolean isTimeout = false;  
    private static final String VERIFYSTRING = "FE 04 06"; //校验字串  
      
    /**定时器*/  
    private Timer timer;  
      
    private SerialPortWatcher watcher;  
      
      
    public SerialPortCommUtil(String portName, int baudRate, int dataBits,  
            int stopBit, int verifyBit) {  
        this.portName = portName.toUpperCase();  
        this.baudRate = baudRate;  
        this.dataBits = dataBits;  
        this.stopBit = stopBit;  
        this.verifyBit = verifyBit;  
        this.enableLog = Boolean.valueOf(PropUtil.getInstance().getProperty("enableLog"));  
    }  
  
    /** 
     * 初始化串口通信 
     */  
    public void initSerial() {  
        LogUtil.info(LogUtil.INFO_SERIAL + "SerialCommunication.initSerial 串口通信初始化!");  
        responseTime = SysConstant.SERIAL_RESPONSE_TIME * 1000;  
        try {  
            if(!initTest)  
                listPort();  
            selectPort(portName);  
            resetCallbackTimes();  
            readBuffer = null;   
            initTest = true;  
        } catch (KtVendExceptoin e) {  
            LogUtil.error(e, e.getMessage());  
            e.getMessage();  
        }  
    }  
      
    /** 
     * 列出所有可用的串口 
     *  
     * @return :void 
     */  
    @SuppressWarnings("rawtypes")  
    public void listPort() {  
        if(enableLog){  
            LogUtil.info(LogUtil.INFO_SERIAL + "SerialCommunication.listPort 列出所有可用的串口 ");  
            CommPortIdentifier cpid;  
            Enumeration en = CommPortIdentifier.getPortIdentifiers();  
      
            while (en.hasMoreElements()) {  
                cpid = (CommPortIdentifier) en.nextElement();  
                if (cpid.getPortType() == CommPortIdentifier.PORT_SERIAL) {  
                    LogUtil.debug(cpid.getName() + ", " + cpid.getCurrentOwner());  
                }  
            }  
        }  
    }  
  
    /** 
     * 选择一个端口(比如:COM1) 
     *  
     * @param portName 
     * @return :void 
     */  
    @SuppressWarnings("rawtypes")  
    public void selectPort(String portName) {  
        LogUtil.info(LogUtil.INFO_SERIAL + "选择端口: " + portName);  
        this.commPort = null;  
        CommPortIdentifier cpid;  
        Enumeration en = CommPortIdentifier.getPortIdentifiers();  
        while (en.hasMoreElements()) {  
            cpid = (CommPortIdentifier) en.nextElement();  
            if (cpid.getPortType() == CommPortIdentifier.PORT_SERIAL && cpid.getName().equals(portName)) {  
                this.commPort = cpid;  
                break;  
            }  
        }  
        openPort(portName);  
    }  
  
    /** 
     * 打开SerialPort 
     *  
     * @return :void 
     */  
    private void openPort(String portName) {  
        if (commPort == null)  
            throw new KtVendExceptoin(String.format("无法找到名字为'%1$s'的串口!", portName));  
        else {  
            if(enableLog)  
                LogUtil.debug("打开端口:" + commPort.getName() + ",现在实例化 SerialPort:");  
            try {  
                serialPort = (SerialPort) commPort.open("TemperatureMonitor", timeout);  
                serialPort.setSerialPortParams(baudRate,   
                        SysConstant.SERIAL_DATA_BITS,   
                        SysConstant.SERIAL_STOP_BITS,   
                        SysConstant.SERIAL_PARITY);  
                  
                serialPort.addEventListener(this);  
                if(enableLog)  
                    LogUtil.debug("实例 SerialPort 成功!");  
            } catch (Exception e) {  
                LogUtil.error(e);  
                throw new KtVendExceptoin(String.format("端口'%1$s'正在使用中!", commPort.getName()), e);  
            }  
        }  
    }  
      
    /** 
     * 数据接收的监听处理函数 
     *  
     * @return : void 
     */  
    @Override  
    public void serialEvent(SerialPortEvent arg0) {  
        if(totalDataLen == 0)  
            throw new KtVendExceptoin("返回数据长度未初始化...");  
        switch (arg0.getEventType()) {  
        case SerialPortEvent.BI:/* Break interrupt,通讯中断 */  
            break;  
        case SerialPortEvent.OE:/* Overrun error,溢位错误 */  
            break;  
        case SerialPortEvent.FE:/* Framing error,传帧错误 */  
            errLog(portName + " Framing error,传帧错误");  
            break;  
        case SerialPortEvent.PE:/* Parity error,校验错误 */  
            break;  
        case SerialPortEvent.CD:/* Carrier detect,载波检测 */  
            break;  
        case SerialPortEvent.CTS:/* Clear to send,清除发送 */  
            log(portName + " ====CTS====");  
            break;  
        case SerialPortEvent.DSR:/* Data set ready,数据设备就绪 */  
            break;  
        case SerialPortEvent.RI:/* Ring indicator,响铃指示 */  
            break;  
        case SerialPortEvent.OUTPUT_BUFFER_EMPTY:/* Output buffer is empty,输出缓冲区清空*/  
            log(portName + " ====OUTPUT_BUFFER_EMPTY====");  
            break;  
        case SerialPortEvent.DATA_AVAILABLE:/* Data available at the serial port 端口有可用数据*/  
            //温度监控读取响应数据分两次返回,因此在第二次返回时接着上一次的开始读  
            LogUtil.info("callback=" + callbackTimes + "  readlength=" + readlength);  
            if(readlength  % totalDataLen == 0){  
                if(readBuffer == null)  
                    readBuffer = new byte[totalDataLen];  
                readlength = 0;  
                  
            }  
            callbackTimes++;  
              
            try {                 
                // 读取串口返回信息  
                if (inputStream!=null && inputStream.available() > 0) {  
                    readlength += inputStream.read(readBuffer, readlength, totalDataLen-readlength);  
                    if(enableLog){  
                        LogUtil.info("callback:" + callbackTimes + "--readed:" + readlength);  
                        LogUtil.info("串口返回数据[len:" +readlength +"]" + AppUtil.showByteData(readBuffer, readlength));  
                    }                     
                      
                    if(initTest) {  
                        if(readlength == totalDataLen){  
                            boolean verifyRst = verifyRtnData(readBuffer);  
                              
//                      LogUtil.info(LogUtil.INFO_SERIAL + "返回数据校验成功!");  
                            //数据读取完成时调用监视事件  
                            if(watcher != null){  
                                WatchEvent event = new WatchEvent();  
                                if(verifyRst)  
                                    event.setData(readBuffer);  
                                else  
                                    event.setData(correctData(readBuffer));  
                                watcher.doWatch(event);  
                            }  
                              
                            //准备下一次数据读取  
                            resetCallbackTimes();   
                            // 关闭定时器  
                            timer.cancel();   
                            log("set watting = false");  
                            setWatting(false);  
                            serialPort.notifyOnDataAvailable(false);  
                              
                              
                            //close input stream  
                            if(inputStream!=null){  
                                log("close inputStream...");  
                                try {  
                                    inputStream.close();  
                                    inputStream = null;  
                                } catch (IOException e) {  
                                    e.printStackTrace();  
                                }  
                            }  
                            //TODO 视情况可放开以重新初始化端口  
                            if(!verifyRst){  
                                reInitSerial();  
                            }  
                                  
                        }  
                    }  
                }  
            } catch (Exception e) {  
                e.printStackTrace();  
                LogUtil.error(new KtVendExceptoin("串口通信失败!", e));  
            }  
        }  
    }  
      
    /** 
     * 向端口发送数据 
     *  
     * @param message 
     * @return :void 
     */  
    public void write(byte[] message) {  
        setWatting(true);  
        if(enableLog)  
            LogUtil.info(LogUtil.INFO_SERIAL + portName + "发送数据Preapre:" + AppUtil.showByteData(message, 8));  
        checkPort();  
          
        serialTimeOut();  
  
        try {  
            outputStream = new BufferedOutputStream(serialPort.getOutputStream());  
        } catch (IOException e) {  
            throw new KtVendExceptoin("获取端口的OutputStream出错:" + e.getMessage(), e);  
        }  
  
        try {  
            outputStream.write(message);  
            if(enableLog)  
                LogUtil.info(LogUtil.INFO_SERIAL + portName + "发送数据成功");  
            if(inputStream == null){  
                inputStream = new BufferedInputStream(serialPort.getInputStream());  
            }  
            serialPort.notifyOnDataAvailable(true);  
              
//          startRead();  
        } catch (IOException e) {  
            throw new KtVendExceptoin(portName + "向端口发送信息时出错:" + e.getMessage(), e);  
        } finally {  
            try {  
                outputStream.close();  
            } catch (Exception e) {  
            }  
        }  
    }  
      
  
      
    /** 
     * 关闭 SerialPort 
     *  
     * @return :void 
     */  
    public void close() {  
        final Timer closeTimer = new Timer();  
        TimerTask task=new TimerTask() {   
            @Override  
            public void run() {  
                closeNow();  
                SysConstant.tempWatchTimer.cancel();  
                closeTimer.cancel();                  
            }  
        };  
        closeTimer.schedule(task, 1000);          
    }  
      
    public void closeNow(){  
        if(serialPort != null)  
            serialPort.close();  
        serialPort = null;  
        commPort = null;  
          
        if(inputStream!=null){  
            try {  
                inputStream.close();  
                inputStream = null;  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
          
        LogUtil.info(LogUtil.INFO_SERIAL + portName + "已关闭!");  
    }  
      
    /** 
     * 串口通信状态监控定时器,状态不正常时重启端口 
     */  
    private void serialTimeOut(){  
        if(timer!=null){  
            timer.cancel();  
            timer = null;  
        }  
        timer = new Timer();  
        TimerTask timerTask=new TimerTask() {   
            @Override  
            public void run() {  
                LogUtil.error(LogUtil.ERR + portName + "---串口通信" + responseTime + "秒内未返回完整数据!");  
                LogUtil.info(LogUtil.INFO_SERIAL + "---重新启动端口---" + SysConstant.spc.getPortName()  + " Start...");  
                  
                reInitSerial();  
                  
                LogUtil.info(LogUtil.INFO_SERIAL + "---重新启动端口---" + SysConstant.spc.getPortName()  + " Success...");  
                setTimeout(true);  
            }  
        };  
        timer.schedule(timerTask, responseTime);  
    }  
      
    private void reInitSerial(){  
        log("重新初始化串口" + portName + "...");  
        closeNow();  
        initSerial();  
        setWatting(false); //让端口可写   
    }  
      
    /** 校验读取到的数据是否合法 **/  
    private boolean verifyRtnData(byte[] readBuffer) {  
        if(AppUtil.getHexByteString(readBuffer, 0, 3).equals(VERIFYSTRING))  
            return true;  
        LogUtil.error(LogUtil.INFO_SERIAL + portName+"数据校验失败...");  
        return false;  
    }  
      
    /** 校验不合法时重构缓冲区数组 
串口会返回8+N个字节。N为前回未读取完的字节数。如下面的第一个FE是多节的: 
FE FE 04 06 08 00 08 06 08 
 **/  
    private byte[] correctData(byte[] buffer) {  
        LogUtil.error(LogUtil.INFO_SERIAL + portName+"重构缓冲区数组...");  
        int indexOf = AppUtil.indexOfHexByteArray(buffer, "FE 04 06");  
        byte[] newData = new byte[totalDataLen];  
        for(int i=indexOf,j=0; i < totalDataLen; i++,j++){  
            newData[j] = buffer[i];  
        }  
                readlength = readlength - indexOf;//修正已读取长度,把inputstream把剩下的数据读完,否则下次还是错误的。  
        //for(int i=0, j=totalDataLen-indexOf; i < indexOf; i++,j++){  
        //  System.out.println(j);  
        //  newData[j] = buffer[i];  
        //}  
        return newData;  
    }  
      
  
    /** 
     * 检查端口是否正确连接 
     *  
     * @return :void 
     */  
    private void checkPort() {  
        if (commPort == null) {  
            throw new KtVendExceptoin("没有找到端口!");  
        }  
  
        if (serialPort == null) {  
            throw new KtVendExceptoin("SerialPort 对象无效!");  
        }  
    }  
      
      
    private void resetCallbackTimes(){  
        log("[IN]resetCallbackTimes.");  
        this.callbackTimes = 0;  
        this.readlength = 0;  
        readBuffer = null;  
          
    }  
    public String getPortName() {  
        return portName;  
    }  
    public void setPortName(String portName) {  
        this.portName = portName.toUpperCase();  
    }  
    public int getBaudRate() {  
        return baudRate;  
    }  
    public void setBaudRate(int baudRate) {  
        this.baudRate = baudRate;  
    }  
  
    public int getDataBits() {  
        return dataBits;  
    }  
  
    public void setDataBits(int dataBits) {  
        this.dataBits = dataBits;  
    }  
  
    public int getStopBit() {  
        return stopBit;  
    }  
  
    public void setStopBit(int stopBit) {  
        this.stopBit = stopBit;  
    }  
  
    public int getVerifyBit() {  
        return verifyBit;  
    }  
  
    public void setVerifyBit(int verifyBit) {  
        this.verifyBit = verifyBit;  
    }  
  
  
    public int getRtnDataLen() {  
        return totalDataLen;  
    }  
  
    public void setRtnDataLen(int rtnDataLen) {  
        this.totalDataLen = rtnDataLen;  
        this.readlength = 0;  
    }  
  
    public void setWatcher(SerialPortWatcher watcher) {  
        this.watcher = watcher;  
    }  
  
    public boolean isWatting() {  
        return isWatting;  
    }  
  
    public void setWatting(boolean isWatting) {  
        this.isWatting = isWatting;  
    }  
      
    public boolean isTimeout() {  
        return isTimeout;  
    }  
  
    public void setTimeout(boolean isTimeout) {  
        this.isTimeout = isTimeout;  
    }  
    public int getResponseTime() {  
        return responseTime;  
    }  
  
    private void log(String str){  
        if(enableLog){  
            LogUtil.info(LogUtil.INFO_SERIAL + portName + ":" + str);  
        }  
    }  
    private void errLog(String str){  
        if(enableLog){  
            LogUtil.error(LogUtil.INFO_SERIAL + portName + ":" + str);  
        }  
    }  
}  
        private String showByteData(byte[] b, int length) {  
        String str = "";  
        for (int i=0; i<b.length && i < length; i++) {  
            str += String.format("%02X", b[i]) + " ";  
        }  
        return str;  
    }  
  
      

添加一个串口监视器接口,当串口有数据返回时,利用doWatch(WatchEvent we)方法做实际业务想做的事情:

/** 
 * @author weih535 
 * 串口监视器接口 
 */  
public interface SerialPortWatcher {  
    public void doWatch(WatchEvent event);  
}  

串口监视器具体类:

@Component  
public class TemperatureWatcher implements SerialPortWatcher {  
  
    @Resource  
    private WebServiceManager webServiceManager; //注入自己的业务service类  
      
    /* 温度监控器 
     * @see com.kotei.ktvm.common.serial.SerialPortWatcher#doWatch(com.kotei.ktvm.common.serial.WatchEvent) 
     */  
    @Override  
    public void doWatch(WatchEvent event) {  
        // TODO Auto-generated method stub  
        byte[] data = event.getData();  
        System.out.println("DoWatch:::::" + showByteData(data, data.length));  
        //调用service上传温度监控值。这里请根据自己业务需要调用业务方法。  
        ServiceResponse serviceResponse = webServiceManager.upTemperatureInfo(  
                SysConstant.UP_TEMPINFO, JsonUtil.listToJson(data));  
    }  
  
    private static String showByteData(byte[] b, int length) {  
        String str = "";  
        if(b!=null){  
            for (int i=0; i<b.length && i < length; i++) {  
                str += String.format("%02X", b[i]) + " ";  
            }  
        }  
        return str;  
    }  
  
}  

测试类:

package com.ktvm.common.serial;  
  
  
public class TestComm {  
  
    public static void main(String[] args) {  
        testSerial();  
    }  
      
    private static void testSerial() {  
          
        final SerialPortCommUtil sp = new SerialPortCommUtil("com1", 9600, 8 , 1, 0);  
                  
        sp.setRtnDataLen(11); //这里根据串口设备的API文档说明,将串口返回的字节数量设为11.(这意味着串口监听需要2跳才能接收到一组完整数据)  
          
        sp.initSerial();  
        sp.write(new byte[]{(byte)0xFE, 0x04, 0x00 ,0x00 ,0x00 ,0x03, (byte)0xA4, 0x04});  
        sp.close();  
          
        //定时写入、读取测试  
        /*final Timer closeTimer = new Timer();  
        SysConstant.spc.initSerial();  
          
        //定时写入温度串口信息  
        TimerTask task=new TimerTask() {   
            @Override  
            public void run() {               
                if(!sp.isWatting())  
                    sp.write(new byte[]{(byte)0xFE, 0x04, 0x00 ,0x00 ,0x00 ,0x03, (byte)0xA4, 0x04});  
                else  
                    LogUtil.info(LogUtil.INFO_SERIAL + SysConstant.spc.getPortName() + " is watting...");  
            }  
        };  
                  
        closeTimer.schedule(task, 1000, SysConstant.SERIAL_TEMP_INTERVAL * 1000);  
          
    }  
  
}  

运行结果如下:

2015-10-24 17:34:45:789 INFO [LogUtil.java:36] - [DEBUGINFO][SERIAL] SerialCommunication.initSerial 串口通信初始化!
2015-10-24 17:34:45:791 INFO [LogUtil.java:36] - [DEBUGINFO][SERIAL] SerialCommunication.listPort 列出所有可用的串口
2015-10-24 17:34:45:798 DEBUG [LogUtil.java:40] - COM1, Port currently not owned
2015-10-24 17:34:45:799 INFO [LogUtil.java:36] - [DEBUGINFO][SERIAL] 选择端口: COM1
2015-10-24 17:34:45:800 DEBUG [LogUtil.java:40] - 端口选择成功,当前端口:COM1,现在实例化 SerialPort:
2015-10-24 17:34:45:803 DEBUG [LogUtil.java:40] - 实例 SerialPort 成功!
2015-10-24 17:34:45:806 INFO [LogUtil.java:36] - [DEBUGINFO][SERIAL] COM1发送数据Preapre:FE 04 00 00 00 03 A4 04
2015-10-24 17:34:45:807 INFO [LogUtil.java:36] - [DEBUGINFO][SERIAL] COM1已发送数据:FE 04 00 00 00 03 A4 04
callback:0–readed:8
2015-10-24 17:34:45:833 DEBUG [LogUtil.java:40] - 串口返回数据[len:8]FE 04 06 0A DF 0A E2 0A
2015-10-24 17:34:45:833 INFO [LogUtil.java:36] - [DEBUGINFO][SERIAL] 返回数据校验成功!
callback:1–readed:11
2015-10-24 17:34:45:841 DEBUG [LogUtil.java:40] - 串口返回数据[len:11]FE 04 06 0A DF 0A E2 0A EC 14 DD
2015-10-24 17:34:45:841 INFO [LogUtil.java:36] - [DEBUGINFO][SERIAL] 返回数据校验成功!
2015-10-24 17:34:47:808 INFO [LogUtil.java:36] - [DEBUGINFO][SERIAL] COM1已关闭!


Java comm串口通信Utils类
https://blog.fulequn.online/2020/08/Article202008304/
作者
Fulequn
发布于
2020年8月30日
许可协议