Как-то раз в карибском море… В нашем чате, снова прозвучала тема про управление ПЛК по протоколу ModbusTCP
Главная сложность: «Как организовать управление из нескольких источников?». Если развернуть этот вопрос, то имеется следующая проблема — есть переменная в технологическом процессе, которая является уставкой. Как организовать обмен данными и логику ППО ПЛК таким образом, чтобы мы могли контролировать источник данных для этой переменной, работая только про протоколу ModbusTCP.
Если рассматривать организованную систему управления, то должна выйти следующая схема

При рассмотрении данной схемы видно, что управление алгоритмом осуществляется с двух мест — это панель ЧМИ, и также с АРМ операторов или других людей, отвечающих за производство, но все эти пользователи будут скрыты для среднего уровня сервером, который и будет осуществлять опрос оборудования среднего уровня(можно назвать эти режимы как дистанционный-местный и дистанционный-удаленный, есть еще местный режим, где управление осуществляет с пультов управления).
Логика ППО для обработки данных будет иметь следующий вид:

Данные из разных источников попадают в соответствующие регистры в карте Modbus, далее блоком «Выбор уставки от заданного источника» в тех процесс попадает то значение, которое является приоритетным.
На схеме видно, что приоритет данных задается с панели оператора, которая находиться ближе всего к ПЛК, а не с АРМ. Все это сделано для того, чтобы случайно, не были отправлены какие-то команды с удаленного источника.
При таком построении мы можем максимально масштабировать количество АРМов, так как логику взаимодействия берет на себя сервер и по факту мы всегда имеем два источника команд, но что делать, если у нас нет центрального сервера, а множество локальных решений?
Представим такую ситуацию, у нас на производстве есть разные HMI и АРМ, которые заведены в одну сеть и они обмениваются данными и пытаются управлять различными ПЛК в сети.

Схема выше является вымышленным примером того, как может быть организовано взаимодействие верхнего и среднего уровней
Если вспомнить, что программа на ПЛК должна обладать такой вещью как детерминированность, что предполагает получение однозначного результата вычислительного процecca при заданных исходных данных, то я бы предложил следующее решение.

На каждый новый источник данных требуется заводить новый регистр и его обрабатывать. Минусы такого решения в том, что при добавлении нового источника данных требуется изменять ППО ПЛК, что приводит к загрузке в стопе, а также увеличению занимаемой памяти, плюс еще придется плодить сущности контроллеров, которые бы это все обрабатывали.
Другое решение, которое было озвучено в чате, и которое мне очень понравилось — это передача клиентом как команды, так и своего идентификатора, на основании которого и будет приниматься решение системы.

При таком подходе мы развязываем все решение на трех регистрах, что позволяет нам подключать 2^16 клиентов, не надо постоянно менять и прогружать ППО.
В данной статье мы рассмотрим возможность создания такого контролера для работы с несколькими панелями, которые будут являться источниками команд. Здесь не будет затрагиваться тема SCADA систем, так как является большой темой для проработки, но если хотите присоединиться, то милости прошу.
Обмен данным HMI и ПЛК
Для начала стоит посмотреть как обмен данными происходит внутри сети. Для теста возьму Weintek панель и воспользуюсь ее симуляцией. Весь сетевой трафик буду просматривать через Wireshark.
Первым делом создадим необходимые регистры в ПЛК) и добавим соответствующие им теги на панель.


При изменении одного из значений панель отправляет соответствующую команду Modbus на ПЛК.

Отправляются только те тэги, которые подверглись изменению, так как с 80% вероятностью я уверен, что на элементе стоит HTMLный onChange.
Значит надо как-то отправлять данные пачкой, чтобы при изменении одного значения к нему в пару вставал идентификатор устройства.
Одним из таких способов являются — триггеры, или действия. Логика работы триггера будет заключаться в следующем, что при изменении параметра Value должен отправиться еще и идентификатор устройства, который должен быть равен 6.


При изменении значения происходит отправка уже двух значений, но они также передаются в разных посылках

Первая посылка отправляет измененное значение

А вторая посылка уже отправляет идентификатор клиента.

Если посмотреть на разницу по времени, то получается, что разрыв между двумя посылками составляет 200 миллисекунд, что для сетей передачи данных является огромным окном.
При таком разрыве передачи данных мы не можем гарантировать согласованность данных.
Согласованность данных — — согласованность данных друг с другом, целостность данных, а также внутренняя непротиворечивость.
Чтобы избежать подобного события надо переписать логику работы панели таким образом, чтобы за раз были отправленные все данные: регистр идентификатора и регистр значения.
Для начала идентификатор и значение переносятся на внутренние переменные


Далее меняем логику работы действия. Теперь при изменении внутренней переменной мы передаем данные, меняем, несколько тегов, которые отвечают за регистры Modbus

Ниже на скриншоте видно, что мы передаем с LW 0 два слова в регистры, по порядку, начиная с регистра ID

После изменения значения Value, данные будут передаваться сразу полным объемом. Что позволяет нам обеспечить целостность данных и обработать контроллером наши переменные как положено.

Разрыв данных в посылке
Может произойти такая ситуация, что данные при написании ППО были сгруппированы не очень аккуратно и на одном экране находятся переменные, которые связаны с регистрами не по порядку.
Что же произойдет тогда при отправке сообщений? Регистры, которые находятся в промежутке будут иметь актуальное значение или какие-нибудь нули?
Давайте смоделируем такую ситуацию. Представим, что на первом экране у нас находятся следующие данные для записи.

Где HR — это номер Holding Register и он совпадет с номером LW.
На втором экране есть данные, которые должны быть в общей посылке, если мы за раз решим передать все переменные.

Теперь при запуске панели я поменяю регистры на втором экране, а потом на первом и посмотрим, что у нас будет в посылке Modbus.
Данные для второго и третьего значения.


После изменения Value срабатывает триггер, который будет отправлять разом 40 регистров. В этот диапазон попадают как регистры с первого экрана так и со второго. Посмотри пакет в wireshark

В теле пакета указано, что передалась вся цепочка и были подхвачены значения регистров со второго экрана. Т.е. при передаче данных будут подтягиваться значения всех переменных, со всех экранов, которые попадают в данный диапазон.
Если кому-то интересно как 2 регистра превратились в 40, то я просто изменил количество слов в триггере.
Актуальные значения в программе Codesys такие же.

Ну и более крупный вариант в нашей структуре.

Разбор данных ModbusTCP
И небольшой бонус, как быстро разбирать данные Modbus сразу в структуру.
Для этого пригодиться такой тип данных как UNION
Первым делом я создал структуру, которая бы описывала все регистры, которые я должен получить.

Главное правило заключается в том, что размер структуры не должен превышать размер связанного с ней массива.
Теперь в типе UNION объявим две переменные. Первая будет массив WORD, который и будет связан с нашими регистрами, а тип второй переменной — структура, которая была объявлена выше.

Далее в глобальных данных объявляем переменную тип которой — объединение unionModbusInputController.

И при соотнесении входов/выходов для ModbusTCP Slave указываем массив вордов ModbusData

А в программе уже используем переменные из структуры в этом объединении.
Вывод
При такой настройки панели мы можем передавать огромной посылкой все необходимые данные вместе с идентификатором.
На что точно стоит обратить внимание — группировка данных. Для удобной работы стоит все же разбивать данные на какие-то логические группы, которыми могут быть как экраны или какие-то устройства, при таком варианте стоит везде добавлять поле идентификатора и отправлять данные группами, организуя контроллеры для каждой группы.
Группа в ТГ: «Я вам че — Автоматизатор?»
Наш Чат: https://t.me/wtfplc
