В этой статье я хотел бы рассказать о своем знакомстве с протоком передачи данных МЭК 870-5-104 со стороны контролируемого (slave) устройства путем написания простой библиотеки на 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++;
}
Прочитав данные необходимо разобрать их и сформировать ответ.
Вот как выглядит посылка содержащая блок 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 байтом, сохраню его в целочисленную переменную. Из рисунка выше видно, что тип 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 идентификатором <100> C_IC_NA_1.
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. Для отправки подтверждения необходимо записать в байт указывающий на причину передачи (CauseTX) значение равное 7. Для оправки завершения активации необходимо записать в байт указывающий на причину передачи (CauseTX) значение равное 10.
#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;
case 00://команда опроса значение указателя опроса QOI <20> - опрос станции (общий)
delay(100);
//подтверждение активации
iec104ReciveArray[0]=iec104ReciveArray[0];// START2 = 68h;
iec104ReciveArray[1]=iec104ReciveArray[1];//длина APDU
iec104ReciveArray[2]=iec104ReciveArray[4];//TX
iec104ReciveArray[3]=iec104ReciveArray[5];//TX
iec104ReciveArray[4]=iec104ReciveArray[2];//RX
iec104ReciveArray[5]=iec104ReciveArray[3];//RX
iec104ReciveArray[6]=100;//тип ASDU
iec104ReciveArray[7]=01;//SQ
iec104ReciveArray[8]=07;//причина передачи
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]=00;// QOI
MessageLength = iec104ReciveArray[1]+2;//16 bytes =APDU LENGHT+2
client.write(iec104ReciveArray, MessageLength);
delay(100);
//завершение активации
iec104ReciveArray[0]=iec104ReciveArray[0];// START2 = 68h;
iec104ReciveArray[1]=iec104ReciveArray[1];//длина APDU
iec104ReciveArray[2]=iec104ReciveArray[4];//TX
iec104ReciveArray[3]=iec104ReciveArray[5];//TX
iec104ReciveArray[4]=iec104ReciveArray[2];//RX
iec104ReciveArray[5]=iec104ReciveArray[3];//RX
iec104ReciveArray[6]=100;//тип ASDU
iec104ReciveArray[7]=01;//SQ
iec104ReciveArray[8]=10;//причина передачи
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]=00;// QOI
MessageLength = iec104ReciveArray[1]+2;//16 bytes
client.write(iec104ReciveArray, MessageLength);
break;
}}}
После обновления скетча наблюдаем следующий порядок обмена:
Установку соединения, запрос на передачу данных, подтверждение, общий опрос станции, подтверждение запроса и пока что неизвестные APDU формата S и спустя некоторое время U TESTFR.
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 можно начать передавать имеющиеся у нас в распоряжении данные, не забывая увеличивать номер передаваемого кадра.
#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];
word iecData[256];
int txcnt;
void setup()
{
Ethernet.begin(mac, ip, gateway, subnet);
}
void EEPROM_float_write(int addr, float val,int IOA,int number,bool zam)
{
SetQDS(val,number,zam);
byte *x = (byte *)&val;
byte *xxx = (byte *)&IOA;
for(int jj = 0; jj <2; jj++)
{
EEPROM.write(addr,xxx[jj]);
addr+=1;
}
for(byte i = 0; i < 4; i++)
{
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)
{
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
{
EEPROM.write(addr,xxxx[jj]);
addr+=1;
}
for(byte i = 0; i < 1; i++) //Data
{
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)
{
SetQDS(val,number,zam);
byte *x = (byte *)&val;
byte *xx = (byte *)&IOA;
for(int jj = 0; jj <2; jj++)
{
EEPROM.write(addr,xx[jj]);
addr+=1;
}
for(byte i = 0; i < 2; i++)
{
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;
counter[i]=0;
}
}
else
{
qds[i]=0;
counter[i]=0;
previusValue[i]=currvalue;
}
}
else
{
qds[i]=48;
}
}
void loop()
{
EEPROM_byte_write(0,0,4096,0,0);
EEPROM_byte_write(3,random(0, 2),4097,1,1);//bl
EEPROM_int_write(6, 67,8192,2,1);//float data
EEPROM_int_write(11, random(10, 20),8193,3,0);// int data +5
EEPROM_float_write(16, random(-1000, 2000),8194,4,1);//float data+7
EEPROM_float_write(23, 78.66f,8195,5,1);//float data+7
client = iec104Server.available();
if(client.available())
{
delay(100);
int i = 0;
while(client.available())
{
iec104ReciveArray[i] = client.read();
i++;
}
TypeQuerry= iec104ReciveArray[2];//определяем тип посылки
switch(TypeQuerry)
{
case 07:
iec104ReciveArray[0]=iec104ReciveArray[0];//кадр переменной длины, начинающийся байтом START2 = 68h;
iec104ReciveArray[1]=iec104ReciveArray[1];//длина APDU LENGHT
iec104ReciveArray[2] = iec104ReciveArray[2]+4; //TYPE
iec104ReciveArray[3]=0;
iec104ReciveArray[4]=0;
iec104ReciveArray[5]=0;
MessageLength = iec104ReciveArray[1]+2;//определение длины сообщения + 2 байта Start and Lenght
delay(100);
client.write(iec104ReciveArray, MessageLength);
break;
case 00:
delay(100);
iec104ReciveArray[0]=iec104ReciveArray[0];//кадр переменной длины, начинающийся байтом START2 = 68h;
iec104ReciveArray[1]=iec104ReciveArray[1];//длина
iec104ReciveArray[2]=iec104ReciveArray[4];//тип, TX H
iec104ReciveArray[3]=iec104ReciveArray[5];//TX L
iec104ReciveArray[4]=iec104ReciveArray[2];//RX H
iec104ReciveArray[5]=iec104ReciveArray[3];//RX L
iec104ReciveArray[6]=100;//type
iec104ReciveArray[7]=01;//sq
iec104ReciveArray[8]=07;//cause con
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]=00;//IOA, QOI
MessageLength = iec104ReciveArray[1]+2;//16 bytes =APDU LENGHT+2 BAIT
client.write(iec104ReciveArray, MessageLength);
delay(100);
iec104ReciveArray[0]=iec104ReciveArray[0];//кадр переменной длины, начинающийся байтом START2 = 68h;
iec104ReciveArray[1]=iec104ReciveArray[1];//длина
iec104ReciveArray[2]=02;//тип, TX H
iec104ReciveArray[3]=iec104ReciveArray[5];//TX L
iec104ReciveArray[4]=iec104ReciveArray[2];//RX H
iec104ReciveArray[5]=iec104ReciveArray[3];//RX L
iec104ReciveArray[6]=100;//type
iec104ReciveArray[7]=01;//sq
iec104ReciveArray[8]=10;//cause con
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]=20;//IOA, QOI
MessageLength = iec104ReciveArray[1]+2;//16 bytes
client.write(iec104ReciveArray, MessageLength);
break;
case 01:
txcnt=word(iec104ReciveArray[5],iec104ReciveArray[4]);
iec104ReciveArray[0]=iec104ReciveArray[0];//кадр переменной длины, начинающийся байтом START2 = 68h;
iec104ReciveArray[1]=14;//длина APDU=4 APCI+ (6 ASDU + 8 DATA*2)
iec104ReciveArray[2]=lowByte(txcnt);//TX
iec104ReciveArray[3]=highByte(txcnt);// iec104ReciveArray[3];//TX
iec104ReciveArray[4]=0;//RX
iec104ReciveArray[5]=0;//RX
iec104ReciveArray[6]=1;//type 1
iec104ReciveArray[7]=01;//sq
iec104ReciveArray[8]=01;//cause
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];//lowByte(iecData);//value [DATA 1]
MessageLength = iec104ReciveArray[1]+2;
client.write(iec104ReciveArray, MessageLength);
delay(5);
txcnt=txcnt+2;
iec104ReciveArray[0]=iec104ReciveArray[0];//кадр переменной длины, начинающийся байтом START2 = 68h;
iec104ReciveArray[1]=14;//длина APDU=4 APCI+ (6 ASDU + 8 DATA*2)
iec104ReciveArray[2]=lowByte(txcnt);//TX
iec104ReciveArray[3]=highByte(txcnt);// iec104ReciveArray[3];//TX
iec104ReciveArray[4]=0;//RX
iec104ReciveArray[5]=0;//RX
iec104ReciveArray[6]=1;//type 13
iec104ReciveArray[7]=01;//sq
iec104ReciveArray[8]=01;//cause
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];//iecData[11];
MessageLength = iec104ReciveArray[1]+2;
client.write(iec104ReciveArray, MessageLength);
delay(5);
txcnt=txcnt+2;
iec104ReciveArray[0]=iec104ReciveArray[0];// START2 = 68h;
iec104ReciveArray[1]=22;//длина APDU=4 APCI+ (6 ASDU + 8 DATA*2)
iec104ReciveArray[2]=lowByte(txcnt);
iec104ReciveArray[3]=highByte(txcnt);
iec104ReciveArray[4]=0;//RX
iec104ReciveArray[5]=0;//RX
iec104ReciveArray[6]=11;//type 11
iec104ReciveArray[7]=02;//sq
iec104ReciveArray[8]=01;//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 2]
iec104ReciveArray[16]=iecData[9];//value [DATA 2]
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]=0;
iec104ReciveArray[5]=0;
iec104ReciveArray[6]=13;
iec104ReciveArray[7]=02;
iec104ReciveArray[8]=01;
iec104ReciveArray[9]=00;
iec104ReciveArray[10]=01;
iec104ReciveArray[11]=00;
iec104ReciveArray[12]=iecData[16];
iec104ReciveArray[13]=iecData[17];
iec104ReciveArray[14]=0;
iec104ReciveArray[15]=iecData[18];
iec104ReciveArray[16]=iecData[19];
iec104ReciveArray[17]=iecData[20];
iec104ReciveArray[18]=iecData[21];
iec104ReciveArray[19]=iecData[22];
iec104ReciveArray[20]=iecData[23];
iec104ReciveArray[21]=iecData[24];
iec104ReciveArray[22]=0;
iec104ReciveArray[23]=iecData[25];
iec104ReciveArray[24]=iecData[26];
iec104ReciveArray[25]=iecData[27];
iec104ReciveArray[26]=iecData[28];
iec104ReciveArray[27]=iecData[29];
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:
В ответ на полученные данные master будет отправлять блоки формата S и процесс зациклится до тех пор пока контролируемое(slave) устройство не перестанет передавать кадры.
5. Процедуры тестирования
Процедуры тестирования применяются с целью контроля за работоспособностью транспортных соединений. Процедура выполняется независимо от «активности» IP-соединения, если в течение контрольного времени t3 не было принято ни одного кадра (I, U, S). Время t3 является предметом согласования и называется «Тайм-аут для посылки блоков тестирования в случае долгого простоя». Процедура тестирования реализуется путем посылки тестового APDU (TESTFR =act), которое подтверждается принимаемой станцией с помощью APDU (TESTFR =con).
Если от контролирующей (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);
На этом всё, если кому-нибудь интересно то в следующей статье я рассмотрю протокол МЭК 670-5-104 со стороны контролирующей (master) станции на примере Arduino.
Автор: maxsic1985
how to set data for control ?