Modbus 是一种工业通讯协议,主要分为两种传输模式:
pymodbus?虽然像 minimalmodbus 这样的库在 RTU 方面很简单,但 pymodbus 是最全面的:
确保你已经安装了 Python (建议 3.8+)。在终端执行以下命令安装库:
Bash
pip install pymodbus pyserial
(注意:pyserial 是运行 Modbus RTU 所必需的依赖)
在编写代码前,你需要了解 Modbus 的功能码和数据类型:
| 功能码 (Function Code) | 描述 | 数据类型 | 读/写 | Python 方法 (pymodbus) |
|---|---|---|---|---|
| 01 (0x01) | 读线圈 (Coils) | Bool (ON/OFF) | 读 | read_coils |
| 02 (0x02) | 读离散输入 (Discrete Inputs) | Bool (ON/OFF) | 读 | read_discrete_inputs |
| 03 (0x03) | 读保持寄存器 (Holding Registers) | 16-bit Word | 读 | read_holding_registers |
| 04 (0x04) | 读输入寄存器 (Input Registers) | 16-bit Word | 读 | read_input_registers |
| 05 (0x05) | 写单个线圈 | Bool | 写 | write_coil |
| 06 (0x06) | 写单个寄存器 | 16-bit Word | 写 | write_register |
| 15 (0x0F) | 写多个线圈 | Bool Array | 写 | write_coils |
| 16 (0x10) | 写多个寄存器 | Word Array | 写 | write_registers |
这是最常见的场景:Python 作为主站(Client),去读取 PLC 或远程 I/O 模块(Server)的数据。
Python
from pymodbus.client import ModbusTcpClient
import time
# 1. 配置连接参数
# 请替换为你设备的实际 IP
IP_ADDRESS = '192.168.1.10'
PORT = 502
# 2. 创建客户端实例
client = ModbusTcpClient(IP_ADDRESS, port=PORT)
try:
# 3. 建立连接
connection = client.connect()
if connection:
print(f"成功连接到 {IP_ADDRESS}:{PORT}")
# --- 读取操作 ---
# 例子:读取从地址 100 开始的 10 个保持寄存器 (Function Code 03)
# slave=1 是从站 ID (Unit ID),在 TCP 中通常默认为 1 或 0,但也可能被设备指定
rr = client.read_holding_registers(address=100, count=10, slave=1)
if not rr.isError():
print("读取成功:", rr.registers)
# rr.registers 返回的是一个整数列表,例如 [123, 0, 45, ...]
else:
print("读取失败:", rr)
# --- 写入操作 ---
# 例子:向地址 100 写入数值 55 (Function Code 06)
wr = client.write_register(address=100, value=55, slave=1)
if not wr.isError():
print("写入成功")
else:
print("写入失败:", wr)
else:
print("连接失败")
finally:
# 4. 关闭连接
client.close()
场景:使用 USB 转 RS-485 适配器连接传感器。
Python
from pymodbus.client import ModbusSerialClient
# 1. 配置串口参数
# Windows下通常是 'COM3', 'COM4' 等
# Linux下通常是 '/dev/ttyUSB0' 或 '/dev/ttyS0'
COM_PORT = 'COM3'
client = ModbusSerialClient(
port=COM_PORT,
baudrate=9600, # 波特率
bytesize=8, # 数据位
parity='N', # 校验位: 'N'-None, 'E'-Even, 'O'-Odd
stopbits=1, # 停止位
timeout=1 # 超时时间(秒)
)
try:
if client.connect():
print(f"串口 {COM_PORT} 连接成功")
# 读取从站地址为 1 的设备的寄存器
# 读取地址 0 开始的 2 个寄存器
res = client.read_holding_registers(address=0, count=2, slave=1)
if not res.isError():
print(f"收到数据: {res.registers}")
else:
print(f"读取错误: {res}")
else:
print(f"无法打开串口 {COM_PORT}")
finally:
client.close()
Modbus 寄存器本身只有 16 位(0-65535)。如果你要读取一个 32位浮点数 (Float) 或 32位整数 (Int32),数据会被拆分成两个寄存器。
你需要处理 字节序 (Endianness) 和 字序 (Word Order)。pymodbus 提供了 BinaryPayloadDecoder 和 BinaryPayloadBuilder 来完美解决这个问题。
假设设备在地址 200 存放了一个 Float (占2个寄存器)。
Python
from pymodbus.constants import Endian
from pymodbus.payload import BinaryPayloadDecoder
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient('192.168.1.10')
client.connect()
# 1. 读取原始寄存器 (Float 需要读取 2 个寄存器 = 32位)
rr = client.read_holding_registers(address=200, count=2, slave=1)
if not rr.isError():
# 2. 使用解码器处理数据
# byteorder 和 wordorder 取决于你的 PLC 厂商
# Big Endian (大端) 是最常见的网络标准
decoder = BinaryPayloadDecoder.fromRegisters(
rr.registers,
byteorder=Endian.Big,
wordorder=Endian.Big
)
# 3. 解析为 32位 浮点数
decoded_value = decoder.decode_32bit_float()
print(f"解码后的温度值: {decoded_value:.2f}")
client.close()
Python
from pymodbus.constants import Endian
from pymodbus.payload import BinaryPayloadBuilder
# 1. 创建构建器
builder = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Big)
# 2. 添加数据
desired_value = 123.45
builder.add_32bit_float(desired_value)
# 3. 获取 payload (这是准备写入的 16位 寄存器列表)
payload = builder.to_registers()
print(f"转换后的寄存器值: {payload}")
# 输出类似: [17260, 52429]
# 4. 写入设备
# skip_encode=True 告诉 pymodbus 我们已经把数据处理好了,直接发
client.write_registers(address=200, values=payload, slave=1, skip_encode=True)
address=0。如果不对,尝试 address=1。BinaryPayloadDecoder 中的 byteorder 和 wordorder。常见的组合有 Big/Big (Modicon), Little/Little (Intel), 或 Big/Little (Swap)。timeout 参数。
- 如果是 RTU,检查波特率、校验位是否与设备完全一致。如果你手头没有 PLC,可以用 Python 电脑变成一个 Modbus 服务器来测试你的 Client 代码。
Python
from pymodbus.server import StartTcpServer
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
def run_server():
# 1. 定义存储区
# 创建 0-100 的地址空间,初始值为 0
store = ModbusSlaveContext(
di=ModbusSequentialDataBlock(0, [0]*100), # 离散输入
co=ModbusSequentialDataBlock(0, [0]*100), # 线圈
hr=ModbusSequentialDataBlock(0, [0]*100), # 保持寄存器
ir=ModbusSequentialDataBlock(0, [0]*100) # 输入寄存器
)
# single=True 表示只模拟一个从站 ID
context = ModbusServerContext(slaves=store, single=True)
print("启动 Modbus TCP 服务器在 localhost:5020...")
# 2. 启动服务 (注意:端口使用 5020,因为 502 通常需要管理员权限)
StartTcpServer(context=context, address=("localhost", 5020))
if __name__ == "__main__":
run_server()