ФБ «Скрипт C#» и его использование в MasterSCADA. Обработка архивов

ФБ «Скрипт C#» и его использование в MasterSCADA. Обработка архивов

Ранее мы рассмотрели ФБ «Скрипт C#» и общие принципы работы с ним. В данной статье мы изучим практическое его применение – решим задачу, которую другими средствами решить нельзя. В качестве такой задачи мы разберем анализ архивов данных.
В MasterSCADA есть штатные средства для работы с архивами – у модуля «Расчет» (и аналогично у «События») есть функции для работы с архивами (интеграл, сумма значений, мгновенное значение, пробег), также с помощью редактора отчетов можно выполнять обработку и выдачу значений архива. Но «Расчет» может не удовлетворить гибкостью, а редактор отчетов выведет результат в собственном окне, что может не подойти если значение нужно передать в дерево объектов для дальнейшей обработки. В таком случае на выручку придет «Скрипт C#».

В качестве примера напишем скрипт, который будет производить поиск максимального значения за заданный интервал времени, и выдавать на выход значение и его метку времени.
Сначала подготовим необходимые для отладки компоненты. Добавим в объект команду, включим у нее архивацию и наполним ее данными. Например, получится вот такой набор данных:
Добавим в дерево скрипт.
Начнем создавать скрипт – перейдем на вкладку «Код». Сперва создадим необходимые для функционирования входы и выходы. Нам понадобятся – непосредственно вход с архивными данными (вход у которого будет связь с командой, и от которой он унаследует архив); входы для задания начала и конца времени поиска; вход по сигналу, с которого мы запустим поиск значения (на него можно будет привязать команду-кнопку); выходы результата – значение и его метка времени.
Поиск мы будем выполнять по переднему фронту на входе «Найти». Для того чтобы код выполнялся строго по фронту, нужно сохранять прошлое значение входа «Найти» в переменную, а затем проверять состояние входа «Найти» (оно должно быть True) и этой переменной (она должна быть False), а в конце скрипта переменной присваивать значение входа. Если объявить переменную сохранения в теле метода Execute, то сохранятся она не будет – так как при каждом вызове метода, она будете инициализироваться заново. Чтобы значение переменной сохранялось, ее нужно объявить вне метода – в теле класса. Объявим переменную, которой дадим имя М.
public partial class ФБ : ScriptBase
{  
 bool? M=false;
    public override void Execute()


Теперь уже в методе Execute, приступим к проверке.
public partial class ФБ : ScriptBase
{  
 bool? M=false;
    public override void Execute()
    {        
    if (Найти==true && M==false)
     {    
     
 } 
    M=Найти;    
    }
}

Теперь код в теле условия if будет выполняться по фронту. Однако нужно еще проверить достаточно ли всех условий для выполнения поиска значения – у переменных «Начало» и «Конец» должны быть заданы значения, и «Конец» должен быть больше «Начало». Исправим условие:
if (Найти==true && M==false && Начало.HasValue && Конец.HasValue && Конец>Начало)

Теперь можно приступить к чтению архива из входа. Чтобы получить полный доступ ко всем параметрам входа, а не только к его текущему значению, нужно получить его интерфейс ITreePinHlp:
var elem = HostFB.InputGroup.GetPin("Вход").TreePinHlp;

var elem – это объявление переменной. Оператор var – это оператор объявления переменной неявного типа. При таком объявлении компилятор сам определяет какой тип будет у переменной на основе анализа кода. Можно конечно было объявить и  ITreePinHlp elem, но использование оператора var, в большинстве случае, упрощает написание кода.
HostFB – это обращение к текущему ФБ (то есть скрипту), InputGroup – к входной группе ФБ. Затем следует метод GetPin, в качестве аргумента которому передается имя входа, который вас интересует. TreePinHlp – это как раз конечный интерфейс, который нам нужно получить чтобы потом получить доступ к архиву входа.
var elem = HostFB.InputGroup.GetPin("Вход").TreePinHlp;    
var k=elem.DataArchiveItem;  

Итак, переменная k, получит класс DataArchiveItem, в котором есть методы для выборки архива.

Теперь объявим локальные переменные для поиска – начало и конец.
DateTime EndTime=Конец.Value.ToUniversalTime();
DateTime StartTime=Начало.Value.ToUniversalTime();

Поскольку переменные начало и конца простые (время), то объявим их явно – указав тип DateTime (но, разумеется можно объявить и как var). Переменным присваивается значение входа, через свойство Value, а затем выполняется метод ToUniversalTime. Данный метод приводит время ко времени UTC – то есть убирает локальные часовые пояса. Проще говоря Московское время это +3 часа от UTC, соответственно этот метод вычтет из времени 3 часа. Зачем это нужно? Дело в том, что архивы в MasterSCADA хранятся в формате UTC (чтобы избежать проблем при переводе времени с летнего на зимнее), и для поиска данных также нужно указать формат UTC.
Теперь выполним выборку из архива:
var mas=k.Read(StartTime, EndTime, false);

У переменной k (которая является классом архива входа), вызывается метод Read – чтение архива, в качестве аргумента ему передается время начала, конца и флаг необходимости чтения граничных данных (то есть нужно ли вернуть значения на границе заданного диапазона). Метод вернет PinValue[] – это массив класса PinValue, то есть набор состояний входа и всех его атрибутов (значение, метка времени, признак качества).
Теперь обработаем архив:
  var mas=k.Read(StartTime, EndTime, false);     
   double? Val=null;
   DateTime? TimeStamp=null;
   foreach (var element in mas)
    {
     if (Val.HasValue==false || Convert.ToDouble(element.Value)>Val.Value)
     {
     Val=Convert.ToDouble(element.Value);
     TimeStamp=element.Time.ToLocalTime();
     }
    }
Значение=Val;
МеткаВремени=TimeStamp; 

Вначале объявим переменную Val – типа Double(Nullable) и переменную TimeStamp типа DateTime(Nullable), эти переменные и получат значение и метку времени найденного максимума.
Затем происходит перебор значений в цикле. В качестве цикла используется foreach– данный цикл перебирает массив mas, и каждое значение массива кладет в переменную element (тип PinValue), в которому можно обратится в теле цикла.
Затем мы проверяем – если значение Val пустое (то есть не имеет значение и равно Null) или новое значение из массива больше сохраненного ранее значения Val, то мы сохраняем в Val значения элемента массива (свойство Value), а в переменную TimeStamp – метку времени элемента (свойство Time), предварительно приведя его из UTC в локальное время с помощью метода ToLocalTime. Свойство  element.Value имеет тип object, поэтому чтобы присвоить его переменной Val, сначала делается приведение с помощью метода Convert.ToDouble.
После того как перебор массива закончился, найденные значения записываются на выходы скрипта. Итоговый код выглядит так:
public partial class ФБ : ScriptBase
{ 
 bool? M=false;
    public override void Execute()
    {        
    if (Найти==true && M==false && Начало.HasValue && Конец.HasValue && Конец>Начало)
    {    
     var elem = HostFB.InputGroup.GetPin("Вход").TreePinHlp;    
    var k=elem.DataArchiveItem;
     
     DateTime EndTime=Конец.Value.ToUniversalTime();
     DateTime StartTime=Начало.Value.ToUniversalTime();
     var mas=k.Read(StartTime, EndTime, false);     
     double? Val=null;
     DateTime? TimeStamp=null;
     foreach (var element in mas)
     {      
      if (Val.HasValue==false || Convert.ToDouble(element.Value)>Val.Value)
      {
      Val=Convert.ToDouble(element.Value);
      TimeStamp=element.Time.ToLocalTime();      
      }
     }
  Значение=Val;
  МеткаВремени=TimeStamp;    
 } 
    M=Найти;    
    }
}


Данный код можно использовать в качестве основы собственных скриптов анализа архива. Фактически анализ происходит в цикле foreach – можно легко видоизменить алгоритм поиска под собственную задачу.
Проверим работу скрипта. Запустим режим исполнения, зададим значения переменным «Начало» и «Конец» и запустим поиск сигналом на входе «Найти».
Найдено максимальное значение и его метка времени.
Если нужно определить также и признак качества найденного значения, то для этого используется свойство element.Quality, которое содержит ряд свойств – isBad (плохой признак качества), isGood (хороший признак качества), isUncertain (неопределенное значение).

Скачать пример скрипта можно по ссылке.