SpringBoot+modbus4j实现ModebusTCP通讯读取数据

场景

Windows上ModbusTCP模拟Master与Slave工具的使用:

Windows上ModbusTCP模拟Master与Slave工具的使用-CSDN博客

Modebus TCP

Modbus由MODICON公司于1979年开发,是一种工业现场总线协议标准。

1996年施耐德公司推出基于以太网TCP/IP的Modbus协议:ModbusTCP。

Modbus协议是一项应用层报文传输协议,包括ASCII、RTU、TCP三种报文类型。

标准的Modbus协议物理层接口有RS232、RS422、RS485和以太网接口,采用master/slave方式通信。

Modbus的操作对象有四种:

线圈、离散输入、保持寄存器、输入寄存器。

Modbus功能码:

SpringBoot+modbus4j实现ModebusTCP通讯读取数据_第1张图片

关于Java的开源库:

Jamod:

Java Modbus实现:Java Modbus库。该库由Dieter Wimberger实施。

ModbusPal:

ModbusPal是一个正在进行的Java项目,用于创建逼真的Modbus从站模拟器。

下面介绍基于Modebus4j实现读线圈状态数据。

由于预定义的数学函数和/或Python脚本,寄存器值是动态生成的。

ModbusPal依赖于RxTx进行串行通信,而Jython则依赖于脚本支持。

Modbus4J:

Serotonin Software用Java编写的Modbus协议的高性能且易于使用的实现。

支持ASCII,RTU,TCP和UDP传输作为从站或主站,自动请求分区,响应数据类型解析和节点扫描。

JLibModbus:

JLibModbus是java语言中Modbus协议的一种实现。jSSC和RXTX用于通过串行端口进行通信。

该库是一个经过积极测试和改进的项目。

注:

博客:
霸道流氓气质_C#,架构之路,SpringBoot-CSDN博客

实现

1、基于modbus4j实现线圈状态数据的读取。

modbus4j

GitHub - MangoAutomation/modbus4j: A high-performance and ease-of-use implementation of the Modbus protocol written in Java. Supports ASCII, RTU, TCP, and UDP transports as slave or master, automatic request partitioning and response data type parsing.

按照官网介绍,需要在pom文件中添加repository的配置

    
        
            
                false
            
            
                true
            
            ias-snapshots
            Infinite Automation Snapshot Repository
            https://maven.mangoautomation.net/repository/ias-snapshot/
        
        
            
                true
            
            
                false
            
            ias-releases
            Infinite Automation Release Repository
            https://maven.mangoautomation.net/repository/ias-release/
        
    

添加位置

然后添加依赖

        
            com.infiniteautomation
            modbus4j
            3.0.3
        

2、新建modbus4j工具类

package com.badao.demo.utils;

import com.serotonin.modbus4j.BatchRead;
import com.serotonin.modbus4j.BatchResults;
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.locator.BaseLocator;

public class Modbus4jUtils {

    /**
     * 工厂。
     */
    static ModbusFactory modbusFactory;

    static {
        if (modbusFactory == null) {
            modbusFactory = new ModbusFactory();
        }
    }

    /**
     * 获取master
     *
     * @return
     * @throws ModbusInitException
     */
    public static ModbusMaster getMaster(String ip,int port) throws ModbusInitException {
        IpParameters params = new IpParameters();
        params.setHost(ip);
        params.setPort(port);
        // modbusFactory.createRtuMaster(wapper); //RTU 协议
        // modbusFactory.createUdpMaster(params);//UDP 协议
        // modbusFactory.createAsciiMaster(wrapper);//ASCII 协议
        ModbusMaster master = modbusFactory.createTcpMaster(params, false);// TCP 协议
        master.init();
        return master;
    }

    /**
     * 读取[01 Coil Status 0x]类型 开关数据
     *
     * @param slaveId
     *            slaveId
     * @param offset
     *            位置
     * @return 读取值
     * @throws ModbusTransportException
     *             异常
     * @throws ErrorResponseException
     *             异常
     */
    public static Boolean readCoilStatus(ModbusMaster master,int slaveId, int offset)
            throws ModbusTransportException, ErrorResponseException {
        // 01 Coil Status
        BaseLocator loc = BaseLocator.coilStatus(slaveId, offset);
        Boolean value = master.getValue(loc);
        return value;
    }

    /**
     * 读取[02 Input Status 1x]类型 开关数据
     *
     * @param slaveId
     * @param offset
     * @return
     * @throws ModbusTransportException
     * @throws ErrorResponseException
     */
    public static Boolean readInputStatus(ModbusMaster master,int slaveId, int offset)
            throws ModbusTransportException, ErrorResponseException {
        // 02 Input Status
        BaseLocator loc = BaseLocator.inputStatus(slaveId, offset);
        Boolean value = master.getValue(loc);
        return value;
    }

    /**
     * 读取[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)
            throws ModbusTransportException, ErrorResponseException {
        // 03 Holding Register类型数据读取
        BaseLocator loc = BaseLocator.holdingRegister(slaveId, offset, dataType);
        Number value = master.getValue(loc);
        return value;
    }

    /**
     * 读取[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)
            throws ModbusTransportException, ErrorResponseException {
        // 04 Input Registers类型数据读取
        BaseLocator loc = BaseLocator.inputRegister(slaveId, offset, dataType);
        Number value = master.getValue(loc);
        return value;
    }

    /**
     * 批量读取使用方法
     *
     * @throws ModbusTransportException
     * @throws ErrorResponseException
     * @throws ModbusInitException
     * @return
     */
    public static BatchResults batchRead(ModbusMaster master,BatchRead batchRead) throws ModbusTransportException, ErrorResponseException, ModbusInitException {
        // 是否连续请求
        batchRead.setContiguousRequests(false);
        BatchResults results = master.send(batchRead);
        return results;
    }
}

3、使用方式

新建master

ModbusMaster master = Modbus4jUtils.getMaster(modebustcpIp, port);

这里的ip和端口来自配置文件

单个读取数据

Boolean aBoolean = Modbus4jUtils.readCoilStatus(master, 1, 0);

这里1代表slaveId,0代表offset

批量读取数据

        BatchRead batch = new BatchRead<>();
        List locatorList = locatorConfig.getLocatorList();
        locatorList.forEach(locator -> batch.addLocator(locator.getId(), BaseLocator.coilStatus(locator.getSlaveId(),locator.getOffset())));

        BatchResults batchResults = Modbus4jUtils.batchRead(master, batch);
        Boolean valueOne = (Boolean) batchResults.getValue(0);
        Boolean valueTwo = (Boolean) batchResults.getValue(1);
        Boolean valueThree = (Boolean) batchResults.getValue(2);

这里批量读取的配置来自配置文件

4、业务示例

在定时任务中批量读取指定slaveId和offset的线圈状态数据

import com.badao.demo.config.LocatorConfig;
import com.badao.demo.entity.Locator;
import com.badao.demo.utils.Modbus4jUtils;
import com.serotonin.modbus4j.BatchRead;
import com.serotonin.modbus4j.BatchResults;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.locator.BaseLocator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import java.time.LocalDateTime;
import java.util.List;

@Configuration
@EnableScheduling
public class GetModbusTCPDataTask {

    @Value("${modebustcp.ip}")
    private String modebustcpIp;

    @Value("${modebustcp.port}")
    private Integer port;

    @Autowired
    private LocatorConfig locatorConfig;

    @Scheduled(fixedRateString = "2000")
    public void  getData() throws ModbusInitException {
        ModbusMaster master = Modbus4jUtils.getMaster(modebustcpIp, port);
        BatchRead batch = new BatchRead<>();
        List locatorList = locatorConfig.getLocatorList();
        locatorList.forEach(locator -> batch.addLocator(locator.getId(), BaseLocator.coilStatus(locator.getSlaveId(),locator.getOffset())));
        try {
            //单个读取
            //Boolean aBoolean = Modbus4jUtils.readCoilStatus(master, 1, 0);
            //批量读取
            BatchResults batchResults = Modbus4jUtils.batchRead(master, batch);
            Boolean valueOne = (Boolean) batchResults.getValue(0);
            Boolean valueTwo = (Boolean) batchResults.getValue(1);
            Boolean valueThree = (Boolean) batchResults.getValue(2);
            System.out.println(LocalDateTime.now());
            System.out.println("valueOne:"+valueOne);
            System.out.println("valueTwo:"+valueTwo);
            System.out.println("valueThree:"+valueThree);
        } catch (ModbusTransportException e) {
            e.printStackTrace();
        } catch (ErrorResponseException e) {
            e.printStackTrace();
        } catch (ModbusInitException e) {
            e.printStackTrace();
        }
    }
}

5、本地测试效果

SpringBoot+modbus4j实现ModebusTCP通讯读取数据_第2张图片

6、线上测试效果

线上使用RS485转以太网模块,会将其转换成MODBUS TCP服务端,端口为默认502,slaveid为2

SpringBoot+modbus4j实现ModebusTCP通讯读取数据_第3张图片

通讯测试效果

SpringBoot+modbus4j实现ModebusTCP通讯读取数据_第4张图片

7、SpringBoot中进行ModbusTCP通讯时提示:

com.serotonin.modbus4j.exception.ErrorResponseException:Illegal function

这是因为功能码不对应,使用Modbus Slave Definition定义的功能码为03 Holding Register(4x),而在代码中连接后执行的是读取线圈状态的功能码

SpringBoot+modbus4j实现ModebusTCP通讯读取数据_第5张图片

所以将功能码修改对应即可

SpringBoot+modbus4j实现ModebusTCP通讯读取数据_第6张图片

你可能感兴趣的:(SpringBoot,spring,boot,后端,java)