Java comm串口通信Utils类

标签: java comm 串口 | 发表时间:2015-10-24 18:05 | 作者:hwei199
出处:http://www.iteye.com
    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 rtnDataLen = 0;	//串口返回字节数据长度
	private int rtnCount = 0;	//返回字节数为rtnlength时,需要回调多少次
	
	private int readlength = 0;
	private boolean initTest = false;
	
	private int callbackTimes; //串口通信当前回调次数
	byte[] readBuffer = null;
	
	/**定时器*/
	private Timer timer;
	
	
	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;
	}

	/**
	 * 初始化串口通信
	 */
	public void initSerial() {
		LogUtil.info(LogUtil.INFO_SERIAL + "SerialCommunication.initSerial 串口通信初始化!");
		responseTime = SysConstant.SERIAL_RESPONSE_TIME * 1000;
		try {
			listPort();
			selectPort(portName);
			resetCallbackTimes();
			initTest = true;
		} catch (KtVendExceptoin e) {
			LogUtil.error(e, e.getMessage());
			e.getMessage();
		}
	}
	
	/**
	 * 列出所有可用的串口
	 * 
	 * @return :void
	 */
	@SuppressWarnings("rawtypes")
	public void listPort() {
		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 {
			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);
				LogUtil.debug("实例 SerialPort 成功!");
			} catch (Exception e) {
				throw new KtVendExceptoin(String.format("端口'%1$s'正在使用中!", commPort.getName()), e);
			}
		}
	}
	
	/**
	 * 数据接收的监听处理函数
	 * 
	 * @return : void
	 */
	@Override
	public void serialEvent(SerialPortEvent arg0) {
		if(rtnDataLen == 0)
			throw new KtVendExceptoin("返回数据长度未初始化...");
		switch (arg0.getEventType()) {
		case SerialPortEvent.BI:/* Break interrupt,通讯中断 */
		case SerialPortEvent.OE:/* Overrun error,溢位错误 */
		case SerialPortEvent.FE:/* Framing error,传帧错误 */
		case SerialPortEvent.PE:/* Parity error,校验错误 */
		case SerialPortEvent.CD:/* Carrier detect,载波检测 */
		case SerialPortEvent.CTS:/* Clear to send,清除发送 */
		case SerialPortEvent.DSR:/* Data set ready,数据设备就绪 */
		case SerialPortEvent.RI:/* Ring indicator,响铃指示 */
		case SerialPortEvent.OUTPUT_BUFFER_EMPTY:/* Output buffer is empty,输出缓冲区清空*/
			break;
		case SerialPortEvent.DATA_AVAILABLE:/* Data available at the serial port 端口有可用数据*/
			//温度监控读取响应数据分两次返回,因此在第二次返回时接着上一次的开始读
			if(callbackTimes % rtnCount == 0){
				if(readBuffer == null)
					readBuffer = new byte[rtnDataLen];
				readlength = 0;
				resetCallbackTimes(); 
			}

			try {
				// 关闭定时器
				timer.cancel();
				// 读取串口返回信息
				while (inputStream.available() > 0) {
					readlength += inputStream.read(readBuffer, readlength, rtnDataLen-readlength);
				}
				System.out.println("callback:" + callbackTimes + "--readed:" + readlength);
				callbackTimes++;
				LogUtil.debug("串口返回数据[len:" +readlength +"]" + showByteData(readBuffer, readlength));
				if(initTest) {
					if(verifyRtnData(readBuffer)) {
						LogUtil.info(LogUtil.INFO_SERIAL + "返回数据校验成功!");
						// TODO write info to server
						if(callbackTimes % 2 == 0){
							//读取第二次数据上传到服务器
						}
						
					} else {
						LogUtil.error(LogUtil.INFO_SERIAL + "返回数据校验失败!");
					}
					return;
				}
				serialPort.notifyOnDataAvailable(false);
				
			} catch (Exception e) {
				e.printStackTrace();
				LogUtil.error(new KtVendExceptoin("串口通信失败!", e));
			}
		}
	}
	
	/**
	 * 向端口发送数据
	 * 
	 * @param message
	 * @return :void
	 */
	public void write(byte[] message) {
		LogUtil.info(LogUtil.INFO_SERIAL + portName + "发送数据Preapre:" + 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);
			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) {
			}
		}
	}
	

	/**
	 * 显示串口返回的数据
	 * 
	 * @param b
	 */
	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;
	}
	/**
	 * 关闭 SerialPort
	 * 
	 * @return :void
	 */
	public void close() {
		final Timer closeTimer = new Timer();
		TimerTask task=new TimerTask() { 
            @Override
            public void run() {
            	if(serialPort != null)
        			serialPort.close();
        		serialPort = null;
        		commPort = null;
        		
        		if(inputStream!=null){
					try {
						inputStream.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
        		}
        		closeTimer.cancel();
        		LogUtil.info(LogUtil.INFO_SERIAL  + portName +  "已关闭!");
            }
        };
        closeTimer.schedule(task, 2000);
		
	}
	/**
	 * 串口通信定时器(检查是否有数据返回)
	 */
	private void serialTimeOut(){
		timer = new Timer();
		TimerTask timerTask=new TimerTask() { 
            @Override
            public void run() {
                LogUtil.error(LogUtil.ERR + "串口通信5秒内没有反应!");
                if(initTest) {
					LogUtil.info(LogUtil.INFO_SERIAL + "串口测试失败!");
					timer.cancel();
					return;
				}
                //rewrite();
                timer.cancel();
            }
        };
        timer.schedule(timerTask, responseTime);
    }
	
	private boolean verifyRtnData(byte[] readBuffer) {
		return true;
	}

	/**
	 * 检查端口是否正确连接
	 * 
	 * @return :void
	 */
	private void checkPort() {
		if (commPort == null) {
			throw new KtVendExceptoin("没有找到端口!");
		}

		if (serialPort == null) {
			throw new KtVendExceptoin("SerialPort 对象无效!");
		}
	}
	
	
	private void resetCallbackTimes(){
		this.callbackTimes = 0;
	}
	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 rtnDataLen;
	}

	public void setRtnDataLen(int rtnDataLen) {
		this.rtnDataLen = rtnDataLen;
		this.rtnCount = (int)(Math.ceil((double)rtnDataLen / 8));
		System.out.println("rtnCount:" + rtnCount);
	}
	
	
	
	
	
}

    



测试类:
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);
		
		sp.initSerial();
		sp.write(new byte[]{(byte)0xFE, 0x04, 0x00 ,0x00 ,0x00 ,0x03, (byte)0xA4, 0x04});
		sp.close();
		
		//定时写入、读取测试
		/*final Timer closeTimer = new Timer();
		TimerTask task=new TimerTask() { 
            @Override
            public void run() {
            	count++;
            	sp.write(new byte[]{(byte)0xFE, 0x04, 0x00 ,0x00 ,0x00 ,0x03, (byte)0xA4, 0x04});
            	if(count == 2){
            		closeTimer.cancel();
            		sp.close();
            	}
            }
        };
        closeTimer.schedule(task, 1000, 3000);*/
        
	}

}


运行结果如下:

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] 温度监控器已关闭!

已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [java comm 串口] 推荐:

Java comm串口通信Utils类

- - 行业应用 - ITeye博客
    javax.comm包提供了java原生的串口通信API,实际中用到的场景很多,例如很多设备的控制信号都是通过串口进行控制的,只要向指定串口发送指定消息,就可以控制设备或读取设备信息,例如读取温度传感器信息、控制自动贩卖机出货等等.     使用javax.comm进行串口通信大概分为以下几个步骤:.

Java中的锁(Locks in Java)

- - 并发编程网 - ifeve.com
原文链接 作者:Jakob Jenkov 译者:申章 校对:丁一. 锁像synchronized同步块一样,是一种线程同步机制,但比Java中的synchronized同步块更复杂. 因为锁(以及其它更高级的线程同步机制)是由synchronized同步块的方式实现的,所以我们还不能完全摆脱synchronized关键字( 译者注:这说的是Java 5之前的情况).

Java PaaS 对决

- 呆瓜 - IBM developerWorks 中国 : 文档库
本文为 Java 开发人员比较了三种主要的 Platform as a Service (PaaS) 产品:Google App Engine for Java、Amazon Elastic Beanstalk 和 CloudBees RUN@Cloud. 它分析了每种服务独特的技术方法、优点以及缺点,而且还讨论了常见的解决方法.

Java浮点数

- d0ngd0ng - 译言-电脑/网络/数码科技
Thomas Wang, 2000年3月. Java浮点数的定义大体上遵守了二进制浮点运算标准(即IEEE 754标准). IEEE 754标准提供了浮点数无穷,负无穷,负零和非数字(Not a number,简称NaN)的定义. 在Java开发方面,这些东西经常被多数程序员混淆. 在本文中,我们将讨论计算这些特殊的浮点数相关的结果.

Qt——转战Java?

- - 博客 - 伯乐在线
编者按:事实上,在跨平台开发方面,Qt仍是最好的工具之一,无可厚非,但Qt目前没有得到任何主流移动操作系统的正式支持. 诺基亚的未来计划,定位非常模糊,这也是令很多第三方开发者感到失望,因此将导致诺基亚屡遭失败的原因. Qt的主要开发者之一Mirko Boehm在博客上强烈讽刺Nokia裁了Qt部门的决定,称其为“绝望之举”,而非“策略变更”.

java 验证码

- - ITeye博客
// 创建字体,字体的大小应该根据图片的高度来定. // 随机产生160条干扰线,使图象中的认证码不易被其它程序探测到. // randomCode用于保存随机产生的验证码,以便用户登录后进行验证. // 随机产生codeCount数字的验证码. // 得到随机产生的验证码数字. // 产生随机的颜色分量来构造颜色值,这样输出的每位数字的颜色值都将不同.

Java异常

- - CSDN博客推荐文章
“好的程序设计语言能够帮助程序员写出好程序,但是无论哪种语言都避免不了程序员写出坏的程序.                                                                                                                          ----《Java编程思想》.

java面试题

- - Java - 编程语言 - ITeye博客
 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面. 抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节. 抽象包括两个方面,一是过程抽象,二是数据抽象.  继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法. 对象的一个新类可以从现有的类中派生,这个过程称为类继承.

Java使用memcached

- - 互联网 - ITeye博客
首先到 http://danga.com/memcached下载memcached的windows版本和java客户端jar包,目前最新版本是memcached-1.2.1-win32.zip和java_memcached-release_1.6.zip,分别解压后即可. 然后是安装运行memcached服务器,我们将memcached-1.2.1-win32.zip解压后,进入其目录,然后运行如下命令:c:>;memcached.exe -d install
c:>memcached.exe -l 127.0.0.1 -m 32 -d start.

Java线程池

- - 企业架构 - ITeye博客
线程的使用在java中占有极其重要的地位,在jdk1.4极其之前的jdk版本中,关于线程池的使用是极其简陋的. 在jdk1.5之后这一情况有了很大的改观. Jdk1.5之后加入了java.util.concurrent包,这个包中主要介绍java中线程以及线程池的使用. 为我们在开发中处理线程的问题提供了非常大的帮助.