Говоря сухим языком, МЭК 60870-5-104 – протокол телемеханики, предназначенный для передачи сигналов в систему верхнего уровня, регламентирующий использование сетевого доступа по протоколу TCP/IP. Чаще всего применяется в энергетике для информационного обмена между энергосистемами, а также для получения данных от измерительных преобразователей (вольтметры, измерительные преобразователи и прочее).
Особенностями протокола МЭК 60870-5-104 являются:
малое количество трафика, что актуально для передачи информации через сотовые каналы связи
поддержка инициативных сообщений от опрашиваемого устройства
передача аналоговых значений по апературе - изменение значимого порога
поддержка меток времени
поддержка синхронизации времени
Говоря чуть попроще. В условиях, когда объекты управления находятся на значительном расстоянии, каналы связи могут быть очень «шумными» и не обеспечивать обычную надежную работу, как в локальной сети передачи данных, при этом информация с объектов и команды для них должны проходить обязательно, используются протоколы телемеханики. МЭК-104 является одним из таких протоколов и работает поверх (на прикладном уровне) TCP/IP. Протокол МЭК-101 можно назвать старшим братом «сто четвертого». «Сто первый» предназначен для работы по последовательным каналам связи и имеет ряд отличий. И протоколы МЭК-101/104, и WinCC OA являются событийно-ориентированными, в них не применяется постоянный опрос подчиненного устройства, как, например, в Modbus, а информация отправляется только по изменению.
Настройка сервера протокола 104 во многом похожа на настройку клиента любого другого протокола, но имеет ряд неочевидных отличий. Для начала создаем стандартный (standart legacy, он же с базовыми параметрами безопасности) проект с именем i104srv. Редактируем его конфигурационный файл, добавляем секцию iec_2:
[iec_2]
tcpServerPort = 2404
Эта секция означает, что драйвер протокола IEC, запущенный в системе под номером 2, будет выполнять функцию сервера и «слушать» порт с номером 2404.
Далее проект необходимо запустить и добавить драйвер в консоль под номером 2.
После запуска драйвера стоит убедиться, что он действительно слушает необходимый нам порт. Для этого достаточно выполнить строковую команду netstat -a | grep 2404
В случае, если драйвер не запускается, вполне возможно, что порт 2404 уже занят. Следует проверить его до запуска драйвера и при необходиомсти изменить номер порта в конфигуриционном файле на другой, свободный.
Следующий шаг - настройка соединения для сервера 104, которая располагается в «Управление системой - Драйверы МЭК»
Появляется окно, содержащее перечисление определенных драйверов протокола с сопоставлением каждому драйверу коннектов. В системе уже определено заранее три драйвера (три точки данных) - Iec_1, Iec_2 и Iec_3.
Мы работаем с драйвером Iec_2. Меняем номер драйвера в верхней части, после чего добавляем в список этого драйвера уже существующий коннект с какой-либо системой, названием этого коннекта - IECTARGET. Соединение с именем IECTARGET в системе было предопределено, но, разумеется, оно не настроено.
Переходим к редактированию соединения.
Показываю сразу настроенное окно
Что такое локальный и глобальный списки? Должен заметить, что в соответствии с документацией, если в настройках адресного конфига элемента точки данных явно указано имя соединения, то содержимое этих списков игнорируется. По этой причине в нашем случае списки пустые. Сами списки предсталяют собой набор строк вида x1.x2.x3.x4.x5, где x - это байт.
x1 и x2 - двухбайтовый common address
x3, x4, x5 - трехбайтовый адрес переменной протокола, вместо конкретного значения часто используется сивол шаблона *
Пример такого списка:
0.1.*.*.*
Это означает, что по этому соединению драйвер должен передавать любые (*.*.*) переменные, которые соответствуют common адресу 1 (0.1)
Локальный список содержит адреса станций, доступных только через это подключение. Глобальный список содержит адреса всех станций, доступных не только из этой системы, но и из других. Это позволит не вводить параметры для резервированных подключений несколько раз.
Более подробно со списками можно ознакомиться в документации. Списки - это своего рода «фильтр» или «маршрутизация», предназначенная для сложных систем. Наша система очень простая, по этому причине списки не используются.
Далее окно настройки соединения должно содержать перечисление машин, с которых сервер принимает подключения. В рамках этого соединения используется только один хост вот с такими настройками
Эта настройка означает, что любой клиент протокола (_ANY_любоеназвание, в нашем случае _ANY_host0) может подключиться к этому серверу. При необходимости можно вместо _ANY_ указывать явно ip-адрес клиента или его «доменное» (или «символьное») имя , вроде host.domain. В этом случае сервер будет принимать соединение только с заданного адреса. Порт в этой настройке указываем, равным 0. Значение таймаута оставляем по умолчанию.
Текущая настройка позволяет одному клиенту подключиться к нашему серверу. При необходимости большего количества клиентов, надо прописать больше хостов.
Ставим теперь галочки «Active» для соединения, хост становится «активным «автоматически. Мне кажется, многих коллег сбивала с пути галочка «Change Role», которая, казалось бы, должна менять роль клиента на сервер, однако, всего лишь, является аналогом Active Connection Establishment для связи S7. В соотвествии с документацией, с выставленной настройкой Change Role сервер не ждет соединения с клиентом, сам подключается к клиенту, при этом выполняет функцию сервера.
В настоящий момент ни один из клиентов не подключен. Ровно как и не описаны данные, которые наш сервер 104 протокола будет отдавать клиентам. Для этого на нужный элемент точки данных требуется разместить адресный конфиг, в отличии, например, от сервера OPC UA, где мы просто вносим нужные точки данных в группы. Краткое резюме - если мы хотим отдавать переменную по 104 протоколу, она должна быть представлена в виде точки данных с размещенным адресным конфигом. Тут мы видим хороший такой подводный камень - любой активный адресный конфиг сервера тоже «кушает» одну внешнюю переменную из лицензии. Есть и еще один камушек. Если мы создаем «гейт» между протоколами, например, превращаем модбас в 104, то на один DPE потребуется разместить два адресных конфига - один для чтения под модбасу, второй для отдачи по МЭК. Разумеется, это невозможно, но вполне решаемо на уровне скриптов или функции точки данных.
Для удобства создаем следующий тип точек данных.
В этом типе spi отвечает за отдачу данных по протоколу 104. Orig является источником этих данных (например, в нему мы забираем инфу по модбасу, а на практике будем просто оперировать его original значением). Тип этих элементов - булевый. Элемент целочисленного типа cot потребуется нам в дальнейшем, он будет содержать код причины передачи информации (Cause Of Transmission).
Создаем точку данных этого типа с именем complexSPI, на spi размещаем конфиг «функция ТД»
В соответствии с этим конфигом любое изменение в элементе orig автоматически переноситя в элемент spi, включая метку времени с точностью до миллисекунды. Размещаем на элементе spi адресный конфиг и настраиваем его. Имя соединения указываем явно, как и номер драйвера. Тип информации - одноэлементная (бит данных) с меткой времени CP56time, общий адрес - 1 (0.1), информационный адрес - 1 (0.0.1). Направление - выход, с точки зрения сервера мы отдаем этот бит наружу. Если б это была команда, требовалось бы задать «Вход». «Выход (групп.)» означает, что будет передан весь объем информации, включая вспомогательный, на чем в нашем случае можно не заострять внимание.
Делаем адресный конфиг активным и пробуем подключиться. В качестве клиента я использую вот такую программу:
Без лицензии в демо-режиме программа работает 15 минут, чего в нашем случае более, чем достаточно. И программа-клиент, и сервер протокола 104 работают на одном копьютере, так что общаются они по локальному интерфейсу 127.0.0.1. Создаю в клиенте подключение, все по умолчанию
Перехожу на вкладку объектов данных, наживаю «Start Communication»
Связь установлена, но я не вижу никаких объектов данных
Для начала посмотрим состояние связи на стороне сервера
Итого, связь есть. Но информации на клиенте нет, даже с включенной опцией «создавать объекты данных автоматически». Любое установление связи в семействе 101/104 протоколов начинается с т.н. General Interogation, или первичного опроса, в рамках которого сервер сообщает клиенту о всех своих информационных объектах, их значении и метке времени, и уж потом после первичного опроса отдает только изменения в этом списке. Очевидно, надо настроить на стороне сервера МЭК-104 этот самый первичный опрос. Но вначале необходимо убедиться, что обмен данными вообще хоть как-то выполняется. Для этого в модуле para поменяем значение orig (что автоматически скопируется в spi).
Теперь в клиенте информация появилась. Тем не менее, для работы сервера первичный опрос является обязательным. В документации на систему дается легкий намек - реакция на запрос general interogation должна быть описана на уровне скриптов. Клиент при подключении отправлять запрос GI, этот запрос фиксируется в точке данных драйвера, вот таким образом.
Запрос фиксируется в виде имени соединения (IECTARGET) и Common Address (255.255 - запрос на все данные, если я правильно понял документацию).
По факту «отлова» этого запроса мы должны будем записть в драйвер значение информационного объекта, задав код причины передачи = 20. Код причины передачи уже заложен нами в тип точек данных.
Для того, чтобы мы смогли менять COT, необходимо разместить на этот элемент еще один адресный конфиг. То есть, мы кушаем еще одну точку лицензии. Есть второй способ, без дополнительного адресного конфига, мы рассмотрим его позже.
Адресный конфиг тут выглядит аналогично, за исключением того, что выславлена опция Тип адреса COT. Так же не забывайте, что тип этого элемента - целое (независимо от типа переменной МЭК - SPI, ME и прочее). Давайте проведем экмперимент - выставим COT = 3 (спонтанная передача данных) и запишем что-нибудь в драйвер.
А теперь изменим COT на 20 (первичный опрос) и запишем то же самое значение в драйвер.
При таком подходе в обработчике события «первичный опрос» надо для всех выдаваемых переменных дать значение COT = 20, затем записать в драйвер то же значение, что и было, после чего - сбросить COT на значение 3 (спонтанная передача). Слишком много событий, конечно, но и первичный опрос выполняется не каждую минуту.
Создаем скрипт с именем iecGI
Функция main
main()
{
dpConnect("MonitorGI", false, "_Iec_2.Monitor.GQ");
}
Функция - обработчик MonitorGI
void MonitorGI(string dpe, string result)
{
bool act, val;
// Анализ запрашиваемого общего адреса в переменной result (Monitor.GQ)
// [реализация]
//GI, основанный на DPE COT
//в конфиге не должно быть connUserByteCOT
dpGet("complexSPI.spi:_address.._active", act);
if(act) {
dpSet("complexSPI.cot", 20);
dpGet("complexSPI.spi", val);
dpSet("complexSPI.spi", val);
dpSet("complexSPI.cot", 3);
}
}
Важным в коментариях является слово «реализация». Конечно, в боевых системах потребуется анализировать, кого запрашивают на GI, кто запрашивает, какие у нас есть DP под этот Common Address и прочее. Все, разумеется, зависит от конкретного проекта и его информационного обеспечения.
Остальные коментарии относятся ко второму способу реализации задания COT, и пока не заслуживают внимания.
Запускаем скрипт в работу.
Подключаемся клиентом. Видим, что в списке переменных клиента появился наш информационный объект с адресом 0.0.1, значением истина, с качеством GOOD и кодом причины перадачи -INTEROGEN.
Меняем значение переменной.
В клиенте наблюдаем, как значение поменялось на ложь, и изменился код причины передачи на SPONT.
Второй способ управления причиной передачи заключается в использовании пользовательских байт. Помните, для каждого типизированного элемента данных есть пользовательские биты? На скриншоте чуть повыше в модуле para они находятся в «Изменяемые биты». 32 бита прекрасно складываются в 4 байта. Итого, для каждого значения dpe у нас есть 32 бита, или же 4 байта. Для управления COT нам потребуется всего лишь 1 байт. Внесем изменения в конфиг-файл проекта, добавим опцию connUserByteCOT=1 и перезапустим драйвер. Теперь код причины передачи контролируется в userbyte1.
Для второго способа создадим еще одну точку данных, simpleSPI того же типа. В нем точно так же элемент orig автоматически копируется в spi, а адресный конфиг немного отличается - это будет уже объект данных с адресом 0.0.2.
Внесем изменения в ранее созданный скрипт
Скрипт в текстовом виде и без комментариев:
main()
{
dpConnect("MonitorGI", false, "_Iec_2.Monitor.GQ");
}
void MonitorGI(string dpe, string result)
{
bool act, val;
dpGet("simpleSPI.spi:_address.._active", act);
if(act) {
dpSet("simpleSPI.spi:_original.._userbyte1", 20);
}
}
После перезапуска скрипта увидим следующее:
Однако, при последующих изменениях код причины передачи остается INTEROGATION
Связано это с тем, что во втором примере, в отличии от первого, код причины передачи никто не сбрасывает до 3 (спонтанная). Поэтому любая посылка идет с кодом 20, первичный опрос. И в данном случае тут нет красивого решения, чем-то приходится жертвовать. Первый способ - самый простой. Сбросить код причине в 3 сразу же в скрипте обработки опроса, вот так:
Второй способ GI наименее затратен с точки зрения лицензионных точек и ресурсом менеджера событий. Недостаток заключается в том, что после подключения клиент выполнит первичный вопрос, возможно, даже положит эту информацию в базу данных с кодом 20, но потом, фактически мгновенно, получит новую информацию с кодом 3, что и будет отображено в экране клиента (зависит от клиента). То есть, юридически GI выполнен, но клиент рапортует о спонтанном изменении данных.
Второй способ куда менее эффективен с точки зрения ресурсов менеджера событий, он удваивает количество сообщений в системе. Прописываем еще один dpConnect.
main()
{
dpConnect("MonitorGI", false, "_Iec_2.Monitor.GQ");
dpConnect("SetSpontCOT", false, "simpleSPI.spi:_original");
}
Привязываемся к изменению значения отдаваемой точки данных (для второго способа эта точка данных simpleSPI). При изменении значения этой точки данных смотрим код причины передачи. Если он был 20 (опрос), меняем его на 3 (спонтанно). Итого, вместо одного события в системе на одно изменение переменной протокола, событий становится от 2 до 3, в зависимости от состояния системы. Насколько это применимо на практике - судить не берусь.
void SetSpontCOT(string dpe, bool val)
{
string dp = dpSubStr(dpe, DPSUB_DP);
int cot;
dpGet(dp + ".:_original.._userbyte1", cot);
if (cot==20)
dpSet(dp + ".:_original.._userbyte1", 3);
}
Управление качеством отдаваемых данных так же можно осуществлять через пользовательские байты, добавив в конфиг опцию
connUserByteQ = 2
В данном случае за качество сигнала отвечает второй пользовательский байт.
Изложенной выше информации вполне достаточно для того, чтобы самостоятельно поднять свой сервер протокола 104 на базе системы WinCC OA и начать «затачивать» его под нужды конкретного проекта.
Оставшаяся важнейшая часть информации относится к буферизации данных на момент отсутствия связи. На уровне драйвера это никак не осуществляется и даже не описывается. Буферизация данных потребует реализации на пользовательском уровне и будет описано во второй части.