As I wrote the library under IEC 870-5-104 on Arduino using Wireshark
What is IEC 870-5-104 and where is it used?
IEC 60870-5-104 is a telemechanics protocol designed to transmit TM signals to ASTU, which regulates the use of network access via TCP / IP. It is most often used in the energy sector for information exchange between energy systems, as well as for receiving data from measuring transducers (voltmeters, electricity meters, etc.).
Protocol stack IEC 670-5-104:

Materials used
- Arduino UNO Board
- Ethernet shield (HR911105a);
- Microscada from ABB will act as the master of IEC 60870-5-104;
- Wireshark for traffic analysis.
A brief description of the stages of work
- Install TCP / IP connection on port 2404;
- Confirmation of a request for data transfer (STARTDT act / con);
- Request for a general survey of the station;
- Preparation and transmission of data to the master station;
- Testing Procedures
Training
- Arduino board connected to PC;
- The network interface is configured accordingly;
- A master station has been configured (104 lines have been added and a slave device has been added).

Terms and abbreviations
APCI - Application Level Control Information can be used as a standalone control frame (U frame or S frame).
ASDU - Application-level data blocks, consists of a data block identifier and one or more information objects, each of which includes one or more homogeneous information elements (or combinations of information elements).
APDU - Protocol data unit of the application layer.
TS - tele-alarm.
TI - telemetry.
TU - telecontrol.
1. Establish TCP / IP connection port 2404
A master station initiates a TCP connection by sending a TCP packet with a flag (SYS). A connection is considered established if, during the monitoring time (t0), the monitored station (slave) issued to its TCP / IP level an “active open” confirmation (SYS ACK). The control time t0 is called “Connection setup timeout”. The timer t0 determines when the opening is canceled and does not determine the start of a new connection attempt.

Interaction with the transport layer is performed by the standard library for Arduino Ethernet.h boards. That is, first of all, it is necessary to establish a TCP / IP connection between the controlled and monitoring stations. To do this, in the Arduino sketch, initialize the device and create a server that will wait for incoming connections through the specified port.
#include
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();//подсоединение клиентов
}
If you download this sketch, the following will happen:

Establishing a connection, then the STARTDT act package, unknown to Arduino, comes and after a certain time the connection breaks. Next, you need to understand what STARTDT act is.
2. Confirmation of a request for data transfer (STARTDT act / con)
In IEC 670-5-104, there are 3 types of formats for transmission:
- I-format for transmitting telemetry data;
- S-format for transmitting receipts;
- U-format for transmission of communication establishment packets and communication channel testing.
After a successful “triple handshake”, the master station sends an APDU STARTDT (data transfer start). STARTDT initiates for the controlled (Slave) station permission to transfer ASDUs (frames I) in the direction of the controlling (master), to continue operation, STARTDT must be confirmed if the controlled (Slave) station is ready to transmit data blocks. If the slave station does not confirm the STARTDT, then the master station causes the IP connection to be closed.

Thus, further it is necessary to read the bytes received from the controlling (master) station and parse them.
uint8_t iec104ReciveArray[128];//массив для приема
EthernetClient client = iec104Server.available();
if(client.available())
{
delay(100);
int i = 0;
while(client.available())
{
iec104ReciveArray[i] = client.read();//записываем в буфер приема данные
i++;
}
After reading the data, you need to parse them and form an answer.

Here's what the package containing the STARTDT block in Wireshark looks like, the APDU is a U-format block that consists only of APCI.
APCI - Application Level Control Information can be used as a standalone control frame (U frame or S frame).

In short, the APCI determines the type of APDU and its length. APCI consists of the following six bytes:
1. An initialization flag of a variable-length APDU, starting with byte START2 68h;
2. The length of the APDU, in this example, is four bytes;
3. The control byte in which the APDU type is determined, in this example, a value of seven is written, which means a request for data transfer;
4,5,6 Not used.

Based on the above, before answering, it would not hurt to determine what type of APDU the monitoring station sent to us. Knowing that the APDU type is written in the third byte order of reading the APCI block, I will save it in an integer variable.
Note: If a packet of format “I” is received, then 3 bytes in APCI will also contain the value of the low word of the counter of received packets,

so I had to slightly complicate the design of determining the type of 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;
}
It can be seen from the figure above that the APDU type corresponding to the value 7 is STARTDT act; accordingly, it is necessary to respond with the same packet in structure, only the type value must have the value 11 (0b), which corresponds to STARTDT con.
#include
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;
}
}
}
After updating the sketch, we observe the following exchange order:

Establishing a connection, request for data transfer, confirming the request and another new, yet unknown APDU of format I type 1 C_IC_NA Act.
3. Request for a general station survey
The polling team C_IC ACT requests the full volume or a given specific subset of the polled information to the CP. A subset (group) is selected using the QOI polling descriptor.
The station interrogation team requires the stations to transmit the current status of their information, usually transmitted sporadically (transmission reason = 3), to the monitoring station with transmission reasons from <20> to <36>. Station polling is used to synchronize process information at a controlling station and controlled stations. It is also used to update information at the monitoring station after the initialization procedure or after the monitoring station detects a channel loss (unsuccessful repetition of the link layer request) and its subsequent restoration. The response to station polling should include process information objects that are stored at the station being monitored. In response to station polling, these information objects are transmitted with identifiers of types <1>, <3>, <5>, <7>, <

APDU <100> C_IC_NA_1, in addition to the APCI block, also has an ASDU block (application layer data block), which together form the APDU Protocol Data Unit.

Consider in more detail the resulting APDU.
APCI:
- The first byte indicates type 0, which means that it is a polling command;
- In the second, the length of the APDU is 14 bytes;
ASDU:
- The first byte in the ASDU determines the type of information object, in this case <100> C_IC_NA_1 (general station polling);
- The second structure of the data block;
- The third reason for the transfer (CauseTx), a value of six means an activation request;
- The fourth common address of the country;
- Fifth address of the slave station;
- From the sixth to the eighth address of the information object is zero;
- Ninth information byte - QOI - request descriptor with the following values:
In response to <100> C_IC_NA_1, it is necessary to respond with a confirmation to the request, transfer the process information objects that are stored at the monitored station and complete the activation.
To send confirmation, it is necessary to write in ASDU <100> C_IC_NA_1 a byte indicating the reason for the transfer (CauseTX), a value equal to 7; to send an activation completion, it is necessary to write a byte indicating the reason for the transfer (CauseTX) equal to 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;
After updating the sketch, we observe the following exchange order:

Establishing a connection, requesting data transfer, confirmation, request for a general station polling from the controlling station, completion of initialization, request for a general station polling in the direction of the controlled station, confirmation of a general poll, sending the values of all available signals to the monitoring station, and completing the general poll unknown format APCI S.
Request polling station is issued in a direction-controlled station:
- if received from the monitored station "K Heff INITIALIZATION "or
- if the central station detects the loss of the channel (channel level query unsuccessful repetition) and its subsequent recovery.
4. Preparation and data transfer
APDU S format unit consisting only of APCI is intended to confirm the adopted APDU I format. For the S-format, the 7 high bits of the service field of byte 1 and byte 2 are not used, and byte 3 (7 high bits) and byte 4 determine the current number of the received message.

In this case, block S indicates that the controlling (master) station is ready to receive data within a certain time, not exceeding the timeout t3 defined on the side of the controlling (master) station. That is, the master station tells us "I am ready to receive data!" Next, you need to take care of what data to transfer and where to get it from.
What can be transmitted? There are several types of information defined in IEC 870-5-104:
- Control;
- Manager;
- Parameters;
- File transfer.

In this example, the transmission of control information by the example of 1, 11 and 13 functions (single-element, measurement is scalable, measurement is short format with a floating point) is considered. Data is generated randomly. It is also necessary to consider that each transmitted signal has a quality byte.

A simple algorithm for determining signal quality:
- If substitution of the active signal is used, then the BL (blocking) and SB (substitution) flags are set;
- If the signal value has not changed during the control period, the NT flag is set (not relevant);
- If there is a sign of inoperability of a node or device of a lower level (sensor or other) then flag IV is set (not a reliable value).
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
}
}
It is also necessary to take into account that for each signal there is a unique IOA identifier, in the power industry it is customary to distribute these addresses as follows:
- TS-starting from 4096;
- TI-starting at 8192;
- TU-starting at 20480.
To transfer the signal value to the array for sending, use the EEPROM library:
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;
}
}
And so, having received the APDU confirmation of S or I format from the monitoring station, you can start transmitting the available data, without forgetting to increase the number of the transmitted frame.

#include
#include
#include
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;
}
}
}
After loading the sketch into Wireshark, we observe that the data transfer has finally begun.
The following is a description of the structure of the ASDU <100> M_SP_NA_1 singleton indication.

TypeId - type of information.
SQ - classifier of variable structure.
Two data block structures are envisaged:
1. A block containing i information objects, each of which contains one information element (or one combination of elements); the most significant bit of the classifier of the variable structure SQ (single / sequence) is 0, the remaining 7 bits specify the number i.
2. A block containing one information object that contains j elements or the same combinations of information elements; the most significant bit (27 = 80h) of the SQ qualifier is 1, the remaining 7 bits specify the number j.

CauseTx - the reason for the transfer.

Addr - the address of the slave (specified when configuring the wizard).
IOA - address of the information object, at this address the controlling station will attach its tag
SIQ - an indicator of the quality of the transmitted signal.
The ASDU structure of function block <11> M_ME_NB_1 :

In response to the received data, the master will send S-format blocks and the process will loop until the monitored (slave) device ceases to transmit frames.
5. Testing procedures
Testing procedures are used to monitor the health of transport connections. The procedure is performed regardless of the “activity” of the IP connection if no frames (I, U, S) were received during the control time t3. Time t3 is subject to approval and is called the “Timeout for sending test blocks in the event of a long downtime”. The testing procedure is implemented by sending a test APDU (TESTFR = act), which is confirmed by the received station using APDU (TESTFR = con).


If an APDU arrives from the controlling (master) station, whose value in the byte of the APDU type is equal to sixty networks (TESTFR), this indicates that no frames were received from the controlled station during time t3 (I, U, S) , and if during time t1 you do not respond with confirmation, the connection will be disconnected.
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);

That's all, if anyone is interested, then in the next article I will consider the IEC 670-5-104 protocol from the master station using the Arduino as an example.