Loading...

Java使用modbus4j实现ModbusTCP通信

ModbusTCP协议

Modbus由MODICON公司于1979年开发,是一种工业现场总线协议标准。1996年施耐德公司推出基于以太网TCP/IP的Modbus协议:ModbusTCP。Modbus协议是一项应用层报文传输协议,包括ASCII、RTU、TCP三种报文类型。标准的Modbus协议物理层接口有RS232、RS422、RS485和以太网接口,采用master/slave方式通信。

93931e962ec61f6d8c333b6571ee69cd.png

个人感觉:

modbus协议也是对地址变量进行读取或者写入操作,变化的可能是地址变量的地址数据类型
这个功能码(指定要做什么,对4个不同modbus对象寄存器:是读啊,是写啊,还是对多个一起操作啊)

Modbus和RS485的关系:Modbus是协议,物理层接口有RS232、RS422、RS485和以太网接口几种

仿真软件

我要写一个Master(主站),所以需要一个Slave(从站)

  • Modbus Slave下载
  • 安装:一直下一步
  • 激活码:5455415451475662
  • 激活:Connection-->connect...(F3),输入激活码,下面截图没输入激活码,因为当时没找到激活码
  • 操作:新建四个不同功能码的窗口,然后运行代码,修改仿真软件上的值。

6105705cd9f49d763044d25abe009956.png

daa377a06ce932b99f5c18b6683f825b.png

验证4个常用功能码,仿真软件上面有F=01,F=02,F=03和F=04来显示

  • 0x01:读线圈
  • 0x02:读离散量输入
  • 0x03:读保持寄存器
  • 0x04:读输入寄存器

对应的代码要写4个方法

代码参数的理解

saveid:看资料"从站在modbus总线上可以有多个",仿真软件就能模拟一个从站,就是ID=1,当然可以修改成ID=2功能码:4个功能码,对应写4个方法,,仿真软件上的F=1,或者F=2,3,4addr:一开始看代码4个方法addr都是从0开始,是否重复?答案是:4个功能码表示4个区域或者设备,addr表示各自区域的地址编号。

c09373283ceef36f163ab756a5127bf2.png

选择TCP模式,端口是固定的502

b391aab07e0dc2f03af250dccf44098e.png

地址类型

F8:

557e4c4a201f5a222717408ceb9b05b8.png

Slave Definition

b66a08e9ddfd1da370a8c182960d43f2.png

可以自由设置地址的开始地址是多少(默认0),设置有多少个数量(默认10个)。

功能码

操作:新建四个不同功能码的窗口,然后运行代码,修改仿真软件上的值。

aa135253d11917df6674e5d1ef67ec30.png

数据类型

功能码01

67cfeb22bbdd5f339b81cdeef9d96e85.png


功能码02

ca7cbee2e4099c3d5408128b87be92e3.png


功能码03,选择Float类型

6f84547d63dbae5610c569a0686db40d.png

signed:有符号
unsigned:无符号
hex:十六进制
binary:二进制

big-endian:大端,将高序字节存储在起始地址(高位编址)
little-endian:小端,将低序字节存储在起始地址(低位编址)

swap:交换

6f9ddd5abf73206a2f17fde659e03ae5.png

双击第一个地址输入数据,会提示输入数据的类型,32位数据占2个地址,所以下一个地址是--

be3632a13231d365f274146f5ed33fb0.png


功能码04

e6348137b8f822003306a0e53c1984ce.png



使用modbus4j

maven依赖

<!-- 若想引用modbus4j需要引入下列repository id:ias-snapshots id:ias-releases 两个 ,使用默认仓库下载,不要使用阿里云仓库-->
    <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>
     <!-- modbus4j -->
        <dependency>
            <groupId>com.infiniteautomation</groupId>
            <artifactId>modbus4j</artifactId>
            <version>3.0.3</version>
        </dependency>
复制

Modbus连接工厂

ModbusTcpMaster连接

@Service(value = "ModbusTcpMaster")
public class ModbusTcpMaster {
    private final ModbusFactory modbusFactory = new ModbusFactory();

    /**
     * 获取slave
     * @return
     * @throws ModbusInitException
     */
    public ModbusMaster getSlave(String ip,int port) {
        ModbusMaster master = null;
        try {
            IpParameters params = new IpParameters();
            params.setHost(ip);
            params.setPort(port);
            //这个属性确定了协议帧是否是通过tcp封装的RTU结构,采用modbus tcp/ip时,要设为false, 采用modbus rtu over tcp/ip时,要设为true
            params.setEncapsulated(false);
            // modbusFactory.createRtuMaster(wapper); //RTU 协议
            // modbusFactory.createUdpMaster(params);//UDP 协议
            // modbusFactory.createAsciiMaster(wrapper);//ASCII 协议
            master = modbusFactory.createTcpMaster(params, false);
            //最大等待时间
            master.setTimeout(2000);
            //最大连接次数
            master.setRetries(5);
            master.init();
        } catch (ModbusInitException e) {
            e.printStackTrace();

        }
        return master;
    }
}
复制

Java通过modbus4j对数据的读取

Modbus4jReadUtil

public class Modbus4jReadUtil {

    /**
     * 读取[01 Coil Status 0x]类型 开关数据
     *
     * @param slaveId slaveId
     * @param offset  位置
     * @return 读取值
     * @throws ModbusTransportException 异常
     * @throws ErrorResponseException   异常
     */
    public static Boolean readCoilStatus(ModbusMaster master,int slaveId, int offset,String dev_code){
        // 01 Coil Status
        BaseLocator<Boolean> loc = BaseLocator.coilStatus(slaveId, offset);
        try {
            return master.getValue(loc);
        }catch (Exception e){
            if (e.getMessage().equals("java.net.SocketTimeoutException: connect timed out")) System.err.println(dev_code+":"+e.getMessage());
            else e.printStackTrace();
            return null;
        }
    }


    /**
     * 读取[02 Input Status 1x]类型 开关数据
     *
     * @param slaveId
     * @param offset
     * @return
     * @throws ModbusTransportException
     * @throws ErrorResponseException
     */
    public static Boolean readInputStatus(ModbusMaster master,int slaveId, int offset,String dev_code) {
        // 02 Input Status
        BaseLocator<Boolean> loc = BaseLocator.inputStatus(slaveId, offset);
        try{
            return master.getValue(loc);
        }catch (Exception e){
            if (e.getMessage().equals("java.net.SocketTimeoutException: connect timed out")) System.err.println(dev_code+":"+e.getMessage());
            else e.printStackTrace();
            return null;
        }
    }


    /**
     * 读取[03 Holding Register类型 2x]模拟量数据
     *
     * @param slaveId  slave Id
     * @param offset   位置
     * @param dataType 数据类型,来自com.serotonin.modbus4j.code.DataType
     * @return
     * @throws ModbusTransportException 异常
     * @throws ErrorResponseException   异常
     */
    public static Number readHoldingRegister(ModbusMaster master,int slaveId, int offset, int dataType,String dev_code) {
        // 03 Holding Register类型数据读取
        BaseLocator<Number> loc = BaseLocator.holdingRegister(slaveId, offset, dataType);
        try {
            return master.getValue(loc);
        }catch (Exception e){
            if (e.getMessage().equals("java.net.SocketTimeoutException: connect timed out")) System.err.println(dev_code+":"+e.getMessage());
            else e.printStackTrace();
            return null;
        }
    }


    /**
     * 读取[04 Input Registers 3x]类型 模拟量数据
     *
     * @param slaveId  slaveId
     * @param offset   位置
     * @param dataType 数据类型,来自com.serotonin.modbus4j.code.DataType
     * @return 返回结果
     * @throws ModbusTransportException 异常
     * @throws ErrorResponseException   异常
     */
    public static Number readInputRegisters(ModbusMaster master,int slaveId, int offset, int dataType,String dev_code) {
        // 04 Input Registers类型数据读取
        BaseLocator<Number> loc = BaseLocator.inputRegister(slaveId, offset, dataType);
        try{
            return master.getValue(loc);
        }catch (Exception e){
            if (e.getMessage().equals("java.net.SocketTimeoutException: connect timed out")) System.err.println(dev_code+":"+e.getMessage());
            else e.printStackTrace();
            return null;
        }
    }

    /**
     * 批量读取使用方法
     *
     * @throws ModbusTransportException
     * @throws ErrorResponseException
     */
    public static void batchRead(ModbusMaster master) throws ModbusTransportException, ErrorResponseException {
        BatchRead<Integer> batch = new BatchRead<Integer>();
        batch.addLocator(0, BaseLocator.holdingRegister(1, 1, DataType.TWO_BYTE_INT_SIGNED));
        batch.addLocator(1, BaseLocator.inputStatus(1, 0));
        batch.setContiguousRequests(true);
        BatchResults<Integer> results = master.send(batch);
        System.out.println("batchRead:" + results.getValue(0));
        System.out.println("batchRead:" + results.getValue(1));
    }

}
复制

测试

  @Autowired
    @Qualifier(value = "ModbusTcpMaster")
    ModbusTcpMaster masterTcp;
    @Test
    public void test() {
        //开启ModbusTcpMaster连接
        ModbusMaster master = masterTcp.getSlave("127.0.0.1", 502);
        try {
            // 01测试
            Boolean v011 = Modbus4jReadUtil.readCoilStatus(master,1, 0,"test_code");
            Boolean v012 = Modbus4jReadUtil.readCoilStatus(master,1, 1,"test_code");
            Boolean v013 = Modbus4jReadUtil.readCoilStatus(master,1, 6,"test_code");
            System.out.println("v011:" + v011);
            System.out.println("v012:" + v012);
            System.out.println("v013:" + v013);
            // 02测试
            Boolean v021 = Modbus4jReadUtil.readInputStatus(master,1, 0,"test_code");
            Boolean v022 = Modbus4jReadUtil.readInputStatus(master,1, 1,"test_code");
            Boolean v023 = Modbus4jReadUtil.readInputStatus(master,1, 2,"test_code");
            System.out.println("v021:" + v021);
            System.out.println("v022:" + v022);
            System.out.println("v023:" + v023);

            // 03测试
            Number v031 = Modbus4jReadUtil.readHoldingRegister(master,1, 1, DataType.FOUR_BYTE_FLOAT,"test_code");// 注意,float
            Number v032 = Modbus4jReadUtil.readHoldingRegister(master,1, 3, DataType.FOUR_BYTE_FLOAT,"test_code");// 同上
            System.out.println("v031:" + v031);
            System.out.println("v032:" + v032);

            // 04测试
            Number v041 = Modbus4jReadUtil.readInputRegisters(master,1, 0, DataType.FOUR_BYTE_FLOAT,"test_code");//
            Number v042 = Modbus4jReadUtil.readInputRegisters(master,1, 2, DataType.FOUR_BYTE_FLOAT,"test_code");//
            System.out.println("v041:" + v041);
            System.out.println("v042:" + v042);
            // 批量读取
            Modbus4jReadUtil.batchRead(master);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
复制

slave配置

操作:新建四个不同功能码的窗口,然后运行代码,修改仿真软件上的值。

8f10e029810e6e92581d9a0d126842a4.png

输出:

v011:true
v012:false
v013:true
v021:true
v022:false
v023:true
v031:7.5
v032:10.5
v041:1.5
v042:3.0
7.5
true
复制

Java通过modbus4j对数据的写入

Modbus4jWriteUtils类

public class Modbus4jWriteUtils{

    /**
     * 写单个(线圈)开关量数据
     * 功能码为:05,开关量输出点Q置位或复位,写入数据到真机的DO类型的寄存器上面,可以读写的布尔类型(0x)
     * @param slaveId     slave的ID
     * @param writeOffset 位置-预访问的地址-地址范围:0-255
     * @param writeValue  值-置位则为1,复位则为0
     * @return 是否写入成功
     */
    public static boolean writeCoil(ModbusMaster master,int slaveId, int writeOffset, boolean writeValue){
        boolean flag = false;
        try {
            // 创建请求
            WriteCoilRequest request = new WriteCoilRequest(slaveId, writeOffset, writeValue);
            // 发送请求并获取响应对象
            WriteCoilResponse response = (WriteCoilResponse) master.send(request);
            flag =  !response.isException();
        }catch (ModbusTransportException e){
            e.printStackTrace();
        }
        return flag;
    }

    /**
     * 写多个开关量数据(线圈)
     * 功能码为:0F,写多个开关量数据(线圈)
     * @param slaveId     slaveId
     * @param startOffset 开始位置
     * @param bdata       写入的数据
     * @return 是否写入成功
     */
    public static boolean writeCoils(ModbusMaster master,int slaveId, int startOffset, boolean[] bdata) {
        boolean flag = false;
        try {
            // 创建请求
            WriteCoilsRequest request = new WriteCoilsRequest(slaveId, startOffset, bdata);
            // 发送请求并获取响应对象
            WriteCoilsResponse response = (WriteCoilsResponse) master.send(request);
            flag = !response.isException();
        }catch (ModbusTransportException e){
            e.printStackTrace();
        }
        return flag;
    }

    /***
     *  保持寄存器写单个
     *  功能码为:06,将数据写入至V存储器, 数据到真机,数据类型是Int,可以读写的数字类型(4x)
     * @param slaveId slaveId
     * @param writeOffset 开始位置
     * @param writeValue 写入的数据
     */
    public static boolean writeRegister(ModbusMaster master,int slaveId, int writeOffset, short writeValue){
        boolean flag = false;
        try {
            // 创建请求对象
            WriteRegisterRequest request = new WriteRegisterRequest(slaveId, writeOffset, writeValue);
            // 发送请求并获取响应对象
            WriteRegisterResponse response = (WriteRegisterResponse) master.send(request);
            flag = !response.isException();
        }catch (ModbusTransportException e){
            e.printStackTrace();
        }
        return flag;
    }

    /**
     * 保持寄存器写入多个模拟量数据
     * 功能码为:16,将数据写入至多个V存储器,写入数据到真机,数据类型是short[],可以读写的数字类型(4x)
     * @param slaveId     modbus的slaveID
     * @param startOffset 起始位置偏移量值
     * @param sdata       写入的数据
     * @return 返回是否写入成功
     */
    public static boolean writeRegisters(ModbusMaster master,int slaveId, int startOffset, short[] sdata) {
        boolean flag = false;
        try {
            // 创建请求对象
            WriteRegistersRequest request = new WriteRegistersRequest(slaveId, startOffset, sdata);
            // 发送请求并获取响应对象
            WriteRegistersResponse response = (WriteRegistersResponse) master.send(request);
            flag = !response.isException();
        }catch (ModbusTransportException e){
            e.printStackTrace();
        }
        return flag;
    }

    /**
     * 根据类型写数据(如:写入Float类型的模拟量、Double类型模拟量、整数类型Short、Integer、Long)
     *
     * @param value    写入值
     * @param dataType com.serotonin.modbus4j.code.DataType
     */
    public static void writeHoldingRegister(ModbusMaster master,int slaveId, int offset, Number value, int dataType) {
        try {
            // 类型
            BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, offset, dataType);
            master.setValue(locator, value);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

}
复制

测试

 @Autowired
    @Qualifier(value = "ModbusTcpMaster")
    ModbusTcpMaster masterTcp;
    @Test
    public void test() {
        //开启ModbusTcpMaster连接
        ModbusMaster master = masterTcp.getSlave("127.0.0.1", 502);
        try {
            //写模拟量
            writeHoldingRegister(master,1,0, 10.1f, DataType.FOUR_BYTE_FLOAT);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
复制


参考文献: https://www.cnblogs.com/ioufev/p/10831289.html

0

回到顶部