Как написать INPUT REGISTERS, используя pymodbus для внешнего клиента Modbus, который будет их читать - PullRequest
0 голосов
/ 06 января 2019

Мне было поручено реализовать сервер Modbus на базе pymodbus. Сервер будет работать на Linux-машине, например, на Raspberry Pi или Up2. Ожидается, что он будет взаимодействовать с клиентом Modbus, который я не могу контролировать. Этот внешний клиент Modbus ожидает, что он сможет читать входящие регистры, а также хранить регистры, обслуживаемые моим сервером Modbus.

Я могу установить значения регистров HOLDING, которые будут считываться внешним клиентом. Мне не удалось установить значения регистров INPUT, которые внешний клиент будет читать. Как это сделать?

Я видел этот пост, в котором задавался похожий вопрос, но на этот вопрос, похоже, никогда не было ответа:

Как записать во входные регистры ПЛК, используя pymodbus

Заранее спасибо за любую помощь!

Ответы [ 2 ]

0 голосов
/ 10 января 2019

Спасибо Маркеру и всем примерам онлайн. Я, наконец, получил это работает, как я хотел. Надеюсь, это поможет кому-то еще.

Было несколько ошибок, с которыми я столкнулся:

  1. Я попробовал следующие примеры, которые я нашел в Интернете, и все они использовали pymodbus.server.async вместо pymodbus.server.sync. Я обнаружил, что не могу импортировать pymodbus.server.async, потому что «async» - зарезервированное слово в Python3.7! (не в более старых версиях Python). В любом случае я хотел использовать pymodbus.server.sync, потому что я хотел избежать импорта витой, если это вообще возможно. К этому серверу будет подключено не более 1-3 клиентов.
  2. Все примеры, показывающие, что писатель обновлений использовал "LoopingCall" из Twisted. Я понятия не имею, что такое Twisted, и я не хотел использовать его, если бы мне не пришлось. Я был знаком с многопроцессорностью и многопоточностью. Я уже запускал ModbusTcpServer в процессе и пытался создать управляемый объект (ы) вокруг хранилища / контекста, чтобы у меня был другой процесс, выполняющий обновление. Но это не сработало: я предполагаю, что StartTcpServer не нравится получать управляемые объекты (?), И я не хотел углубляться в эту функцию.
  3. В одном из примеров отмечалось, что можно использовать поток Python, и это решает эту проблему. У меня все еще есть ModbusTcpServer, запущенный в Process, но прямо перед вызовом «StartTcpServer» я запускаю THREAD, а не PROCESS с модулем обновления. Тогда мне не нужно было помещать хранилище / контекст в управляемый объект (ы), поскольку поток может видеть то же пространство данных, что и процесс, который его запустил. Мне просто нужен ДРУГОЙ управляемый объект для отправки сообщений в этот поток, как я уже привык делать с процессом.

Тааак ...

Сначала я должен был сделать это: from threading import Thread

Затем я запустил следующее в Process, как я делал раньше, но ПРЯМО ПЕРЕД вызовом 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))

В цикле while update_writer у меня есть код, который опрашивает 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.

0 голосов
/ 08 января 2019

Как я уже сказал, я не знаком с python или pymodbus, но взгляните на этот пример, который выглядит примерно так, как я ожидал: https://pymodbus.readthedocs.io/en/latest/source/example/updating_server.html

Четыре 100 «регистровых» массива создаются в качестве хранилища данных. Я предполагаю, что 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)

Эти значения затем обновляются в "update_writer (a)", который вызывается фоновым потоком. Мне кажется, он просто добавляет 1 к каждому значению при каждом вызове. В реальном ПЛК эта функция, вероятно, будет считывать такие данные, как датчики, настройки и другие данные о работе / состоянии / конфигурации.

...