<< 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. Все что нужно для этого сделать - открыть редактор скрипта, выделить весь код в буфер обмена, а затем вставить его в блокнот или любой другой текстовый редактор:
Затем файл нужно сохранить, при этом обязательно нужно указать кодировку ANSI - это делается в окне сохранения файла:
Теперь данный файл нужно разместить в любой папке, откуда потом он будет вызываться. Мы рекомендуем расположить его в папку MODULES в папке конфигураций сервера.
Примечание. В Windows 7 и 10 полный путь - C:\Users\All Users\Application Data\InSAT\MasterOPC Universal Modbus Server\ MODULES\
Для того чтобы получить путь к этой папке в ОРС сервере есть специальная константа LUA_MODULES
Теперь удаляем весь код из скрипта устройства и размещаем в нем функцию dofile:
dofile (LUA_MODULES.."\\Script.lua");
Теперь если потребуется изменить код скрипта, достаточно отредактировать файл Script.lua. Можно скопировать его назад в редактор скрипта ОРС, исправить, а затем скопировать обратно в файла. Также можно использовать сторонние редакторы- VS Code, Notepad++ и т.д, но обязательно контролируйте, что сохранение файла идет в формате ANSI.