Как я писал библиотеку под МЭК 870-5-104 на Arduino при помощи Wireshark

в 7:47, , рубрики: arduino, IEC 670-5-104, scada, wireshark, программирование микроконтроллеров, Сетевые технологии

В этой статье я хотел бы рассказать о своем знакомстве с протоком передачи данных МЭК 870-5-104 со стороны контролируемого (slave) устройства путем написания простой библиотеки на Arduino.

Что такое МЭК 870-5-104 это и где применяется?

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

Стэк протокола МЭК 670-5-104:

Как я писал библиотеку под МЭК 870-5-104 на Arduino при помощи Wireshark - 1

Используемые материалы

  • плата Arduino UNO;
  • Ethernet shield (HR911105a);
  • в роли мастера МЭК 60870-5-104 будет выступать MicroScada от ABB;
  • Wireshark для анализа трафика.

Краткое описание этапов работы

  1. Установка TCP/IP соединение по 2404 порту;
  2. Подтверждение запроса на передачу данных (STARTDT act/con);
  3. Запрос на общий опрос станции;
  4. Подготовка и передача данных контролирующей (master) станции;
  5. Процедуры тестирования.

Подготовка

  • Подключена плата Arduino к ПК;
  • Настроен соответствующим образом сетевой интерфейс;
  • Настроена контролирующая (master) станция (добавлена 104 линия и добавлено контролируемое (slave) устройство).

Как я писал библиотеку под МЭК 870-5-104 на Arduino при помощи Wireshark - 2

Термины и сокращения

APCI — Управляющая Информация Прикладного Уровня может применяться как самостоятельный управляющий кадр (кадр U или кадр S).
ASDU — Блоки данных прикладного уровня, состоит из идентификатора блока данных и одного или более объектов информации, каждый из которых включает в себя один или более однородных элементов информации (либо комбинаций элементов информации).
APDU — Протокольный блок данных прикладного уровня.
ТС — телесигнализация.
ТИ — телеизмерения.
ТУ — телеуправление.

1. Установка TCP/IP соединение порт 2404

Контролирующая (master) станция инициализирует установку TCP соединения путем посылки TCP пакета с флагом (SYS). Соединение считается установленным, если в течение контрольного времени (t0) контролируемая станция (slave) выдала на свой уровень TCP/IP подтверждение «активного открытия» (SYS ACK). Контрольное время t0 называется «Тайм-аут установки соединения». Таймер t0 определяет, когда открытие отменяется и не определяет начало новой попытки соединения.

Как я писал библиотеку под МЭК 870-5-104 на Arduino при помощи Wireshark - 3

Взаимодействие с транспортным уровнем выполняет стандартная библиотека для плат 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();//подсоединение клиентов 
}

Если загрузить этот скетч то будет происходить следующее:

Как я писал библиотеку под МЭК 870-5-104 на Arduino при помощи Wireshark - 4

Установка соединения, далее приходит пока неизвестный для 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- соединения.

Картинка

Как я писал библиотеку под МЭК 870-5-104 на Arduino при помощи Wireshark - 5

Таким образом далее необходимо считать байты полученные от контролирующей (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

Как я писал библиотеку под МЭК 870-5-104 на Arduino при помощи Wireshark - 6

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

Как я писал библиотеку под МЭК 870-5-104 на Arduino при помощи Wireshark - 7

Вкратце можно сказать, что блок APCI определяет тип блока APDU и его длину. APCI состоит из следующих шести байтов:

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

Как я писал библиотеку под МЭК 870-5-104 на Arduino при помощи Wireshark - 8

Исходя из вышеописанного, перед тем как ответить, не мешало бы определить какой тип 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;
  }
 }
}

После обновления скетча наблюдаем следующий порядок обмена:

Как я писал библиотеку под МЭК 870-5-104 на Arduino при помощи Wireshark - 9

Установку соединения, запрос на передачу данных, подтверждение запроса и еще один новый пока неизвестный APDU формата I типа 1 C_IC_NA Act.

3. Запрос на общий опрос станции

При инициализации оборудования формируется общий опрос станции кадр c идентификатором <100> C_IC_NA_1.

Wireshark

Как я писал библиотеку под МЭК 870-5-104 на Arduino при помощи Wireshark - 10

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

Как я писал библиотеку под МЭК 870-5-104 на Arduino при помощи Wireshark - 11

Рассмотрим более подробно полученный 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;
}}}

После обновления скетча наблюдаем следующий порядок обмена:

Как я писал библиотеку под МЭК 870-5-104 на Arduino при помощи Wireshark - 12

Установку соединения, запрос на передачу данных, подтверждение, общий опрос станции, подтверждение запроса и пока что неизвестные APDU формата S и спустя некоторое время U TESTFR.

4. Подготовка и передача данных

APDU блок формата S, состоящий только из APCI предназначен для подтверждения принятого APDU I формата. Для S-формата 7 старших бит служебного поля байта 1 и байт 2 не задействованы, а байт 3 (7 старших бит) и байт 4 определяют текущий номер принятой посылки.

Как я писал библиотеку под МЭК 870-5-104 на Arduino при помощи Wireshark - 13

В данном случае блок S указывает на то, что контролирующая (master) станция готова к приему данных в течении определенного времени, не превышающего, тайм-аут t3 определенного на стороне контролирующей (master) станции. То есть контролирующая (master) станция говорит нам «я готова к приему данных!». Далее необходимо позаботиться о том какие данные передавать и откуда их брать.

Что можно передавать? Существует несколько видов информации определённых в МЭК 870-5- 104:

  • Контрольная;
  • Управляющая;
  • Параметры;
  • Передача файлов.

Картинка

Как я писал библиотеку под МЭК 870-5-104 на Arduino при помощи Wireshark - 14

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

Как я писал библиотеку под МЭК 870-5-104 на Arduino при помощи Wireshark - 15

Простой алгоритм определения качества сигнала:

  • Если используется замещение действующего сигнала то выставляются флаги 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 можно начать передавать имеющиеся у нас в распоряжении данные, не забывая увеличивать номер передаваемого кадра.

Как я писал библиотеку под МЭК 870-5-104 на Arduino при помощи Wireshark - 16

Скетч

#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 наблюдаем, что наконец-то началась передача данных.

Как я писал библиотеку под МЭК 870-5-104 на Arduino при помощи Wireshark - 17

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

Как я писал библиотеку под МЭК 870-5-104 на Arduino при помощи Wireshark - 18

TypeId — вид информации.
SQ — классификатора переменной структуры.

Предусматриваются две структуры блоков данных:

1. Блок, содержащий i объектов информации, каждый из которых содержит по одному элементу информации (или по одной комбинации элементов); старший бит классификатора переменной структуры SQ (single/sequence) равен 0, остальные 7 битов задают число i.

2. Блок, содержащий один объект информации, который содержит j элементов либо одинаковых комбинаций элементов информации; старший бит (27 = 80h) классификатора SQ равен 1, остальные 7 битов задают число j.

Картинка

Как я писал библиотеку под МЭК 870-5-104 на Arduino при помощи Wireshark - 19

CauseTx — причина передачи.

Картинка

Как я писал библиотеку под МЭК 870-5-104 на Arduino при помощи Wireshark - 20

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

Структура ASDU блока функции <11>M_ME_NB_1:

Wireshark

Как я писал библиотеку под МЭК 870-5-104 на Arduino при помощи Wireshark - 21

В ответ на полученные данные master будет отправлять блоки формата S и процесс зациклится до тех пор пока контролируемое(slave) устройство не перестанет передавать кадры.

5. Процедуры тестирования

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

Картинка

Как я писал библиотеку под МЭК 870-5-104 на Arduino при помощи Wireshark - 22

Wireshark

Как я писал библиотеку под МЭК 870-5-104 на Arduino при помощи Wireshark - 23

Если от контролирующей (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

Как я писал библиотеку под МЭК 870-5-104 на Arduino при помощи Wireshark - 24

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

Автор: maxsic1985

Источник


  1. Yongki:

    how to set data for control ?

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js