четверг, 1 октября 2015 г.

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

В предыдущем примере мы рассмотрели задачу чтения архива и его анализа. Теперь рассмотрим решение обратной задачи – генерации архива с собственными метками времени.
Генерация архива средствами скрипта может потребоваться в различных ситуациях, но чаще всего это требуется если необходимо обрабатывать внешние архивные источники данных – файлы или базы данных. В качестве примера рассмотрим чтение архива из CSV файла.

Допустим у нас существует CSV файл, из которого нужно периодически (по сигналу) выполнять считывание данных с последующей записью в архив. CSV файл состоит из 6 столбцов – метки времени в формате Дата-Время, и 5 вещественных значений, поля разделены символом «точка с запятой».

Добавим скрипт, и добавим в него 2 входа – «Считать» и «Имя файла», а также 5 выходов «Параметр1» .. «Параметр5».
Что данные сохранялись нужно настроить архивацию у выходов. При этом для такого способа формирования архива лучше использовать периодическую архивацию с шагом в 00:00:00 – при таком способе будут записываться все значения, которые будут поступать из кода, а при остановке скады не будет формироваться значение с признаком Норма-Останов. Включим данный способ архивации у каждого выхода.
Приступим к написанию кода. Для чтения файла мы будем использовать класс StreamReader – данный класс позволяет считывать символы из потока байтов в заданной кодировки. Для того чтобы данный класс был доступен в коде нужно в секции using объявить класс:
using System.IO;
Приступим написанию кода. В секции класса объявим переменную M, в которую будем сохранять значение входа «Считать» - для выполнения кода по переднему фронту на входе. Также перед тем как выполнять код нужно убедится, что задан путь файла. Для этого проверим его на значение Null, а также убедимся, что имя не пустое – для этого нужно сравнить ее со свойством string.Empty или просто с пустой строкой - "".
public partial class ФБ : ScriptBase
{
bool? M=false;
    public override void Execute()
    {
    if (Считать==true && M==false && Файл!=string.Empty && Файл!=null)
    	{    
  
}
	M=Считать;
    }
}
Для того чтобы открыть файл на чтение, создадим класс StreamReader. При создании класса вызывается его конструктор (метод который позволяет инициализировать параметры класса). Конструкторов у данного класса несколько (так называемый перегруженный конструктор). Мы вызовем конструктор с одним параметром – именем файла. Описание данного конструктора можно прочитать здесь:
Поскольку у нас файл содержит только цифры и несколько символов, такого вызова будет достаточно. Однако если читать файл, содержащий символы (особенно кириллические), то следует вызывать метод с тремя аргументами – именем, кодировкой и флагом обнаружения порядка байта:

При этом класс StreamReader мы вызовем через оператор using (не путайте с директивой using). Данный оператор предназначен для работы с неуправляемыми ресурсами – файлами, SQL соединениями и т.д. Вызов через данный оператор гарантирует очищение памяти после выполнения кода.
	   using(var file = new StreamReader(Файл)) //открытие файла для чтения
		{
			//здесь мы разместим код чтения файла
		}

Итак, у нас создана переменная file, которую мы будем использовать для работы с нашим файлом. Файл состоит из строк, нам необходимо построчно считывать его, разбирать строку на компоненты (метка времени и значения) и записывать на выходы.
Для считывания строки используется метод ReadLine, метод возвращает строку.
var line=file.ReadLine();//считывание строк из файла
Чтобы считать все строки мы будем использовать цикл while – данный цикл выполняется пока условие равно true. Условием выхода из цикла в данном случае будет окончание файла – в этом случае переменная line будет равна null.
var line=file.ReadLine();//считывание строк из файла
while (line!=null) 
	{				
		//здесь мы обработаем строку
		line = file.ReadLine();			
	}

Теперь нужно обработать строку line – разбить ее на составляющие. Напомним, что каждая строка у нас представляет собой метку времени и 5 значений, разделенных символом «точка с запятой». Для разбора строки на составляющие нужно использовать метод строки Split. Данный метод разбирает строку на составляющие разделенные заданным символом. В качестве результата метод возвращает массив строк.
String[] substrings = line.Split(';'); //разбор строки на составляющие
Массив у нас содержит 6 строк. Нулевой элемент массива – метка времени, но она все еще представлена в виде строки. Для перевода ее во время у класса DateTime есть метод Parse – данный метод преобразует строку в эквивалентное ему значение времени.
var Time = DateTime.Parse(substrings[0]);
Получить значение числа можно аналогичным образом - вызвав этот же метод но уже у класса Double.
Теперь осталось записать значения и метку времени на выходы архива. Для того чтобы записать значение на выход с определенным значением и меткой времени используется метод SetValue. В качестве аргументов ему необходимо передать – имя выхода, и значение выхода (содержащее непосредственно значение, метку времени, и при необходимости – признак качества). Пример такого вызова метода:
SetValue("Выход", new PinValue(Value,Time));

Как можно видеть создается класс PinValue, в конструктор которого передается значение и метка времени.
Имена выходов у нас имеют имена Параметр1..Параметр5. Можно сделать 5 строк вызывающих метод SetValue, либо произвести запись в цикле. Остановимся на втором варианте – реализуем его с помощью цикла for.
for (int i=1;i<=5;i++)
	{
		SetValue("Параметр"+i.ToString(), new PinValue(Double.Parse(substrings[i]),Time));
	}

В итоге наш код будет выглядеть следующим образом.
public partial class ФБ : ScriptBase
{
bool? M=false;
    public override void Execute()
    {    
		if (Считать==true && M==false && Файл!=string.Empty && Файл!=null)
		{    
			using(var file = new StreamReader(Файл)) //открытие файла для чтения
			{
				var line=file.ReadLine();
				while (line!=null)
				{	   	   	
					String[] substrings = line.Split(';'); //разбор строки на составляющие				
					var Time = DateTime.Parse(substrings[0]);
					for (int i=1;i<=5;i++)
					{
						SetValue("Параметр"+i.ToString(), new PinValue(Double.Parse(substrings[i]),Time));
					}
					line=file.ReadLine();//считывание строк из файла
				} 	   	   
			}
		}
		M=Считать;
	}
}

Запустим режим исполнения и выполним считывание файла – данные считались и записались на выходы.

Обратите внимание, что архив генерируется с признаком качества «Норма» (192 – Good). Если необходимо генерировать архив с различными метками времени, то нужно сначала сформировать нужный вам признак качества. Для этого предназначен класс
MasterSCADA.Hlp.Pins.PinQuality
Данный класс также имеет перегруженный конструктор, в один из которых можно передать значение признака качества. Например, инициализация класса с признаком качества Good (Норма):
var qual= new MasterSCADA.Hlp.Pins.PinQuality(192);
После этого можно вызвать другой конструктор класса PinValue содержащий признак качества
var qual= new MasterSCADA.Hlp.Pins.PinQuality(192);
SetValue("Параметр"+i.ToString(), new PinValue(Double.Parse(substrings[i]),qual,Time));
Скрипт можно считать завершенным, однако полезно обеспечить защиту кода от ошибок, например, если файла не окажется по указанному пути. Для этого используются операторы trycatch. Оператор try в случае появления исключения перехватывает исключение, и передает оператору catch. Это позволяет корректно обработать ошибки и должны образом проинформировать пользователя.
Пример описанного скрипта для чтения CSV файла, дополненного обработкой ошибок через try-catch с выводом сигнала ошибки на выход ФБ и в лог скады можно скачать по этой ссылке.


1 комментарий:

  1. Этот комментарий был удален администратором блога.

    ОтветитьУдалить

Поделиться