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

如何在Python代码中移除pymodbus的LoopingCall并自主调用函数

解决Modbus服务器自主控制寄存器更新的问题

我来帮你搞定这个问题!你的核心痛点是固定间隔的LoopingCall无法适配串口设备随机延迟的异步数据读取,想要自主控制寄存器更新时机,下面一步步给你解决方案:

1. 先解决直接调用updating_writer的参数问题

你之前直接调用updating_writer()报错,是因为原函数需要接收包含context的元组参数。我们可以先把函数参数改得更直观,避免元组传参的迷惑性:

def update_modbus_registers(context, start_addr, values):
    """单独封装寄存器更新逻辑,更灵活"""
    log.debug(f"更新起始地址 {start_addr} 的寄存器")
    register_type = 0x03  # 保持寄存器
    slave_id = 0x01
    context[slave_id].setValues(register_type, start_addr, values)
    log.debug(f"新值: {str(values)}")

def updating_writer(context):
    """原逻辑简化,直接传入context"""
    all_values = data_decoder1() + data_decoder2() + data_decoder3() + data_decoder4() + data_decoder5() + data_decoder6()
    update_modbus_registers(context, 0x01, all_values)

现在你如果要手动调用一次,直接写updating_writer(context)就可以了,但这只能执行一次,我们需要的是持续响应串口数据的异步更新,所以还要往下看。

2. 移除LoopingCall,用多线程适配异步串口读取

因为6台设备的串口数据是随机延迟返回的,固定定时循环完全不匹配。我们可以给每台设备单独开一个线程,让它持续读取对应串口的数据,一旦拿到有效数据就立即更新对应的寄存器段,不需要等其他设备:

第一步:编写设备读取线程函数

import threading
import time

def device_read_loop(context, decoder_func, start_addr):
    """单个设备的持续读取+寄存器更新循环"""
    while True:
        try:
            # 读取并解析当前设备的数据
            device_values = decoder_func()
            if device_values != [0]*33:  # 仅当读取到有效非零数据时更新
                update_modbus_registers(context, start_addr, device_values)
            # 加短延迟避免CPU空转,可根据设备响应频率调整
            time.sleep(0.1)
        except Exception as e:
            log.error(f"读取设备出错: {str(e)}")
            time.sleep(1)  # 出错后等待1秒再重试

第二步:修改服务器启动逻辑,替换LoopingCall为多线程

def run_updating_server():
    store = ModbusSlaveContext(
        di = ModbusSequentialDataBlock(0, [0]*1),
        co = ModbusSequentialDataBlock(0, [0]*1),
        hr = ModbusSequentialDataBlock(0, [0]*198),
        ir = ModbusSequentialDataBlock(0, [0]*1))
    context = ModbusServerContext(slaves=store, single=True)
    identity = ModbusDeviceIdentification()
    identity.VendorName = 'pymodbus'
    identity.ProductCode = 'PM'
    identity.ProductName = 'pymodbus Server'
    identity.ModelName = 'pymodbus Server'
    identity.MajorMinorRevision = '1.0'

    # 启动6台设备的读取线程,每台对应独立的寄存器段(每台33个寄存器)
    # 注意:确保每个data_decoder使用的是独立的串口对象(ser_JB1~ser_JB6)
    threading.Thread(target=device_read_loop, args=(context, data_decoder1, 0), daemon=True).start()
    threading.Thread(target=device_read_loop, args=(context, data_decoder2, 33), daemon=True).start()
    threading.Thread(target=device_read_loop, args=(context, data_decoder3, 66), daemon=True).start()
    threading.Thread(target=device_read_loop, args=(context, data_decoder4, 99), daemon=True).start()
    threading.Thread(target=device_read_loop, args=(context, data_decoder5, 132), daemon=True).start()
    threading.Thread(target=device_read_loop, args=(context, data_decoder6, 165), daemon=True).start()

    # 启动Modbus TCP服务器
    StartTcpServer(context, identity=identity, address=("10.10.10.253", 502))

run_updating_server()

3. 关键注意事项

  • 串口线程安全:如果你的data_decoder函数使用的是独立的串口对象(比如ser_JB1到ser_JB6分别对应6台设备),那没问题;如果是共享同一个串口,一定要加threading.Lock()来保护串口读写操作,避免多线程冲突。
  • 数据有效性判断:我在示例里加了device_values != [0]*33的判断,你可以根据实际串口数据的规则调整,确保只有真正读取到有效数据时才更新寄存器。
  • 延迟调整:线程里的time.sleep(0.1)可以根据设备的响应频率微调,平衡CPU占用和响应速度。

这样修改后,每台设备的数据一读取完成就会立即更新对应的Modbus寄存器,完全适配你串口设备随机延迟的场景,彻底摆脱LoopingCall的固定定时限制。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:36:52