![](https://habrastorage.org/files/7c9/3a6/ce7/7c93a6ce7f9c4fa19f3bbfc7d430d726.jpg)
Есть такая электростанция — «Три ущелья». На нее потратили 10 лет и $26 млрд. Под воду ушли два города, переселили 1,3 млн человек. Она генерит 100 млрд кВт⋅ч в год, но это покрывает… 1,7% потребностей Китая.
В мире есть 192 атомные электростанции с 444 энергоблоками общей электрической мощностью около 386 276 МВт. (Мощность «Трех ущелий» 22 500 МВт).
Когда я играл в C&C, RedAlert и Total Annihilation, было милым делом прорваться и уничтожить/захватить парочку вражеских электростанций, тем самым затормозить развитие противника. Не мудрено, что сейчас энергетическая инфраструктура является лакомой целью для хакеров: «Взлом электросети: от отдельной подстанции к блэкауту», «Подробности о беспрецедентном взломе электрической сети Украины».
Кто и как пишет софт для систем такого масштаба?
Разработчики компании EDISON рассказали, как они писали систему мониторинга электросети и визуализации событий. В общей сложности на проект потрачено 14984 человеко-часа, с апреля 2010 по август 2011.
Введение
Энергетический сектор обеспечивает жизнедеятельность всех отраслей народного хозяйства. Экономический рост неизбежно влечет за собой увеличение спроса на энергетические ресурсы и требует решения накопившихся проблем старения оборудования.
Для долгосрочного стабильного обеспечения экономики и населения региона энергоресурсами необходима обоснованная электроэнергетическая политика, основанная на чутком контроле и мониторинге процессов в электросетях.
Увеличить отклик системы мониторинга электросети на события, происходящие в узлах сети, и призвана программа «Сфера» — замена устаревшего ПО визуализации событий. «Сфера» предназначена для отображения схем электросетей на экране с большим разрешением и размером в центре мониторинга электросетей. Важным параметром который требовался от программы являлась работа в режиме реального времени с локальным обновлением изображения в точке события. Для этого применялась библиотека WPF. Были разработаны инструменты для отображения и редактирования элементов схемы также в реальном времени. Для управления событиями использовался скриптовый движок Pascal. Графическая часть разработана на WPF/C#, логика событий — через Delphi/ScriptPascal. Язык Delphi не удалось избежать из-за требований обратной совместимости с целым рядом старых приложений.
Основные компоненты
Модульная архитектура
Подсистема Delphi.
- БД графического представления.
- Модуль сценариев (скриптов).
Подсистема C#/WPF.
- Визуализация схемы графического представления.
- Редактирование схемы графического представления.
Графическое ядро
Модуль графики системы предназначен для визуализации схем, основанных на данных CIM-модели. Отображения расчетных величин и данных телеметрии — как реального времени, так и на заданное время. Графика использует для визуализации высокопроизводительную библиотеку Windows Presentation Foundation (WPF), которая может производить рендеринг как программно, так и используя аппаратное ускорение современных видеокарт.
Схемы графики автоматически обновляют данные при изменении данных в CIM-модели. Графическое представление может настраиваться с использованием скриптового языка, что позволяет гибко реализовать бизнес-логику. Скриптовые сценарии производят расчеты и могут полностью определять поведение объектов графического представления.
Внешний вид визуального представления подобен графу, вершинами которого являются узлы и объекты-оборудование, а ребрами — ветви и объекты-связи. Помимо узлов и ветвей на визуальном представлении расположены блоки вывода данных, которые отображают некоторые значения из хранилища, и графические примитивы для оформления схемы.
Узлы могут быть представлены в виде:
- круга;
- горизонтальной шины;
- вертикальной шины.
На узле расположены точки присоединения, которые используются для присоединения дополнительного оборудования и узлов. Они расположены по периметру узла с определенным шагом, который можно настраивать. Подключение дополнительного оборудования контролируется сценариями, которые по настраиваемой логике включают или выключают отображение знаков дополнительного оборудования:
- генератор;
- синхронный компенсатор;
- нагрузка;
- реактор;
- батарея статических конденсаторов;
- активный шунт.
Ветви соединяют между собой узлы. Ветвь изображается ломаной линией, концы которой присоединены к точкам подключения узла начала и конца. Сплошной линией отображается включенная ветвь, пунктирной — отключенная. На ветви может располагаться знак, отображение которого контролируется сценариями:
- трансформатор;
- устройство продольной компенсации;
- кабельная линия;
- токоограничивающий реактор;
- коммутационный аппарат.
Отображение знака и состояния линии зависит от параметров сущности, с которой связана ветвь.
Представление содержит множество слоев, и любой объект представления может быть отнесен к одному или нескольким слоям. Если хотя бы один из слоев, к которым отнесен объект, включен, то объект видим. Если объект не отнесен ни к одному слою, то он виден всегда.
Узлы в представлении можно объединять в группы для замены при определенном масштабе на изображение группы. Например, узлы, входящие в состав подстанции, можно объединить в группу «подстанция», а подстанции объединить в группу «станция». Настроив разные масштабы замены отображения узлов на группу, можно добиться, что при приближении (увеличении масштаба) изображение станции детализируется до изображений подстанций, при дальнейшем приближении будут показаны индивидуальные узлы подстанций.
Объекты-оборудование и объекты-связи объединяют узлы и ветви. Как и в случае с группами, перемещение объектов приводит к перемещению входящих в состав ветвей и узлов.
Для отображения карт местности или другой графической информации графическое представление позволяет отображать растровые и векторные изображения. Изображения всегда отображаются под объектами.
На графическом уровне WPF работа шла с такими элементами как Visual, DrawingVisual, Drawing, XAML, для работы с большими объемами графических примитивов использовалось квадродерево, которое оптимизировало VisualCollection и Canvas-объекты.
Дополнительной оптимизацией графики, в которой использовалось квадродерево был режим, который сам по себе не свойственен системе WPF, но был добавлен нами через функции WinApi. Этот режим использовал функции WinApi, которые выполняли перемещение видимого на экране фрагмента изображения попиксельно в зависимости от направления скролинга, при этом WPF оставалось обновить лишь небольшой фрагмент, который оставался после смещения изображения. Здесь включалось квадродерево и передавало в движок WPF, работающий с VisualCollection и getVisualChild, только набор объектов, входящих в область обновления. Когда программа запускается в центре мониторинга на большом мониторе, составленном из множества обычных мониторов (около 20-30 штук), то при отображении событий использование квадродерева существенно снимало нагрузку с WPF и обеспечивало высокую производительность графики.
Трудности, с которыми столкнулись при разработке, и оптимизация
Оптимизация хит-теста
При работе с хит тестом в системе WPF были выявлены проблемы производительности для некоторых типов примитивов. Решено было заменить отображенный тип на эквивалентный, но не имеющий этих проблем при операции хит-теста.
Так как мы управляем хит-тестом в обход перебора объектов системой WPF, то в обработчики попадания мы собственными силами передаем информацию о реальном визуальном элементе, для которого выполняется хит-тест, а не о том элементе который возвращает WPF. В главной функции обработки хит-теста он был нами оптимизирован под использование квадродерева. Система хит-теста в WPF реализована не достаточно эффективно и не достаточно гибко для наших задач. Таким образом, чтобы реализовать оптимизацию под квадродерево, пришлось имитировать хит-тест «микро-представления» для каждого объекта в отдельности с помощью вынесения вектора перемещения и матрицы трансформации из объектов Drawing в объекты DrawingVisual.
Внутри самой функции хит-теста алгоритм следующий.
- Делаем регион в виде очень короткой линии и проверяем ее на пересечение.
- Создаем заглушку «микро-представления» — аналог Canvas.
- Специальный визуальный элемент помещается на «микро-представление» и инкапсулирует векторную графику из каждого визуального элемента по очереди.
- Передаем информацию о трансформации в визуальный элемент назначения из исходного и отменяем соответствующую трансформацию векторной графики.
var t = VisualTreeHelper.GetTransform(v); var o = VisualTreeHelper.GetOffset(v); destVisual.Transform = t; destVisual.Offset = o; if (drw.IsFrozen) drw = (DrawingGroup)drw.Clone(); drw.Transform = Transform.Identity;
- Особым образом формируем элемент для хит-теста перетока мощности, который при своем рендеринге подготавливает набор визуальных элементов, корректно для хит-теста заменяющих низкоуровневые операции трансформации.
- Восстанавливаем трансформацию векторной графики. Операция отмены-восстановления трансформации связана с некорректной работой системы хит-теста с трансформацией внутри Drawing объектов, в то время как рендеринг в битмапку, конвертирование в EMF и т.д. требует сборки векторной графики на низком уровне (Drawing) с учетом трансформации визуальных элементов переданных внутрь этой графики.
Оптимизация рендеринга векторных объектов
По умолчанию WPF использует векторную модель прорисовки всех объектов, что снижало производительность. Для исправления была реализована система кэшей битмапов поверх векторных объектов Visual. Кэши перерисовывались при смене масштаба либо обновлении контента кэшированых Visual.
Система кэширования построена таким образом, что на каждый визуальный элемент типа Visual приходилось создавать отдельный битмап объект через взаимодействие с ОС Windows и получение хэндла на битмап, а так как количество доступных хэндлов для процесса ограничено, то необходимо было учитывать эти лимиты. Соответственно в случае, если в кадре было видно большое количество объектов, то при превышении этого лимита приходилось оставлять часть объектов без кэшей, но такие случаи бывают не часто, и часть закэшированых изображений которая работала все равно достаточно хорошо, повышала производительность. Для расчета лимитов используется следующий подход.
RegistryKey hklm = Registry.LocalMachine;
hklm = hklm.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows");
GDIResourcesMax = (int)hklm.GetValue("GDIProcessHandleQuota");
double GDIUsageLimit = 0.7;
double GDISafeResourcesLimit = GDIUsageLimit * GDIResourcesMax;
public static int GetGuiResourcesGDICount()
{
return GetGuiResources(Process.GetCurrentProcess().Handle, 0);
}
- GDIUsageLimit — коэффициент, который показывает сколько хэндлов мы можем безопасно зарезервировать для нормальной работы ПО;
- GDISafeResourcesLimit — соответственно, количество объектов с учетом коэффициента;
- GetGuiResourcesGDICount — функция, позволяющая определить сколько выделено хендлов текущим процессом.
На основе данных параметров при старте приложения происходила резервация хендлов битмапок в массив, из которого они в дальнейшем извлекались системой кэширования по мере необходимости.
Каждый битмап-объект резервируется таким кодом:
var bm = new BitmapCacheBrush();
bitmapCacheFree.Add(bm);
База данных
Файл графического представления представляет собой бэкап базы данных Firebird и хранит информацию о способе визуализации графа. Визуальное представление создается пользователем и находится в файле с расширением “*.GVIEW”. Файл может содержать несколько схем-представлений.
Основной таблицей является BASE_ENTITY, в ней хранятся все основные свойства объектов представления. Для каждого конкретного типа есть таблица, в которой хранятся остальные поля. Например, для узлов это BUS_ENTITY, для ветвей — BRANCH_ENTITY. Эти детализирующие таблицы имеют связь один к одному с таблицей BASE_ENTITY. Целостность данных в основном контролируется ссылочной целостностью между таблицами. Например, удаление представления из таблицы Gview автоматически удаляет все принадлежащие ей объекты из таблицы BASE_ENTITY, а затем каскадно удаляются детализации из таблиц конкретных типов.
Шаблоны блоков имеют структуру, схожую с представлениями, и хранятся также в таблице GView с установленным флагом HIDDEN. В файле не сохраняются внутренние объекты блоков, они полностью конструируются при создании на основе шаблонов. Поэтому настройки внутренних объектов блока, сделанные сценариями, теряются при сохранении и загрузке.
Сценарии
Сценарии (скрипты) управляют знаками оборудования и другими элементами схемы в зависимости от событий, поступающих от реального оборудования в центр мониторинга. В зависимости от событий скрипты управляют видимостью, эффектом мерцания, цветовой подсветкой, выводом данных в элементы дополнительной информации, которые якорями привязаны к знакам оборудования, видимостью групп, отдельных элементов и т.д.
Сценарии необходимы для тонкой настройки визуализации CIM-объектов и для вычисления данных, отображаемых полями и блоками вывода. Модуль скриптов позволяет в режиме реального времени производить произвольные вычисления над объектами графического представления и связанными с ними сущностями, на основании полученных данных управлять внешним видом и выводить расчетные данные и данные телеметрии.
Благодаря скриптам можно по-разному визуализировать различные атрибуты объекта графического представления, такие как текст, шрифт, цвет, размер и угол наклона, произвольно управлять видимостью представлений.
Перед помещением объекта на графическое представление или при изменении свойств сущности, связанной с объектом, модуль скриптов вызывает скрипт и передает в него контекст объекта и его окружения. Суть скрипта заключается в установке видимости, настройке свойств объекта, переданного в обработку или расчете данных выводимых для объекта. Например, на основании достоверности данных можно изменить цвет объекта или определить, какие дополнительные поля должны быть показаны, а затем скрыть или показать привязанные к объекту поля или блоки данных.
Исполнение скриптов
Исполнение сценариев происходит в контексте главного потока приложения.
В целях улучшения производительности применяется «ленивое» выполнение, т.е. исполнение сценария, всегда откладывается до момента, когда его данные действительно нужны. Например, исполнение скриптов, используемых для получения текста и других параметров полей вывода, откладывается до момента визуализации. Это означает, что если представление привязано к невидимому слою, или значение масштаба невидимости не позволяет его увидеть, то и скрипт, вычисляющий поле, не будет исполняться до тех пор, пока представление не станет видимым.
При изменении данных сущности происходит автоматическое обновление всех представлений, привязанных к нему, для этого повторно выполняются скрипты, которые зависят от данных сущности. Т.е. если в скрипте происходит обращение к контекстной переменной Entity, то считается, что скрипт зависит от сущности, и он будет пересчитан.
На действия пользователя по клику или двойному клику соответствующие сценарии из текущего профиля скриптов запускаются немедленно.
В процессе выполнения скрипта могут происходить ошибочные ситуации, приводящие к выбросу исключений. Необработанные исключительные ситуации отображаются модулем нотификации в виде всплывающих уведомлений, а также сохраняются в локальный каталог пользователя.
Общие модули
Общие модули предназначены для определения кода программных модулей, многократно используемых скриптами графического представления.
В секции «Модули» диалогового окна «Общие модули скриптов» можно добавить, изменить или удалить модули скриптов, нажав соответствующие кнопки. При определении общего модуля необходимо задать его имя, по которому он будет доступен из скрипта. В правой части диалогового окна, в секции «Модуль», с помощью редактора скриптов задается исходный код модуля. Созданные модули можно многократно использовать при определении кода различных скриптов.
Для использования кода общих модулей в скриптах следует воспользоваться следующей формой записи скрипта, и в заголовке скрипта с помощью определения uses перечислить через запятую подключаемые модули.
{$MANUAL}
// Подключение общих модулей
uses 'common', 'math';
// Определение функции скрипта
function __f__: Variant;
begin
// Вызов функции A() модуля ‘common’
Result := A();
end;
Begin
end.
Профили скриптов
Профили скриптов позволяют создавать несколько наборов скриптов, вызываемых при возникновении следующих событий графического представления:
- изменение данных сущности;
- одинарный или двойной клик мыши по графическому объекту;
- изменение масштаба графического представления.
При изменении данных сущностей производится обновление текста полей вывода и блоков, привязанных к этим сущностям. Для этого выполняются скрипты, содержащиеся в полях вывода данных, которые вычисляют выводимый текст и задают форматирование.
При одинарном или двойном клике мыши по графическому объекту может быть полезным вывести какой-либо всплывающий диалог, содержащий детализацию по выделенному графическому объекту.
При изменении масштаба графического представления может быть полезным немного увеличить или уменьшить масштаб объектов графического представления, либо скрыть излишние объекты, чтобы не загромождать ими экран.
Суть профилей сценариев представления заключается в предоставлении возможности по-разному обработать перечисленные выше события, в зависимости от выбранного пользователем профиля. Так можно создать профили, которые будут настраивать цвета объектов по-разному или по-разному будут рассчитывать выводимые данные. В ленточном меню графического представления пользователь может выбрать активный профиль скриптов, от которого будет зависеть текущее поведение графического представления. При смене профиля все сущности графического представления обрабатываются скриптом «Изменение сущности»; если сценарий сложный, и объектов много, то это может занять некоторое время.
Выбрать активный профиль скриптов можно с помощью выпадающего списка.
Профиль скриптов в ленточном меню.
Чтобы добавить новый профиль скриптов, требуется нажать кнопку «Добавить», расположенную под списком профилей диалогового окна «Настройка профилей сценариев», и указать имя и описание профиля. После чего, с помощью редактора скриптов можно ввести код профиля во вкладках:
- изменение данных сущности;
- щелчок мыши;
- двойной щелчок мыши;
- изменение масштаба.
Дополнительные функции программы
Редактирование ветвей и примитивов
Функция редактирования ветвей включает возможность создания узлов излома на ветви и удаления узлов. Также поддерживается ортогональный режим, в котором ветви соответственно выровнены по осям координат под прямыми углами. Поддерживается функционал интегрирования знаков оборудования на ветвь и перемещения их вдоль ветви.
При редактировании примитивов можно нарисовать произвольные элементы, такие как ломаные поли-линии, окружности, секторы, текстовые блоки и т.д.
![](https://habrastorage.org/files/601/5fc/6a6/6015fc6a6b524d299b31b2ddf1ff4a5c.png)
Работа со знаками оборудования
В работу со знаками оборудования в случае размещения на ветви входит функция отражения знака на ветви относительно оси ветви. Возможность изменения знака оборудования и изменения скрипта управляющего знаком. Как для обычных знаков, так и для знаков на ветви доступны функции привязки якорем дополнительных элементов управления, которые могут управляться скриптом и отображать дополнительную информацию.
Группы объектов
Объекты можно группировать и управлять видимостью групп из скриптов. Внешний вид групп определяется спецификацией CIM-моделей. Группы могут быть вложенными друг в друга и автоматически расширяться в зависимости от видимых/невидимых вложенных подгрупп.
![](https://habrastorage.org/files/bdd/3b7/fb6/bdd3b7fb63ba4b4bb7cc8bc22dc07156.png)
Блок вывода данных
Вкладка «Блок вывода данных» позволяет настроить конкретный экземпляр блока вывода данных, его положение, имя блока для использования в скриптах, координаты относительно объекта, к которому привязан блок вывода.
Форма редактирования блока
![](https://habrastorage.org/files/30d/45a/1ad/30d45a1ad1104f51af418b9903d636f3.png)
Пример вывода блоков на схеме
![](https://habrastorage.org/files/2b0/085/23d/2b008523ddcd4baf8d6bd9a3ab92a57e.png)
Использование слоев
Механизм слоев позволяет выполнять оптимизацию количества видимых объектов в зависимости от масштаба, либо от логики скриптов, либо настраиваться в ручную.
Копирование в буфер
Буфер обмена является мильтиформатным и поддерживает копирование как в векторном формате данных, так и в растровом.
Печать
Доступны функции как цветной печати, так и черно-белойЧ. Распечатать можно любую произвольную область схемы.
Экспорт в различные форматы
Доступен экспорт в различных форматах как растровых, так и векторных (WMF, EMF, SVG).
Поиск
Функция позволяет по фрагменту текста найти местоположение объектов на схеме, с автоматическим позиционированием области просмотра.
Навигатор
Окно навигатора позволяет видеть набросок всей схемы целиком в упрощенном виде с отображением текущей области просмотра.
![](https://habrastorage.org/files/a74/4d6/861/a744d68611e64a4592614ef33995a65b.png)
Оборудование, на котором работал софт
Видеокарта была специализированная, которая выдавала разрешение 8192x3072, у нее не было никаких ускорений, она поддерживала все только программно, чтобы тянуть большое разрешение на экране в центре мониторинга.
Объектов в кадре около 100 тысяч.
FPS в районе 1.
Описание установки в центре управления.
Большой экран в центре управления состоит из 24 секций, 8 в ширину и 3 в высоту. Каждая секция — это отдельный маленький экран разрешением 1024х1024 пикселя. 24 проектора установленны с обратной стороны, каждый светит на свой квадрат.
Видео-фрагмент масштабирования электросхемы на экране
![](https://habrastorage.org/files/398/998/a96/398998a96bd342d18422b1dbb66a271e.jpg)
Больше проектов:
Как за 5233 человеко-часа создать софт для микротомографа
SDK для внедрения поддержки электронных книг в формате FB2
Управление доступом к электронным документам. От DefView до Vivaldi
Интегрируем две системы видеонаблюдения: Axxon Next и SureView
Подробнее о разработке софта рентгеновского томографа
«Сфера»: как мониторить миллиарды киловатт-часов
Разработка простого плагина для JIRA для работы с базой данных
В помощь DevOps: сборщик прошивок для сетевых устройств на Debian за 1008 часов
Автообновление службы Windows через AWS для бедных