詳解物聯網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設備的設置。
- 7個連環問揭開java多線程背後的彎彎繞
- 漏洞檢測方法如何選?詳解源代碼與二進制SCA檢測原理
- 華為雲數據庫GaussDB(for Influx)揭祕第二期:解密GaussDB(for Influx)的數據壓縮
- 編程謎題:提升你解決問題的訓練場
- 換個角度思考勒索攻擊事件
- 從源碼分析快速實現對新開源軟件的檢測
- 一文講透數倉臨時表的用法
- 7個獲取訪問者真實IP的方法,速學!!!
- 詳解工作流框架Activiti的服務架構和組件
- IaaS首席架構師的架構設計思考與實踐
- 同態加密實現數據隱私計算,能讓你的小祕密更加祕密
- 帶你瞭解Typescript的14個基礎語法
- JDK 動態代理與 CGLIB 動態代理,它倆真的不一樣
- 解讀HetuEngine On Yarn原理
- 面試官:Java 線程如何啟動的?
- 華為雲企業級Redis評測第一期:穩定性與擴容表現
- 你知道,什麼時候用Vue計算屬性嗎?
- 看FusionInsight Spark如何支持JDBCServer的多實例特性
- 開發好能重構的代碼,都是這麼幹的
- 文本分類:Keras RNN vs傳統機器學習