摘要:Modbus是当前非常流行的一种通讯协议。

本文分享自华为云社区《一文搞懂物联网Modbus通讯协议丨【拜托了,物联网!】》,作者: jackwangcumt。

1 概述

随着IT技术的快速发展,当前已经步入了智能化时代,其中的物联网技术将在未来占据越来越重要的地位。根据百度百科的定义,物联网(Internet of things,简称IOT )即“万物相连的互联网”,是互联网基础上的延伸和扩展的网络,物联网将各种信息有机的结合起来,实现任何时间、任何地点,人、机、物的互联互通。物联网从技术上来说,很重要的核心是通讯协议,即如何按约定的通讯协议,把机、物和人与互联网相连接,进行信息通信,以实现对人、机和物的智能化识别、定位、跟踪、监控和管理的一种网络。

一般来说,常见的物联网通讯协议众多,如蓝牙、Zigbee、WiFi、ModBus、PROFINET、EtherCAT、蜂窝等。而在众多的物联网通讯协议中,Modbus是当前非常流行的一种通讯协议。它一种串行通信协议,是Modicon公司于1979年为使用可编程逻辑控制器(PLC)通信而制定的,可以说,它已经成为工业领域通信协议的业界标准。其优势如下:

  • 免费无版税限制
  • 容易部署
  • 灵活限制少

2 ModBus协议概述

Modbus通讯协议使用请求-应答机制在主(Master)(客户端Client)和从(Slave)(服务器Server)之间交换信息。Client-Server原理是通信协议的模型,其中一个主设备控制多个从设备。这里需要注意的是:Modbus通讯协议当中的Master对应Client,而Slave对应Server。Modbus通讯协议的官网为http://www.modbus.org。目前官网组织已经建议将Master-Slave替换为Client-Server。从协议类型上可以分为:Modbus-RTU(ASCII)、Modbus-TCP和Modbus-Plus。本文主要介绍Modbus-RTU(ASCII)的通讯协议原理。标准的Modbus协议物理层接口有RS232、RS422、RS485和以太网接口。

通讯示意图如下:

一般来说,Modbus通信协议原理具备如下的特征:

  • 一次只有一个主机(Master)连接到网络
  • 只有主设备(Master)可以启动通信并向从设备(Slave)发送请求
  • 主设备(Master)可以使用其特定地址单独寻址每个从设备(Slave),也可以使用地址0(广播)同时寻址所有从设备(Slave)
  • 从设备(Slave)只能向主设备(Master)发送回复
  • 从设备(Slave)无法启动与主设备(Master)或其他从设备(Slave)的通信

Modbus协议可使用2种通信模式交换信息:

  • 单播模式
  • 广播模式

不管是请求报文还是答复报文,数据结构如下:

即报文(帧数据)由4部分构成:地址(Slave Number)+功能码(Function Codes)+数据(Data)+校验(Check) 。其中的地址代表从设备的ID地址,作为寻址的信息。功能码表示当前的请求执行具体什么操作,比如读还是写。数据代表需要通讯的业务数据,可以根据实际情况来确定。最后一个校验则是验证数据是否有误。其中的功能码说明如下:

比如功能码为03代表读取当前寄存器内一个或多个二进制值,而06代表将二进制值写入单一寄存器。为了模拟Modbus通讯协议过程,这里可以借助模拟软件:

  • Modbus Poll(Master)
  • Modbus Slave

具体的安装过程这里不再赘述。首先这里需要模拟一个物联网传感器设备,这里用Modbus Slave来定义,首先打开此软件,并定义一个ID为1的设备:

此功能码为03。另外,设置连接参数,示例界面如下:

下面再用Modbus Poll软件来模拟主机,来获取从设备的数据。首先定义一个读写报文。

然后再定义一个连接信息:

注意:两个COM口要使用不同的名称。

成功建立通讯后,通信的报文格式如下:

Tx代表请求报文,而Rx代表答复报文。

3 ModBus Java实现

下面介绍一下如何用Java来实现一个Modbus TCP通信。这里Java框架采用Spring Boot,首先需要引入Modbus库。Maven依赖库的pom.xml定义如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency> <!--Modbus Master -->
<dependency>
<groupId>com.digitalpetri.modbus</groupId>
<artifactId>modbus-master-tcp</artifactId>
<version>1.2.0</version>
</dependency>
<!--Modbus Slave -->
<dependency>
<groupId>com.digitalpetri.modbus</groupId>
<artifactId>modbus-slave-tcp</artifactId>
<version>1.2.0</version>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> </project>

其中关于Modbus库的依赖项为com.digitalpetri.modbus,它分modbus-master-tcpmodbus-slave-tcp 。此示例用Java项目模拟了一个Modbus Master端,用Modbus Slave软件模拟了Slave端,通信连接方式选择Modbus TCP/IP方式,IP地址和端口限定了Slave设备。示意图如下:

由于此处连接方式采用Modbus TCP方式,因此在Modbus Slave的连接配置的地方,需要调整连接方式,示意截图如下:

Java核心代码如下:

package com.example.demo.modbus;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.digitalpetri.modbus.codec.Modbus;
import com.digitalpetri.modbus.master.ModbusTcpMaster;
import com.digitalpetri.modbus.master.ModbusTcpMasterConfig;
import com.digitalpetri.modbus.requests.ReadHoldingRegistersRequest;
import com.digitalpetri.modbus.responses.ReadHoldingRegistersResponse;
import io.netty.buffer.ByteBufUtil;
import io.netty.util.ReferenceCountUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; public class MBMaster { private final Logger logger = LoggerFactory.getLogger(getClass()); private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); private final List<ModbusTcpMaster> masters = new CopyOnWriteArrayList<>();
private volatile boolean started = false; private final int nMasters ;
private final int nRequests ; public MBMaster(int nMasters, int nRequests) {
if (nMasters < 1){
nMasters = 1;
}
if (nRequests < 1){
nMasters = 1;
}
this.nMasters = nMasters;
this.nRequests = nRequests;
}
//启动
public void start() {
started = true; ModbusTcpMasterConfig config = new ModbusTcpMasterConfig.Builder("127.0.0.1")
.setPort(50201)
.setInstanceId("S-001")
.build(); new Thread(() -> {
while (started) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} double mean = 0.0;
int mcounter = 0; for (ModbusTcpMaster master : masters) {
mean += master.getResponseTimer().getMeanRate();
mcounter += master.getResponseTimer().getCount();
} logger.info("Mean Rate={}, counter={}", mean, mcounter);
}
}).start(); for (int i = 0; i < nMasters; i++) {
ModbusTcpMaster master = new ModbusTcpMaster(config);
master.connect();
masters.add(master);
for (int j = 0; j < nRequests; j++) {
sendAndReceive(master);
}
}
}
//发送请求
private void sendAndReceive(ModbusTcpMaster master) {
if (!started) return; //10个寄存器
CompletableFuture<ReadHoldingRegistersResponse> future =
master.sendRequest(new ReadHoldingRegistersRequest(0, 10), 0); //响应处理
future.whenCompleteAsync((response, ex) -> {
if (response != null) {
//System.out.println("Response: " + ByteBufUtil.hexDump(response.getRegisters()));
System.out.println("Response: " + ByteBufUtil.prettyHexDump(response.getRegisters()));
//[00 31 00 46 00 00 00 b3 00 00 00 00 00 00 00 00]
byte[] bytes = ByteBufUtil.getBytes(response.getRegisters());
System.out.println("Response Value = " + bytes[3]);//根据业务情况获取寄存器数值
ReferenceCountUtil.release(response);
} else {
logger.error("Error Msg ={}", ex.getMessage(), ex);
}
scheduler.schedule(() -> sendAndReceive(master), 1, TimeUnit.SECONDS);
}, Modbus.sharedExecutor());
} public void stop() {
started = false;
masters.forEach(ModbusTcpMaster::disconnect);
masters.clear();
} public static void main(String[] args) {
//启动Client进行数据交互
new MBMaster(1, 1).start();
}
}

首先,需要用ModbusTcpMasterConfig来初始化一个Modbus Tcp Master 主机的配置信息,比如IP地址(127.0.0.1)和端口号(50201),此需要和Slave一致。其次,将配置信息config作为参数传递到ModbusTcpMaster对象中,构建一个 master实例。最后,用master.sendRequest(new ReadHoldingRegistersRequest(0, 10), 0)对象来查询数据,此功能码为03,寄存器数据为10。在Modbus Slave开启连接后,设置界面如下所示:

运行Java程序。控制台输出示例如下所示:

Response Value = 16
Response: +-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 08 00 11 00 1b 00 00 00 00 00 00 00 00 00 00 |................|
|00000010| 00 00 00 00 |.... |
+--------+-------------------------------------------------+----------------+
Response Value = 17
Response: +-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 09 00 12 00 1c 00 00 00 00 00 00 00 00 00 00 |................|
|00000010| 00 00 00 00 |.... |
+--------+-------------------------------------------------+----------------+
Response Value = 18

由此,可以知晓,返回的报文中在0到f这15个位置中,有需要的业务数据,具体获取哪个位置,取决于Slave设备的设置。

点击关注,第一时间了解华为云新鲜技术~

详解物联网Modbus通讯协议的更多相关文章

  1. 《TCP/IP详解卷1:协议》读书笔记

    <TCP/IP详解卷1:协议>读书笔记 - QingLiXueShi - 博客园https://www.cnblogs.com/mengwang024/p/4425834.html < ...

  2. Angular6 学习笔记——组件详解之组件通讯

    angular6.x系列的学习笔记记录,仍在不断完善中,学习地址: https://www.angular.cn/guide/template-syntax http://www.ngfans.net ...

  3. 《TCP/IP详解卷1:协议》第1章 概述-读书笔记

    章节回顾: <TCP/IP详解卷1:协议>第1章 概述-读书笔记 <TCP/IP详解卷1:协议>第2章 链路层-读书笔记 <TCP/IP详解卷1:协议>第3章 IP ...

  4. 《TCP/IP详解卷1:协议》第2章 链路层-读书笔记

    章节回顾: <TCP/IP详解卷1:协议>第1章 概述-读书笔记 <TCP/IP详解卷1:协议>第2章 链路层-读书笔记 <TCP/IP详解卷1:协议>第3章 IP ...

  5. 《TCP/IP详解卷1:协议》第3章 IP:网际协议(1)-读书笔记

    章节回顾: <TCP/IP详解卷1:协议>第1章 概述-读书笔记 <TCP/IP详解卷1:协议>第2章 链路层-读书笔记 <TCP/IP详解卷1:协议>第3章 IP ...

  6. 《TCP/IP详解卷1:协议》第3章 IP:网际协议(2)-读书笔记

    章节回顾: <TCP/IP详解卷1:协议>第1章 概述-读书笔记 <TCP/IP详解卷1:协议>第2章 链路层-读书笔记 <TCP/IP详解卷1:协议>第3章 IP ...

  7. 《TCP/IP详解卷1:协议》第4章 ARP:地址解析协议-读书笔记

    章节回顾: <TCP/IP详解卷1:协议>第1章 概述-读书笔记 <TCP/IP详解卷1:协议>第2章 链路层-读书笔记 <TCP/IP详解卷1:协议>第3章 IP ...

  8. 《TCP/IP详解卷1:协议》第5章 RARP:逆地址解析协议-读书笔记

    章节回顾: <TCP/IP详解卷1:协议>第1章 概述-读书笔记 <TCP/IP详解卷1:协议>第2章 链路层-读书笔记 <TCP/IP详解卷1:协议>第3章 IP ...

  9. 《TCP/IP详解卷1:协议》第6章 ICMP:Internet控制报文协议-读书笔记

    章节回顾: <TCP/IP详解卷1:协议>第1章 概述-读书笔记 <TCP/IP详解卷1:协议>第2章 链路层-读书笔记 <TCP/IP详解卷1:协议>第3章 IP ...

  10. 《TCP/IP详解卷1:协议》第11章 UDP:用户数据报协议-读书笔记

    章节回顾: <TCP/IP详解卷1:协议>第1章 概述-读书笔记 <TCP/IP详解卷1:协议>第2章 链路层-读书笔记 <TCP/IP详解卷1:协议>第3章 IP ...

随机推荐

  1. 🎁平平无奇的 Docker 命令(日常流)

    Docker search docker search 命令用于在 Docker Hub 上搜索镜像,语法如下: docker search [OPTIONS] TERM 常用的选项包括: --fil ...

  2. CF48C [The Race]

    Problem 题目简述 现有 \(n\) 个已经加过油的加油站,如果当前剩余油量 \(< 10\) 升,则会加 \(x\) 升的油. 初始状态下,有 \(x\) 升油.每个加油站之间的距离为 ...

  3. Production Environment Difference Between Development, Stage, And Production

    There are three different environments that you'll probably deal with at some point. Each environmen ...

  4. 🔥🔥TCP协议:超时重传、流量控制、keep-alive和端口号,你真的了解吗?

    引言 在之前的讲解中,我们已经介绍了TCP协议的一些面试内容,相信大家对于TCP也有了一些新的了解.今天,我们将继续深入探讨TCP的超时重传.流量控制.TCP的keepalive机制以及端口号等相关信 ...

  5. c#利用反射获取枚举的信息

    1.将不同的枚举类型作为形参传入某函数内时,形参为Enum,在函数体内进行类型强转. private T GetEnumType<T>(object o) { T enumVal = (T ...

  6. 如何在Notepad++中轻松删除包含指定文本的字符串

    如果你需要在大量文本中删除指定模式的字符串,可以使用Notepad++中的正则表达式功能.下面是一个示例,让你可以快速学会如何删除包含指定文本的字符串.我们将使用以下示例字符串: This is a ...

  7. 一篇文章带你了解Python基础测试工具——UnitTest

    一篇文章带你了解Python基础测试工具--UnitTest 测试人员一般使用Python作为主语言脚本来进行自动化开发,而Python自带的UnitTest脚本通常就是测试人员首先掌握的 那么本篇文 ...

  8. Acwing 180. 排书

    给定 \(n\) 本书,编号为 1∼n. 在初始状态下,书是任意排列的. 在每一次操作中,可以抽取其中连续的一段,再把这段插入到其他某个位置. 我们的目标状态是把书按照 1∼n 的顺序依次排列. 求最 ...

  9. 制作交互式页面动画 | animate+javaweb

    目前是做得这样的作业,有想法改一改.

  10. 【Android】学习day05|RadioButton

    注意事项:当使用默认选中标签:check时,必须要给标签加id,否则失效. 这个没什么,挺简单的,就记录一下代码[监听事件] package com.example.app02; import and ...