java-串口通讯-连接硬件

串口通信(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; 有很多种状态,目前业务需求监听数据响应状态。

3、监听到数据后,可以把数据传输给第三方接口,或者保存下来,但是如果要和WEB界面交互,这里有一个小技巧,可以把数据张贴到键盘聚焦点,使用hutool工具RobotUtil.keyPressString(data); ,, 简单的说就是把串口通讯又转换为了 usb-kwb-hid通讯了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值