Я работаю в компании, которая занимается автоматизацией производственных процессов. Знаком не по наслышке с программируемыми логическими контроллерами (PLC), человеко-машинным интерфейсом (HMI) и SCADA (диспетчерское управление и сбор данных).
А еще я увлекаюсь программированием. В основном на языках C# и Java (Android). Когда я познакомился с технологией WPF и увидел как просто на ней реализуется графический интерфейс пользователя, восторгу моему не было предела. «Да это же убийца SCADA-систем», — подумал я. Надо просто каким-то образом связать .NET проект с устройствами ввода-вывода (PLC).
И тут ко мне пришла идея, которая в определенный момент приходит к каждому автоматчику. Сделать свою SCADA-систему. И не просто SCADA-систему, а SCADA-систему для .NET разработчиков (прежде всего для себя самого). Потому что порой когда переключаешься из Visual Studio в редактор «современной» SCADA-системы, хочется вырвать себе последние волосы. Но настоящие трудности начинаются, когда необходимо реализовать функционал, выходящий за рамки базового. Другое дело — мир программистов. Ответ практически на любой вопрос можно быстро найти на сайте stackoverflow.com через его поисковый интерфейс google.com.
Далее я опишу то, что у меня получилось сделать. Исходный код проекта доступен по ссылке https://github.com/phmi/phmi.
Для использования PHmi (так я ее назвал) должны быть удовлетворены следующие условия:
Разработку проекта можно разделить на две фазы: конфигурирование сервера и разработку проекта клиента.
Запустим PHmiConfigurator.exe. Появится следующее окно:

Нажмем на кнопку “New project”. Появится окно “New project”:

Введем параметры связи с PostgreSQL и желаемое название базы данных:

После нажатия на кнопку “Ok” новый проект будет создан:

Нажмем на кнопку “I/O devices”. В конфигураторе откроется вкладка “I/O devices”:

Добавим новое устройство ввода-вывода “IoDev” типа “Generic”. «Generic» — это устройство ввода вывода для отладочных целей. Либо оно используется, когда не нужно соединяться с реальным устройством. Оно поддерживает любые адреса тегов:


Нажмем на кнопку «Save» и закроем вкладку.
Откроем вкладку “Digital tags” и создадим новый цифровой тег.

Откроем вкладку “Numeric tags” и создадим новый аналоговый тег:

Поля описания, формата, единицы измерения, калибровочные границы необязательны и могут быть опущены.
Создадим новую категорию аварий:

Создадим новый тег аварий:

Создадим категорию трендов:

Создадим новый тег трендов:

В конфигураторе нажмем на кнопку “Build client”:

Выберем путь к файлам, чтобы их создать.
«Namespace» должно совпадать с названием будущего проекта Visual Studio.
Нажмем на кнопку “Build”.
По указанному пути должны появиться файлы:

Запустим Microsoft Visual Studio и создадим новый проект WPF. Целевой фреймворк должен быть “.Net Framework 4.0” или выше:


Добавим ссылку к PHmiClient.dll:



Добавим файлы PHmi.cs и PHmiResources.resx, созданные заранее:


Кликнем двойным щелчком по PHmiResources.resx и поменяем “Access modifier” на Public:

Откроем MainWindow.xaml. Добавим кнотрол Root в корневой Grid.
Привяжем PHmi к DataContext Rootа. Для этого создадим новый объект PHmi в ресурсах окна.
Добавим папку «Pages» для страниц. Добавим туда UserControl под названием «HomePage».

Страницы должны реализовывать интерфейс IPage. Листинг HomePage.xaml.cs представлен ниже.
А вот разметка Xaml файла:
Мы добавили TextBlock для отображения значения тегов.
Привяжем тип домашней страницы к Root:
Запустим приложение и посмотрим, что получилось:

Значений тегов не видно. Это потому что не запущен PHmiRunner.exe. Нажмем кнопку “Run” в конфигураторе.

Теперь добавим на страницу элементы управления.

Если включить “Digital tag", возникнет авария.
Добавим на страницу тренд.

Для отображения строк в соответствии с региональными настройками (дата, время и прочее), необходимо изменить XmlLanguage в конструкторе окна:
Спасибо!
А еще я увлекаюсь программированием. В основном на языках C# и Java (Android). Когда я познакомился с технологией WPF и увидел как просто на ней реализуется графический интерфейс пользователя, восторгу моему не было предела. «Да это же убийца SCADA-систем», — подумал я. Надо просто каким-то образом связать .NET проект с устройствами ввода-вывода (PLC).
И тут ко мне пришла идея, которая в определенный момент приходит к каждому автоматчику. Сделать свою SCADA-систему. И не просто SCADA-систему, а SCADA-систему для .NET разработчиков (прежде всего для себя самого). Потому что порой когда переключаешься из Visual Studio в редактор «современной» SCADA-системы, хочется вырвать себе последние волосы. Но настоящие трудности начинаются, когда необходимо реализовать функционал, выходящий за рамки базового. Другое дело — мир программистов. Ответ практически на любой вопрос можно быстро найти на сайте stackoverflow.com через его поисковый интерфейс google.com.
Далее я опишу то, что у меня получилось сделать. Исходный код проекта доступен по ссылке https://github.com/phmi/phmi.
Установка
Для использования PHmi (так я ее назвал) должны быть удовлетворены следующие условия:
- PHmi
- СУБД PostgreSQL v9.2 или выше (ее можно скачать с сайта www.postgresql.com)
- Microsoft Visual Studio 2010 или выше (экспресс версия или Community Edition подойдет)
Разработка проекта
Разработку проекта можно разделить на две фазы: конфигурирование сервера и разработку проекта клиента.
Конфигурирование сервера
Запустим PHmiConfigurator.exe. Появится следующее окно:

Нажмем на кнопку “New project”. Появится окно “New project”:

Введем параметры связи с PostgreSQL и желаемое название базы данных:

После нажатия на кнопку “Ok” новый проект будет создан:

Нажмем на кнопку “I/O devices”. В конфигураторе откроется вкладка “I/O devices”:

Добавим новое устройство ввода-вывода “IoDev” типа “Generic”. «Generic» — это устройство ввода вывода для отладочных целей. Либо оно используется, когда не нужно соединяться с реальным устройством. Оно поддерживает любые адреса тегов:


Нажмем на кнопку «Save» и закроем вкладку.
Откроем вкладку “Digital tags” и создадим новый цифровой тег.

Откроем вкладку “Numeric tags” и создадим новый аналоговый тег:

Поля описания, формата, единицы измерения, калибровочные границы необязательны и могут быть опущены.
Создадим новую категорию аварий:

Создадим новый тег аварий:

Создадим категорию трендов:

Создадим новый тег трендов:

Разработка проекта клиента
В конфигураторе нажмем на кнопку “Build client”:

Выберем путь к файлам, чтобы их создать.
«Namespace» должно совпадать с названием будущего проекта Visual Studio.
Нажмем на кнопку “Build”.
По указанному пути должны появиться файлы:

Запустим Microsoft Visual Studio и создадим новый проект WPF. Целевой фреймворк должен быть “.Net Framework 4.0” или выше:


Добавим ссылку к PHmiClient.dll:



Добавим файлы PHmi.cs и PHmiResources.resx, созданные заранее:


Кликнем двойным щелчком по PHmiResources.resx и поменяем “Access modifier” на Public:

Откроем MainWindow.xaml. Добавим кнотрол Root в корневой Grid.
<Window x:Class="ClientSample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:pHmiControls="clr-namespace:PHmiClient.Controls;assembly=PHmiClient" Title="MainWindow" Height="350" Width="525"> <Grid> <pHmiControls:Root/> </Grid> </Window>
Привяжем PHmi к DataContext Rootа. Для этого создадим новый объект PHmi в ресурсах окна.
<Window x:Class="ClientSample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:pHmiControls="clr-namespace:PHmiClient.Controls;assembly=PHmiClient" xmlns:clientSample="clr-namespace:ClientSample" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <clientSample:PHmi x:Key="PHmi"/> </Window.Resources> <Grid> <pHmiControls:Root DataContext="{StaticResource PHmi}"/> </Grid> </Window>
Добавим папку «Pages» для страниц. Добавим туда UserControl под названием «HomePage».

Страницы должны реализовывать интерфейс IPage. Листинг HomePage.xaml.cs представлен ниже.
using PHmiClient.Controls.Pages; using System; using System.Windows.Controls; namespace ClientSample.Pages { /// <summary> /// Interaction logic for HomePage.xaml /// </summary> public partial class HomePage : UserControl, IPage { public HomePage() { InitializeComponent(); } public object PageName { get { return "Home page"; } } public PHmiClient.Controls.IRoot Root { get; set; } } }
А вот разметка Xaml файла:
<UserControl x:Class="ClientSample.Pages.HomePage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:clientSample="clr-namespace:ClientSample" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" d:DataContext="{d:DesignInstance clientSample:PHmi, IsDesignTimeCreatable=True}"> <Grid> <StackPanel> <TextBlock Text="{Binding Path=IoDev.DigitalTag.Value}"/> <TextBlock Text="{Binding Path=IoDev.NumericTag.ValueString}"/> </StackPanel> </Grid> </UserControl>
Мы добавили TextBlock для отображения значения тегов.
Привяжем тип домашней страницы к Root:
<Window x:Class="ClientSample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:pHmiControls="clr-namespace:PHmiClient.Controls;assembly=PHmiClient" xmlns:clientSample="clr-namespace:ClientSample" xmlns:pages="clr-namespace:ClientSample.Pages" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <clientSample:PHmi x:Key="PHmi"/> </Window.Resources> <Grid> <pHmiControls:Root DataContext="{StaticResource PHmi}" HomePage="{x:Type pages:HomePage}"/> </Grid> </Window>
Запустим приложение и посмотрим, что получилось:

Значений тегов не видно. Это потому что не запущен PHmiRunner.exe. Нажмем кнопку “Run” в конфигураторе.

Теперь добавим на страницу элементы управления.
<UserControl x:Class="ClientSample.Pages.HomePage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:clientSample="clr-namespace:ClientSample" xmlns:pHmiControls="clr-namespace:PHmiClient.Controls;assembly=PHmiClient" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" d:DataContext="{d:DesignInstance clientSample:PHmi, IsDesignTimeCreatable=True}"> <Grid> <StackPanel> <TextBlock Text="{Binding Path=IoDev.DigitalTag.Value}"/> <TextBlock Text="{Binding Path=IoDev.NumericTag.ValueString}"/> <CheckBox IsChecked="{Binding Path=IoDev.DigitalTag.Value, Mode=TwoWay}" Content="{Binding Path=IoDev.DigitalTag.Description}"/> <pHmiControls:NumericInput NumericTag="{Binding Path=IoDev.NumericTag}"/> </StackPanel> </Grid> </UserControl>

Если включить “Digital tag", возникнет авария.
Добавим на страницу тренд.
<UserControl x:Class="ClientSample.Pages.HomePage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:clientSample="clr-namespace:ClientSample" xmlns:pHmiControls="clr-namespace:PHmiClient.Controls;assembly=PHmiClient" xmlns:trends="clr-namespace:PHmiClient.Controls.Trends;assembly=PHmiClient" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" d:DataContext="{d:DesignInstance clientSample:PHmi, IsDesignTimeCreatable=True}"> <Grid> <StackPanel> <TextBlock Text="{Binding Path=IoDev.DigitalTag.Value}"/> <TextBlock Text="{Binding Path=IoDev.NumericTag.ValueString}"/> <CheckBox IsChecked="{Binding Path=IoDev.DigitalTag.Value, Mode=TwoWay}" Content="{Binding Path=IoDev.DigitalTag.Description}"/> <pHmiControls:NumericInput NumericTag="{Binding Path=IoDev.NumericTag}"/> <trends:TrendViewer Height="400"> <trends:TrendPen TrendTag="{Binding Path=Trends.NumericTagTrend}" Brush="Red"/> </trends:TrendViewer> </StackPanel> </Grid> </UserControl>

Задание языка окна
Для отображения строк в соответствии с региональными настройками (дата, время и прочее), необходимо изменить XmlLanguage в конструкторе окна:
using System.Threading; using System.Windows; using System.Windows.Markup; namespace ClientSample { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { Language = XmlLanguage.GetLanguage( Thread.CurrentThread.CurrentUICulture.IetfLanguageTag); InitializeComponent(); } } }
Спасибо!