Отладка сложных скриптов — различия между версиями

Материал из WebHMI Wiki
Перейти к: навигация, поиск
(Отметить эту версию для перевода)
 
(не показано 9 промежуточных версий этого же участника)
Строка 1: Строка 1:
 +
<languages/>
 
<translate>
 
<translate>
== Отладка сложных скриптов ==
+
 
=== Инициализация проекта ===
+
=== Инициализация проекта === <!--T:1-->
 
==== Энергонезависимые регистры ====
 
==== Энергонезависимые регистры ====
 
Следует помнить что регистры типа Dхх и некоторые другие внутренние регистры, кроме DSxx возвращаются в начальное состояние (как правило в 0) после инициализации проекта (что происходит при любом редактировании элементов проекта - скриптов, регистров, соединений и т.д.). Если такие регистры используются как переменные для формирования триггеров событий или условий выполнения других скриптов, это может приводить к нарушению логики выполнения. Следует учитывать эту особенность при выборе регистров как входных и выходных переменных скриптов.
 
Следует помнить что регистры типа Dхх и некоторые другие внутренние регистры, кроме DSxx возвращаются в начальное состояние (как правило в 0) после инициализации проекта (что происходит при любом редактировании элементов проекта - скриптов, регистров, соединений и т.д.). Если такие регистры используются как переменные для формирования триггеров событий или условий выполнения других скриптов, это может приводить к нарушению логики выполнения. Следует учитывать эту особенность при выборе регистров как входных и выходных переменных скриптов.
  
==== Глобальные переменные ====  
+
==== Глобальные переменные ==== <!--T:2-->
 
Переменные в скриптах, объявленные до функции main сохраняют свои значения между вызовами скрипта, но при инициализации также принимают начальные значения. Их можно использовать для хранения констант, коэффициентов и др. подобных величин.  
 
Переменные в скриптах, объявленные до функции main сохраняют свои значения между вызовами скрипта, но при инициализации также принимают начальные значения. Их можно использовать для хранения констант, коэффициентов и др. подобных величин.  
  
==== Первый скан ====  
+
==== Первый скан ==== <!--T:3-->
 
Скрипту можно назначить способ запуска на первом скане при инициализации проекта и сделать некую инициализацию переменных из одного места.  
 
Скрипту можно назначить способ запуска на первом скане при инициализации проекта и сделать некую инициализацию переменных из одного места.  
 
Иногда может понадобится определить внутри самого скрипта, был ли это первый вызов после инициализации. Можно создать глобальную переменную first_scan:
 
Иногда может понадобится определить внутри самого скрипта, был ли это первый вызов после инициализации. Можно создать глобальную переменную first_scan:
 
<syntaxhighlight lang = lua>
 
<syntaxhighlight lang = lua>
first_scan = true;
+
first_scan = true -- global var remember value between scans
 
function main (userId)
 
function main (userId)
 +
local var = 0 -- local var will be initialized in each script call
 
   if first_scan then  
 
   if first_scan then  
 
     first_scan = false;
 
     first_scan = false;
Строка 21: Строка 23:
 
  end --if  
 
  end --if  
 
end -- main  
 
end -- main  
 
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
+
<!--T:4-->
 
Следует помнить что регистры типа Dхх, CDxx и другие внутренние регистры, кроме DSxx возвращаются в начальное состояние (как правило в 0) после инициализации проекта (что происходит при любом редактировании элементов проекта - скриптов, регистров, соединений и т.д.). Если такие регистры используются как переменные для формирования триггеров событий или условий выполнения других скриптов, это может приводить к нарушению логики выполнения. Следует учитывать эту особенность при выборе регистров как входных и выходных переменных скриптов.
 
Следует помнить что регистры типа Dхх, CDxx и другие внутренние регистры, кроме DSxx возвращаются в начальное состояние (как правило в 0) после инициализации проекта (что происходит при любом редактировании элементов проекта - скриптов, регистров, соединений и т.д.). Если такие регистры используются как переменные для формирования триггеров событий или условий выполнения других скриптов, это может приводить к нарушению логики выполнения. Следует учитывать эту особенность при выборе регистров как входных и выходных переменных скриптов.
  
=== Модульность ===
+
=== Отличия записи во внутренние регистры от регистров в устройствах === <!--T:5-->
Рекомендуется разбивать сложные скрипты на более простые и часто используемые функции, которые можно использовать повторно. Разбивка на более простые части, расположение их в нужном порядке и группировка помогает контролировать логику работы системы и легче наладить систему. Например, если есть задача управления положением задвижки, можно выделить такие части:
+
:блок "захвата" момента включения привода, для инициализации начальной позиции и времени начала движения
+
:блок расчета текущей позиции в движении (который определяет время между вызовами скрипта и вычисляет пройденный путь штока)
+
:блок авт. управления (проверяет рассогласование между уставкой и тек. положением, определяет направление и дает команду, если нет других блокировок)
+
:блок ручного управления
+
:блок блокировок по наезду на конечные выключатели - для снятия текущей команды и установки позиции в 0/100%
+
 
+
 
+
 
+
Сложные функции перед вложением их другие скрипты можно отладить отдельно в пользовательском скрипте, выделив часть внутренних регистров под входные переменные, часть под выходные. Эти же регистры вынести на дешборд, вместе с кнопкой запуска отлаживаемого скрипта. Тогда меняя входные наборы данных удобно видеть тут же результат выполнения. Также можно использовать отдельную IDE типа Eclipse для Lua или онлайн версию, чтобы удобно и быстро отлаживать маленькие фрагменты, изучать работу новых функций и т.д.
+
 
+
=== Отличия записи во внутренние регистры от регистров в устройствах ===  
+
 
Есть некоторые отличия в работе функций SetReg и WriteReg применительно к внутренним регистрам (Dxx, DSxx). Эти функции непосредственно меняют значения внутренних регистров внутри скана, а не откладывают запись WriteReg на следующий скан. Таким образом, в конце скана внутренний регистр может иметь значение, отличное от того которые было на входе в скан. Тогда, например возможна ситуация, когда:  
 
Есть некоторые отличия в работе функций SetReg и WriteReg применительно к внутренним регистрам (Dxx, DSxx). Эти функции непосредственно меняют значения внутренних регистров внутри скана, а не откладывают запись WriteReg на следующий скан. Таким образом, в конце скана внутренний регистр может иметь значение, отличное от того которые было на входе в скан. Тогда, например возможна ситуация, когда:  
 
*скрипт 1 меняет значение некоего регистра Dn. (выполняется в каждом скане)
 
*скрипт 1 меняет значение некоего регистра Dn. (выполняется в каждом скане)
Строка 46: Строка 34:
 
Если порядок выполнения скриптов будет 1 - 2, то все будет работать, потому что на входе в скан скрипт 2 видел одно значение, и перед своим выполнением другое (которое успел изменить скрипт 1), и отработает "по изменению". Если же порядок выполнения скриптов поменять местами, то скрипт 2 перестанет, работать, так как на входе в текущий скан он будет видеть измененное значение, а новое изменение произойдет после скрипта 2 в скрипте 1.
 
Если порядок выполнения скриптов будет 1 - 2, то все будет работать, потому что на входе в скан скрипт 2 видел одно значение, и перед своим выполнением другое (которое успел изменить скрипт 1), и отработает "по изменению". Если же порядок выполнения скриптов поменять местами, то скрипт 2 перестанет, работать, так как на входе в текущий скан он будет видеть измененное значение, а новое изменение произойдет после скрипта 2 в скрипте 1.
  
=== Отладочная печать ===  
+
=== Отладочная печать === <!--T:6-->
Желательно сразу ставить после ключевых моментов логики в скриптах отладочную печать функциями TRACE, c DEBUG c номером скрипта или названием функции. Тогда эти фрагменты удобно искать и анализировать в коммуникационном логе. Однако в большой системе, когда отладочной печати становится много, становится неудобно искать необходимые данные.  
+
Желательно после ключевых моментов логики и расчетов в скриптах сразу ставить отладочную печать функциями INFO, ERROR, DEBUG, TRACE, в начале скрипта выводить его название, номер. Тогда эти фрагменты удобно искать и анализировать в коммуникационном логе. В редакторе скрипта есть отладочная консоль, в которой всегда выводится печать из функций INFO, DEBUG, ERROR, TRACE вне зависимости от системных настроек уровня лога.
Можжно поступить следующим образом - назначить свою функцию отладочной печати, которая будет вызываться только, если отладочная печать в этом скрипте разрешена. Например, можно в регистр "debug_ID" записывать id скрипта в котором нужна отладночная печать, а функция внутри скрипта будет смотреть на этот номер. Например:
+
Однако в большой системе с множеством взаимосвязанных скриптов, когда отладочной печати становится много, становится неудобно искать необходимые данные, особенно если нужно отследить конкретную цепочку выполнения нескольких скриптов, которые могут вызываться не в каждом скане, а по определенным условиям.  
 +
Кроме того, фильтрация ненужной информации в логах также полезна с точки зрения производительности, так как операции постоянной записи на флеш-карту большого объема информации могут снижать производительность системы.
 +
Можно поступить следующим образом - назначить свою функцию отладочной печати, которая будет вызываться только, если отладочная печать этого скрипта разрешена. Например, можно в строковый регистр "debug_ID" записывать id скриптов в которых нужна отладочная печать в данный момент, а функция печати внутри скрипта будет смотреть на этот номер и включаться или отключаться. Например:
 +
 
 +
<!--T:7-->
 
<syntaxhighlight lang = lua>
 
<syntaxhighlight lang = lua>
  
 
+
<!--T:8-->
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
+
 
+
 
+
</syntaxhighlight>
+
Внутри функции задавать уровень лога - INFO, ERROR и т.д. а в настройках системы временно отключать неиспользуемые уровни, чтобы сфокусироваться только на нужной отладочной информации.
+
Можно развить вариант до одновременного вывода в нескольких скриптах, если debug_ID сделать строкой вида "25 26 27 ", т.е. перечень скриптов с включенной отладкой. Номера можно быстро выделять используя строковые функции поиска паттернов:
+
 
+
 
+
<syntaxhighlight lang = lua>
+
 
------- debug printing -------
 
------- debug printing -------
  
 
+
<!--T:9-->
 +
thisScriptID = 15
 
function DEBUG_(str)
 
function DEBUG_(str)
  
 +
<!--T:10-->
 +
local i, tmp = 0, ""
  
local i = 0;
+
<!--T:11-->
local tmp = "";
+
 
+
 
+
 
while true do  
 
while true do  
i,_, tmp = string.find(str, "(%d+)%s+",i+1);
+
i,_, tmp = string.find(str, "%s+(%d+)%s+",i+1) -- искать шаблон группа цифр внутри пробелов
 
+
  
 +
<!--T:12-->
 
if i == nil  then break end  -- не найдено  
 
if i == nil  then break end  -- не найдено  
 
     if (tmp ~= "0") then  
 
     if (tmp ~= "0") then  
 
         -- найдено проверить на совпадение  
 
         -- найдено проверить на совпадение  
 
         if (tmp == tostring(thisScriptID)) then  
 
         if (tmp == tostring(thisScriptID)) then  
             ERROR(str);
+
             ERROR(str)  
 
         end  
 
         end  
 
     end  
 
     end  
Строка 101: Строка 68:
 
      
 
      
 
end -- DEBUG_
 
end -- DEBUG_
------------------------------
+
</syntaxhighlight>
  
 +
<!--T:13-->
 +
Также можно для удобства интересующие цепочки скриптов сгруппировать как строковые переменные и выбирать их в новом скрипте.
 +
<syntaxhighlight lang = lua>
  
 +
<!--T:14-->
 +
local valve_control = " 2 30 7 9 5 8 10 18  " -- script chain one
 +
local heater_control = " 20 21 23 29 43 48"  -- script chain two
 +
-- local uLimits_control = "14 25 12 13 24 38 121 "
 +
local uLimits_control = " 47 46  "
 +
local pid_control = " 45 "
 +
local test_debug_print = " 32 5 "
 +
local none = "  ";
 +
 
 +
function main (userId)
 +
    WriteReg("debug_IDs", uLimits_control); -- debug_IDs (S0@WH system)
 +
        --  DEBUG_("test new debug_ in 32 script")
 +
end
 +
 +
<!--T:15-->
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 +
 +
=== Модульность ===  <!--T:16-->
 +
Рекомендуется разбивать сложные скрипты на более простые и часто используемые функции, которые можно использовать повторно. Разбивка на более простые части, расположение их в нужном порядке и группировка помогает контролировать логику работы системы и легче наладить систему. Например, если есть задача управления положением задвижки, можно выделить такие части:
 +
:блок "захвата" момента включения привода, для инициализации начальной позиции и времени начала движения
 +
:блок расчета текущей позиции в движении (который определяет время между вызовами скрипта и вычисляет пройденный путь штока)
 +
:блок авт. управления (проверяет рассогласование между уставкой и тек. положением, определяет направление и дает команду, если нет других блокировок)
 +
:блок ручного управления
 +
:блок блокировок по наезду на конечные выключатели - для снятия текущей команды и установки позиции в 0/100%
 +
Сложные функции перед вложением их другие скрипты можно отладить отдельно в пользовательском скрипте, выделив часть внутренних регистров под входные переменные, часть под выходные. Эти же регистры вынести на дешборд, вместе с кнопкой запуска отлаживаемого скрипта. Тогда меняя входные наборы данных удобно видеть тут же результат выполнения. Также можно использовать отдельную IDE типа Eclipse для Lua или онлайн версию, чтобы удобно и быстро отлаживать маленькие фрагменты, изучать работу новых функций и т.д.
 
</translate>
 
</translate>

Текущая версия на 10:23, 23 марта 2018

Другие языки:
English • ‎русский

Инициализация проекта

Энергонезависимые регистры

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

Глобальные переменные

Переменные в скриптах, объявленные до функции main сохраняют свои значения между вызовами скрипта, но при инициализации также принимают начальные значения. Их можно использовать для хранения констант, коэффициентов и др. подобных величин.

Первый скан

Скрипту можно назначить способ запуска на первом скане при инициализации проекта и сделать некую инициализацию переменных из одного места. Иногда может понадобится определить внутри самого скрипта, был ли это первый вызов после инициализации. Можно создать глобальную переменную first_scan:

first_scan = true -- global var remember value between scans
function main (userId)
local var = 0 -- local var will be initialized in each script call 
  if first_scan then 
     first_scan = false;
    --[[
 Действия при инициализации проекта
    --]]             
 end --if 
end -- main

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

Отличия записи во внутренние регистры от регистров в устройствах

Есть некоторые отличия в работе функций SetReg и WriteReg применительно к внутренним регистрам (Dxx, DSxx). Эти функции непосредственно меняют значения внутренних регистров внутри скана, а не откладывают запись WriteReg на следующий скан. Таким образом, в конце скана внутренний регистр может иметь значение, отличное от того которые было на входе в скан. Тогда, например возможна ситуация, когда:

  • скрипт 1 меняет значение некоего регистра Dn. (выполняется в каждом скане)
  • скрипт 2 работает по изменению этого регистра Dn. (выполняется по изменению регистра)

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

Отладочная печать

Желательно после ключевых моментов логики и расчетов в скриптах сразу ставить отладочную печать функциями INFO, ERROR, DEBUG, TRACE, в начале скрипта выводить его название, номер. Тогда эти фрагменты удобно искать и анализировать в коммуникационном логе. В редакторе скрипта есть отладочная консоль, в которой всегда выводится печать из функций INFO, DEBUG, ERROR, TRACE вне зависимости от системных настроек уровня лога. Однако в большой системе с множеством взаимосвязанных скриптов, когда отладочной печати становится много, становится неудобно искать необходимые данные, особенно если нужно отследить конкретную цепочку выполнения нескольких скриптов, которые могут вызываться не в каждом скане, а по определенным условиям. Кроме того, фильтрация ненужной информации в логах также полезна с точки зрения производительности, так как операции постоянной записи на флеш-карту большого объема информации могут снижать производительность системы. Можно поступить следующим образом - назначить свою функцию отладочной печати, которая будет вызываться только, если отладочная печать этого скрипта разрешена. Например, можно в строковый регистр "debug_ID" записывать id скриптов в которых нужна отладочная печать в данный момент, а функция печати внутри скрипта будет смотреть на этот номер и включаться или отключаться. Например:

------- debug printing -------

thisScriptID = 15
function DEBUG_(str)

local i, tmp = 0, ""

while true do 
i,_, tmp = string.find(str, "%s+(%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_

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

local valve_control = " 2 30 7 9 5 8 10 18  " -- script chain one
local heater_control = " 20 21 23 29 43 48"   -- script chain two
-- local uLimits_control = "14 25 12 13 24 38 121 "
local uLimits_control = " 47 46  "
local pid_control = " 45 " 
local test_debug_print = " 32 5 "
local none = "  ";
  
function main (userId)
    WriteReg("debug_IDs", uLimits_control); -- debug_IDs (S0@WH system)
         --   DEBUG_("test new debug_ in 32 script")
end


Модульность

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

блок "захвата" момента включения привода, для инициализации начальной позиции и времени начала движения
блок расчета текущей позиции в движении (который определяет время между вызовами скрипта и вычисляет пройденный путь штока)
блок авт. управления (проверяет рассогласование между уставкой и тек. положением, определяет направление и дает команду, если нет других блокировок)
блок ручного управления
блок блокировок по наезду на конечные выключатели - для снятия текущей команды и установки позиции в 0/100%

Сложные функции перед вложением их другие скрипты можно отладить отдельно в пользовательском скрипте, выделив часть внутренних регистров под входные переменные, часть под выходные. Эти же регистры вынести на дешборд, вместе с кнопкой запуска отлаживаемого скрипта. Тогда меняя входные наборы данных удобно видеть тут же результат выполнения. Также можно использовать отдельную IDE типа Eclipse для Lua или онлайн версию, чтобы удобно и быстро отлаживать маленькие фрагменты, изучать работу новых функций и т.д.