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

2023-12-29 21:33:06

场景

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功能码:

关于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的配置

??? <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>

添加位置

然后添加依赖

??????? <dependency>
??????????? <groupId>com.infiniteautomation</groupId>
??????????? <artifactId>modbus4j</artifactId>
??????????? <version>3.0.3</version>
??????? </dependency>

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<Boolean> 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<Boolean> 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<Number> 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<Number> loc = BaseLocator.inputRegister(slaveId, offset, dataType);
??????? Number value = master.getValue(loc);
??????? return value;
??? }

??? /**
???? * 批量读取使用方法
???? *
???? * @throws ModbusTransportException
???? * @throws ErrorResponseException
???? * @throws ModbusInitException
???? * @return
???? */
??? public static BatchResults<Integer> batchRead(ModbusMaster master,BatchRead<Integer> batchRead) throws ModbusTransportException, ErrorResponseException, ModbusInitException {
??????? // 是否连续请求
??????? batchRead.setContiguousRequests(false);
??????? BatchResults<Integer> 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<Integer> batch = new BatchRead<>();
??????? List<Locator> locatorList = locatorConfig.getLocatorList();
??????? locatorList.forEach(locator -> batch.addLocator(locator.getId(), BaseLocator.coilStatus(locator.getSlaveId(),locator.getOffset())));

????????BatchResults<Integer> 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<Integer> batch = new BatchRead<>();
??????? List<Locator> 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<Integer> 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、本地测试效果

6、线上测试效果

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

通讯测试效果

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

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

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

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

文章来源:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/135292378
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。