简体   繁体   English

如何使用pymodbus编写用于外部Modbus客户端的INPUT寄存器,它将读取它们

[英]How to write INPUT REGISTERS using pymodbus for external Modbus client which will read them

I have been tasked with implementing a pymodbus-based Modbus server. 我的任务是实现基于pymodbus的Modbus服务器。 The server will run on a Linux machine like a Raspberry Pi or Up2 controller. 该服务器将在Raspberry Pi或Up2控制器等Linux机器上运行。 It is expected to interface with a Modbus client which I have no control over. 预计将与我无法控制的Modbus客户端对接。 That external Modbus client is expecting to be able to read INPUT REGISTERS as well as holding registers served by my Modbus server. 该外部Modbus客户端期望能够读取输入寄存器以及保存由我的Modbus服务器提供的寄存器。

I can set the values of the HOLDING registers that will be read by the external client. 我可以设置将由外部客户端读取的HOLDING寄存器的值。 I have been unable to set the values of the INPUT registers that the external client will read. 我无法设置外部客户端将读取的INPUT寄存器的值。 How does one do that? 怎么做到的?

I saw this post which asked a similar question but the question doesn't seem to ever have been answered: 我看到这篇文章问了类似的问题,但似乎从未有人回答过这个问题:

How to write to PLC input registers using pymodbus 如何使用pymodbus写入PLC输入寄存器

Thanks in advance for any help! 在此先感谢您的帮助!

As I said, I am not familiar with python or pymodbus, but take a look at this example which is something like what I expected would exist: https://pymodbus.readthedocs.io/en/latest/source/example/updating_server.html 就像我说的那样,我对python或pymodbus不熟悉,但是请看一下这个示例,它与我预期的存在类似: https ://pymodbus.readthedocs.io/en/latest/source/example/updating_server 。 HTML

Four 100 "register" arrays are created as the data store. 创建四个100个“寄存器”阵列作为数据存储。 I assume di=digital inputs, co=coils, hr=holding registers, ir=input registers 我假设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)

These values are then updated in "updating_writer(a)" which is called by the background thread. 然后在后台线程调用的“ updating_writer(a)”中更新这些值。 It looks to me like it just adds 1 to each value every time it is called. 在我看来,它每次调用时都会对每个值加1。 In a real world PLC, this function would probably read things like sensors, settings, and other operational/state/configuration data. 在实际的PLC中,此功能可能会读取诸如传感器,设置以及其他操作/状态/配置数据之类的信息。

Thanks to Marker and to all the examples online. 感谢Marker和所有在线示例。 I finally got this working as I wanted. 我终于按需完成了这项工作。 Hope this helps someone else. 希望这对其他人有帮助。

There were several gotchas I ran into: 我遇到了几个陷阱:

  1. I tried following examples I found online all of which used pymodbus.server.async instead of pymodbus.server.sync. 我尝试了以下示例,这些示例在网上找到,所有示例都使用pymodbus.server.async而不是pymodbus.server.sync。 I found that I could not import pymodbus.server.async because "async" is a reserved word in Python3.7! 我发现我无法导入pymodbus.server.async,因为“异步”是Python3.7中的保留字! (not in older versions of Python). (不适用于旧版本的Python)。 Either way I wanted to use pymodbus.server.sync because I wanted to avoid importing twisted if at all possible. 无论哪种方式,我都想使用pymodbus.server.sync,因为我想尽可能避免导入twisted。 This server will have 1-3 clients connecting to it at the most. 该服务器最多将有1-3个客户端与其连接。
  2. All the examples showing an updating writer used "LoopingCall" from Twisted. 所有显示更新编写器的示例都使用了Twisted的“ LoopingCall”。 I have no idea what Twisted is and didn't want to use it unless I had to. 我不知道Twisted是什么,除非我必须,否则不想使用它。 I was familiar with multiprocessing and threading. 我熟悉多处理和线程。 I was already launching the ModbusTcpServer in a process and was trying to create managed object(s) around the store / context so I could have a different Process doing the updating. 我已经在一个进程中启动了ModbusTcpServer,并试图在商店/上下文周围创建托管对象,因此我可以让另一个进程来进行更新。 But that didn't work: I'm guessing StartTcpServer doesn't like receiving managed objects(?) and I didn't want to delve into that function. 但这没有用:我猜想StartTcpServer不喜欢接收托管对象(?),并且我不想深入研究该函数。
  3. One of the examples commented that a Python thread could be used, and that solved it. 其中一个示例评论说可以使用Python线程,从而解决了该问题。 I still have the ModbusTcpServer launched in a Process but right before I call "StartTcpServer" I kickoff a THREAD rather than a PROCESS with the updating writer. 我仍然在进程中启动了ModbusTcpServer,但是在我调用“ StartTcpServer”之前,我用更新编写器启动了一个线程而不是一个过程。 Then I didn't need to put the store / context in managed object(s) since the Thread can see the same dataspace as the Process that kicked it off. 然后,我不需要将存储/上下文放在托管对象中,因为线程可以看到与启动它的进程相同的数据空间。 I just needed ANOTHER managed object to send messages into this Thread the way I was already used to doing with a Process. 我只需要一个ANANT托管对象就可以像以前使用Process一样将消息发送到该线程中。

Sooo... SOOO ...

First I had to do this: from threading import Thread 首先,我必须这样做: from threading import Thread

Then I kicked the following off in a Process as I'd done before, but RIGHT BEFORE calling StartTcpServer I kicked off the updating_writer Thread (all start_addr, init_val and num_addrs variables are set earlier). 然后,我像以前一样在进程中开始以下操作,但是在调用StartTcpServer之前,我正确地启动了update_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))

In the While loop of updating_writer I have code that polls the managed_obj to receive messages. 在update_writer的While循环中,我有代码轮询managed_obj以接收消息。 In adding the key bits of code in that loop are: 在该循环中添加代码的关键位是:

mb_context[0].setValues(4, addr_to_write, regs_to_write)

...where 4 is the write function, addr_to_write is the register address at which to start writing and regs_to_write is a list of register values...AND... ...其中4是写入功能,addr_to_write是开始写入的寄存器地址,regs_to_write是寄存器值的列表... AND ...

regs_to_read = mb_context[0].getValues(3, addr_to_read, num_regs_to_read)

...where 3 is the read function, addr_to_read is the register address at which to start reading. ...其中3是读取功能,addr_to_read是开始读取的寄存器地址。 regs_to_read will be a list of length num_regs_to_read. regs_to_read将是长度为num_regs_to_read的列表。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM