串口通信(Serial Communications)的概念非常简单,串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。它很简单并且能够实现远距离通信。比如IEEE488定义并行通行状态时,规定设备线总长不得超过20米,并且任意两个设备间的长度不得超过2米;而对于串口而言,长度可达1200米。典型地,串口用于ASCII码字符的传输。通信使用3根线完成,分别是地线、发送、接收。由于串口通信是异步的,端口能够在一根线上发送数据同时在另一根线上接收数据。其他线用于握手,但不是必须的。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通信的端口,这些参数必须匹配。
上面说的,对于非专业人士来说,都是多余的话。
一句话简单的说:串口通讯,一般情况下指windows电脑(或者一些安卓系统,单片机系统)和硬件进行数据交互的协议(方式)(硬件指,扫码抢,扫码盒子,刷卡机,继电器等一些硬件)。
这些硬件一般通过串口线,或者是USB线, 如果不带USB线也可以通过转换成USB线的方式,和电脑连接。
举例几个场景,一个windows 系统的柜式机器,控制开门关门。
一个windows机器的闸机,控制开门关门,或者开灯关灯
一个扫码枪,想要获取扫码数据并处理扫码获得的数据。等等各种情况
我这里主要是以 继电器 和 扫码器两种情况为例子。
场景一:windows电脑控制继电器的开关**
这种场景主要是,windows主动发送串口指令到硬件,控制硬件的状态
淘宝上买了一个继电器
话不多说,直接上串口通讯代码
POM 依赖包
<dependency>
<groupId>com.fazecast</groupId>
<artifactId>jSerialComm</artifactId>
<version>2.9.0</version>
</dependency>
package com.hzsmk.serial.service;
import com.fazecast.jSerialComm.SerialPort;
import com.hzsmk.common.exception.BusinessException;
import com.hzsmk.common.util.RString;
public class SerialHander {
private static final byte[] open = new byte[] {(byte) 0xA0,0x01,0x01,(byte) 0xA2};
private static final byte[] close = new byte[] {(byte) 0xA0,0x01,0x00,(byte) 0xA1};
//private static final byte[] query = new byte[] {(byte) 0xA0,0x01,0x05,(byte) 0xA6};
private static SerialPort connectPort(String portDescription,String systemPortName) {
// 列举所有可用的串口
SerialPort[] commPorts = SerialPort.getCommPorts();
for (SerialPort port : commPorts) {
String des = port.getPortDescription();
System.out.println(des);
if(RString.isNotBlank(portDescription) && !portDescription.equals(des)) {
//端口描述不為空,需要检验
continue;
}
String portname = port.getSystemPortName();
if(RString.isNotBlank(systemPortName) && !systemPortName.equals(portname)) {
//端口不為空,需要检验
continue;
}
return port;
}
throw new BusinessException("未找到设备:"+portDescription+",端口"+systemPortName);
}
/**
* 发送一个开关信号
* @param portDescription
* @param systemPortName
* @throws InterruptedException
*/
public static void hand(String portDescription,String systemPortName) {
//获取可用端口
SerialPort serialPort = connectPort(portDescription, systemPortName);
try {
//连接
if(!serialPort.isOpen()) {
boolean isopen = serialPort.openPort();
if(!isopen) {
//连接失败
throw new BusinessException("连接设备失败,请重试,设备:"+portDescription+",端口"+systemPortName);
}
}
//发送2个数据过去
serialPort.writeBytes(open, open.length);
Thread.sleep(300);//随眠200毫秒 关闭
serialPort.writeBytes(close, close.length);
}catch (InterruptedException e) {
e.printStackTrace();
} catch (BusinessException e) {
throw e;
} finally {
// 关闭串口
serialPort.closePort();
}
}
}
1、核心代码是,查询出已经连接的串口设备,SerialPort.getCommPorts();
2、打开串口设备:serialPort.openPort();
3、发送串口数据:serialPort.writeBytes(close, close.length);
场景2,被动接受扫码器扫码获得的数据
这种场景主要是,串口支持被动获取数据。,
要解决的问题1: 要动态监测串口设备是否已失去连接,
2:串口设备支持自动重连。
package com.hzsmk.serial.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import com.fazecast.jSerialComm.SerialPort;
import com.hzsmk.common.exception.BusinessException;
import com.hzsmk.common.util.RString;
@Service
public class ScanHander {
private static SerialPort scanSerialPort = null;
public static String scanSerialPortName = "";
@Value("${serial.scanportdes}")
String envScanPortdes;
/**
* 每5秒检查一次
*/
@Scheduled(cron = "0/5 * * * * *")
public void scheduled(){
System.out.println("定时检查设备是否运行中");
if(scanSerialPort == null || !scanSerialPort.isOpen() || scanSerialPort.bytesAvailable() == -1) {
//主要依赖 bytesAvailable 参数, isopen基本无用
scanSerialPortName = "";//重置信号参数
synchronized (scanSerialPortName) {
try {
if(RString.isBlank(envScanPortdes)) {
createListen("SM-2D PRODUCT USB UART","");
}else {
createListen(envScanPortdes,"");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
private static SerialPort connectPort(String portDescription,String systemPortName) {
try {
// 列举所有可用的串口
SerialPort[] commPorts = SerialPort.getCommPorts();
for (SerialPort port : commPorts) {
String des = port.getPortDescription();
System.out.println(des);
if(RString.isNotBlank(portDescription) && !portDescription.equals(des)) {
//端口描述不為空,需要检验
continue;
}
String portname = port.getSystemPortName();
if(RString.isNotBlank(systemPortName) && !systemPortName.equals(portname)) {
//端口不為空,需要检验
continue;
}
return port;
}
} catch (Exception e) {
// TODO: handle exception
}
throw new BusinessException("获取设备异常,请重试");
}
public static void createListen(String portDescription,String systemPortName) {
if(scanSerialPort != null) {
//如果是断线重连
scanSerialPort.closePort();
}
//获取可用端口
scanSerialPort = connectPort(portDescription, systemPortName);
try {
scanSerialPortName = scanSerialPort.getSystemPortName()+":"+scanSerialPort.getPortDescription();
scanSerialPort.addDataListener(new ScanDatLislen());
boolean isopen = scanSerialPort.openPort();
if(!isopen) {
//连接失败
throw new BusinessException("连接设备失败,请重试,设备:"+portDescription+",端口"+systemPortName);
}
System.out.println("open......");
} catch (BusinessException e) {
throw e;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
//scanSerialPort.closePort();
}
}
}
##监听函数
package com.hzsmk.serial.service;
import com.fazecast.jSerialComm.SerialPort;
import com.fazecast.jSerialComm.SerialPortDataListener;
import com.fazecast.jSerialComm.SerialPortEvent;
import com.hzsmk.common.util.RString;
import cn.hutool.core.swing.RobotUtil;
public class ScanDatLislen implements SerialPortDataListener{
@Override
public int getListeningEvents() {
// TODO Auto-generated method stub
return SerialPort.LISTENING_EVENT_DATA_RECEIVED;
}
@Override
public void serialEvent(SerialPortEvent event) {
System.out.println("监听执行 LISTENING_EVENT_DATA_RECEIVED");
// 读取数据
try {
byte[] buffer = event.getReceivedData(); // 缓冲区大小
if (buffer.length > 0) {
// 将读取的字节转换为字符串
String data = new String(buffer, 0, buffer.length, "UTF-8");
if(!data.startsWith("QRCODE") || !data.startsWith("SRC")) {
String code = RString.byte2hex(buffer);
if(code.startsWith("810169")) {
data = code;
}
}
RobotUtil.keyPressString(data);
}
Thread.sleep(1000);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
核心逻辑,1、开启定时任务,定时检测设备是否在线
重点,重点,重点,重点,重点,重点,重点,重点,重点,重点,重点,重点,这里有一个比较恶心的API,SerialPort.isOpen() 这个API,在打开过端口之后,设备掉线或者插口拔掉后,它依然是open状态。 这里的OPEN时间上只能说是 本地开启的服务是否open ,并不能检测设备的在线状态。
所以实际上有用的 SerialPort.bytesAvailable() ,查看是否有byte数据可用。用这个检测设备是否在线
2、开启监听函数,可以监听端口或者数据接收的事件addDataListener
SerialPort.LISTENING_EVENT_DATA_RECEIVED; 有很多种状态,目前业务需求监听数据响应状态。