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

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

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

Для того чтобы обратится к отдельной переменной нужно получить интерфейс ITreePinHlp. Если нужно найти переменную по полному имени, то нужно выполнить команду:
var item = (ITreePinHlp)HostFB.TreeItemHlp.Project.Item("Объект.Объект 1.Объект 1.Значение 1");
Теперь можно получить значение переменной в режиме исполнения:
var ItemValue=(double?)(item.GetRTPin().ObjectValue);

Если нужно получить признак качества и метку времени, то вместо свойства ObjectValue соответственно вызываются Quality и TimeStamp. Получить архив можно аналогичным образом, как и для входа скрипта – используя класс DataArchiveItem (см. предыдущий пример).

Если нужно найти переменную находящуюся на одном уровне со скриптом, то нужно вызвать метод GetChildкласса ParentObject:
var item =(ITreePinHlp)HostFB.TreeItemHlp.ParentObject.GetChild("Значение 1");

Как поступить если нужно перебрать все имеющиеся значения (команды, расчеты) находящиеся в одном объекте со скриптом и вложенных подобъектах? Для этого используется метод NavigateChilds. Рассмотрим использование этого метода подробно на конкретной задаче.

Например, необходимо сохранить в CSV файл параметры значений – их имена, текущее значение и метку времени.

Создадим в объекте несколько значений с произвольными именами, а также скрипт. Скрипт будет содержать два входа: «Записать» - по переднему фронту на этом входе произойдет запись данных в файл, «ПутьФайла» - путь файла, куда будут сохраняться данные, а также выход для сигнала ошибки.
Сделаем, чтобы наш скрипт исполнялся по переднему фронту, как мы делали это ранее в других примерах.
public partial class ФБ : ScriptBase
{
 bool? M=false;
    public override void Execute()
     {           
     if (Записать==true && M==false)
      {     
       
      }
     M=Записать;
     }    
 
    }
}

В качестве времени записи мы будем использовать текущее время компьютера, на момент запуска скрипта (но при необходимости можно получить и метку времени каждой переменной):
string dtstring=DateTime.Now.ToString();

Для осуществления записи в файл воспользуемся классом StreamWriter. Воспользуемся перегруженным конструктором с тремя параметрами – именем файла, флагом перезаписи (false – перезапись файла, true – добавление новых данных в файл), и кодировка. Класс вызываем через оператор using, как в предыдущем примере скрипта.
if (Записать==true && M==false)
  {          
   string dtstring=DateTime.Now.ToString();     
using(var file = new StreamWriter(ПутьФайла, false, Encoding.GetEncoding("windows-1251")))
//открытие файла для записи
{        
 
   }          
  }
  M=Записать;

Теперь реализуем перебор всех переменных текущего объекта. Рассмотрим использование метода NavigateChilds:
using(var file = new StreamWriter(ПутьФайла, false, Encoding.GetEncoding("windows-1251")))
//открытие файла для записи
{        
 //перебор всех значений данного объекта
 HostFB.TreeItemHlp.Parent.NavigateChilds(delegate(ITreeObjectHlp item)
  {     
    if (item.ObjectType != EObjectType.otValue)   
     return true;    
    ITreePinHlp command = (ITreePinHlp) item;        
    string objValue = (command.GetRTPin().ObjectValue).ToString();         
    file.WriteLine("{0};{1};{2}", command.Name,dtstring,objValue); //записываем в файл
    return true;
  }, TreeItemMask.Pin, NavigateItemsFlags.CurrentComputer);     
   }   


Рассмотрим код построчно. В метод NavigateChilds передается три параметра – делегат, вызываемый для каждого дочернего элемента, тип искомых элементов (TreeItemMask.Pin), флаги поиска. Про делегат поговорим ниже и сначала разберем последние два параметра. TreeItemMask.Pin – определяет среди каких элементов проекта нужно выполнять поиск. Pin – это элементы тип входа - «Значение» и «Вход». Можно также задать Pout (выходы, команды), Parser (Расчет), Event (Событие), FuncBlock (функциональный блок), All (все) и другие варианты. NavigateItemsFlags.CurrentComputer – флаги, определяющие пространство поиска переменных, в данном случае – только на текущем компьютере.
Теперь рассмотрим делегат. Делегат вызывается для каждого дочернего элемента (item), если делегат вернет false, перебор останавливается. Далее следует лямда-выражение которое описывает работу делегата – именно в нем мы и выполняем перебор нужных нам переменных. Разберем это выражение построчно.
В первой строке мы проверяем нужный ли тип переменной проекта мы получили:
if (item.ObjectType != EObjectType.otValue)   
return true;    


Поскольку метод NavigateChilds при параметре TreeItemMask.Pin возвращает как значения, так и входы, то нам нужно дополнительно убедиться, что мы получили именно значение. Для это тип полученной переменной проверяется с типом otValue (значение), и если они не совпадают, то выполняется оператор return true, происходит выход из функции и NavigateChilds переходит к следующей переменной объекта.
ITreePinHlp command = (ITreePinHlp) item;        
string objValue = (command.GetRTPin().ObjectValue).ToString();


Чтобы получить значение переменной, item сначала приводится к типу ITreePinHlp, а затем с помощью метода GetRTPin извлекается текущее значение переменное, которое преобразуется в строку методом ToString().
file.WriteLine("{0};{1};{2}", command.Name,dtstring,objValue); //записываем в файл


Теперь, когда значение нам известно, можно произвести запись в файл методом WriteLine. Мы запишем в файл имя переменной (command.Name), время (dtstring) и непосредственно значение (objValue).
Обратите внимание на конкатенацию (объединение строк) в методе записи в файл. Первым аргументом метода передается строка форматирования – мы будем записывать 3 параметра разделенным символом «точка с запятой», затем перечислены непосредственно 3 переменных, чьи значения будут подставлены в строку. Конечно можно выполнить слияние строк и с помощью оператора «+», но подобная запись более простая. Подробнее про такой способ конкатенации можно посмотреть здесь:
https://msdn.microsoft.com/ru-ru/library/system.string.format(v=vs.110).aspx#Starting
После того как запись произведена выполняется return true – происходит переход к следующей переменной объекта.
Полный код скрипта:
public partial class ФБ : ScriptBase
{
 bool? M=false;
    public override void Execute()
    {           
     if (Записать==true && M==false)
     {          
     string dtstring=DateTime.Now.ToString();     
   using(var file = new StreamWriter(ПутьФайла, false, Encoding.GetEncoding("windows-1251")))
   //открытие файла для записи
   {        
    //перебор всех значений данного объекта
    HostFB.TreeItemHlp.Parent.NavigateChilds(delegate(ITreeObjectHlp item)
     {     
       if (item.ObjectType != EObjectType.otValue)   
        return true;    
       ITreePinHlp command = (ITreePinHlp) item;        
       string objValue = (command.GetRTPin().ObjectValue).ToString();         
       file.WriteLine("{0};{1};{2}", command.Name,dtstring,objValue); //записываем в файл
       return true;
     }, TreeItemMask.Pin, NavigateItemsFlags.CurrentComputer);     
      }          
     }
     M=Записать;
 }        
 
}

Как адаптировать скрипт под свою задачу?
Сначала, нужно определится с поиском – где и переменные каких типов вы ищете. Если вы вызываете метод NavigateChilds у HostFB.TreeItemHlp.Parent, то вы выполняете поиск в объекте где находится скрипт и всех его подообъектов. Если нужно произвести поиск во всем проекте, то метод вызывается у HostFB.TreeItemHlp.Project. С помощью второго аргумента метода NavigateChilds вы определяете переменные какого типа вас интересуют (входы, выходы, расчеты или все переменные).
Затем нужно адаптировать код в лямбда-функции. Если нужно – вначале выполнить дополнительную проверку на тип возвращенной переменной, и, если она вам неинтересна, выполнить return true.

После преобразования переменной к типу ITreePinHlp, можно работать со значением, меткой времени и качеством. В нашем примере значение переменной преобразовывалось в строку. Если нужно получить конкретный тип, например, Double, то преобразование можно выполнить так:
double objValue = Convert.ToDouble(command.GetRTPin().ObjectValue);

Если нужно проверить переменную на определенный тип, то для этого нужно использовать оператор is. Например, если нас не интересуют переменные строкового типа нужно выполнить:
if (objValue is string) return true;

Теперь получив значения, вы можете оперировать с ними как вам требуется – выполнять математическую обработку, определять состояние определенных значений у переменных, экспортировать данные в СУБД и т.д.
Пример описанного скрипта можно скачать по ссылке. Скрипт несколько расширен – реализовано создание папки для сохранения файла, а также контроль выполнения кода с помощью операторов try-catch, при этом в случае возникновения ошибки информация пишется в лог скады с помощью метода ReportError.