В данном разделе представлена информация по отладке скриптов, а также примеры часто встречающихся ошибок и способы их устранения.
Как выполняются скрипты
Скрипты добавляются в очередь скриптов в том порядке, в котором они были вызваны и выполняются последовательно друг за другом. Все скрипты выполняются по наступлению какого-либо события. Например OnClick, или OnDataChange. Т.е., если пользователь сначала кликнул объект, затем снова кликнул объект, затем изменилась переменная, то скрипты выполнятся в такой же последовательности: OnClick, OnClick, OnDataChange. Секундные скрипты также выполняются последовательно, в том порядке, в котором были созданы.
Ошибки возникающие на этапе компиляции
После того как скрипт будет написан, необходимо его скомпилировать через пункт меню "Скрипт -> Компилировать" или нажав клавишу "F9". Если скрипт скомпилируется успешно, то на панели информации отобразится надпись "Компиляция завершена!", иначе будет выдан список ошибок с указанием строки, позиции и описанием ошибки, например:
Ошибки выявленные при компиляции необходимо исправить. К примеру, в скрипте выше ошибка в том, что производится недопустимая запись в переменную(TM_Variable). Для записи в переменную нужно использовать свойство Value, а в правой части скрипта, при сложении переменных использовать значение переменной приведенное к нужному типу. Исправленный скрипт:
begin
Var_1.Value := Var_2.AsInt + Var_3.AsInt;
end.
Ошибки возникающие во время работы проекта
Ошибки в скриптах, которые невозможно определить на этапе компиляции будут записаны в журнал сервера Simple-Scada. Откройте журнал сервера и просмотрите его на предмет наличия ошибок в скриптах. Если в журнале имеются скрипты с ошибками, то их нужно исправить. В журнале будет указано имя скрипта, при выполнении которого возникла ошибка и номер строки вызвавшей ошибку. Дополнительно, для отладки скриптов можно:
•запустить проект в режиме эмуляции - вместо подключения к реальным OPC-серверам, будут созданы их виртуальные версии и внешние переменные будут постоянно изменяться, принимая случайные значения в пределах шкалы переменной. Это позволит протестировать скрипты, которые выполняются по изменению переменных. Если в журнале сервера Simple-Scada будут возникать ошибки по скриптам, то их нужно исправить.
•в настройках Options.exe активировать опцию "Лог скриптов", тогда имена вызываемых скриптов будут автоматически записываться в лог-файл сервера(...\Simple-Scada 2\Logs\Server-log.txt) и просмотрев его, можно будет понять, какие скрипты и в какой последовательности вызывались.
Ниже рассмотрены наиболее частые ошибки при работе со скриптами, начиная от самых простых, к более сложным.
Ошибки в сравнениях с несколькими условиями
Для примера рассмотрим скрипт с ошибкой:
if Var_1.AsBool = True and Var_2.AsBool = True then
Отдельные условия перечисляемые в конструкции if...then должны быть заключены в скобки, иначе компилятор сначала произведет логические операции и затем сравнит результаты. Например, в скрипте выше сначала будет вычислена последняя часть: "Var_2.AsBool = True". Т.е., выполнится проверка, равна ли переменная Var_2 значению True. Если равна, то результатом проверки будет True, иначе False. Допустим, что результат False, тогда получаем:
if Var_1.AsBool = True and False then
, результатом логической операции "True and False" будет False. И, наконец, условие становится таким:
if Var_1.AsBool = False then
, его и проверит компилятор в итоге. Это может быть трудным для понимания, но чтобы исключить подобные ошибки, достаточно заключать отдельные условия в скобки. Правильный вариант скрипта:
if (Var_1.AsBool = True) and (Var_2.AsBool = True) then
также, проверку на True можно сократить, т.е. не обязательно писать "= True":
if (Var_1.AsBool) and (Var_2.AsBool) then
|
Ошибки при сравнении c True/False
Для примера рассмотрим скрипт с ошибкой:
begin
if MyVar.Value = True then
Field1.Color := clGreen;
end.
, условие данного скрипта может выполняться некорректно, если значение переменной MyVar не является типом Boolean (например, вместо False / True равно 0 / 1). Чтобы проблемы никогда не возникали, в скриптах рекомендуется всегда использовать явное приведение значения переменной к нужному типу. Например, при сравнении с True/False нужно брать значение переменной переведенное в тип Boolean (свойство AsBool):
if MyVar.AsBool = True then
, если требуется сравнить значение переменной с целым числом (0/1), то можно взять значение переменной переведенное в тип Integer(свойство AsInt):
if MyVar.AsInt = 1 then
, оба перечисленных варианта будут работать корректно.
|
Ошибочное изменение цвета объекта на черный цвет
Такая проблема может возникнуть если при изменении цвета объекта из скрипта, цвет указывается как clNone(без цвета), например:
begin
if Sender is TM_Object then // проверяем, что Sender это объект
with Sender as TM_Object do // приводим Sender к типу "TM_Object"
if AsInt = 1 then // если значение переменной объекта равно 1, то
Color := clGreen // изменить цвет объекта на зеленый
else // иначе
Color := clNone; // изменить цвет объекта на "без цвета"
end.
Константе clNone соответствует черный цвет, поэтому в приведенном выше скрипте будет происходить смена цвета с зеленого на черный. Константу clNone можно применять только при изменении цвета изображения, когда требуется показать оригинальное изображение, без наложения цвета. При работе с остальными объектами всегда известно, какие цвета будут у объекта, поэтому в скрипте нужно точно указывать необходимый цвет:
begin
if Sender is TM_Object then // проверяем, что Sender это объект
with Sender as TM_Object do // приводим Sender к типу "TM_Object"
if AsInt = 1 then // если значение переменной объекта равно 1, то
Color := clGreen // изменить цвет объекта на зеленый
else // иначе
Color := clRed; // изменить цвет объекта на красный
end.
|
Ошибки в скриптах по событию OnDataChangeEx
Скрипты по событию OnDataChangeEx обычно используются, когда требуется изменять какие-либо свойства объекта по изменению дополнительной переменной связанной с объектом. Здесь важно отличать основную и доп. переменную связанную с объектом. Для обращения к дополнительной переменной объекта из скрипта нужно использовать свойство VariableEx. Часто пользователи пишут скрипт на событие OnDataChangeEx объекта, но в скрипте по ошибке работают с основной переменной вместо дополнительной, из-за чего создается впечатление что скрипт не работает. Пример скрипта:
begin
if Sender is TM_Object then // проверяем, что Sender это объект
with Sender as TM_Object do // приводим Sender к типу "TM_Object"
if AsBool = True then // здесь идет работа с основной переменной
Visible := True // показать объект
else // иначе
Visible := False; // скрыть объект
end.
, в данном скрипте идет работа с основной переменной, поэтому если такой скрипт назначить на событие OnDataChangeEx, то он будет работать неправильно. Если требуется изменять свойства объекта в зависимости от значения доп. переменной, то нужно использовать свойство VariableEx:
begin
if Sender is TM_Object then // проверяем, что Sender это объект
with Sender as TM_Object do // приводим Sender к типу "TM_Object"
if VariableEx.AsBool = True then // если значение доп. переменной объекта = True
Visible := True // показать объект
else // иначе
Visible := False; // скрыть объект
end.
Также, при использовании скриптов по событиям OnDataChange, OnDataChangeEx пользователи часто забывают назначить объекту основную/доп. переменную или путают местами скрипты OnDataChange, OnDataChangeEx, например пишут скрипт по событию OnDataChangeEx, а назначают его на событие OnDataChange. Поэтому, если такой скрипт не выполняется или работает неправильно, то следует внимательно проверить возможные ошибки описанные выше.
|
Ошибки при сложении строк
Важно! Описанные здесь ошибки при работе со строками возможны только в версиях ниже 2.5.13.0. Начиная с версии 2.5.13.0 все свойства и методы работающие со строками имеют тип String и не требуют дополнительного преобразования перед сложением.
При работе со строками, например при использовании функций отправки E-mail/SMS/Telegram, а также при записи в текстовые файлы и т.д., полученный текст может отображаться в нечитаемом виде(вместо букв отображаются символы). Это происходит из-за того, что в скрипте формирования строки производится сложение строк с разными типами данных, например UTF8String + String, что недопустимо. При сложении строк, нужно обязательно привести все части текста к какому-то одному типу данных используя функции для работы со строками, тогда итоговая строка всегда будет формироваться правильно. Рассмотрим пример скрипта, в котором неправильно складываются строки разных типов:
begin
AddMessageToGroup(Now, mkMessage, 9, Field1.Hint + ': Нет ошибок', False, False);
TextFileWriteLn(DateTimeToStr(Now) + 'Ошибка!');
SendTelegram('Авария котла!' + MyVar.Description, '258578874');
end.
, в каждой строке данного скрипта производится сложение строк с разными типами данных, что будет приводить к нечитаемому результату. Описание любого свойства или метода и его тип данных можно посмотреть в руководстве по скриптам, а также установив курсор на требуемое свойство и нажав сочетание клавиш "Ctrl + Пробел", после чего появится всплывающее окно с описанием. Например, в первой строке производится сложение текста подсказки(Hint) объекта "Field1" с произвольной строкой. Строки, которые в коде заключены в кавычки всегда имеют тип данных String. Посмотрев синтаксис метода AddMessageToGroup, мы увидим, что текст сообщения имеет тип String. Далее, посмотрим синтаксис свойства Hint - как видим, оно имеет тип данных UTF8String. Т.е., в итоге производится сложение UTF8String + String, поэтому нужно привести строку к одному типу, в данном случае к типу String. Для этого, используя функции для работы со строками преобразуем подсказку Hint в тип String:
AddMessageToGroup(Now, mkMessage, 9, UTF8ToString(Field1.Hint) + ': Нет ошибок', False, False);
по аналогии просмотрев синтаксис нужных свойств исправим остальные строки скрипта:
begin
AddMessageToGroup(Now, mkMessage, 9, UTF8ToString(Field1.Hint) + ': Нет ошибок', False, False);
TextFileWriteLn(UTF8ToString(DateTimeToStr(Now)) + 'Ошибка!');
SendTelegram('Авария котла!' + UTF8ToString(MyVar.Description), '258578874');
end.
|
Ошибка "Обращение к несуществующему объекту" (Access violation)
Если при выполнении скрипта возникает ошибка "Обращение к несуществующему объекту" (Access violation), то выполнение скрипта прерывается на строке, которая вызвала ошибку и скрипт не выполняется дальше. Сообщение об ошибке с указанием номера строки, в которой возникла ошибка выдается в журнал сервера. Ошибка говорит о том, что в скрипте выполняется обращение к несуществующему объекту. Пример ошибки:
Ошибка в скрипте "Button1_OnClick" в строке 5. Обращение к несуществующему объекту (00476E74. Write of address 00000268)
Причин возникновения такой ошибки может быть много. Например, можно создать таблицу с 10 строками, а в коде обратиться к 11 строке. Или создать таблицу с 2 колонками, а в коде обратиться к несуществующей 3 колонке. Можно выполнить поиск переменной по несуществующему имени и попытаться обратиться к ненайденной переменной. Можно написать универсальный скрипт с обращением к переменной объекта, назначить его объектам, но забыть привязать переменную к объекту и т.д. Во всех этих случаях, будет возникать ошибка "Обращение к несуществующему объекту", т.к. скрипт написан с ошибками. К сожалению, невозможно как-то запретить это, т.к. на этапе разработки и написания скрипов компилятор ничего не знает о том, какие значения будут у указателей и объектов. Чтобы таких ошибок не возникало, нужно внимательно писать код скриптов и при необходимости добавлять проверки на существование объекта. Ниже рассмотрены несколько примеров ошибки "Обращение к несуществующему объекту".
|
Ошибки при использовании функций поиска
При выполнении скриптов, в которых используются функции поиска возможно возникновение ошибки "Обращение к несуществующему объекту" (Access violation) в журнале сервера, если в скрипте нет проверки существования переменной/объекта. Пример скрипта, который может приводить к ошибке:
var
aVar: TM_Variable;
begin
aVar := GetVariableByName('MyVariable'); // ищем переменную по имени
aVar.Value := 15; // записываем в переменную значение 15
end.
, в данном скрипте происходит поиск переменной по имени и затем в нее записывается значение 15. Но в скрипте не учитывается случай, когда переменная не будет найдена, например если переменной с таким именем не существует в проекте или изменилось имя переменной. В этом случае, произойдет попытка записи в несуществующую переменную, что приведет к ошибке "Обращение к несуществующему объекту" и скрипт не выполнится. Описанная ситуация касается использования любых функций поиска - перед тем как работать в скрипте с найденной переменной/объектом, нужно обязательно убедиться, что данная переменная/объект существует. Т.к. функции поиска возвращают "nil" если переменная/объект не найдены, то достаточно добавить в скрипт проверку на "nil".
Исправленный скрипт:
var
aVar: TM_Variable;
begin
aVar := GetVariableByName('MyVariable'); // ищем переменную по имени
if aVar <> nil then // если переменная существует
aVar.Value := 15; // то, записываем в нее значение 15
end.
|
Ошибки при работе с ячейками таблицы из скрипта
Данные ошибки аналогичны примеру рассмотренному выше. При выполнении скриптов, в которых производится работа с ячейками таблицы, возможно возникновение ошибки "Обращение к несуществующему объекту" (Access violation) в журнале сервера, если в скрипте нет проверки существования ячейки. Особенно часто такие ошибки возникают при обращении к ячейкам таблицы из цикла. Пример скрипта, который может приводить к ошибке:
var
aCell: TM_TableCell;
begin
aCell := Table1.GetCell(5, 5);
aCell.Color := clRed;
end.
, в данном скрипте происходит получение ячейки таблицы и затем изменяется цвет фона найденной ячейки. Но в скрипте не учитывается случай, когда ячейка не будет найдена, например если данной ячейки нет в таблице. В этом случае, произойдет попытка изменения цвета несуществующей ячейки, что приведет к ошибке "Обращение к несуществующему объекту" и скрипт не выполнится. Перед тем как работать в скрипте с ячейкой таблицы, рекомендуется проверять, что данная ячейка существует. Для этого, достаточно добавить в скрипт проверку на "nil".
var
aCell: TM_TableCell;
begin
aCell := Table1.GetCell(5, 5);
if aCell <> nil then
aCell.Color := clRed;
end.
|
Переполнение очереди скриптов, бесконечные скрипты
Характерные признаки:
Проект работает медленнее чем обычно, возможны задержки при обновлении значения переменных, задержки при смене цвета объекта из скрипта, и другие задержки связанные с выполнением скриптов. Также, может создаваться впечатление, что скрипты не выполняются. Выключение сервера Simple-Scada или остановка проекта происходит с задержкой.
Причины:
В скриптах допущены грубые ошибки (например бесконечные циклы, зацикленные друг на друга скрипты и т.д.), которые приводят к бесконечному росту очереди скриптов. Из-за этого очередь скриптов переполняется, очищается и скрипты накопленные в очереди не выполняются. Пример зацикленных друг на друга скриптов:
// скрипт по изменению переменной vrA
procedure vrA_OnDataChange(Sender: TM_Control);
begin
vrB.Value := Random(100); // меняем значение переменной vrB
end.
// скрипт по изменению переменной vrB
procedure vrB_OnDataChange(Sender: TM_Control);
begin
vrA.Value := Random(100); // меняем значение переменной vrA
end.
В подобных ситуациях, в журнал сервера Simple-Scada будет выдано сообщение "Стек скриптов переполнен...", содержащее список скриптов, которые вызываются чаще всего. Необходимо проверить и исправить ошибки в данных скриптах. Дополнительно, для поиска ошибок в скриптах, можно в настройках Options.exe активировать опцию "Лог скриптов". Тогда имена вызываемых скриптов будут автоматически записываться в лог-файл сервера(...\Simple-Scada 2\Logs\Server-log.txt) и просмотрев его, можно будет понять, какие скрипты и в какой последовательности вызывались.
|