python实现ModBusTCP协议的client是一件简单的事情,只要通过pymodbus或pyModbusTCP任意模块就可以实现,本文采用pymodbus。
一、ModBusTCP协议
1、了解ModBusTCP协议
Modbus TCP 是一种基于 TCP/IP 协议栈的 Modbus 通信协议,它用于在工业自动化系统中进行设备之间的通信。Modbus TCP 将 Modbus 协议封装在 TCP/IP 协议之上,通过网络连接设备,实现数据的读取和写入。
以下是 Modbus TCP 的基本特点:
(1)基于 TCP/IP 协议:Modbus TCP 使用 TCP/IP 网络进行通信,可以通过以太网、互联网等方式进行远程通信。
(2)实时性:Modbus TCP 具有较高的实时性,适用于需要快速响应的控制系统。
(3)异步通信:Modbus TCP 支持异步通信,允许设备之间的非同步数据交换。
(4)客户端-服务器模型:Modbus TCP 通信采用客户端-服务器模型。客户端(通常是控制系统或监控系统)向服务器(设备或传感器)发出请求,服务器返回响应数据。
(5)支持多种数据类型:Modbus TCP 支持不同数据类型的读写操作,包括线圈(Coil)、离散输入(Discrete Input)、保持寄存器(Holding Register)和输入寄存器(Input Register)等。
(6)数据传输格式:Modbus TCP 使用 Modbus 协议的格式进行数据传输,包括设备地址、功能码、数据域等。
(7)安全性:由于 Modbus TCP 通信是基于 TCP/IP 的,因此可以通过网络安全措施(例如 VPN、防火墙等)提供数据传输的安全性。
总的来说,Modbus TCP 提供了一种可靠的、灵活的工业通信解决方案,广泛用于自动化领域中的各种设备之间的数据交换。
2、ModBusTCP协议的client与TCPclient的区别?
Modbus TCP 是一种特定的应用层通信协议,用于在工业自动化系统中设备之间进行数据交换。它是在 TCP/IP 协议栈上运行的 Modbus 协议的变种。Modbus TCP 协议的数据包是通过 TCP/IP 协议进行传输的。
TCP client 是一种通用的网络通信模式,它指的是通过 TCP/IP 协议与远程服务器建立连接,并向服务器发送请求并接收响应的程序。TCP client 可以用于与任何支持 TCP/IP 协议的服务器进行通信,不限于 Modbus 协议。
区别主要在于:
(1)用途不同:Modbus TCP 是一种特定的工业自动化通信协议,用于工业设备之间的数据交换;而 TCP client 是一种通用的网络通信模式,可以与各种服务器进行通信,不限于 Modbus 协议。
(2)协议不同:Modbus TCP 使用 Modbus 协议进行数据传输,而 TCP client 没有固定的协议限制,可以与各种应用层协议进行通信。
(3)功能不同:Modbus TCP 协议定义了特定的功能码和数据格式,用于读写线圈、离散输入、保持寄存器等;TCP client 则没有固定的功能码和数据格式,可以根据具体需求自定义通信内容。
(4)适用场景不同:Modbus TCP 主要用于工业自动化控制系统中,用于实时数据交换;TCP client 可以用于各种通信场景,包括 Web 客户端、数据库客户端、文件传输等。
综上所述,Modbus TCP 是一种特定协议的 TCP client,用于在工业自动化领域实现设备之间的数据交换。TCP client 则是一个更通用的概念,可以与各种服务器进行通信,不受特定协议限制。
3、ModBusTCP协议的数据帧格式是怎样的?
大体如上图红色部分所描述,Modbus TCP 协议的数据帧格式如下:
(1)MBAP 头部(Modbus Application Protocol Header):
Transaction Identifier(事务标识符):2 字节,用于标识事务,通常是递增的序号。
Protocol Identifier(协议标识符):2 字节,固定为0,表示 Modbus 协议。
Length(数据长度):2 字节,表示 MBAP 后面数据的长度,包括单元标识符(Unit Identifier)和数据字段。
Unit Identifier(单元标识符):1 字节,用于标识 Modbus 设备,通常为 1。
(2)PDU(Protocol Data Unit):
Function Code(功能码):1 字节,表示 Modbus 操作的类型,如读取保持寄存器、写入线圈等。
Data(数据):根据功能码的不同,数据的格式和长度会有所变化。
数据帧的格式可以根据不同的功能码和操作类型而变化,例如:
(1)对于读取保持寄存器(Function Code 0x03):
起始地址:2 字节,表示要读取的寄存器的起始地址。
寄存器数量:2 字节,表示要读取的寄存器的数量。
(2)对于写入单个保持寄存器(Function Code 0x06):
寄存器地址:2 字节,表示要写入的寄存器的地址。
寄存器值:2 字节,表示要写入的寄存器的值。
总体来说,Modbus TCP 协议的数据帧格式是固定的,但是具体的数据内容和长度会根据功能码的不同而有所变化。详细的数据帧格式需要根据具体的功能码和操作类型来确定。
二、一个Demo及其引发的问题
1、一个Demo
from pymodbus.client import ModbusTcpClient
if __name__ == "__main__":
# Modbus TCP服务器的IP地址和端口号
server_ip = "192.168.1.189"
port = 502
station = 1
# 创建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)读取线圈状态(Read Coils):功能码 0x01,用于读取输出线圈的状态。
read_coils(address, count=1, slave=0)
address
: 起始线圈的地址count
: 要读取的线圈数量slave
: Modbus 单元标识符(站号)
(2)读取离散输入状态(Read Discrete Inputs):功能码 0x02,用于读取输入线圈的状态。
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,用于写入一个输出线圈的状态。
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,用于写入多个输出线圈的状态。
write_coils(address, values, slave=0)
address
: 起始线圈的地址values
: 要写入的线圈状态,是一个布尔值列表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
,说明你读取的数值可能被解释为了大端字节序(big-endian)而不是你期望的小端字节序(little-endian)。在大端字节序中,高位字节保存在低地址,而在小端字节序中,高位字节保存在高地址。
这里为什么说可能呢?原因很简单,通信的两端如果内存存储方式不一样,读到的数据就是反的。比如PC是小端序,嵌入式设备是大端序,存储在嵌入式设备的值为AC,高位是A,低位是C,那么PC在以小端序存储时,会把对方高位的A存储低位,把对方低位的C存储到高位,就变成了CA。
如果二者都是大端序或小端序的存储方式,则不会有该问题,因为他们的读取和存储方式都是一样的。
4、大端序与小端序之间的传输
当数据在大端字节序(big-endian)和小端字节序(little-endian)之间传送时,通常需要进行大小端转换。这是因为不同的处理器和计算机体系结构可能使用不同的字节序,如果发送方和接收方的字节序不同,就需要进行转换。
-
大端字节序 :在大端字节序中,高位字节保存在低地址,低位字节保存在高地址。例如,整数
0x12345678
在大端字节序中存储为12 34 56 78
。 -
小端字节序 :在小端字节序中,低位字节保存在低地址,高位字节保存在高地址。同样的整数
0x12345678
在小端字节序中存储为78 56 34 12
。
如果数据在不同字节序的系统之间传递,发送方需要将数据按照目标系统的字节序进行转换,接收方再将接收到的数据进行反向转换,以确保数据的正确传递。在很多网络通信和文件传输的场景下,大小端转换是非常常见的操作。
5、线圈、输入寄存器、保持寄存器、离散寄存器分别占用多少bit位
在Modbus协议中,不同类型的数据(线圈、输入寄存器、保持寄存器、离散寄存器)占用的位数如下:
(1)线圈(Coils):线圈是只能读写的二进制输出,每个线圈占用1位。这意味着一个线圈的状态只能是开(1)或者关(0)。
(2)输入寄存器(Input Registers):输入寄存器是只读的,每个输入寄存器占用16位(2个字节)。
(3)保持寄存器(Holding Registers):保持寄存器是读写的,每个保持寄存器也占用16位(2个字节)。
(4)离散输入(Discrete Inputs):离散输入是只读的二进制输入,每个离散输入占用1位。类似于线圈,一个离散输入的状态只能是开(1)或者关(0)。
总结:
- 线圈:1位
- 输入寄存器:16位(2字节)
- 保持寄存器:16位(2字节)
- 离散输入:1位
请注意,这些大小是Modbus协议规定的标准大小,不同的设备可能有不同的实现,因此在实际应用中,你应该查阅设备的文档以确认具体的数据大小。
6、一个保持寄存器能存2个字母,用两个保持寄存器存储ACK1,第一个寄存器存储AC,第二个存储K1,大端序会怎么存储?
在大端序(Little Endian)下,字节的存储顺序是低位字节在前,高位字节在后。如果每个保持寄存器能存储两个字母,要存储字符串"ACK1",它将被拆分为两个部分:'AC' 和 'K1'。以AC为例,A为高位,C为低位。
在大端序下,存储顺序如下:
- 第一个保持寄存器(低位字节在前(高位),高位字节在后(低位)):存储67(0x43)和65(0x41),即存储为0x4341。
- 第二个保持寄存器(低位字节在前(高位),高位字节在后(低位)):存储49(0x31)和75(0x4B),即存储为0x314B。
所以,在小端序下,字符串"ACK1"会按照上述方式存储到两个保持寄存器中。
7、如何判断win10存储是大端序还是小端序?
在通用的个人计算机(包括Windows 10系统)中,主流处理器(如x86和x86-64架构的处理器)使用的是小端序(Little Endian)存储。这意味着在存储多字节数据时,低位字节存储在内存的低地址处,高位字节存储在内存的高地址处。
如果你想要确认你的Windows 10系统是使用小端序还是大端序,可以使用Python来进行测试。以下是一个简单的Python代码片段,它可以帮助你判断系统的字节序:
import sys
print(sys.byteorder)
运行这段代码,如果输出结果是'little'
,说明你的系统是小端序;如果输出结果是'big'
,则表示系统是大端序。在大部分个人计算机上,特别是使用x86和x86-64架构的系统,输出应该是'little'
。