modbus协议学习
举例说明:
接com和ttls口
package com.zygh.environment.monitor.data.service.impl; import com.zygh.environment.monitor.data.service.DeviceConnectionHandle; import com.zygh.environment.monitor.data.utils.ByteUtil; import gnu.io.*; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.boot.web.server.PortInUseException; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Enumeration; import java.util.TooManyListenersException; /** * 设备处理类(连接、发送数据、关闭连接) * * @author: liubh * @since: 2023/5/29 */ @Service @Slf4j @Primary public class DeviceConnectionHandleImpl<E> implements DeviceConnectionHandle<E>, SerialPortEventListener { // 串口信息 private SerialPort serialPort; // 输入流 private InputStream inputStream; // 输出流 private OutputStream outputStream; //响应数据 private volatile String returnData; /** * 建立连接 * * @param port 设备端口名称 */ @Override public void connection(String port) { if (serialPort == null) { // 获取系统中所有的通讯端口 Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers(); // 记录是否含有指定串口 boolean isExist = false; // 循环通讯端口 while (portList.hasMoreElements()) { // 检测系统中可用的通讯端口类 CommPortIdentifier commPortId = portList.nextElement(); log.info("通用的端口:{},端口类型:{}", commPortId.toString(), commPortId.getPortType()); // 判断是否是串口 if (commPortId.getPortType() == CommPortIdentifier.PORT_SERIAL) { // 比较串口名称是否是指定串口 log.info("端口名称:{}", commPortId.getName()); if (port.equalsIgnoreCase(commPortId.getName())) { // 串口存在 isExist = true; // 打开串口 try { // open:(应用程序名【随意命名】,阻塞时等待的毫秒数) serialPort = (SerialPort) commPortId.open(Object.class.getSimpleName(), 2000); // 设置串口监听 serialPort.addEventListener(this); // 设置串口数据时间有效(可监听) serialPort.notifyOnDataAvailable(true); // 设置串口通讯参数:波特率,数据位,停止位,校验方式 serialPort.setSerialPortParams(9600, 8, 1, 0); log.info("{}:端口连接成功", port); } catch (PortInUseException e) { log.error("端口被占用:" + e.getMessage()); } catch (TooManyListenersException e) { log.error("监听器过多:" + e.getMessage()); } catch (UnsupportedCommOperationException e) { log.error("不支持的COMM端口操作异常:" + e.getMessage()); } catch (gnu.io.PortInUseException e) { throw new RuntimeException(e); } // 结束循环 break; } } } // 若不存在该串口则抛出异常 if (!isExist) { log.error("串口:" + port + "不存在!"); } } } /** * 实现接口SerialPortEventListener中的方法 读取从串口中接收的数据 */ @Override public void serialEvent(SerialPortEvent ev) { log.info("有数据到达:",ev); //解决响应数据不完整问题 try { Thread.sleep(3000); } catch (InterruptedException e) { throw new RuntimeException(e); } switch (ev.getEventType()) { case SerialPortEvent.BI: // 通讯中断 case SerialPortEvent.OE: // 溢位错误 case SerialPortEvent.FE: // 帧错误 case SerialPortEvent.PE: // 奇偶校验错误 case SerialPortEvent.CD: // 载波检测 case SerialPortEvent.CTS: // 清除发送 case SerialPortEvent.DSR: // 数据设备准备好 case SerialPortEvent.RI: // 响铃侦测 case SerialPortEvent.OUTPUT_BUFFER_EMPTY: // 输出缓冲区已清空 break; case SerialPortEvent.DATA_AVAILABLE: // 有数据到达 // 读取/解析串口返回数据 try { returnData = receiveData((E) serialPort); } catch (Exception e) { log.error("读取串口数据时发生IO异常:" + e.getMessage()); } break; default: break; } try { inputStream.close(); inputStream = null; } catch (IOException e) { throw new RuntimeException(e); } } /** * 关闭连接 */ @Override public void closeSerialPort() { if (serialPort != null) { serialPort.notifyOnDataAvailable(false); serialPort.removeEventListener(); if (inputStream != null) { try { inputStream.close(); inputStream = null; } catch (IOException e) { log.error("关闭输入流时发生IO异常:" + e.getMessage()); } } if (outputStream != null) { try { outputStream.close(); outputStream = null; } catch (IOException e) { log.error("关闭输出流时发生IO异常:" + e.getMessage()); } } serialPort.close(); serialPort = null; } } /** * 发送数据 * @param com 端口名称 * @param hexStr 消息内容 * @return */ @Override public String sendData(String com, String hexStr) { log.info("主要发送"); String resHexStr = null; try { synchronized (this) { if (serialPort == null) { connection(com); } outputStream = serialPort.getOutputStream(); outputStream.write(ByteUtil.hexStringToBytes(hexStr)); outputStream.flush(); do { resHexStr = returnData; } while (StringUtils.isBlank(resHexStr)); returnData = null; } } catch (NullPointerException e) { log.error("找不到串口:" + e.getMessage()); } catch (IOException e) { log.error("发送信息到串口时发生IO异常:" + e.getMessage()); } return resHexStr; } /** * 接收数据 * @param serialPort * @return */ @Override public String receiveData(E serialPort) throws IOException { SerialPort serialPort1= (SerialPort) serialPort; inputStream = serialPort1.getInputStream(); try { // 通过输入流对象的available方法获取数组字节长度 byte[] readByte = new byte[inputStream.available()]; // 直接获取到的数据 inputStream.read(readByte); // 读取后置空流对象 return ByteUtil.bytesToHexStr(readByte); } catch (IOException e) { throw new RuntimeException(e); } } }
接usb口
package com.zygh.environment.monitor.data.service.impl; import com.fazecast.jSerialComm.SerialPort; import com.fazecast.jSerialComm.SerialPortDataListener; import com.zygh.environment.monitor.data.service.DeviceConnectionHandle; import com.zygh.environment.monitor.data.utils.ByteUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.concurrent.atomic.AtomicBoolean; /** * 设备处理类(连接、发送数据、关闭连接) * * @author: liubh * @since: 2023/5/29 */ @Service("deviceUsbConnectionHandleImpl") @Slf4j public class DeviceUsbConnectionHandleImpl<E> implements DeviceConnectionHandle<E> { // 串口信息 private com.fazecast.jSerialComm.SerialPort serialPort; // 输入流 private InputStream inputStream; // 输出流 private OutputStream outputStream; //响应数据 private volatile String returnData; /** * 建立连接 * * @param port 设备端口名称 */ @Override public void connection(String port) { if (serialPort == null) { // 记录是否含有指定串口,ttyCH9344USB0 AtomicBoolean isExist = new AtomicBoolean(false); com.fazecast.jSerialComm.SerialPort[] commPorts = com.fazecast.jSerialComm.SerialPort.getCommPorts(); com.fazecast.jSerialComm.SerialPort obj= commPorts[0]; // 比较串口名称是否是指定串口 log.info("端口名称:{}", obj.getSystemPortName()); if (port.equalsIgnoreCase(obj.getSystemPortName())) { // 串口存在 isExist.set(true); // 打开串口 try { // //设置波特率为9600 // obj.setBaudRate(9600); obj.setComPortTimeouts(com.fazecast.jSerialComm.SerialPort.TIMEOUT_READ_BLOCKING | com.fazecast.jSerialComm.SerialPort.TIMEOUT_WRITE_BLOCKING, 1000, 1000);//设置超时 //设置RTS。也可以设置DTR // obj.setRTS(); // obj.setFlowControl(com.fazecast.jSerialComm.SerialPort.FLOW_CONTROL_DISABLED); // 设置串口通讯参数:波特率,数据位,停止位,校验方式 obj.setComPortParameters(9600, 8, 1, 0); if (!obj.isOpen()) { //判断串口是否打开,如果没打开,就打开串口。打开串口的函数会返回一个boolean值,用于表明串口是否成功打开了 obj.openPort(); obj.addDataListener(new SerialPortDataListener() { @Override public int getListeningEvents() { //返回要监听的事件类型, return com.fazecast.jSerialComm.SerialPort.LISTENING_EVENT_DATA_AVAILABLE; } @Override public void serialEvent(com.fazecast.jSerialComm.SerialPortEvent event) { //解决响应数据不完整问题 try { Thread.sleep(3000); } catch (InterruptedException e) { throw new RuntimeException(e); } if (event.getEventType() != com.fazecast.jSerialComm.SerialPort.LISTENING_EVENT_DATA_AVAILABLE){ return;//判断事件的类型 } // 读取/解析串口返回数据 try { returnData = receiveData((E) serialPort); } catch (Exception e) { log.error("读取串口数据时发生IO异常:" + e.getMessage()); } } }); serialPort=obj; } log.info("{}:端口连接成功", port); } catch (org.springframework.boot.web.server.PortInUseException e) { log.error("端口被占用:" + e.getMessage()); } } // 若不存在该串口则抛出异常 if (!isExist.get()) { log.error("串口:" + port + "不存在!"); } } } /** * 关闭连接 */ @Override public void closeSerialPort() { if (serialPort != null) { serialPort.removeDataListener(); if (inputStream != null) { try { inputStream.close(); inputStream = null; } catch (IOException e) { log.error("关闭输入流时发生IO异常:" + e.getMessage()); } } if (outputStream != null) { try { outputStream.close(); outputStream = null; } catch (IOException e) { log.error("关闭输出流时发生IO异常:" + e.getMessage()); } } serialPort.closePort(); serialPort = null; } } /** * 发送数据 * * @param com 端口名称 * @param hexStr 消息内容 * @return */ @Override public String sendData(String com, String hexStr) { String resHexStr = null; try { synchronized (this) { if (serialPort == null) { connection(com); } serialPort.writeBytes(ByteUtil.hexStringToBytes(hexStr),ByteUtil.hexStringToBytes(hexStr).length); do { resHexStr = returnData; } while (StringUtils.isBlank(resHexStr)); returnData = null; } } catch (NullPointerException e) { log.error("找不到串口:" + e.getMessage()); } return resHexStr; } /** * use串口接收数据实现类 * @param serialPort * @return */ @Override public String receiveData(E serialPort) { SerialPort serialPortClass = (SerialPort) serialPort; byte[] readByte = new byte[serialPortClass.bytesAvailable()]; serialPortClass.readBytes(readByte, readByte.length); return ByteUtil.bytesToHexStr(readByte); } }
modbus poll和modus slave下载地址:包含序列号
https://www.aliyundrive.com/s/5AA3T9XebLV
launch virtual port driver模拟串口下载地址:https://www.aliyundrive.com/s/yiv3xJycBxS
参考:https://zhuanlan.zhihu.com/p/529828725
https://blog.csdn.net/u012749085/article/details/125270869
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 我与微信审核的“相爱相杀”看个人小程序副业
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~