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.rhkb.cn/news/37634.html

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

相关文章

【论文阅读】Contrastive Clustering Learning for Multi-Behavior Recommendation

论文地址&#xff1a;Contrastive Clustering Learning for Multi-Behavior Recommendation | ACM Transactions on Information Systems 摘要 近年来&#xff0c;多行为推荐模型取得了显著成功。然而&#xff0c;许多模型未充分考虑不同行为之间的共性与差异性&#xff0c;以…

C/C++蓝桥杯算法真题打卡(Day6)

一、P8615 [蓝桥杯 2014 国 C] 拼接平方数 - 洛谷 方法一&#xff1a;算法代码&#xff08;字符串分割法&#xff09; #include<bits/stdc.h> // 包含标准库中的所有头文件&#xff0c;方便编程 using namespace std; // 使用标准命名空间&#xff0c;避免每次调用…

纯vue手写流程组件

前言 网上有很多的vue的流程组件&#xff0c;但是本人不喜欢很多冗余的代码&#xff0c;喜欢动手敲代码&#xff1b;刚开始写的时候&#xff0c;确实没法下笔&#xff0c;最后一层一层剥离&#xff0c;总算实现了&#xff1b;大家可以参考我写的代码&#xff0c;可以拿过去定制…

[特殊字符][特殊字符][特殊字符][特殊字符][特殊字符][特殊字符]壁紙 流光染墨,碎影入梦

#Cosplay #&#x1f9da;‍♀️Bangni邦尼&#x1f430;. #&#x1f4f7; 穹妹 Set.01 #后期圈小程序 琼枝低垂&#xff0c;霜花浸透夜色&#xff0c;风起时&#xff0c;微光轻拂檐角&#xff0c;洒落一地星辉。远山隐于烟岚&#xff0c;唯余一抹青黛&#xff0c;勾勒出天光水…

kafka压缩

最近有幸公司参与kafka消息压缩&#xff0c;背景是日志消息量比较大。kafka版本2.4.1 一、确认压缩算法 根据场景不同选择不同。如果是带宽敏感患者推荐高压缩比的zstd&#xff0c;如果是cpu敏感患者推荐lz4 lz4和zstd底层都使用的是lz77算法&#xff0c;具体实现逻辑不同&am…

Java EE(14)——网络原理——UDPTCP数据报的结构

前言 本文主要介绍传输层的两个知名协议——UDP&TCP&#xff08;想了解其他层协议请移步Java EE(12)——初始网络&#xff09; 一.传输层的作用 传输层主要实现端对端的数据传输&#xff0c;在传输层的数据报中会包含源端口/目的端口的信息。端口的作用就是标识主机中的…

ccfcsp2701如此编码

//如此编码 #include<iostream> using namespace std; int main(){int n,m;cin>>n>>m;int a[21],b[21],c[21];for(int i1;i<n;i){cin>>a[i];}c[0]1;for(int i1;i<n;i){c[i]c[i-1]*a[i];}b[1](m%c[1])/c[0];int s1,s20;for(int i2;i<n;i){s2s2…

麒麟操作系统安装人大金仓数据库

如果你想拥有你从未拥有过的东西&#xff0c;那么你必须去做你从未做过的事情 在当前数字化转型和信息安全备受重视的背景下&#xff0c;众多公司积极推进国产化改造进程。在操作系统领域&#xff0c;统信、open 欧拉、中标麒麟、银河麒麟等国产操作系统崭露头角&#xff0c;逐…

【工具变量】全国地级市地方ZF债务数据集(2014-2023年)

地方ZF债务是地方财政运作的重要组成部分&#xff0c;主要用于基础设施建设、公共服务及经济发展&#xff0c;是衡量地方财政健康状况的重要指标。近年来&#xff0c;我国地级市的地方ZF债务规模不断变化&#xff0c;涉及一般债务和专项债务等多个方面&#xff0c;对金融市场、…

vlan实验

一、实验拓扑及要求&#xff1a; 二、实验步骤-思路&#xff1a; 实验需求解读&#xff1a; 首先PC1和PC3所在接口为access接口&#xff0c;属于VLAN 2&#xff0c;那么首先需求在SW1和SW2创建VLAN2&#xff0c;并且配置对应连接PC的接口链路类型为Access并放通VLAN 2PC2/4/5…

[samba配置]宿主机访问虚拟机目录

[samba配置]宿主机访问虚拟机目录 1、安装和启动Samba服务 sudo apt update sudo apt install samba2、查看samba服务是否正在运行 sudo systemctl status smbd sudo systemctl status nmbd3、配置samba服务设置为开机启动。 sudo systemctl enable smbd nmbd4、创建一个共…

PDF文件转Markdown,基于开源项目marker

​ 首先我们来问下deepseek 为啥要选marker呢 基于深度学习&#xff0c;一看就逼格拉满。搞科研必备&#xff0c;效果应该不会太差。跟其他的阿猫阿狗工具没法比。 看下官网 https://github.com/VikParuchuri/marker ​ 一看头像是个印度佬&#xff0c;自吹——又快又好。…

【深度学习与大模型基础】第6章-对角矩阵,对称矩阵,正交矩阵

一、对角矩阵 对角矩阵&#xff08;Diagonal Matrix&#xff09;是一种特殊的方阵&#xff0c;其非对角线上的元素均为零&#xff0c;只有对角线上的元素可能非零。具体来说&#xff0c;对于一个 nn的矩阵 A[]&#xff0c;如果满足 则 AA 称为对角矩阵。对角矩阵通常表示为&am…

C语言 数据结构【动态顺序表】详解

引言 详细介绍了顺序表中各个接口的实现&#xff0c;一定要亲自动手敲一遍&#xff0c;要能想象出具体的图像 第一次敲可能不能完全靠自己敲出来&#xff08;很正常&#xff09;&#xff0c;过一段时间可以根据顺序表的原理敲第二遍 孰能生巧 一、线性表 在介绍顺序表之前先…

人脸表情识别系统分享(基于深度学习+OpenCV+PyQt5)

最近终于把毕业大论文忙完了&#xff0c;众所周知硕士大论文需要有三个工作点&#xff0c;表情识别领域的第三个工作点一般是做一个表情识别系统出来&#xff0c;如下图所示。 这里分享一下这个表情识别系统&#xff1a; 采用 深度学习OpenCVPyQt5 构建&#xff0c;主要功能包…

集成学习(下):Stacking集成方法

一、Stacking的元学习革命 1.1 概念 Stacking&#xff08;堆叠法&#xff09; 是一种集成学习技术&#xff0c;通过组合多个基学习器&#xff08;base learner&#xff09;的预测结果&#xff0c;并利用一个元模型&#xff08;meta-model&#xff09;进行二次训练&#xff0c…

tcping 命令的使用,ping IP 和端口

1. ‌Windows系统安装‌ ‌下载tcping工具‌&#xff1a;根据系统位数&#xff08;32位或64位&#xff09;下载对应的tcping.exe文件。‌安装步骤‌&#xff1a; 将下载的tcping.exe文件复制到C:\Windows\System32目录下。如果下载的是64位版本&#xff0c;需将文件名改为tcpi…

浅谈跨平台框架的演变(H5混合开发->RN->Flutter)

引言 这里分为四个阶段&#xff1a; 第一阶段 &#xff1a; 原生开发 第二阶段 &#xff1a; H5混合开发 第三阶段&#xff1a; 跨平台RN 第四阶段&#xff1a; 跨平台Flutter 正文 第一阶段&#xff1a; 原生开发 开发成本比较大 &#xff1a; 需要Android 和ios 开发两…

《TCP/IP网络编程》学习笔记 | Chapter 20:Windows 中的线程同步

《TCP/IP网络编程》学习笔记 | Chapter 20&#xff1a;Windows 中的线程同步 《TCP/IP网络编程》学习笔记 | Chapter 20&#xff1a;Windows 中的线程同步用户模式和内核模式用户模式同步内核模式同步 基于 CRITICAL_SECTION 的同步内核模式的同步方法基于互斥量对象的同步基于…

力扣45.跳跃游戏

45. 跳跃游戏 II - 力扣&#xff08;LeetCode&#xff09; 代码区&#xff1a; #include<vector> class Solution {public:int jump(vector<int>& nums) {int ans[10005] ;memset(ans,1e4,sizeof(ans));ans[0]0;for(int i0;i<nums.size();i){for(int j1;j…