Я работаю в компании, которая занимается автоматизацией производственных процессов. Знаком не по наслышке с программируемыми логическими контроллерами (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 (так я ее назвал) должны быть удовлетворены следующие условия:
- 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();
}
}
}
Спасибо!
Автор: phmi