詳解物聯網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通訊協議的官網為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、ModBusJava實現
下面介紹一下如何用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 http://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(newReadHoldingRegistersRequest(0, 10), 0)物件來查詢資料,此功能碼為03,暫存器資料為10。在Modbus Slave開啟連線後,設定介面如下所示:
執行Java程式。控制檯輸出示例如下所示:
由此,可以知曉,返回的報文中在0到f這15個位置中,有需要的業務資料,具體獲取哪個位置,取決於Slave裝置的設定。
- 帶你瞭解敏捷和DevOps的釋出策略
- 技術解析 程式碼實戰,帶你入門華為雲政務區塊鏈平臺
- NDPQ(NDP PQ),定義分散式資料庫新方向
- 併發程式設計中,你加的鎖未必安全
- 移動計算雲分散式資料快取服務,實現快速可靠的跨區域多活複製
- 併發程式設計中,你加的鎖未必安全
- 為什麼要做漏洞掃描呢?
- NDPQ(NDP PQ),定義分散式資料庫新方向
- 移動計算雲分散式資料快取服務,實現快速可靠的跨區域多活複製
- 萬字講解WiFi為何物
- Python 可以滿足你任何 API 使用需求
- 歸併排序,我舉個例子你就看懂了
- 歸併排序,我舉個例子你就看懂了
- Python 可以滿足你任何 API 使用需求
- 基於Serverless的端邊雲一體化媒體網路
- 鴻蒙輕核心原始碼分析:虛實對映
- “元宇宙”究竟是什麼
- 鴻蒙輕核心原始碼分析:虛實對映
- 使用MRS CDL實現實時資料同步的極致效能
- 破解資料匱乏現狀:縱向聯邦學習場景下的邏輯迴歸(LR)