python實現ModBusTCP協(xié)議的client是一件簡單的事情,只要通過pymodbus、pyModbusTCP等模塊都可以實現,本文采用pymodbus。
一、ModBusTCP協(xié)議
1、了解ModBusTCP協(xié)議
Modbus TCP 是一種基于 TCP/IP 協(xié)議棧的 Modbus 通信協(xié)議,它用于在工業(yè)自動化系統(tǒng)中進行設備之間的通信。Modbus TCP 將 Modbus 協(xié)議封裝在 TCP/IP 協(xié)議之上,通過網絡連接設備,實現數據的讀取和寫入。
以下是 Modbus TCP 的基本特點:
(1)基于 TCP/IP 協(xié)議:Modbus TCP 使用 TCP/IP 網絡進行通信,可以通過以太網、互聯網等方式進行遠程通信。
(2)實時性:Modbus TCP 具有較高的實時性,適用于需要快速響應的控制系統(tǒng)。
(3)異步通信:Modbus TCP 支持異步通信,允許設備之間的非同步數據交換。
(4)客戶端-服務器模型:Modbus TCP 通信采用客戶端-服務器模型??蛻舳耍ㄍǔJ强刂葡到y(tǒng)或監(jiān)控系統(tǒng))向服務器(設備或傳感器)發(fā)出請求,服務器返回響應數據。
(5)支持多種數據類型:Modbus TCP 支持不同數據類型的讀寫操作,包括線圈(Coil)、離散輸入(Discrete Input)、保持寄存器(Holding Register)和輸入寄存器(Input Register)等。
(6)數據傳輸格式:Modbus TCP 使用 Modbus 協(xié)議的格式進行數據傳輸,包括設備地址、功能碼、數據域等。
(7)安全性:由于 Modbus TCP 通信是基于 TCP/IP 的,因此可以通過網絡安全措施(例如 VPN、防火墻等)提供數據傳輸的安全性。
總的來說,Modbus TCP 提供了一種可靠的、靈活的工業(yè)通信解決方案,廣泛用于自動化領域中的各種設備之間的數據交換。
2、ModBusTCP協(xié)議的client與TCPclient的區(qū)別?
Modbus TCP 是一種特定的應用層通信協(xié)議,用于在工業(yè)自動化系統(tǒng)中設備之間進行數據交換。它是在 TCP/IP 協(xié)議棧上運行的 Modbus 協(xié)議的變種。Modbus TCP 協(xié)議的數據包是通過 TCP/IP 協(xié)議進行傳輸的。
TCP client 是一種通用的網絡通信模式,它指的是通過 TCP/IP 協(xié)議與遠程服務器建立連接,并向服務器發(fā)送請求并接收響應的程序。TCP client 可以用于與任何支持 TCP/IP 協(xié)議的服務器進行通信,不限于 Modbus 協(xié)議。
區(qū)別主要在于:
(1)用途不同:Modbus TCP 是一種特定的工業(yè)自動化通信協(xié)議,用于工業(yè)設備之間的數據交換;而 TCP client 是一種通用的網絡通信模式,可以與各種服務器進行通信,不限于 Modbus 協(xié)議。
(2)協(xié)議不同:Modbus TCP 使用 Modbus 協(xié)議進行數據傳輸,而 TCP client 沒有固定的協(xié)議限制,可以與各種應用層協(xié)議進行通信。
(3)功能不同:Modbus TCP 協(xié)議定義了特定的功能碼和數據格式,用于讀寫線圈、離散輸入、保持寄存器等;TCP client 則沒有固定的功能碼和數據格式,可以根據具體需求自定義通信內容。
(4)適用場景不同:Modbus TCP 主要用于工業(yè)自動化控制系統(tǒng)中,用于實時數據交換;TCP client 可以用于各種通信場景,包括 Web 客戶端、數據庫客戶端、文件傳輸等。
綜上所述,Modbus TCP 是一種特定協(xié)議的 TCP client,用于在工業(yè)自動化領域實現設備之間的數據交換。TCP client 則是一個更通用的概念,可以與各種服務器進行通信,不受特定協(xié)議限制。
3、ModBusTCP協(xié)議的數據幀格式是怎樣的?
大體如上圖紅色部分所描述,Modbus TCP 協(xié)議的數據幀格式如下:
(1)MBAP 頭部(Modbus Application Protocol Header):
Transaction Identifier(事務標識符):2 字節(jié),用于標識事務,通常是遞增的序號。
Protocol Identifier(協(xié)議標識符):2 字節(jié),固定為0,表示 Modbus 協(xié)議。
Length(數據長度):2 字節(jié),表示 MBAP 后面數據的長度,包括單元標識符(Unit Identifier)和數據字段。
Unit Identifier(單元標識符):1 字節(jié),用于標識 Modbus 設備,通常為 1。
(2)PDU(Protocol Data Unit):
Function Code(功能碼):1 字節(jié),表示 Modbus 操作的類型,如讀取保持寄存器、寫入線圈等。
Data(數據):根據功能碼的不同,數據的格式和長度會有所變化。
數據幀的格式可以根據不同的功能碼和操作類型而變化,例如:
(1)對于讀取保持寄存器(Function Code 0x03):
起始地址:2 字節(jié),表示要讀取的寄存器的起始地址。
寄存器數量:2 字節(jié),表示要讀取的寄存器的數量。
(2)對于寫入單個保持寄存器(Function Code 0x06):
寄存器地址:2 字節(jié),表示要寫入的寄存器的地址。
寄存器值:2 字節(jié),表示要寫入的寄存器的值。
總體來說,Modbus TCP 協(xié)議的數據幀格式是固定的,但是具體的數據內容和長度會根據功能碼的不同而有所變化。詳細的數據幀格式需要根據具體的功能碼和操作類型來確定。
二、一個Demo及其引發(fā)的問題
1、一個Demo
from pymodbus.client import ModbusTcpClient
if __name__ == "__main__":
# Modbus TCP服務器的IP地址和端口號
server_ip = "192.168.1.189"
port = 502
station = 1
# 創(chuàng)建Modbus TCP客戶端
MDclient = ModbusTcpClient(server_ip, port)
if MDclient.connect():
# 讀取保持寄存器的示例
address = 0 # 起始寄存器地址
count = 10 # 要讀取的寄存器數量
MDclient.write_registers(address, 115, slave=1)
time.sleep(2)
response = MDclient.read_holding_registers(10, 10, slave=station)
print(response.registers[0])
MDclient.close()
2、pymodbus.client.ModbusTcpClient 都實現了哪些功能碼
pymodbus.client.ModbusTcpClient
類是 PyModbus 庫中用于 Modbus TCP 客戶端通信的類。它支持以下常用的 Modbus 功能碼:
(1)讀取線圈狀態(tài)(Read Coils):功能碼 0x01,用于讀取輸出線圈的狀態(tài)。
read_coils(address, count=1, slave=0)
address
: 起始線圈的地址count
: 要讀取的線圈數量slave
: Modbus 單元標識符(站號)
(2)讀取離散輸入狀態(tài)(Read Discrete Inputs):功能碼 0x02,用于讀取輸入線圈的狀態(tài)。
read_discrete_inputs(address, count=1, slave=0)
address
: 起始輸入線圈的地址count
: 要讀取的輸入線圈數量slave
: Modbus 單元標識符
(3)讀取保持寄存器(Read Holding Registers):功能碼 0x03,用于讀取保持寄存器的值。
read_holding_registers(address, count=1, slave=0)
address
: 起始保持寄存器的地址count
: 要讀取的保持寄存器數量slave
: Modbus 單元標識符
(4)讀取輸入寄存器(Read Input Registers):功能碼 0x04,用于讀取輸入寄存器的值。
read_input_registers(address, count=1, slave=0)
address
: 起始輸入寄存器的地址count
: 要讀取的輸入寄存器數量slave
: Modbus 單元標識符
(5)寫單個線圈(Write Single Coil):功能碼 0x05,用于寫入一個輸出線圈的狀態(tài)。
write_coil(address, value, slave=0)
address
: 線圈的地址value
: 要寫入的值,True 表示 ON,False 表示 OFFslave
: Modbus 單元標識符
(6)寫單個寄存器(Write Single Register):功能碼 0x06,用于寫入一個保持寄存器的值。
write_register(address, value, slave=0)
address
: 寄存器的地址value
: 要寫入的值slave
: Modbus 單元標識符
(7)寫多個線圈(Write Multiple Coils):功能碼 0x0F,用于寫入多個輸出線圈的狀態(tài)。
write_coils(address, values, slave=0)
address
: 起始線圈的地址values
: 要寫入的線圈狀態(tài),是一個布爾值列表slave
: Modbus 單元標識符
(8)寫多個寄存器(Write Multiple Registers):功能碼 0x10,用于寫入多個保持寄存器的值。
write_registers(address, values, slave=0)
address
: 起始寄存器的地址values
: 要寫入的寄存器值,是一個整數列表slave
: Modbus 單元標識符
3、讀到的結果與寫入的值為什么是反的
只能說有可能是反的。
比如我用 response = MDclient.read_holding_registers(10, 10, slave=station) print(response.registers[0])打印了一個寄存器,能讀到數值為17217,轉化成16進制再轉化成字符串為CA,但實際的字符串應該為AC,這是為什么呢?
當你期望的結果是AC
而不是CA
,說明你讀取的數值可能被解釋為了大端字節(jié)序(big-endian)而不是你期望的小端字節(jié)序(little-endian)。在大端字節(jié)序中,高位字節(jié)保存在低地址,而在小端字節(jié)序中,高位字節(jié)保存在高地址。
這里為什么說可能呢?原因很簡單,通信的兩端如果內存存儲方式不一樣,讀到的數據就是反的。比如PC是小端序,嵌入式設備是大端序,存儲在嵌入式設備的值為AC,高位是A,低位是C,那么PC在以小端序存儲時,會把對方高位的A存儲低位,把對方低位的C存儲到高位,就變成了CA。
如果二者都是大端序或小端序的存儲方式,則不會有該問題,因為他們的讀取和存儲方式都是一樣的。
4、大端序與小端序之間的傳輸
當數據在大端字節(jié)序(big-endian)和小端字節(jié)序(little-endian)之間傳送時,通常需要進行大小端轉換。這是因為不同的處理器和計算機體系結構可能使用不同的字節(jié)序,如果發(fā)送方和接收方的字節(jié)序不同,就需要進行轉換。
- 大端字節(jié)序:在大端字節(jié)序中,高位字節(jié)保存在低地址,低位字節(jié)保存在高地址。例如,整數
0x12345678
在大端字節(jié)序中存儲為12 34 56 78
。 - 小端字節(jié)序:在小端字節(jié)序中,低位字節(jié)保存在低地址,高位字節(jié)保存在高地址。同樣的整數
0x12345678
在小端字節(jié)序中存儲為78 56 34 12
。
如果數據在不同字節(jié)序的系統(tǒng)之間傳遞,發(fā)送方需要將數據按照目標系統(tǒng)的字節(jié)序進行轉換,接收方再將接收到的數據進行反向轉換,以確保數據的正確傳遞。在很多網絡通信和文件傳輸的場景下,大小端轉換是非常常見的操作。
5、線圈、輸入寄存器、保持寄存器、離散寄存器分別占用多少bit位
在Modbus協(xié)議中,不同類型的數據(線圈、輸入寄存器、保持寄存器、離散寄存器)占用的位數如下:
(1)線圈(Coils):線圈是只能讀寫的二進制輸出,每個線圈占用1位。這意味著一個線圈的狀態(tài)只能是開(1)或者關(0)。
(2)輸入寄存器(Input Registers):輸入寄存器是只讀的,每個輸入寄存器占用16位(2個字節(jié))。
(3)保持寄存器(Holding Registers):保持寄存器是讀寫的,每個保持寄存器也占用16位(2個字節(jié))。
(4)離散輸入(Discrete Inputs):離散輸入是只讀的二進制輸入,每個離散輸入占用1位。類似于線圈,一個離散輸入的狀態(tài)只能是開(1)或者關(0)。
總結:
- 線圈:1位
- 輸入寄存器:16位(2字節(jié))
- 保持寄存器:16位(2字節(jié))
- 離散輸入:1位
請注意,這些大小是Modbus協(xié)議規(guī)定的標準大小,不同的設備可能有不同的實現,因此在實際應用中,你應該查閱設備的文檔以確認具體的數據大小。
6、一個保持寄存器能存2個字母,用兩個保持寄存器存儲ACK1,第一個寄存器存儲AC,第二個存儲K1,大端序會怎么存儲?
在大端序(Little Endian)下,字節(jié)的存儲順序是低位字節(jié)在前,高位字節(jié)在后。如果每個保持寄存器能存儲兩個字母,要存儲字符串“ACK1”,它將被拆分為兩個部分:'AC' 和 'K1'。以AC為例,A為高位,C為低位。
在大端序下,存儲順序如下:
- 第一個保持寄存器(低位字節(jié)在前(高位),高位字節(jié)在后(低位)):存儲67(0x43)和65(0x41),即存儲為0x4341。
- 第二個保持寄存器(低位字節(jié)在前(高位),高位字節(jié)在后(低位)):存儲49(0x31)和75(0x4B),即存儲為0x314B。
所以,在小端序下,字符串“ACK1”會按照上述方式存儲到兩個保持寄存器中。
7、如何判斷win10存儲是大端序還是小端序?
在通用的個人計算機(包括Windows 10系統(tǒng))中,主流處理器(如x86和x86-64架構的處理器)使用的是小端序(Little Endian)存儲。這意味著在存儲多字節(jié)數據時,低位字節(jié)存儲在內存的低地址處,高位字節(jié)存儲在內存的高地址處。
如果你想要確認你的Windows 10系統(tǒng)是使用小端序還是大端序,可以使用Python來進行測試。以下是一個簡單的Python代碼片段,它可以幫助你判斷系統(tǒng)的字節(jié)序:
import sys
print(sys.byteorder)
運行這段代碼,如果輸出結果是'little'
,說明你的系統(tǒng)是小端序;如果輸出結果是'big'
,則表示系統(tǒng)是大端序。在大部分個人計算機上,特別是使用x86和x86-64架構的系統(tǒng),輸出應該是'little'
。