前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java使用modbus4j实现ModbusTCP通信

Java使用modbus4j实现ModbusTCP通信

作者头像
Jensen_97
发布2023-07-20 14:56:07
5.6K0
发布2023-07-20 14:56:07
举报
文章被收录于专栏:技术客栈

ModbusTCP协议

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

个人感觉:

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

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

仿真软件

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

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

验证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表示各自区域的地址编号。

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

地址类型

F8:

Slave Definition

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

功能码

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

数据类型

功能码01


功能码02


功能码03,选择Float类型

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

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

swap:交换

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


功能码04


使用modbus4j

maven依赖

代码语言:javascript
复制
<!-- 若想引用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连接类

代码语言:javascript
复制
@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类

代码语言:javascript
复制
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));
    }

}

测试

代码语言:javascript
复制
  @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配置

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

输出:

代码语言:javascript
复制
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类

代码语言:javascript
复制
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();
        }
    }

}

测试

代码语言:javascript
复制
 @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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023/07/09 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ModbusTCP协议
  • 仿真软件
    • 地址类型
      • 数据类型
      • 使用modbus4j
        • maven依赖
          • Modbus连接工厂
            • Java通过modbus4j对数据的读取
              • Java通过modbus4j对数据的写入
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档