java使用Modbus4J读写Modbus RTU over Tcp示例



1、引入modbus4j的maven

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.18</version>
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>modbus-demo</artifactId>
    <version>0.0.1</version>
    <name>modbus-demo</name>

    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.infiniteautomation</groupId>
            <artifactId>modbus4j</artifactId>
            <version>3.0.3</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <includeSystemScope>true</includeSystemScope>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <releases>
                <enabled>false</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
            <id>ias-snapshots</id>
            <name>Infinite Automation Snapshot Repository</name>
            <url>https://maven.mangoautomation.net/repository/ias-snapshot/</url>
        </repository>
        <repository>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
            <id>ias-releases</id>
            <name>Infinite Automation Release Repository</name>
            <url>https://maven.mangoautomation.net/repository/ias-release/</url>
        </repository>
    </repositories>

</project>

需留意,在 pom.xml 中运用 repository 时,于 Maven 中的 settings.xml 文件里,mirrorOf 节点务必将 ias-snapshots 以及 ias-releases 予以排除。

<mirrors>
        <mirror>
            <id>aliyunmaven</id>
            <mirrorOf>*,!ias-snapshots,!ias-releases,!huaweicloud</mirrorOf>
            <name>阿里云公共仓库</name>
            <url>https://maven.aliyun.com/repository/public</url>
        </mirror>

        <mirror>
            <id>huaweicloud</id>
            <mirrorOf>*,!ias-snapshots,!ias-releases,</mirrorOf>
            <url>https://repo.huaweicloud.com/repository/maven/</url>
        </mirror>

    </mirrors>

2、编写ModbusUtils工具类

import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.msg.ModbusRequest;
import com.serotonin.modbus4j.msg.ModbusResponse;
import com.serotonin.modbus4j.msg.ReadHoldingRegistersRequest;
import com.serotonin.modbus4j.msg.WriteRegistersRequest;
import com.serotonin.modbus4j.sero.util.queue.ByteQueue;
import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;

/**
 * Modbus TCP 协议工具类
 * <p>提供 Modbus TCP 通信核心功能及数据转换工具方法</p>
 */
@Slf4j
public class Modbus4jUtils {
    /**
     * Modbus 工厂实例(单例模式)
     */
    static ModbusFactory modbusFactory;

    // 静态初始化块:保证工厂实例唯一性
    static {
        if (modbusFactory == null) {
            modbusFactory = new ModbusFactory();
        }
    }

    /**
     * 创建并初始化 Modbus TCP 主站连接
     *
     * @param ip   设备IP地址(如 "192.168.1.100")
     * @param port 设备端口(如 502)
     * @return 初始化后的 ModbusMaster 实例
     */
    public static ModbusMaster getMaster(String ip, Integer port) {
        // 配置连接参数
        IpParameters params = new IpParameters();
        params.setHost(ip);               // 设置目标IP
        params.setPort(port);             // 设置目标端口
        params.setEncapsulated(true);     // 启用 RTU over TCP 封装
        // 创建 TCP 主站(第二个参数 false 表示不共享连接)
        ModbusMaster master = modbusFactory.createTcpMaster(params, false);

        try {
            master.init();
        } catch (ModbusInitException e) {
            log.error("Modbus 初始化失败", e);
            e.printStackTrace();
        }
        return master;
    }

    /**
     * 读取保持寄存器(功能码 03)
     *
     * @param slaveId    从站设备地址
     * @param tcpMaster  Modbus 主站实例
     * @param start      寄存器起始地址(十进制)
     * @param readLenth  读取寄存器数量
     * @return 包含响应数据的字节队列
     */
    public static ByteQueue modbusTCPRead(int slaveId, ModbusMaster tcpMaster, int start, int readLenth) {
        //发送请求
        ModbusRequest modbusRequest = null;
        try {
            // 创建读寄存器请求(功能码 03)
            modbusRequest = new ReadHoldingRegistersRequest(slaveId, start, readLenth);//功能码03
        } catch (ModbusTransportException e) {
            log.error("请求构造失败", e);
            e.printStackTrace();
        }
        //收到响应
        ModbusResponse modbusResponse = null;
        try {
            // 发送请求并获取响应
            modbusResponse = tcpMaster.send(modbusRequest);
        } catch (ModbusTransportException e) {
            log.error("通信异常", e);
            e.printStackTrace();
        }
        // 将响应写入字节队列
        ByteQueue byteQueue = new ByteQueue(12); // 初始容量 12 字节
        modbusResponse.write(byteQueue);
        log.debug("功能码:" + modbusRequest.getFunctionCode());
        log.debug("从站地址:" + modbusRequest.getSlaveId());
        log.debug("开始地址:" + start);
        log.debug("响应大小:" + byteQueue.size() + " bytes");
        log.debug("原始响应值:" + byteQueue);
        return byteQueue;
    }

    /**
     * 写多个寄存器(功能码 16)
     *
     * @param slaveId     从站设备地址
     * @param tcpMaster   Modbus 主站实例
     * @param writeOffset 写入起始地址
     * @param data        要写入的 short 数组数据
     * @return 包含响应数据的字节队列
     */
    public static ByteQueue modbusTCPWrite(int slaveId, ModbusMaster tcpMaster, int writeOffset, short[] data) {
        WriteRegistersRequest writeRegistersRequest = null;
        //收到响应
        ModbusResponse modbusResponse = null;
        try {
            // 创建写寄存器请求(功能码 16)
            writeRegistersRequest = new WriteRegistersRequest(slaveId, writeOffset, data);
            modbusResponse = tcpMaster.send(writeRegistersRequest);
            // 处理异常响应
            if (modbusResponse.isException()) {
                log.error("Exception response: message=" + modbusResponse.getExceptionMessage());
            } else {
                log.debug("Success");
            }
        } catch (ModbusTransportException e) {
            log.error("通信异常", e);
            e.printStackTrace();
        }
        // 将响应写入字节队列
        ByteQueue byteQueue = new ByteQueue(12);
        modbusResponse.write(byteQueue);
        log.debug("功能码:" + writeRegistersRequest.getFunctionCode());
        log.debug("从站地址:" + writeRegistersRequest.getSlaveId());
        log.debug("收到的响应信息大小:" + byteQueue.size());
        log.debug("收到的响应信息值:" + byteQueue);
        return byteQueue;
    }

    // region 数据转换工具方法

    /**
     * 将字节数组转换为浮点数(IEEE 754 单精度)
     *
     * @param bytes 4字节数组
     * @return 转换后的双精度浮点数
     */
    public static double toDouble(byte[] bytes) {
        return ByteBuffer.wrap(bytes).getFloat();
    }


    /**
     * 字节数组转浮点数(保留两位小数)
     *
     * @param bytes 4字节数组
     * @return 保留两位小数的双精度值
     */
    public static double toDouble00(byte[] bytes) {
        double f = ByteBuffer.wrap(bytes).getFloat();
        return toDouble00(f);
    }


    /**
     * 浮点数四舍五入保留两位小数
     *
     * @param f 原始浮点数
     * @return 格式化后的双精度值
     */
    public static double toDouble00(double f) {
        BigDecimal b = new BigDecimal(f);
        // 是小数点后只有两位的双精度类型数据
        double f1 = b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
        return f1;
    }


    /**
     * 十六进制字符串转十进制整数
     *
     * @param s 十六进制字符串(不带 0x 前缀)
     * @return 十进制整数值
     */
    public static int get16to10(String s) {
        return Integer.parseInt(s, 16);
    }

    /**
     * 将byte[]转为各种进制的字符串
     *
     * @param bytes byte[]
     * @param radix 基数可以转换进制的范围,从Character.MIN_RADIX到Character.MAX_RADIX,超出范围后变为10进制
     * @return 转换后的字符串
     * <p>
     * 类型 占用 bit(位)
     * byte(字节) 8
     * short(短整型) 16
     * int(整型) 32
     * long(长整型) 64
     * float(单精度浮点型) 32
     * double(双精度浮点型) 64
     * char(字符) 16
     * boolean(布尔型) 1
     */
    public static String binary(byte[] bytes, int radix) {
        return new BigInteger(1, bytes).toString(radix);// 这里的1代表正数
    }

    /**
     * 将byte数组转换为整数
     * 转换为bit后,最左边的那位表示,符号位(有符号/无符号)
     */
    public static int bytesToInt(byte[] bs) {
        int a = 0;
        for (int i = bs.length - 1; i >= 0; i--) {
            a += bs[i] * Math.pow(255, bs.length - i - 1);
        }
        return a;
    }

    /**
     * 字节数组转 short(大端序)
     *
     * @param b 2字节数组
     * @return 转换后的 short 值
     */
    public static short bytesToshort(byte[] b) {
        short l = 0;
        for (int i = 0; i < 2; i++) {
            l <<= 8;  // 左移 8 位腾出空间
            l |= (b[i] & 0xff); //和上面也是一样的  l = l | (b[i]&0xff)
        }
        return l;
    }
    // endregion

}

3、测试

import com.example.modbusdemo.modbus.Modbus4jUtils;
import com.serotonin.modbus4j.sero.util.queue.ByteQueue;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;

@RestController
@Slf4j
public class IndexController {

    @GetMapping("/")
    public String index() {

        ByteQueue byteQueue = Modbus4jUtils.modbusTCPRead(1, Modbus4jUtils.getMaster("127.0.0.1", 502), 0, 4);
        // 01 03 08 00 0C 00 00 00 00 00 0D 98 12 // 完整数据
        // 01 03 08 00 0C 00 00 00 00 00 0D //排除校验位的数据
        byte[] bytes = byteQueue.peekAll();
        // 08 长度
        byte[] lengthBytes = Arrays.copyOfRange(bytes, 2, 3);
        int dataLength = Modbus4jUtils.bytesToInt(lengthBytes);
        // 每个数据占两个字节
        short[] data = new short[dataLength / 2];
        // 00 0C 00 00 00 00 00 0D //数据
        byte[] dataBytes = Arrays.copyOfRange(bytes, 3, dataLength + 3);
        for (int i = 0; i < dataBytes.length; i++) {
            if (i % 2 != 0) {
                data[i / 2] = Modbus4jUtils.bytesToshort(new byte[]{dataBytes[i - 1], dataBytes[i]});
            }
        }
        log.debug(Arrays.toString(data));
        return "index";
    }

}
原文链接:,转发请注明来源!