乐趣区

关于物联网:详解物联网Modbus通讯协议

摘要: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 设施的设置。

点击关注,第一工夫理解华为云陈腐技术~

退出移动版