Как написать ВХОДНЫЕ РЕГИСТРЫ с использованием pymodbus для внешнего клиента Modbus, который будет их читать

Мне было поручено реализовать сервер Modbus на основе pymodbus. Сервер будет работать на компьютере с Linux, например, на контроллере Raspberry Pi или Up2. Ожидается, что он будет взаимодействовать с клиентом Modbus, которым я не могу управлять. Ожидается, что этот внешний клиент Modbus сможет читать РЕГИСТРЫ ВВОДА, а также регистры хранения, обслуживаемые моим сервером Modbus.

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

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

Как писать во входные регистры ПЛК с помощью pymodbus

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


person Akash Sharma    schedule 05.01.2019    source источник
comment
Как вы устанавливаете значения регистров HOLDING на вашем сервере?   -  person Marker    schedule 07.01.2019
comment
У меня есть другой клиент MB, вызывающий метод write_register в pymodbus.client.sync. Кажется, что регистры временного хранения все в порядке.   -  person Akash Sharma    schedule 07.01.2019
comment
На самом деле я думал, что мой внутренний клиент сможет выполнять всю работу сервера. ВНЕШНЕМУ клиенту знать не нужно. К сожалению, внешний клиент хочет прочитать оба регистра INPUT вместе с регистрами HOLDING. Я знаю, как установить регистры HOLDING, но не INPUT.   -  person Akash Sharma    schedule 07.01.2019
comment
Я так и подумал. Регистры ввода Modbus не предназначены для записи (извне). Они предназначены для использования на устройстве для значений только для чтения. Например, значение датчика температуры, аналого-цифровое преобразование, время работы устройства в секундах и т. Д.   -  person Marker    schedule 07.01.2019
comment
Я видел это из всех комментариев в Интернете, но есть НЕКОТОРЫЙ код, устанавливающий эти значения входного регистра. Например, на ПЛК должен быть НЕКОТОРЫЙ код, устанавливающий эти значения регистров. Если я хочу реализовать устройство, подобное ПЛК, с использованием Python, как мне передать эти значения входного регистра для некоторого внешнего клиента, который хочет обращаться с моим устройством, как если бы это был ПЛК?   -  person Akash Sharma    schedule 08.01.2019
comment
Хорошо, извините, я не могу с этим помочь, я много использую Modbus, но я не очень знаком с pymodbus. Я бы сказал, что должен быть способ сопоставления переменных и блоков памяти из вашего запущенного серверного приложения pymodbus во входные регистры, которые вы хотите опубликовать.   -  person Marker    schedule 08.01.2019
comment
Не беспокойся. Спасибо за попытку. Это кажется трудным.   -  person Akash Sharma    schedule 08.01.2019


Ответы (2)


Как я уже сказал, я не знаком с 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 к каждому значению при каждом вызове. В реальном ПЛК эта функция, вероятно, будет считывать такие вещи, как датчики, настройки и другие данные о работе / состоянии / конфигурации.

person Marker    schedule 08.01.2019
comment
Приносим извинения за задержку с возвращением к этому. Я использовал предложенный вами пример, но я пытался избежать использования скрученного (я использую pymodbus.server.sync вместо pymodbus.server.async, поэтому скрученный не используется). Я изо всех сил пытался использовать один процесс для TcpServer, а другой процесс для создания модуля записи обновлений, и он не работал. Я попытался поместить хранилище и / или контекст в управляемый объект, чтобы поделиться им с обновляющим писателем, но ничего не сработало. Наконец, я попытался использовать поток, порожденный процессом TcpServer, и это сработало! Еще раз спасибо! - person Akash Sharma; 10.01.2019

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

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

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

Ооочень ...

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

Затем я запустил следующее в процессе, как делал раньше, но ПЕРЕД вызовом 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 - список значений регистров ... И ...

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.

person Akash Sharma    schedule 10.01.2019