java串口通讯-实现rs485半双工轮询

 通讯方式: 485半双工轮询方式, 由主机(PC机)对仪表进行轮询访问,仪表接收到数据后根据协议返回命令;

准备工作:

要与串口通信首先要在项目添加RXTXcomm.jar包(放在项目中的lib目录下,并添加到build Path中)(win64位下载地址:http://pan.baidu.com/s/1o6zLmTc);另外,还需要将解压后的rxtxParallel.dll和rxtxSerial.dll两个文件放在%JAVA_HOME%/jre/bin目录和windows/System32下,这样该包才能被正常的加载和调用。

程序代码如下:

//包括向数据库读写数据库,发送的命令都是根据协议拼接

//没有main方法是因为要在启动服务时启动,可以自己添加main方法,比较简单,如果不会可以提出来

package com.elel.weigher.common;


import java.io.*;
import java.net.DatagramPacket;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import com.alibaba.fastjson.JSONArray;
import com.cloud.sys.exception.BusinessException;
import com.elel.weigher.controller.FillDateAdd;
import gnu.io.*;


//@Component
public class PortComm extends Thread implements SerialPortEventListener { // SerialPortEventListener
// 监听器,独立开辟一个线程监听串口数据
static CommPortIdentifier portId; // 串口通信管理类
static Enumeration<?> portList; // 有效连接上的端口的枚举
InputStream inputStream; // 从串口来的输入流
static OutputStream outputStream;// 向串口输出的流
static SerialPort serialPort; // 串口的引用
byte[] readBuffer = new byte[1024];
String trueString = "";
int tid = 0;


// 堵塞队列用来存放读到的数据
private BlockingQueue<String> msgQueue = new LinkedBlockingQueue<String>();


@Override
/**
* SerialPort EventListene 的方法,持续监听端口上是否有数据流
*/
public void serialEvent(SerialPortEvent event) {


switch (event.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,端口有可用数据。读到缓冲数组,输出到终端*/


int numBytes = -1;
try {


while (inputStream.available() > 0) {
numBytes = inputStream.read(readBuffer);
}
if (numBytes > 0) {


DatagramPacket packet = new DatagramPacket(readBuffer, numBytes);
readBuffer = packet.getData();


// 将字节数组转换为16进制字符串
String hexString = HexConvert.BinaryToHexString(readBuffer);


// 截取真实的目标字符转
trueString = trueString + hexString.substring(0, numBytes * 3);
System.out.println("---真实的trueString----::" + trueString);


int i = trueString.indexOf("FF FF FF FF");
System.out.println("i等于:::::::" + i);


if (i != -1) {
// 去掉字符串空格
String ss = trueString.replaceAll("\\s*", "");
System.out.println("ss:" + ss);


// 截取CRC校验码
String crc1 = ss.substring(ss.length() - 12, ss.length() - 8);
System.out.println("crc1:" + crc1);


// 需要校验的字符串
String ss1 = ss.substring(0, ss.length() - 12);


// 开始字符串校验
byte[] sbuf = CRC16M.getSendBuf(ss1);
String crc2 = CRC16M.getBufHexStr(sbuf).substring(CRC16M.getBufHexStr(sbuf).length() - 4,
CRC16M.getBufHexStr(sbuf).length());


System.out.println("crc2:" + crc2);


// 接收的校验码和生成的校验码作验证,一样则为我们需要的数据
if (crc1.equals(crc2)) {
System.out.println(trueString + "是我们需要的");


handelMessage(ss);
}


trueString = "";
}


} else {
msgQueue.add("额------没有读到数据");
}


} catch (IOException e) {
e.printStackTrace();
}
break;
}


}


public void handelMessage(String ts) {


String cw = ts.substring(2, 6);
System.out.println("cw:" + cw);


// OD01是设备常态时返回的命令;OCO1是上位机发送OC命令后开始灌装返回的命令;O3O1是灌装完成后上位机发03命令后,设备返回的命令;
if ("0D01".equals(cw) || "0C01".equals(cw) || "0301".equals(cw) || "0D00".equals(cw)) {


try {
String id1 = ts.substring(0, 2); // 获取设备id;


String sd1 = id1 + "0D";
// 开始校验并获取16进制及校验码字符串
byte[] cbuf1 = CRC16M.getSendBuf(sd1);
String crcsd1 = CRC16M.getBufHexStr(cbuf1).substring(CRC16M.getBufHexStr(cbuf1).length() - 4,
CRC16M.getBufHexStr(cbuf1).length());


// 将16进制字符串转换成0x00类型字节
byte j[] = HexConvert.hexStringToBytes(id1);
byte k[] = HexConvert.hexStringToBytes(crcsd1);


byte[] send = new byte[8];
System.arraycopy(j, 0, send, 0, 1);
send[1] = (byte) 0x0D;
System.arraycopy(k, 0, send, 2, 2);
send[4] = (byte) 0xFF;
send[5] = (byte) 0xFF;
send[6] = (byte) 0xFF;
send[7] = (byte) 0xFF;


outputStream.write(send);
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}


}


// 0D0B命令是电子秤上放上重物并按下启动按钮,设备返回的命令
if ("0D0B".equals(cw)) {
tid = 0;
try {
// List<TaskInfo> list = FillDateAdd.select(ts);
JSONArray list = (JSONArray) FillDateAdd.select(ts);
if (list.size() > 0) {
tid = (int) list.getJSONObject(0).get("tid");
String xsh = (String) list.getJSONObject(0).get("tno"); // 销售号
Integer no = Integer.parseInt(xsh);
String aa = HexAndInt.IntToHex(no);


int high = (int) list.getJSONObject(0).get("high") / 10; // 上限
String hh = HexAndInt.IntToHex(high);


int low = (int) list.getJSONObject(0).get("low") / 10; // 下限
String ll = HexAndInt.IntToHex(low);


String sno = HexAndInt.padLeft(aa, 8); // 转换成8位的16进制销售号
String target = ts.substring(32, 38); // 目标值直接从0B命令里获取
String hi = HexAndInt.padLeft(hh, 6); // 转换成6位的16进制上限
String lo = HexAndInt.padLeft(ll, 6); // 转换成6位的16进制下限


String id2 = ts.substring(0, 2); // 获取id;


String cc = id2 + "0C01" + sno + target + "00002d" + lo + hi + "000000"; // 拼接成需要交验的16进制字符串


// 开始校验并获取16进制及校验码字符串
byte[] cbuf = CRC16M.getSendBuf(cc);


String crc = CRC16M.getBufHexStr(cbuf).substring(CRC16M.getBufHexStr(cbuf).length() - 4,
CRC16M.getBufHexStr(cbuf).length());


// 将16进制字符串转换成0x00类型字节
byte s[] = HexConvert.hexStringToBytes(sno);
byte t[] = HexConvert.hexStringToBytes(target);
byte h[] = HexConvert.hexStringToBytes(hi);
byte l[] = HexConvert.hexStringToBytes(lo);
byte y[] = HexConvert.hexStringToBytes(id2);
byte c[] = HexConvert.hexStringToBytes(crc);


byte[] send = new byte[28];
System.arraycopy(y, 0, send, 0, 1);
send[1] = (byte) 0x0C;
send[2] = (byte) 0x01;
System.arraycopy(s, 0, send, 3, 4); // 插入销售号字节
System.arraycopy(t, 0, send, 7, 3); // 插入目标值字节
send[10] = (byte) 0x00;
send[11] = (byte) 0x00;
send[12] = (byte) 0x2d;
System.arraycopy(l, 0, send, 13, 3); // 插入下限字节
System.arraycopy(h, 0, send, 16, 3); // 插入上限字节
send[19] = (byte) 0x00;
send[20] = (byte) 0x00;
send[21] = (byte) 0x00;
System.arraycopy(c, 0, send, 22, 2); // 插入校验码字节
send[24] = (byte) 0xFF;
send[25] = (byte) 0xFF;
send[26] = (byte) 0xFF;
send[27] = (byte) 0xFF;


outputStream.write(send);
outputStream.flush();


FillDateAdd.update(tid); // 更新灌装状态为1(灌装中)
} else {
System.out.println("该客户下没有当前规格的任务单!");
}


} catch (IOException | ParseException e) {
e.printStackTrace();
}


}


// 0D03命令是灌装完成后设备返回的命令
if ("0D03".equals(cw)) {


try {


FillDateAdd.upp(ts); // 更新灌装状态为2(灌装完)
FillDateAdd.add(ts); // 插入灌装数据


String id3 = ts.substring(0, 2);


String sd3 = id3 + "0301";


// 开始校验并获取16进制及校验码字符串
byte[] cbufsd3 = CRC16M.getSendBuf(sd3);
String crcsd3 = CRC16M.getBufHexStr(cbufsd3).substring(CRC16M.getBufHexStr(cbufsd3).length() - 4,
CRC16M.getBufHexStr(cbufsd3).length());


// 将16进制字符串转换成0x00类型字节
byte m[] = HexConvert.hexStringToBytes(id3);
byte n[] = HexConvert.hexStringToBytes(crcsd3);


byte[] send = new byte[9];
System.arraycopy(m, 0, send, 0, 1);
send[1] = (byte) 0x03;
send[2] = (byte) 0x01;
System.arraycopy(n, 0, send, 3, 2);
send[5] = (byte) 0xFF;
send[6] = (byte) 0xFF;
send[7] = (byte) 0xFF;
send[8] = (byte) 0xFF;


outputStream.write(send);
outputStream.flush();
} catch (IOException | ParseException e) {
e.printStackTrace();
}


}


}


/**

* 通过程序打开串口,设置监听器以及相关的参数

* @return 返回1 表示端口打开成功,返回 0表示端口打开失败
* @throws ParseException
*/
public int startComPort() {
// 通过串口通信管理类获得当前连接上的串口列表
portList = CommPortIdentifier.getPortIdentifiers();


while (portList.hasMoreElements()) {


// 获取相应串口对象
portId = (CommPortIdentifier) portList.nextElement();


/*
* System.out.println("设备类型:--->" + portId.getPortType());
* System.out.println("设备名称:---->" + portId.getName());
*/


// 判断端口类型是否为串口
if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) {
// 判断如果串口存在,就打开该串口
if (portId.getName().equals("COM1")) {


try {


// 打开串口,延迟为2毫秒
serialPort = (SerialPort) portId.open(portId.getName(), 2000);


System.out.println("-----SerialPort----::::" + serialPort);


} catch (PortInUseException e) {
e.printStackTrace();
return 0;
}
// 设置当前串口的输入输出流
try {
inputStream = serialPort.getInputStream();
outputStream = serialPort.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
return 0;
}
// 给当前串口添加一个监听器
try {
serialPort.addEventListener(this);
} catch (TooManyListenersException e) {
e.printStackTrace();
return 0;
}
// 设置监听器生效,即:当有数据时通知
serialPort.notifyOnDataAvailable(true);


// 设置串口的一些读写参数
try {
// 比特率、数据位、停止位、奇偶校验位
serialPort.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
} catch (UnsupportedCommOperationException e) {
e.printStackTrace();


}


return 1;
}


}
}
return 0;
}


@Override
// @Scheduled(initialDelay = 5000, fixedDelay = 2000)
public void run() {
try {


System.out.println("--------------任务处理线程运行了--------------");


while (true) {


// 如果堵塞队列中存在数据就将其输出
if (msgQueue.size() > 0) {
System.out.println(msgQueue.take());
}


JSONArray list = (JSONArray) FillDateAdd.selectDevice(); //查找所有记录在数据库中的设备


long time = 150 * list.size() + 1;
Thread.sleep(time);


try {


if (list.size() > 0) {

                        //对查到的设备进行轮询,并根据查询到的地址发送命令
for (int i = 0; i < list.size(); i++) {

Thread.sleep(150);


int idt = (int) list.getJSONObject(i).get("daddress");
String tt = HexAndInt.IntToHex(idt);
String td = HexAndInt.padLeft(tt, 2); // 转换成1位的16进制销售号


String td1 = td + "0D";


System.out.println("--------轮询到设备--------::::" + idt);


// 开始校验并获取16进制及校验码字符串
byte[] cbuftd1 = CRC16M.getSendBuf(td1);
String crctd1 = CRC16M.getBufHexStr(cbuftd1).substring(
CRC16M.getBufHexStr(cbuftd1).length() - 4, CRC16M.getBufHexStr(cbuftd1).length());


// 将16进制字符串转换成0x00类型字节
byte ts[] = HexConvert.hexStringToBytes(td);
byte tc[] = HexConvert.hexStringToBytes(crctd1);


byte[] send = new byte[8];
System.arraycopy(ts, 0, send, 0, 1);
send[1] = (byte) 0x0D;
System.arraycopy(tc, 0, send, 2, 2);
send[4] = (byte) 0xFF;
send[5] = (byte) 0xFF;
send[6] = (byte) 0xFF;
send[7] = (byte) 0xFF;


outputStream.write(send);
outputStream.flush();
}
} else {
System.out.println("没有查询到设备!");
}


} catch (IOException e) {
e.printStackTrace();
}


}


} catch (InterruptedException | BusinessException e) {
e.printStackTrace();
}
}

}

//以下是程序启动时,启动线程,包括service服务注入

package com.elel.weigher.common;


import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;


import com.dlh.un.context.SpringContextHolder;
import com.elel.weigher.Feign.weigher.service.IFillDataService;


@Component
public class ApplicationStartup implements ApplicationListener<ContextRefreshedEvent> {
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("-----parent-----:"+event.getApplicationContext().getParent().getParent());
if (event.getApplicationContext().getParent().getParent() == null) {
System.out.println(">>>>>>22222222222222");
PortComm cRead = new PortComm();
int i = cRead.startComPort();
if (i == 1) {
// 启动线程来处理收到的数据
cRead.start();
} else {
// return;
System.out.println("------------" + i);
cRead.start();
}
}


}


@SuppressWarnings("unused")
private void scheduler() {
// ProxyConfiguration config =
// SpringContextHolder.getBean(ProxyConfiguration.class);


IFillDataService config = SpringContextHolder.getBean(IFillDataService.class);


// TODO
}


}




参考下面文章:

https://blog.csdn.net/update_java/article/details/46898937




  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值