Вопросы по скриптам в Simple-Scada 2

Автор Simple-Scada, 29 января 2016, 15:19:20

TeNQ

#15
Написал простой скрипт, может кому-нибудь понадобится.
Задача. Я ввожу в поле ввода секунды, должен произойти пересчет, и в OPC должны уйти миллисекунды.
Ограничение ввода от 0 до 20 секунд.
Как это сделать.
Имеется переменная OPC: Имя Time_vibro, тип Integer, шкала от 0 до 20000, формат вывода 0 в которой будет хранится значение в миллисекундах.
Я создаю виртуальную переменную, например, a1. Тип integer, шкала от 0 до 20. Формат вывода 0. В ней будет хранится промежуточное значение в секундах.
Создаю поля ввода Field1 для ввода секунд. Переменная для этого поля будет а1. Также у него есть событие OnDoneInput. Оно будет вызвано по окончанию ввода в поле. Cоздаю следующий скрипт.
Код: pascal
Procedure Field1_OnDoneInput(Sender: TM_Control);
begin
  Time_Vibro.Value := Field1.ValueAsInt  * 1000;
end.  
Что он делает. Допустим мы ввели в поле секунд 2. Это значение будет выведено в Field1.ValueAsInt.  Умножением на 1000 переведу секунды в миллисекунды и сохраню результат в переменную OPC.
Все работает, но. Так как виртуальная переменная не хранит результат после закрытия скады, то при перезапуске скады, мы в поле ввода увидим 0. Хотя какое-то значение записано в Time_Vibro.Value.
Значит нужно считать его из OPC в поле ввода секунд с обратным преобразованием.
Я это сделал так.
Создал вспомогательное поле ввода Field2. Сделал его недоступным и невидимым. Переменной для этого поля выбрал Time_Vibro. И использовал событие OnDataChange.
Код: pascal
Procedure Field2_OnDataChange(Sender: TM_Control);
begin
  Field1.Value := Time_Vibro / 1000
end.
При загрузке скады произойдет сравнение значение переменной Time_Vibro в клиенте скады и значение переменной Time_Vibro в OPC сервере. И если будет обнаружено различие вызовется событие OnDataChange. Миллисекунды переведем в секунды делением и полученное значение присвоим полю ввода секунд.
Так можно преобразовывать любые значение при вводе, а также дополнив скрипт OnDoneInput проверять введенное значение на соответствие нужным условиям и соответственно принимать какие то действия.

TeNQ

#16
Чуть посложнее.
Имеется функция генерирующая программно ШИМ. Есть два поля ввода. Ввод в скаде в секундах, передаем в контроллер в мс. Длительность импульса и период следования. Длительность импульса не должна превышать период следования, функция этого не переварит. Следовательно нужно ввести проверку на ввод пользователя и сделать следующее:
1) Если введенная длительность импульса(с) не превышает период следования(мс), то преобразуем в мс и передаем контроллеру.
2) Если превышает, то делаем длительность импульса(мс) равной периоду следования(мс). Получаем постоянный сигнал на выходе.
Код: pascal
Procedure Field43_OnDoneInput(Sender: TM_Control);
var
  Vvod_s: Integer;
  Time_ms: Integer;
  Period_ms: Integer;
begin
  Vvod_s    := Field43.ValueAsInt;
  Time_ms   := D450.ValueAsInt;
  Period_ms := D451.ValueAsInt;

  if Vvod_s < Period_ms/1000 then
    D450.Value  := Vvod_s * 1000 
  else
    D450.Value  := Period_ms;
end.
Vvod_s - введенное в поле значение длительности импульса в сек. (Вирт. переменная)
Time_ms - преобразованное значение длительности импульса в мс (Переменная OPC сервера)
D450.Value оно же, но для присвоения.
Period_ms - преобразованное значение периода следования в мс (Переменная OPC сервера)

Simple-Scada

#17
Отличное и неожиданное решение задач! Но, т.к. в первой задаче у вас прямая зависимость между значением переменной на OPC-сервере и значением отображаемом в поле, то здесь можно было бы обойтись и вовсе без скриптов. Решение первой задачи без скриптов выглядит так:

- добавляем новую переменную с OPC-сервера которая хранит значение времени в миллисекундах;
- ставим этой переменной смещение запятой = -3;
- ставим формат переменной = 0;
- ставим шкалу для этой переменной 0-20000;

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

Этой задачей Вы навели нас на мысль о том, что нужно добавить объектам Поле и Уровень два свойства: "Визуальный минимум" и "Визуальный максимум" (такие свойства были в старой версии скады, но мы их убрали). С их помощью можно было бы работать с переменными в любой собственной шкале, даже без прямой зависимости.

TeNQ

#18
Цитировать- добавляем новую переменную с OPC-сервера которая хранит значение времени в миллисекундах;
- ставим этой переменной смещение запятой = -3;
- ставим формат переменной = 0;
- ставим шкалу для этой переменной 0-20000;
Красиво. Но мне все равно нужна была проверка.

У меня  есть проблема. Есть два поля ввода. К ним привязаны переменные
D442 Тип Integer Шкала мин 0 макс 1 Формат 0 Сдвиг 0 - Привязано к Field28
D443 Тип Integer Шкала мин 0 макс 1 Формат 0 Сдвиг 0
Суть в чем, эти переменные отвечают за очередность выбора.
0 - первая компонента
1 - вторая компонента
Одновременно они не могут быть первыми или вторыми (допустим) Соответственно, если я ввожу 1 в первое поле мне нужно автоматически ввести 0 во второе. И также если я ввожу 0, то 1 во второе. Ручного ввода во второе поле пока не касаюсь.
Создаю простейший скрипт.

Код: pascal
Procedure Field28_OnDoneInput(Sender: TM_Control);
begin
if field28.Value = 1 then D443.Value  := 0 else D443.Value  := 1;
end.      
И он не работает. Я ввожу в поле Field28 1 и получаю 1 во втором; 0 и 0; 2(огр. до 1) = 1 и 0. Продолжаю вводить 1, получаю 0; ввожу 0, получаю 0. Вообщем я этого никак не могу понять.
Заменяю переменные на виртуальные того же типа. Все ок. 1 и 0; 0 и 1; 2(огр. до 1) = 1 и 0.
Оставляю привязанной к первому полю виртуальную переменную, а кто второму OPC переменную D443. Все ок.
Привязываю к первому опять OPC переменную и опять ерунда. Переменные при обмене не пересекаются, в контролере тоже не влияют друг на друга. Что это может быть.
Очень не хватает отладчика, где можно пошагово посмотреть, что происходит на исполнении скрипта и значения переменных во время исполнения.

Simple-Scada

Скрипт не работает, потому что он выполняется по событию OnDoneInput. Т.е. сразу же после того как на клиенте пользователь ввёл значение в поле, скрипт проверяет значение переменной, связанной с этим полем и в зависимости от этого значения меняет переменную другого поля. Ошибка состоит в том, что внешняя переменная не меняет свое значение моментально. После того, как клиент ввел новое значение в поле, значение переменной не изменяется! Новое значение сначала отправляется на сервер, чтобы он записал его в OPC-тег. И только после того, как значение успешно записалось в OPC-тег - клиент получает новое значение переменной. Т.е. на момент выполнения вашего скрипта, значение "field28.Value" всё ещё неизменно, несмотря на то, что пользователь ввел в поле новое значение. А вот с виртуальными переменными всё работает. Ведь им не требуется время на запись, они всегда в памяти и меняются моментально и на момент события OnDoneInput виртуальная переменная уже имеет введенное пользователем значение.

Simple_Scada

#20
Еще один небольшой пример.

Рассмотрим случай, когда нам нужно сделать мигание объекта при определенном значении связанной с ним переменной. Для этого:
1. В свойстве "Переменная" объекта выбираем нужную нам переменную.
2. В инспекторе объектов переходим на вкладку События и дважды кликаем по событию OnDataChange (изменение значения переменной связанной с объектом).
3. Пишем скрипт, например для изображения:

Код: pascal
begin
  { отключаем мигание установив цвет мигания «без цвета» - таким образом не нужно будет 
    отдельно отключать мигание отдельно для каждого условия ни-же. }
  Image1.FlashColor := clNone;
  case Image1.ValueAsInt of             // если значение переменной равно:
    1: Image1.Color := clRed;           // 1 - изменить цвет на красный.
    2:
    begin
      Image1.Color := clNone;           // 2 – изменить цвет на «без цвета» - для того, чтобы мигание было только зеленым цветом, а не с красного на зеленый.   
      Image1.FlashColor := clGreen;     // и включаем мигание зеленым цветом.
    end;
    3: Image1.Color := clGreen;         // 3 - меняем цвет на зеленый.
  end;
end.
Все объекты в проекте, для которых применяется мигание будут мигать синхронно - по умолчанию с частотой 1 секунда. Управление частотой мигания будет доступно в следующих обновлениях.

Simple_Scada

#21
Рассмотрим работу с файлами для ситуации, когда нам нужно сохранить различные данные в файл, а потом загрузить их в различные объекты.
Создадим две кнопки – для сохранения в файл и для загрузки из файла. В событии OnClick первой кнопки напишем скрипт для сохранения в файл, например:
Код: pascal
begin
  if FileOpen('Hello.sbm') then  // открыть если существует или создать файл Hello.sbm (расширение вы можете использовать любое)
  begin
    FileWriteString(Field1.ValueAsStr);      // записать в файл строку из поля
    FileWriteInteger(Field2.ValueAsInt);     // записать в файл целое число из поля
    FileWriteSingle (Field3.ValueAsFloat);   // записать в файл вещественное число из поля
    FileWriteString (Text1.Text);            // записать в файл содержимое текста
    FileWriteDateTime(Now);                  // записать в файл текущую дату и время
    // закрывать файл не обязательно, Simple-Scada сделает это автоматически.  
  end;
end.

В событии OnClick второй кнопки напишем скрипт для чтения данных из файла, например:
Код: pascal
begin
// нужно считывать все данные в той последовательности, в которой мы их записывали
  if FileOpen('Hello.sbm') then    // открыть файл если существует
  begin
    Field1.Value := FileReadString;     // считать в переменную поля строку
    Field2.Value := FileReadInteger;    // считать в переменную поля целое число
    Field3.Value := FileReadSingle;     // считать в переменную поля вещественное число
    Text2.Text   := FileReadString;     // считать в текст строку
    Text3.Text   := DateTimeToStr(FileReadDateTime);// считать в текст время и дату
  end;
end.

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



Работа с файлами будет доступна после сегодняшнего обновления.

Al_Kuz

Здравствуйте! Не получается работа с файлом из окна. Файл создается, но записи и чтения не происходит. Со страницы запись/чтение в норме. Может, что не так делаю, проект прилагаю.

Pr_Alex

#23
Добрый день!
Возможно ли в скриптах динамически создавать объекты, что-то типа:

Код: pascal
procedure TForm1.Button1Click(Sender: TObject);
var 
   Button : array [1..20] of TButton;
begin
  for i:=1 to 20 do
  begin
    Button[i]:=TButton.Create(Form1);
    Button[i].Parent:=Form1;
    Button[i].Left:=80;
    Button[i].Top:=80;
    Button[i].Name:='Buttons'+IntToStr(i); 
  end;
end;
...и обращаться по имени к объектам, типа:
Код: pascal
var
  i:integer;
begin
  for i:=1 to 20 do 
    (FindComponent('Edit'+IntToStr(i)) as TEdit).Text:='0';
end;

Simple-Scada

#24
Цитата: Al_Kuz от 08 февраля 2016, 13:29:17Не получается работа с файлом из окна. Файл создается, но записи и чтения не происходит.
Спасибо, сейчас займемся этим вопросом.

Цитата: Pr_Alex от 08 февраля 2016, 13:54:23Возможно ли в скриптах динамически создавать объекты, что-то типа:
Нет, это запрещено по многим причинам (из соображений быстродействия, безопасности в коде, работы по сети и др.). Если вы хотите, чтобы 20 кнопок появились после выполнения скрипта, то их обязательно нужно предварительно создать в редакторе и, к примеру, сделать скрытыми выключив свойство "Видимость". А скриптом придется просто включать видимость, но для каждой кнопки это придется делать отдельно:

Код: pascal
begin
  Button1.Visible := True;
  Button2.Visible := True;
  ...
  Button20.Visible := True;
end;

А как в примере с FindComponent можно было бы осуществить, если мы добавим такую функцию в скрипты. Правда такая функция будет значительно медленнее, чем обращение к объекту по имени напрямую, ведь придется искать её вреди всех объектов проекта. Поэтому подобные скрипты нежелательны и по возможности их лучше избегать. Мы подумаем ещё по поводу FindComponent.

TeNQ

OnExit – скрипт выполнится при выходе из проекта.
А что считается выходом из проекта? В версии 1 можно было настроить кнопку по которой осуществляется выход. А во 2 я такой опции не нашел. По ESC из проекта не выйдешь как раньше, я просто жму ALT+F4. При этом у меня либо скрипт исполняется неверно, либо событие не происходит. И я пока не пойму почему.

Simple-Scada

Выходом считается реальное завершение работы проекта на сервере. Сейчас мы планируем также добавить события OnInitClient и OnExitClient, которые будут выполняться  по запуску/выходу из клиента.

ЦитироватьВ версии 1 можно было настроить кнопку по которой осуществляется выход. А во 2 я такой опции не нашел.
Да, мы забыли её включить. Сейчас по Shift+ESCAPE осуществляется выход.

Al_Kuz

По работе с файлами. Часто для работы оборудования требуются некие параметры. Набор таких параметров или рецептов создается пользователем и хранится в файле. Вот мыслю, как это сделать имеющимися в S-S2 возможностями. Если в файл записывать только данные числового типа, то каждая запись группы параметров имеет фиксированную длину в байтах, но пользователю нужен не только номер набора параметров или рецепта, но и название (имя). Строчные данные имеют разную длину, поэтому не одинаковую длину будет иметь и группа параметров. Как организовать поиск и считывание из файла нужных мне параметров?

Поскольку синтаксис скриптов S-S2 похож на синтаксис Дельфи, то подсмотрел в справке как там работают напрямую с файлом. Было бы не плохо научить S-S2 компиль cоздавать типизированные файлы, а так же писать и читать переменные типа record.

Simple-Scada

#28
ЦитироватьСтрочные данные имеют разную длину, поэтому не одинаковую длину будет иметь и группа параметров. Как организовать поиск и считывание из файла нужных мне параметров?
Допустим Вы записываете в файл следующим образом:

Код: pascal
begin
  if FileOpen('MyFile.sfl') then
  begin
    FileWriteInteger(10);              // первое целое число
    FileWriteString('Привет, мир!');   // строка
    FileWriteInteger(20);              // второе целое число
    // ... и т.д.
  end;
end;
При чтении, чтобы добраться до второго числа в файле, Вы должны выполнить чтение первых двух параметров в той же последовательности.

Код: pascal
var
  I: Integer;
  S: UTF8String;
begin
  if FileOpen('MyFile.sfl') then
  begin
    I := FileReadInteger;    // первое целое число
    S := FileReadString;     // строка
    I := FileReadInteger;    // второе целое число
    // ... и т.д.
  end;
end;
Для случая, когда в файле хранятся строки (т.е. динамические данные), только так. Т.е. чтобы переместиться в файле на длину строки - нужно её считать. Как мы поняли Вы хотите хранить в одном файле несколько рецептов, а затем, при необходимости, загружать нужный рецепт из файла. А так как рецепт содержит строки, быстро перейти к нужному рецепту в файле не получается. Нужно будет читать рецепты до него, как в примере выше. Если это так, то здесь можно просто хранить рецепты в разных файлах и всегда читать нужный рецепт, без лишних данных.

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

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

Al_Kuz

#29
Цитироватьможно просто хранить рецепты в разных файлах и всегда читать нужный рецепт, без лишних данных.
ЦитироватьЕсли хранить их нужно строго в одном файле, то можно предложить ещё одно решение. Перед сохранением рецепта в файл, можно сохранять в него и длину рецепта (т.е. суммарную длину всех параметров рецепта), как целочисленное значение.
Эти варианты я рассматривал. В первом варианте нужно создать может быть полсотню файлов и не понятно как искать
нужный файл. Во втором - можно только дополнять файл, если отредактировать нужный рецепт, то все последующие будут не доступны для чтения. Идеальный вариант как в Дельфи:

Код: pascal
type
TRecipe = record		//Создаем структуру типа запись
  number:integer;
  name  :string[16];
  weight:integer;
 end;
 
var
 Recipe: TRecipe;		//переменная записи
 RecipeF: file of TRecipe;	//файловая переменная

begin
    Recipe.number:= Field1.Value;//присвоение значений 
    Recipe.name:= Field2.Value;
    Recipe.weight:= Field3.Value;
       
  write(RecipeF,Recipe);	//записать данные в файл. Каждая запись имеет строго определенную длину.
end. //Как то так. Вы и без меня знаете.