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

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

pan2000

   Здравствуйте.

Цитата: PeninAndrey1996 от 28 июля 2025, 13:52:25... но если авария уже была и появилась новая, то значение переменной не изменилось и скрипт открытия окна не выполнится пока мы не сбросим все аварии...
Возможны два варианта:
- использовать для скрипта открытия окна изменение переменной "Счетчик ошибок";
- повторно открывать окно при попытке закрыть окно с ошибками (подобно австралийцу, который пытается выбросить старый бумеранг).

В примере два варианта:
- вызов скрипта открытия окна по счетчику ошибок;
- вызов скрипта непосредственно по битам ошибок (для случая когда все ошибки доступны в окне).
Окно закрывается после сброса всех ошибок.
Второй вариант предпочтительней для случая большого количества окон (особенно шаблонных), т.к. скрипт "Изменились переменные" будет либо включать длинные списки переменных с какой-то логикой выборки переменных для конкретного окна, либо много-много отдельных скриптов.

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

Серега

#1636
Добрый день!
Скрипт универсальный, вешается на тексты с разным значением текста, но значение текста может повторяться, на событие onClick.
Нажимаем на текст с инфо и стрелка показывает где это расположено.
Писать для каждого текста через Name объекта можно, работает. Но хотел подсократить объем кода.
Понятно что вызывающий скрипт объект TM_Control. Но мы как бы приводим его к типу TM_Text, а у него уже свойство .Text есть.
Можете объяснить почему данный скрипт вызывает ошибку 'Undeclared identifier "Text", ... ':
Код: pascal
  with (Sender as TM_Text) do
    shWhereProblem.Visible := True;
    if Sender.Text = 'A2.2' then
    begin
      shWhereProblem.X := 506;
      shWhereProblem.Y := 744;
    end;

Хотел бы услышать мнение разработчиков.

Simple-Scada

Если код не заключен в begin..end, то действие будет распространяться только на первую строку.
Также не нужно писать "Sender.Text", когда выше уже использовали "with (Sender as TM_Text) do", можно сразу обращаться к свойству Text.
Исправленный код:
Код: pascal
with (Sender as TM_Text) do
begin
  shWhereProblem.Visible := True;
  if Text = 'A2.2' then
  begin
    shWhereProblem.X := 506;
    shWhereProblem.Y := 744;
  end;
end;

Ещё можно делать так (проще для понимания):
Код: pascal
var
  aText: TM_Text;
begin
  aText := Sender as TM_Text;
  shWhereProblem.Visible := True;
  if aText.Text = 'A2.2' then
  begin
    shWhereProblem.X := 506;
    shWhereProblem.Y := 744;
  end;
end.

Серега

Как то упустил из виду что для with тоже надо использовать begin..end. Все заработало.
2ой способ как то нагляднее. уже завтра попробую.
Спасибо!

OlegM

#1639
Добрый день.

Помогите, пожалуйста, разобраться со скриптом.
Реализовал свою простенькую функцию FormatStr.
Не всегда, но достаточно часто вижу в логах сервера, вот такую ошибку на этот скрипт: 'Ошибка в скрипте Utils в строке 192. Обращение к несуществующему объекту (017C0647 . Read of address 00000028)'.
Utils -- это мой глобальный модуль, а строка 192 -- это завершающий мою функцию `end;` .

Вот сама функция:
Код: pascal
// === Format String ===============================================================================
// =================================================================================================
// Подставляет в строку вместо %s элементы из массива.
// Может принимать элементы для подстановки разных типов (Variant).
// Каждый элемент для подстановки рассматривается внутри функции как тип Variant,
//   который затем приводится к строке через VarToStrDef().
// Экранирование процента делается так: '%%' -> '%'.


// Стандартная делфишная функция Format() не поддерживается.


// Пример:
// FormatStr('Процент: %s%%, значение: %s.', [85, 'Ok']);
// => 'Процент: 85%, значение: Ok.'


function FormatStr(const AString: string; const Args: array of Variant): string;
var
  I, ArgIndex: Integer;
  NextSeq: string;
  V: Variant;
begin
  Result := '';
  ArgIndex := 0;
  I := 1;


  while I <= Length(AString) do
  begin
    if (AString[I] = '%') and (I < Length(AString)) then
    begin
      NextSeq := Copy(AString, I, 2); // например: '%s', '%%', '%x'


      if NextSeq = '%%' then
      begin
        Result := Result + '%';
        Inc(I, 2);
      end
      else if NextSeq = '%s' then
      begin
        if (Length(Args) > 0) and (ArgIndex <= High(Args)) then
        begin
          V := Args[ArgIndex];
          Result := Result + VarToStrDef(V, '<unsupported>');
          Inc(ArgIndex);
        end
        else
          Result := Result + '<missing>';
        Inc(I, 2);
      end
      else // неизвестная последовательность -- оставляем просто '%'
      begin
        Result := Result + '%';
        Inc(I);
      end;
    end
    else
    begin
      Result := Result + AString[I];
      Inc(I);
    end;
  end;
end; 
PS: Странно, что эта функция не поддерживается из коробки. Очень удобно ей пользоваться, например, при формировании sql-запросов с несколькими подстановками.

Вот код вызова, он иногода выполняется без ошибок, а иногда с этой ошибкой по памяти.
Но! Если перед этим кодом добавить строку для записи в виртуальную переменную любого значения, то всегда работает без ошибок.
Код: pascal
    Test.Value := 'Ok'; // С этой строкой FormatStr всегда работает без ошибок.
    GetVariableByName(FormatStr('Ch%s_PrgName',   [ChamberNo])).Value :=           tblPrograms.GetCell(Ord( colPrgName      ), SelRow).Text;
    GetVariableByName(FormatStr('Ch%s_BoardWood', [ChamberNo])).Value :=           tblPrograms.GetCell(Ord( colPrgBoardWood ), SelRow).Text;
    GetVariableByName(FormatStr('Ch%s_BoardThk',  [ChamberNo])).Value := StrToInt( tblPrograms.GetCell(Ord( colPrgBoardThk  ), SelRow).Text );

OlegM

#1640
Переделал на функцию без Variant: вместо %s функция подставляет только строки из массива.
Проблема осталась. По прежнему выбивает эту ошибку 'Ошибка в скрипте Utils в строке 262. Обращение к несуществующему объекту (017C0647 . Read of address 00000028)'.
Код: pascal
// === FormatStr ===============================================================================
// =============================================================================================
// Функция подставляет в строку вместо '%s' строки из массива.

function FormatStr(const S: string; const Args: array of string): string;
var
  I, ArgIndex: Integer;
  NextChar: Char;
begin
  Result := '';
  ArgIndex := 0;
  I := 1;

  while I <= Length(S) do
  begin
    if (S[I] = '%') and (I < Length(S)) then
    begin
      NextChar := S[I + 1];

      if NextChar = '%' then
      begin
        Result := Result + '%';
        Inc(I, 2);
      end
      else if NextChar = 's' then
      begin
        if ArgIndex < Length(Args) then
        begin
          Result := Result + Args[ArgIndex];
          Inc(ArgIndex);
        end
        else
          Result := Result + '<missing>';
        Inc(I, 2);
      end
      else
      begin
        // неизвестная последовательность — оставляем просто '%'
        Result := Result + '%';
        Inc(I);
      end;
    end
    else
    begin
      Result := Result + S[I];
      Inc(I);
    end;
  end;
end;

OlegM

#1641
Если сделать через конкатенацию, то все выполняется без ошибок.
Код: pascal
    // Выполняется без ошибок.
    GetVariableByName('Ch'+ IntToStr(ChamberNo) +'_PrgName'  ).Value :=           tblPrograms.GetCell(Ord( colPrgName      ), SelRow).Text;
    GetVariableByName('Ch'+ IntToStr(ChamberNo) +'_BoardWood').Value :=           tblPrograms.GetCell(Ord( colPrgBoardWood ), SelRow).Text;
    GetVariableByName('Ch'+ IntToStr(ChamberNo) +'_BoardThk' ).Value := StrToInt( tblPrograms.GetCell(Ord( colPrgBoardThk  ), SelRow).Text );

Simple-Scada

#1642
Явно как-то портится память (скорее всего связанная со строками). Можете выслать на support@simple-scada.com проект в котором воспроизводится ошибка?

yurgin_757

#1643
Здравствуйте!
...делаю тестовый проект, опрос МВ110-8А, 6 датчиков и два порта сухие контакты
Возникла потребность в коррекции данных приходящих от датчиков
применил скрипт:
Код: pascal
begin
  result.Value := t_1.Value + corr.Value;
end.
...он работает, но работает именно только с тем датчиком к которому привязан!
В связи с этим вопрос, как сделать универсальный скрип, а не жестко привязанный к ОПС переменной (в последующем их ожидается около 1000 шт)

Simple Scada

#1644
Здравствуйте.

Можно использовать следующие способы:
1. Если требуется преобразовать значение из одной шкалы в другую, то можно использовать режим масштабирования переменной.
2.1. Создать внутреннюю переменную (в которую будем записывать результат вычисления) с именем, как у исходной переменной + приставка "_Corr". Например, для переменной "vrTemp" -> "vrTemp_Corr". Далее создать скрипт с типом события "Изменились переменные", добавить внешнюю переменную, изменение которой необходимо отслеживать, в список скрипта и написать такой скрипт:
Код: pascal
var
  aTemp_Corr: TM_Variable;
begin
  aTemp_Corr := GetVariableByName(Variable.Name + '_Corr');  // выполняем поиск переменной vrTemp_Corr
  if aTemp_Corr <> nil then                                  // если переменная существует, то
    aTemp_Corr.Value := Variable.AsInt + corr.AsInt;         // записываем в нее значение переменной vrTemp + значение переменной "corr"
end.
Теперь в переменную "vrTemp_Corr" будет записываться значение переменной "vrTemp" + значение переменной "vrTemp_Corr". Данный скрипт универсальный, если таких переменных несколько, то их все можно добавить в список скрипта и он будет выполняться для каждой переменной отдельно.
Данный пример подойдет для группы переменных с одинаковым значением корректировки.
2.2. Если для каждой внешней переменной будет уникальное значение корректировки, то для переменных корректировки также нужно задать однотипные имена, например "CorrValue_" + имя исходной переменной, и использовать следующий код скрипта:
Код: pascal
var
  aTemp_Corr, aCorr: TM_Variable;
begin
  aTemp_Corr := GetVariableByName(Variable.Name + '_Corr');  // выполняем поиск переменной vrTemp_Corr
  aCorr := GetVariableByName('CorrValue_' + Variable.Name);  // выполняем поиск переменной CorrValue_vrTemp
  if (aTemp_Corr <> nil) and (aCorr <> nil) then             // если переменные существуют, то
    aTemp_Corr.Value := Variable.AsInt + aCorr.AsInt;        // в переменную vrTemp_Corr записываем значение переменной vrTemp + значение переменной CorrValue_vrTemp
end.
3. Если эту переменную не требуется выводить в тренды, отчеты, генерировать по ней сообщения о нарушении границ и т.д., то можно использовать следующий способ: выводить значение переменной в компонент "Текст" (см. пример №5 по ссылке), с прибавлением необходимого значения. Для этого внешнюю переменную нужно указать в свойстве "Переменная", а переменную корректировки в свойстве "Доп. переменная", и на событие "OnDataChange" использовать следующий код:
Код: pascal
begin
  if Sender is TM_Text then                         // проверяем, что Sender это текст
    with Sender as TM_Text do                       // приводим Sender к типу "TM_Text"
      Text := IntToStr(AsInt + VariableEx.AsInt);   // записываем в текст значение основной переменной + значение доп. переменной
end.
Если необходимо выполнять скрипт после изменения значения переменной корректировки, то данный скрипт также необходимо назначить на событие "OnDataChangeEx".

yurgin_757

#1645
Спасибо огромное, вариант 2.2 должен подойти я думаю, буду пробовать. А как бы в этот скрипт еще проверку качества добавить, на вроде того: if t_1.IsGoodQuality = False то vrTemp_Corr:=0;
..а скрипт 2.2 тоже имеет тип "изменилась переменная" ??

Simple Scada

Здравствуйте.

Код: pascal
var
  aTemp_Corr, aCorr: TM_Variable;
begin
  aTemp_Corr := GetVariableByName(Variable.Name + '_Corr');  // выполняем поиск переменной vrTemp_Corr
  aCorr := GetVariableByName('CorrValue_' + Variable.Name);  // выполняем поиск переменной CorrValue_vrTemp
  if (aTemp_Corr <> nil) and (aCorr <> nil) then             // если переменные существуют, то
    if Variable.IsGoodQuality then                           // если переменная имеет хорошее качество, то
      aTemp_Corr.Value := Variable.AsInt + aCorr.AsInt       // в переменную vrTemp_Corr записываем значение переменной vrTemp + значение переменной CorrValue_vrTemp
    else                                                     // иначе (если переменная имеет плохое качество)
      aTemp_Corr.Value := 0;                                 // в переменную vrTemp_Corr записываем 0
end.

yurgin_757

#1647
Спасибо! Работает...
Только вот как сделать чтобы переменная, в которую выводится значение реального тега + коррекция был в формате не целого числа а с двумя знаками после запятой.
...и что бы этот тег применялся по вводу коррекции, а не по изменению реальной переменной.

Simple_Scada

Цитироватьбыл в формате не целого числа а с двумя знаками после запятой.
Для этого переменной необходимо задать вещественный тип и настроить нужный формат. Также, в скрипте нужно использовать приведение значения переменной к вещественному типу (AsSingle).

Цитироватьи что бы этот тег применялся по вводу коррекции, а не по изменению реальной переменной.
Если сделать, чтобы пересчет производился только по вводу коррекции, то потеряется смысл коррекции, т.к. значение итоговой переменной будет вычисляться только при изменении значения коррекции и затем при изменении исходной переменной не будет меняться.

Вообще, подобные вычисления следует производить на контроллере и передавать в скада-систему готовое значение, без необходимости его корректировки.

yurgin_757

#1649
Цитата: Simple_Scada от 09 декабря 2025, 12:18:26
Цитироватьбыл в формате не целого числа а с двумя знаками после запятой.
Для этого переменной необходимо задать вещественный тип и настроить нужный формат. Также, в скрипте нужно использовать приведение значения переменной к вещественному типу (AsSingle).
Ага, спасибо это понял! ...сделал так:
Код: pascal
aTemp_Corr.Value := Variable.AsFloat + aCorr.AsFloat;

ЦитироватьЕсли сделать, чтобы пересчет производился только по вводу коррекции, то потеряется смысл коррекции, т.к. значение итоговой переменной будет вычисляться только при изменении значения коррекции и затем при изменении исходной переменной не будет меняться.
...хммм, странно, в МастерСкаде было именно так

ЦитироватьВообще, подобные вычисления следует производить на контроллере и передавать в скада-систему готовое значение, без необходимости его корректировки.
...к сожалению данные берутся не из ПЛК или СПК а из аналоговых модулей МВ110-8А. В старой МастерСкаде ведётся паралельный съём данных так как оборудование "БигДайчмен" не даёт своих протоколов.

В любом случае ещё раз спасибо!