如何使用 pymodbus 为将读取它们的外部 Modbus 客户端编写输入寄存器
How to write INPUT REGISTERS using pymodbus for external Modbus client which will read them
我的任务是实现一个基于 pymodbus 的 Modbus 服务器。服务器将 运行 在 Linux 机器上,如 Raspberry Pi 或 Up2 控制器。它应该与我无法控制的 Modbus 客户端接口。该外部 Modbus 客户端期望能够读取输入寄存器以及保存由我的 Modbus 服务器提供服务的寄存器。
我可以设置将由外部客户端读取的 HOLDING 寄存器的值。我无法设置外部客户端将读取的 INPUT 寄存器的值。如何做到这一点?
我看到这个 post 问了一个类似的问题,但这个问题似乎从来没有得到回答:
在此先感谢您的帮助!
正如我所说,我不熟悉 python 或 pymodbus,但看看这个例子,它就像我预期的那样:https://pymodbus.readthedocs.io/en/latest/source/example/updating_server.html
创建了四个 100 "register" 数组作为数据存储。我假设 di=数字输入,co=线圈,hr=保持寄存器,ir=输入寄存器
store = ModbusSlaveContext(
di=ModbusSequentialDataBlock(0, [17]*100),
co=ModbusSequentialDataBlock(0, [17]*100),
hr=ModbusSequentialDataBlock(0, [17]*100),
ir=ModbusSequentialDataBlock(0, [17]*100))
context = ModbusServerContext(slaves=store, single=True)
这些值随后在后台线程调用的 "updating_writer(a)" 中更新。在我看来,它只是在每次调用时为每个值加 1。在现实世界的 PLC 中,此功能可能会读取传感器、设置和其他 operational/state/configuration 数据。
感谢 Marker 和所有在线示例。我终于按照我的意愿让它工作了。希望这对其他人有帮助。
我 运行 遇到了几个陷阱:
- 我尝试了以下我在网上找到的示例,所有这些示例都使用 pymodbus.server.async 而不是 pymodbus.server.sync。我发现无法导入pymodbus.server.async,因为"async"是Python3.7中的保留字! (不在 Python 的旧版本中)。无论哪种方式,我都想使用 pymodbus.server.sync 因为我想尽可能避免导入扭曲。此服务器最多将有 1-3 个客户端连接到它。
- 所有显示更新编写器的示例都使用了来自 Twisted 的 "LoopingCall"。我不知道 Twisted 是什么,除非万不得已,否则我不想使用它。我熟悉多处理和线程。我已经在一个进程中启动了 ModbusTcpServer,并试图在商店/上下文周围创建托管对象,这样我就可以让一个不同的进程进行更新。但这没有用:我猜 StartTcpServer 不喜欢接收托管对象(?)而且我不想深入研究该功能。
- 其中一个示例评论说可以使用 Python 线程,这解决了它。我仍然在一个进程中启动了 ModbusTcpServer,但就在我调用 "StartTcpServer" 之前,我启动了一个 THREAD 而不是更新编写器的进程。然后我不需要将存储/上下文放在托管对象中,因为线程可以看到与启动它的进程相同的数据空间。我只需要另一个托管对象将消息发送到此线程,就像我已经习惯处理进程的方式一样。
太...
首先我必须这样做:
from threading import Thread
然后我像以前一样在进程中启动了以下进程,但是在调用 StartTcpServer 之前我启动了 updating_writer 线程(所有 start_addr、init_val 和num_addrs 变量设置较早。
discrete_inputs_obj = ModbusSequentialDataBlock(di_start_addr, [di_init_val]*di_num_addrs)
coils_obj = ModbusSequentialDataBlock(co_start_addr, [co_init_val]*co_num_addrs)
holding_regs_obj = ModbusSequentialDataBlock(hr_start_addr, [hr_init_val]*hr_num_addrs)
input_regs_obj = ModbusSequentialDataBlock(ir_start_addr, [ir_init_val]*ir_num_addrs)
mb_store = ModbusSlaveContext(di=discrete_inputs_obj, co=coils_obj, hr=holding_regs_obj, ir=input_regs_obj, zero_mode=True)
mb_context = ModbusServerContext(slaves=mb_store, single=True)
mb_store = ModbusSlaveContext(
di=ModbusSequentialDataBlock(di_start_addr, [di_init_val]*di_num_addrs),
co=ModbusSequentialDataBlock(co_start_addr, [co_init_val]*co_num_addrs),
hr=ModbusSequentialDataBlock(hr_start_addr, [hr_init_val]*hr_num_addrs),
ir=ModbusSequentialDataBlock(ir_start_addr, [ir_init_val]*ir_num_addrs))
mb_context = ModbusServerContext(slaves=mb_store, single=True)
updating_writer_cfg = {}
updating_writer_cfg["mb_context"] = mb_context
updating_writer_cfg["managed_obj"] = managed_obj #For being able to send messages to this Thread
updating_writer_thread = Thread(target = updating_writer, args = [updating_writer_cfg]) # We need this to be a thread in this process so that they can share the same datastore
updating_writer_thread.start()
StartTcpServer(mb_context, address=("", port))
在 updating_writer 的 While 循环中,我有轮询 managed_obj 以接收消息的代码。在该循环中添加代码的关键位是:
mb_context[0].setValues(4, addr_to_write, regs_to_write)
...其中 4 是写入函数,addr_to_write 是开始写入的寄存器地址,regs_to_write 是寄存器值列表...AND...
regs_to_read = mb_context[0].getValues(3, addr_to_read, num_regs_to_read)
...其中3为读取函数,addr_to_read为开始读取的寄存器地址。 regs_to_read 将是一个长度为 num_regs_to_read.
的列表
我的任务是实现一个基于 pymodbus 的 Modbus 服务器。服务器将 运行 在 Linux 机器上,如 Raspberry Pi 或 Up2 控制器。它应该与我无法控制的 Modbus 客户端接口。该外部 Modbus 客户端期望能够读取输入寄存器以及保存由我的 Modbus 服务器提供服务的寄存器。
我可以设置将由外部客户端读取的 HOLDING 寄存器的值。我无法设置外部客户端将读取的 INPUT 寄存器的值。如何做到这一点?
我看到这个 post 问了一个类似的问题,但这个问题似乎从来没有得到回答:
在此先感谢您的帮助!
正如我所说,我不熟悉 python 或 pymodbus,但看看这个例子,它就像我预期的那样:https://pymodbus.readthedocs.io/en/latest/source/example/updating_server.html
创建了四个 100 "register" 数组作为数据存储。我假设 di=数字输入,co=线圈,hr=保持寄存器,ir=输入寄存器
store = ModbusSlaveContext(
di=ModbusSequentialDataBlock(0, [17]*100),
co=ModbusSequentialDataBlock(0, [17]*100),
hr=ModbusSequentialDataBlock(0, [17]*100),
ir=ModbusSequentialDataBlock(0, [17]*100))
context = ModbusServerContext(slaves=store, single=True)
这些值随后在后台线程调用的 "updating_writer(a)" 中更新。在我看来,它只是在每次调用时为每个值加 1。在现实世界的 PLC 中,此功能可能会读取传感器、设置和其他 operational/state/configuration 数据。
感谢 Marker 和所有在线示例。我终于按照我的意愿让它工作了。希望这对其他人有帮助。
我 运行 遇到了几个陷阱:
- 我尝试了以下我在网上找到的示例,所有这些示例都使用 pymodbus.server.async 而不是 pymodbus.server.sync。我发现无法导入pymodbus.server.async,因为"async"是Python3.7中的保留字! (不在 Python 的旧版本中)。无论哪种方式,我都想使用 pymodbus.server.sync 因为我想尽可能避免导入扭曲。此服务器最多将有 1-3 个客户端连接到它。
- 所有显示更新编写器的示例都使用了来自 Twisted 的 "LoopingCall"。我不知道 Twisted 是什么,除非万不得已,否则我不想使用它。我熟悉多处理和线程。我已经在一个进程中启动了 ModbusTcpServer,并试图在商店/上下文周围创建托管对象,这样我就可以让一个不同的进程进行更新。但这没有用:我猜 StartTcpServer 不喜欢接收托管对象(?)而且我不想深入研究该功能。
- 其中一个示例评论说可以使用 Python 线程,这解决了它。我仍然在一个进程中启动了 ModbusTcpServer,但就在我调用 "StartTcpServer" 之前,我启动了一个 THREAD 而不是更新编写器的进程。然后我不需要将存储/上下文放在托管对象中,因为线程可以看到与启动它的进程相同的数据空间。我只需要另一个托管对象将消息发送到此线程,就像我已经习惯处理进程的方式一样。
太...
首先我必须这样做:
from threading import Thread
然后我像以前一样在进程中启动了以下进程,但是在调用 StartTcpServer 之前我启动了 updating_writer 线程(所有 start_addr、init_val 和num_addrs 变量设置较早。
discrete_inputs_obj = ModbusSequentialDataBlock(di_start_addr, [di_init_val]*di_num_addrs)
coils_obj = ModbusSequentialDataBlock(co_start_addr, [co_init_val]*co_num_addrs)
holding_regs_obj = ModbusSequentialDataBlock(hr_start_addr, [hr_init_val]*hr_num_addrs)
input_regs_obj = ModbusSequentialDataBlock(ir_start_addr, [ir_init_val]*ir_num_addrs)
mb_store = ModbusSlaveContext(di=discrete_inputs_obj, co=coils_obj, hr=holding_regs_obj, ir=input_regs_obj, zero_mode=True)
mb_context = ModbusServerContext(slaves=mb_store, single=True)
mb_store = ModbusSlaveContext(
di=ModbusSequentialDataBlock(di_start_addr, [di_init_val]*di_num_addrs),
co=ModbusSequentialDataBlock(co_start_addr, [co_init_val]*co_num_addrs),
hr=ModbusSequentialDataBlock(hr_start_addr, [hr_init_val]*hr_num_addrs),
ir=ModbusSequentialDataBlock(ir_start_addr, [ir_init_val]*ir_num_addrs))
mb_context = ModbusServerContext(slaves=mb_store, single=True)
updating_writer_cfg = {}
updating_writer_cfg["mb_context"] = mb_context
updating_writer_cfg["managed_obj"] = managed_obj #For being able to send messages to this Thread
updating_writer_thread = Thread(target = updating_writer, args = [updating_writer_cfg]) # We need this to be a thread in this process so that they can share the same datastore
updating_writer_thread.start()
StartTcpServer(mb_context, address=("", port))
在 updating_writer 的 While 循环中,我有轮询 managed_obj 以接收消息的代码。在该循环中添加代码的关键位是:
mb_context[0].setValues(4, addr_to_write, regs_to_write)
...其中 4 是写入函数,addr_to_write 是开始写入的寄存器地址,regs_to_write 是寄存器值列表...AND...
regs_to_read = mb_context[0].getValues(3, addr_to_read, num_regs_to_read)
...其中3为读取函数,addr_to_read为开始读取的寄存器地址。 regs_to_read 将是一个长度为 num_regs_to_read.
的列表