<< 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 есть пример работы с данным модулем, с дополнительной поддержкой поканального считывания, а также реализация протокола для модуля дискретного ввода-вывода ОВЕН МДВВ.