springboot整合modbus实现通讯

springboot整合modbus4j实现tcp通讯

前言

本文基于springboot和modbus4j进行简单封装,达到开箱即用的目的,目前本方案仅实现了tcp通讯。代码会放在最后,按照使用方法操作后就可以直接使用

介绍

在使用本方案之前,有必要对modbus有一个简单的认知,其中包含modbus协议

Modbus通讯协议简介

Modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气Schneider Electric)于1979年为使用可编程逻辑控制器(PLC)通信而发表。Modbus已经成为工业领域通信协议的业界标准(De facto),并且现在是工业电子设备之间常用的连接方式。 [1]Modbus比其他通信协议使用的更广泛的主要原因有:

  1. 公开发表并且无版权要求
  2. 易于部署和维护
  3. 对供应商来说,修改移动本地的比特或字节没有很多限制

Modbus允许多个 (大约240个) 设备连接在同一个网络上进行通信,举个例子,一个测量温度和湿度的装置,并且将结果发送给计算机。在数据采集与监视控制系统(SCADA)中,Modbus通常用来连接监控计算机和远程终端控制系统(RTU)。

Modbus功能码(部分)

代码

名称

寄存器地址范围

位/字操作

操作数量

01

读线圈状态(Read Coils)

00001 ~ 09999

位操作

单个或多个

02

读离散输入状态(Read Discrete Inputs)

10001 ~ 19999

位操作

单个或多个

03

读保存寄存器(Read Holding Registers)

40001 ~ 49999

字操作

单个或多个

04

读输入寄存器(Read Input Registers)

30001 ~ 39999

字操作

单个或多个

05

写单个线圈(Write Single Coil)

00001 ~ 09999

字操作

单个

06

写单个保存寄存器(Write Single Register)

40001 ~ 49999

字操作

单个

Modbus仿真软件

**modbus poll:**modbus主机(master)仿真器,用于测试和调试modbus从设备。该软件支持modbus rtu、ASCII、TCP/IP。用来帮助开发人员测试modbus从设备,或者其它modbus协议的测试和仿真。它支持多文档接口,即,可以同时监视多个从设备/数据域。每个窗口简单地设定从设备ID,功能,地址,大小和轮询间隔。你可以从任意一个窗口读写寄存器和线圈。如果你想改变一个单独的寄存器,简单地双击这个值即可。或者你可以改变多个寄存器/线圈值。提供数据的多种格式方式,比如浮点、双精度、长整型(可以字节序列交换)。

**modbus slave:**modbus从设备(slave)仿真器,可以仿真32个从设备/地址域。每个接口都提供了对EXCEL报表的OLE自动化支持。主要用来模拟Modbus从站设备,接收主站的命令包,回送数据包。帮助Modbus通讯设备开发人员进行Modbus通讯协议的模拟和测试,用于模拟、测试、调试Modbus通讯设备。可以32个窗口中模拟多达32个Modbus子设备。与Modbus Poll的用户界面相同,支持功能01、02、03、04、05、06、15、16、22和23,监视串口数据。

image-20241016140552969

这两款软件请自行下载

使用方式

本方案基于springboot,可以在springboot中引入该项目后简单操作(maven安装到本地,modbus4j install > modbus-spring-boot-autoconfigure install > modbus-spring-boot-starter)直接使用,高效方便~

引入jar包
<dependency><groupId>com.dashuai</groupId><artifactId>modbus-spring-boot-starter</artifactId><version>0.0.1-SNAPSHOT</version>
</dependency>
编写配置文件

配置根据实际情况下编写,支持一下所有的配置,至于是服务器做主还是做从,根据实际情况而定

modbus:tcp:master:# 默认的主设备地址和端口default-ip: 192.168.11.180default-port: 502# 主设备的地址和端口集合,按照顺序一一对应ips:- 192.168.11.180ports:- 1502slave:# 从设备端口port: 1502encapsulated: false# 从设备详细配置process-images:# 从设备号,可创建多个- slave-id: 1# 线圈coils:# 地址位和默认值- offset: 0value: true# 离散输入状态inputs:- offset: 100value: true# 保持寄存器holding-register:# 起始地址位和寄存器个数start-offset: 200count: 100# 输入寄存器input-register:start-offset: 300count: 100- slave-id: 2coils:- offset: 0value: trueinputs:- offset: 100value: trueholding-register:start-offset: 200count: 100input-register:start-offset: 300count: 100
注入ModbusTCPMaster对象
    @Autowiredprivate ModbusTCPMaster modbusTCPMaster;
支持的方法
  1. 读取01/02/03/04功能码的数据
  2. 按照数据类型(目标类型、返回类型)直接读取
  3. 批量读取
  4. 写入数据
Master测试案例

由于是测试Modbus TCP 的通讯,所以我准备了两台机器,我本地ip是192.168.11.180,测试机器ip为192.168.11.194

Master可以理解为客户端,Slave可以理解为服务端,客户端向服务器请求(读取)数据,我在我本地和测试机器中分别启动一个modbus slave,在我本地使用当前方案进行读取

读取线圈状态

在本地中开启一个modbus slave,连接方式为TCP/IP,端口为502

image-20241017155742240

选择Setup->Slave Definition,开启一个从设备,从设备为1,功能码为01,地址位从10开始,一共2个

image-20241017160426459

设置10号地址位的值为0(false),11号地址位的值为1(true),使用以上同样的方式在192.168.11.194上设置一个从设备,端口为1502,从设备号为1,10号地址位的值为1(true),11号地址位的值为0(flase)

image-20241017160711847

测试程序配置文件,默认的master为读取我本地的数据

modbus:tcp:master:# 默认的主设备地址和端口default-ip: 192.168.11.180default-port: 502# 主设备的地址和端口集合,按照顺序一一对应ips:- 192.168.11.194ports:- 1502

注入ModbusTCPMaster对象

    @Autowiredprivate ModbusTCPMaster modbusTCPMaster;

测试方法

	@Testpublic void readCoilStatus() throws ErrorResponseException, ModbusTransportException, ModbusInitException {// 使用默认的master进行读取Boolean value = modbusTCPMaster.readCoilStatus(1, 10);System.out.println("default,slaveId:1,address:10,value = " + value);value = modbusTCPMaster.readCoilStatus(1, 11);System.out.println("default,slaveId:1,address:11,value = " + value);// 指定ip进行读取,默认的master也可以进行指定ip读取value = modbusTCPMaster.readCoilStatus("192.168.11.194", 1502, 1, 10);System.out.println("ip:192.168.11.194,port:1502,slaveId:2,address:10,value = " + value);value = modbusTCPMaster.readCoilStatus("192.168.11.194", 1502, 1, 11);System.out.println("ip:192.168.11.194,port:1502,slaveId:2,address:11,value = " + value);}

测试结果

image-20241021163937169

读取离散输入状态

在本地新建一个slave,File->New,选择Setup->Slave Definition将slaveId设置为2,选择02功能码,地址位从100开始,初始化2个寄存器,100地址位的值设置为1,101地址位的值设置为0,用同样的方式在192.168.11.194那台测试服务器上配置,地址位一样,但是值都配置为1进行测试读取

image-20241021165637473

测试方法

	@Testpublic void readInputStatus() throws ErrorResponseException, ModbusTransportException, ModbusInitException {// 使用默认的master进行读取Boolean value = modbusTCPMaster.readInputStatus(2, 100);System.out.println("default,slaveId:2,address:100,value = " + value);value = modbusTCPMaster.readInputStatus(2, 101);System.out.println("default,slaveId:2,address:101,value = " + value);// 指定ip进行读取value = modbusTCPMaster.readInputStatus("192.168.11.194", 1502, 2, 100);System.out.println("ip:192.168.11.194,port:1502,slaveId:2,address:100,value = " + value);value = modbusTCPMaster.readInputStatus("192.168.11.194", 1502, 2, 101);System.out.println("ip:192.168.11.194,port:1502,slaveId:2,address:101,value = " + value);}

测试结果

image-20241021165847599

读取保存寄存器
读取整数

在本地新建一个slave,File->New,选择Setup->Slave Definition将slaveId设置为3,选择03功能码,地址位从200开始,初始化2个寄存器,200地址位的值设置为20,201地址位的值设置为21,用同样的方式在192.168.11.194那台测试服务器上配置,地址位一样,值分别设置成30和31

image-20241021170146747

测试方法

	@Testpublic void read03Short() throws ErrorResponseException, ModbusTransportException, ModbusInitException {Short value = modbusTCPMaster.read03Short(3, 200);System.out.println("default,slaveId:3,address:200,value = " + value);value = modbusTCPMaster.read03Short(3, 201);System.out.println("default,slaveId:3,address:201,value = " + value);value = modbusTCPMaster.read03Short("192.168.11.194", 1502, 3, 200);System.out.println("ip:192.168.11.194,port:1502,slaveId:3,address:200,value = " + value);value = modbusTCPMaster.read03Short("192.168.11.194", 1502, 3, 201);System.out.println("ip:192.168.11.194,port:1502,slaveId:3,address:201,value = " + value);}

测试结果

image-20241021171146468

读取小数

在本地继续新建一个slave,File->New,选择Setup->Slave Definition将slaveId设置为4,选择03功能码,地址位从300开始,初始化4个寄存器

image-20241021172206160

按住Ctrl+A全选,点击鼠标右键,选择Format,选择Float AB CD,双击数值区域将300地址位的值设置成33.33,302地址位的数值设置成44.44,在192.168.11.194测试服务器上做同样的操作,数值分别设置成55.55和66.66

image-20241021172343483

image-20241021172607128

测试代码

	@Testpublic void read03FloatABCD() throws ErrorResponseException, ModbusTransportException, ModbusInitException {Float value = modbusTCPMaster.read03FloatABCD(4, 300);System.out.println("default,slaveId:4,address:300,value = " + value);value = modbusTCPMaster.read03FloatABCD(4, 302);System.out.println("default,slaveId:4,address:302,value = " + value);value = modbusTCPMaster.read03FloatABCD("192.168.11.194", 1502, 4, 300);System.out.println("ip:192.168.11.194,port:1502,slaveId:4,address:300,value = " + value);value = modbusTCPMaster.read03FloatABCD("192.168.11.194", 1502, 4, 302);System.out.println("ip:192.168.11.194,port:1502,slaveId:4,address:302,value = " + value);}

测试结果

image-20241021174011993

批量读取

在本地新建一个slave,File->New,选择Setup->Slave Definition将slaveId设置为5,选择03功能码,地址位从400开始,初始化5个寄存器,400地址位的值设置为0,后续5个地址的数值依次加1,分别为0、1、2、3、4,用同样的方式在192.168.11.194那台测试服务器上配置,地址位一样,值分别设置成0、11.11、22.22、33.33、44.44、55.55,批量读取支持两种方式进行

本地slave

image-20241101165518769

192.168.11.194测试机器上slave配置

image-20241101165554364

测试方法1

	@Testpublic void testBatchRead() throws ModbusTransportException, ErrorResponseException {final ArrayList<BatchReadParam> batchReadParamList = new ArrayList<>();for (int i = 0; i < 5; i++) {final BatchReadParam batchReadParam = new BatchReadParam(5, 400 + i);batchReadParamList.add(batchReadParam);}final Map<Integer, Short> resultMap = modbusTCPMaster.batchRead(batchReadParamList,DataType.TWO_BYTE_INT_SIGNED, Short.class);System.out.println("default,slaveId:5,address:400~404,resultMap = " + resultMap);}

测试结果1

image-20241101165656423

测试方法2

	@Testpublic void testBatchRead2() throws ModbusTransportException, ErrorResponseException, ModbusInitException {List<Integer> floatOffsets = Arrays.asList(400, 402, 404, 406, 408);final Map<Integer, Float> resultMap = modbusTCPMaster.batchRead("192.168.11.194", 1502,5,floatOffsets, DataType.FOUR_BYTE_FLOAT, Float.class);System.out.println("ip:192.168.11.194,port:1502,slaveId:5,address:400~408,resultMap = " + resultMap);}

测试结果2

image-20241101165752508

写入线圈状态

在本地新建一个slave,File->New,选择Setup->Slave Definition将slaveId设置为6,选择01功能码,地址位从600开始,初始化2个寄存器,用同样的方式在192.168.11.194那台测试服务器上配置,初始化的地址位一样。我们分别向本地的600地址位写入true和194上的600以及601批量写入true

本地配置

image-20241101170445460

192.168.11.194测试机器上slave配置

image-20241101170516539

测试代码1

    @Testpublic void testWriteCoil() throws ModbusTransportException {final boolean result = modbusTCPMaster.writeCoil(6, 600, true);System.out.println("default,slaveId:6,address:600,result = " + result);}

测试结果1

image-20241101170901798

测试代码2

	@Testpublic void testBatchWriteCoil() throws ModbusTransportException {final boolean[] writeValueArr = {true, true};final boolean result = modbusTCPMaster.batchWriteCoil("192.168.11.194", 1502, 6, 600, writeValueArr);System.out.println("ip:192.168.11.194,port:1502,slaveId:6,address:600~601,result = " + result);}

测试结果2

image-20241101171228623

image-20241101171243945

写入保存寄存器

在本地新建一个slave,File->New,选择Setup->Slave Definition将slaveId设置为7,选择03功能码,地址位从700开始,初始化4个寄存器,我们向本地的700地址位写入11.11

本地配置

image-20241101172924234

测试代码

    @Testpublic void testWriteHoldingRegister() throws ModbusTransportException, ErrorResponseException {modbusTCPMaster.writeHoldingRegister(7, 700, 11.11, DataType.FOUR_BYTE_FLOAT);}

测试结果

image-20241101172823442

其实还有一些方法,这里就不进行逐个测试了,有兴趣的可以自己搭建测试一下~~~

Slave测试案例

使用我本地的环境进行测试,测试在我本地启动一个slave,并初始化对应的地址位和数值,然后使用modbus poll软件进行连接和读取

测试程序配置文件(主要是slave部分)

modbus:tcp:master:# 默认的主设备地址和端口default-ip: 192.168.11.180default-port: 502# 主设备的地址和端口集合,按照顺序一一对应ips:- 192.168.11.194ports:- 1502slave:# 从设备端口port: 2502# 从设备详细配置process-images:# 从设备号,可创建多个- slave-id: 1# 线圈coils:# 地址位和默认值- offset: 1value: true# 离散输入状态inputs:- offset: 100value: true# 保持寄存器holding-register:# 起始地址位和寄存器个数start-offset: 200count: 100# 输入寄存器input-register:start-offset: 300count: 100- slave-id: 2coils:- offset: 500value: true

**解释:**我在我本地启动了2个从设备,从设备号分别为1和2,在从设备1中,我启动了线圈状态,地址位为1,默认数值为true,启动了离散输入状态,地址位为100,默认数值为true,启动了保存寄存器,起始地址位为200,一共初始化100个地址位,启动了输入寄存器,起始位置为300,也是初始化100个地址位;在从设备2中,我只启动了线圈状态,地址位为500,默认数值为true

测试代码

	@Testpublic void testModbusSlave() {System.out.println("我启动了slave~~~~~");System.out.println("modbusTCPSlave = " + modbusTCPSlave);try {Thread.sleep(600000);} catch (InterruptedException e) {e.printStackTrace();}}

测试结果

image-20241101175008614

image-20241101175117260

image-20241101175221033

image-20241101175254835

image-20241101175338392

可以看到全部都能够正常连接并读取,写入的话就不测试了

代码地址

到此结束,附上代码,请动动小手给个star,谢谢~~~

modbus4j:https://github.com/MangoAutomation/modbus4j.git

modbus-spring-boot-autoconfigure:https://gitee.com/qiu_min/modbus-spring-boot-autoconfigure.git

modbus-spring-boot-starter:https://gitee.com/qiu_min/modbus-spring-boot-starter.git

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/67702.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

iOS-YModel

YModel 是一个高效的 iOS/OSX 的模型转换框架&#xff0c;可以轻松地将 JSON 转换成 Model&#xff0c;或者将 Model 转换成 JSON。以下是详细的使用指南&#xff1a; 导入 YYModel: 确保在你的项目中导入了 YYModel。使用 CocoaPods 的话可以在 Podfile 中加入以下代码&#…

PhyCAGE:符合物理规律的图像到 3D 生成

Paper: Yan H, Zhang M, Li Y, et al. PhyCAGE: Physically Plausible Compositional 3D Asset Generation from a Single Image[J]. arXiv preprint arXiv:2411.18548, 2024. Introduction: https://wolfball.github.io/phycage/ Code: Unreleased PhyCAGE 是一种 image-to-3D…

麒麟操作系统服务架构保姆级教程(十三)tomcat环境安装以及LNMT架构

如果你想拥有你从未拥有过的东西&#xff0c;那么你必须去做你从未做过的事情 之前咱们学习了LNMP架构&#xff0c;但是PHP对于技术来说确实是老掉牙了&#xff0c;PHP的市场占有量越来越少了&#xff0c;我认识一个10年的PHP开发工程师&#xff0c;十年工资从15k到今天的6k&am…

网站HTTP改成HTTPS

您不仅需要知道如何将HTTP转换为HTTPS&#xff0c;还必须在不妨碍您的网站自成立以来建立的任何搜索排名权限的情况下进行切换。 为什么应该从HTTP转换为HTTPS&#xff1f; 与非安全HTTP于不同&#xff0c;安全域使用SSL&#xff08;安全套接字层&#xff09;服务器上的加密代…

大模型GUI系列论文阅读 DAY1:《基于大型语言模型的图形用户界面智能体:综述》(6.6W 字长文)

摘要 图形用户界面&#xff08;Graphical User Interfaces, GUIs&#xff09;长期以来一直是人机交互的核心&#xff0c;为用户提供了直观且以视觉为驱动的方式来访问和操作数字系统。传统上&#xff0c;GUI交互的自动化依赖于基于脚本或规则的方法&#xff0c;这些方法在固定…

实战经验:使用 Python 的 PyPDF 进行 PDF 操作

文章目录 1. 为什么选择 PyPDF&#xff1f;2. 安装 PyPDF3. PDF 文件的合并与拆分3.1 合并 PDF 文件3.2 拆分 PDF 文件 4. 提取 PDF 文本5. 修改 PDF 元信息6. PDF 加密与解密6.1 加密 PDF6.2 解密 PDF 7. 页面旋转与裁剪7.1 旋转页面7.2 裁剪页面 8. 实战经验总结 PDF 是一种非…

MySQL 安装配置(完整教程)

文章目录 一、MySQL 简介二、下载 MySQL三、安装 MySQL四、配置环境变量五、配置 MySQL5.1 初始化 MySQL5.2 启动 MySQL 服务 六、修改 MySQL 密码七、卸载 MySQL八、结语 一、MySQL 简介 MySQL 是一款广泛使用的开源关系型数据库管理系统&#xff08;RDBMS&#xff09;&#…

PostgreSQL的学习心得和知识总结(一百六十六)|深入理解PostgreSQL数据库之\watch元命令的实现原理

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《PostgreSQL数据库内核分析》 2、参考书籍&#xff1a;《数据库事务处理的艺术&#xff1a;事务管理与并发控制》 3、PostgreSQL数据库仓库…

当当网书籍信息爬虫

1.基本理论 1.1概念体系 网络爬虫又称网络蜘蛛、网络蚂蚁、网络机器人等&#xff0c;可以按照我们设置的规则自动化爬取网络上的信息&#xff0c;这些规则被称为爬虫算法。是一种自动化程序&#xff0c;用于从互联网上抓取数据。爬虫通过模拟浏览器的行为&#xff0c;访问网页…

计算机网络 (50)两类密码体制

前言 计算机网络中的两类密码体制主要包括对称密钥密码体制&#xff08;也称为私钥密码体制、对称密码体制&#xff09;和公钥密码体制&#xff08;也称为非对称密码体制、公开密钥加密技术&#xff09;。 一、对称密钥密码体制 定义&#xff1a; 对称密钥密码体制是一种传…

【PowerQuery专栏】实现JSON数据的导入

Json 格式数据是在互联网数据格式传输使用的非常频繁的一类数据,图7.44为Json数据格式中比较典型的数据格式。 PowerQuery进行Json数据解析使用的是Json.Document进行数据解析,Json.Document目前有2个参数。 参数1为内容数据,数据类型为二进制类型,值为需要解析的Json数据参…

软件授权管理中的软件激活向导示例

软件激活向导示例 在软件许可中&#xff0c;提供许可应该是简单和安全的。这适用于想要在中央许可证服务器上创建新许可证的软件开发人员&#xff0c;也适用于需要在其设备上获得许可证的最终用户。如果所讨论的系统有互联网连接&#xff0c;或是暂时的连接&#xff0c;就可以…

模型部署工具01:Docker || 用Docker打包模型 Build Once Run Anywhere

Docker 是一个开源的容器化平台&#xff0c;可以让开发者和运维人员轻松构建、发布和运行应用程序。Docker 的核心概念是通过容器技术隔离应用及其依赖项&#xff0c;使得软件在不同的环境中运行时具有一致性。无论是开发环境、测试环境&#xff0c;还是生产环境&#xff0c;Do…

【云岚到家】-day03-门户缓存方案选择

【云岚到家】-day03-门户缓存方案选择 1.门户常用的技术方案 什么是门户 说到门户马上会想到门户网站&#xff0c;中国比较早的门户网站有新浪、网易、搜狐、腾讯等&#xff0c;门户网站为用户提供一个集中的、易于访问的平台&#xff0c;使他们能够方便地获取各种信息和服务…

网络安全VS数据安全

关于网络安全和数据安全&#xff0c;我们常听到如下两种不同声音&#xff1a; 观点一&#xff1a;网络安全是数据安全的基础&#xff0c;把当年做网络安全的那一套用数据安全再做一遍。 观点二&#xff1a;数据安全如今普遍以为是网络安全的延伸&#xff0c;实际情况是忽略数据…

Python----Python高级(文件操作open,os模块对于文件操作,shutil模块 )

一、文件处理 1.1、文件操作的重要性和应用场景 1.1.1、重要性 数据持久化&#xff1a; 文件是存储数据的一种非常基本且重要的方式。通过文件&#xff0c;我们可 以将程序运行时产生的数据永久保存下来&#xff0c;以便将来使用。 跨平台兼容性&#xff1a; 文件是一种通用…

Ubuntu 24.04 LTS 安装 Docker Desktop

Docker 简介 Docker 简介和安装Ubuntu上学习使用Docker的详细入门教程Docker 快速入门Ubuntu版&#xff08;1h速通&#xff09; Docker 安装 参考 How to Install Docker on Ubuntu 24.04: Step-by-Step Guide。 更新系统和安装依赖 在终端中运行以下命令以确保系统更新并…

python+pygame+pytmx+map editor开发一个tiled游戏demo 05使用object层初始化player位置

代码 import mathimport pygame# 限制物体在屏幕内 import pytmxdef limit_position_to_screen(x, y, width, height):"""限制物体在屏幕内"""x max(0, min(x, SCREEN_WIDTH - width)) # 限制x坐标y max(0, min(y, SCREEN_HEIGHT - height))…

函数递归的介绍

1.递归的定义 在C语言中&#xff0c;递归就是函数自己调用自己 上面的代码就是 main 函数在函数主体内 自己调用自己 但是&#xff0c;上面的代码存在问题&#xff1a;main 函数反复地 自己调用自己 &#xff0c;不受限制&#xff0c;停不下来。 最终形成死递归&#xff0c;…

【PCIe 总线及设备入门学习专栏 6.1 -- PCIe MCTP】

文章目录 1 什么是 MCTP?2 MCTP 消息在 PCIe 中的传输特点3 PCIe MCTP 的局限性(1) 出站(Outbound)MCTP 消息分解的限制(2) 入站(Inbound)MCTP 消息组装的限制4 MCTP 消息的实际使用流程发送端处理流程接收端处理流程5 实际使用场景例 1:管理命令传输例 2:监控数据报告例…