📚 ModbusLink 客户端使用说明
本文档详细介绍了 ModbusLink 软件包中客户端的使用方法。ModbusLink 的客户端分为 异步 (Async) 和 同步 (Sync) 两种实现,分别适用于高并发/异步框架(如 FastAPI, GUI)和传统的阻塞式脚本开发。
核心概念:依赖注入
ModbusLink 采用 传输层 (Transport) 与 协议层 (Client) 分离的设计。 在使用客户端之前,您必须先创建对应的传输层实例(TCP, RTU 或 ASCII),然后将其传递给客户端。
- Transport: 负责底层的字节发送与接收(处理 Socket, Serial, 帧校验等)。
- Client: 负责 Modbus 协议的逻辑封装(构建 PDU, 解析响应, 数据转换)。
1. 异步客户端 (AsyncModbusClient)
适用于需要非阻塞 I/O、高并发采集或集成到异步框架的场景。
1.1 初始化
使用 async with 语法可以自动管理连接的建立与断开。
import asyncio
from modbuslink import AsyncModbusClient
# 根据硬件选择对应的传输层:
from modbuslink import AsyncTcpTransport # 以太网
# from modbuslink import AsyncRtuTransport # 串口 RTU
# from modbuslink import AsyncAsciiTransport # 串口 ASCII
async def main():
# 1. 配置传输层 (以 TCP 为例)
transport = AsyncTcpTransport(host='127.0.0.1', port=502, timeout=1.0)
# 2. 注入传输层创建客户端
# 使用 async with 自动处理 connect/close
async with AsyncModbusClient(transport) as client:
# 在此处执行操作…
pass
if __name__ == "__main__":
asyncio.run(main())
1.2 基本操作示例 (Basic Operations)
涵盖 Modbus 标准协议的基础读写功能。
🔹 读线圈 / 离散输入 / 寄存器
| read_coils | 0x01 | 读线圈状态 (RW) | slave_id: 从站地址start_address: 起始地址quantity: 数量 (1-2000) |
| read_discrete_inputs | 0x02 | 读离散输入状态 (RO) | 同上 |
| read_holding_registers | 0x03 | 读保持寄存器 (RW) | slave_id: 从站地址start_address: 起始地址quantity: 数量 (1-125) |
| read_input_registers | 0x04 | 读输入寄存器 (RO) | 同上 |
代码示例:
# 1. 读取线圈 (0x01)
# 结果: [True, False, True, …]
coils = await client.read_coils(slave_id=1, start_address=0, quantity=10)
# 2. 读取离散输入 (0x02)
inputs = await client.read_discrete_inputs(slave_id=1, start_address=0, quantity=10)
# 3. 读取保持寄存器 (0x03)
# 结果: [100, 200, 300, …] (原始 16位 整数)
holds = await client.read_holding_registers(slave_id=1, start_address=0, quantity=10)
# 4. 读取输入寄存器 (0x04)
in_regs = await client.read_input_registers(slave_id=1, start_address=0, quantity=10)
🔹 写线圈 / 寄存器
| write_single_coil | 0x05 | 写单个线圈 | slave_id: 从站地址address: 地址value: bool (True/False) |
| write_single_register | 0x06 | 写单个寄存器 | slave_id: 从站地址address: 地址value: int (0-65535) |
| write_multiple_coils | 0x0F | 写多个线圈 | start_address: 起始地址values: List[bool] (数量 1-1968) |
| write_multiple_registers | 0x10 | 写多个寄存器 | start_address: 起始地址values: List[int] (数量 1-123) |
代码示例:
# 5. 写单个线圈 (0x05)
await client.write_single_coil(slave_id=1, address=0, value=True)
# 6. 写单个寄存器 (0x06)
await client.write_single_register(slave_id=1, address=10, value=12345)
# 7. 写多个线圈 (0x0F)
await client.write_multiple_coils(
slave_id=1,
start_address=0,
values=[True, False, True, True]
)
# 8. 写多个寄存器 (0x10)
await client.write_multiple_registers(
slave_id=1,
start_address=10,
values=[11, 22, 33, 44]
)
1.3 高级数据类型示例 (Advanced Operations)
ModbusLink 内置了 PayloadCoder,可以自动将多个 16 位寄存器合并转换为浮点数、32/64位整数或字符串。
通用参数说明:
- byte_order: 字节序 (“big” / “little”), 默认为 “big”。
- word_order: 字序 (“high” / “low”), 默认为 “high”。
- encoding: 字符串编码 (如 “utf-8”, “ascii”), 默认为 “utf-8”。
🔹 浮点数与整数 (32位/64位)
# — 32位 浮点数 (Float32) —
# 写入 (占用 2 个寄存器)
await client.write_float32(slave_id=1, start_address=100, value=25.6)
# 读取
temp = await client.read_float32(slave_id=1, start_address=100)
# — 32位 整数 (Int32 / UInt32) —
# 写入有符号整数 (占用 2 个寄存器)
await client.write_int32(slave_id=1, start_address=102, value=–50000)
# 读取无符号整数
speed = await client.read_uint32(slave_id=1, start_address=104)
# — 64位 整数 (Int64 / UInt64) —
# 写入 (占用 4 个寄存器)
await client.write_int64(slave_id=1, start_address=106, value=9876543210)
# 读取
total_energy = await client.read_int64(slave_id=1, start_address=106)
# — 指定字节序/字序 (解决设备兼容性) —
# 例如:某些 PLC 使用 Little Endian, Low Word First (CDAB格式)
val = await client.read_float32(
slave_id=1,
start_address=200,
byte_order="little",
word_order="low"
)
🔹 字符串 (String)
# 写入字符串
# 库会自动计算所需的寄存器数量并进行填充
await client.write_string(slave_id=1, start_address=300, value="Hello Modbus")
# 读取字符串
# 注意:length 为字节长度,不是寄存器数量
text = await client.read_string(slave_id=1, start_address=300, length=12)
print(text) # 输出: Hello Modbus
1.4 回调操作示例 (Callback)
异步客户端的所有读写方法都支持 callback 参数。当 I/O 操作完成并收到数据后,会在后台 Task 中自动调用该函数,主程序无需等待处理逻辑。
def on_temp_read(value):
print(f"[回调] 当前温度: {value}°C")
def on_write_done():
print("[回调] 参数写入成功")
# 1. 带回调的读取
# 该行代码会立即返回,不会阻塞后续代码执行
# 当数据从设备返回时,on_temp_read 会被触发
await client.read_float32(
slave_id=1,
start_address=100,
callback=on_temp_read
)
# 2. 带回调的写入
await client.write_single_register(
slave_id=1,
address=200,
value=1,
callback=on_write_done
)
# 模拟主线程继续做其他事情
print("主线程继续运行…")
await asyncio.sleep(1)
1.5 并发操作示例 (Concurrency)
利用 asyncio.gather,可以同时向设备发送多个请求(或向多个设备发送请求),极大地缩短总轮询时间。
# 创建任务列表 (此时请求尚未发送)
tasks = [
# 任务A: 读取电压
client.read_float32(slave_id=1, start_address=0),
# 任务B: 读取电流
client.read_float32(slave_id=1, start_address=2),
# 任务C: 读取功率
client.read_float32(slave_id=1, start_address=4),
# 任务D: 读取状态位 (10个)
client.read_coils(slave_id=1, start_address=0, quantity=10)
]
print("开始并发采集…")
start_time = asyncio.get_event_loop().time()
# 并发执行所有任务
# results 列表将按 tasks 的顺序包含返回结果
results = await asyncio.gather(*tasks)
end_time = asyncio.get_event_loop().time()
print(f"采集耗时: {end_time – start_time:.3f}秒")
# 解包结果
voltage, current, power, status = results
print(f"V={voltage}, A={current}, W={power}, St={status}")
2. 同步客户端 (SyncModbusClient)
适用于简单的脚本、测试工具或不需要并发能力的场景。接口命名与异步客户端完全一致,但调用方式为阻塞式。
2.1 初始化
from modbuslink import SyncModbusClient
from modbuslink import SyncRtuTransport # 以 RTU 为例
def main():
# 1. 配置串口参数
transport = SyncRtuTransport(
port='COM3',
baudrate=9600,
bytesize=8,
parity='N',
stopbits=1,
timeout=1.0
)
# 2. 创建同步客户端
with SyncModbusClient(transport) as client:
# 执行操作…
pass
if __name__ == "__main__":
main()
2.2 基本操作示例
# 读取操作 (直接返回结果)
coils = client.read_coils(slave_id=1, start_address=0, quantity=10)
regs = client.read_holding_registers(slave_id=1, start_address=0, quantity=10)
print(f"线圈: {coils}")
print(f"寄存器: {regs}")
# 写入操作
client.write_single_coil(slave_id=1, address=0, value=True)
client.write_multiple_registers(slave_id=1, start_address=10, values=[100, 200])
2.3 高级数据类型示例
同步客户端同样支持自动编解码,使用方法与异步版本一致(除了没有 await 和 callback)。
# 读写 Float32
client.write_float32(slave_id=1, start_address=0, value=123.45)
val = client.read_float32(slave_id=1, start_address=0)
# 读写 Int32 (指定大端序)
client.write_int32(slave_id=1, start_address=4, value=–1000, byte_order="big")
val_i32 = client.read_int32(slave_id=1, start_address=4, byte_order="big")
# 读写 String
client.write_string(slave_id=1, start_address=10, value="SYNC_TEST")
val_str = client.read_string(slave_id=1, start_address=10, length=9)
附录:数据编码参数说明
在高级操作 (read_float32, write_int32 等) 中,byte_order 和 word_order 参数用于适配不同厂商 PLC 的数据存储格式。
| byte_order | “big” (默认) | 大端序 (Big-Endian)。高位字节在前。例如 0x1234 存储为 12 34。 |
| “little” | 小端序 (Little-Endian)。低位字节在前。例如 0x1234 存储为 34 12。 | |
| word_order | “high” (默认) | 高字在前。32位数据的高16位存储在第一个寄存器地址。 |
| “low” | 低字在前。32位数据的低16位存储在第一个寄存器地址。 |
常见组合速查:
- ABCD (标准): byte_order=“big”, word_order=“high”
- CDAB (常见于部分PLC): byte_order=“big”, word_order=“low”
- BADC: byte_order=“little”, word_order=“high”
- DCBA: byte_order=“little”, word_order=“low”
GitHub地址
Miraitowa-la/ModbusLink





