本人在项目工作中遇到了需要低时延、高并发采集Modbus设备数据的场景,为了最大限度的节省资源,于是尝试采用了modbus-master-tcp库,该库基于Netty编写,天然的支持异步和并发。实际效果还不错,所以把自己编写的工具类,分享给大家。

一、导入依赖

在pom.xml中添加如下依赖并更新

<dependency>
    <groupId>com.digitalpetri.modbus</groupId>
    <artifactId>modbus-master-tcp</artifactId>
    <version>1.2.0</version>
</dependency>

二、编写工具类

为了封装ip和port,这里我封装了一个ModbusNetworkAddress类

import ModbusMasterUtil;

import java.util.Objects;

/**
 * @author huangji
 * @version 1.0
 **/
public class ModbusNetworkAddress {
    private String ipAddr;
    private int port = ModbusMasterUtil.TCP_PORT;

    public ModbusNetworkAddress(String ipAddr) {
        this.ipAddr = ipAddr;
    }

    public ModbusNetworkAddress(String ipAddr, Integer port) {
        this.ipAddr = ipAddr;
        if (port != null) {
            this.port = port;
        }
    }

    public ModbusNetworkAddress(String ipAddr, String port) {
        this.ipAddr = ipAddr;
        if (port != null) {
            this.port = Integer.parseInt(port);
        }
    }

    public String getIpAddr() {
        return ipAddr;
    }

    public void setIpAddr(String ipAddr) {
        this.ipAddr = ipAddr;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    @Override
    public String toString() {
        return "ModbusNetwork{" +
                "ipAddr='" + ipAddr + '\'' +
                ", port=" + port +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ModbusNetworkAddress that = (ModbusNetworkAddress) o;
        return port == that.port && ipAddr.equals(that.ipAddr);
    }

    @Override
    public int hashCode() {
        return Objects.hash(ipAddr, port);
    }
}

ModbusMasterUtil类如下

import com.digitalpetri.modbus.codec.Modbus;
import com.digitalpetri.modbus.master.ModbusTcpMaster;
import com.digitalpetri.modbus.master.ModbusTcpMasterConfig;
import com.digitalpetri.modbus.requests.*;
import com.digitalpetri.modbus.responses.*;

import io.netty.buffer.ByteBuf;
import io.netty.util.ReferenceCountUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import ModbusNetworkAddress
import java.time.Duration;
import java.util.BitSet;
import java.util.concurrent.CompletableFuture;

/**
 * @author huangji
 * @version 1.0
 **/
public class ModbusMasterUtil {
    public final static int TCP_PORT = 502;
    public final static String TIMEOUT_DURATION = "PT5s";
    private final static Logger log = (Logger) LogManager.getLogger(ModbusMasterUtil.class);
    private ModbusTcpMaster modbusMaster = null;

    /**
     * 将两个int数拼接成为一个浮点数
     *
     * @param highValue 高16位数值
     * @param lowValue  低16位数值
     * @return 返回拼接好的浮点数
     * @author huangji
     */
    public static float concatenateFloat(int highValue, int lowValue) {
        int combinedValue = ((highValue << 16) | (lowValue & 0xFFFF));
        return Float.intBitsToFloat(combinedValue);
    }

    public static int[] floatToIntArray(float floatValue) {
        int combinedIntValue = Float.floatToIntBits(floatValue);
        int[] resultArray = new int[2];
        resultArray[0] = (combinedIntValue >> 16) & 0xFFFF;
        resultArray[1] = combinedIntValue & 0xFFFF;
        return resultArray;
    }

    /**
     * 将传入的boolean[]类型数组按位转换成byte[]类型数组
     *
     * @param booleans 传入的boolean数组
     * @return 返回转化后的 byte[]
     * @author huangji
     */
    public static byte[] booleanToByte(boolean[] booleans) {
        BitSet bitSet = new BitSet(booleans.length);
        for (int i = 0; i < booleans.length; i++) {
            bitSet.set(i, booleans[i]);
        }
        return bitSet.toByteArray();
    }

    /**
     * 将传入的int[]类型数组转换成为byte[]类型数组
     *
     * @param values 传入的int[]数组
     * @return 返回 byte[]类型的数组
     * @author huangji
     */
    public static byte[] intToByte(int[] values) {
        byte[] bytes = new byte[values.length * 2];
        for (int i = 0; i < bytes.length; i += 2) {
            bytes[i] = (byte) (values[i / 2] >> 8 & 0xFF);
            bytes[i + 1] = (byte) (values[i / 2] & 0xFF);
        }
        return bytes;
    }

    /**
     * 根据传入的ip地址,创建modbus连接器
     *
     * @param ipAddr ip地址
     * @return 创建连接器,并进行连接,之后返回此连接器
     * @author huangji
     */
    public CompletableFuture<ModbusTcpMaster> createModbusConnector(String ipAddr) {
        return createModbusConnector(ipAddr, TCP_PORT);
    }

    /**
     * 根据传入的ip地址,创建modbus连接器
     *
     * @param ipAddr ip地址
     * @param port   端口号
     * @return 创建连接器,并进行连接,之后返回此连接器
     * @author huangji
     */
    public CompletableFuture<ModbusTcpMaster> createModbusConnector(String ipAddr, int port) {
        return createModbusConnector(new ModbusNetworkAddress(ipAddr, port));
    }

    /**
     * 根据传入的ModbusNetworkAddress\引用,创建modbus连接器
     *
     * @param modbusNetworkAddress ModbusNetworkAddress类型的实体对象引用
     * @return 创建连接器,并进行连接,之后返回此连接器
     * @author huangji
     */
    public CompletableFuture<ModbusTcpMaster> createModbusConnector(ModbusNetworkAddress modbusNetworkAddress) {
        String ipAddr = modbusNetworkAddress.getIpAddr();
        int port = modbusNetworkAddress.getPort();
        if (modbusMaster == null) {
            ModbusTcpMasterConfig masterConfig = new ModbusTcpMasterConfig.Builder(ipAddr).setPort(port).setTimeout(Duration.parse(TIMEOUT_DURATION)).setPersistent(true).setLazy(false).build();
            modbusMaster = new ModbusTcpMaster(masterConfig);
        }
        return modbusMaster.connect();
    }

    public void setBooleanArray(short unsignedShortValue, int[] array, int index, int size) {
        for (int i = index; i < index + size; i++) {
            array[i] = (unsignedShortValue & (0x01 << (i - index))) != 0 ? 1 : 0;
        }
    }

    /**
     * 异步方法,读取modbus设备的线圈值,对应功能号01
     *
     * @param slaveId  设备id
     * @param address  要读取的寄存器地址
     * @param quantity 要读取的寄存器数量
     * @return 返回 CompletableFuture<int[]>
     * @author huangji
     */
    public CompletableFuture<int[]> readCoils(int slaveId, int address, int quantity) {
        CompletableFuture<ReadCoilsResponse> futureResponse = modbusMaster.sendRequest(new ReadCoilsRequest(address, quantity),
                slaveId);
        return futureResponse.handle((response, ex) -> {
            if (ex != null) {
                ReferenceCountUtil.release(response);
                return null;
            } else {
                ByteBuf byteBuf = response.getCoilStatus();
                int[] values = new int[quantity];
                int minimum = Math.min(quantity, byteBuf.capacity() * 8);
                for (int i = 0; i < minimum; i += 8) {
                    setBooleanArray(byteBuf.readUnsignedByte(), values, i, Math.min(minimum - i, 8));
                }
                ReferenceCountUtil.release(response);
                return values;
            }
        });
    }

    /**
     * 异步方法,读取modbus设备的离散输入值,对应功能号02
     *
     * @param slaveId  设备id
     * @param address  要读取的寄存器地址
     * @param quantity 要读取的寄存器数量
     * @return 返回 CompletableFuture<int[]>
     * @author huangji
     */
    public CompletableFuture<int[]> readDiscreteInputs(int slaveId, int address, int quantity) {
        CompletableFuture<ReadDiscreteInputsResponse> futureResponse = modbusMaster.sendRequest(new ReadDiscreteInputsRequest(address, quantity),
                slaveId);
        return futureResponse.handle((response, ex) -> {
            if (ex != null) {
                ReferenceCountUtil.release(response);
                return null;
            } else {
                ByteBuf byteBuf = response.getInputStatus();
                int[] values = new int[quantity];
                int minimum = Math.min(quantity, byteBuf.capacity() * 8);
                for (int i = 0; i < minimum; i += 8) {
                    setBooleanArray(byteBuf.readUnsignedByte(), values, i, Math.min(minimum - i, 8));
                }
                ReferenceCountUtil.release(response);
                return values;
            }
        });
    }

    /**
     * 异步方法,读取modbus设备的保持寄存器值,对应功能号03
     *
     * @param slaveId  设备id
     * @param address  要读取的寄存器地址
     * @param quantity 要读取的寄存器数量
     * @return 返回 CompletableFuture<int[]>
     * @author huangji
     */
    public CompletableFuture<int[]> readHoldingRegisters(int slaveId, int address, int quantity) {
        CompletableFuture<ReadHoldingRegistersResponse> futureResponse = modbusMaster.sendRequest(new ReadHoldingRegistersRequest(address, quantity),
                slaveId);
        return futureResponse.handle((response, ex) -> {
            if (ex != null) {
                ReferenceCountUtil.release(response);
                return null;
            } else {
                ByteBuf byteBuf = response.getRegisters();
                int[] values = new int[quantity];
                for (int i = 0; i < byteBuf.capacity() / 2; i++) {
                    values[i] = byteBuf.readUnsignedShort();
                }
                ReferenceCountUtil.release(response);
                return values;
            }
        });
    }

    /**
     * 异步方法,读取modbus设备的输入寄存器值,对应功能号04
     *
     * @param slaveId  设备id
     * @param address  要读取的寄存器地址
     * @param quantity 要读取的寄存器数量
     * @return 返回 CompletableFuture<int[]>
     * @author huangji
     */
    public CompletableFuture<int[]> readInputRegisters(int slaveId, int address, int quantity) {
        CompletableFuture<ReadInputRegistersResponse> futureResponse = modbusMaster.sendRequest(new ReadInputRegistersRequest(address, quantity),
                slaveId);
        return futureResponse.handle((response, ex) -> {
            if (ex != null) {
                ReferenceCountUtil.release(response);
                return null;
            } else {
                ByteBuf byteBuf = response.getRegisters();
                int[] values = new int[quantity];
                for (int i = 0; i < byteBuf.capacity() / 2; i++) {
                    values[i] = byteBuf.readUnsignedShort();
                }
                ReferenceCountUtil.release(response);
                return values;
            }
        });
    }

    /**
     * 异步方法,写入单个线圈的数值,对应功能号05
     *
     * @param slaveId 设备id
     * @param address 要读取的寄存器地址
     * @param value   要写入的boolean值
     * @return 返回 CompletableFuture<Boolean>
     * @author huangji
     */
    public CompletableFuture<Boolean> writeSingleCoil(int slaveId, int address, boolean value) {
        CompletableFuture<WriteSingleCoilResponse> futureResponse = modbusMaster.sendRequest(new WriteSingleCoilRequest(address, value),
                slaveId);
        return futureResponse.handle((response, ex) -> {
            if (ex != null) {
                ReferenceCountUtil.release(response);
                return false;
            } else {
                boolean responseValue = response.getValue() != 0;
                ReferenceCountUtil.release(response);
                return responseValue == value;
            }
        });
    }

    /**
     * 异步方法,写入单个寄存器的数值,对应功能号06
     *
     * @param slaveId 设备id
     * @param address 要读取的寄存器地址
     * @param value   要写入的值
     * @return 返回 CompletableFuture<Boolean>
     * @author huangji
     */
    public CompletableFuture<Boolean> writeSingleRegister(int slaveId, int address, int value) {
        CompletableFuture<WriteSingleRegisterResponse> futureResponse = modbusMaster.sendRequest(new WriteSingleRegisterRequest(address, value),
                slaveId);
        return futureResponse.handle((response, ex) -> {
            if (ex != null) {
                ReferenceCountUtil.release(response);
                return false;
            } else {
                int responseValue = response.getValue();
                ReferenceCountUtil.release(response);
                return responseValue == value;
            }
        });
    }

    /**
     * 异步方法,写入多个线圈的数值,对应功能号15
     *
     * @param slaveId  设备id
     * @param address  要写入的寄存器地址
     * @param quantity 要写入的寄存器个数
     * @param values   要写入的boolean[]
     * @return 返回 CompletableFuture<Boolean>
     * @author huangji
     */
    public CompletableFuture<Boolean> writeMultipleCoils(int slaveId, int address, int quantity, boolean[] values) {
        byte[] bytes = booleanToByte(values);
        CompletableFuture<WriteMultipleCoilsResponse> futureResponse = modbusMaster.sendRequest(new WriteMultipleCoilsRequest(address, quantity, bytes),
                slaveId);
        return futureResponse.handle((response, ex) -> {
            if (ex != null) {
                ReferenceCountUtil.release(response);
                return false;
            } else {
                int responseQuantity = response.getQuantity();
                ReferenceCountUtil.release(response);
                return values.length == responseQuantity;
            }
        });
    }

    /**
     * 异步方法,写入多个寄存器的数值,对应功能号16
     *
     * @param slaveId  设备id
     * @param address  要写入的寄存器地址
     * @param quantity 要写入的寄存器个数
     * @param values   要写入的int[]
     * @return 返回 CompletableFuture<Boolean>
     * @author huangji
     */
    public CompletableFuture<Boolean> writeMultipleRegisters(int slaveId, int address, int quantity, int[] values) {
        byte[] bytes = intToByte(values);
        CompletableFuture<WriteMultipleRegistersResponse> futureResponse = modbusMaster.sendRequest(new WriteMultipleRegistersRequest(address, quantity, bytes),
                slaveId);
        return futureResponse.handle((response, ex) -> {
            if (ex != null) {
                ReferenceCountUtil.release(response);
                return false;
            } else {
                int responseQuantity = response.getQuantity();
                ReferenceCountUtil.release(response);
                return values.length == responseQuantity;
            }
        });
    }

    /**
     * 关闭连接器并释放相关资源
     *
     * @author huangji
     */
    public void disposeModbusConnector() {
        if (modbusMaster != null) {
            modbusMaster.disconnect();
        }
        Modbus.releaseSharedResources();
    }

}

三、使用该工具类

ModbusMasterUtil modbusMasterUtil = new ModbusMasterUtil();
modbusMasterUtil.createModbusConnector("192.168.1.3", 502);
CompletableFuture<int[]> registerFuture = modbusMasterUtil.readCoils(200, 500, 5);
int[] registerValues = registerFuture.get(2000);

这里举一个简单的例子,连接ip地址为192.168.1.3,端口号为502的modbus设备,读取其线圈寄存器,对应设备id为200,起始地址500,寄存器数量为5。