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

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

Зачастую в системах необходимо отслеживать возникающие события и как-либо реагировать на них – передавать сигнал в контроллер, записывать данные в файл и т.д.
Рассмотрим пример скрипта, отслеживающего генерацию сообщений.


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


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


В объектах находятся два события – «Отключение защиты» и «Авария». Оба имеют сообщения с одинаковыми категориями.




Нам необходимо отслеживать состояние сообщения «Отключение защиты» и выдать импульсы соответствующие выходы ФБ.
Оптимальным режимом работы с сообщениями является режим по подписке – его суть сводится к следующему:
1. Скрипт подписывается на возникновение и изменение (завершение активности или квитирование) сообщений
2. В момент формирования события системой происходит вызов метода, который был указан при подписке
3. В этом методе мы можем размещать весь необходимый нам код.
В итоге в режиме подписки выполнение кода происходит только тогда, когда это требуется, что снижает нагрузку на систему.
Для решения данной задачи предназначены два события - AlarmManager_OnRecordsChangeEvent и AlarmManager_OnRecordsChangeEvent. Создадим собственный метод AlarmManager_OnRecordsChangeEvent и сделаем на него подписку в методе Start (данный метод вызывается один раз в начале скрипта).
public partial class ФБ : ScriptBase
{
    public override void Start()
    {
        //подписка на изменение сообщений
        HostFB.TreeItemHlp.Project.AlarmManager.OnRecordsChangeEvent += AlarmManager_OnRecordsChangeEvent; 
        //подписка на добавление сообщений
        HostFB.TreeItemHlp.Project.AlarmManager.OnRecordsAddEvent += AlarmManager_OnRecordsChangeEvent;     
    }
     
    void AlarmManager_OnRecordsChangeEvent(MasterSCADA.Hlp.Events.AlarmManagerHlp manager, MasterSCADA.Interfaces.EventID[] eventIDs)
    {     
     
    }
}



В метод нам приходит массив ID сообщений – EventID (сообщений может быть несколько, если, например, было групповое квитирование). Зная ID сообщений, мы можем выполнить поиск по архиву сообщений и найти нужное нам. Для этого предназначен метод GetEvents класса AlarmManager. В данный метод необходимо передать:
1. Объект, в котором будет выполняться поиск сообщения (в нашем случае родительский объект скрипта)
2. Фильтр, по которому будет выполняться поиск
3. Максимальное количество возвращаемых значений
С объектом проблем нет – чтобы получить родительский объект достаточно получить свойство HostFB.TreeItemHlp.Parent. Разберемся с фильтром.
В метод GetEvents необходимо передать ID параметров, которые изменились – в противном случае он вернет нам все сообщения, которые есть в архиве. Создадим фильтр:
var filter = new EventFilterData();               
filter.EventIDs=eventIDs; //фильтруем по новым EventID    



Чтобы создать класс EventFilterData необходимо добавить в секцию using директиву:
using MasterSCADA.Hlp.Events;


Она предоставляет доступ к классам архива сообщений.
При необходимости в фильтр можно добавить и другие варианты фильтрации – по категориям, по приоритетам и т.д. Позже мы рассмотрим, как добавить фильтрацию по категориям.
Теперь вызовем метод GetEvents, куда передадим необходимые нам параметры.
var project = HostFB.TreeItemHlp.Project;        
var events = project.AlarmManager.GetEvents(HostFB.TreeItemHlp.Parent, filter, 1000);        


Метод вернет нам коллекцию сообщений events – теперь можно ее перебрать и узнать какие сообщения у нас возникли. Перебор выполним с помощью цикла foreach.
Сначала определим нужное ли сообщение появилось, проверив свойство Source:
foreach (var NewEvent in events)
     {
      if (NewEvent.Source=="Отключение защиты")
      {
       
       
      }      
     }


Теперь можно выполнить обработку – узнать, что именно произошло с событием. С ним могло произойти следующее:
1. Сообщение возникло (стало активным)
2. Сообщение было квитировано
3. Сообщение пропало (перестало быть активным)
Для того чтобы определить, что именно с ним произошло можно использовать два свойства: AckTime – время квитирования и InactiveTime – время окончание активности. ActTime и Inactive имеют тип DateTime(Nullable), то есть пока событие не потеряло активность или не было квитирование, соответствующие им свойства равны Null. Зная есть ли значение в свойство и его время, мы можем определить, что произошло с событием.
Проверим было ли сообщение квитировано и в этом случае включим выход:
if (NewEvent.AckTime!=null && (NewEvent.InactiveTime==null || NewEvent.InactiveTime<NewEvent.AckTime))
          {
           Квитирование=true;
         }


Проверим стало ли сообщение активным и также включим выход.
if (NewEvent.ActiveTime!=null && NewEvent.InactiveTime==null && NewEvent.AckTime==null) //сообщение активно
         {                                 
          Срабатывание=true;
          }


Вот и все.
Теперь необходимо сбросить выходы на следующем цикле. Для этого в методе Execute добавим код:
public override void Execute()
    {
     if (OffAck==true) {Срабатывание=false; OffAck=false;}  
     if (Срабатывание==true) OffAck=true;
     if (OffActive==true) {Квитирование=false; OffActive=false;}
     if (Квитирование==true) OffActive=true;
    }  




Переменные OffAck и OffActive объявлены в секции класса скрипта – чтобы сохранялись между цикла. При включении выхода соответствующая ему переменная включается, а на следующем цикле от ее состояния происходит сброс выхода и самой переменной.
Проверим работу скрипта в режиме исполнения:



Теперь рассмотрим использование фильтра сообщений. Можно выполнять фильтрацию и в цикле перебора сообщений, но проще сделать этот через фильтр и подать фильтр в метод GetEvents.
Для примера добавим фильтрацию по категориям – сделаем чтобы возвращались только сообщения категории «Авария». Объявим в классе скрипта коллекцию, в которую мы будем хранить категории:
List<uint> Category = new List<uint>();      //список категорий




Теперь в методе Start, выполним перебор всех категорий сообщений, найдем нужную и добавим ее ID в коллекцию.
var project = HostFB.TreeItemHlp.Project;
     //фильтр по определенным категориям
        foreach (var cat in project.SystemTreeRootItem.EventCategories.Values)
        {        
            if (cat.Name=="Авария")
            {
             Category.Add((uint)cat.ID);    //добавляем в список ID категории
            }
        } 




Теперь присвоим свойству EventCategories значение этой коллекции, приведенной к массиву.
filter.EventCategories=Category.ToArray();




Итоговый код выглядит следующим образом:
public partial class ФБ : ScriptBase
{
 bool OffAck=false;
 bool OffActive=false;
 List<uint> Category = new List<uint>();      //список категорий
  
    public override void Start()
    {
     var project = HostFB.TreeItemHlp.Project;
     //фильтр по определенным категориям
        foreach (var cat in project.SystemTreeRootItem.EventCategories.Values)
        {        
            if (cat.Name=="Авария")
            {
             Category.Add((uint)cat.ID);    //добавляем в список ID категории
            }
        }            
     
        //подписка на изменение сообщений
        HostFB.TreeItemHlp.Project.AlarmManager.OnRecordsChangeEvent += AlarmManager_OnRecordsChangeEvent; 
        //подписка на добавление сообщений
        HostFB.TreeItemHlp.Project.AlarmManager.OnRecordsAddEvent += AlarmManager_OnRecordsChangeEvent;     
    }
     
    void AlarmManager_OnRecordsChangeEvent(MasterSCADA.Hlp.Events.AlarmManagerHlp manager, MasterSCADA.Interfaces.EventID[] eventIDs)
    {     
     var filter = new EventFilterData();               
       filter.EventIDs=eventIDs; //фильтруем по новым EventID                   
       filter.EventCategories=Category.ToArray();
  var project = HostFB.TreeItemHlp.Project;        
       var events = project.AlarmManager.GetEvents(HostFB.TreeItemHlp.Parent, filter, 1000);        
      
     foreach (var NewEvent in events)
     {
      if (NewEvent.Source=="Отключение защиты")
      {
       //сообщение было квитировано
       if (NewEvent.AckTime!=null && (NewEvent.InactiveTime==null || NewEvent.InactiveTime<NewEvent.AckTime))
          {
           Квитирование=true;           
          }
          //сообщение стало активным
       if (NewEvent.ActiveTime!=null && NewEvent.InactiveTime==null && NewEvent.AckTime==null) //сообщение активно
          {                                     
           Срабатывание=true;
          }
      }      
     }
      
    }
     
     
    public override void Execute()
    {
     if (OffAck==true) {Срабатывание=false; OffAck=false;}  
     if (Срабатывание==true) OffAck=true;
     if (OffActive==true) {Квитирование=false; OffActive=false;}
     if (Квитирование==true) OffActive=true;
    }    
}




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