串口通信(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 blocke.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{@Overridepublic int getListeningEvents() {// TODO Auto-generated method stubreturn SerialPort.LISTENING_EVENT_DATA_RECEIVED;}@Overridepublic 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 exceptione.printStackTrace();}}}
核心逻辑,1、开启定时任务,定时检测设备是否在线
重点,重点,重点,重点,重点,重点,重点,重点,重点,重点,重点,重点,这里有一个比较恶心的API,SerialPort.isOpen() 这个API,在打开过端口之后,设备掉线或者插口拔掉后,它依然是open状态。 这里的OPEN时间上只能说是 本地开启的服务是否open ,并不能检测设备的在线状态。
所以实际上有用的 SerialPort.bytesAvailable() ,查看是否有byte数据可用。用这个检测设备是否在线
2、开启监听函数,可以监听端口或者数据接收的事件addDataListener
SerialPort.LISTENING_EVENT_DATA_RECEIVED; 有很多种状态,目前业务需求监听数据响应状态。