Рассмотрим пример скрипта, отслеживающего генерацию сообщений.
Вообще, в 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; } } |
Скачать проект с данным скриптом можно по ссылке.