Initial Push

This commit is contained in:
2026-02-11 08:38:36 +01:00
commit 627050501d
81 changed files with 5500 additions and 0 deletions

63
ads-wrapper/.gitattributes vendored Normal file
View File

@@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

363
ads-wrapper/.gitignore vendored Normal file
View File

@@ -0,0 +1,363 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd

331
ads-wrapper/AdsManager.cs Normal file
View File

@@ -0,0 +1,331 @@
using System.Text.Json;
using System.Timers;
using TwinCAT;
using TwinCAT.Ads;
using TwinCAT.Ads.TypeSystem;
using TwinCAT.TypeSystem;
namespace Heisig.HMI.AdsManager;
// AdsManager Interface for dependency injection
public interface IAdsManager
{
public void Register(string variableName, EventHandler<ValueChangedEventArgs> eventHandler);
public void Deregister(string variableName, EventHandler<ValueChangedEventArgs> eventHandler);
public void WriteValue(string variableName, object value);
public object? ReadValue(string variableName);
public ISymbolLoader? GetSymbolLoader();
}
public sealed class AdsManager : IDisposable, IAdsManager
{
enum ConnectionState
{
DISCONNECTED,
CONNECTED
}
// Ads client instance
private readonly AdsClient _adsClient = new();
// Ads symbol loader
private ISymbolLoader? _symbolLoader;
// Last value of online change count
private uint? _lastOnlineChangeCount = null;
// Online change detected
private bool _onlineChangeDetected = false;
// Dictionary with symbols and events for variable changing events
private readonly Dictionary<string, List<EventHandler<ValueChangedEventArgs>>> _test = [];
// Timer to test cyclically if we are still connected
private readonly System.Timers.Timer _timer = new();
// Internal connection state
private ConnectionState _state = ConnectionState.DISCONNECTED;
// Filename with the connection settings
private const string _filename = "AdsSettings.json";
// Settings for the connection
private readonly AdsSettings _settings = new();
// Thread safety lock
private static readonly object _lock = new();
// Json serializer options
private readonly JsonSerializerOptions _jsonOptions = new() { WriteIndented = true };
public AdsManager()
{
// Read settings file
_settings = ReadSettings();
// Start cyclic connection test
_timer.Interval = _settings.ReconnectIntervalMS;
_timer.Elapsed += CheckConnection;
_timer.Start();
}
public void Dispose()
{
_timer.Stop();
_timer.Dispose();
_adsClient.Dispose();
}
private void WriteSettings(AdsSettings settings)
{
string text = JsonSerializer.Serialize(settings, _jsonOptions);
File.WriteAllText(_filename, text);
}
private AdsSettings ReadSettings()
{
AdsSettings settings = new();
string text;
if (!File.Exists(_filename))
WriteSettings(settings);
else
{
text = File.ReadAllText(_filename);
settings = JsonSerializer.Deserialize<AdsSettings>(text) ?? new AdsSettings();
}
return settings;
}
// Register a callback on variable change
public void Register(string variableName, EventHandler<ValueChangedEventArgs> eventHandler)
{
List<EventHandler<ValueChangedEventArgs>> tempList;
// Add new variable entry
#pragma warning disable CA1854 // Methode „IDictionary.TryGetValue(TKey, out TValue)“ bevorzugen
if (!_test.ContainsKey(variableName))
#pragma warning restore CA1854 // Methode „IDictionary.TryGetValue(TKey, out TValue)“ bevorzugen
{
// Add new entry
tempList = [eventHandler];
_test.Add(variableName, tempList);
}
else
{
// Append event to variable
tempList = _test[variableName];
foreach (EventHandler<ValueChangedEventArgs> handler in tempList)
{
if (handler == eventHandler)
return;
}
tempList.Add(eventHandler);
_test[variableName] = tempList;
}
// If we are already connected, directly register variable
if (_state == ConnectionState.CONNECTED)
{
try
{
Symbol? symbol = (Symbol?)_symbolLoader?.Symbols[variableName];
if (symbol != null)
symbol.ValueChanged += eventHandler;
}
catch (Exception)
{
Console.WriteLine($"Variable {variableName} could not be found");
}
Console.WriteLine($"Variable {variableName} registered");
}
}
// Remove a registration from the dictionary
public void Deregister(string variableName, EventHandler<ValueChangedEventArgs> eventHandler)
{
List<EventHandler<ValueChangedEventArgs>>? tempList;
bool eventRemoved = false;
if (_test.TryGetValue(variableName, out tempList))
{
foreach (EventHandler<ValueChangedEventArgs> handler in tempList)
{
if (handler == eventHandler)
{
tempList.Remove(eventHandler);
// If we are connected remove directly remove variable changed event
if (_state == ConnectionState.CONNECTED)
{
try
{
Symbol? symbol = (Symbol?)_symbolLoader?.Symbols[variableName];
if (symbol != null)
symbol.ValueChanged -= eventHandler;
}
catch(Exception)
{
Console.WriteLine($"Variable {variableName} could not be found and deregistered");
}
Console.WriteLine("Variable {0} deregistered", variableName);
}
break;
}
if (eventRemoved)
_test[variableName] = tempList;
}
}
}
private void RegisterValueChangedEvents()
{
if (_symbolLoader == null)
return;
Symbol symbol;
foreach(KeyValuePair<string, List<EventHandler<ValueChangedEventArgs>>> entry in _test)
{
symbol = (Symbol)_symbolLoader.Symbols[entry.Key];
foreach (EventHandler<ValueChangedEventArgs> handler in entry.Value)
symbol.ValueChanged += handler;
}
}
public void WriteValue(string variableName, object value)
{
if (_state == ConnectionState.CONNECTED)
{
_adsClient.TryWriteValue(variableName, value);
}
}
public object? ReadValue(string variableName)
{
if (_state == ConnectionState.CONNECTED)
{
_adsClient.TryReadValue(variableName, out object? value);
return value;
}
else
return null;
}
// Cyclic check if we are still connected to the ads server
private void CheckConnection(object? sender, ElapsedEventArgs e)
{
lock(_lock)
{
// Stop timer
_timer.Stop();
}
switch (_state)
{
case ConnectionState.DISCONNECTED:
// Connect if not already connected
if (!_adsClient.IsConnected)
_adsClient.Connect(_settings.AdsAdress, _settings.AdsPort);
// Check if we have a connection
if (TestIfConnected())
{
Console.WriteLine("Connected");
Console.WriteLine("Reading Symbols ...");
// Load ads symbols
_symbolLoader = SymbolLoaderFactory.Create(_adsClient, SymbolLoaderSettings.Default);
Console.WriteLine("{0} Symbols read", _symbolLoader.Symbols.Count);
// Read last online change count
lock(_lock)
{
_lastOnlineChangeCount = (uint)((Symbol)_symbolLoader.Symbols[_settings.OnlineChangeCntVar]).ReadValue();
// Change state to connected
_state = ConnectionState.CONNECTED;
}
// Register value changed events
RegisterValueChangedEvents();
}
break;
case ConnectionState.CONNECTED:
if (!TestIfConnected())
{
Console.WriteLine("Connection lost");
lock (_lock)
_state = ConnectionState.DISCONNECTED;
}
else
{
try
{
uint onlineChangeCnt = (uint)((Symbol)_symbolLoader!.Symbols[_settings.OnlineChangeCntVar]).ReadValue();
lock (_lock)
{
_onlineChangeDetected = false;
if (onlineChangeCnt != _lastOnlineChangeCount)
_onlineChangeDetected = true;
_lastOnlineChangeCount = (uint)((Symbol)_symbolLoader.Symbols[_settings.OnlineChangeCntVar]).ReadValue();
}
// Reread symbols
if (_onlineChangeDetected)
{
Console.WriteLine("Online change detected. Rereading symbols ...");
_symbolLoader = SymbolLoaderFactory.Create(_adsClient, SymbolLoaderSettings.Default);
Console.WriteLine("{0} Symbols read", _symbolLoader.Symbols.Count);
RegisterValueChangedEvents();
}
}
catch(Exception)
{
Console.WriteLine("Could not read online change count");
}
}
break;
}
lock (_lock)
{
// Restart timer
_timer.Start();
}
}
private bool TestIfConnected()
{
bool connected;
// Test if connection could be established and is still alive
try
{
// Read ads server status
StateInfo state = _adsClient.ReadState();
connected = true;
}
catch (AdsErrorException)
{
connected = false;
}
return connected;
}
public ISymbolLoader? GetSymbolLoader()
{
return _symbolLoader;
}
}

View File

@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Title>Ads Manager</Title>
<Authors>M.Heisig</Authors>
<Company>Heisig GmbH</Company>
<Description>Wrapper für den Ads Client von Beckhoff um automatische reconnects und onlinechanges zu behandeln.</Description>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageOutputPath>C:\Users\matthias.heisig\Documents\nuget_packages</PackageOutputPath>
<PackageProjectUrl>https://gitlab.cmblu.de/matthias.heisig/ads-wrapper</PackageProjectUrl>
<Version>1.0.0.7</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Beckhoff.TwinCAT.Ads" Version="6.1.197" />
</ItemGroup>
<ItemGroup>
<None Update="README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,25 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.8.34525.116
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdsManager", "AdsManager.csproj", "{4B80F068-4E6D-4D2D-831B-8150970600C7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4B80F068-4E6D-4D2D-831B-8150970600C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4B80F068-4E6D-4D2D-831B-8150970600C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4B80F068-4E6D-4D2D-831B-8150970600C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4B80F068-4E6D-4D2D-831B-8150970600C7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {32AA8544-D809-4C87-81DE-505FDD964D85}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,27 @@
namespace Heisig.HMI.AdsManager;
/// <summary>
/// Struct with the settings for connecting to the ads server
/// </summary>
public class AdsSettings
{
/// <summary>
/// Ads server address
/// </summary>
public string AdsAdress { get; set; } = "127.0.0.1.1.1";
/// <summary>
/// Ads server port
/// </summary>
public int AdsPort { get; set; } = 851;
/// <summary>
/// Intervall for reconnect tries
/// </summary>
public int ReconnectIntervalMS { get; set; } = 2000;
/// <summary>
/// Variable name for which contains the online change counter
/// </summary>
public string OnlineChangeCntVar { get; set; } = "TWinCAT_SystemInfoVarList._AppInfo.OnlineChangeCnt";
}

2
ads-wrapper/README.md Normal file
View File

@@ -0,0 +1,2 @@
# Ads Manager
Ads Wrapper klasse um automatische reconnects und online changes mit einem ADS Server der Beckhoff steuerung zu behandeln.

63
uniper_hmi/.gitattributes vendored Normal file
View File

@@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

367
uniper_hmi/.gitignore vendored Normal file
View File

@@ -0,0 +1,367 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Testproject folders
AdsSessionTest/
HMI_Export/
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd

View File

@@ -0,0 +1 @@
{"AdsAdress":"10.103.32.50.1.1","AdsPort":851,"ReconnectIntervalMS":1000,"OnlineChangeCntVar":"TWinCAT_SystemInfoVarList._AppInfo.OnlineChangeCnt"}

98
uniper_hmi/README.md Normal file
View File

@@ -0,0 +1,98 @@
# Uniper
Uniper maintenance hmi project.
# Gitflow
1. A develop branch is created from main
2. A release branch is created from develop
3. Feature branches are created from develop
4. When a feature is complete it is merged into the develop branch
5. When the release branch is done it is merged into develop and main
6. If an issue in main is detected a hotfix branch is created from main
7. Once the hotfix is complete it is merged to both develop and main
## Start new feature
```
git flow feature start feature_branch
```
oder
```
git checkout develop
git checkout -b feature_branch
```
## Finish a feature
```
git flow feature finish feature_branch
```
oder
```
git checkout develop
git merge feature_branch
```
## Making a release
```
git flow release start 0.1.0
```
oder
```
git checkout develop
git checkout -b release/0.1.0
```
## Finishing a release
```
git flow release finish '0.1.0'
```
oder
```
git checkout main
git merge release/0.1.0
```
## Starting a Hotfix
```
git flow hotfix start hotfix_branch
```
oder
```
git checkout main
git checkout -b hotfix_branch
```
## Finishing a Hotfix
```
git flow hotfix finish hotfix_branch
```
oder
```
git checkout main
git merge hotfix_branch
git checkout develop
git merge hotfix_branch
git branch -D hotfix_branch
```
## Workflow example
```
git checkout main
git checkout -b develop
git checkout -b feature_branch
# work happens on feature branch
git checkout develop
git merge feature_branch
git checkout main
git merge develop
git branch -d feature_branch
```
## Hotfix example
```
git checkout main
git checkout -b hotfix_branch
# work is done commits are added to the hotfix_branch
git checkout develop
git merge hotfix_branch
git checkout main
git merge hotfix_branch
```

25
uniper_hmi/Uniper.sln Normal file
View File

@@ -0,0 +1,25 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.8.34408.163
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UniperHMI", "UniperHMI\UniperHMI.csproj", "{8D725B27-1242-4C66-ACD8-45F02098C7D3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{8D725B27-1242-4C66-ACD8-45F02098C7D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8D725B27-1242-4C66-ACD8-45F02098C7D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8D725B27-1242-4C66-ACD8-45F02098C7D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8D725B27-1242-4C66-ACD8-45F02098C7D3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {13973C5F-C164-4478-A4B1-1694557CC459}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,17 @@
<Application x:Class="UniperHMI.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:UniperHMI">
<Application.Resources>
<!-- MahApps Metro style themes -->
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- MahApps.Metro resource dictionaries. Make sure that all file names are Case Sensitive! -->
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
<!-- Theme setting -->
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Dark.Blue.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -0,0 +1,46 @@
using System.Windows;
using Heisig.HMI.AdsManager;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using TcEventLoggerAdsProxyLib;
namespace UniperHMI;
public partial class App : Application
{
public static IHost? AppHost { get; private set; }
public App()
{
AppHost = Host.CreateDefaultBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddSingleton<MainWindow>();
services.AddSingleton<MainWindowVM>();
services.AddSingleton<IAdsManager, AdsManager>();
services.AddTransient<AutomaticModePageVM>();
services.AddTransient<BatteryOverviewPageVM>();
services.AddTransient<StringOverviewPageVM>();
services.AddTransient<ModuleOverviewPageVM>();
services.AddSingleton<TcEventLogger>();
})
.Build();
}
protected override async void OnStartup(StartupEventArgs e)
{
await AppHost!.StartAsync();
var startupForm = AppHost.Services.GetRequiredService<MainWindow>();
startupForm.Show();
base.OnStartup(e);
}
protected override async void OnExit(ExitEventArgs e)
{
await AppHost!.StopAsync();
base.OnExit(e);
}
}

View File

@@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View File

@@ -0,0 +1,33 @@
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace UniperHMI
{
public class DateTimeToEventTimeConverter : IValueConverter
{
// 599264352000000000 ticks is a date used by beckhoff for events that didnt happen up to this point
public const long NoTime = 599264352000000000;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is DateTime dt)
{
if (dt.Ticks == NoTime)
return "";
else
{
CultureInfo cultureInfo = CultureInfo.CurrentCulture;
return dt.ToString("G", cultureInfo);
}
}
else
throw new InvalidOperationException("Target must be of type DateTime");
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
}

View File

@@ -0,0 +1,12 @@
<Window x:Class="HMIToolkit.AnalogMotorWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:HMIToolkit"
mc:Ignorable="d"
Title="AnalogMotorWindow" SizeToContent="WidthAndHeight">
<Grid>
<local:AnalogMotorControl />
</Grid>
</Window>

View File

@@ -0,0 +1,14 @@
using System.Windows;
namespace HMIToolkit;
/// <summary>
/// Interaktionslogik für AnalogMotorWindow.xaml
/// </summary>
public partial class AnalogMotorWindow : Window
{
public AnalogMotorWindow()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,12 @@
<Window xmlns:HMIToolkit="clr-namespace:HMIToolkit" x:Class="UniperHMI.BinaryValveWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:UniperHMI"
mc:Ignorable="d"
Title="BinaryValveWindow" SizeToContent="WidthAndHeight" ResizeMode="NoResize">
<Grid>
<HMIToolkit:BinaryValveControl />
</Grid>
</Window>

View File

@@ -0,0 +1,15 @@
using System.Windows;
namespace UniperHMI
{
/// <summary>
/// Interaktionslogik für BinaryValveWindow.xaml
/// </summary>
public partial class BinaryValveWindow : Window
{
public BinaryValveWindow()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,46 @@
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace HMIToolkit;
class FeedbackToColorConverter : IValueConverter
{
public Object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (targetType != typeof(System.Windows.Media.Brush))
throw new InvalidOperationException("The target must be a brush");
#pragma warning disable CS8604 // Mögliches Nullverweisargument.
int num = int.Parse(value.ToString());
#pragma warning restore CS8604 // Mögliches Nullverweisargument.
switch (num)
{
case 0:
return DependencyProperty.UnsetValue;
case 1:
return System.Windows.Media.Brushes.Green;
case 2:
return System.Windows.Media.Brushes.GreenYellow;
case 3:
return System.Windows.Media.Brushes.DarkOrange;
case 4:
return System.Windows.Media.Brushes.DarkRed;
default:
return DependencyProperty.UnsetValue;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}

View File

@@ -0,0 +1,91 @@
<UserControl x:Class="HMIToolkit.AnalogMotorControl"
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:local="clr-namespace:HMIToolkit"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:AnalogMotorControlVM, IsDesignTimeCreatable=True}"
Width="Auto"
Height="Auto">
<UserControl.Resources>
<local:FeedbackToColorConverter x:Key="feedbackConverter" />
</UserControl.Resources>
<d:DesignerProperties.DesignStyle>
<Style TargetType="UserControl">
<!-- Property="Background" Value="White" /> -->
<Setter Property="Height" Value="Auto" />
<Setter Property="Width" Value="Auto" />
</Style>
</d:DesignerProperties.DesignStyle>
<Grid>
<Grid Margin="5" ShowGridLines="False">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Row 0 -->
<Label Grid.Row="0" Content="{Binding SName}" HorizontalAlignment="Center" FontSize="24" FontWeight="Bold"/>
<!-- Row 1 -->
<Label Grid.Row="1" Content="Interlocks:" Margin="0,0,0,-3"/>
<!-- Row 2 -->
<Grid Grid.Row="2">
<local:IntlkButtonControl DataContext="{Binding Interlocks}"/>
</Grid>
<!-- Row 3 -->
<Label Grid.Row="3" Content="Setpoint:" Margin="0,0,0,-3"/>
<!-- Row 4 -->
<local:AnalogValue DataContext="{Binding Setpoint}" Grid.Row="4"/>
<!-- Row 5 -->
<Label Grid.Row="5" Content="Process value:" Margin="0,0,0,-3"/>
<!-- Row 6 -->
<local:AnalogValue DataContext="{Binding ProcessValue}" Grid.Row="6"/>
<!-- Row 7 -->
<Grid Grid.Row="7">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Content="Control:" Margin="0,0,0,-3" />
<Button x:Name="btnOpen" DataContext="{Binding StartButton}" Command="{Binding ButtonClickedCommand}" IsEnabled="{Binding XRelease}" Background="{Binding IFeedback, Converter={StaticResource feedbackConverter}}" Grid.Row="1" Grid.Column="0" Content="Start" Height="80" Width="80" Margin="0,0,5,5" />
<Button x:Name="btnClose" DataContext="{Binding StopButton}" Command="{Binding ButtonClickedCommand}" IsEnabled="{Binding XRelease}" Background="{Binding IFeedback, Converter={StaticResource feedbackConverter}}" Grid.Row="1" Grid.Column="1" Content="Stop" Height="80" Width="80" Margin="0,-5,0,0"/>
</Grid>
<!-- Row 8 -->
<Grid Grid.Row="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Content="Mode:" Margin="0,0,0,-3" />
<Button x:Name="btnAuto" DataContext="{Binding AutomaticButton}" Command="{Binding ButtonClickedCommand}" IsEnabled="{Binding XRelease}" Grid.Row="1" Grid.Column="0" Content="Auto" Height="80" Width="80" Margin="0,0,5,0"/>
<Button x:Name="btnManual" DataContext="{Binding ManualButton}" Command="{Binding ButtonClickedCommand}" IsEnabled="{Binding XRelease}" Grid.Row="1" Grid.Column="1" Content="Man" Height="80" Width="80" Margin="0,-5,0,-5"/>
</Grid>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,23 @@
using System.Windows.Controls;
namespace HMIToolkit
{
/// <summary>
/// Interaktionslogik für AnalogMotorControl.xaml
/// </summary>
public partial class AnalogMotorControl : UserControl
{
public AnalogMotorControl()
{
InitializeComponent();
// Unloaded += OnUnloaded;
}
private void OnUnloaded(object? sender, EventArgs e)
{
var disposable = DataContext as IDisposable;
disposable?.Dispose();
}
}
}

View File

@@ -0,0 +1,83 @@
using CommunityToolkit.Mvvm.ComponentModel;
using TwinCAT.TypeSystem;
using Heisig.HMI.AdsManager;
namespace HMIToolkit
{
public sealed partial class AnalogMotorControlVM : ObservableObject
{
[ObservableProperty]
private string sName = "No Name";
public HMIControlButtonVM? AutomaticButton { get; private set; }
public HMIControlButtonVM? ManualButton { get; private set; }
public HMIControlButtonVM StartButton { get; private set; }
public HMIControlButtonVM StopButton { get; private set; }
public IntlkControlVM? Interlocks { get; private set; }
public AnalogValueVM? Setpoint { get; private set; }
public AnalogValueVM? ProcessValue { get; private set; }
private readonly string? _variableName;
private IAdsManager? _adsManager;
public AnalogMotorControlVM()
{
AutomaticButton = new HMIControlButtonVM();
ManualButton = new HMIControlButtonVM();
StartButton = new HMIControlButtonVM();
StopButton = new HMIControlButtonVM();
Interlocks = new IntlkControlVM();
Setpoint = new AnalogValueVM();
ProcessValue = new AnalogValueVM();
}
public AnalogMotorControlVM(IAdsManager adsManager, string variableName)
{
_adsManager = adsManager;
_variableName = variableName;
AutomaticButton = new HMIControlButtonVM(_adsManager, _variableName + ".stAutomaticButton");
ManualButton = new HMIControlButtonVM(_adsManager, _variableName + ".stManualButton");
StartButton = new HMIControlButtonVM(_adsManager, _variableName + ".stStartButton");
StopButton = new HMIControlButtonVM(_adsManager, _variableName + ".stStopButton");
Interlocks = new IntlkControlVM(_adsManager, _variableName + ".stInterlock");
Setpoint = new AnalogValueVM(_adsManager, _variableName + ".stSetpoint", false);
ProcessValue = new AnalogValueVM(_adsManager, _variableName + ".stProcessValue", true);
_adsManager.Register(_variableName + ".sName", NameChanged);
}
public void Dispose()
{
_adsManager?.Deregister(_variableName + ".sName", NameChanged);
_adsManager = null;
AutomaticButton?.Dispose();
AutomaticButton = null;
ManualButton?.Dispose();
ManualButton = null;
StartButton?.Dispose();
StartButton = null;
StopButton?.Dispose();
StopButton = null;
Interlocks?.Dispose();
Interlocks = null;
Setpoint?.Dispose();
Setpoint = null;
ProcessValue?.Dispose();
ProcessValue = null;
}
private void NameChanged(object? sender, ValueChangedEventArgs e)
{
SName = (string)e.Value;
}
}
}

View File

@@ -0,0 +1,33 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System.Globalization;
using System.Windows.Controls;
namespace HMIToolkit
{
public sealed partial class AnalogRangeValidator : ValidationRule
{
public float Min { get; set; }
public float Max { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
float analogValue = 0;
try
{
if (((string)value).Length > 0)
analogValue = float.Parse((string)value);
}
catch (Exception e)
{
return new ValidationResult(false, $"Illegal characters or {e.Message}");
}
if ((analogValue < Min) || (analogValue > Max))
return new ValidationResult(false, $"Please enter a value in the range: {Min}-{Max}.");
return ValidationResult.ValidResult;
}
}
}

View File

@@ -0,0 +1,33 @@
<UserControl x:Class="HMIToolkit.AnalogValue"
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:local="clr-namespace:HMIToolkit"
d:DataContext="{d:DesignInstance Type=local:AnalogValueVM, IsDesignTimeCreatable=True}"
mc:Ignorable="d"
Width="Auto"
Height="Auto">
<!-- :DataContext="{d:DesignInstance Type=local:AnalogValueVM, IsDesignTimeCreatable=True}" -->
<!-- Style to see things in the designer-->
<d:DesignerProperties.DesignStyle>
<Style TargetType="UserControl">
<!-- Property="Background" Value="White" /> -->
<Setter Property="Height" Value="Auto" />
<Setter Property="Width" Value="200" />
</Style>
</d:DesignerProperties.DesignStyle>
<Grid Height="Auto">
<Grid.ColumnDefinitions>
<!-- <ColumnDefinition Width="Auto" /> -->
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- <Label Grid.Column="0" Content="{Binding SName}" VerticalAlignment="Center" HorizontalAlignment="Left"/> -->
<TextBox x:Name="tbValue" Text="{Binding RValue, Mode=TwoWay, StringFormat=N2}" Grid.Column="0" MaxLines="1" Width="125" HorizontalContentAlignment="Right" VerticalContentAlignment="Center" IsReadOnly="{Binding Readonly}" />
<Label Grid.Column="1" Content="{Binding SUnit}" VerticalAlignment="Center" HorizontalAlignment="Left"/>
</Grid>
</UserControl>

View File

@@ -0,0 +1,32 @@
using System.Text.RegularExpressions;
using System.Windows.Controls;
using System.Windows.Input;
namespace HMIToolkit
{
/// <summary>
/// Interaktionslogik für AnalogValue.xaml
/// </summary>
public partial class AnalogValue : UserControl
{
public bool IsReadonly { get; set; }
public AnalogValue()
{
InitializeComponent();
// Unloaded += OnUnloaded;
}
private void OnUnloaded(object? sender, EventArgs e)
{
var disposable = DataContext as IDisposable;
disposable?.Dispose();
}
private void NumberValidation(object sender, TextCompositionEventArgs e)
{
Regex regex = new("^[-+]?[0-9]*,?[0-9]+$");
e.Handled = regex.IsMatch(e.Text);
}
}
}

View File

@@ -0,0 +1,144 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System.ComponentModel.DataAnnotations;
using TwinCAT.TypeSystem;
using Heisig.HMI.AdsManager;
namespace HMIToolkit;
public sealed class InRangeAttribute(string propMin, string propMax) : ValidationAttribute
{
public string PropMin { get; } = propMin;
public string PropMax { get; } = propMax;
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
{
object instance = validationContext.ObjectInstance;
float minValue = (float)(instance.GetType().GetProperty(PropMin)?.GetValue(instance) ?? 0.0f);
float maxValue = (float)(instance.GetType().GetProperty(PropMax)?.GetValue(instance) ?? 0.0f);
float tempValue = (float)(value ?? 0.0f);
if (tempValue <= maxValue && tempValue >= minValue)
return ValidationResult.Success;
return new($"Value has to be greater than {minValue} and smaller then {maxValue}");
}
}
public sealed partial class AnalogValueVM : ObservableValidator, IDisposable
{
[ObservableProperty]
private string sName;
// 1 = Ok; 2 = Error
[ObservableProperty]
private short iStatus;
private float rValue;
[InRangeAttribute(nameof(RMin), nameof(RMax))]
public float RValue
{
get => rValue;
set
{
SetProperty(ref rValue, value, true);
if (value >= RMin && value <= RMax)
{
if (!Readonly)
WriteValue(value);
}
}
}
private void WriteValue(float value)
{
_adsManager?.WriteValue(_variableName + ".rValue", value);
}
[ObservableProperty]
private string sUnit;
[ObservableProperty]
private float rMin;
[ObservableProperty]
private float rMax;
public bool Readonly { get; private set; }
private IAdsManager? _adsManager;
private readonly string? _variableName;
public AnalogValueVM()
{
sName = "No Name:";
iStatus = 2;
rValue = 0.0f;
sUnit = "";
Readonly = true;
rMin = 0.0f;
rMax = 100.0f;
}
public AnalogValueVM(IAdsManager adsManager, string variableName, bool isReadonly) : this()
{
_adsManager = adsManager;
_variableName = variableName;
Readonly = isReadonly;
_adsManager.Register(_variableName + ".sName", NameChanged);
_adsManager.Register(_variableName + ".iStatus", StatusChanged);
//if (isReadonly)
_adsManager.Register(_variableName + ".rValue", ValueChanged);
_adsManager.Register(_variableName + ".sUnit", UnitChanged);
_adsManager.Register(_variableName + ".rMin", MinChanged);
_adsManager.Register(_variableName + ".rMax", MaxChanged);
}
public void Dispose()
{
_adsManager?.Deregister(_variableName + ".sName", NameChanged);
_adsManager?.Deregister(_variableName + ".iStatus", StatusChanged);
_adsManager?.Deregister(_variableName + ".rValue", ValueChanged);
_adsManager?.Deregister(_variableName + ".sUnit", UnitChanged);
_adsManager?.Deregister(_variableName + ".rMin", MinChanged);
_adsManager?.Deregister(_variableName + ".rMax", MaxChanged);
_adsManager = null;
}
private void NameChanged(object? sender, ValueChangedEventArgs e)
{
string temp = (string)e.Value;
if (temp != String.Empty)
SName = temp + ":";
else
SName = "";
}
private void StatusChanged(object? sender, ValueChangedEventArgs e)
{
IStatus = (short)e.Value;
}
private void ValueChanged(object? sender, ValueChangedEventArgs e)
{
RValue = (float)e.Value;
}
private void UnitChanged(object? sender, ValueChangedEventArgs e)
{
SUnit = (string)e.Value;
}
private void MinChanged(object? sender, ValueChangedEventArgs e)
{
RMin = (float)e.Value;
}
private void MaxChanged(object? sender, ValueChangedEventArgs e)
{
RMax = (float)e.Value;
}
}

View File

@@ -0,0 +1,87 @@
<UserControl xmlns:AnalogValue="clr-namespace:HMIToolkit" x:Class="HMIToolkit.AnalogValveControl"
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:local="clr-namespace:HMIToolkit"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:AnalogValveControlVM, IsDesignTimeCreatable=True}"
Width="Auto"
Height="Auto">
<d:DesignerProperties.DesignStyle>
<Style TargetType="UserControl">
<!-- Property="Background" Value="White" /> -->
<Setter Property="Height" Value="Auto" />
<Setter Property="Width" Value="Auto" />
</Style>
</d:DesignerProperties.DesignStyle>
<Grid>
<Grid Margin="5" ShowGridLines="False">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Row 0 -->
<Label Grid.Row="0" Content="{Binding SName}" HorizontalAlignment="Center" FontSize="24" FontWeight="Bold"/>
<!-- Row 1 -->
<Label Grid.Row="1" Content="Interlocks:" Margin="0,0,0,-3"/>
<!-- Row 2 -->
<Grid Grid.Row="2">
<local:IntlkButtonControl DataContext="{Binding Interlocks}"/>
</Grid>
<!-- Row 3 -->
<Label Grid.Row="3" Content="Setpoint:" Margin="0,0,0,-3"/>
<!-- Row 4 -->
<AnalogValue:AnalogValue DataContext="{Binding Setpoint}" Grid.Row="4"/>
<!-- Row 5 -->
<Label Grid.Row="5" Content="Process value:" Margin="0,0,0,-3"/>
<!-- Row 6 -->
<AnalogValue:AnalogValue DataContext="{Binding ProcessValue}" Grid.Row="6"/>
<!-- Row 7 -->
<Grid Grid.Row="7">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Content="Control:" Margin="0,0,0,-3" />
<Button x:Name="btnOpen" DataContext="{Binding OpenButton}" Command="{Binding ButtonClickedCommand}" IsEnabled="{Binding XRelease}" Grid.Row="1" Grid.Column="0" Content="Open" Height="80" Width="80" Margin="0,0,5,5" />
<Button x:Name="btnClose" DataContext="{Binding CloseButton}" Command="{Binding ButtonClickedCommand}" IsEnabled="{Binding XRelease}" Grid.Row="1" Grid.Column="1" Content="Close" Height="80" Width="80" Margin="0,-5,0,0"/>
</Grid>
<!-- Row 8 -->
<Grid Grid.Row="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Content="Mode:" Margin="0,0,0,-3" />
<Button x:Name="btnAuto" DataContext="{Binding AutomaticButton}" Command="{Binding ButtonClickedCommand}" IsEnabled="{Binding XRelease}" Grid.Row="1" Grid.Column="0" Content="Auto" Height="80" Width="80" Margin="0,0,5,0"/>
<Button x:Name="btnManual" DataContext="{Binding ManualButton}" Command="{Binding ButtonClickedCommand}" IsEnabled="{Binding XRelease}" Grid.Row="1" Grid.Column="1" Content="Man" Height="80" Width="80" Margin="0,-5,0,-5"/>
</Grid>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace HMIToolkit
{
/// <summary>
/// Interaktionslogik für AnalogValveControl.xaml
/// </summary>
public partial class AnalogValveControl : UserControl
{
public AnalogValveControl()
{
InitializeComponent();
// Unloaded += OnUnloaded;
}
private void OnUnloaded(object? sender, EventArgs e)
{
var disposable = DataContext as IDisposable;
disposable?.Dispose();
}
}
}

View File

@@ -0,0 +1,83 @@
using CommunityToolkit.Mvvm.ComponentModel;
using TwinCAT.TypeSystem;
using Heisig.HMI.AdsManager;
namespace HMIToolkit
{
public sealed partial class AnalogValveControlVM : ObservableObject
{
[ObservableProperty]
private string sName = "No Name";
public HMIControlButtonVM? AutomaticButton { get; private set; }
public HMIControlButtonVM? ManualButton { get; private set; }
public HMIControlButtonVM? OpenButton { get; private set; }
public HMIControlButtonVM? CloseButton { get; private set; }
public IntlkControlVM? Interlocks { get; private set; }
public AnalogValueVM? Setpoint { get; private set; }
public AnalogValueVM? ProcessValue { get; private set; }
private readonly string? _variableName;
private IAdsManager? _adsManager;
public AnalogValveControlVM()
{
AutomaticButton = new HMIControlButtonVM();
ManualButton = new HMIControlButtonVM();
OpenButton = new HMIControlButtonVM();
CloseButton = new HMIControlButtonVM();
Interlocks = new IntlkControlVM();
Setpoint = new AnalogValueVM();
ProcessValue = new AnalogValueVM();
}
public AnalogValveControlVM(IAdsManager adsManager, string variableName)
{
_adsManager = adsManager;
_variableName = variableName;
AutomaticButton = new HMIControlButtonVM(_adsManager, _variableName + ".stAutomaticButton");
ManualButton = new HMIControlButtonVM(_adsManager, _variableName + ".stManualButton");
OpenButton = new HMIControlButtonVM(_adsManager, _variableName + ".stOpenButton");
CloseButton = new HMIControlButtonVM(_adsManager, _variableName + ".stCloseButton");
Interlocks = new IntlkControlVM(_adsManager, _variableName + ".stInterlock");
Setpoint = new AnalogValueVM(_adsManager, _variableName + ".stSetpoint", false);
ProcessValue = new AnalogValueVM(_adsManager, _variableName + ".stProcessValue", true);
_adsManager.Register(_variableName + ".sName", NameChanged);
}
public void Dispose()
{
_adsManager?.Deregister(_variableName + ".sName", NameChanged);
_adsManager = null;
AutomaticButton?.Dispose();
AutomaticButton = null;
ManualButton?.Dispose();
ManualButton = null;
OpenButton?.Dispose();
OpenButton = null;
CloseButton?.Dispose();
CloseButton = null;
Interlocks?.Dispose();
Interlocks = null;
Setpoint?.Dispose();
Setpoint = null;
ProcessValue?.Dispose();
ProcessValue = null;
}
private void NameChanged(object? sender, ValueChangedEventArgs e)
{
SName = (string)e.Value;
}
}
}

View File

@@ -0,0 +1,74 @@
<UserControl x:Class="HMIToolkit.BinaryValveControl"
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:local="clr-namespace:HMIToolkit"
d:DataContext="{d:DesignInstance Type=local:BinaryValveControlVM, IsDesignTimeCreatable=True}"
mc:Ignorable="d"
Height="Auto"
Width="Auto">
<UserControl.Resources>
<local:FeedbackToColorConverter x:Key="feedbackConverter" />
</UserControl.Resources>
<!-- Style to see things in the designer-->
<d:DesignerProperties.DesignStyle>
<Style TargetType="UserControl">
<!-- Property="Background" Value="White" /> -->
<Setter Property="Height" Value="Auto" />
<Setter Property="Width" Value="Auto" />
</Style>
</d:DesignerProperties.DesignStyle>
<Grid Margin="5" ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Row 0 -->
<Label Grid.Row="0" Content="{Binding SName}" HorizontalAlignment="Center" FontSize="24" FontWeight="Bold"/>
<!-- Row 1 -->
<Label Grid.Row="1" Content="Interlocks:" Margin="0,0,0,-3"/>
<!-- Row 2 -->
<Grid Grid.Row="2">
<local:IntlkButtonControl DataContext="{Binding Interlocks}"/>
</Grid>
<!-- Row 3 -->
<Grid Grid.Row="3">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Content="Control:" Margin="0,0,0,-3" />
<Button x:Name="btnOpen" DataContext="{Binding OpenButton}" Command="{Binding ButtonClickedCommand}" IsEnabled="{Binding XRelease}" Background="{Binding IFeedback, Converter={StaticResource feedbackConverter}}" Grid.Row="1" Grid.Column="0" Content="Open" Height="80" Width="80" Margin="0,0,5,5" />
<Button x:Name="btnClose" DataContext="{Binding CloseButton}" Command="{Binding ButtonClickedCommand}" IsEnabled="{Binding XRelease}" Background="{Binding IFeedback, Converter={StaticResource feedbackConverter}}" Grid.Row="1" Grid.Column="1" Content="Close" Height="80" Width="80" Margin="0,-5,0,0"/>
</Grid>
<!-- Row 4 -->
<Grid Grid.Row="4">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Content="Mode:" Margin="0,0,0,-3" />
<Button x:Name="btnAuto" DataContext="{Binding AutomaticButton}" Command="{Binding ButtonClickedCommand}" IsEnabled="{Binding XRelease}" Grid.Row="1" Grid.Column="0" Content="Auto" Height="80" Width="80" Margin="0,0,5,0"/>
<Button x:Name="btnManual" DataContext="{Binding ManualButton}" Command="{Binding ButtonClickedCommand}" IsEnabled="{Binding XRelease}" Grid.Row="1" Grid.Column="1" Content="Man" Height="80" Width="80" Margin="0,-5,0,-5"/>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace HMIToolkit
{
/// <summary>
/// Interaktionslogik für BinaryValveControl.xaml
/// </summary>
public partial class BinaryValveControl : UserControl
{
public BinaryValveControl()
{
InitializeComponent();
// Unloaded += OnUnloaded;
}
private void OnUnloaded(object? sender, EventArgs e)
{
var disposable = DataContext as IDisposable;
disposable?.Dispose();
}
}
}

View File

@@ -0,0 +1,70 @@
using CommunityToolkit.Mvvm.ComponentModel;
using TwinCAT.TypeSystem;
using Heisig.HMI.AdsManager;
// {Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.OpenButton}
namespace HMIToolkit
{
public sealed partial class BinaryValveControlVM : ObservableObject, IDisposable
{
[ObservableProperty]
private string sName = "No Name";
public HMIControlButtonVM? AutomaticButton { get; private set; }
public HMIControlButtonVM? ManualButton { get; private set; }
public HMIControlButtonVM OpenButton { get; private set; }
public HMIControlButtonVM CloseButton { get; private set; }
public IntlkControlVM? Interlocks { get; private set; }
private readonly string? _variableName;
private IAdsManager? _adsManager;
public BinaryValveControlVM()
{
AutomaticButton = new HMIControlButtonVM();
ManualButton = new HMIControlButtonVM();
OpenButton = new HMIControlButtonVM();
CloseButton = new HMIControlButtonVM();
Interlocks = new IntlkControlVM();
}
public BinaryValveControlVM(IAdsManager adsManager, string variableName)
{
_adsManager = adsManager;
_variableName = variableName;
AutomaticButton = new HMIControlButtonVM(_adsManager, _variableName + ".stAutomaticButton");
ManualButton = new HMIControlButtonVM(_adsManager, _variableName + ".stManualButton");
OpenButton = new HMIControlButtonVM(_adsManager, _variableName + ".stOpenButton");
CloseButton = new HMIControlButtonVM(_adsManager, _variableName + ".stCloseButton");
Interlocks = new IntlkControlVM(_adsManager, _variableName + ".stInterlock");
_adsManager.Register(_variableName + ".sName", NameChanged);
}
public void Dispose()
{
_adsManager?.Deregister(_variableName + ".sName", NameChanged);
_adsManager = null;
AutomaticButton?.Dispose();
AutomaticButton = null;
ManualButton?.Dispose();
ManualButton = null;
OpenButton?.Dispose();
CloseButton?.Dispose();
Interlocks?.Dispose();
Interlocks = null;
}
private void NameChanged(object? sender, ValueChangedEventArgs e)
{
SName = (string)e.Value;
}
}
}

View File

@@ -0,0 +1,25 @@
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
namespace HMIToolkit
{
public class BoolToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType != typeof(Brush))
throw new InvalidOperationException("The target must be a brush");
bool temp = bool.Parse(value.ToString()!);
return (temp ? Brushes.DarkGreen : Brushes.DarkRed);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
}

View File

@@ -0,0 +1,74 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using TwinCAT.TypeSystem;
using Heisig.HMI.AdsManager;
namespace HMIToolkit
{
public sealed partial class HMIControlButtonVM : ObservableObject, IDisposable
{
private IAdsManager? _adsManager;
private readonly string _variableName;
// Action triggered when the button is about to be clicked
public event EventHandler? ButtonClickedStarted;
// Action triggered when the button is done being clicked
public event EventHandler? ButtonClickedEnded;
// Event when button feedback changed
public event EventHandler? FeedbackChanged;
// Event when release changed
public event EventHandler? ReleaseChanged;
[ObservableProperty]
private bool xRelease;
// 0 = none, 1 = active, 2 = pending, 3 = waring, 4 = error
[ObservableProperty]
private short iFeedback;
public HMIControlButtonVM()
{
_variableName = string.Empty;
XRelease = false;
IFeedback = 4;
}
public HMIControlButtonVM(IAdsManager adsManager, string variableName)
{
_adsManager = adsManager;
_variableName = variableName;
_adsManager.Register(_variableName + ".xRelease", XReleaseCahnged);
_adsManager.Register(_variableName + ".iFeedback", IFeedbackChanged);
}
public void Dispose()
{
_adsManager?.Deregister(_variableName + ".xRelease", XReleaseCahnged);
_adsManager?.Deregister(_variableName + ".iFeedback", IFeedbackChanged);
}
[RelayCommand]
private void ButtonClicked()
{
ButtonClickedStarted?.Invoke(this, EventArgs.Empty);
_adsManager?.WriteValue(_variableName + ".xRequest", true);
ButtonClickedEnded?.Invoke(this, EventArgs.Empty);
}
private void XReleaseCahnged(object? sender, ValueChangedEventArgs e)
{
XRelease = (bool)e.Value;
ReleaseChanged?.Invoke(this, EventArgs.Empty);
}
private void IFeedbackChanged(object? sender, ValueChangedEventArgs e)
{
IFeedback = (short)e.Value;
FeedbackChanged?.Invoke(this, EventArgs.Empty);
}
}
}

View File

@@ -0,0 +1,33 @@
<UserControl x:Class="HMIToolkit.IntlkButtonControl"
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:local="clr-namespace:HMIToolkit"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:IntlkControlVM, IsDesignTimeCreatable=True}"
d:DesignHeight="35" d:DesignWidth="175">
<UserControl.Resources>
<local:BoolToBrushConverter x:Key="myBoolConverter" />
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Padding="0" Command="{Binding ShowProcessIntlksCommand}">
<StackPanel Orientation="Horizontal">
<Label>Process</Label>
<Rectangle Width="10" Height="10" Fill="{Binding Path=XProcessIntlkOk, Converter={StaticResource myBoolConverter}}" RadiusX="2" RadiusY="2" Margin="0,2,0,0"/>
</StackPanel>
</Button>
<Button Grid.Column="1" Padding="0" Margin="5,0,0,0" Command="{Binding ShowSafetyIntlksCommand}">
<StackPanel Orientation="Horizontal">
<Label>Safety</Label>
<Rectangle Width="10" Height="10" Fill="{Binding Path=XSafetyIntlkOk, Converter={StaticResource myBoolConverter}}" RadiusX="2" RadiusY="2" Margin="0,2,0,0"/>
</StackPanel>
</Button>
</Grid>
</UserControl>

View File

@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace HMIToolkit
{
/// <summary>
/// Interaktionslogik für ProcessIntlkButtonControl.xaml
/// </summary>
public partial class IntlkButtonControl : UserControl
{
public IntlkButtonControl()
{
InitializeComponent();
// Unloaded += OnUnloaded;
}
private void OnUnloaded(object? sender, EventArgs e)
{
var disposable = DataContext as IDisposable;
disposable?.Dispose();
}
}
}

View File

@@ -0,0 +1,109 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using TwinCAT.TypeSystem;
using Heisig.HMI.AdsManager;
namespace HMIToolkit;
public sealed partial class IntlkControlVM : ObservableObject, IDisposable
{
[ObservableProperty]
private bool xProcessIntlkOk;
[ObservableProperty]
private bool xSafetyIntlkOk;
[ObservableProperty]
private IntlkDetailsVM safetyInterlocksVM;
[ObservableProperty]
private IntlkDetailsVM processInterlocksVM;
private IntlkDetailsWindow? processIntlksDetailsWindow;
private IntlkDetailsWindow? safetyIntlksDetailsWindow;
private readonly string _variableName;
private IAdsManager? _adsManager;
public IntlkControlVM()
{
XProcessIntlkOk = false;
XSafetyIntlkOk = false;
_variableName = string.Empty;
safetyInterlocksVM = new();
processInterlocksVM = new();
}
public IntlkControlVM(IAdsManager adsManager, string variableName)
{
_adsManager = adsManager;
_variableName = variableName;
SafetyInterlocksVM = new IntlkDetailsVM(_adsManager, _variableName + ".wSafetyINTLKStatus", _variableName + ".asSafetyINTLKName", "Safety Interlock");
ProcessInterlocksVM = new IntlkDetailsVM(_adsManager, _variableName + ".wProcessINTLKStatus", _variableName + ".asProcessINTLKName", "Process Interlock");
_adsManager.Register(_variableName + ".xProcessINTLKOk", ProcessIntlkOkChanged);
_adsManager.Register(_variableName + ".xSafetyINTLKOk", SafetyIntlkOkChanged);
}
public void Dispose()
{
SafetyInterlocksVM.Dispose();
ProcessInterlocksVM.Dispose();
_adsManager?.Deregister(_variableName + ".xProcessINTLKOk", ProcessIntlkOkChanged);
_adsManager?.Deregister(_variableName + ".xSafetyINTLKOk", SafetyIntlkOkChanged);
processIntlksDetailsWindow?.Close();
safetyIntlksDetailsWindow?.Close();
_adsManager = null;
}
private void ProcessIntlkOkChanged(object? sender, ValueChangedEventArgs e)
{
XProcessIntlkOk = (bool)e.Value;
}
private void SafetyIntlkOkChanged(object? sender, ValueChangedEventArgs e)
{
XSafetyIntlkOk = (bool)e.Value;
}
[RelayCommand]
private void ShowProcessIntlks()
{
if (_adsManager != null && processIntlksDetailsWindow == null)
{
processIntlksDetailsWindow = new() { DataContext = ProcessInterlocksVM };
processIntlksDetailsWindow.Closed += ProcessIntlksDetailsWindow_Closed;
processIntlksDetailsWindow.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen;
processIntlksDetailsWindow.Show();
}
}
private void ProcessIntlksDetailsWindow_Closed(object? sender, EventArgs e)
{
processIntlksDetailsWindow!.Close();
processIntlksDetailsWindow = null;
}
[RelayCommand]
private void ShowSafetyIntlks()
{
if (_adsManager != null && safetyIntlksDetailsWindow == null)
{
safetyIntlksDetailsWindow = new() { DataContext = SafetyInterlocksVM };
safetyIntlksDetailsWindow.Closed += SafetyIntlksDetailsWindow_Closed;
safetyIntlksDetailsWindow.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen;
safetyIntlksDetailsWindow.Show();
}
}
private void SafetyIntlksDetailsWindow_Closed(object? sender, EventArgs e)
{
safetyIntlksDetailsWindow!.Close();
safetyIntlksDetailsWindow = null;
}
}

View File

@@ -0,0 +1,132 @@
<UserControl x:Class="HMIToolkit.IntlkDetails"
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:local="clr-namespace:HMIToolkit"
d:DataContext="{d:DesignInstance Type=local:IntlkDetailsVM, IsDesignTimeCreatable=True}"
mc:Ignorable="d"
MinWidth="500" MinHeight="300"
d:DesignHeight="300" d:DesignWidth="500"
>
<UserControl.Resources>
<local:BoolToBrushConverter x:Key="myBoolConverter" />
</UserControl.Resources>
<Grid Margin="5" ShowGridLines="True" >
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Row 0 -->
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Content="{Binding InterlockName}" />
<!-- Row 1 -->
<!-- <ListBox Grid.Column="0" Grid.Row="1" ItemsSource="{Binding ListBoxItemsLeft}"/> -->
<!-- <ListBox Grid.Column="1" Grid.Row="1" ItemsSource="{Binding ListBoxItemsRight}" /> -->
<Grid Grid.Column="0" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Row 0 -->
<Rectangle Grid.Row="0" Grid.Column="0" Width="10" Height="10" Fill="{Binding Path=InterlockStatus[0], Converter={StaticResource myBoolConverter}}" RadiusX="2" RadiusY="2" Margin="0,2,0,0"/>
<Label Grid.Row="0" Grid.Column="1" Content="{Binding InterlockNames[0]}" />
<!-- Row 1 -->
<Rectangle Grid.Row="1" Grid.Column="0" Width="10" Height="10" Fill="{Binding Path=InterlockStatus[1], Converter={StaticResource myBoolConverter}}" RadiusX="2" RadiusY="2" Margin="0,2,0,0"/>
<Label Grid.Row="1" Grid.Column="1" Content="{Binding InterlockNames[1]}" />
<!-- Row 2 -->
<Rectangle Grid.Row="2" Grid.Column="0" Width="10" Height="10" Fill="{Binding Path=InterlockStatus[2], Converter={StaticResource myBoolConverter}}" RadiusX="2" RadiusY="2" Margin="0,2,0,0"/>
<Label Grid.Row="2" Grid.Column="1" Content="{Binding InterlockNames[2]}" />
<!-- Row 3 -->
<Rectangle Grid.Row="3" Grid.Column="0" Width="10" Height="10" Fill="{Binding Path=InterlockStatus[3], Converter={StaticResource myBoolConverter}}" RadiusX="2" RadiusY="2" Margin="0,2,0,0"/>
<Label Grid.Row="3" Grid.Column="1" Content="{Binding InterlockNames[3]}" />
<!-- Row 4 -->
<Rectangle Grid.Row="4" Grid.Column="0" Width="10" Height="10" Fill="{Binding Path=InterlockStatus[4], Converter={StaticResource myBoolConverter}}" RadiusX="2" RadiusY="2" Margin="0,2,0,0"/>
<Label Grid.Row="4" Grid.Column="1" Content="{Binding InterlockNames[4]}" />
<!-- Row 5 -->
<Rectangle Grid.Row="5" Grid.Column="0" Width="10" Height="10" Fill="{Binding Path=InterlockStatus[5], Converter={StaticResource myBoolConverter}}" RadiusX="2" RadiusY="2" Margin="0,2,0,0"/>
<Label Grid.Row="5" Grid.Column="1" Content="{Binding InterlockNames[5]}" />
<!-- Row 6 -->
<Rectangle Grid.Row="6" Grid.Column="0" Width="10" Height="10" Fill="{Binding Path=InterlockStatus[6], Converter={StaticResource myBoolConverter}}" RadiusX="2" RadiusY="2" Margin="0,2,0,0"/>
<Label Grid.Row="6" Grid.Column="1" Content="{Binding InterlockNames[6]}" />
<!-- Row 7 -->
<Rectangle Grid.Row="7" Grid.Column="0" Width="10" Height="10" Fill="{Binding Path=InterlockStatus[7], Converter={StaticResource myBoolConverter}}" RadiusX="2" RadiusY="2" Margin="0,2,0,0"/>
<Label Grid.Row="7" Grid.Column="1" Content="{Binding InterlockNames[7]}" />
</Grid>
<Grid Grid.Column="1" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Row 0 -->
<Rectangle Grid.Row="0" Grid.Column="0" Width="10" Height="10" Fill="{Binding Path=InterlockStatus[8], Converter={StaticResource myBoolConverter}}" RadiusX="2" RadiusY="2" Margin="0,2,0,0"/>
<Label Grid.Row="0" Grid.Column="1" Content="{Binding InterlockNames[8]}" />
<!-- Row 1 -->
<Rectangle Grid.Row="1" Grid.Column="0" Width="10" Height="10" Fill="{Binding Path=InterlockStatus[9], Converter={StaticResource myBoolConverter}}" RadiusX="2" RadiusY="2" Margin="0,2,0,0"/>
<Label Grid.Row="1" Grid.Column="1" Content="{Binding InterlockNames[9]}" />
<!-- Row 2 -->
<Rectangle Grid.Row="2" Grid.Column="0" Width="10" Height="10" Fill="{Binding Path=InterlockStatus[10], Converter={StaticResource myBoolConverter}}" RadiusX="2" RadiusY="2" Margin="0,2,0,0"/>
<Label Grid.Row="2" Grid.Column="1" Content="{Binding InterlockNames[10]}" />
<!-- Row 3 -->
<Rectangle Grid.Row="3" Grid.Column="0" Width="10" Height="10" Fill="{Binding Path=InterlockStatus[11], Converter={StaticResource myBoolConverter}}" RadiusX="2" RadiusY="2" Margin="0,2,0,0"/>
<Label Grid.Row="3" Grid.Column="1" Content="{Binding InterlockNames[11]}" />
<!-- Row 4 -->
<Rectangle Grid.Row="4" Grid.Column="0" Width="10" Height="10" Fill="{Binding Path=InterlockStatus[12], Converter={StaticResource myBoolConverter}}" RadiusX="2" RadiusY="2" Margin="0,2,0,0"/>
<Label Grid.Row="4" Grid.Column="1" Content="{Binding InterlockNames[12]}" />
<!-- Row 5 -->
<Rectangle Grid.Row="5" Grid.Column="0" Width="10" Height="10" Fill="{Binding Path=InterlockStatus[13], Converter={StaticResource myBoolConverter}}" RadiusX="2" RadiusY="2" Margin="0,2,0,0"/>
<Label Grid.Row="5" Grid.Column="1" Content="{Binding InterlockNames[13]}" />
<!-- Row 6 -->
<Rectangle Grid.Row="6" Grid.Column="0" Width="10" Height="10" Fill="{Binding Path=InterlockStatus[14], Converter={StaticResource myBoolConverter}}" RadiusX="2" RadiusY="2" Margin="0,2,0,0"/>
<Label Grid.Row="6" Grid.Column="1" Content="{Binding InterlockNames[14]}" />
<!-- Row 7 -->
<Rectangle Grid.Row="7" Grid.Column="0" Width="10" Height="10" Fill="{Binding Path=InterlockStatus[15], Converter={StaticResource myBoolConverter}}" RadiusX="2" RadiusY="2" Margin="0,2,0,0"/>
<Label Grid.Row="7" Grid.Column="1" Content="{Binding InterlockNames[15]}" />
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace HMIToolkit
{
/// <summary>
/// Interaktionslogik für IntlkDetails.xaml
/// </summary>
public partial class IntlkDetails : UserControl
{
public IntlkDetails()
{
InitializeComponent();
// Unloaded += OnUnloaded;
}
private void OnUnloaded(object? sender, EventArgs e)
{
var disposable = DataContext as IDisposable;
disposable?.Dispose();
}
}
}

View File

@@ -0,0 +1,151 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using TwinCAT.TypeSystem;
using Heisig.HMI.AdsManager;
namespace HMIToolkit
{
public sealed partial class IntlkDetailsVM : ObservableObject, IDisposable
{
[ObservableProperty]
private string interlockName;
[ObservableProperty]
private BitArray interlockStatus;
[ObservableProperty]
private string[] interlockNames;
[ObservableProperty]
private ListBoxItem[] listBoxItemsLeft;
[ObservableProperty]
private ListBoxItem[] listBoxItemsRight;
[ObservableProperty]
private Visibility isVisible;
private readonly BoolToBrushConverter boolToBrushConverter = new();
private readonly int numIntlksLeftSide;
private readonly int numIntlksRightSide;
private readonly string _variableNameStatus;
private readonly string _variableNameNames;
private IAdsManager? _adsManager;
public IntlkDetailsVM()
{
interlockName = "Interlocks";
interlockStatus = new BitArray(HMIConstants.NumInterlocks);
interlockNames = new string[HMIConstants.NumInterlocks];
Array.Fill(interlockNames, "Not used");
// Split all interlocks into two parts
numIntlksLeftSide = (int)Math.Ceiling(HMIConstants.NumInterlocks * 0.5);
numIntlksRightSide = HMIConstants.NumInterlocks - numIntlksLeftSide;
listBoxItemsLeft = new ListBoxItem[numIntlksLeftSide];
listBoxItemsRight = new ListBoxItem[numIntlksRightSide];
_variableNameStatus = System.String.Empty;
_variableNameNames = System.String.Empty;
// CreateContent();
}
public IntlkDetailsVM(IAdsManager adsManager, string variableNameStatus, string variableNameNames, string intlkName) : this()
{
interlockName = intlkName;
_variableNameStatus = variableNameStatus;
_variableNameNames = variableNameNames;
_adsManager = adsManager;
interlockStatus = new BitArray(HMIConstants.NumInterlocks);
interlockNames = new string[HMIConstants.NumInterlocks];
_adsManager.Register(_variableNameStatus, InterlockStatusChanged);
_adsManager.Register(_variableNameNames, InterlockNamesChanged);
}
public void Dispose()
{
_adsManager?.Deregister(_variableNameStatus, InterlockStatusChanged);
_adsManager?.Deregister(_variableNameNames, InterlockNamesChanged);
_adsManager = null;
}
/*private void CreateContent()
{
// Create left side
for (int i = 0; i < HMIConstants.NumInterlocks; i++)
{
// Create the stack panel
StackPanel stackPanel = new StackPanel
{
Orientation = Orientation.Horizontal
};
// Create the box
// <Rectangle Width="10" Height="10" Fill="{Binding Path=InterlockStatus[10], Converter={StaticResource myBoolConverter}}" RadiusX="2" RadiusY="2" Margin="0,2,0,0"/>
Rectangle rectangle = new Rectangle
{
Width = 10,
Height = 10,
RadiusX = 2,
RadiusY = 2
};
// Create binding
Binding binding = new()
{
Source = this,
Path = new PropertyPath("InterlockStatus[" + i + "]"),
Converter = boolToBrushConverter,
};
// Set binding
rectangle.SetBinding(Rectangle.FillProperty, binding);
// Create label
Label label = new();
binding = new()
{
Source = this,
Path = new PropertyPath("InterlockNames[" + i + "]")
};
label.SetBinding(Label.ContentProperty, binding);
// Add items to stack panel
stackPanel.Children.Add(rectangle);
stackPanel.Children.Add(label);
// Add stack panel to listbox items
ListBoxItem tempListBoxItem = new()
{
Content = stackPanel
};
if (i < numIntlksLeftSide)
ListBoxItemsLeft[i] = tempListBoxItem;
else
ListBoxItemsRight[i - numIntlksLeftSide] = tempListBoxItem;
}
}*/
private void InterlockStatusChanged(object? sender, ValueChangedEventArgs e)
{
ushort temp = (ushort)e.Value;
InterlockStatus = new BitArray(BitConverter.GetBytes(temp));
}
private void InterlockNamesChanged(object? sender, ValueChangedEventArgs e)
{
InterlockNames = (string[])e.Value;
}
}
}

View File

@@ -0,0 +1,13 @@
<Window xmlns:AdsSessionTest="clr-namespace:HMIToolkit" x:Class="HMIToolkit.IntlkDetailsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:HMIToolkit"
mc:Ignorable="d"
SizeToContent="WidthAndHeight"
Title="IntlkDetailsWindow" Height="450" Width="800">
<Grid>
<AdsSessionTest:IntlkDetails />
</Grid>
</Window>

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace HMIToolkit
{
/// <summary>
/// Interaktionslogik für IntlkDetailsWindow.xaml
/// </summary>
public partial class IntlkDetailsWindow : Window
{
public IntlkDetailsWindow()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,218 @@
using System.Runtime.InteropServices;
using System.Text;
namespace HMIToolkit
{
// PLC - C#
// --------
// int - short
// word - ushort
// Constants for interaction with data
public static class HMIConstants
{
public const int StringLength = 81;
public const int NumInterlocks = 16;
}
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public struct HMIAnalogValue
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = HMIConstants.StringLength)]
public string sName;
// 1 = Ok, 2 = Error
public short iStatus;
public float rValue;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = HMIConstants.StringLength)]
public string sUnit;
[MarshalAs(UnmanagedType.I1)]
public bool xUsed;
}
// TwinCAT2 Pack = 1, TwinCAT 3 Pack = 0
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public struct HMIControlButton
{
[MarshalAs(UnmanagedType.I1)]
public bool xRequest;
[MarshalAs(UnmanagedType.I1)]
public bool xRelease;
public short iFeedback;
}
// TwinCAT2 Pack = 1, TwinCAT 3 Pack = 0
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public struct HMIInterlock
{
public ushort wProcessINTLKStatus;
public ushort wSafeyINTLKStatus;
public ushort wProcessINTLKUsed;
public ushort wSafeyINTLKUsed;
// 16 * String(80) = 81 bytes a 16 indexes
// combined in one string because reading a two dimensional array is not possible
[MarshalAs(UnmanagedType.ByValArray, SizeConst = HMIConstants.StringLength * HMIConstants.NumInterlocks)]
public byte[] asProcessINTLKName;
// 16 * String(80) = 81 bytes a 16 indexes
// combined in one string because reading a two dimensional array is not possible
[MarshalAs(UnmanagedType.ByValArray, SizeConst = HMIConstants.StringLength * HMIConstants.NumInterlocks)]
public byte[] asSafetyINTLKName;
[MarshalAs(UnmanagedType.I1)]
public bool xProcessINTLKOk;
[MarshalAs(UnmanagedType.I1)]
public bool xSafetyINTLKOk;
}
// TwinCAT2 Pack = 1, TwinCAT 3 Pack = 0
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public struct HMIValveData
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = HMIConstants.StringLength)]
public string sName;
public HMIControlButton stAutomaticButton;
public HMIControlButton stManualButton;
public HMIControlButton stOpenButton;
public HMIControlButton stCloseButton;
// 1 = Opened, 2 = Opening/Closing, 3 = Closed, 4 = Error
public short iStatus;
// 1 = Automatic mode, 2 = Manual mode
public short iCurrentMode;
public HMIInterlock stInterlock;
[MarshalAs(UnmanagedType.I1)]
public bool xUsed;
}
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public struct HMIAnalogValveData
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = HMIConstants.StringLength)]
public string sName;
public HMIControlButton stAutomaticButton;
public HMIControlButton stManualButton;
public HMIControlButton stOpenButton;
public HMIControlButton stCloseButton;
// 1 = Opened, 2 = Opening/Closing, 3 = Closed, 4 = Error
public short iStatus;
// 1 = Automatic mode, 2 = Manual mode
public short iCurrentMode;
public HMIInterlock stInterlock;
HMIAnalogValue stSetpoint;
HMIAnalogValue stProcessValue;
[MarshalAs(UnmanagedType.I1)]
public bool xUsed;
}
// TwinCAT2 Pack = 1, TwinCAT 3 Pack = 0
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public struct HMIAnalogMotorData
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = HMIConstants.StringLength)]
public string sName;
public HMIControlButton stAutomaticButton;
public HMIControlButton stManualButton;
public HMIControlButton stStartButton;
public HMIControlButton stStopButton;
// 1 = Opened, 2 = Opening/Closing, 3 = Closed, 4 = Error
public short iStatus;
// 1 = Automatic mode, 2 = Manual mode
public short iCurrentMode;
public HMIAnalogValue stSetpoint;
public HMIAnalogValue stProcessValue;
public HMIInterlock stInterlock;
[MarshalAs(UnmanagedType.I1)]
public bool xUsed;
}
// TwinCAT2 Pack = 1, TwinCAT 3 Pack = 0
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public struct HMIOrpSensorData
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = HMIConstants.StringLength)]
public string sName;
// 1 = Ok, 2 = Error
public short iStatus;
public float rValuePH;
public float rValueTemp;
public float rValueORP;
public float rValueDLI;
[MarshalAs(UnmanagedType.I1)]
public bool xUsed;
}
static class HMIUtilities
{
// Converts the interlock byte array into a string array
public static string[] GetInterlockStringArray(byte[] byteArray)
{
string[] temp = new string[HMIConstants.NumInterlocks];
int size;
// Check if byteArray is of correct size
if (byteArray.Length != (HMIConstants.StringLength * HMIConstants.NumInterlocks))
return temp;
for (int i = 0; i < HMIConstants.NumInterlocks; i++)
{
// Calculate length of string by finding the 0 terminator so the unused bytes get truncated
size = Array.IndexOf(byteArray, (byte)0, i * HMIConstants.StringLength) - (i * HMIConstants.StringLength);
// Check if we found a valid 0 terminator
if (size >= 0)
// Build string from byteArray with calculated size
temp[i] = Encoding.ASCII.GetString(byteArray, i * HMIConstants.StringLength, size);
else
// No valid 0 string terminator was found so return an empty string
temp[i] = "";
}
return temp;
}
}
}

View File

@@ -0,0 +1,85 @@
<Window x:Class="UniperHMI.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mah="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:local="clr-namespace:UniperHMI" xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
mc:Ignorable="d"
x:Name="MainControlWindow"
d:DataContext="{d:DesignInstance Type=local:MainWindowVM, IsDesignTimeCreatable=False}"
WindowStartupLocation="CenterScreen"
Title="Uniper HMI" Height="800" Width="1024" ResizeMode="NoResize">
<Grid HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<!-- Latest event line -->
<Grid Grid.Row="0">
<Border BorderThickness="0px 0px 0px 2px" BorderBrush="White" Background="Black">
<Label Content="{Binding EventsPageVM.CurrentEvent.Message}" FontSize="20" Height="45" VerticalAlignment="Center" HorizontalAlignment="Left" Visibility="{Binding StatusBarVisible}"/>
</Border>
</Grid>
<!-- Breadcrumb line -->
<Label Grid.Row="1" Content="{Binding Breadcrumb}"/>
<!-- Page frame -->
<Frame x:Name="MainFrame" NavigationUIVisibility="Hidden" Content="{Binding CurrentPage}" Grid.Row="2" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" />
<!-- Softkey grid -->
<Grid Grid.Row="3" Margin="5,5,3,5" Width="Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Softkey 1 -->
<Button Grid.Column="0" MinWidth="80" MinHeight="{Binding RelativeSource={RelativeSource Self}, Path=ActualWidth}" Margin="0,0,2,0" Content="Automatic" Command="{Binding AutomaticModeClickedCommand}"/>
<!-- Command="{Binding AutomaticModeCommand}" -->
<!-- Softkey 2 -->
<Button Grid.Column="1" MinWidth="80" MinHeight="80" Margin="0,0,2,0" Content="Manual" Command="{Binding ManualModeClickedCommand}" />
<!-- Command="{Binding ManualModeCommand}" -->
<!-- Softkey 3 -->
<Button Grid.Column="2" MinWidth="80" MinHeight="80" Margin="0,0,2,0" Content="Settings" Command="{Binding SettingsWindowCommand}" />
<!-- Command="{Binding SettingsWindowCommand}" -->
<!-- Softkey 4 -->
<Button Grid.Column="3" MinWidth="80" MinHeight="80" Margin="0,0,2,0" Content="Events" Command="{Binding EventsListClickedCommand}"/>
<!-- Softkey 5 -->
<Button Grid.Column="4" MinWidth="80" MinHeight="80" Margin="0,0,2,0" />
<!-- Softkey 6 -->
<Button Grid.Column="5" MinWidth="80" MinHeight="80" Margin="0,0,2,0" />
<!-- Softkey 7 -->
<Button Grid.Column="6" MinWidth="80" MinHeight="80" Margin="0,0,2,0" />
<!-- Softkey 8 -->
<Button Grid.Column="7" Content="Ack Alarms" Command="{Binding AckAlarmsCommand}" MinWidth="80" MinHeight="80" Margin="0,0,2,0" />
<!-- Softkey 9 -->
<Button Grid.Column="8" MinWidth="80" MinHeight="80" Margin="0,0,2,0" />
<!-- Softkey 10 -->
<Button Grid.Column="9" Content="Back" Command="{Binding NavigateBackCommand}" IsEnabled="{Binding CanNavigateBack}" MinWidth="80" MinHeight="80" Margin="0,0,2,0" />
</Grid>
</Grid>
</Window>

View File

@@ -0,0 +1,26 @@
using HMIToolkit;
using MahApps.Metro.Controls;
namespace UniperHMI
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow
{
public MainWindow(MainWindowVM mainWindowVM)
{
this.DataContext = mainWindowVM;
InitializeComponent();
Closed += OnClosedEvent;
}
private void OnClosedEvent(object? sender, EventArgs e)
{
if (DataContext is IDisposable dataContext)
dataContext.Dispose();
}
}
}

View File

@@ -0,0 +1,273 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Heisig.HMI.AdsManager;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System.Windows;
using System.Windows.Controls;
using TcEventLoggerAdsProxyLib;
namespace UniperHMI;
public sealed partial class MainWindowVM : ObservableObject, IRecipient<NavigateMessage>, IDisposable
{
[ObservableProperty]
private StringControlButtonVM dummyStringVM;
[ObservableProperty]
private Page currentPage;
[ObservableProperty]
private bool canNavigateBack;
[ObservableProperty]
private Visibility statusBarVisible;
[ObservableProperty]
private string breadcrumb;
private readonly IAdsManager _adsManager;
private readonly IConfiguration _config;
private readonly TcEventLogger _eventlogger;
// Last active event
[ObservableProperty]
private string currentActiveEvent = "";
private readonly object _lock = new();
// Empty page
private readonly Page _emptyPage;
// Last navigate message
private readonly Stack<NavigateMessage> _messageStack = new();
NavigateMessage? _currentMessage;
// Events page view model
[ObservableProperty]
EventsPageVM _eventsPageVM;
// Settings page viem model
SettingsPageVM? _settingsPageVM;
public MainWindowVM(IAdsManager adsManager, IConfiguration config, TcEventLogger eventLogger)
{
_adsManager = adsManager;
_config = config;
// Create dummy string
DummyStringVM = new StringControlButtonVM();
// Create empty page
_emptyPage = new();
// Create events page viewmodel
_eventlogger = eventLogger;
_eventsPageVM = new(_eventlogger);
CurrentPage = _emptyPage;
_currentMessage = new NavigateMessage("", typeof(Page));
_messageStack.Push(_currentMessage);
WeakReferenceMessenger.Default.Register<NavigateMessage>(this);
breadcrumb = "";
}
[RelayCommand]
private void SettingsWindow()
{
StatusBarVisible = Visibility.Visible;
_messageStack.Clear();
NavigateMessage message = new("", typeof(SettingsPage));
Receive(message);
}
[RelayCommand]
public void AutomaticModeClicked()
{
StatusBarVisible = Visibility.Visible;
_messageStack.Clear();
_currentMessage = new NavigateMessage("", typeof(Page));
NavigateMessage message = new(_config["AutomaticModeVarName"]!, typeof(AutomaticModePage));
Receive(message);
}
[RelayCommand]
public void ManualModeClicked()
{
StatusBarVisible = Visibility.Visible;
_messageStack.Clear();
_currentMessage = new NavigateMessage("", typeof(Page));
NavigateMessage message = new("", typeof(BatteryOverviewPage));
Receive(message);
}
[RelayCommand]
public void EventsListClicked()
{
StatusBarVisible = Visibility.Collapsed;
_messageStack.Clear();
_currentMessage = new NavigateMessage("", typeof(Page));
NavigateMessage message = new("", typeof(EventsPage));
Receive(message);
}
[RelayCommand]
public void NavigateBack()
{
if (_messageStack.Count == 0)
{
CanNavigateBack = false;
return;
}
_currentMessage = _messageStack.Pop();
// Update if we can use the navigate back button
if (_messageStack.Count > 0)
CanNavigateBack = true;
else
{
StatusBarVisible = Visibility.Visible;
CanNavigateBack = false;
}
// Remove last two entrys from Breadcrumbs
int index = -1;
for (int i = 0; i < 2; i++)
{
index = Breadcrumb.LastIndexOf('>');
if (index != -1)
Breadcrumb = Breadcrumb[..index];
}
// Navigate to page
Navigate(_currentMessage);
}
// Only use for forward traversal!
public void Receive(NavigateMessage message)
{
// Only change page if its a new page type
if (CurrentPage.GetType() == message.type)
return;
// Push current message
if (_currentMessage != null)
_messageStack.Push(_currentMessage);
// Save current message for later push
_currentMessage = message;
// Set can navigate back
CanNavigateBack = true;
Navigate(message);
}
private void Navigate(NavigateMessage message)
{
// Dispose current pages viewmodel
if (CurrentPage.DataContext is IDisposable viewModel)
{
CurrentPage.DataContext = null;
viewModel.Dispose();
}
// Create new page
switch (message.type.Name)
{
case nameof(AutomaticModePage):
var automaticModeViewModel = (AutomaticModePageVM)ActivatorUtilities.CreateInstance(App.AppHost!.Services, typeof(AutomaticModePageVM), message.VariableName); //App.AppHost!.Services.GetRequiredService<AutomaticModePageVM>();
AutomaticModePage automaticModePage = new() { DataContext = automaticModeViewModel };
CurrentPage = automaticModePage;
Breadcrumb = "> Automatic";
break;
case nameof(BatteryOverviewPage):
var batteryOverviewPageVM = App.AppHost!.Services.GetRequiredService<BatteryOverviewPageVM>();
BatteryOverviewPage batteryOverviewPage = new() { DataContext = batteryOverviewPageVM };
CurrentPage = batteryOverviewPage;
Breadcrumb = "> Manual";
break;
case nameof(StringOverviewPage):
var stringOverviewVM = (StringOverviewPageVM)ActivatorUtilities.CreateInstance(App.AppHost!.Services, typeof(StringOverviewPageVM), message.VariableName);
StringOverviewPage stringPage = new() { DataContext = stringOverviewVM };
CurrentPage = stringPage;
if (message.Header.Length > 0)
AppendBreadcrumb(message.Header);
break;
case nameof(ModuleOverviewPage):
var moduleOverviewVM = (ModuleOverviewPageVM)ActivatorUtilities.CreateInstance(App.AppHost!.Services, typeof(ModuleOverviewPageVM), message.VariableName);
ModuleOverviewPage modulePage = new() { DataContext = moduleOverviewVM };
CurrentPage = modulePage;
if (message.Header.Length > 0)
AppendBreadcrumb(message.Header);
break;
case nameof(UnitOverviewPage):
var unitOverviewVM = (UnitOverviewPageVM)ActivatorUtilities.CreateInstance(App.AppHost!.Services, typeof(UnitOverviewPageVM), message.VariableName);
unitOverviewVM.UnitName = message.Header;
UnitOverviewPage unitPage = new() { DataContext = unitOverviewVM };
CurrentPage = unitPage;
if (message.Header.Length > 0)
AppendBreadcrumb(message.Header);
break;
case nameof(EventsPage):
#pragma warning disable MVVMTK0034 // Direct field reference to [ObservableProperty] backing field
EventsPage eventsPage = new() { DataContext = _eventsPageVM };
#pragma warning restore MVVMTK0034 // Direct field reference to [ObservableProperty] backing field
CurrentPage = eventsPage;
Breadcrumb = " > Events";
break;
case nameof(SettingsPage):
// Create seetings page view model only once
if (_settingsPageVM == null)
_settingsPageVM = new(_adsManager, "GVL_CONFIG.stUnitConfig");
SettingsPage settingsPage = new() { DataContext = _settingsPageVM };
CurrentPage = settingsPage;
Breadcrumb = " > Settings";
break;
default:
CurrentPage = new Page();
break;
}
}
private void AppendBreadcrumb(string path)
{
if (Breadcrumb[^1] != ' ')
Breadcrumb += " > " + path;
else
Breadcrumb += "> " + path;
}
[RelayCommand]
private void AckAlarms()
{
_adsManager.WriteValue("GVL_SCADA.stAckAlarmsButton.xRequest", true);
}
public void Dispose()
{
// Dispose current pages viewmodel
if (CurrentPage.DataContext is IDisposable viewModel)
{
CurrentPage.DataContext = null;
viewModel.Dispose();
}
DummyStringVM.Dispose();
}
}

View File

@@ -0,0 +1,3 @@
namespace UniperHMI;
public record class NavigateMessage(string VariableName, Type type, string Header = "");

View File

@@ -0,0 +1,18 @@
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Heisig.HMI.AdsManager;
namespace UniperHMI;
public sealed partial class ModuleControlButtonVM : SMUBaseVM, IDisposable
{
public ModuleControlButtonVM() : base() { }
public ModuleControlButtonVM(IAdsManager adsManager, string variableName) : base(adsManager, variableName) { }
[RelayCommand]
private void Clicked()
{
WeakReferenceMessenger.Default.Send(new NavigateMessage(_variableName, typeof(ModuleOverviewPage)));
}
}

View File

@@ -0,0 +1,60 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Heisig.HMI.AdsManager;
using TwinCAT.TypeSystem;
namespace UniperHMI;
public enum E_COMPONENT_STATUS : short
{
OFF = 0,
ON = 1,
CHARGING = 2,
DISCHARGING = 3,
ERROR = 4
}
public partial class SMUBaseVM : ObservableObject, IDisposable
{
[ObservableProperty]
protected float voltage;
[ObservableProperty]
protected E_COMPONENT_STATUS status;
protected readonly string _variableName;
protected readonly IAdsManager? _adsManager;
public SMUBaseVM()
{
_variableName = string.Empty;
_adsManager = null;
voltage = 0.0f;
status = E_COMPONENT_STATUS.OFF;
}
public SMUBaseVM(IAdsManager adsManager, string variableName)
{
Status = E_COMPONENT_STATUS.OFF;
_adsManager = adsManager;
_variableName = variableName;
_adsManager!.Register(_variableName + ".rVoltage", VoltageChanged);
_adsManager.Register(_variableName + ".eStatus", StatusChanged);
}
protected void VoltageChanged(object? sender, ValueChangedEventArgs e)
{
Voltage = (float)e.Value;
}
protected void StatusChanged(object? sender, ValueChangedEventArgs e)
{
Status = (E_COMPONENT_STATUS)((short)e.Value);
}
public void Dispose()
{
_adsManager?.Deregister(_variableName + ".rVoltage", VoltageChanged);
_adsManager?.Deregister(_variableName + ".eStatus", StatusChanged);
}
}

View File

@@ -0,0 +1,16 @@
using Heisig.HMI.AdsManager;
namespace UniperHMI;
public sealed partial class StringControlButtonVM : SMUBaseVM, IDisposable
{
public StringControlButtonVM() : base() { }
public StringControlButtonVM(IAdsManager adsManager, string variableName) : base(adsManager, variableName) { }
//[RelayCommand]
//private void Clicked()
//{
// WeakReferenceMessenger.Default.Send(new NavigateMessage(_variableName, typeof(StringOverviewPage)));
//}
}

View File

@@ -0,0 +1,19 @@
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Heisig.HMI.AdsManager;
namespace UniperHMI.OwnControls
{
public sealed partial class UnitControlButtonVM : SMUBaseVM
{
public UnitControlButtonVM() : base() { }
public UnitControlButtonVM(IAdsManager adsManager, string variableName) : base(adsManager, variableName) { }
[RelayCommand]
private void Clicked()
{
WeakReferenceMessenger.Default.Send(new NavigateMessage(_variableName, typeof(UnitDetailsControl)));
}
}
}

View File

@@ -0,0 +1,494 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using HMIToolkit;
using TwinCAT.TypeSystem;
using Heisig.HMI.AdsManager;
namespace UniperHMI
{
public sealed partial class UnitDetailsControlVM : ObservableObject, IDisposable
{
[ObservableProperty]
private float rVoltage;
[ObservableProperty]
private E_COMPONENT_STATUS status;
[ObservableProperty]
private AnalogValueVM pressureNegolytSegmentInVM;
[ObservableProperty]
private AnalogValueVM pressureNegolytTankInVM;
[ObservableProperty]
private AnalogValueVM temperatureNegolytTankInVM;
[ObservableProperty]
private AnalogValueVM pressurePosolytSegmentInVM;
[ObservableProperty]
private AnalogValueVM pressurePosolytTankInVM;
[ObservableProperty]
private AnalogValueVM temperaturePosolytTankInVM;
[ObservableProperty]
private bool canOpenBothValves;
[ObservableProperty]
private bool canCloseBothValves;
[ObservableProperty]
private short feedbackOpenValves;
[ObservableProperty]
private short feedbackCloseValves;
[ObservableProperty]
private bool canStartBothPumps;
[ObservableProperty]
private bool canStopBothPumps;
[ObservableProperty]
private short feedbackStartPumps;
[ObservableProperty]
private short feedbackStopPumps;
private float _posolytPumpOnSpeed;
private float _negolytPumpOnSpeed;
private float valveWindowHorizontalPosition;
private readonly BinaryValveControlVM _valveNegolytVM;
private readonly BinaryValveControlVM _valvePosolytVM;
private readonly AnalogMotorControlVM _pumpNegolytVM;
private readonly AnalogMotorControlVM _pumpPosolytVM;
private BinaryValveWindow? _windowValveNegolyt;
private BinaryValveWindow? _windowValvePosolyt;
private AnalogMotorWindow? _windowPumpNegolyt;
private AnalogMotorWindow? _windowPumpPosolyt;
private readonly IAdsManager? _adsManager;
private readonly string _variableName;
public UnitDetailsControlVM()
{
Status = E_COMPONENT_STATUS.OFF;
rVoltage = 0.0f;
_variableName = "";
// Negolyt
PressureNegolytSegmentInVM = new AnalogValueVM();
PressureNegolytTankInVM = new AnalogValueVM();
TemperatureNegolytTankInVM = new AnalogValueVM();
_valveNegolytVM = new BinaryValveControlVM();
_pumpNegolytVM = new AnalogMotorControlVM();
_windowValveNegolyt = null;
// Posolyt
PressurePosolytSegmentInVM = new AnalogValueVM();
PressurePosolytTankInVM = new AnalogValueVM();
TemperaturePosolytTankInVM = new AnalogValueVM();
_valvePosolytVM = new BinaryValveControlVM();
_pumpPosolytVM = new AnalogMotorControlVM();
_windowValvePosolyt = null;
valveWindowHorizontalPosition = 10;
}
public UnitDetailsControlVM(IAdsManager adsManager, string variableName)
{
_adsManager = adsManager;
_variableName = variableName;
Status = E_COMPONENT_STATUS.OFF;
rVoltage = 0.0f;
// Negolyt
PressureNegolytSegmentInVM = new AnalogValueVM(_adsManager, _variableName + ".stP21", true);
PressureNegolytTankInVM = new AnalogValueVM(_adsManager, _variableName + ".stP22", true);
TemperatureNegolytTankInVM = new AnalogValueVM(_adsManager, _variableName + ".stT21", true);
_valveNegolytVM = new BinaryValveControlVM(_adsManager, _variableName + ".stNS22");
_valveNegolytVM.OpenButton.FeedbackChanged += OnValveNegolytOpenFeedbackChanged;
_valveNegolytVM.CloseButton.FeedbackChanged += OnValveNegolytCloseFeedbackChanged;
_valveNegolytVM.OpenButton.ReleaseChanged += OnValveNegolytOpenReleaseChanged;
_valveNegolytVM.CloseButton.ReleaseChanged += OnValveNegolytCloseReleaseChanged;
_pumpNegolytVM = new AnalogMotorControlVM(_adsManager, _variableName + ".stNS21");
_pumpNegolytVM.StartButton.FeedbackChanged += OnPumpNegolytStartFeedbackChanged;
_pumpNegolytVM.StopButton.FeedbackChanged += OnPumpNegolytStopFeedbackChanged;
_pumpNegolytVM.StartButton.ReleaseChanged += OnPumpNegolytStartReleaseChanged;
_pumpNegolytVM.StopButton.ReleaseChanged += OnPumpNegolytStopReleaseChanged;
// Posolyt
PressurePosolytSegmentInVM = new AnalogValueVM(_adsManager, _variableName + ".stP11", true);
PressurePosolytTankInVM = new AnalogValueVM(_adsManager, _variableName + ".stP12", true);
TemperaturePosolytTankInVM = new AnalogValueVM(_adsManager, _variableName + ".stT11", true);
_valvePosolytVM = new BinaryValveControlVM(_adsManager, _variableName + ".stNS12");
_valvePosolytVM.OpenButton.FeedbackChanged += OnValvePosolytOpenFeedbackChanged;
_valvePosolytVM.CloseButton.FeedbackChanged += OnValvePosolytCloseFeedbackChanged;
_valvePosolytVM.OpenButton.ReleaseChanged += OnValvePosolytOpenReleaseChanged;
_valvePosolytVM.CloseButton.ReleaseChanged += OnValvePosolytCloseReleaseChanged;
_pumpPosolytVM = new AnalogMotorControlVM(_adsManager, _variableName + ".stNS11");
_pumpPosolytVM.StartButton.FeedbackChanged += OnPumpPosolytStartFeedbackChanged;
_pumpPosolytVM.StopButton.FeedbackChanged += OnPumpPosolytStopFeedbackChanged;
_pumpPosolytVM.StartButton.ReleaseChanged += OnPumpPosolytStartReleaseChanged;
_pumpPosolytVM.StopButton.ReleaseChanged += OnPumpPosolytStopReleaseChanged;
// Current status
_adsManager.Register(_variableName + ".eStatus", StatusChanged);
_adsManager.Register(_variableName + ".rVoltage", VoltageChanged);
// Configured pump speed for on
_adsManager.Register("GVL_CONFIG.rPumpNegolytOnPower", NegolytPumpOnSpeedChanged);
_adsManager.Register("GVL_CONFIG.rPumpPosolytOnPower", PosolytPumpOnSpeedChanged);
valveWindowHorizontalPosition = 10;
}
private void NegolytPumpOnSpeedChanged(object? sender, ValueChangedEventArgs e)
{
_negolytPumpOnSpeed = (float)e.Value;
}
private void PosolytPumpOnSpeedChanged(object? sender, ValueChangedEventArgs e)
{
_posolytPumpOnSpeed = (float)e.Value;
}
private void VoltageChanged(object? sender, ValueChangedEventArgs e)
{
RVoltage = (float)e.Value;
}
public void Dispose()
{
// Dispose all necessary view models
// Negolyt
PressureNegolytSegmentInVM.Dispose();
PressureNegolytTankInVM.Dispose();
TemperatureNegolytTankInVM.Dispose();
_valveNegolytVM.OpenButton.FeedbackChanged -= OnValveNegolytOpenFeedbackChanged;
_valveNegolytVM.CloseButton.FeedbackChanged -= OnValveNegolytCloseFeedbackChanged;
_valveNegolytVM.OpenButton.ReleaseChanged -= OnValveNegolytOpenReleaseChanged;
_valveNegolytVM.CloseButton.ReleaseChanged -= OnValveNegolytCloseReleaseChanged;
_valveNegolytVM.Dispose();
_pumpNegolytVM.Dispose();
// Posolyt
PressurePosolytSegmentInVM.Dispose();
PressurePosolytTankInVM.Dispose();
TemperaturePosolytTankInVM.Dispose();
_valvePosolytVM.OpenButton.FeedbackChanged -= OnValvePosolytOpenFeedbackChanged;
_valvePosolytVM.CloseButton.FeedbackChanged -= OnValvePosolytCloseFeedbackChanged;
_valvePosolytVM.OpenButton.ReleaseChanged -= OnValvePosolytOpenReleaseChanged;
_valvePosolytVM.CloseButton.ReleaseChanged -= OnValvePosolytCloseReleaseChanged;
_valvePosolytVM.Dispose();
_pumpPosolytVM.Dispose();
// Deregister variables
_adsManager?.Deregister(_variableName + ".eStatus", StatusChanged);
_adsManager?.Deregister(_variableName + ".rVoltage", VoltageChanged);
_adsManager?.Deregister("GVL_CONFIG.rPumpNegolytOnPower", NegolytPumpOnSpeedChanged);
_adsManager?.Deregister("GVL_CONFIG.rPumpPosolytOnPower", PosolytPumpOnSpeedChanged);
// Destroy windows
_windowValveNegolyt?.Close();
_windowValvePosolyt?.Close();
_windowPumpNegolyt?.Close();
_windowPumpPosolyt?.Close();
}
private void StatusChanged(object? sender, ValueChangedEventArgs e)
{
Status = (E_COMPONENT_STATUS)((short)e.Value);
}
[RelayCommand]
private void ShowValveNegolyt()
{
if (_adsManager != null && _windowValveNegolyt == null)
{
_windowValveNegolyt = new() { DataContext = _valveNegolytVM };
_windowValveNegolyt.Closed += WindowValveNegolyt_Closed;
_windowValveNegolyt.Show();
}
}
private void WindowValveNegolyt_Closed(object? sender, EventArgs e)
{
_windowValveNegolyt!.Close();
_windowValveNegolyt = null;
}
[RelayCommand]
private void ShowValvePosolyt()
{
if (_adsManager != null && _windowValvePosolyt == null)
{
_windowValvePosolyt = new() { DataContext = _valvePosolytVM };
_windowValvePosolyt.Closed += WindowValvePosolyt_Closed;
_windowValvePosolyt.Show();
}
}
private void WindowValvePosolyt_Closed(object? sender, EventArgs e)
{
_windowValvePosolyt!.Close();
_windowValvePosolyt = null;
}
[RelayCommand]
public void ShowPumpNegolyt()
{
if (_adsManager != null && _windowPumpNegolyt == null)
{
_windowPumpNegolyt = new() { DataContext = _pumpNegolytVM };
_windowPumpNegolyt.Closed += WindowPumpNegolyt_Closed;
_windowPumpNegolyt.Show();
}
}
private void WindowPumpNegolyt_Closed(object? sender, EventArgs e)
{
_windowPumpNegolyt!.Close();
_windowPumpNegolyt = null;
}
[RelayCommand]
public void ShowPumpPosolyt()
{
if (_adsManager != null && _windowPumpPosolyt == null)
{
_windowPumpPosolyt = new() { DataContext = _pumpPosolytVM };
_windowPumpPosolyt.Closed += WindowPumpPosolyt_Closed;
_windowPumpPosolyt.Show();
}
}
[RelayCommand]
private void OpenBothValves()
{
_valveNegolytVM.OpenButton?.ButtonClickedCommand.Execute(null);
_valvePosolytVM.OpenButton?.ButtonClickedCommand.Execute(null);
}
[RelayCommand]
private void CloseBothValves()
{
_valveNegolytVM.CloseButton?.ButtonClickedCommand.Execute(null);
_valvePosolytVM.CloseButton?.ButtonClickedCommand.Execute(null);
}
private void OnValveNegolytOpenFeedbackChanged(object? sender, EventArgs e)
{
CalculateOpenFeedback();
}
private void OnValvePosolytOpenFeedbackChanged(object? sender, EventArgs e)
{
CalculateOpenFeedback();
}
private void CalculateOpenFeedback()
{
if (_valveNegolytVM?.OpenButton.IFeedback == 1 && _valvePosolytVM?.OpenButton.IFeedback == 1)
FeedbackOpenValves = 1;
else if (_valveNegolytVM?.OpenButton.IFeedback == 0 && _valvePosolytVM?.OpenButton.IFeedback == 0)
FeedbackOpenValves = 0;
else
FeedbackOpenValves = 2;
}
private void OnValveNegolytCloseFeedbackChanged(object? sender, EventArgs e)
{
CalculateCloseFeedback();
}
private void OnValvePosolytCloseFeedbackChanged(object? sender, EventArgs e)
{
CalculateCloseFeedback();
}
private void CalculateCloseFeedback()
{
if (_valveNegolytVM?.CloseButton.IFeedback == 1 && _valvePosolytVM?.CloseButton.IFeedback == 1)
FeedbackCloseValves = 1;
else if (_valveNegolytVM?.CloseButton.IFeedback == 0 && _valvePosolytVM?.CloseButton.IFeedback == 0)
FeedbackCloseValves = 0;
else
FeedbackCloseValves = 2;
}
private void OnValveNegolytOpenReleaseChanged(object? sender, EventArgs e)
{
CalculateOpenRelease();
}
private void OnValvePosolytOpenReleaseChanged(object? sender, EventArgs e)
{
CalculateOpenRelease();
}
private void CalculateOpenRelease()
{
if (_valvePosolytVM == null || _valveNegolytVM == null)
return;
if (_valveNegolytVM.OpenButton.XRelease && _valvePosolytVM.OpenButton.XRelease)
CanOpenBothValves = true;
else
CanOpenBothValves = false;
}
private void OnValveNegolytCloseReleaseChanged(object? sender, EventArgs e)
{
CalculateCloseRelease();
}
private void OnValvePosolytCloseReleaseChanged(object? sender, EventArgs e)
{
CalculateCloseRelease();
}
private void CalculateCloseRelease()
{
if (_valvePosolytVM == null || _valveNegolytVM == null)
return;
if (_valveNegolytVM.CloseButton.XRelease && _valvePosolytVM.CloseButton.XRelease)
CanCloseBothValves = true;
else
CanCloseBothValves = false;
}
[RelayCommand]
private void StartBothPumps()
{
if (_adsManager == null || _pumpNegolytVM == null || _pumpPosolytVM == null)
return;
_pumpNegolytVM.Setpoint.RValue = _negolytPumpOnSpeed;
_pumpPosolytVM.Setpoint.RValue = _posolytPumpOnSpeed;
_pumpNegolytVM.StartButton?.ButtonClickedCommand.Execute(null);
_pumpPosolytVM.StartButton?.ButtonClickedCommand.Execute(null);
}
[RelayCommand]
private void StopBothPumps()
{
_pumpNegolytVM.StopButton?.ButtonClickedCommand.Execute(null);
_pumpPosolytVM.StopButton?.ButtonClickedCommand.Execute(null);
}
private void CalculateStartRelease()
{
if (_pumpNegolytVM == null || _pumpPosolytVM == null)
return;
if (_pumpNegolytVM.StartButton.XRelease && _pumpPosolytVM.StartButton.XRelease)
CanStartBothPumps = true;
else
CanStartBothPumps = false;
}
private void CalculatStopRelease()
{
if (_pumpNegolytVM == null || _pumpPosolytVM == null)
return;
if (_pumpNegolytVM.StopButton.XRelease && _pumpPosolytVM.StopButton.XRelease)
CanStopBothPumps = true;
else
CanStopBothPumps = false;
}
private void CalculateStartFeedback()
{
if (_pumpNegolytVM == null || _pumpPosolytVM == null)
return;
if (_pumpNegolytVM.StartButton.IFeedback == 1 && _pumpPosolytVM.StartButton.IFeedback == 1)
FeedbackStartPumps = 1;
else if (_pumpNegolytVM.StartButton.IFeedback == 0 && _pumpPosolytVM.StartButton.IFeedback == 0)
FeedbackStartPumps = 0;
else
FeedbackStartPumps = 2;
}
private void CalculateStopFeedback()
{
if (_pumpNegolytVM == null || _pumpPosolytVM == null)
return;
if (_pumpNegolytVM.StopButton.IFeedback == 1 && _pumpPosolytVM.StopButton.IFeedback == 1)
FeedbackStopPumps = 1;
else if (_pumpNegolytVM.StopButton.IFeedback == 0 && _pumpPosolytVM.StopButton.IFeedback == 0)
FeedbackStopPumps = 0;
else
FeedbackStopPumps = 2;
}
private void OnPumpPosolytStopReleaseChanged(object? sender, EventArgs e)
{
CalculatStopRelease();
}
private void OnPumpPosolytStartReleaseChanged(object? sender, EventArgs e)
{
CalculateStartRelease();
}
private void OnPumpPosolytStopFeedbackChanged(object? sender, EventArgs e)
{
CalculateStopFeedback();
}
private void OnPumpPosolytStartFeedbackChanged(object? sender, EventArgs e)
{
CalculateStartFeedback();
}
private void OnPumpNegolytStopReleaseChanged(object? sender, EventArgs e)
{
CalculatStopRelease();
}
private void OnPumpNegolytStartReleaseChanged(object? sender, EventArgs e)
{
CalculateStartRelease();
}
private void OnPumpNegolytStopFeedbackChanged(object? sender, EventArgs e)
{
CalculateStopFeedback();
}
private void OnPumpNegolytStartFeedbackChanged(object? sender, EventArgs e)
{
CalculateStartFeedback();
}
private void WindowPumpPosolyt_Closed(object? sender, EventArgs e)
{
_windowPumpPosolyt!.Close();
_windowPumpPosolyt = null;
}
}
}

View File

@@ -0,0 +1,42 @@
<Button x:Class="UniperHMI.SMUControlButton"
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:local="clr-namespace:UniperHMI"
d:DataContext="{d:DesignInstance Type=local:StringControlButtonVM, IsDesignTimeCreatable=True}"
mc:Ignorable="d"
Style="{StaticResource MahApps.Styles.Button}"
HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"
Width="Auto" Height="Auto"
x:Name="root">
<Grid x:Name="mainGrid" Width="Auto" Height="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Row 0 -->
<Label Content="{Binding SMUName, ElementName=root}" Grid.Row="0" Grid.ColumnSpan="3" FontSize="24" HorizontalAlignment="Center"/>
<!-- Row 1 -->
<Line Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" X1="0" X2="{Binding ElementName=mainGrid, Path=ActualWidth}" Stroke="White" Margin="0,5,0,10"/>
<!-- Row 2 -->
<Label Grid.Row="2" Grid.Column="0" Content="Spannung:" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Voltage, StringFormat=N2}" MaxLines="1" Width="70" TextAlignment="Right" IsReadOnly="True" />
<Label Grid.Row="2" Grid.Column="2" Content="V" />
<!-- Row 3 -->
<Label Content="Status:" Grid.Row="3" Grid.Column="0" />
<Label Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2" Content="{Binding Status}" HorizontalAlignment="Center"/>
</Grid>
</Button>

View File

@@ -0,0 +1,23 @@
using System.Windows;
using System.Windows.Controls;
namespace UniperHMI
{
/// <summary>
/// Interaktionslogik für StringControlButton.xaml
/// </summary>
public partial class SMUControlButton : Button
{
public static readonly DependencyProperty SMUNameProperty = DependencyProperty.Register("SMUName", typeof(string), typeof(SMUControlButton));
public String SMUName
{
get { return (string)GetValue(SMUNameProperty); }
set { SetValue(SMUNameProperty, value); }
}
public SMUControlButton()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,138 @@
<UserControl xmlns:HMIToolkit="clr-namespace:HMIToolkit" x:Class="UniperHMI.UnitDetailsControl"
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:local="clr-namespace:UniperHMI"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:UnitDetailsControlVM, IsDesignTimeCreatable=True}"
HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" Width="Auto" Height="Auto"
x:Name="root">
<UserControl.Resources>
<HMIToolkit:FeedbackToColorConverter x:Key="feedbackConverter" />
</UserControl.Resources>
<Grid x:Name="mainGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Row 0 -->
<Label Content="{Binding UnitName, ElementName=root}" Grid.Row="0" Grid.ColumnSpan="4" FontSize="24" HorizontalAlignment="Center"/>
<!-- Row 1 -->
<Line Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="4" X1="0" X2="{Binding ElementName=mainGrid, Path=ActualWidth}" Stroke="White" Margin="0,5,0,10"/>
<!-- Row 2 -->
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal" Width="Auto" HorizontalAlignment="Left">
<Label Content="Spannung:" />
<TextBox Text="{Binding RVoltage, StringFormat=N2}" MaxLines="1" Width="70" TextAlignment="Right" IsReadOnly="True" />
<Label Content="V" />
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Label Content="Status:" Margin="20,0,0,0"/>
<Label Grid.ColumnSpan="2" Content="{Binding Status}" HorizontalAlignment="Center"/>
</StackPanel>
</Grid>
<!-- Row 3 -->
<Line Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="4" X1="0" X2="{Binding ElementName=mainGrid, Path=ActualWidth}" Stroke="White" Margin="0,5,0,10"/>
<!-- Row 4 -->
<Grid Grid.Row="4">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="Negolyt:" FontWeight="Bold" HorizontalAlignment="Center"/>
<Label Grid.Row="0" Grid.Column="2" Content="Posolyt:" FontWeight="Bold" HorizontalAlignment="Center"/>
<Button Grid.Row="1" Grid.Column="0" Content="Tankventil" Command="{Binding ShowValveNegolytCommand}" HorizontalAlignment="Center" Width="100" />
<Button Grid.Row="1" Grid.Column="2" Content="Tankventil" Command="{Binding ShowValvePosolytCommand}" HorizontalAlignment="Center" Width="100" />
<!-- Open Close both valves -->
<Label Grid.Row="0" Grid.Column="1" Content="Both:" FontWeight="Bold" HorizontalAlignment="Center"/>
<Grid Grid.Row="1" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Content="Open" Width="50" Command="{Binding OpenBothValvesCommand}" IsEnabled="{Binding CanOpenBothValves}" Background="{Binding FeedbackOpenValves, Converter={StaticResource feedbackConverter}}"/>
<Button Grid.Column="1" Content="Close" Width="50" Command="{Binding CloseBothValvesCommand}" IsEnabled="{Binding CanCloseBothValves}" Background="{Binding FeedbackCloseValves, Converter={StaticResource feedbackConverter}}" Margin="5,0,0,0"/>
</Grid>
<!-- Start stop both pumps -->
<Grid Grid.Row="2" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Content="Start" Command="{Binding StartBothPumpsCommand}" IsEnabled="{Binding CanStartBothPumps}" Background="{Binding FeedbackStartPumps, Converter={StaticResource feedbackConverter}}" Width="50" Margin="0,5,0,0"/>
<Button Grid.Column="1" Content="Stop" Command="{Binding StopBothPumpsCommand}" IsEnabled="{Binding CanStopBothPumps}" Background="{Binding FeedbackStopPumps, Converter={StaticResource feedbackConverter}}" Width="50" Margin="5,5,0,0"/>
</Grid>
<Button Grid.Row="2" Grid.Column="0" Content="Pumpe" Command="{Binding ShowPumpNegolytCommand}" HorizontalAlignment="Center" Width="100" Margin="0,5,0,0"/>
<Button Grid.Row="2" Grid.Column="2" Content="Pumpe" Command="{Binding ShowPumpPosolytCommand}" HorizontalAlignment="Center" Width="100" Margin="0,5,0,0"/>
</Grid>
<!-- Row 5 -->
<Line Grid.Row="5" X1="0" X2="{Binding ElementName=mainGrid, Path=ActualWidth}" Stroke="White" Margin="0,5,0,10"/>
<!-- Row 6 -->
<Grid Grid.Row="6">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="1" Content="Negolyt:" FontWeight="Bold" />
<Label Grid.Row="0" Grid.Column="2" Content="Posolyt:" FontWeight="Bold" />
<Label Grid.Row="1" Grid.Column="0" Content="Druck Segment Inlet:" />
<HMIToolkit:AnalogValue Grid.Row="1" Grid.Column="1" DataContext="{Binding PressureNegolytSegmentInVM}" HorizontalAlignment="Left" />
<HMIToolkit:AnalogValue Grid.Row="1" Grid.Column="2" DataContext="{Binding PressurePosolytSegmentInVM}" HorizontalAlignment="Left" />
<Label Grid.Row="2" Grid.Column="0" Content="Druck Tank Inlet:" Margin="0,5,0,0"/>
<HMIToolkit:AnalogValue Grid.Row="2" Grid.Column="1" DataContext="{Binding PressureNegolytTankInVM}" HorizontalAlignment="Left" Margin="0,5,0,0"/>
<HMIToolkit:AnalogValue Grid.Row="2" Grid.Column="2" DataContext="{Binding PressurePosolytTankInVM}" HorizontalAlignment="Left" Margin="0,5,0,0"/>
<Label Grid.Row="3" Grid.Column="0" Content="Temperatur Tank Inlet:" Margin="0,5,0,5"/>
<HMIToolkit:AnalogValue Grid.Row="3" Grid.Column="1" DataContext="{Binding TemperatureNegolytTankInVM}" HorizontalAlignment="Left" Margin="0,5,0,5"/>
<HMIToolkit:AnalogValue Grid.Row="3" Grid.Column="2" DataContext="{Binding TemperaturePosolytTankInVM}" HorizontalAlignment="Left" Margin="0,5,0,5"/>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,22 @@
using System.Windows;
using System.Windows.Controls;
namespace UniperHMI
{
/// <summary>
/// Interaktionslogik für UnitControl.xaml
/// </summary>
public partial class UnitDetailsControl : UserControl
{
public static readonly DependencyProperty UnitNameProperty = DependencyProperty.Register("UnitName", typeof(string), typeof(UnitDetailsControl));
public String UnitName
{
get { return (string)GetValue(UnitNameProperty); }
set { SetValue(UnitNameProperty, value); }
}
public UnitDetailsControl()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,151 @@
using CommunityToolkit.Mvvm.ComponentModel;
using HMIToolkit;
using System.ComponentModel.DataAnnotations;
using Heisig.HMI.AdsManager;
using CommunityToolkit.Mvvm.Input;
using TwinCAT.TypeSystem;
using System.Collections.ObjectModel;
namespace UniperHMI
{
public enum E_BMS_CONTROL_MODE : short
{
AUTO_REMOTE = 1,
AUTO_LOCAL = 2,
SAFETY_CHECK = 3,
CAPACITY_TEST = 4,
MANUAL = 5
}
public class BMSControlModeEntry(E_BMS_CONTROL_MODE mode, string name)
{
public E_BMS_CONTROL_MODE eMode = mode;
public string Name = name;
public override string ToString()
{
return Name;
}
}
public sealed partial class AutomaticModePageVM : ObservableValidator, IDisposable
{
private int _setpoint;
[Range(-40000, 40000)]
public int Setpoint
{
get => this._setpoint;
set => SetProperty(ref this._setpoint, value, true);
}
[ObservableProperty]
private int processValue;
[ObservableProperty]
public HMIControlButtonVM? startButton;
[ObservableProperty]
public HMIControlButtonVM? stopButton;
[ObservableProperty]
private E_BMS_CONTROL_MODE bmsControlMode;
[ObservableProperty]
public ObservableCollection<BMSControlModeEntry> reqBMSControlModes =
[
new BMSControlModeEntry(E_BMS_CONTROL_MODE.AUTO_REMOTE, "Auto Remote"),
new BMSControlModeEntry(E_BMS_CONTROL_MODE.AUTO_LOCAL, "Auto Local"),
new BMSControlModeEntry(E_BMS_CONTROL_MODE.SAFETY_CHECK, "Safety Check"),
new BMSControlModeEntry(E_BMS_CONTROL_MODE.CAPACITY_TEST, "Capacity Test"),
new BMSControlModeEntry(E_BMS_CONTROL_MODE.MANUAL, "Manual")
];
[ObservableProperty]
private BMSControlModeEntry selectedControlMode;
[ObservableProperty]
private bool canChangeControlMode;
private readonly string? _variableName;
private readonly IAdsManager? _adsManager;
public AutomaticModePageVM()
{
StartButton = new HMIControlButtonVM();
StopButton = new HMIControlButtonVM();
SelectedControlMode = ReqBMSControlModes[1];
canChangeControlMode = true;
}
public AutomaticModePageVM(IAdsManager adsManager, string variableName)
{
_adsManager = adsManager;
_variableName = variableName;
//StartButton = new HMIControlButtonVM(_adsManager, _variableName + ".stStartAutoButton");
//StopButton = new HMIControlButtonVM(_adsManager, _variableName + ".stStopAutoButton");
SelectedControlMode = ReqBMSControlModes[1];
_adsManager.Register("GVL_SCADA.eCurrentControlMode", CurrentControlModeChanged);
_adsManager.Register("GVL_SCADA.xCanChangeControlMode", CCCMChanged);
_adsManager.Register(_variableName + ".diSetpointAutomatic", SetpointChanged);
}
private void SetpointChanged(object? sender, ValueChangedEventArgs e)
{
Setpoint = (int)e.Value;
}
private void CCCMChanged(object? sender, ValueChangedEventArgs e)
{
CanChangeControlMode = (bool)e.Value;
}
private void CurrentControlModeChanged(object? sender, ValueChangedEventArgs e)
{
BmsControlMode = (E_BMS_CONTROL_MODE)e.Value;
SelectedControlMode.eMode = BmsControlMode;
SelectedControlMode.Name = "Test";
}
public void Dispose()
{
StartButton?.Dispose();
StartButton = null;
StopButton?.Dispose();
StopButton = null;
_adsManager?.Deregister("GVL_SCADA.eCurrentControlMode", CurrentControlModeChanged);
_adsManager?.Deregister("GVL_SCADA.xCanChangeControlMode", CCCMChanged);
_adsManager?.Deregister(_variableName + ".diSetpointAutomatic", SetpointChanged);
}
[RelayCommand]
private void StartAutomatic()
{
_adsManager?.WriteValue(_variableName + ".diSetpointAutomatic", Setpoint);
}
[RelayCommand]
private void StopAutomatic()
{
_adsManager?.WriteValue(_variableName + ".diSetpointAutomatic", 0);
Setpoint = 0;
}
public static ValidationResult ValidatePower(int power, ValidationContext context)
{
if (power < -40000 || power > 40000)
return new("Must be between -40.000 and +40.000");
else
return ValidationResult.Success!;
}
}
}

View File

@@ -0,0 +1,55 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Heisig.HMI.AdsManager;
namespace UniperHMI
{
public sealed partial class BatteryOverviewPageVM : ObservableObject, IDisposable
{
[ObservableProperty]
private StringControlButtonVM? string1VM;
[ObservableProperty]
private StringControlButtonVM? string2VM;
[ObservableProperty]
private StringControlButtonVM? dummyStringVM;
private readonly IAdsManager? _adsManager;
public BatteryOverviewPageVM()
{
string1VM = new StringControlButtonVM();
string2VM = new StringControlButtonVM();
}
public BatteryOverviewPageVM(IAdsManager adsManager)
{
_adsManager = adsManager;
string1VM = new StringControlButtonVM(adsManager, "GVL_SCADA.stHMIInterface[0]");
string2VM = new StringControlButtonVM(adsManager, "GVL_SCADA.stHMIInterface[1]");
}
public void Dispose()
{
String1VM?.Dispose();
String1VM = null;
String2VM?.Dispose();
String2VM = null;
}
[RelayCommand]
private void String1Clicked()
{
WeakReferenceMessenger.Default.Send(new NavigateMessage("GVL_SCADA.stHMIInterface[0]", typeof(StringOverviewPage), "String 1"));
}
[RelayCommand]
private void String2Clicked()
{
WeakReferenceMessenger.Default.Send(new NavigateMessage("GVL_SCADA.stHMIInterface[1]", typeof(StringOverviewPage), "String 2"));
}
}
}

View File

@@ -0,0 +1,109 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections.ObjectModel;
using System.Windows;
using TcEventLoggerAdsProxyLib;
namespace UniperHMI
{
public partial class EventData : ObservableObject
{
[ObservableProperty]
public uint id;
[ObservableProperty]
public string? message;
[ObservableProperty]
public DateTime raised;
[ObservableProperty]
public DateTime cleared;
[ObservableProperty]
public DateTime confirmed;
};
public sealed partial class EventsPageVM : ObservableObject
{
public ObservableCollection<EventData> CurrentEvents { get; private set; } = [];
private readonly object _lock = new();
private readonly TcEventLogger _logger;
[ObservableProperty]
private EventData? currentEvent;
// 599264352000000000 ticks is a date used by beckhoff for events that didnt happen up to this point
private const long NoTime = 599264352000000000;
public EventsPageVM(TcEventLogger logger)
{
_logger = logger;
_logger.AlarmRaised += SimpleAlarmRaisedEvent;
_logger.AlarmCleared += SimpleAlarmClearedEvent;
_logger.AlarmConfirmed += SimpleConfirmedAlarmEvent;
_logger.Connect("10.103.32.50.1.1");
GetAllActiveEvents();
}
private void RebuildCurrentEventsList()
{
lock (_lock)
{
CurrentEvents.Clear();
}
GetAllActiveEvents();
}
private void SimpleConfirmedAlarmEvent(TcAlarm alarm, bool remove)
{
Application.Current.Dispatcher.BeginInvoke(RebuildCurrentEventsList);
}
private void SimpleAlarmClearedEvent(TcAlarm alarm, bool remove)
{
Application.Current.Dispatcher.BeginInvoke(RebuildCurrentEventsList);
}
private void SimpleAlarmRaisedEvent(TcAlarm alarm)
{
Application.Current.Dispatcher.BeginInvoke(RebuildCurrentEventsList);
}
private void GetAllActiveEvents()
{
EventData eventData;
List<EventData> tempEventList = [];
lock (_lock)
{
foreach (var alarm in _logger.ActiveAlarms)
{
eventData = new()
{
Id = alarm.EventId,
Message = alarm.GetText(1033),
Raised = alarm.TimeRaised,
Cleared = alarm.TimeCleared,
Confirmed = alarm.TimeConfirmed
};
tempEventList.Add(eventData);
}
IEnumerable<EventData> _eventQuery =
from data in tempEventList
orderby data.Raised descending
select data;
CurrentEvent = _eventQuery.FirstOrDefault();
CurrentEvents = new ObservableCollection<EventData>(_eventQuery);
}
}
}
}

View File

@@ -0,0 +1,69 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Heisig.HMI.AdsManager;
using UniperHMI.OwnControls;
namespace UniperHMI
{
public sealed partial class ModuleOverviewPageVM : ObservableObject, IDisposable
{
[ObservableProperty]
private UnitControlButtonVM unit1;
[ObservableProperty]
private UnitControlButtonVM unit2;
[ObservableProperty]
private UnitControlButtonVM unit3;
[ObservableProperty]
private UnitControlButtonVM unit4;
private readonly IAdsManager? _adsManager;
private readonly string? _variableName;
public ModuleOverviewPageVM(IAdsManager adsManager, string variableName)
{
_adsManager = adsManager;
_variableName = variableName;
unit1 = new(_adsManager, _variableName + ".stHMIInterfaceUnit1");
unit2 = new(_adsManager, _variableName + ".stHMIInterfaceUnit2");
unit3 = new(_adsManager, _variableName + ".stHMIInterfaceUnit3");
unit4 = new(_adsManager, _variableName + ".stHMIInterfaceUnit4");
}
public void Dispose()
{
Unit1?.Dispose();
Unit2?.Dispose();
Unit3?.Dispose();
Unit4?.Dispose();
}
[RelayCommand]
public void Unit1Clicked()
{
WeakReferenceMessenger.Default.Send(new NavigateMessage(_variableName + ".stHMIInterfaceUnit1", typeof(UnitOverviewPage), "Unit 1"));
}
[RelayCommand]
public void Unit2Clicked()
{
WeakReferenceMessenger.Default.Send(new NavigateMessage(_variableName + ".stHMIInterfaceUnit2", typeof(UnitOverviewPage), "Unit 2"));
}
[RelayCommand]
public void Unit3Clicked()
{
WeakReferenceMessenger.Default.Send(new NavigateMessage(_variableName + ".stHMIInterfaceUnit3", typeof(UnitOverviewPage), "Unit 3"));
}
[RelayCommand]
public void Unit4Clicked()
{
WeakReferenceMessenger.Default.Send(new NavigateMessage(_variableName + ".stHMIInterfaceUnit4", typeof(UnitOverviewPage), "Unit 4"));
}
}
}

View File

@@ -0,0 +1,56 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Heisig.HMI.AdsManager;
namespace UniperHMI;
public sealed partial class StringOverviewPageVM : ObservableObject, IDisposable
{
[ObservableProperty]
private ModuleControlButtonVM module1;
[ObservableProperty]
private ModuleControlButtonVM module2;
[ObservableProperty]
private ModuleControlButtonVM module3;
private readonly IAdsManager? _adsManager;
private readonly string? _variableName;
public StringOverviewPageVM(IAdsManager adsManager, string variableName)
{
_adsManager = adsManager;
_variableName = variableName;
module1 = new(_adsManager, _variableName + ".stHMIInterfaceModule1");
module2 = new(_adsManager, _variableName + ".stHMIInterfaceModule2");
module3 = new(_adsManager, _variableName + ".stHMIInterfaceModule3");
}
public void Dispose()
{
Module1?.Dispose();
Module2?.Dispose();
Module3?.Dispose();
}
[RelayCommand]
private void Module1Clicked()
{
WeakReferenceMessenger.Default.Send(new NavigateMessage(_variableName + ".stHMIInterfaceModule1", typeof(ModuleOverviewPage), "Module 1"));
}
[RelayCommand]
private void Module2Clicked()
{
WeakReferenceMessenger.Default.Send(new NavigateMessage(_variableName + ".stHMIInterfaceModule2", typeof(ModuleOverviewPage), "Module 2"));
}
[RelayCommand]
private void Module3Clicked()
{
WeakReferenceMessenger.Default.Send(new NavigateMessage(_variableName + ".stHMIInterfaceModule3", typeof(ModuleOverviewPage), "Module 3"));
}
}

View File

@@ -0,0 +1,31 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Heisig.HMI.AdsManager;
namespace UniperHMI;
public sealed partial class UnitOverviewPageVM : ObservableObject, IDisposable
{
[ObservableProperty]
private string unitName;
[ObservableProperty]
private UnitDetailsControlVM unitControlVM;
private readonly IAdsManager? _adsManager;
private readonly string? _variableName;
public UnitOverviewPageVM(IAdsManager adsManager, string variableName)
{
_adsManager = adsManager;
_variableName = variableName;
unitControlVM = new(_adsManager, _variableName);
unitName = "Unit X";
}
public void Dispose()
{
UnitControlVM?.Dispose();
}
}

View File

@@ -0,0 +1,49 @@
<Page x:Class="UniperHMI.AutomaticModePage"
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:local="clr-namespace:UniperHMI"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:AutomaticModePageVM, IsDesignTimeCreatable=True}"
d:DesignHeight="450" d:DesignWidth="800"
Title="AutomaticModePage">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<Label Grid.Row="1" Grid.Column="1" Content="Requested Control Mode:" VerticalAlignment="Center" HorizontalAlignment="Left" />
<ComboBox Grid.Row="1" Grid.Column="2" IsEnabled="{Binding CanChangeControlMode}" ItemsSource="{Binding ReqBMSControlModes}" SelectedItem="{Binding SelectedControlMode}"/>
<Label Grid.Row="2" Grid.Column="1" Content="Current Control Mode:" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBox Grid.Row="2" Grid.Column="2" Text="{Binding BmsControlMode}" MaxLines="1" IsReadOnly="True" TextAlignment="Right"/>
<Label Grid.Row="3" Grid.Column="1" Content="Status:" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBox Grid.Row="3" Width="125" Grid.Column="2" Text="Charging" MaxLines="1" IsReadOnly="True" TextAlignment="Right"/>
<Label Grid.Row="4" Grid.Column="1" Content="Target Power (W):" Margin="0,5,0,0" VerticalAlignment="Center" HorizontalAlignment="Left"/>
<TextBox Grid.Row="4" Width="125" Grid.Column="2" Text="{Binding Setpoint}" MaxLines="1" Margin="0,5,0,0" TextAlignment="Right" />
<Label Grid.Row="5" Grid.Column="1" Content="Actual Power (W):" Margin="0,5,0,0" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBox Grid.Row="5" Width="125" Grid.Column="2" Text="{Binding ProcessValue}" MaxLines="1" Margin="0,5,0,0" IsReadOnly="True" TextAlignment="Right" />
<Button Grid.Row="6" Grid.Column="1" Command="{Binding StartAutomaticCommand}" Content="Start" Margin="0,5,5,0" />
<Button Grid.Row="6" Grid.Column="2" Command="{Binding StopAutomaticCommand}" Content="Stop" Margin="5,5,0,0" />
</Grid>
</Page>

View File

@@ -0,0 +1,22 @@
using System.Windows.Controls;
namespace UniperHMI
{
/// <summary>
/// Interaktionslogik für AutomaticModePage.xaml
/// </summary>
public partial class AutomaticModePage : Page
{
public AutomaticModePage()
{
InitializeComponent();
// Unloaded += OnUnloaded;
}
private void OnUnloaded(object? sender, EventArgs e)
{
var disposable = DataContext as IDisposable;
disposable?.Dispose();
}
}
}

View File

@@ -0,0 +1,39 @@
<Page x:Class="UniperHMI.BatteryOverviewPage"
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:local="clr-namespace:UniperHMI"
xmlns:mah="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
mc:Ignorable="d"
x:Name="root"
d:DataContext="{d:DesignInstance Type=local:BatteryOverviewPageVM, IsDesignTimeCreatable=False}"
Title="ManualModePage" Height="Auto" Width="Auto" VerticalAlignment="Center" HorizontalAlignment="Center">
<Grid Margin="10" Width="Auto" Height="Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<local:SMUControlButton Grid.Row="0" Grid.Column="0" DataContext="{Binding String1VM}" Command="{Binding ElementName=root, Path=DataContext.String1ClickedCommand}" SMUName="String 1" Margin="0,0,5,0" />
<local:SMUControlButton Grid.Row="0" Grid.Column="1" DataContext="{Binding String2VM}" Command="{Binding ElementName=root, Path=DataContext.String2ClickedCommand}" SMUName="String 2" Margin="0,0,5,0" />
<local:SMUControlButton Grid.Row="0" Grid.Column="2" DataContext="{Binding DummyStringVM}" SMUName="String 3" Margin="0,0,5,0" IsEnabled="False"/>
<local:SMUControlButton Grid.Row="0" Grid.Column="3" DataContext="{Binding DummyStringVM}" SMUName="String 4" Margin="0,0,5,0" IsEnabled="False"/>
<local:SMUControlButton Grid.Row="0" Grid.Column="4" DataContext="{Binding DummyStringVM}" SMUName="String 5" Margin="0,0,5,0" IsEnabled="False"/>
<local:SMUControlButton Grid.Row="0" Grid.Column="5" DataContext="{Binding DummyStringVM}" SMUName="String 6" IsEnabled="False"/>
<local:SMUControlButton Grid.Row="1" Grid.Column="0" DataContext="{Binding DummyStringVM}" SMUName="String 7" Margin="0,5,5,0" IsEnabled="False"/>
<local:SMUControlButton Grid.Row="1" Grid.Column="1" DataContext="{Binding DummyStringVM}" SMUName="String 8" Margin="0,5,5,0" IsEnabled="False"/>
<local:SMUControlButton Grid.Row="1" Grid.Column="2" DataContext="{Binding DummyStringVM}" SMUName="String 9" Margin="0,5,5,0" IsEnabled="False"/>
<local:SMUControlButton Grid.Row="1" Grid.Column="3" DataContext="{Binding DummyStringVM}" SMUName="String 10" Margin="0,5,5,0" IsEnabled="False"/>
<local:SMUControlButton Grid.Row="1" Grid.Column="4" DataContext="{Binding DummyStringVM}" SMUName="String 11" Margin="0,5,5,0" IsEnabled="False"/>
<local:SMUControlButton Grid.Row="1" Grid.Column="5" DataContext="{Binding DummyStringVM}" SMUName="String 12" Margin="0,5,0,0" IsEnabled="False"/>
</Grid>
</Page>

View File

@@ -0,0 +1,22 @@
using System.Windows.Controls;
namespace UniperHMI
{
/// <summary>
/// Interaktionslogik für ManualModePage.xaml
/// </summary>
public partial class BatteryOverviewPage : Page
{
public BatteryOverviewPage()
{
InitializeComponent();
//Unloaded += OnUnloaded;
}
private void OnUnloaded(object? sender, EventArgs e)
{
var disposable = DataContext as IDisposable;
disposable?.Dispose();
}
}
}

View File

@@ -0,0 +1,25 @@
<Page x:Class="UniperHMI.EventsPage"
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:local="clr-namespace:UniperHMI"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:EventsPageVM, IsDesignTimeCreatable=False}"
d:DesignHeight="450" d:DesignWidth="800"
Title="EventsPage">
<Page.Resources>
<local:DateTimeToEventTimeConverter x:Key="dtConverter" />
</Page.Resources>
<Grid>
<DataGrid ItemsSource="{Binding CurrentEvents}" AutoGenerateColumns="False" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Message" Binding="{Binding Message}" Width="400"/>
<DataGridTextColumn Header="Raised" Binding="{Binding Raised, Converter={StaticResource dtConverter}}" Width="*" SortDirection="Descending"/>
<DataGridTextColumn Header="Cleared" Binding="{Binding Cleared, Converter={StaticResource dtConverter}}" Width="*"/>
<DataGridTextColumn Header="Confirmed" Binding="{Binding Confirmed, Converter={StaticResource dtConverter}}" Width="*"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Page>

View File

@@ -0,0 +1,14 @@
using System.Windows.Controls;
namespace UniperHMI;
/// <summary>
/// Interaktionslogik für EventsPage.xaml
/// </summary>
public partial class EventsPage : Page
{
public EventsPage()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,25 @@
<Page x:Class="UniperHMI.ModuleOverviewPage"
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:local="clr-namespace:UniperHMI"
d:DataContext="{d:DesignInstance Type=local:ModuleOverviewPageVM, IsDesignTimeCreatable=False}"
mc:Ignorable="d"
Width="Auto" Height="Auto" x:Name="root" HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid Margin="10" ShowGridLines="False" Width="Auto" Height="Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<local:SMUControlButton SMUName="Unit 1" DataContext="{Binding Unit1}" Command="{Binding ElementName=root, Path=DataContext.Unit1ClickedCommand}" Grid.Row="0" Grid.Column="0" Margin="0,0,5,0" />
<local:SMUControlButton SMUName="Unit 2" DataContext="{Binding Unit2}" Command="{Binding ElementName=root, Path=DataContext.Unit2ClickedCommand}" Grid.Row="0" Grid.Column="1" />
<local:SMUControlButton SMUName="Unit 3" DataContext="{Binding Unit3}" Command="{Binding ElementName=root, Path=DataContext.Unit3ClickedCommand}" Grid.Row="1" Grid.Column="0" Margin="0,5,5,0" />
<local:SMUControlButton SMUName="Unit 4" DataContext="{Binding Unit4}" Command="{Binding ElementName=root, Path=DataContext.Unit4ClickedCommand}" Grid.Row="1" Grid.Column="1" Margin="0,5,0,0"/>
</Grid>
</Page>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace UniperHMI
{
/// <summary>
/// Interaktionslogik für ModuleOverviewPage.xaml
/// </summary>
public partial class ModuleOverviewPage : Page
{
public ModuleOverviewPage()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,23 @@
<Page x:Class="UniperHMI.StringOverviewPage"
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:local="clr-namespace:UniperHMI"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:StringOverviewPageVM, IsDesignTimeCreatable=False}"
Title="StringOverviewPage"
Width="Auto" Height="Auto" HorizontalAlignment="Center" VerticalAlignment="Center"
x:Name="root">
<Grid Margin="10" Width="Auto" Height="Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<local:SMUControlButton SMUName="Modul 1" DataContext="{Binding Module1}" Command="{Binding ElementName=root, Path=DataContext.Module1ClickedCommand}" Grid.Column="0" Margin="0,0,5,0"/>
<local:SMUControlButton SMUName="Modul 2" DataContext="{Binding Module2}" Command="{Binding ElementName=root, Path=DataContext.Module2ClickedCommand}" Grid.Column="1" Margin="0,0,5,0"/>
<local:SMUControlButton SMUName="Modul 3" DataContext="{Binding Module3}" Command="{Binding ElementName=root, Path=DataContext.Module3ClickedCommand}" Grid.Column="2"/>
</Grid>
</Page>

View File

@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace UniperHMI
{
/// <summary>
/// Interaktionslogik für StringOverviewPage.xaml
/// </summary>
public partial class StringOverviewPage : Page
{
public StringOverviewPage()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,16 @@
<Page x:Class="UniperHMI.UnitOverviewPage"
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:local="clr-namespace:UniperHMI"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:UnitOverviewPageVM, IsDesignTimeCreatable=False}"
Width="Auto" Height="Auto"
Title="UnitOverviewPage"
x:Name="root">
<Grid>
<local:UnitDetailsControl UnitName="{Binding ElementName=root, Path=DataContext.UnitName}" DataContext="{Binding UnitControlVM}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</Page>

View File

@@ -0,0 +1,14 @@
using System.Windows.Controls;
namespace UniperHMI;
/// <summary>
/// Interaktionslogik für UnitOverviewPage.xaml
/// </summary>
public partial class UnitOverviewPage : Page
{
public UnitOverviewPage()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,36 @@
<Page x:Class="UniperHMI.SettingsPage"
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:mah="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:local="clr-namespace:UniperHMI"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance Type=local:SettingsPageVM}"
Title="SettingsPageView">
<Grid>
<!-- {d:SampleData ItemCount=5} -->
<!--<DataGrid d:ItemsSource="{Binding CollectionView.View}"/>-->
<TreeView x:Name="treeview" ItemsSource="{Binding RootItem}" SelectedValuePath="Value" MinWidth="200" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding SubEntries}">
<TreeViewItem>
<TreeViewItem.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Content="{Binding Path=Name}"/>
<TextBox Text="{Binding Value}" Visibility="{Binding Visibility}" Margin="10,0,0,0" Width="150" TextAlignment="Right"/>
</Grid>
</TreeViewItem.Header>
<!-- <TextBox Text="{Binding Value}" Visibility="{Binding Visibility}"/> -->
</TreeViewItem>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</Page>

View File

@@ -0,0 +1,22 @@
using System.Windows.Controls;
namespace UniperHMI
{
/// <summary>
/// Interaktionslogik für SettingsPageView.xaml
/// </summary>
public partial class SettingsPage : Page
{
public SettingsPage()
{
InitializeComponent();
Unloaded += OnUnloaded;
}
private void OnUnloaded(object? sender, EventArgs e)
{
var disposable = DataContext as IDisposable;
disposable?.Dispose();
}
}
}

View File

@@ -0,0 +1,125 @@
using CommunityToolkit.Mvvm.ComponentModel;
using MahApps.Metro.Controls;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows;
using TwinCAT.Ads.TypeSystem;
using TwinCAT.TypeSystem;
using Heisig.HMI.AdsManager;
namespace UniperHMI
{
public partial class SettingsEntry : ObservableObject
{
[ObservableProperty]
private string name = "";
[ObservableProperty]
private string instancePath = "";
[ObservableProperty]
private string? dataType;
[ObservableProperty]
private Object? value;
public ObservableCollection<SettingsEntry> SubEntries { get; set; } = [];
[ObservableProperty]
private Visibility visibility = Visibility.Collapsed;
private readonly IAdsManager _adsManager;
public SettingsEntry(IAdsManager adsManager)
{
_adsManager = adsManager;
}
partial void OnValueChanging(object? oldValue, object? newValue)
{
if (newValue == null)
{
newValue = oldValue;
return;
}
try
{
_adsManager.WriteValue(InstancePath, newValue);
}
catch (Exception)
{
newValue = oldValue;
Debug.WriteLine("Value {0} could not be written to {1}", newValue, InstancePath);
}
}
}
public partial class SettingsPageVM : ObservableObject, IDisposable
{
public ObservableCollection<SettingsEntry> RootItem { get; private set; } = [];
private readonly IAdsManager _adsManager;
private readonly string _variableName;
public SettingsPageVM(IAdsManager adsManager, string variableName)
{
_adsManager = adsManager;
_variableName = variableName;
_adsManager.Register(_variableName, ConfigChangedEvent);
}
private void ConfigChangedEvent(object? sender, ValueChangedEventArgs e)
{
App.Current.Invoke(CreateSettingsTree);
}
private void CreateSettingsTree()
{
ISymbolLoader? symbolLoader = _adsManager.GetSymbolLoader();
if (symbolLoader == null)
return;
Symbol configSymbol = (Symbol)symbolLoader.Symbols[_variableName];
SettingsEntry entry = GetSymbol(configSymbol);
RootItem.Add(entry);
}
private SettingsEntry GetSymbol(Symbol symbol)
{
// Create Symbol
SettingsEntry entry = new(_adsManager)
{
Name = symbol.InstanceName,
InstancePath = symbol.InstancePath,
DataType = symbol.DataType?.Name
};
// Check if symbol has sub symbols
if (!symbol.IsPrimitiveType)
{
foreach (Symbol subSymbol in symbol.SubSymbols.Cast<Symbol>())
{
entry.SubEntries.Add(GetSymbol(subSymbol));
}
}
else
{
entry.Visibility = Visibility.Visible;
entry.Value = symbol.ReadValue();
}
return entry;
}
public void Dispose()
{
_adsManager?.Deregister(_variableName, ConfigChangedEvent);
}
}
}

View File

@@ -0,0 +1,53 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
<AssemblyVersion>0.1.0</AssemblyVersion>
<FileVersion>0.1</FileVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AdsManager" Version="1.0.0.6" />
<PackageReference Include="Beckhoff.TwinCAT.Ads" Version="6.1.197" />
<PackageReference Include="Beckhoff.TwinCAT.TcEventLoggerAdsProxy.Net" Version="2.7.11" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="MahApps.Metro" Version="2.4.10" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<Compile Update="HMIToolkit\HMIObjects\AnalogMotorControl\AnalogMotorControl.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="HMIToolkit\HMIObjects\AnalogValue\AnalogValue.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="HMIToolkit\HMIObjects\AnalogValveControl\AnalogValveControl.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="HMIToolkit\HMIObjects\BinaryValveControl\BinaryValveControl.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="HMIToolkit\HMIObjects\InterlockControl\IntlkControl.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="HMIToolkit\HMIObjects\InterlockDetailsControl\IntlkDetails.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="HMIToolkit\HMIObjects\InterlockDetailsControl\IntlkDetailsWindow.xaml.cs">
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,8 @@
{
"AutomaticModeVarName": "GVL_SCADA.stAutomaticModeHMI",
"String1VarName": "GVL_SCADA.stHMIInterface",
"Module1VarName": ".stHMIInterfaceModule1",
"Module2VarName": ".stHMIInterfaceModule2",
"Module3VarName": ".stHMIInterfaceModule3"
}