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

 

posted @   刘百会  阅读(107)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 我与微信审核的“相爱相杀”看个人小程序副业
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~
点击右上角即可分享
微信分享提示