如何在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
相关产品推荐
相关产品推荐

