Создание скрипта

<< Click to Display Table of Contents >>

Navigation:  Modbus Universal MasterOPC Server > Руководство по языку Lua 5.1 > Примеры и полезности > Реализация протокола DCON >

Создание скрипта

Разместим в функции OnBeforeReading() следующий код, а затем разберем его построчно.

-- функция,выполняющаяся перед чтением тегов

function OnBeforeReading()                                  

local Addr=server.GetCurrentDeviceAddress(); --считываем адрес устройства

local send={}; --инициализируем таблицу запроса

send[1] = "#"; --оператор типа запроса

send[2] =  string.format ("%02X",Addr); --преобразование адреса к нужному виду              

send[3]="\r"; --добавление символа перевода каретки  

local sendmask={"string","string","string"}; --маска запроса

local dest={}; --инициализация таблицы принятых значений

local destmask={"string:1","sdouble:8:7"}; --маска ответа

--дополнительные переменные: ошибка, количество элементов в таблице dest,

--количество принятых байт

local err,len; --объявление переменных - флаг ошибки и количество принятых элементов

local n=0; --количество попыток запроса

--запрос к устройству в цикле

repeat                      

   --функция опроса устройства    

err,dest,len=server.SendAndReceiveDataByMask(1,table.maxn(sendmask),sendmask,send,destmask,200);

   --если ответ получен и первый символ - признак корректного ответа (">")

   if err>=0 and dest[1]==">" then  

       --то начинаем записывать значения из принятой таблицы в теги

       for i=2,len,1 do                

          local NameTag="Вход"..(i-1);

          if dest[i]>(-999) then                    

server.WriteTagByRelativeName(NameTag,dest[i],OPC_QUALITY_GOOD); --значение корректно - записываем его в тег                    

          else

    --если принятое значение менее -999, значит произошел обрыв датчика - записываем плохой признак в тег

local val=server.ReadTagByRelativeName(NameTag);                    

server.WriteTagByRelativeName(NameTag,val,OPC_QUALITY_SENSOR_FAILURE);          

end;

       end;

   end;

--счетчик запросов

n=n+1;  

--условие выхода из цикла - корректный ответ или превышение количества повторов запроса (задается в свойствах устройства)

until err>=0 or n>=server.GetCurrentDeviceRetry( );  

--если корректный ответ так и не был принят

if err<0 or dest[1]~=">" then                              

   --то записываем во все теги плохой признак качества

   for i=1,8,1 do

       local NameTag="Вход"..(i);

       local val=server.ReadTagByRelativeName(NameTag);

       server.WriteTagByRelativeName(NameTag,val,OPC_QUALITY_BAD);

   end;

end;

end;

В первой строке уже знакомая нам функция для получения адреса устройства. Затем мы инициализируем таблицу send – таблицу с данными для запроса.

local Addr=server.GetCurrentDeviceAddress(); --считываем адрес устройства

local send={}; --инициализируем таблицу запроса

После этого, мы начинаем заполнять таблицу send данными.

send[1] = "#"; --оператор типа запроса

send[2] =  string.format ("%02X",Addr); --преобразование адреса к нужному виду              

send[3]="\r"; --добавление символа перевода каретки  

Первый элемент таблицы – символ команды запроса ("#" - чтение аналоговых значений).

Второй элемент таблицы – это адрес модуля. Как мы писали в начале, протокол DCON является символьным протоколом – то есть данные в нем передаются в виде символов. Поэтому нам необходимо преобразовать номер модуля в строку. При этом, согласно стандарту, адрес всегда должен составлять два символа, т.е. если устройство например имеет адрес 1, то устройству нужно передать два символа "0" и "1".

Для преобразования в строку используется стандартная Lua функция string.format. Данная функция является аналогом С-функции printf:

http://www.cplusplus.com/reference/cstdio/printf/

Первый аргумент функции – это строка формата. В данном случае строка формата имеет вид "%02X". Первый символ ("%") – это признак начала управляющей последовательности, т.е. символ указывает что c данного символа начинается строка форматирования. Второй символ ("0") является флагом, и означает что строку нужно дополнить слева символами "ноль", если размер строки меньше указанного в поле ширина (следующий символ). Третий символ ("2") указывает что ширина поля должна как минимум 2 символа. Четвертый символ ("X") – спецификатор, он определяет способ интерпретации преобразуемого элемента. В данном случае X – указывает, что необходимо произвести преобразование числа в шестнадцатеричный формат.

Таким образом, функция выполнит преобразование адреса – преобразует в шестнадцатеричный формат, и дополнит слева нулем если адрес состоит из одного символа, после чего преобразует его в строковый тип.

Третий элемент таблицы – символ перевода каретки. На языке Lua он обозначается как "\r".

А как же контрольная сумма? Контрольная сумма будет автоматически вычисляться в функции server.SendAndReceiveDataByMask(), и подставляться перед символом перевода каретки.

Затем мы производим инициализацию таблиц масок для запроса и ответа, и инициализацию маски принимаемых значений.

local sendmask={"string","string","string"}; --маска запроса

local dest={}; --инициализация таблицы принятых значений

local destmask={"string:1","sdouble:8:7"}; --маска ответа

Маска запроса sendmask состоит из трех элементов типа "string".

Подробнее остановимся на маске приема destmask. Как мы писали ранее ответ состоит из одного символа достоверности и 8 блоков по 7 символов, каждый блок – вещественное число. Для преобразования получаемых чисел указываем маску "sdouble:8:7". Первый элемент этой маски – тип преобразования, данный тип предназначен для преобразования строки в вещественное число. Строка может содержать цифры и символы "+", "-", ".". Второй элемент маски (цифра "8") – количество принимаемых элементов. Третий элемент маски (цифра "7") – количество байт.

Контрольная сумма и перевод каретки при выполнении функции server.SendAndReceiveDataByMask()  в таблицу принятых элементов не попадает, поэтому описывать их в маске не нужно.

Теперь можно приступить к выполнению запроса. Как и в предыдущем примере мы будем выполнять запрос в цикле repeat. Но предварительно создадим переменные для флага ошибки, количества принятых элементов, а также переменную для счетчика запросов.

--дополнительные переменные: ошибка, количество элементов в таблице dest, количество принятых байт

local err,len; --объявление переменных - флаг ошибки,количество принятых элементов

local n=0; --количество попыток запроса

--запрос к устройству в цикле

repeat                      

--функция опроса устройства    

err,dest,len=server.SendAndReceiveDataByMask(1,table.maxn(send),sendmask,send,destmask,200);    

Непосредственно сам запрос к устройству выполняется в строке:

err,dest,len=server.SendAndReceiveDataByMask(1,table.maxn(send),sendmask,send,destmask,200);

Функция знакома нам по предыдущем примеру. Единственное отличие – первый параметр (способ вычисления контрольной суммы). В прошлом примере он был равен двум (мы вычисляли контрольную сумму Modbus), в текущем примере мы вычисляем контрольную сумму DCON, поэтому здесь номер равен единице. Также обратите внимание на последний параметр - количество ожидаемых байт, здесь он задан с большим запасом - 200. В прошлом разделе мы писали, что следует указывать значение байт равное ожидаемому от устройству иначе опрос будет медленным, однако при использовании стоповый символ (как в данном случае "перевод каретки"), такой проблемы не будет - завершение функции произойдет сразу при получении данного символа, после чего начинается разбор принятого пакета. Поэтому в случае использования протоколов со стоповыми символами, количество ожидаемых байт можно задавать с запасом.

После того как запрос был выполнен можно приступать к проверке его достоверности. Убеждаемся, что флаг err больше нуля (отрицательное число говорит, что ответ от устройства не был получен), и проверяем что первый элемент в таблице dest равен символу ">" - то есть проверяем, что получен достоверный ответ.

Если ответ получен достоверный, то можно приступать к записи значений в теги. Когда мы создавали теги, мы специально задали им возрастающие имена ("Вход1", "Вход2""Вход8"), теперь это позволит нам записывать значения в теги используя цикл.

for i=2,len,1 do                

local NameTag="Вход"..(i-1);

 if dest[i]>(-999) then      

 --значение корректно - записываем его в тег                  

 server.WriteTagByRelativeName(NameTag,dest[i],OPC_QUALITY_GOOD);

 else

 --если принятое значение менее -999, значит произошел обрыв датчика -   записываем плохой признак в тег

 local val=server.ReadTagByRelativeName(NameTag);                  

 server.WriteTagByRelativeName(NameTag,val,OPC_QUALITY_SENSOR_FAILURE);

end;

Цикл мы начинаем с номера 2, шаг приращения – 1, продолжаем до достижения переменной i значения количества принятых элементов (переменная len).

Переменная NameTag хранит имя тега, в который будет производится запись. Наш тег состоит из статичной части ("Вход") и номера от 1 до 8. Для их объединения нужно использовать операцию конкатенации – операцию сложения строк. На языке Lua она имеет вид двух точек (..).

Даже если получен достоверный ответ, один или несколько каналов могут передать ошибку измерения. В этом случае, как мы писали ранее, модуль передает число -999.90.

Введем проверку этой ситуации. Если значение более -999, то можно производить запись значения в тег с хорошим признаком качества. Это делается в строке:

server.WriteTagByRelativeName(NameTag,dest[i],OPC_QUALITY_GOOD);

В противном случае нам нужно записать в тег признак качества "Ошибка датчика". При этом лучше будет сохранить старое значение, которое было в теге. Поэтому сначала считываем значение из тега, а затем запишем в него это же значение, но уже с плохим признаком качества:

local val=server.ReadTagByRelativeName(NameTag);                  

server.WriteTagByRelativeName(NameTag,val, OPC_QUALITY_SENSOR_FAILURE);

Условие выхода из цикла опроса – корректный ответ или превышение количества попыток запроса. Счетчик количества попыток инкрементируем каждый цикл опроса.

n=n+1;  

--условие выхода из цикла - корректный ответ или превышение количества повторов запроса (задается в свойствах устройства)

until err>=0 or n>=server.GetCurrentDeviceRetry( );  

Если все же ответ так и не будет принят, или был получен признак недостоверного ответа, то производим в теги запись плохого признака качества.

if err<0 or dest[1]~=">" then                              

  --то записываем во все теги плохой признак качества

   for i=1,8,1 do

       local NameTag="Вход"..(i);

       local val=server.ReadTagByRelativeName(NameTag);

       server.WriteTagByRelativeName(NameTag,val,OPC_QUALITY_BAD);

   end;

end;

Итак, мы поддержали протокол DCON для данного модуля. Используя данный код как шаблон можно поддержать и другие функции этого протокола.

Примечание. Готовая конфигурация OPC сервера с полным кодом данного примера доступна по ссылке и называется МВ110-8АС DCON.mbp. Кроме того, в поставляемой с OPC сервером конфигурации simulator.mbp есть пример работы с данным модулем, с дополнительной поддержкой поканального считывания, а также реализация протокола для модуля дискретного ввода-вывода ОВЕН МДВВ.