Обнаружил в базе знаний Siemens (SIOS) интересный пример использования контроллеров линейки S7-1200 и S7-1500 в качестве клиента протокола MQTT
По ссылке можно найти саму библиотеку с функциональным блоком MQTT_Client (с открытыми исходниками, кстати) и пояснительную записку к примеру. Документация сама по себе вполне исчерпывающая, знающие английский язык могут смело идти по ссылке и не читать эту заметку, подарив тем самым себе несколько увлекательных часов инженерных изысканий. А для остальных, менее опытных и/или более ленивых, я кратко расскажу основное и продемонстрирую свой пример работы клиента, творчески переработанный под более-менее реальное применение.
Кратко о терминах.
MQTT — message queuing telemetry transport. Протокол телеметрии для передачи сообщений. Затрудняюсь перевести название корректно на русский.
Message — сообщение. Непосредственно, сами передаваемые данные. Сообщение состоит из нескольких частей:
Topic, тема сообщения. Символьная строка, размер которой в исходном примере ограничен 200 символами. Клиенты для получения сообщений должны предварительно подписаться на конкретную тему, т.е. topic
QoS, quality of service. Дополнительный признак, указывающий — ждать ли подтверждения получения сообщения или нет
Message text, текст сообщения. Текстовая строка из 500 символов
Протокол построен на модели подписчик/издатель, общая логика которого слегка отличается от обычной клиент-серверной модели, привычной в промышленных протоколах.
При клиент-серверном подходе один или несколько участников исполняет роль сервера, т.е. узла в сети, который отвечает на запросы клиента. Сервер сам по себе не инициирует связь и не проявляет активность, он лишь отдает запрошенные данные и принимает уставки и команды. При этом адресация данных, тип данных и прочее закладываюся и структурируются изначально на этапе разработки серверной части. Клиент тоже должен знать эту «карту адресов Modbus». Изменение набора данных требует перенастройки и клиента, и сервера.
В модели подписчик-издатель, применяемой в протоколе MQTT, совершенно необязательно заранее формировать, конфигурировать или программировать набор данных (топики). Вообще никак данные конфигурировать в серверной части не требуется. Топики создаются автоматически от издателя. Подписчики, в свою очередь, подписываются на топики, причем можно подписаться даже на тот топик, в который не написал ни разу ни один издатель. Как только в этот топик будет отправлено хотя бы одно сообщение, все подписчики его получат.
Итак, роли в протоколе MQTT.
Издатель — он же publisher. Узел, который отправляет сообщения (текстовую информацию) на определенную тему (topic).
Подписчик — он же subscriber. Узел, который подписывается на определенные темы. Новые сообщения в заявленных темах подписчик получает автоматически.
Роли издателя и подписчика могут совмещаться на одном и том же узле сети. Это — роли клиента.
Роль сервера в протоколе MQTT выполняет узел, который называется брокер. Именно брокер запоминает, какие клиенты на какие темы подписались. Именно брокер принимает сообщения от издателей на определенную тему и автоматически раздает их соответствующим подписчикам.
К брокеру может быть подключено несколько и подписчиков, и издателей. Максимальное количество мне неизвестно. Предположу, что все ограничено лишь мощностью железа, на которой работает брокер и настройками стека TCP/IP операционной системы.
В первоисточнике (см. ссылке в начале) идет архив с библиотекой LMQTT_Client. Архив необходимо распаковать, а библиотеку — подключить к уже созданному проекту Step 7. Подключение библиотеки выполняется через пункт меню Options > Global Libraries > Open library. В результате Вы увидите следующее:
Библиотека подключена к проекту
Библиотека содержит две версии функционального блока клиента протокола MQTT — для контроллеров S7-1200 и S7-1500. В моем примере будет использоваться младший ПЛК, S7-1214. Реализации отличаются тем, что старшие S7-1500 позволяют адресовать брокер по доменному имени, а S7-1200 — только по ip-адресу. Необходимо перетащить блок LMQTT_Client из библитеки во вкладку Program Files контроллера. Типы данных скопируются в проект автоматически. Далее я отхожу от примера и осуществляю вызов ФБ MQTT_Client из своего собственного функционального блока под названием MQTTExchange:
В принципе, если выполнить все настройки правильно и играть запросами через поля экземпляра функционального блока, то можно проверить работоспособность прямо сейчас. Но вначале пройдемся по основным входным и выходным пинам этого ФБ.
enable — при положительном фронте этой дискретной переменной устанавливается соединение с брокером MQTT, при отрицательном фронте соединение разрывается. Т.е. для работы необходимо держать этот вход в состоянии TRUE
publishData — структура для отправки (публикации) сообщения. Состоит из бита запроса на публикацию (для отправки сообщения бит необходимо взвести в истину и снять в ложь после появления флага done или error), топика и текста сообщения, а так же признака качестве QoS
subscribeToTopic — структура, которая содержит флаг запроса на подписку, флаг запроса на отписку (да, можно в любой момент отписаться от топика), непосредственно имя топика и признак качества
На начальном этапе нам важны следующие две структуры, которые необходимо для установления связи с брокером: это tcpConnParam и mqttParam. Эти структуры я заполняю единожды в OB100 при запуске контроллера. Необходимый минимум для работы протокола описывается ниже.
Аппаратный идентификатор интерфейса контроллера
Идентификатор интерфейса ( «сетевой карты» ) ПЛК. В моем случае у меня всего один интерфейс. Его ID я уже помню наизусть, поэтому пишу 64. Подсмотреть Hardware ID можно в аппаратной конфигурации ПЛК.
Следующее — идентификатор соединения. Именно логического соединения по протоколу TCP/IP, connection ID. Должно иметь значение от 1 до 4096, назначается программистом, у каждого логического соединения должен быть свой уникальный «айди», иначе связь не будет функционировать. В моем случае у меня присутствует одно-единственное соединения, и я смело назначаю ему «1»
Следующее назначение — IP-адрес хоста, на котором функционирует брокер.
В данном примере брокер работает на моем домашнем «рабочем» компьютере с публичным статическим ip-адресом. Из соображений информационной безопасности два байта ip-адреса стерты. В качестве брокера выступает mosquitto под Windows. Разные способы установки брокера хорошо описаны по ссылке.
Там же можно скачать уже настроенный в базе брокер, его я и использую.
Никаких особенных настроек не применяется. Сертификаты шифрования не используются (об этом чуть позже). На удаленном компьютере просто запущена программа mosquitto.exe, а на удаленном маршрутизаторе сделан проброс порта 1883 (порт для стандартных коммуникаций MQTT) в локальную сеть.
Я умышленно использую брокер, недоступный локально. Одно дело поднять все в рамках локальной сети в горячо любимом диапазоне адресов 192.168.0.х или 192.168.1.х, а совсем другое — убедиться в возможности работы системы, как ей и положено, по удаленным каналам связи. Ведь сам контроллер находится сейчас в лаборатории, он подключен к местному маршрутизатору от провайдера, и у контроллера есть только локальный ip-адрес, а не публичный и, тем более, не публичный статический ip.
Кстати, помним, что для организации «выхода в интернет» контроллеру в данном случае необходимо явно указывать «шлюз» в настройках его ip-адреса.
Обязательно надо прописать router address, иначе ПЛК не сможет подключиться к внешнем ресурсам глобальной сети
Следующая настройка — порт, по которому удаленный брокер слушает соединения. По непонятной причине в примере этот порт указан, как 1884, в то время как стандартным портом для нешифрованных коммуникаций является 1883. Задаю его явно:
Последняя настройка — символьное имя клиента. Должно быть уникально в системе. В качестве имени у меня задано S7-1214.
Гораздо разумнее было бы сделать имя клиента уникальным автоматически. Например, сделать его составным из слова «SIMATIC» и уникального серийного номера центрального процессора (прочитав его соответствующей системной функцией и переведя в строковое представление). С остальными настройками рекомендую ознакомиться в документации. Остановлюсь на двух из них, весьма интересных.
Первое. Last will. Буквально на русском языке — «завещание» (сетевые граждане такие юмористы!). Если выполнить эту настройку, то клиент при подключении к брокеру передает и ее. В случае «отвала связи» клиента, брокер автоматически разошлет это завещание всем участникам обмена. «Завещание» является таким же сообщением, у него так же задается топик и текст.
Второе. Возможность шифрования трафика. Для общего тестирования можно обойтись и без него. В продакшене — ни в коем случае.
Выставив поле activateSecureConn в настройках необходимо провести еще ряд манипуляций — активировать глобальные настройки безопасности проекта, импортировать сертификат брокера, создать сертификат контроллера и так далее. Вопросы зашифрованного соединения я уже поднимал в заметке про OPC UA коммуникации. В целом же действия тут больше напоминают настройки безопасного соединения для Open User Communications (SecOUC). В настоящем примере вопросами безопасности передаваемых данных я пренебрегаю. Подробности настройки описаны все в той же документации.
Закончив настройки, необходимо убедиться, что все работает, как надо. Для начала, конечно же, просто подпимем соединение с удаленным брокером. Для этого достаточно подать истину на вход enable функционального блока LMQTT_Client:
Как видно, при поданном enable выходные биты tcpEstablished и mqttEstablished содержат «истину», это означает, что связь установлена успешно. В процессе испытаний я обнаружил интересное поведение блока, а именно — при подачи «истины» на вход enable подтверждение связи появлялось на одну-две секунды и пропадало. Связь устанавливалась только со второй попытки. Кроме того, при физическом обрыве связи этот ФБ просто информирует об отсутсвии соединения, и не предпринимает попыток автоматически пересоединиться. В целях автоматического соединения и пересоединения добавлен следующий нетворк:
Смысл следующий — если в течении 5 секунд установлен флаг запроса связи, но нет подтверждения, снять флаг запроса связи. И если флага запроса нет в течении 5 секунд, установить флаг запроса. В тепличных условиях лаборатории это прекрасно работает. В полевых условиях при нестабильной связи временные константы желательно скорректировать.
В данном примере предполагается, что контроллер не только отправляет какую-либо информацию брокеру, но и подписан на топик. Точнее, даже на два топика, чтобы пример был интереснее. В качестве принимаемой информации может быть, что угодно — команды, уставки, запросы архивной информации или состояния оборудования. В принципе, все ограничивается исключительно объёмом свободной рабочей памяти. Вполне логичным будет подписаться на нужные топики сразу после установления связи. И для этого потребуется поиграть с полями структуры subscribeToTopic, которую лучше все воспринимать, как «управляющую» структуру.
По переднему фронту (то есть, по единомоментному факту) установления соединения я поднимаю локальную битовую переменную #SubscriveToTopics и выставляю «номер шага» процедуры подписки в 1. Номер шага используется в связи с тем, что все операции у нас ассинхронные, и их надо выполнять последовательно, одну за другой, а не все сразу (все сразу не выполнятся).
Первый шаг подписки — подписаться на топик с названием «global». В случае успешного выполнения операции подписки переходим на следующий шаг. Иначе выполняем процедуру переподключения (хотя, реакция может быть иной, на усмотрение разработчика)
Смотрим. Если выставлен бит «выполняем подписку» и шаг = 1, то…
…если не выставлен бит управляющей структуры «подписатся», то задать имя интересующего топика (global) и поднять бит управляющей структуры «подписаться»
…если бит управляющей структуры «подписаться» поднят, то смотреть на биты выполнения задания done и error. При появлении бита done сбросить бит управляющей структуры «подписаться» и перейти на шаг №2. В случае возникновения ошибки — выполнить переподключение к брокеру.
Шаг №2. На этом шаге не нужно делать вообще ничего. Этот шаг требуется для того, чтобы ФБ клиента вызвался хотя бы один раз со сброшенным битом управляющей структуры «подписаться». Для этого есть и более изящные решения, но я обошелся простейшим таймером.
После 100мс ожидания просто идем на следующий шаг, к подписке на второй топик (шаг №3).
Шаг №3 аналогичен шагу №1, за исключением имени топика. После успешного завершения шага №3 сбрасывается локальный бит «выполнить подписку» (#SubscriveToTopics) и обнуляются шаги «подпрограммы подписки».
После выполнения подписки можно смело проверять работу клиента. Для этого я вызываю программу mosquitto_pub.exe:
mosquitto_pub.exe -h myhost.mydomain.ru -t global -m «kill all humans»
, где
myhost.mydomain.ru — доменное имя удаленного брокера
global — топик «global», на который только что подписался клиент
kill all humans — текст сообщения в топике global
После отправки сообщения смотрим выходную структуру subscriptionsMessage:
Как видно, в топике global прошло сообщение «kill all humans»
Флаг newMessageReceived нам еще пригодится в дальнейшем. Этот флаг выставляется сразу по получению нового сообщения и действителен в течении одного цикла сканирования. По нему очень удобно будет обрабатывать полученные команды, уставки или запросы.
Вторая задача контроллера в этом примере — отправлять информацию о технологическом процессе через равные промежутки времени. Например, каждые сутки. Или каждые два часа. Ну, или каждые 10 секунд, потому что в учебном примере не очень хочется терять время на пустое ожидание. Поэтому каждые 10 секунд я поднимаю локальный флаг «отправить данные» и выставлю шаг «подпрограммы отправки» в 1:
Нетворк отправки данных для разнообразия написан на SCL, а не нарисован в LAD. Право же, оперировать строками в графических языках — не самое удобное дело.
Всего отправляется значение 4 переменных, три из которых заданы статично в блоке данных, а одна — постоянно увеличивается на небольшую дельту. На первом шаге «подпрограммы» задается имя топика, это имя personal0. А так же формируется строка сообщения. Поскольку оперируем мы именно символьными данными, приходится выполнять преобразование типа REAL_TO_WSTRING и конкатенацию строк. Для контроллеров, тем более, начального уровня, это не самое лучшее занятие — очень быстро расходуется память и неплохо так съедаются вычислительные ресурсы. Длина передаваемого сообщения — 500 символов, есть куда развернуться. Можно, так же, добавить еще и метку времени. Можно формировать буфер сообщений, тем самым аккумулируя хотя бы минимум данных на период отсутствия связи. В общем, тут можно делать, что угодно (но лучше делать, что написано в ТЗ).
После формирования строки поднимается бит управляющей структуры «отправить сообщение» и меняется номер шага. На втором шаге идет проверка успешности или неуспешности отправки, после чего сбрасывается локальный бит «выполнить отправку» и сбрасывается номер шага «подпрограммы отправки».
Запустим клиент MQTT и посмотрим, что приходит в топике personal0 (именно в этот топик ПЛК и отправляет данные):
Ну, и напоследок. Демонстрация возможностей удаленного управления. Если в топике personal0 пришло сообщение exterminate, дискретный выход Q0.0 устанавливается в значение «истина».
Команда издателя:
mosquitto_pub.exe -h host.domain.ru -t personal0 -m «exterminate»
Нетворк программы контроллера:
Проверка пришедшего сообщения выполняется только по факту прихода самого сообщения (бит newMessageReceived), что вполне логично. А далее необходимо только проверять имя топика и текст сообщения. Любые дальнейшие действия программируются, как угодно.
На этом технический пример заканчивается, и хочется немного порассуждать о возможностях применения. Они есть, и, кажется, весьма широкие. Фактически, это можно применять в любых распределенных недорогих системах, где присутствуют малые объемы информации и, в силу этого, использование специализированных «телемеханических» протоколов нецелесообразно в виду их высокой стоимости. Ну, например, в ЖКХ. Если маленький ПЛК смотрит на расход энергоресурсов (общедомовых, а, может, даже и поквартирных) и раз в сутки отправляет сводку в диспетчерский центр. Или смотрит на состояние общедомового оборудования, шлет параметры раз в час, но при аварии — моментально. Достаточно лишь снабдить ПЛК GSM-модемом, и фактически, ничего больше не требуется, кроме простого компьютера с фиксированным ip-адресом. Рассуждая дальше, можно и без физического ПК обойтись, засунув его «в облако». Главное, не забыть должным образом настроить шифрование трафика. В этом случае имеет смысл данные потребления энергоресурсов из клиента сразу складывать в базу данных, но это уже требует высокоуровневого программирования
Или, как вариант, та же распределенная система, но не только сбор данных, но и взаимодействие M2M (контроллер-контроллер). Тут точно так же, достаточно минимального канала связи и доступного брокера.
Можно даже и на скаду информацию выводить, не особо заморачиваясь с программированием серверной (брокерской) части. Ведь ничто не мешает настроить все полевые контроллеры на отправку сообщений брокеру, взять еще один контроллер, читать эти сообщения и их результат раскладывать по блокам данных, и уже из них выводить показания на тот же WinCC Advanced.
Дальнейшие исследование показали отличное применение mqtt совместно со средой Node-RED. На Node-RED была "нарисована" программа, принимающая эти данные от брокера, разбирающая полученную строку и записывающая всю информацию (метку времени, значение) в базу данных MariaDB. Она же, программа на Node-RED позволила вытаскивать информацию за указанный временной промежуток, показывать ее в виде таблицы, графика и делать выгрузку в виде .csv файла.
vakhramov
Чем стандартный сокет не угодил :)? Какие специфические задачи должен был решить именно брокер? Куча подписчиков и куча издателей — имхо для этого и надо брокера, для остального есть сокет.
akcount Автор
Коллега, привет!
Классный вопрос, ответ на него я не знаю )
В моем представлении именно такая ситуация, куча ПЛК (издателей), разбросанных территориально с приватными айпишниками за натом, один брокер (меньше нет смысла) и несколько подписчиков, которые эти данные собирают, складывают в БД, визуализируют (в конце публикации про Node-RED и прочие MariaDB, вот как раз оно этим и занимается).
Тут еще такое дело. АСУшники в большинстве своем не очень то любят и (честно признаться) умеют программировать. Поэтому готовая реализация mqtt меня сильно заинтересовала.