Оживим уже созданную (но пустую) панель Trends графиком изменения переменной во времени. Однако, перед тем, как смотреть на тренды, их необходимо сконфигурировать и каким-то образом задать значения, чтобы они скопились в базе данных. Необходимо, чтобы в системе была переменная, которая меняла свое значение. Необходимо повесить соответствующий конфиг на эту переменную, чтобы значения складывались в архив. Для типа точек данных Flap у нас есть DPE с названием Flow (расход) и типом int. Этот DPE и будем использовать для ознакомления с трендами. Для имитации поведения системы у нас уже есть созданный control-скрипт Model. Предлагаю его и использовать для имитации расхода. Откроем скрипт Model:

Напомню, что «точкой входа» в скрипт (как и в языке C) является функция main(). В настоящий момент функция main() исполняется, происходит связывание изменения DPE с его функцией-обработчиком (callback-функцией), после чего main завершает работу, но в памяти остаются сами callback-функции.

Изменяем функцию main следующим образом:

main()
{
  dpConnect("OnOpen_CB1", "System1:Flap1.Commands.Open");
  dpConnect("OnOpen_CB2", "System1:Flap2.Commands.Open");
  dpConnect("OnOpen_CB3", "System1:Flap3.Commands.Open");

  for (;;) {
    dpSet("System1:Flap1.Inputs.Flow", rand());
    dpSet("System1:Flap2.Inputs.Flow", rand());
    delay(1);
  }
}

Теперь после выполнения связывания DPE с callback у нас стоит бесконечный цикл (при помощи конструкции for(;;)), который задает случайным образом значения расхода для клапанов 1 и 2, после чего ожидает 1 секунду при помощи функции delay. В этом случае функция main не будет завершаться и будет работать, пока запущен ее CTRL-менеджер.

(Кстати, да — тут достаточно и одного вызова dpSet, а не двух. Совершаю ошибки, про которые сам же и говорил ранее).

После перезапуска CTRL-менеджера (в связи с изменением скрипта) нужно убедиться, что модель работает, взглянув на значения расхода в модуле para.

Пришло время визуализировать изменения расхода. Откроем панель Trends. Перенесем на панель графический элемент Trend из палитры инструментов редактора gedi.

Необходимо выбрать режим отображения тренда

Стандартный режим — значение по времени. Второй режим — Value over value был разработан для специфических требований некоторых заказчиков для сопоставления «невременных» значений. Например — распределение давления по трубопроводу, где по оси X — километровая отметка трубы, а по оси Y — значение давления на отметке. В нашем случае выбираем первый вариант, расположение — горизонтальное, нажимаем ОК. Далее выбираем точку данных для отображения, выбираем расход первого клапана.

Далее через кнопку Append добавляем еще одну кривую — расход второго клапана. Итого закладка Curve (кривая) выглядит так

…если выбрали график #1_1, и так:

…если выбрали (для настроек отображения, конечно же) график с именем #1_2. Нажимаем кнопку Close. Панель Trends в редакторе теперь выглядит так

Сохраняем и закрываем панель Trends и запускаем панель Main, нажимаем в исполнении кнопку TRENDS. После я вижу, что окно с трендами как-то меняется, то есть я вижу динамику, но пока она очень ненаглядна… и весьма непонятна.

Связано это с тем, что значение меняется каждую секунду, отображается сейчас текущее значение (архивация еще не настроена), а по умолчанию выбрана временная шкала в два часа. Разумеется, график выглядит очень «сжатым». Поиграв колесиком мышки на шкале времени, на шкалах оси X для каждого из трендов, а так же на основном рабочем поле, можно получить более наглядное изображение. Оси можно не только масштабировать колесиком, но и двигать, перемещая мышь с зажатой на оси левой кнопкой. Можно выделить мышью на рабочем поле тренда любую его часть, экран будет масштабирован под выделенное. Если вы «что-то нажали, и все сломалось», просто нажмите кнопку «1:1» в верхней части экрана, масштабы вернутся к изначальной конфигурации.

Однократный щелчок мышью на рабочем поле показывает «линейку» или ruler:

При активном рулере можно с нажатым Shift снова кликнуть на рабочем поле, тогда отобразится т.н. «диференциальный рулер».

Функционал этого окна очень широк и дает гораздо больше возможностей, чем может показаться с первого взгляда. Можно синхронизировать оси. Можно выставлять несколько рулеров. И т.д. Очень много чего можно. На официальных курсах трендам уделяется примерно половина учебного дня из пяти, и это только базовый курс.

В настоящий момент, если полностью закрыть окно тестирования и открыть его заново, то все значения начнут отображаться только с момента открытия окна графиков. Это вполне закономерно, так как на точках данных не размещен конфиг архивации, т.е. данные не пишутся в базу. Вернемся в модуль para. Встанем на элемент точки данных Flap1.Input.Flow и добавим к нему конфиг _archive.

Тут необходимо выбрать Archive Settings. Появился конфиг _archive, выберем его.

Тут необходимо выбрать один из шести типов архивов:

Если посмотреть на консоль системы, то и менеджеров архива у нас запущено тоже шесть экземпляров:

То, что я сейчас рассказываю про тренды (точнее, про архивирование) касается исключительно встроенной файловой базы данных. В WinCC OA есть возможность архивации в базу Oracle. В версии 3.17 появилась возможность использования InfluxDB, которая при создании проекта обозначена, как NextGen архиватор. Сейчас же говорим про классическую файловую базу. В качестве индекса (первичного ключа) в ней выступает метка времени сигнала. Если говорить очень приблизительно, то на все заданные тренды у нас создается одна таблица. А значения сигналов «с поля» меняются по-своему, но записываются в базу данных по изменению. Если свести все тренды в одну таблицу, то ее использование будет весьма неоптимальным, в таблице будет очень много «пробелов». Для оптимизации нам предлагается в рамках одного менеджера архивов (у которого есть свои настройки, и они могут отличаться или отличаются от настроек «соседних» архивных менеджеров) хранить переменные, интенсивность изменений которых более-менее одинаковая. Не рекомендуется совмещать в рамках одного архива переменные, которые меняют свои значения с очень разной частотой, например — раз в секунду и раз в час. Это очень неточное описание, которое лишь приблизительно объясняет наличие нескольких архивных менеджеров в системе. А при наличии NextGen архиватора на базе InfluxDB, возможно, данное описание вообще теряет смысл, однако в настоящей заметке дублируется оригинальный курс, так что это объяснение сохраняется по соображениям «совместимости».

В нашем демонстрационном примере обе переменные, меняющиеся раз в секунду, необходимо архивировать в рамках одного менеджера. Пусть это будет менеджер ValueArchive_0000.

Выбираем Active, нажимаем Apply.

То же самое проделываем для второго клапана. Запускаем снова на исполнение панель Main, открываем тренды, подгоняем масштаб и смотрим, что изменилось:

Красной линией рисуется расход первого клапана, синим — второго. На этом скриншоте видно, что синяя линия начала рисоваться позже красной. Это связано с тем, что архивные настройки для второго клапана были применены после того, как я настроил архивацию первого клапана. Запись данных началась сразу после выставления «Active» и нажатия кнопки «Apply».

На практике может потребоваться не просто визуально отобразить исторические значения переменной, но и получить их в числовом виде (в виде массива, например) для какой-либо дальнейшей обработки. Универсальным инструментов для этого является старый-добрый SQL-запрос. Для более удобного визуального формирования запроса к базе данных WinCC OA предоставляет отдельный инструмент SQL Query. Чтобы добраться до него необходимо запустить (в том же gedi в меню Module) System Management.

Далее открыть Reports:

И SQL-Query. Ставим тут галочку напротив слова ALL в рамке Value type (в скриншоте не проставлена, а должна быть).

На вкладке SELECT выбираем те значения DPE, которые необходимо включить в выборку. Система архивирования, на самом деле, запоминает не только пару «метка времени — значение», но и массу другой информации. Но мы сейчас прочитаем из базы только значение и метку времени — originalvalue и originalstime. Для этого из выпадающего списка Configuration выбираем искомое и добавляем его в список Elements of the SELECT Statements при помощи кнопок «Append» или «Insert», расположенных справа от списка. Кнопки не подписаны. К этим кнопкам и этому диалоговому меню необходимо привыкнуть, на первый взгляд там все немного неоднозначно.

На следующей вкладке From мы определяем, из каких точек данных мы хотим вытащить значения.

Тут можно воспользоваться механизмом шаблонов, написать в текстовом поле Flap* и добавить этот текст в список Elements of the FROM statement.

Все остальные параметры пока игнорируем и переходим на вкладку Data:

Нажимаем вначале кнопку Create query, после чего кнопку Start query, получаем набор данных.

Попробуем эти же данные получить в программном коде. Скопируем куда-нибудь SQL-запрос. Откроем панель Main и добавим на нее кнопку с меткой EXPORT.

Начнем программировать скрипт на событие Click этой кнопки. SQL-запрос, скопированный из окна SQL-query я временно разместил в комментарии к этому коду:

Сейчас нам потребуется применить функцию dpQuery. dpQuery отличается от функции dpGet тем, что возвращает массив значений, которые удовлетворяют SQL-запросу, в то время, как dpGet возвращает лишь одно значение. Первый параметр функции — запрос SQL. Второй параметр в переводе на русский звучит как двумерный массив неопределенного типа и неопределенной размерности, а в типах данных WinCC OA — как dyndynanytype. Объявим в скрипте переменную этого типа. Разбирать полученные данные мы будет, конечно же, имея представление — что именно мы запрашиваем. И это представление мы имеем, как авторы запроса.

main(mapping event)
{
  dyn_dyn_anytype Tab;
  dpQuery("SELECT ALL '_original.._value', '_original.._stime' FROM 'Flap*'", Tab);
}

Результат запроса складывается в Tab, но этот результат необходимо еще куда-то вывести. В качестве примера выводить полученные данные будем Log Viewer при помощи функции DebugN. Данная функция незаменима для дебага во время разработки, тестирования и наладки системы. В качестве параметров допускается приводить несколько строк. Совет из практики — указывать дополнительно в DebugN не только отладочную информацию, но и «заголовок» отладочной информации. Если проект очень большой и над ним работает большой штат, то не помешает так же указывать, кто именно выводит в лог. Причина на то простая — разработчиков может быть много, а лог в системе один. Зная, что выводится и кто выводит — можно подойти к коллеге и вежливо поинтересоваться, а что тут, собственно, происходит. Зная заголовок вывода можно провести глобальный поиск в проекте и найти этот вызов на тот случай, если вызов DebugN был оставлен после ПНР, его вывод осуществляется далеко не на постоянной основе, сам проект давно в работе, а разработчик давно уволился. Культура разработки, если двумя словами.

В конечном итоге обработчик кнопки EXPORT выглядит следующим образом:

main(mapping event)
{
  dyn_dyn_anytype Tab;
  dpQuery("SELECT ALL '_original.._value', '_original.._stime' FROM 'Flap*'", Tab);
  DebugN("SQL", Tab);

}

Сохранив скрипт, запустим снова окно Main на исполнение. Найдем окно Log Viewer (оно открывается всегда автоматически при запуске системы), очистим вывод журнала кнопкой «крестик» для нашего удобства:

Нажмем в рантайме кнопку EXPORT и посмотрим на вывод в журнале:

Выгрузка получилась очень большая, и показано далеко не все, о чем LogViewer честно сообщает:

Обидно, но логично. Выводить несколько мегабайт текста в журнал — не лучший выбор. На практике переменную Tab можно разобрать и натравить на нее свою математику. Можно вывести информацию в таблицу и так далее.