г. Москва, ул. Азовская, 14
+7 (495) 310-97-15
Пн-пт: с 9.00 до 18.00
Заказать звонок
Обратный звонок
Ваше имя *
Ваш телефон *
Ваш Email *
Перезвоните мне
Образец исходного кода для контроллера Fastwel

Реализация протокола SNMP на контроллерах Fastwel

Особенности реализации протокола SNMP на контроллерах Fastwel.


Fastwel

Была поставлена задача по реализации системы на контроллерах Fastwel. Объект был военный, данные контроллеры отечественного производителя (Россия), система создавалась под специфические запросы.

В статье изложен действующий пример того, как в Codesys можно реализовать проблему отсутствия какого-то нативного (вложенного) протокола, который не поддерживается контроллером по умолчанию. Codesys достаточно мощная и гибкая среда разработки. При достаточно скромных возможностях аппаратного обеспечения  программист может создать сложный проект, используя встроенные возможности программного обеспечения. Программа основана на использовании стандартных сокетов (портов), к которым обращаются стандартные библиотеки.

Если рассматривать разницу между брендами Fastwel или WAGO, то контроллеры Fastwel - это удачная попытка российских производителей сделать свой контроллер, аналогичный европейскому. У производителей есть собственная производственная площадка. Они сами изготавливают платы, сами собирают модули, хотя, конечно, вся элементарная база чипов - это импорт из Китая. С другой стороны, это почти чистый WAGO, где даже форм-фактор (корпуса) и даже некоторые программные библиотеки подходят и работают. Исключением стал протокол SNMP. На Fastwel он просто незаработали библиотеки от WAGO, и поэтому пришлось писать свой код.

Применяют контроллеры Fastwel в основном для государственного сектора, например, для военных и специальных структур. Оборудование имеет сертификат российского производителя и производитель может дать расширенную гарантию в 11-12 лет. Если что-то ломается, то бесплатно по соответствующей партии делается замена. Учитывая, что размеры партий не очень большие, это не сильно влияет на прибыль. В 2017-2018 годы стоимость WAGO была ниже FASTWEL где-то в полтора раза.

О реализации протокола SNMP

Вообще реализацией протокола это назвать можно с огромной натяжкой, так как реализовано только получение данных. С другой стороны, заменой порта и направлением потока данных можно записывать необходимые данные в устройство. Также данная реализация возможна не только на контроллерах Fastwel, а вообще на всех контроллерах, использующих среду разработки Codesys 2.3. Используется библиотека FastwelSysLibSockets, для других контроллеров можно использовать стандартную SysLibSockets.

Итак, сторона принимающая – контроллер Fastwel CPM713. Сторона, отдающая данные, – неведомый контроллер уникального ИБП. В инструкции прописаны адреса данных.

Сразу следует оговориться, что обмен писался под определенное устройство, без возможности на ходу менять адрес, порт, направление чтения и прочее, но весь этот функционал неплохо реализуется при желании.

Итак, у нас есть функция, которая отвечает за формирование строки запроса (за полным описанием протокола лучше обратиться к документу RFC-1592).

FUNCTION RequestForming : ARRAY[0..50] OF BYTE (*Результат – строка запроса в виде последовательности байт. Сделано для совместимости с функциями отправки данных*)

VAR_INPUT

       Address: STRING[50]; (*Адрес переменной в строковом виде, то есть 1.3.6.1.4.1.34498.2.1.1.1.1.0 , например*)

END_VAR

VAR

       Ar: ARRAY [0..20] OF INT; (*Массив для преобразования адреса переменной из строкового в числовой вид*)

       Title: ARRAY[0..38] OF BYTE := 16#30, 16#2D, 16#02, 16#01, 16#01, 16#04, 16#06, 16#70, 16#75, 16#62, 16#6C, 16#69, 16#63, 16#A0, 16#20, 16#02, 16#02, 16#5A, 16#FB, 16#02, 16#01, 16#00, 16#02, 16#01, 16#00, 16#30, 16#14, 16#30, 16#12, 16#06, 16#0E, 16#2B, 16#06, 16#01, 16#04, 16#01, 16#82, 16#8D, 16#42; (*Заголовок запроса. К сожалению, пришлось тянуть его из сниффера, потому как в описании протокола SNMP есть неточности и самостоятельно его сформировать не получилось*)

       I: INT; (*Всяческие временные и служебные переменные*)

       J: BYTE;

       tmpStr: STRING;

END_VAR

(*Заполняем заголовок запроса. Он всегда один*)

FOR I := 0 TO 38 DO

       RequestForming[I] := Title[I];

END_FOR

(*Распознаём строку адреса и формируем битовый адрес для запроса*)

J := 0;

tmpStr := '';

FOR I := 1 TO LEN(Address) DO

       IF MID(Address, 1, I) = '.' THEN

             Ar[J] := STRING_TO_INT(tmpStr);

             J := J+1;

             tmpStr := '';

       ELSE

             tmpStr := CONCAT(tmpStr, MID(Address, 1, I));

       END_IF

END_FOR

(*Дописываем адрес запроса нашими значениями*)

FOR I := 7 TO J DO

       RequestForming[I-6+38] := INT_TO_BYTE(Ar[I]);

END_FOR

(*Ну и меняем необходимые данные*)

RequestForming[38+J-6+1] := 16#05;(*Значение окончания запроса*)

RequestForming[38+J-6+2] := 16#00;

RequestForming[1] := 37+J-4; (*Длина всего запроса*)

RequestForming[14] := 24+J-4;(*Длина запроса Get*)

RequestForming[26] := 12+J-4;(*Длина Цифрового кода переменной*)

RequestForming[28] := 10+J-4;

RequestForming[30] := 8+J-6;

Сама программа отправки запросов и получения ответов

Для начала в разделе типов определим следующие типы данных:

TYPE TSNMPAnswer : ARRAY[0..ANSWER_SIZE] OF BYTE;

END_TYPE;

TYPE TSNMPRequest: ARRAY[0..REQUEST_SIZE] OF BYTE;

END_TYPE;

Массив IBEPReq расположен в глобальных переменных и заполнен адресами переменных

IBEPReq[0] := '1.3.6.1.4.1.34498.2.1.1.1.1.0';

IBEPReq[1] := '1.3.6.1.4.1.34498.2.1.1.1.2.0';

PROGRAM SNMP_READ

VAR

       clntSendSocket: DINT := SOCKET_INVALID;

       clntRecvSocket: DINT := SOCKET_INVALID;

       sockAddr: SOCKADDRESS;

       sockAddrRecv: SOCKADDRESS;

       SendDataBytes: DINT;

       BytesReceived: DINT;

       sendBuffer: TSNMPRequest;

       recvBuffer: TSNMPAnswer;

       ReqNum: INT;

       dintOpt: DINT;

       blRes: BOOL;

       SendingTimer: TON;

       i: DINT;

       StartTimer: TON;

       StartSend: BOOL;

END_VAR

REPEAT (*В бесконечном цикле…*)

       (*Формируем запрос*)

       sendBuffer := RequestForming(IBEPReq[ReqNum]);

       IF clntSendSocket = SOCKET_INVALID THEN

             (*Создать сокет клиента*)

             clntSendSocket := FwSysSockCreate(SOCKET_AF_INET, SOCKET_DGRAM, SOCKET_IPPROTO_UDP);

             dintOpt := 1;

             blRes := FwSysSockSetOption(clntSendSocket, SOCKET_SOL, SOCKET_SO_REUSEADDR, ADR(dintOpt), SIZEOF(dintOpt));

             IF clntSendSocket = SOCKET_INVALID THEN

                    EXIT;

             END_IF;

             sockAddr.sin_family := SOCKET_AF_INET;

             sockAddr.sin_addr := FwSysSockInetAddr(clntIpAddr);

             sockAddr.sin_port := FwSysSockHtons(srvPort);

             blRes := FwSysSockBind(clntSendSocket, ADR(sockAddr), SIZEOF(sockAddr));

       END_IF;

(*Если сокет создан…*)

       IF clntSendSocket <> SOCKET_INVALID THEN

             IF StartSend THEN

                    (*… пытаемся отправить данные*)

                    sockAddr.sin_family := SOCKET_AF_INET;

                    sockAddr.sin_addr := FwSysSockInetAddr(srvIpAddr);

                    sockAddr.sin_port := FwSysSockHtons(srvPort);

                    SendDataBytes := FwSysSockSendTo(clntSendSocket, ADR(sendBuffer), SIZEOF(sendBuffer), 0, ADR(sockAddr), SIZEOF(sockAddr));

                    FOR i := 0 TO ANSWER_SIZE-1 DO

                           recvBuffer[i] := 0;

                    END_FOR

                    (*Запрос сформировали, отправили…*)

                    StartSend := FALSE;

             ELSE

                    (*…ждем ответа…*)

                    BytesReceived := FwSysSockRecvFrom(clntSendSocket, ADR(recvBuffer), ANSWER_SIZE, 0, ADR(sockAddrRecv), SIZEOF(sockAddrRecv));

                    IF BytesReceived > 0 THEN

                           IBEPConn := TRUE;

                           IBEPAnsw[ReqNum] := recvBuffer;

                           StartSend := TRUE;

                           ReqNum := ReqNum + 1;

                           IF ReqNum >= REAL_TO_INT(SIZEOF(IBEPReq)/35) THEN

                                  ReqNum := 0;

                                  IBEP_Slave();(*ФБ обработки ответов*)

                           END_IF

                    ELSE

                           (*Рвем соединение, чтобы подключиться заново*)

                           IBEPConn := FALSE;

                    END_IF

             END_IF

       END_IF

       (*Реализация таймаута получения ответа*)

       IF StartTimer.Q THEN

             StartSend := TRUE;

       END_IF

       StartTimer(IN := NOT StartSend, PT := t#100ms);

UNTIL TRUE

END_REPEAT

Ну и, собственно, разбор полученного ответа в функциональном блоке, так как ИБП может быть несколько

FUNCTION_BLOCK IBEP

VAR_INPUT

       Slave_Status : NET_CONNECTION_STATUS;

END_VAR

VAR_OUTPUT

       SlaveStatus : NET_CONNECTION_STATUS;

       uDC: REAL;

       iDC: REAL;

       ControllerTemp: REAL;

       NumberOfACGroup: INT;

       NumberOfAlarms: INT;

       ACFlag: BOOL;

       DCPower: REAL;

       LoadPercent: REAL;

       MainAlarm: BOOL;

       ACAlarm: BOOL;

       RectifierAlarm: BOOL;

       InverterAlarm: BOOL;

       BattDischargeAlarm: BOOL;

       BattLowAlarm: BOOL;

       BattDisBalanceAlarm: BOOL;

       BattCount: INT;

       BattCurrent: REAL;

END_VAR

VAR

       AnswerType: BYTE;

       AnswerSize: INT;

       AnswerPos: INT;

       I: INT;

       Answer: TSNMPAnswer;

       Stt: STRING;

       J: INT;

       K: INT;

       iValue: INT;

       rValue: REAL;

END_VAR

VAR_IN_OUT

END_VAR

IF IBEPConn THEN

       SlaveStatus := NCS_CONNECTED;

ELSE

       SlaveStatus := NCS_NOT_CONNECTED;

END_IF

(*Вытаскиваем данные из ответа*)

FOR K := 0 TO REAL_TO_INT(SIZEOF(IBEPReq)/35)-1 DO

       Answer := IBEPAnsw[K];

       I := Answer[30]+30;

       AnswerPos := I+3;

       AnswerSize := Answer[I+2];

       AnswerType := Answer[I+1];

       IF AnswerType = 2 THEN

             iValue := Answer[AnswerPos];

       END_IF

       IF AnswerType = 4 THEN

             Stt := '';

             FOR J := 1 TO AnswerSize DO

                    CASE (Answer[AnswerPos+J-1]) OF

                           48..57: Stt := CONCAT(Stt, BYTE_TO_STRING(Answer[AnswerPos+J-1]-48));

                           44, 46: Stt := CONCAT(Stt, '.');

                    ELSE

                           ;

                    END_CASE

             END_FOR

             IF Stt <> '' THEN

                    rValue := STRING_TO_REAL(Stt);

             END_IF

       END_IF

(*Сортируем данные в зависимости от того, с какой переменной это всё пришло*)

       CASE K OF

             1: uDC := rValue;

             2: iDC := rValue;

             3: ControllerTemp := rValue;

             4: NumberOfACGroup := iValue;

             5: NumberOfAlarms := iValue;

             6: ACFlag := iValue > 0;

             7: DCPower := rValue;

             8: LoadPercent := rValue;

             9: MainAlarm := iValue > 0;

             10: ACAlarm := iValue > 0;

             11: RectifierAlarm := iValue > 0;

             12: InverterAlarm := iValue > 0;

             13: BattDischargeAlarm := iValue > 0;

             14: BattLowAlarm := iValue > 0;

             15: BattDisBalanceAlarm := iValue > 0;

             16: BattCount := iValue;

             17: BattCurrent := rValue;

       ELSE

             ;

       END_CASE;

END_FOR

Автор решения: Михаил Туровец (Красноярск)

#Fastwel, #SNMP

Оставьте первый комментарий

Ваш комментарий добавлен