Сопровождение кода

<< Click to Display Table of Contents >>

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

Сопровождение кода

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

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

Размещение кода в узле или устройстве

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

Решением здесь будет вынести основную часть кода в устройство или узел. Рассмотрим такой абстрактный пример:

function OnRead()    

local Addr=server.GetCurrentDeviceAddress( );                  

local send = {Addr,1,1}; --кадр запроса (адрес, канал ,параметр )

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

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

local destmask={"byte","byte","float:1:0132"}; --маска ответа

local err,len;  

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

--выполняем запросы в цикле

repeat

--запрос к устройству

err,dest,len=server.SendAndReceiveDataByMask(2,3,sendmask,send,destmask,8);

if err==-1 then --нет ответа от устройства

   break; --выходим из опроса

end

if err>0 and table.maxn(dest)==table.maxn(destmask) then --проверим что количество ожидаемых элементов равно принятому

    if dest[1]~=send[1] or dest[2]~=send[2] or dest[3]~=send[3] then    --проверяем что совпадает адрес прибора и функция

       err=-2; --ошибка

    end;

end;

n=n+1;

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

if err<0 then                  

   server.WriteCurrentTag(0,OPC_QUALITY_BAD);  --данные не приняты или данные некорректны. Устанавливаем плохой признак качества

else    

   server.WriteCurrentTag(dest[3],OPC_QUALITY_GOOD);    --Записываем в тег принятое значение

end;

end

В устройство посылается 3 байта - адрес устройства, номер канала, номер параметра (и 2 байта контрольной суммы Modbus). Таких тегов может быть несколько сотен, но при этом все отличие скрипта будет сводится к одной строчке:

local send = {Addr,1,1}; --кадр запроса (адрес, канал, параметр)

Т.е. будет отличаться номера канала и параметра. Можно вынести почти весь код в функцию устройства и вызывать его оттуда используя функцию server.RunFunctionFromDevice.

В устройстве пишем собственную функцию и переносим туда большую часть кода:

function Request(channel,parameter)

local Addr=server.GetCurrentDeviceAddress( );                  

local send = {Addr,channel,parameter}; --кадр запроса (адрес, канал ,параметр )

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

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

local destmask={"byte","byte","float:1:0132"}; --маска ответа

local err,len;  

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

--выполняем запросы в цикле

repeat

--запрос к устройству

err,dest,len=server.SendAndReceiveDataByMask(2,3,sendmask,send,destmask,8);

if err==-1 then --нет ответа от устройства

   break; --выходим из опроса

end

if err>0 and table.maxn(dest)==table.maxn(destmask) then --проверим что количество ожидаемых элементов равно принятому

    if dest[1]~=send[1] or dest[2]~=send[2] or dest[3]~=send[3] then     --проверяем что совпадает адрес прибора и функция

       err=-2; --ошибка

    end;

end;

n=n+1;

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

if err<0 then            

   return nil; --ошибка возвращаем nil

else    

   return dest[3]; --возвращаем значение

end;

end

Код почти аналогичен, отличия всего несколько строк:

local send = {Addr,channel,parameter}; --кадр запроса (адрес, канал ,параметр )

Здесь вместо констант, как в исходном скрипте, подставляется номер канала и параметра из принятых в аргументах функции.

if err<0 then            

   return nil; --ошибка возвращаем nil

else    

   return dest[3]; --возвращаем значение

end;

Здесь если принята ошибка мы возвращаем nil, а иначе значение.

Теперь рассмотрим код, который останется в скрипте тега.

function OnRead()

   local Channel=1;

   local Parameter=1;

   local Value = server.RunFunctionFromDevice("Request",1,Channel,Parameter);

   if Value==nil then

       server.WriteCurrentTag(0,OPC_QUALITY_BAD);  

   else

       server.WriteCurrentTag(dest[3],OPC_QUALITY_GOOD);    

   end

end

Код сократился всего до нескольких строк. В нем происходит инициализация номера канала и параметра, а затем происходит вызов функции Request. Все что нужно теперь редактировать - номер канала и номер параметра. Если код поменяется в функции Request - то сразу для всех вызовов этой функции во всех тегах.

Аналогично можно разместить код в функции узла, и вызывать его с помощью функции server.RunFunctionFromNode, но в этом случае нужно передать еще один параметр - адрес устройства, так как функция server.GetCurrentDeviceAddress( ) в узле вернет ошибку, и адрес нужно получить заблаговременно.

Изменение архитектуры скрипта

Рассмотрим все тот же пример с размещением кода в тегах. Если тегов много, и они все находятся на одном уровне (устройства или подустройства), то можно изменить размещение скрипта - разместить его в устройстве, а затем в цикле перебирать теги и формировать для каждого запрос.

Снова рассмотрим прошлый пример. Представим что у нас есть каналы, с идентичным набором тегов - опросим их из скрипта подустройства.

function OnBeforeReading()

local Channel=server.ReadSubDeviceExtProperty("NumChannel"); --номер канала из дополнительного свойства

for i=0,server.GetCountTags( )-1,1 do

   local Value = server.RunFunctionFromDevice("Request",1,Channel,i); --вызов функции

   if Value==nil then

       server.WriteTagByNumber(i,0,OPC_QUALITY_BAD);

   else

       server.WriteTagByNumber(i,dest[3],OPC_QUALITY_GOOD);    

   end

end

Номер канала мы берем из дополнительного свойства подустройства, с помощью функции server.ReadSubDeviceExtProperty. Затем перебираем с помощью цикла все теги данного устройства. Для определения количества тегов в подустройстве используется функция server.GetCountTags. Запись производится функцией server.WriteTagByNumber - по номеру тега.

Такой подход также сильно снижает объем кода, что облегчает как его правку, так и исполнение.

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

Размещение кода во внешнем файле

Данный вариант рекомендуется применять, если конфигурация содержит большое количество схожих скриптов, а также при тиражном использовании конфигурации. В языке Lua допускается загрузить исполняемый код из внешнего файла. При этом можно загрузить код один раз при старте сервера и постоянного его исполнять или периодически вызывать определенную функцию из внешнего файла.

Для вызова кода из файла в Lua есть специальная функция – dofile(). В качестве аргумента в функцию нужно передать путь к текстовому файлу, в котором находится код.

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

primery_i_poleznosti_soprovozhdenie_koda_soprovozhdenie_koda

Затем файл нужно сохранить, при этом обязательно нужно указать кодировку ANSI - это делается в окне сохранения файла:

primery_i_poleznosti_soprovozhdenie_koda_soprovozhdenie_koda1

Теперь данный файл нужно разместить в любой папке, откуда потом он будет вызываться. Мы рекомендуем расположить его в папку MODULES в папке конфигураций сервера.

Примечание. В Windows 7 и 10 полный путь - C:\Users\All Users\Application Data\InSAT\MasterOPC Universal Modbus Server\ MODULES\

Для того чтобы получить путь к этой папке в ОРС сервере есть специальная константа LUA_MODULES

primery_i_poleznosti_soprovozhdenie_koda_soprovozhdenie_koda2

Теперь удаляем весь код из скрипта устройства и размещаем в нем функцию dofile:

dofile (LUA_MODULES.."\\Script.lua");

primery_i_poleznosti_soprovozhdenie_koda_soprovozhdenie_koda3

Теперь если потребуется изменить код скрипта, достаточно отредактировать файл Script.lua. Можно скопировать его назад в редактор скрипта ОРС, исправить, а затем скопировать обратно в файла. Также можно использовать сторонние редакторы- VS Code, Notepad++  и т.д, но обязательно контролируйте, что сохранение файла идет в формате ANSI.