?此賬號為華為云開發(fā)者社區(qū)官方運營賬號,提供全面深入的云計算前景分析、豐富的技術(shù)干貨、程序樣例,分享華為云前沿資訊動態(tài)
本文分享自華為云社區(qū)《一文搞懂物聯(lián)網(wǎng)Modbus通訊協(xié)議丨【拜托了,物聯(lián)網(wǎng)!】》,作者: jackwangcumt。[詳解物聯(lián)網(wǎng)Modbus通訊協(xié)議]。
隨著IT技術(shù)的快速發(fā)展,當前已經(jīng)步入了智能化時代,其中的物聯(lián)網(wǎng)技術(shù)將在未來占據(jù)越來越重要的地位。根據(jù)百度百科的定義,物聯(lián)網(wǎng)(Internet of things,簡稱IOT )即“萬物相連的互聯(lián)網(wǎng)”,是互聯(lián)網(wǎng)基礎上的延伸和擴展的網(wǎng)絡,物聯(lián)網(wǎng)將各種信息有機的結(jié)合起來,實現(xiàn)任何時間、任何地點,人、機、物的互聯(lián)互通。物聯(lián)網(wǎng)從技術(shù)上來說,很重要的核心是通訊協(xié)議,即如何按約定的通訊協(xié)議,把機、物和人與互聯(lián)網(wǎng)相連接,進行信息通信,以實現(xiàn)對人、機和物的智能化識別、定位、跟蹤、監(jiān)控和管理的一種網(wǎng)絡。
一般來說,常見的物聯(lián)網(wǎng)通訊協(xié)議眾多,如藍牙、Zigbee、WiFi、ModBus、PROFINET、EtherCAT、蜂窩等。而在眾多的物聯(lián)網(wǎng)通訊協(xié)議中,Modbus是當前非常流行的一種通訊協(xié)議。它一種串行通信協(xié)議,是Modicon公司于1979年為使用可編程邏輯控制器(PLC)通信而制定的,可以說,它已經(jīng)成為工業(yè)領(lǐng)域通信協(xié)議的業(yè)界標準。其優(yōu)勢如下:
Modbus通訊協(xié)議使用請求-應答機制在主(Master)(客戶端Client)和從(Slave)(服務器Server)之間交換信息。Client-Server原理是通信協(xié)議的模型,其中一個主設備控制多個從設備。這里需要注意的是:Modbus通訊協(xié)議當中的Master對應Client,而Slave對應Server。Modbus通訊協(xié)議的官網(wǎng)為www.modbus.org。目前官網(wǎng)組織已經(jīng)建議將Master-Slave替換為Client-Server。從協(xié)議類型上可以分為:Modbus-RTU(ASCII)、Modbus-TCP和Modbus-Plus。本文主要介紹Modbus-RTU(ASCII)的通訊協(xié)議原理。標準的Modbus協(xié)議物理層接口有RS232、RS422、RS485和以太網(wǎng)接口。
通訊示意圖如下:
一般來說,Modbus通信協(xié)議原理具備如下的特征:
Modbus協(xié)議可使用2種通信模式交換信息:
不管是請求報文還是答復報文,數(shù)據(jù)結(jié)構(gòu)如下:
即報文(幀數(shù)據(jù))由4部分構(gòu)成:地址(Slave Number)+功能碼(Function Codes)+數(shù)據(jù)(Data)+校驗(Check) 。其中的地址代表從設備的ID地址,作為尋址的信息。功能碼表示當前的請求執(zhí)行具體什么操作,比如讀還是寫。數(shù)據(jù)代表需要通訊的業(yè)務數(shù)據(jù),可以根據(jù)實際情況來確定。最后一個校驗則是驗證數(shù)據(jù)是否有誤。其中的功能碼說明如下:
比如功能碼為03代表讀取當前寄存器內(nèi)一個或多個二進制值,而06代表將二進制值寫入單一寄存器。為了模擬Modbus通訊協(xié)議過程,這里可以借助模擬軟件:
具體的安裝過程這里不再贅述。首先這里需要模擬一個物聯(lián)網(wǎng)傳感器設備,這里用Modbus Slave來定義,首先打開此軟件,并定義一個ID為1的設備:
此功能碼為03。另外,設置連接參數(shù),示例界面如下:
下面再用Modbus Poll軟件來模擬主機,來獲取從設備的數(shù)據(jù)。首先定義一個讀寫報文。
然后再定義一個連接信息:
注意:兩個COM口要使用不同的名稱。
成功建立通訊后,通信的報文格式如下:
Tx代表請求報文,而Rx代表答復報文。
下面介紹一下如何用Java來實現(xiàn)一個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>
其中關(guān)于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的連接配置的地方,需要調(diào)整連接方式,示意截圖如下:
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);
}
}
}
//發(fā)送請求
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]);//根據(jù)業(yè)務情況獲取寄存器數(shù)值
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進行數(shù)據(jù)交互
new MBMaster(1, 1).start();
}
}
首先,需要用ModbusTcpMasterConfig來初始化一個Modbus Tcp Master主機的配置信息,比如IP地址(127.0.0.1)和端口號(50201),此需要和Slave一致。其次,將配置信息config作為參數(shù)傳遞到ModbusTcpMaster對象中,構(gòu)建一個 master實例。最后,用master.sendRequest(
newReadHoldingRegistersRequest(0, 10), 0)對象來查詢數(shù)據(jù),此功能碼為03,寄存器數(shù)據(jù)為10。在Modbus Slave開啟連接后,設置界面如下所示:
運行Java程序??刂婆_輸出示例如下所示:
由此,可以知曉,返回的報文中在0到f這15個位置中,有需要的業(yè)務數(shù)據(jù),具體獲取哪個位置,取決于Slave設備的設置。
點擊關(guān)注,第一時間了解華為云新鮮技術(shù)~華為云博客_大數(shù)據(jù)博客_AI博客_云計算博客_開發(fā)者中心-華為云