Отладка сложных скриптов — различия между версиями
(Новая страница: «== Отладка сложных скриптов == === Инициализация проекта === ==== Энергонезависимые регистры =…») |
(→Энергонезависимые регистры) |
||
Строка 2: | Строка 2: | ||
=== Инициализация проекта === | === Инициализация проекта === | ||
==== Энергонезависимые регистры ==== | ==== Энергонезависимые регистры ==== | ||
− | Следует помнить что регистры типа Dхх | + | Следует помнить что регистры типа Dхх и некоторые другие внутренние регистры, кроме DSxx возвращаются в начальное состояние (как правило в 0) после инициализации проекта (что происходит при любом редактировании элементов проекта - скриптов, регистров, соединений и т.д.). Если такие регистры используются как переменные для формирования триггеров событий или условий выполнения других скриптов, это может приводить к нарушению логики выполнения. Следует учитывать эту особенность при выборе регистров как входных и выходных переменных скриптов. |
==== Глобальные переменные ==== | ==== Глобальные переменные ==== |
Версия 09:02, 23 марта 2018
Содержание
Отладка сложных скриптов
Инициализация проекта
Энергонезависимые регистры
Следует помнить что регистры типа Dхх и некоторые другие внутренние регистры, кроме DSxx возвращаются в начальное состояние (как правило в 0) после инициализации проекта (что происходит при любом редактировании элементов проекта - скриптов, регистров, соединений и т.д.). Если такие регистры используются как переменные для формирования триггеров событий или условий выполнения других скриптов, это может приводить к нарушению логики выполнения. Следует учитывать эту особенность при выборе регистров как входных и выходных переменных скриптов.
Глобальные переменные
Переменные в скриптах, объявленные до функции main сохраняют свои значения между вызовами скрипта, но при инициализации также принимают начальные значения. Их можно использовать для хранения констант, коэффициентов и др. подобных величин.
Первый скан
Скрипту можно назначить способ запуска на первом скане при инициализации проекта и сделать некую инициализацию переменных из одного места. Иногда может понадобится определить внутри самого скрипта, был ли это первый вызов после инициализации. Можно создать глобальную переменную first_scan:
first_scan = true;
function main (userId)
if first_scan then
first_scan = false;
--[[
Действия при инициализации проекта
--]]
end --if
end -- main
Следует помнить что регистры типа Dхх, CDxx и другие внутренние регистры, кроме DSxx возвращаются в начальное состояние (как правило в 0) после инициализации проекта (что происходит при любом редактировании элементов проекта - скриптов, регистров, соединений и т.д.). Если такие регистры используются как переменные для формирования триггеров событий или условий выполнения других скриптов, это может приводить к нарушению логики выполнения. Следует учитывать эту особенность при выборе регистров как входных и выходных переменных скриптов.
Модульность
Рекомендуется разбивать сложные скрипты на более простые и часто используемые функции, которые можно использовать повторно. Разбивка на более простые части, расположение их в нужном порядке и группировка помогает контролировать логику работы системы и легче наладить систему. Например, если есть задача управления положением задвижки, можно выделить такие части:
- блок "захвата" момента включения привода, для инициализации начальной позиции и времени начала движения
- блок расчета текущей позиции в движении (который определяет время между вызовами скрипта и вычисляет пройденный путь штока)
- блок авт. управления (проверяет рассогласование между уставкой и тек. положением, определяет направление и дает команду, если нет других блокировок)
- блок ручного управления
- блок блокировок по наезду на конечные выключатели - для снятия текущей команды и установки позиции в 0/100%
Сложные функции перед вложением их другие скрипты можно отладить отдельно в пользовательском скрипте, выделив часть внутренних регистров под входные переменные, часть под выходные. Эти же регистры вынести на дешборд, вместе с кнопкой запуска отлаживаемого скрипта. Тогда меняя входные наборы данных удобно видеть тут же результат выполнения. Также можно использовать отдельную IDE типа Eclipse для Lua или онлайн версию, чтобы удобно и быстро отлаживать маленькие фрагменты, изучать работу новых функций и т.д.
Отличия записи во внутренние регистры от регистров в устройствах
Есть некоторые отличия в работе функций SetReg и WriteReg применительно к внутренним регистрам (Dxx, DSxx). Эти функции непосредственно меняют значения внутренних регистров внутри скана, а не откладывают запись WriteReg на следующий скан. Таким образом, в конце скана внутренний регистр может иметь значение, отличное от того которые было на входе в скан. Тогда, например возможна ситуация, когда:
- скрипт 1 меняет значение некоего регистра Dn. (выполняется в каждом скане)
- скрипт 2 работает по изменению этого регистра Dn. (выполняется по изменению регистра)
Если порядок выполнения скриптов будет 1 - 2, то все будет работать, потому что на входе в скан скрипт 2 видел одно значение, и перед своим выполнением другое (которое успел изменить скрипт 1), и отработает "по изменению". Если же порядок выполнения скриптов поменять местами, то скрипт 2 перестанет, работать, так как на входе в текущий скан он будет видеть измененное значение, а новое изменение произойдет после скрипта 2 в скрипте 1.
Отладочная печать
Желательно сразу ставить после ключевых моментов логики в скриптах отладочную печать функциями TRACE, c DEBUG c номером скрипта или названием функции. Тогда эти фрагменты удобно искать и анализировать в коммуникационном логе. Однако в большой системе, когда отладочной печати становится много, становится неудобно искать необходимые данные. Можжно поступить следующим образом - назначить свою функцию отладочной печати, которая будет вызываться только, если отладочная печать в этом скрипте разрешена. Например, можно в регистр "debug_ID" записывать id скрипта в котором нужна отладночная печать, а функция внутри скрипта будет смотреть на этот номер. Например:
thisScriptID = 15;
function main (userId)
MyDebugPrint("Программа симуляции теплосчетчка");
local VLVpos = GetReg("curPosition"); -- Текущее положение клапана (DS1005@WebHMI 3point control)
--[[
code
--]]
end
function MyDebugPrint(str)
local debug_id = GetReg("debug_ID");
if (debug_id == thisScriptID) then
ERROR(str);
end
end
Внутри функции задавать уровень лога - INFO, ERROR и т.д. а в настройках системы временно отключать неиспользуемые уровни, чтобы сфокусироваться только на нужной отладочной информации. Можно развить вариант до одновременного вывода в нескольких скриптах, если debug_ID сделать строкой вида "25 26 27 ", т.е. перечень скриптов с включенной отладкой. Номера можно быстро выделять используя строковые функции поиска паттернов:
------- debug printing -------
function DEBUG_(str)
local i = 0;
local tmp = "";
while true do
i,_, tmp = string.find(str, "(%d+)%s+",i+1);
if i == nil then break end -- не найдено
if (tmp ~= "0") then
-- найдено проверить на совпадение
if (tmp == tostring(thisScriptID)) then
ERROR(str);
end
end
end
end -- DEBUG_
------------------------------
Формирование суточных, недельных и т.п. отчетов
Для оптимизации производительности устройства данные для отчетов целесообразно регистрировать "в потоке". В WebHMI для этого есть механизм событий, которые после регистрации фактически дают готовые отчеты.
- Необходимые переменные, скрипты и события -
- Необходимые переменные, скрипты и события -
- скрипт, выдающий номер дня, месяца, недели, квартала и т.п. из системного времени во внутренний регистр "день недели", "час", "секунда"
- скрипт подсчета разницы "текущие показание - предыдущие", и фиксации результата в регистре "счетчик" (скрипт работает также по изменению регистра "минута")
- регистр-флаг генерации события, однократного, достаточного для записи текущего посчитанного счетчика и метки времени
- скрипт, снимающий флаг, когда есть событие (т.е. данные уже зафиксированы)'
- событие для регистрации
Для демонстрации можно использовать пример, в котором на каждой секунде "виртуальный" счетчик увеличивается на 2. Другие скрипты реализуют поминутную запись разницы его показаний в отчет. Для добавления других параметров, необходимо по аналогии добавить их внутрь соответствующих скриптов и событий. Скрипт, генерирующий номера минут:
function main (userId)
-- получить номер минуты
local min = os.date("%M",os.time());
-- записать во внутренний регистр
WriteReg("minute", min); -- Минуты
end
Скрипт, работающий на изменение регистра "minute":
function main (userId)
local current = GetReg("FlowMeter"); -- текущие показания
local prevMin = GetReg("pMinTotal"); -- предыдущее значение
local cnt = current - prevMin; -- посчитать разницу
WriteReg("MinuteFlow", cnt); -- записать счетчик во внутр. регистр. он используется в событии
WriteReg("pMinTotal", current); -- записать текущее в предыдущее для след. периода
-- поднять флаг для отчета
WriteReg("EventFlag", 1); -- Установить флаг для события
end
Скрипт, снимающий флаг и соответственно событие после записи:
function main (userId)
-- проверяем выполняется ли событие
local event_state = (GetReg("ES1") == 1); -- Событие 1 выполняется (ES1@Internal register)
-- и сбрасываем флаг
if event_state then
WriteReg("EventFlag", 0); -- Флаг минутного отчета
end
end
Настройки события для данного отчета:
Результатом будет являться отчет такого вида:
При поминутной регистрации данные результаты получаться, если время скана будет меньше чем секунда. Тогда "виртуальный" электросчетчик не пропустит своих секунд.