3. Modbus TCP 服务器模拟
任务目标
本案例将创建一个基于 pyModbus 库的 Modbus TCP 服务器,模拟一个从站设备。该服务器将维护一组寄存器(保持寄存器和线圈),并监听网络端口,供其他 Modbus 客户端(如 Modbus Poll、另一台设备或本机测试脚本)进行读写操作。通过本案例,你将得到一个可独立运行的从站模拟器,用于后续案例中的主站通信测试或边缘网关数据采集开发。
环境与依赖
需要 Python 3.8+ 环境,并安装 pyModbus 库(版本 ≥ 3.0):
pip install pymodbus
完整实现代码
以下代码创建了一个 Modbus TCP 服务器,监听 0.0.0.0:5020 端口。服务器内部维护了 10 个保持寄存器(地址 0-9)和 10 个线圈(地址 0-9),并设置了初始值。客户端可对这些地址进行读写操作。
# modbus_tcp_server.py
from pymodbus.server import StartTcpServer
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
import logging
# 配置日志(可选,便于观察请求)
logging.basicConfig()
logging.getLogger('pymodbus').setLevel(logging.INFO)
def run_server():
# 初始化数据块:保持寄存器(3x)地址0-9,线圈(0x)地址0-9
# 保持寄存器初始值:0-4为100-104,5-9为0
hr_values = [100, 101, 102, 103, 104, 0, 0, 0, 0, 0]
# 线圈初始值:地址0-4为True(ON),5-9为False(OFF)
coil_values = [True, True, True, True, True, False, False, False, False, False]
# 创建数据块(地址从0开始)
hr_block = ModbusSequentialDataBlock(0, hr_values)
coil_block = ModbusSequentialDataBlock(0, coil_values)
# 创建从站上下文(从站ID=1,仅使用保持寄存器和线圈)
slave_context = ModbusSlaveContext(
di=None, # 不模拟离散输入
co=coil_block, # 线圈
hr=hr_block, # 保持寄存器
ir=None # 不模拟输入寄存器
)
# 服务器上下文(单从站模式)
server_context = ModbusServerContext(slaves=slave_context, single=True)
# 启动TCP服务器,监听所有网卡的5020端口
print("Modbus TCP Server 启动,监听端口 5020...")
print("保持寄存器(3x): 地址0-9, 初始值:", hr_values)
print("线圈(0x): 地址0-9, 初始值:", coil_values)
StartTcpServer(context=server_context, address=("0.0.0.0", 5020))
if __name__ == "__main__":
run_server()
运行与验证方法
1. 启动服务器:
python modbus_tcp_server.py
看到打印信息后,服务器即处于监听状态。
2. 使用 Modbus 客户端工具验证(推荐 Modbus Poll 或 QModMaster):
- 连接设置:TCP/IP,IP 填
127.0.0.1,端口5020,从站 ID 设为1(或 255,取决于工具,通常 1 即可)。 - 读保持寄存器:功能码 03,起始地址 0,数量 10。预期读到值:100, 101, 102, 103, 104, 0, 0, 0, 0, 0。
- 写保持寄存器:功能码 06(写单个),地址 5,值设为 200。随后再次读取地址 5,应读到 200。
- 读线圈:功能码 01,起始地址 0,数量 10。预期读到:地址0-4为 ON,地址5-9为 OFF。
- 写线圈:功能码 05(写单个),地址 5,值设为 ON。随后读取线圈地址 5,应变为 ON。
3. 使用 Python 脚本验证(无需第三方 GUI 工具):
在另一个终端中运行以下测试脚本(需安装 pymodbus):
# test_client.py
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient('127.0.0.1', port=5020)
client.connect()
# 读保持寄存器
rr = client.read_holding_registers(0, 5, slave=1)
print("读取保持寄存器0-4:", rr.registers) # 预期 [100, 101, 102, 103, 104]
# 写保持寄存器地址5
client.write_register(5, 200, slave=1)
rr = client.read_holding_registers(5, 1, slave=1)
print("写入后读取地址5:", rr.registers) # 预期 [200]
# 读线圈
cr = client.read_coils(0, 5, slave=1)
print("读取线圈0-4:", cr.bits) # 预期 [True, True, True, True, True]
# 写线圈地址5
client.write_coil(5, True, slave=1)
cr = client.read_coils(5, 1, slave=1)
print("写入后读取线圈5:", cr.bits) # 预期 [True]
client.close()
运行测试脚本:
python test_client.py
若输出与注释中的预期值一致,则说明服务器读写功能正常,案例验证通过。
常见问题
- 端口被占用:若 5020 被占用,可修改代码中的端口号,并同步修改客户端连接端口。
- 连接失败:检查防火墙是否放行 TCP 5020 端口;若跨设备测试,确保 IP 可达。
- 从站 ID 不匹配:本服务器使用
single=True,从站 ID 在 0-255 范围内均可访问(pyModbus 3.x 行为)。若客户端指定从站 ID 为 0 或 255 无法访问,可尝试设为 1。