Modbus TCP数据包由两部分组成:
-
MBAP头(Modbus Application Protocol Header) - 7字节
-
PDU(Protocol Data Unit) - 可变长度
完整数据帧结构:
事务标识符 \]\[ 协议标识符 \]\[ 长度字段 \]\[ 单元标识符 \]\[ 功能码 \]\[ 数据
2字节 2字节 2字节 1字节 1字节 N字节
逐字节解析:
| 字节位置 | 值(十六进制) | 说明 |
|---|---|---|
| 0-1 | 00 01 |
事务标识符(Transaction ID):0x0001,用于匹配请求和响应 |
| 2-3 | 00 00 |
协议标识符(Protocol ID):0x0000,Modbus协议固定值 |
| 4-5 | 00 06 |
长度字段:0x0006 = 6,表示后面还有6个字节 |
| 6 | 01 |
单元标识符(Unit ID/设备地址):0x01 = 设备地址1 |
| 7 | 03 |
功能码(Function Code):0x03 = 读保持寄存器 |
| 8-9 | 01 90 |
起始地址:0x0190 = 十进制400(Modbus地址从0开始,对应40001) |
| 10-11 | 00 0C |
寄存器数量:0x000C = 十进制12 |
逐字节解析响应:
| 字节位置 | 值(十六进制) | 说明 |
|---|---|---|
| 0-1 | 00 01 |
事务标识符:与请求匹配,0x0001 |
| 2-3 | 00 00 |
协议标识符:0x0000 |
| 4-5 | 00 19 |
长度字段:0x0019 = 25,后面有25个字节 |
| 6 | 01 |
单元标识符:设备地址1 |
| 7 | 03 |
功能码:0x03 |
| 8 | 18 |
字节计数:0x18 = 24字节(12个寄存器 × 2字节/寄存器) |
| 9-32 | 00 00 ... |
寄存器数据:每个寄存器2字节,共12个寄存器 |
代码解析:
功能码已包含在各方法中,不需要另外指定。
# 读取保持寄存器 - 功能码 0x03,不需要指定function_code=0x03,方法名已包含
result = self.client.read_holding_registers(...)
# 读取输入寄存器 - 功能码 0x04
result = self.client.read_input_registers(...)
# 读取线圈状态 - 功能码 0x01
result = self.client.read_coils(...)
# 读取离散输入 - 功能码 0x02
result = self.client.read_discrete_inputs(...)
real_vol_int = (registers[0] << 16) | registers[1]
1、registers[0] << 16:将第一个寄存器的值左移16位
假设 registers[0] = 0x1234(16位)
左移16位后变成:0x12340000(32位)
2、registers[1]:与第二个寄存器值进行按位或操作
假设 registers[1] = 0x5678(16位)
0x12340000 | 0x5678 = 0x12345678
3、最终结果:0x12345678(32位整数)
完整示例代码:
from pymodbus.client import ModbusTcpClient
import struct
class ModbusdeviceClient:
def __init__(self, host='*.*.*.*', port=7890, device_address=1):
self.host = host
self.port = port
self.device_address = device_address
self.client = None
self.connect()
def connect(self):
"""连接Modbus设备"""
try:
self.client = ModbusTcpClient(self.host, port=self.port)
if self.client.connect():
print(f"Modbus连接成功 - 主机: {self.host}:{self.port}, 设备地址: {self.device_address}")
else:
print("Modbus连接失败")
except Exception as e:
print(f"Modbus连接错误: {e}")
def read_device_data(self):
try:
# 使用read_holding_registers读取保持寄存器
result = self.client.read_holding_registers(
address=0x0190, # 开始地址,使用偏移地址400
count=12, # 寄存器数量
# unit=self.device_address # 设备地址
device_id = self.device_address
)
if not result.isError():
registers = result.registers
# 解析数据
parsed_data = self._parse_device_data(registers)
return parsed_data
else:
print(f"读取数据错误: {result}")
return None
except Exception as e:
print(f"读取设备数据错误: {e}")
# 尝试重新连接
self.connect()
return None
def _parse_device_data(self, registers):
"""
正确解析流量数据
registers: 包含12个WORD值的列表
"""
if len(registers) < 12:
return None
results = {}
# 1. 实时流量(体积) - 寄存器0,1
real_vol_int = (registers[0] << 16) | registers[1]
results['instant_flow_volume'] = real_vol_int / 100.0 # m³/h
。。。
return results
def build_request_message(self):
print("\n请求报文结构分析:")
print("00 01 - 事务标识符 (Transaction ID)")
print("00 00 - 协议标识符 (Protocol ID)")
print("00 06 - 长度 (Length)")
print(f"{self.device_address:02X} - 单元标识符 (Unit ID/设备地址)") # {:02X} 是 Python 的 格式化字符串
print("03 - 功能码 (Function Code - 读保持寄存器)")
print("9D D0 - 起始地址 (Start Address - 0x000A)")
print("00 0C - 寄存器数量 (Number of Registers)")
# 返回报文结构字典
return {
'transaction_id': 0x0001,
'protocol_id': 0x0000,
'length': 0x0006,
'unit_id': self.device_address,
'function_code': 0x03,
'start_address': 0x9DD0,
'register_count': 0x000C
}
def is_connected(self):
"""检查连接状态"""
return self.client.is_socket_open() if self.client else False
def close(self):
"""关闭连接"""
if self.client:
self.client.close()
print("Modbus连接已关闭")
def main():
print("=" * 70)
print("Modbus设备2数据读取演示")
print("=" * 70)
# 创建Modbus客户端
modbus_client = ModbusdeviceClient('*.*.*.*', 503, 1)
if modbus_client.is_connected():
print("\n" + "=" * 70)
print("实际数据读取")
print("=" * 70)
# 实际读取设备数据
device_data = modbus_client.read_device_data()
if device_data:
print("\n设备数据解析结果:")
print(f"实时流量(体积): {device_data['instant_flow_volume']}m³/h")
else:
print("实际数据读取失败")
# 关闭连接
modbus_client.close()
else:
print("无法连接到Modbus设备")
if __name__ == "__main__":
# 运行主演示
main()