摘要: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-tcp和modbus-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 = 16Response: +-------------------------------------------------+ | 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 = 17Response: +-------------------------------------------------+ | 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设施的设置。
点击关注,第一工夫理解华为云陈腐技术~