You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Python搭建Modbus RTU Slave?现有脚本超时问题排查

问题:搭建Modbus RTU Slave时QModMaster超时的排查与修复

问题描述

需要编写脚本让控制器充当Modbus RTU Slave,响应其他设备请求:控制器需将10个布尔值存储在地址1至10的离散输入中,仅支持通过功能码2读取。尝试用pymodbus实现,但搭建方式存疑,运行脚本时QModMaster出现超时错误,需排查问题所在。

原测试脚本:

from pymodbus.datastore import ModbusSequentialDataBlock
from serial import Serial
import time
import sys
import random

# Create a serial port
serial = Serial(port='COM1',
                baudrate=9600,
                bytesize=8,
                parity='N',
                stopbits=1,
                timeout=0.5)

# Create a Modbus server context
store = ModbusSlaveContext(
    di=ModbusSequentialDataBlock.create(),
    co=ModbusSequentialDataBlock.create(),
    hr=ModbusSequentialDataBlock.create(),
    ir=ModbusSequentialDataBlock.create(),
    zero_mode=True
)

# Create a loop to change the discrete input from adress 1 to 10 every 2 seconds
while True:
    try:
        # Change the discrete input randomly
        for i in range(1, 11):
            store.setValues(1, i, [random.randint(0, 1)])
        time.sleep(2)
    except KeyboardInterrupt:
        server.stop()
        sys.exit()

环境说明:控制器运行Windows系统,支持Python执行,配备RS-232(DB9)端口;测试用安装QModMaster的电脑,读取离散输入时触发超时。


原脚本的核心错误

脚本存在多个关键问题,导致Modbus Slave无法正常响应请求:

  • 未启动Modbus RTU Server:仅创建了串口和数据存储对象,但未将两者关联、启动Slave服务,串口从未处理任何Modbus请求,必然超时。
  • 缺失类导入:使用ModbusSlaveContext但未从pymodbus.datastore导入,运行时会直接抛出NameError中断脚本。
  • 数据块初始化无效:ModbusSequentialDataBlock.create()默认生成空数据块,未预分配地址1-10的离散输入空间,读取时会返回无效地址错误。
  • 数据更新参数错误:store.setValues(1, i, [...])的第一个参数是数据类型代码,离散输入(DI)对应代码为2,而非1(1是线圈CO的代码),原代码实际在修改线圈而非离散输入。
  • 未定义server对象:异常处理中的server.stop()会抛出NameError,因为server从未初始化。

修正后的脚本

from pymodbus.datastore import ModbusSequentialDataBlock, ModbusSlaveContext
from pymodbus.server import StartSerialServer
from serial import Serial
import time
import sys
import random

# 初始化串口(参数需与QModMaster完全匹配)
serial_port = Serial(
    port='COM1',
    baudrate=9600,
    bytesize=8,
    parity='N',
    stopbits=1,
    timeout=0.5
)

# 初始化离散输入数据块:起始地址1,共10个点位,初始值设为0
di_block = ModbusSequentialDataBlock(1, [0]*10)
# 创建Slave上下文,仅启用离散输入(DI),关闭其他不需要的存储区
store = ModbusSlaveContext(
    di=di_block,
    co=None,  # 禁用线圈
    hr=None,  # 禁用保持寄存器
    ir=None,  # 禁用输入寄存器
    zero_mode=True
)

def update_discrete_inputs():
    """后台更新离散输入值的线程函数"""
    while True:
        # 更新地址1-10的离散输入值
        for addr in range(1, 11):
            # 离散输入对应的数据类型代码是2
            store.setValues(2, addr, [random.randint(0, 1)])
        time.sleep(2)

if __name__ == "__main__":
    try:
        # 启动Modbus RTU Slave服务器,同时运行数据更新线程
        StartSerialServer(
            context=store,
            port=serial_port,
            timeout=1,
            custom_processes=[update_discrete_inputs]
        )
    except KeyboardInterrupt:
        serial_port.close()
        sys.exit()

关键修改说明

  • 补全必要导入:添加ModbusSlaveContext和StartSerialServer的导入,确保类可用。
  • 规范数据块初始化:明确指定离散输入的起始地址和点位数量,保证地址1-10有效。
  • 禁用无用存储区:将不需要的线圈、寄存器设为None,减少资源占用。
  • 启动Modbus Server:通过StartSerialServer关联串口与数据存储,正式启动Slave服务处理请求。
  • 线程化数据更新:将数据更新逻辑放在独立线程中,避免阻塞主线程的Modbus请求处理(原脚本的while循环会完全占用主线程,无法响应串口数据)。
  • 修正数据类型代码:用2作为setValues的第一个参数,确保修改的是离散输入。
  • 完善异常处理:确保串口正常关闭,避免资源泄漏。

额外排查点

  • 确认COM端口号正确:检查Windows设备管理器,确认控制器RS-232端口的实际COM编号。
  • 串口接线正确:使用RS-232交叉线(TX-RX交叉连接),确保控制器与测试电脑的串口通信通路正常。
  • QModMaster参数匹配:确保软件中的波特率、奇偶校验、停止位、Slave地址(默认1)与脚本完全一致,读取时选择功能码2,起始地址1,数量10。

内容的提问来源于stack exchange,提问作者Sara Delahaie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 00:47:47