摘要: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. Dynamics CRM中自定义页面实现附件管理包含下载模板、上传、下载、删除

    前言 附件使用的Dynamics CRM平台本身的注释表annotation存储,将附件转换成二进制字节流保存到数据库中,因自带的注释在页面中显示附件不够直观,特做了一个单独的附件管理自定义页面,通过 ...

  2. vscode/sublime 语法高亮定义和代码段的区别

    vscode插件数据格式基于json,sublime插件数据格式基于xml.sublime插件的官方文档说的不清楚,相关教程也很难找,遇到的一些坑记录一下 语法定义文件对比 同样使用TextMate定 ...

  3. Java Lambda 表达式常见面试问题与解答

    公众号「架构成长指南」,专注于生产实践.云原生.分布式系统.大数据技术分享. 在本文中,我们将讨论一些重要且常见的 Java Lambda 表达式面试问题和解答 1.什么是 Lambda 表达式? l ...

  4. 2022.7.13 tongyf 讲课纪要

    前言 这个笔记记晚了,主要是都在跟 \(LCT\) 进行殊死搏斗,所以博客这方面就挂了很久. tongyf 学长当年是拿到省一之后省选炸了,之后暴切高考.ORZ%%% 这节课讲的是线性dp和背包dp, ...

  5. HDL刷题:Edgedetect

    原题链接 一道想了好久的题目,在这种并行执行的程序里怎么才能保存前一个状态,看了题解后才发觉,非阻塞赋值啊,代码如下: module top_module ( input clk, input [7: ...

  6. Welcome to YARP - 8.分布式跟踪

    Welcome to YARP - 1.认识YARP并搭建反向代理服务 Welcome to YARP - 2.配置功能 2.1 - 配置文件(Configuration Files) 2.2 - 配 ...

  7. python列表删除元素之del,pop()和remove()

    del函数 如果知道要删除元素在列表中的位置,可以使用del语句: list_1 = ['one', 'two', 'three'] print(list_1) del list_1[0] print ...

  8. Windows之——pid为4的system进程占用80端口的解决办法

    因为Apache无法启动的原因,用netstat命令查看了一下80端口是否被占用了,如下 C:\Users\Maple>netstat -ano | findstr 0.0.0.0:80 TCP ...

  9. 🔥🔥Java开发者的Python快速进修指南:实战之跳表pro版本

    之前我们讲解了简易版的跳表,我希望你能亲自动手实现一个更完善的跳表,同时也可以尝试实现其他数据结构,例如动态数组或哈希表等.通过实践,我们能够发现自己在哪些方面还有所欠缺.这些方法只有在熟练掌握之后才 ...

  10. 华企盾DSC由于半透明软件设置了需要管理员权限打开导致半透明打不开加密文件

    解决方法: 1.右键该应用程序->属性->兼容性,去掉[以管理员权限运行此程序] 2.也可以打开控制面板->系统和安全->用户账户控制设置调至最低