В этой статье я хотел бы рассказать о своем знакомстве с протоком передачи данных МЭК 870-5-104 со стороны контролируемого (slave) устройства путем написания простой библиотеки на Arduino.
МЭК 60870-5-104 – протокол телемеханики, предназначенный для передачи сигналов ТМ в АСТУ, регламентирующий использование сетевого доступа по протоколу TCP/IP. Чаще всего применяется в энергетике для информационного обмена между энергосистемами, а также для получения данных от измерительных преобразователей (вольтметры, счетчики электроэнергии и прочее).
Стэк протокола МЭК 670-5-104:


APCI — Управляющая Информация Прикладного Уровня может применяться как самостоятельный управляющий кадр (кадр U или кадр S).
ASDU — Блоки данных прикладного уровня, состоит из идентификатора блока данных и одного или более объектов информации, каждый из которых включает в себя один или более однородных элементов информации (либо комбинаций элементов информации).
APDU — Протокольный блок данных прикладного уровня.
ТС — телесигнализация.
ТИ — телеизмерения.
ТУ — телеуправление.
Контролирующая (master) станция инициализирует установку TCP соединения пу��ем посылки TCP пакета с флагом (SYS). Соединение считается установленным, если в течение контрольного времени (t0) контролируемая станция (slave) выдала на свой уровень TCP/IP подтверждение «активного открытия» (SYS ACK). Контрольное время t0 называется «Тайм-аут установки соединения». Таймер t0 определяет, когда открытие отменяется и не определяет начало новой попытки соединения.

Взаимодействие с транспортным уровнем выполняет стандартная библиотека для плат Arduino «Ethernet.h». То есть первым делом необходимо установить TCP/IP соединение между контролируемой и контролирующей станциями. Для этого необходимо в скетче Arduino инициализировать устройство и создать сервер который будет ожидать входящие соединения через указанный порт.
Если загрузить этот скетч то будет происходить следующее:

Установка соединения, далее приходит пока неизвестный для Arduino пакет STARTDT act и по истечении определенного времени рвется соединение. Далее необходимо разобраться что такое STARTDT act.
В МЭК 670-5-104 существует 3 типа формата для передачи:
После успешного «тройного рукопожатия» контролирующая (master) станция посылает APDU STARTDT (старт передачи данных). STARTDT инициирует для контролируемой (Slavе) станции разрешение передачи блоков ASDU (кадров I) в направлении контролирующей (master), для продолжения работы необходимо подтвердить STARTDT, если контролируемая (Slavе) станция готова к передаче блоков данных. Если контролируемая (slave) станция не подтверждает выполнение STARTDT то контролирующая (master) станция вызывает обязательное закрытие IP- соединения.
Таким образом далее необходимо считать байты полученные от контролирующей (master) станции и разобрать их.
Прочитав данные необходимо разобрать их и сформировать ответ.
Вот как выглядит посылка содержащая блок STARTDT в программе Wireshark, APDU блок U-формата, который состоит только из APCI.
APCI-Управляющая Информация Прикладного Уровня может применяться как самостоятельный управляющий кадр (кадр U или кадр S).

Вкратце можно сказать, что блок APCI определяет тип блока APDU и его длину. APCI состоит из следующих шести байтов:
1. Признак инициализации блока APDU переменной длины, начинающийся байтом START2 68h;
2. Длин APDU, в данном примере равна четырем байтам;
3. Байт управления в котором определяется тип APDU, в данном примере записано значение равное семи, что означает запрос на передачу данных;
4,5,6 Не используются.

Исходя из вышеописанного, перед тем как ответить, не мешало бы определить какой тип APDU нам послала контролирующая станция. Зная, что тип APDU записан третьим по порядку чтения блока APCI байтом, сохраню его в целочисленную переменную.
Примечание: Если будет получен пакет формата «I» то 3 байт в APCI будет также содержать значение младшего слова счетчика принятых пакетов,

поэтому пришлось немного усложнить конструкцию определения типа APDU.
Из рисунка выше в��дно, что тип APDU соответствующий значению 7 это STARTDT act соответственно ответить необходимо таким же по структуре пакетом только значение типа должно иметь значение 11 (0b), что соответствует STARTDT con.
После обновления скетча наблюдаем следующий порядок обмена:

Установку соединения, запрос на передачу данных, подтверждение запроса и еще один новый пока неизвестный APDU формата I типа 1 C_IC_NA Act.
APDU <100> C_IC_NA_1 кроме блока APCI так же имеет блок ASDU (блок данных прикладного уровня), которые вместе формируют Протокольный Блок Данных Прикладного Уровня APDU.

Рассмотрим более подробно полученный APDU.
APCI:
ASDU:
В ответ на <100> C_IC_NA_1 необходимо ответить подтверждением на запрос, передать объекты информации о процессе, которые запомнены на контролируемой станции и завершить активацию.
Для отправки подтверждения необходимо в ASDU <100> C_IC_NA_1 записать в байт указывающий на причину передачи (CauseTX) значение равное 7, для оправки завершения активации необходимо записать в байт указывающий на причину передачи (CauseTX) значение равное 10.
После обновления скетча наблюдаем следующий порядок обмена:

Установку соединения, запрос на передачу данных, подтверждение, запрос на общий опрос станции от контролирующей станции, завершение инициализации, запрос на общий опрос станции в направление контролируемой станции, подтверждение общего опроса, пересылка значений всех доступных сигналов на контролирующей станции, завершение общего опроса и неизвестный APCI формата S.
Запрос опроса станции выдается в направлении контролируемой станции:
— если с контролируемой станции получен «КОНЕЦ ИНИЦИАЛИЗАЦИИ» или
— если центральная станция обнаруживает потерю канала (безуспешное повторение запроса канального уровня) и последующее восстановление его.
APDU блок формата S, состоящий только из APCI предназначен для подтверждения принятого APDU I формата. Для S-формата 7 старших бит служебного поля байта 1 и байт 2 не задействованы, а байт 3 (7 старших бит) и байт 4 определяют текущий номер принятой посылки.

В данном случае блок S указывает на то, что контролирующая (master) станция готова к приему данных в течении определенного времени, не превышающего, тайм-аут t3 определенного на стороне контролирующей (master) станции. То есть контролирующая (master) станция говорит нам «я готова к приему данных!». Далее необходимо позаботиться о том какие данные передавать и откуда их брать.
Что можно передавать? Существует несколько видов информации определённых в МЭК 870-5- 104:
В данном примере рассматривается передача контрольной информации на примере 1, 11 и 13 функций (одноэлементная, измерение масштабируемое, измерение короткий формат с плавающей запятой). Данные формируются рандомно. Также необходимо учитывать, что у каждого передаваемого сигнала имеется байт качества.

Простой алгоритм определения качества сигнала:
Так же необходимо учесть, что для каждого сигнала имеется уникальный идентификатор IOA, в энергетике принято распределять эти адреса следующим образом:
Для передачи значения сигналов в массив для отправки используется библиотека EEPROM:
И так получив APDU подтверждение S или I формата от контролирующей станции можно начать передавать имеющиеся в распоряжении данные, не забывая увеличивать номер передаваемого кадра.

Загрузив скетч в Wireshark наблюдаем, что наконец-то началась передача данных.

Далее привожу описание структуры ASDU <100>M_SP_NA_1 одноэлементная индикация.

TypeId — вид информации.
SQ — классификатора переменной структуры.
Предусматриваются две структуры блоков данных:
1. Блок, содержащий i объектов информации, каждый из которых содержит по одному элементу информации (или по одной комбинации элементов); старший бит классификатора переменной структуры SQ (single/sequence) равен 0, остальные 7 битов задают число i.
2. Блок, содержащий один объект информации, который содержит j элементов либо одинаковых комбинаций элементов информации; старший бит (27 = 80h) классификатора SQ равен 1, остальные 7 битов задают число j.
CauseTx — причина передачи.
Addr — адрес слэйва (указывается при конфигурировании мастера).
IOA — адрес объекта информации, по этому адресу контролирующая станция будет привязывать свой тэг
SIQ — показатель качества передаваемого сигнала.
Структура ASDU блока функции <11>M_ME_NB_1:
В ответ на полученные данные master будет отправлять блоки формата S и процесс зациклится до тех пор пока контролируемое(slave) устройство не перестанет передавать кадры.
Процедуры тестирования применяются с целью контроля за работоспособностью транспортных соединений. Процедура выполняется независимо от «активности» IP-соединения, если в течение контрольного времени t3 не было принято ни одного кадра (I, U, S). Время t3 является предметом согласования и называется «Тайм-аут для посылки блоков тестирования в случае долгого простоя». Процедура тестирования реализуется путем посылки тестового APDU (TESTFR =act), которое подтверждается принимаемой станцией с помощью APDU (TESTFR =con).
Если от контролирующей (master) станции придет APDU у которого в байте отвечающего за тип APDU значение равно шестидесяти сети (TESTFR) это говорит о том, что в течении времени t3 от контролируемой станции не было принято ни одного кадра (I, U, S), и если в течении времени t1 не ответить подтверждением то соединение будет разорвано.
На этом всё, если кому-нибудь интересно то в следующей статье я рассмотрю протокол МЭК 670-5-104 со стороны контролирующей (master) станции на примере Arduino.
Что такое МЭК 870-5-104 это и где применяется?
МЭК 60870-5-104 – протокол телемеханики, предназначенный для передачи сигналов ТМ в АСТУ, регламентирующий использование сетевого доступа по протоколу TCP/IP. Чаще всего применяется в энергетике для информационного обмена между энергосистемами, а также для получения данных от измерительных преобразователей (вольтметры, счетчики электроэнергии и прочее).
Стэк протокола МЭК 670-5-104:

Используемые материалы
- плата Arduino UNO;
- Ethernet shield (HR911105a);
- в роли мастера МЭК 60870-5-104 будет выступать MicroScada от ABB;
- Wireshark для анализа трафика.
Краткое описание этапов работы
- Установка TCP/IP соединение по 2404 порту;
- Подтверждение запроса на передачу данных (STARTDT act/con);
- Запрос на общий опрос станции;
- Подготовка и передача данных контролирующей (master) станции;
- Процедуры тестирования.
Подготовка
- Подключена плата Arduino к ПК;
- Настроен соответствующим образом сетевой интерфейс;
- Настроена контролирующая (master) станция (добавлена 104 линия и добавлено контролируемое (slave) устройство).

Термины и сокращения
APCI — Управляющая Информация Прикладного Уровня может применяться как самостоятельный управляющий кадр (кадр U или кадр S).
ASDU — Блоки данных прикладного уровня, состоит из идентификатора блока данных и одного или более объектов информации, каждый из которых включает в себя один или более однородных элементов информации (либо комбинаций элементов информации).
APDU — Протокольный блок данных прикладного уровня.
ТС — телесигнализация.
ТИ — телеизмерения.
ТУ — телеуправление.
1. Установка TCP/IP соединение порт 2404
Контролирующая (master) станция инициализирует установку TCP соединения пу��ем посылки TCP пакета с флагом (SYS). Соединение считается установленным, если в течение контрольного времени (t0) контролируемая станция (slave) выдала на свой уровень TCP/IP подтверждение «активного открытия» (SYS ACK). Контрольное время t0 называется «Тайм-аут установки соединения». Таймер t0 определяет, когда открытие отменяется и не определяет начало новой попытки соединения.

Взаимодействие с транспортным уровнем выполняет стандартная библиотека для плат Arduino «Ethernet.h». То есть первым делом необходимо установить TCP/IP соединение между контролируемой и контролирующей станциями. Для этого необходимо в скетче Arduino инициализировать устройство и создать сервер который будет ожидать входящие соединения через указанный порт.
Скетч
#include <Ethernet.h> byte mac[] = {0x90, 0xA2, 0xDA, 0x0E, 0x94, 0xB7 };//мак адрес IPAddress ip(172, 16, 7, 1);// ip адрес контролируемого устройства IPAddress gateway(172, 16,7, 0);//шлюз IPAddress subnet(255, 255, 0, 0);//маска EthernetClient client; EthernetServer iec104Server(2404);// для МЭК 670-5-104- порт- 2404 void setup() { Ethernet.begin(mac, ip, gateway, subnet); // инициализация Ethernet-устройства } void loop() { client = iec104Server.available();//подсоединение клиентов }
Если загрузить этот скетч то будет происходить следующее:

Установка соединения, далее приходит пока неизвестный для Arduino пакет STARTDT act и по истечении определенного времени рвется соединение. Далее необходимо разобраться что такое STARTDT act.
2. Подтверждение запроса на передачу данных (STARTDT act/con)
В МЭК 670-5-104 существует 3 типа формата для передачи:
- I-формат для передачи данных телеметрии;
- S-формат для передачи квитанций;
- U-формат для передачи посылок установления связи и тестирования канала связи.
После успешного «тройного рукопожатия» контролирующая (master) станция посылает APDU STARTDT (старт передачи данных). STARTDT инициирует для контролируемой (Slavе) станции разрешение передачи блоков ASDU (кадров I) в направлении контролирующей (master), для продолжения работы необходимо подтвердить STARTDT, если контролируемая (Slavе) станция готова к передаче блоков данных. Если контролируемая (slave) станция не подтверждает выполнение STARTDT то контролирующая (master) станция вызывает обязательное закрытие IP- соединения.
Картинка

Таким образом далее необходимо считать байты полученные от контролирующей (master) станции и разобрать их.
Скетч
uint8_t iec104ReciveArray[128];//массив для приема EthernetClient client = iec104Server.available(); if(client.available()) { delay(100); int i = 0; while(client.available()) { iec104ReciveArray[i] = client.read();//записываем в буфер приема данные i++; }
Прочитав данные необходимо разобрать их и сформировать ответ.
Wireshark

Вот как выглядит посылка содержащая блок STARTDT в программе Wireshark, APDU блок U-формата, который состоит только из APCI.
APCI-Управляющая Информация Прикладного Уровня может применяться как самостоятельный управляющий кадр (кадр U или кадр S).

Вкратце можно сказать, что блок APCI определяет тип блока APDU и его длину. APCI состоит из следующих шести байтов:
1. Признак инициализации блока APDU переменной длины, начинающийся байтом START2 68h;
2. Длин APDU, в данном примере равна четырем байтам;
3. Байт управления в котором определяется тип APDU, в данном примере записано значение равное семи, что означает запрос на передачу данных;
4,5,6 Не используются.

Исходя из вышеописанного, перед тем как ответить, не мешало бы определить какой тип APDU нам послала контролирующая станция. Зная, что тип APDU записан третьим по порядку чтения блока APCI байтом, сохраню его в целочисленную переменную.
Примечание: Если будет получен пакет формата «I» то 3 байт в APCI будет также содержать значение младшего слова счетчика принятых пакетов,

поэтому пришлось немного усложнить конструкцию определения типа APDU.
Скетч
ASDU=iec104ReciveArray[6];//пакет ASDU? switch (ASDU) { case 100://опрос станции TypeQuerry=iec104ReciveArray[2]-word(iec104ReciveArray[3],iec104ReciveArray[2]);//Тип rxcnt+=2;//увеличение счетчика принятых пакетов break; case 0: TypeQuerry=iec104ReciveArray[2]; //Тип break; default : TypeQuerry=iec104ReciveArray[2];//Тип break; }
Из рисунка выше в��дно, что тип APDU соответствующий значению 7 это STARTDT act соответственно ответить необходимо таким же по структуре пакетом только значение типа должно иметь значение 11 (0b), что соответствует STARTDT con.
Скетч
#include <Ethernet.h> byte mac[] = {0x90, 0xA2, 0xDA, 0x0E, 0x94, 0xB7 }; IPAddress ip(172, 16, 7, 1); IPAddress gateway(172, 16,7, 0); IPAddress subnet(255, 255, 0, 0); EthernetClient client; EthernetServer iec104Server(2404); int TypeQuerry, MessageLength;// тип APDU и длина посылки uint8_t iec104ReciveArray[128];//буфер приема APDU void setup() { Ethernet.begin(mac, ip, gateway, subnet); } void loop() { client = iec104Server.available(); if(client.available())//клиент подсоединен { delay(100); int i = 0; while(client.available())//чтение байтов { iec104ReciveArray[i] = client.read();//записываем в буфер приема данные i++; } TypeQuerry= iec104ReciveArray[2];//определяем тип APDU switch(TypeQuerry) { case 07:// если пришел тип STARTDT iec104ReciveArray[0]=iec104ReciveArray[0];// START2 = 68h; iec104ReciveArray[1]=iec104ReciveArray[1];//длина APDU iec104ReciveArray[2] = iec104ReciveArray[2]+4; //тип APDU iec104ReciveArray[3]=0; iec104ReciveArray[4]=0; iec104ReciveArray[5]=0; MessageLength = iec104ReciveArray[1]+2;//длина сообщения + 2 байта Start and Lenght APCI delay(100); client.write(iec104ReciveArray, MessageLength);//передача обратно break; } } }
После обновления скетча наблюдаем следующий порядок обмена:

Установку соединения, запрос на передачу данных, подтверждение запроса и еще один новый пока неизвестный APDU формата I типа 1 C_IC_NA Act.
3. Запрос на общий опрос станции
Команда опроса C_IC ACT запрашивает полный объем или заданный определенный поднабор опрашиваемой информации на КП. Поднабор (группа) выбирается с помощью описателя опроса QOI.
Команда опроса станции требует от контролируемых станций передать актуальное состояние их информации, обычно передаваемой спорадически (причина передачи = 3), на контролирующую станцию с причинами передачи от <20> до <36>. Опрос станции используется для синхронизации информации о процессе на контролирующей станции и контролируемых станциях. Он также используется для обновления информации на контролирующей станции после процедуры инициализации или после того, как контролирующая станция обнаружит потерю канала (безуспешное повторение запроса канального уровня) и последующее восстановление его. Ответ на опрос станции должен включать объекты информации о процессе, которые запомнены на контролируемой станции. В ответ на опрос станции эти объекты информации передаются с идентификаторами типов <1>, <3>, <5>, <7>, <9>, <11>, <13>, <20> или <21> и могут также передаваться в других ASDU с идентификаторами типов от <1> до <14>, <20>, <21>, от <30> до <36> и с причинами передачи <1> — периодически/циклически, <2> — фоновое сканирование или <3> — спорадически.
Картинка

APDU <100> C_IC_NA_1 кроме блока APCI так же имеет блок ASDU (блок данных прикладного уровня), которые вместе формируют Протокольный Блок Данных Прикладного Уровня APDU.

Рассмотрим более подробно полученный APDU.
APCI:
- В первом байте указан тип 0 означающий, что это команда опроса;
- Во втором длина APDU 14 байт;
ASDU:
- Первый байт в блоке ASDU определяет тип объекта информации, в данном случае <100> C_IC_NA_1 (общий опрос станции);
- Второй структуру блока данных;
- Третий причину передачи (CauseTx), значение шесть означает запрос на активацию;
- Четвертый общий адрес стануии;
- Пятый адрес контролируемой (slave) станции;
- С шестого по восьмой адрес объекта информации равен нулю;
- Девятый информационный байт — QOI — описатель запроса, имеющий следующие значения:
В ответ на <100> C_IC_NA_1 необходимо ответить подтверждением на запрос, передать объекты информации о процессе, которые запомнены на контролируемой станции и завершить активацию.
Для отправки подтверждения необходимо в ASDU <100> C_IC_NA_1 записать в байт указывающий на причину передачи (CauseTX) значение равное 7, для оправки завершения активации необходимо записать в байт указывающий на причину передачи (CauseTX) значение равное 10.
Скетч
case 00://опрос станции txcnt=txcnt+02; //Подтверждение общего опроса iec104ReciveArray[0]=iec104ReciveArray[0]; iec104ReciveArray[1]=iec104ReciveArray[1]; iec104ReciveArray[2]=lowByte(txcnt);//TX L iec104ReciveArray[3]=highByte(txcnt);//TX H iec104ReciveArray[4]=lowByte(rxcnt);//RX L iec104ReciveArray[5]=highByte(rxcnt);//RX H iec104ReciveArray[6]=100;//опрос станции iec104ReciveArray[7]=01; iec104ReciveArray[8]=7;//cause Actcon iec104ReciveArray[9]=00;//OA iec104ReciveArray[10]=01;//Addr iec104ReciveArray[11]=00;//Addr iec104ReciveArray[12]=00;//IOA iec104ReciveArray[13]=00;//IOA iec104ReciveArray[14]=00;//IOA iec104ReciveArray[15]=20;//IOA, QOI MessageLength = iec104ReciveArray[1]+2; delay(100); client.write(iec104ReciveArray, MessageLength); txcnt=txcnt+2;//увеличение счетчика переданных пакетов //актуальные состояния информации контролирующей станции в ответ на общий опрос iec104ReciveArray[0]=iec104ReciveArray[0]; iec104ReciveArray[1]=14;//длина APDU=APCI(4)+ ASDU(10) iec104ReciveArray[2]=lowByte(txcnt); iec104ReciveArray[3]=highByte(txcnt); iec104ReciveArray[4]=lowByte(rxcnt); iec104ReciveArray[5]=highByte(rxcnt); iec104ReciveArray[6]=1;//type 1 iec104ReciveArray[7]=01;//sq iec104ReciveArray[8]=20;//Cause Inrogen iec104ReciveArray[9]=00;//AO iec104ReciveArray[10]=01;//Adress iec104ReciveArray[11]=00;//Adress iec104ReciveArray[12]=iecData[0];//IOA iec104ReciveArray[13]=iecData[1];//IOA iec104ReciveArray[14]=0;//IOA iec104ReciveArray[15]=iecData[2];//value [DATA 1] MessageLength = iec104ReciveArray[1]+2; client.write(iec104ReciveArray, MessageLength); txcnt=txcnt+2; iec104ReciveArray[0]=iec104ReciveArray[0]; iec104ReciveArray[1]=14; iec104ReciveArray[2]=lowByte(txcnt); iec104ReciveArray[3]=highByte(txcnt); iec104ReciveArray[4]=lowByte(rxcnt); iec104ReciveArray[5]=highByte(rxcnt); iec104ReciveArray[6]=1;//type 1 bool iec104ReciveArray[7]=01; iec104ReciveArray[8]=20;//Cause Inrogen iec104ReciveArray[9]=00; iec104ReciveArray[10]=01; iec104ReciveArray[11]=00; iec104ReciveArray[12]=iecData[3]; iec104ReciveArray[13]=iecData[4]; iec104ReciveArray[14]=0; iec104ReciveArray[15]=iecData[5]; MessageLength = iec104ReciveArray[1]+2; client.write(iec104ReciveArray, MessageLength); delay(5); txcnt=txcnt+2; iec104ReciveArray[0]=iec104ReciveArray[0]; iec104ReciveArray[1]=22; iec104ReciveArray[2]=lowByte(txcnt); iec104ReciveArray[3]=highByte(txcnt); iec104ReciveArray[4]=lowByte(rxcnt); iec104ReciveArray[5]=highByte(rxcnt); iec104ReciveArray[6]=11;//type 11 int iec104ReciveArray[7]=02;//sq iec104ReciveArray[8]=20;//Cause Inrogen iec104ReciveArray[9]=00;//AO iec104ReciveArray[10]=01;//Adress iec104ReciveArray[11]=00;//Adress iec104ReciveArray[12]=iecData[6];//IOA iec104ReciveArray[13]=iecData[7];//IOA iec104ReciveArray[14]=0;//IOA iec104ReciveArray[15]=iecData[8];//value [DATA 1] iec104ReciveArray[16]=iecData[9];//value [DATA 1] iec104ReciveArray[17]=iecData[10];//QDS iec104ReciveArray[18]=iecData[11];//IOA iec104ReciveArray[19]=iecData[12];//OA iec104ReciveArray[20]=0;//IOA iec104ReciveArray[21]=iecData[13];//value [DATA 2] iec104ReciveArray[22]=iecData[14];//value [DATA 2] iec104ReciveArray[23]=iecData[15];//IOA QDS MessageLength = iec104ReciveArray[1]+2; client.write(iec104ReciveArray, MessageLength); delay(5); txcnt=txcnt+2; iec104ReciveArray[0]=iec104ReciveArray[0]; iec104ReciveArray[1]=26; iec104ReciveArray[2]=lowByte(txcnt); iec104ReciveArray[3]=highByte(txcnt); iec104ReciveArray[4]=lowByte(rxcnt); iec104ReciveArray[5]=highByte(rxcnt); iec104ReciveArray[6]=13;//type 13 Float iec104ReciveArray[7]=02;//sq iec104ReciveArray[8]=20;//Cause Inrogen iec104ReciveArray[9]=00;//AO iec104ReciveArray[10]=01;//Adress iec104ReciveArray[11]=00;//Adress iec104ReciveArray[12]=iecData[16];//IOA iec104ReciveArray[13]=iecData[17];//IOA iec104ReciveArray[14]=0; iec104ReciveArray[15]=iecData[18];//value [DATA 1] iec104ReciveArray[16]=iecData[19];//value [DATA 1] iec104ReciveArray[17]=iecData[20];//value [DATA 1] iec104ReciveArray[18]=iecData[21];//value [DATA 1] iec104ReciveArray[19]=iecData[22];//IOA QDS iec104ReciveArray[20]=iecData[23];//IOA iec104ReciveArray[21]=iecData[24];//IOA iec104ReciveArray[22]=0;//IOA iec104ReciveArray[23]=iecData[25];//value [DATA 2] iec104ReciveArray[24]=iecData[26];//value [DATA 2] iec104ReciveArray[25]=iecData[27];//value [DATA 2] iec104ReciveArray[26]=iecData[28];//value [DATA 2] iec104ReciveArray[27]=iecData[29];//IOA QDS MessageLength = iec104ReciveArray[1]+2; client.write(iec104ReciveArray, MessageLength); txcnt=txcnt+2; //Завершение общего опроса iec104ReciveArray[0]=iec104ReciveArray[0]; iec104ReciveArray[1]=iec104ReciveArray[1]; iec104ReciveArray[2]=lowByte(txcnt); iec104ReciveArray[3]=highByte(txcnt); iec104ReciveArray[4]=lowByte(rxcnt); iec104ReciveArray[5]=highByte(rxcnt); iec104ReciveArray[6]=100;//type iec104ReciveArray[7]=01;//sq iec104ReciveArray[8]=10;//cause AckTerm iec104ReciveArray[9]=00; iec104ReciveArray[10]=01; iec104ReciveArray[11]=00; iec104ReciveArray[12]=00; iec104ReciveArray[13]=00; iec104ReciveArray[14]=00; iec104ReciveArray[15]=20; MessageLength = iec104ReciveArray[1]+2; client.write(iec104ReciveArray, MessageLength); iec104ReciveArray[6]=00;//сброс для нормальног определения нового входящего пакета break;
После обновления скетча наблюдаем следующий порядок обмена:

Установку соединения, запрос на передачу данных, подтверждение, запрос на общий опрос станции от контролирующей станции, завершение инициализации, запрос на общий опрос станции в направление контролируемой станции, подтверждение общего опроса, пересылка значений всех доступных сигналов на контролирующей станции, завершение общего опроса и неизвестный APCI формата S.
Запрос опроса станции выдается в направлении контролируемой станции:
— если с контролируемой станции получен «КОНЕЦ ИНИЦИАЛИЗАЦИИ» или
— если центральная станция обнаруживает потерю канала (безуспешное повторение запроса канального уровня) и последующее восстановление его.
4. Подготовка и передача данных
APDU блок формата S, состоящий только из APCI предназначен для подтверждения принятого APDU I формата. Для S-формата 7 старших бит служебного поля байта 1 и байт 2 не задействованы, а байт 3 (7 старших бит) и байт 4 определяют текущий номер принятой посылки.

В данном случае блок S указывает на то, что контролирующая (master) станция готова к приему данных в течении определенного времени, не превышающего, тайм-аут t3 определенного на стороне контролирующей (master) станции. То есть контролирующая (master) станция говорит нам «я готова к приему данных!». Далее необходимо позаботиться о том какие данные передавать и откуда их брать.
Что можно передавать? Существует несколько видов информации определённых в МЭК 870-5- 104:
- Контрольная;
- Управляющая;
- Параметры;
- Передача файлов.
Картинка

В данном примере рассматривается передача контрольной информации на примере 1, 11 и 13 функций (одноэлементная, измерение масштабируемое, измерение короткий формат с плавающей запятой). Данные формируются рандомно. Также необходимо учитывать, что у каждого передаваемого сигнала имеется байт качества.

Простой алгоритм определения качества сигнала:
- Если используется замещение действующего сигнала то выставляются флаги BL(блокировка) и SB(замещение);
- Если значение сигнала не изменялось в течении контрольного промежутка времени то выставляется флаг NT(не актуальное);
- Если имеется признак неработоспособности узла или устройства более нижнего уровня (датчик или прочее) то выставляется флаг IV(не достоверное значение).
Скетч
void SetQDS(int currvalue, int i,bool zam)//определение качества сигнала { if (zam==0)//замещение? { if (currvalue==previusValue[i])//значение не изменялось? { previusValue[i]=currvalue; counter[i]+=1; if (counter[i]>=1000) { qds[i]=64;// NT counter[i]=0; } } else { qds[i]=0; counter[i]=0; previusValue[i]=currvalue; } } else { qds[i]=48;// SB, BL } }
Так же необходимо учесть, что для каждого сигнала имеется уникальный идентификатор IOA, в энергетике принято распределять эти адреса следующим образом:
- ТС-начиная с 4096;
- ТИ-начиная с 8192;
- ТУ-начиная с 20480.
Для передачи значения сигналов в массив для отправки используется библиотека EEPROM:
Скетч
void EEPROM_float_write(int addr, float val,int IOA,int number,bool subs) // начальный адрес в EEPROM, значение сигнала, адрес сигнала, порядковый номер измеряемого сигнала, замещение { SetQDS(val,number, subs);//установка качества сигнала byte *x = (byte *)&val;//float -->byte byte *xxx = (byte *)&IOA;//запись адреса IOA for(int jj = 0; jj <2; jj++) { EEPROM.write(addr,xxx[jj]);//сохранение в EEPROM адреса блока данных в 2 байтах addr+=1; } for(byte i = 0; i < 4; i++) //запись формата float в 4 байтах { EEPROM.write(addr, x[i]); //запись формата float в 4 байтах addr+=1; } EEPROM.write(addr, qds[number]);//запись информации о качестве сигнала if (addr == EEPROM.length()) { addr = 0; } }
И так получив APDU подтверждение S или I формата от контролирующей станции можно начать передавать имеющиеся в распоряжении данные, не забывая увеличивать номер передаваемого кадра.

Скетч
#include <eeprom.h> #include <Ethernet.h> #include <eeprom.h> byte mac[] = {0x90, 0xA2, 0xDA, 0x0E, 0x94, 0xB7 }; IPAddress ip(172, 16, 7, 1); IPAddress gateway(172, 16,7, 0); IPAddress subnet(255, 255, 0, 0); EthernetClient client; EthernetServer iec104Server(2404); int TypeQuerry, MessageLength; uint8_t iec104ReciveArray[128]; int counter[6];//порядковый номер сигнала int qds[6];//порядковый номер для определения качества сигнала int previusValue[6];//порядковый номер для определения статуса NT word iecData[256];//буфер для хранения значений сигналов int ASDU;//для определения типа входящего пакета int txcnt, rxcnt;//счетчики переданных и принятых пакетов void setup() { //создание сервера 2404 порт Ethernet.begin(mac, ip, gateway, subnet); Serial.begin(9600); } void EEPROM_float_write(int addr, float val,int IOA,int number,bool zam) // запись в ЕЕПРОМ значения типа Float функция 13 { SetQDS(val,number,zam);//качество сигнала byte *x = (byte *)&val; byte *xxx = (byte *)&IOA; for(int jj = 0; jj <2; jj++)//запись адреса IOA в 2 байта { EEPROM.write(addr,xxx[jj]); addr+=1; } for(byte i = 0; i < 4; i++)//запись формата float в 4 байта { EEPROM.write(addr, x[i]); addr+=1; } EEPROM.write(addr, qds[number]); if (addr == EEPROM.length()) { addr = 0; } } void EEPROM_byte_write(int addr, bool val,int IOA,int number,bool zam) // запись в ЕЕПРОМ значения типа Bool функция 1 { SetQDS(val,number,zam);//качество сигнала byte c=val+qds[number]; byte *x = (byte *)&c; byte *xxxx = (byte *)&IOA; for(int jj = 0; jj <2; jj++) //запись адреса IOA в 2 байта { EEPROM.write(addr,xxxx[jj]); addr+=1; } for(byte i = 0; i < 1; i++) //запись формата bool + качество сигнала в 1 байт { EEPROM.write(addr, x[i]); } if (addr == EEPROM.length()) { addr = 0; } } void EEPROM_int_write(int addr, int val, int IOA,int number,bool zam) // запись в ЕЕПРОМ значения типа Int функция 11 { SetQDS(val,number,zam); //качество сигнала byte *x = (byte *)&val; byte *xx = (byte *)&IOA; for(int jj = 0; jj <2; jj++)//запись адреса IOA в 2 байта { EEPROM.write(addr,xx[jj]); addr+=1; } for(byte i = 0; i < 2; i++)//запись формата int в 2 байтa { EEPROM.write(addr, x[i]); addr+=1; } EEPROM.write(addr, qds[number]); if (addr == EEPROM.length()) { addr = 0; } } //установка качества сигнала void SetQDS(int currvalue, int i,bool zam)//текущее значение, порядковый номер измеряемого сигнала, замещение { if (zam==0)//замещение? { if (currvalue==previusValue[i])//значение не изменялось? { previusValue[i]=currvalue; counter[i]+=1; if (counter[i]>=1000) { qds[i]=64;//установка флага NT counter[i]=0; } } else { qds[i]=0; counter[i]=0; previusValue[i]=currvalue; } } else { qds[i]=48;//установка флагов ��амещение и блокировки } } void loop() { //Тестовые данные для 1,11,13 функций //в формате: (адрес в EEPROM, значение сигнала, IOA адрес, качество сигнала) EEPROM_byte_write(0,0,4096,0,0); EEPROM_byte_write(3,random(0, 2),4097,1,1); EEPROM_int_write(6, 67,8192,2,1); EEPROM_int_write(11, random(10, 20),8193,3,0); EEPROM_float_write(16, random(-1000, 2000),8194,4,1); EEPROM_float_write(23, 78.66f,8195,5,1); client = iec104Server.available(); if(client.available()) { delay(100); int i = 0; while(client.available()) { iec104ReciveArray[i] = client.read();//записываем в буфер приема данные i++; } ASDU=iec104ReciveArray[6];//пакет ASDU? switch (ASDU) { case 100://опрос тсанции TypeQuerry=iec104ReciveArray[2]-word(iec104ReciveArray[3],iec104ReciveArray[2]); rxcnt+=2;//увеличение счетчика принятых пакетов break; case 0: TypeQuerry=iec104ReciveArray[2];//определяем тип посылки break; default : TypeQuerry=iec104ReciveArray[2]; break; } for(byte z = 0; z <64; z++) //чтение из еепром { iecData[z]= EEPROM.read(z); } //Тип принятого APDU switch(TypeQuerry) { case 07://APDU STARTDT rxcnt=0; txcnt=0; iec104ReciveArray[0]=iec104ReciveArray[0];//кадр переменной длины, начинающийся байтом START2 = 68h; iec104ReciveArray[1]=iec104ReciveArray[1];//длина APDU iec104ReciveArray[2]=11;//STARTDT con iec104ReciveArray[3]=0; iec104ReciveArray[4]=0; iec104ReciveArray[5]=0; MessageLength = iec104ReciveArray[1]+2;//длина пакета client.write(iec104ReciveArray, MessageLength);//отправка обратно //Инициализация iec104ReciveArray[0]=iec104ReciveArray[0];//кадр переменной длины, начинающийся байтом START2 = 68h; iec104ReciveArray[1]=14;//длина APDU iec104ReciveArray[2]=0;//тип, TX L iec104ReciveArray[3]=0;//TX H iec104ReciveArray[4]=0;//RX L iec104ReciveArray[5]=0;//RX H iec104ReciveArray[6]=70;//type End of Init iec104ReciveArray[7]=01;//sq iec104ReciveArray[8]=04;//cause Init iec104ReciveArray[9]=00;//AO iec104ReciveArray[10]=01;//Adress iec104ReciveArray[11]=00;//Adress iec104ReciveArray[12]=00;//IOA iec104ReciveArray[13]=00;//IOA iec104ReciveArray[14]=00;//IOA iec104ReciveArray[15]=129;//IOA, COI MessageLength = iec104ReciveArray[1]+2; client.write(iec104ReciveArray, MessageLength); //Общий опрос в направление контролируемой станции iec104ReciveArray[0]=iec104ReciveArray[0]; iec104ReciveArray[1]=14;//длина APDU iec104ReciveArray[2]=2;//TX L iec104ReciveArray[3]=0;//TX H iec104ReciveArray[4]=0;//RX L iec104ReciveArray[5]=0;//RX H iec104ReciveArray[6]=100;// опрос станции iec104ReciveArray[7]=01; iec104ReciveArray[8]=6;//cause Act iec104ReciveArray[9]=00; iec104ReciveArray[10]=01; iec104ReciveArray[11]=00; iec104ReciveArray[12]=00; iec104ReciveArray[13]=00; iec104ReciveArray[14]=00; iec104ReciveArray[15]=20;//IOA, QOI общий MessageLength = iec104ReciveArray[1]+2; client.write(iec104ReciveArray, MessageLength); txcnt=txcnt+02; break; case 00://опрос станции txcnt=txcnt+02; //Подтверждение общего опроса iec104ReciveArray[0]=iec104ReciveArray[0]; iec104ReciveArray[1]=iec104ReciveArray[1]; iec104ReciveArray[2]=lowByte(txcnt);//TX L iec104ReciveArray[3]=highByte(txcnt);//TX H iec104ReciveArray[4]=lowByte(rxcnt);//RX L iec104ReciveArray[5]=highByte(rxcnt);//RX H iec104ReciveArray[6]=100;//опрос станции iec104ReciveArray[7]=01; iec104ReciveArray[8]=7;//cause Actcon iec104ReciveArray[9]=00;//OA iec104ReciveArray[10]=01;//Addr iec104ReciveArray[11]=00;//Addr iec104ReciveArray[12]=00;//IOA iec104ReciveArray[13]=00;//IOA iec104ReciveArray[14]=00;//IOA iec104ReciveArray[15]=20;//IOA, QOI MessageLength = iec104ReciveArray[1]+2; delay(100); client.write(iec104ReciveArray, MessageLength); txcnt=txcnt+2;//увеличение счетчика переданных пакетов //актуальные состояния информации контролирующей станции в ответ на общий опрос iec104ReciveArray[0]=iec104ReciveArray[0]; iec104ReciveArray[1]=14;//длина APDU=APCI(4)+ ASDU(10) iec104ReciveArray[2]=lowByte(txcnt); iec104ReciveArray[3]=highByte(txcnt); iec104ReciveArray[4]=lowByte(rxcnt); iec104ReciveArray[5]=highByte(rxcnt); iec104ReciveArray[6]=1;//type 1 iec104ReciveArray[7]=01;//sq iec104ReciveArray[8]=20;//Inrogen iec104ReciveArray[9]=00;//AO iec104ReciveArray[10]=01;//Adress iec104ReciveArray[11]=00;//Adress iec104ReciveArray[12]=iecData[0];//IOA iec104ReciveArray[13]=iecData[1];//IOA iec104ReciveArray[14]=0;//IOA iec104ReciveArray[15]=iecData[2];//value [DATA 1] MessageLength = iec104ReciveArray[1]+2; client.write(iec104ReciveArray, MessageLength); txcnt=txcnt+2; iec104ReciveArray[0]=iec104ReciveArray[0]; iec104ReciveArray[1]=14; iec104ReciveArray[2]=lowByte(txcnt); iec104ReciveArray[3]=highByte(txcnt); iec104ReciveArray[4]=lowByte(rxcnt); iec104ReciveArray[5]=highByte(rxcnt); iec104ReciveArray[6]=1; iec104ReciveArray[7]=01; iec104ReciveArray[8]=20; iec104ReciveArray[9]=00; iec104ReciveArray[10]=01; iec104ReciveArray[11]=00; iec104ReciveArray[12]=iecData[3]; iec104ReciveArray[13]=iecData[4]; iec104ReciveArray[14]=0; iec104ReciveArray[15]=iecData[5]; MessageLength = iec104ReciveArray[1]+2; client.write(iec104ReciveArray, MessageLength); delay(5); txcnt=txcnt+2; iec104ReciveArray[0]=iec104ReciveArray[0]; iec104ReciveArray[1]=22; iec104ReciveArray[2]=lowByte(txcnt); iec104ReciveArray[3]=highByte(txcnt); iec104ReciveArray[4]=lowByte(rxcnt); iec104ReciveArray[5]=highByte(rxcnt); iec104ReciveArray[6]=11;//type 11 iec104ReciveArray[7]=02;//sq iec104ReciveArray[8]=20;//cause iec104ReciveArray[9]=00;//AO iec104ReciveArray[10]=01;//Adress iec104ReciveArray[11]=00;//Adress iec104ReciveArray[12]=iecData[6];//IOA iec104ReciveArray[13]=iecData[7];//IOA iec104ReciveArray[14]=0;//IOA iec104ReciveArray[15]=iecData[8];//value [DATA 1] iec104ReciveArray[16]=iecData[9];//value [DATA 1] iec104ReciveArray[17]=iecData[10];//QDS iec104ReciveArray[18]=iecData[11];//IOA iec104ReciveArray[19]=iecData[12];//OA iec104ReciveArray[20]=0;//IOA iec104ReciveArray[21]=iecData[13];//value [DATA 2] iec104ReciveArray[22]=iecData[14];//value [DATA 2] iec104ReciveArray[23]=iecData[15];//IOA QDS MessageLength = iec104ReciveArray[1]+2; client.write(iec104ReciveArray, MessageLength); delay(5); txcnt=txcnt+2; iec104ReciveArray[0]=iec104ReciveArray[0]; iec104ReciveArray[1]=26; iec104ReciveArray[2]=lowByte(txcnt); iec104ReciveArray[3]=highByte(txcnt); iec104ReciveArray[4]=lowByte(rxcnt); iec104ReciveArray[5]=highByte(rxcnt); iec104ReciveArray[6]=13;//type 13 iec104ReciveArray[7]=02;//sq iec104ReciveArray[8]=20;//cause iec104ReciveArray[9]=00;//AO iec104ReciveArray[10]=01;//Adress iec104ReciveArray[11]=00;//Adress iec104ReciveArray[12]=iecData[16];//IOA iec104ReciveArray[13]=iecData[17];//IOA iec104ReciveArray[14]=0; iec104ReciveArray[15]=iecData[18];//value [DATA 1] iec104ReciveArray[16]=iecData[19];//value [DATA 1] iec104ReciveArray[17]=iecData[20];//value [DATA 1] iec104ReciveArray[18]=iecData[21];//value [DATA 1] iec104ReciveArray[19]=iecData[22];//IOA QDS iec104ReciveArray[20]=iecData[23];//IOA iec104ReciveArray[21]=iecData[24];//IOA iec104ReciveArray[22]=0;//IOA iec104ReciveArray[23]=iecData[25];//value [DATA 2] iec104ReciveArray[24]=iecData[26];//value [DATA 2] iec104ReciveArray[25]=iecData[27];//value [DATA 2] iec104ReciveArray[26]=iecData[28];//value [DATA 2] iec104ReciveArray[27]=iecData[29];//IOA QDS MessageLength = iec104ReciveArray[1]+2; client.write(iec104ReciveArray, MessageLength); txcnt=txcnt+2; //Завершение общего опроса iec104ReciveArray[0]=iec104ReciveArray[0]; iec104ReciveArray[1]=iec104ReciveArray[1]; iec104ReciveArray[2]=lowByte(txcnt); iec104ReciveArray[3]=highByte(txcnt); iec104ReciveArray[4]=lowByte(rxcnt); iec104ReciveArray[5]=highByte(rxcnt); iec104ReciveArray[6]=100;//type iec104ReciveArray[7]=01;//sq iec104ReciveArray[8]=10;//cause AckTerm iec104ReciveArray[9]=00; iec104ReciveArray[10]=01; iec104ReciveArray[11]=00; iec104ReciveArray[12]=00; iec104ReciveArray[13]=00; iec104ReciveArray[14]=00; iec104ReciveArray[15]=20; MessageLength = iec104ReciveArray[1]+2; client.write(iec104ReciveArray, MessageLength); iec104ReciveArray[6]=00;//сброс для нормальног определения нового входящего пакета break; //APDU S case 01: txcnt=word(iec104ReciveArray[5],iec104ReciveArray[4]); iec104ReciveArray[0]=iec104ReciveArray[0]; iec104ReciveArray[1]=14; iec104ReciveArray[2]=lowByte(txcnt); iec104ReciveArray[3]=highByte(txcnt); iec104ReciveArray[4]=lowByte(rxcnt); iec104ReciveArray[5]=highByte(rxcnt); iec104ReciveArray[6]=1;//type 1 iec104ReciveArray[7]=01;//sq iec104ReciveArray[8]=01;//cause Cycl iec104ReciveArray[9]=00;//AO iec104ReciveArray[10]=01;//Adress iec104ReciveArray[11]=00;//Adress iec104ReciveArray[12]=iecData[0];//IOA iec104ReciveArray[13]=iecData[1];//IOA iec104ReciveArray[14]=0;//IOA iec104ReciveArray[15]=iecData[2];//value [DATA 1] MessageLength = iec104ReciveArray[1]+2; client.write(iec104ReciveArray, MessageLength); delay(5); txcnt=txcnt+2; iec104ReciveArray[0]=iec104ReciveArray[0]; iec104ReciveArray[1]=14; iec104ReciveArray[2]=lowByte(txcnt); iec104ReciveArray[3]=highByte(txcnt); iec104ReciveArray[4]=lowByte(rxcnt); iec104ReciveArray[5]=highByte(rxcnt); iec104ReciveArray[6]=1;//type 1 Bool iec104ReciveArray[7]=01;//sq iec104ReciveArray[8]=01;//cause Cycl iec104ReciveArray[9]=00;//AO iec104ReciveArray[10]=01;//Adress iec104ReciveArray[11]=00;//Adress iec104ReciveArray[12]=iecData[3];//IOA iec104ReciveArray[13]=iecData[4];//IOA iec104ReciveArray[14]=0;//IOA iec104ReciveArray[15]=iecData[5];//value [DATA 1] MessageLength = iec104ReciveArray[1]+2; client.write(iec104ReciveArray, MessageLength); delay(5); txcnt=txcnt+2; iec104ReciveArray[0]=iec104ReciveArray[0]; iec104ReciveArray[1]=22; iec104ReciveArray[2]=lowByte(txcnt); iec104ReciveArray[3]=highByte(txcnt); iec104ReciveArray[4]=lowByte(rxcnt); iec104ReciveArray[5]=highByte(rxcnt); iec104ReciveArray[6]=11;//type 11 Int iec104ReciveArray[7]=02;//sq iec104ReciveArray[8]=01;//cause Cycl iec104ReciveArray[9]=00;//AO iec104ReciveArray[10]=01;//Adress iec104ReciveArray[11]=00;//Adress iec104ReciveArray[12]=iecData[6];//IOA iec104ReciveArray[13]=iecData[7];//IOA iec104ReciveArray[14]=0;//IOA iec104ReciveArray[15]=iecData[8];//value [DATA 1] iec104ReciveArray[16]=iecData[9];//value [DATA 1] iec104ReciveArray[17]=iecData[10];//QDS iec104ReciveArray[18]=iecData[11];//IOA iec104ReciveArray[19]=iecData[12];//OA iec104ReciveArray[20]=0;//IOA iec104ReciveArray[21]=iecData[13];//value [DATA 2] iec104ReciveArray[22]=iecData[14];//value [DATA 2] iec104ReciveArray[23]=iecData[15];//IOA QDS MessageLength = iec104ReciveArray[1]+2; client.write(iec104ReciveArray, MessageLength); delay(5); txcnt=txcnt+2; iec104ReciveArray[0]=iec104ReciveArray[0]; iec104ReciveArray[1]=26; iec104ReciveArray[2]=lowByte(txcnt); iec104ReciveArray[3]=highByte(txcnt); iec104ReciveArray[4]=lowByte(rxcnt); iec104ReciveArray[5]=highByte(rxcnt); iec104ReciveArray[6]=13;//type 13 Float iec104ReciveArray[7]=02;//sq iec104ReciveArray[8]=01;//cause Cycl iec104ReciveArray[9]=00;//AO iec104ReciveArray[10]=01;//Adress iec104ReciveArray[11]=00;//Adress iec104ReciveArray[12]=iecData[16];//IOA iec104ReciveArray[13]=iecData[17];//IOA iec104ReciveArray[14]=0; iec104ReciveArray[15]=iecData[18];//value [DATA 1] iec104ReciveArray[16]=iecData[19];//value [DATA 1] iec104ReciveArray[17]=iecData[20];//value [DATA 1] iec104ReciveArray[18]=iecData[21];//value [DATA 1] iec104ReciveArray[19]=iecData[22];//IOA QDS iec104ReciveArray[20]=iecData[23];//IOA iec104ReciveArray[21]=iecData[24];//IOA iec104ReciveArray[22]=0;//IOA iec104ReciveArray[23]=iecData[25];//value [DATA 2] iec104ReciveArray[24]=iecData[26];//value [DATA 2] iec104ReciveArray[25]=iecData[27];//value [DATA 2] iec104ReciveArray[26]=iecData[28];//value [DATA 2] iec104ReciveArray[27]=iecData[29];//IOA QDS MessageLength = iec104ReciveArray[1]+2; client.write(iec104ReciveArray, MessageLength); txcnt=txcnt; break; case 67: //TESTFR iec104ReciveArray[0]=iec104ReciveArray[0]; iec104ReciveArray[1]=iec104ReciveArray[1]; iec104ReciveArray[2] =131; //TESTFR con iec104ReciveArray[3] =0; iec104ReciveArray[4] =0; iec104ReciveArray[5] =0; MessageLength = iec104ReciveArray[1]+2; delay(10); client.write(iec104ReciveArray, MessageLength); iec104ReciveArray[0]=iec104ReciveArray[0]; iec104ReciveArray[1]=26;//длина APDU iec104ReciveArray[2]=lowByte(txcnt); iec104ReciveArray[3]=highByte(txcnt); iec104ReciveArray[4]=lowByte(rxcnt); iec104ReciveArray[5]=highByte(rxcnt); iec104ReciveArray[6]=13;//type 13 iec104ReciveArray[7]=02;//sq iec104ReciveArray[8]=03;//spont iec104ReciveArray[9]=00;//AO iec104ReciveArray[10]=01;//Adress iec104ReciveArray[11]=00;//Adress iec104ReciveArray[12]=iecData[16];//IOA iec104ReciveArray[13]=iecData[17];//IOA iec104ReciveArray[14]=0; iec104ReciveArray[15]=iecData[18];//value [DATA 1] iec104ReciveArray[16]=iecData[19];//value [DATA 1] iec104ReciveArray[17]=iecData[20];//value [DATA 1] iec104ReciveArray[18]=iecData[21];//value [DATA 1] iec104ReciveArray[19]=iecData[22];//IOA QDS iec104ReciveArray[20]=iecData[23];//IOA iec104ReciveArray[21]=iecData[24];//IOA iec104ReciveArray[22]=0;//IOA iec104ReciveArray[23]=iecData[25]; iec104ReciveArray[24]=iecData[26]; iec104ReciveArray[25]=iecData[27]; iec104ReciveArray[26]=iecData[28]; iec104ReciveArray[27]=iecData[29];//IOA QDS MessageLength = iec104ReciveArray[1]+2; client.write(iec104ReciveArray, MessageLength); break; } } }
Загрузив скетч в Wireshark наблюдаем, что наконец-то началась передача данных.
Далее привожу описание структуры ASDU <100>M_SP_NA_1 одноэлементная индикация.

TypeId — вид информации.
SQ — классификатора переменной структуры.
Предусматриваются две структуры блоков данных:
1. Блок, содержащий i объектов информации, каждый из которых содержит по одному элементу информации (или по одной комбинации элементов); старший бит классификатора переменной структуры SQ (single/sequence) равен 0, остальные 7 битов задают число i.
2. Блок, содержащий один объект информации, который содержит j элементов либо одинаковых комбинаций элементов информации; старший бит (27 = 80h) классификатора SQ равен 1, остальные 7 битов задают число j.
Картинка

CauseTx — причина передачи.
Картинка

Addr — адрес слэйва (указывается при конфигурировании мастера).
IOA — адрес объекта информации, по этому адресу контролирующая станция будет привязывать свой тэг
SIQ — показатель качества передаваемого сигнала.
Структура ASDU блока функции <11>M_ME_NB_1:
Wireshark

В ответ на полученные данные master будет отправлять блоки формата S и процесс зациклится до тех пор пока контролируемое(slave) устройство не перестанет передавать кадры.
5. Процедуры тестирования
Процедуры тестирования применяются с целью контроля за работоспособностью транспортных соединений. Процедура выполняется независимо от «активности» IP-соединения, если в течение контрольного времени t3 не было принято ни одного кадра (I, U, S). Время t3 является предметом согласования и называется «Тайм-аут для посылки блоков тестирования в случае долгого простоя». Процедура тестирования реализуется путем посылки тестового APDU (TESTFR =act), которое подтверждается принимаемой станцией с помощью APDU (TESTFR =con).
Картинка

Wireshark

Если от контролирующей (master) станции придет APDU у которого в байте отвечающего за тип APDU значение равно шестидесяти сети (TESTFR) это говорит о том, что в течении времени t3 от контролируемой станции не было принято ни одного кадра (I, U, S), и если в течении времени t1 не ответить подтверждением то соединение будет разорвано.
Скетч
case 67: iec104ReciveArray[0]=iec104ReciveArray[0];//кадр переменной длины, начинающийся байтом START2 = 68h; iec104ReciveArray[1]=iec104ReciveArray[1];//длина APDU LENGHT iec104ReciveArray[2] = 131; //TESTDT con iec104ReciveArray[3] =0; iec104ReciveArray[4] =0; iec104ReciveArray[5] =0; MessageLength = iec104ReciveArray[1]+2;//определение длины сообщения + 2 байта Start68H and Lenght delay(10); client.write(iec104ReciveArray, MessageLength);
Wireshark

На этом всё, если кому-нибудь интересно то в следующей статье я рассмотрю протокол МЭК 670-5-104 со стороны контролирующей (master) станции на примере Arduino.
