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

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;
}
}
}