Move examples from root to /Examples

This commit is contained in:
Felix Weiß
2023-08-21 16:21:55 +02:00
parent 47e50078b0
commit 993c3cba7e
26 changed files with 91 additions and 81 deletions

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPublishable>false</IsPublishable>
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\MewtocolNet\MewtocolNet.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,81 @@
using MewtocolNet;
using MewtocolNet.Logging;
namespace Examples.BasicUsage;
internal class Program {
const string IP = "192.168.115.210";
const int PORT = 9094;
static void Main(string[] args) => Task.Run(AsyncMain).Wait();
static async Task AsyncMain () {
//the library provides a logging tool, comment this out if needed
Logger.LogLevel = LogLevel.Critical;
//create a new interface to the plc using ethernet / tcp ip
//the using keyword is optional, if you want to use your PLC instance
//globally leave it
//you can also specify the source ip with:
//Mewtocol.Ethernet(IP, PORT).FromSource("192.168.113.2", 46040).Build()
using (var plc = Mewtocol.Ethernet(IP, PORT).Build()) {
//connect async to the plc
await plc.ConnectAsync();
//check if the connection was established
if (!plc.IsConnected) {
Console.WriteLine("Failed to connect to the plc...");
Environment.Exit(1);
}
//print basic plc info
Console.WriteLine(plc.PlcInfo);
//check if the plc is not in RUN mode, change to run
if(!plc.PlcInfo.IsRunMode) await plc.SetOperationModeAsync(true);
//get information about the plc
Console.WriteLine($"PLC type: {plc.PlcInfo.TypeName}");
Console.WriteLine($"Capacity: {plc.PlcInfo.ProgramCapacity}k");
Console.WriteLine($"Error: {plc.PlcInfo.SelfDiagnosticError}k");
//set the plc to prog mode
//await plc.SetOperationModeAsync(false);
//do disconnect use
plc.Disconnect();
//or
//await plc.DisconnectAsync();
//you can then change the connection settings for example to another PLC
plc.ConfigureConnection("192.168.115.212", 9094);
await plc.ConnectAsync();
plc.Disconnect();
plc.ConfigureConnection("192.168.115.214", 9094);
await plc.ConnectAsync();
plc.Disconnect();
plc.ConfigureConnection("192.168.178.55", 9094);
await plc.ConnectAsync();
}
//you can also find any applicable source endpoints by using:
foreach (var endpoint in Mewtocol.GetSourceEndpoints()) {
Console.WriteLine($"Usable endpoint: {endpoint}");
}
}
}

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPublishable>false</IsPublishable>
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\MewtocolNet\MewtocolNet.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,148 @@
using MewtocolNet;
using MewtocolNet.Logging;
using MewtocolNet.Registers;
namespace Examples.BasicRegisterReadWrite;
internal class Program {
//const string PLC_IP = "192.168.178.55";
const string PLC_IP = "192.168.115.210";
static void Main(string[] args) => Task.Run(AsyncMain).Wait();
static async Task AsyncMain() {
Console.Clear();
//the library provides a logging tool, comment this out if needed
Logger.LogLevel = LogLevel.Critical;
//here we collect our built registers
IRegister<short> simpleNumberRegister = null!;
IRegister<ushort> simpleNumberRegister2 = null!;
IRegister<Word> simpleWordRegister = null!;
IArrayRegister<Word> overlapWordRegister = null!;
IStringRegister stringRegister = null!;
IArrayRegister<string> stringArrayRegister = null!;
IArrayRegister2D<short> simpleNumberRegister2Dim = null!;
//create a new interface to the plc using ethernet / tcp ip
using var plc = Mewtocol.Ethernet(PLC_IP)
.WithRegisters(b => {
//a simple INT at register address DT1000
b.Struct<short>("DT1000").Build(out simpleNumberRegister);
b.Struct<ushort>("DT1001").Build(out simpleNumberRegister2);
//you can also read the same array as an other data type
//not how they are at the same address, that means their values are linked
b.Struct<Word>("DT1000").Build(out simpleWordRegister);
//same link feature is also true for arrays that overlap their addresses
//this will go from DT999 to DT1001 because its a 3 Word array
b.Struct<Word>("DT999").AsArray(3).Build(out overlapWordRegister);
//strings area not stacially sized, they use a different constructor
b.String("DT1024", 32).Build(out stringRegister);
//string can also be arrayed
b.String("DT2030", 5).AsArray(3).Build(out stringArrayRegister);
//a simple 2 dimensional ARRAY [0..2, 0..2] OF INT at DT2003
b.Struct<short>("DT2003").AsArray(3, 3).Build(out simpleNumberRegister2Dim);
b.Struct<bool>("R19A").Build();
})
.Build();
//you can explain the internal register layout and which ones are linked by
Console.WriteLine(plc.Explain());
//connect async to the plc
await plc.ConnectAsync();
//check if the connection was established
if (!plc.IsConnected) {
Console.WriteLine("Failed to connect to the plc...");
Environment.Exit(1);
}
//restarts the program, this will make sure the global registers of the plc get reset each run
await plc.RestartProgramAsync();
//from this point on we can modify our registers
//read the value of the the register
short readValue = await simpleNumberRegister.ReadAsync();
ushort readValue2 = await simpleNumberRegister2.ReadAsync();
//show the value
Console.WriteLine($"Read value for {nameof(simpleNumberRegister)}: {readValue}");
Console.WriteLine($"Read value for {nameof(simpleNumberRegister2)}: {readValue2}");
//write the value
await simpleNumberRegister.WriteAsync(30);
//show the value
Console.WriteLine($"Value of {nameof(simpleNumberRegister)}: {simpleNumberRegister.Value}");
//because the two registers at DT1000 are linked by their memory address in the plc,
//both of their values got updated
Console.WriteLine($"Value of {nameof(simpleWordRegister)}: {simpleWordRegister.Value}");
//also the overlapping word array register value got updated, but only at the DT positions that were read
int i = 0;
overlapWordRegister.Value.ToList().ForEach(x => {
Console.WriteLine($"Value of {nameof(overlapWordRegister)}[{i}]: {x}");
i++;
});
//you can even read out a word bitwise
Console.WriteLine($"Bits of {nameof(simpleWordRegister)}: {simpleWordRegister.Value?.ToStringBits()}");
//or as a single bit
Console.WriteLine($"Bit at index 3 of {nameof(simpleWordRegister)}: {simpleWordRegister.Value?[3]}");
//reading / writing the string register
//await stringRegister.WriteAsync("Lorem ipsum dolor sit amet, cons");
await stringRegister.ReadAsync();
Console.WriteLine($"Value of {nameof(stringRegister)}: {stringRegister.Value}");
//reading writing a string array register
await stringArrayRegister.ReadAsync();
i = 0;
stringArrayRegister.Value.ToList().ForEach(x => {
Console.WriteLine($"Value of {nameof(stringArrayRegister)}[{i}]: {x}");
i++;
});
//you can either set the whole array at once,
//this will be slow if you only want to update one item
await stringArrayRegister.WriteAsync(new string[] {
"Test1",
"Test2",
"Test3",
});
//or update just one
//COMING LATER
//same thing also works for 2 dim arrays
await simpleNumberRegister2Dim.ReadAsync();
//the array is multi dimensional but can also be iterated per element
foreach (var item in simpleNumberRegister2Dim)
Console.WriteLine($"Element of {nameof(simpleNumberRegister2Dim)}: {item}");
//you can also use the array indexer accessors
Console.WriteLine($"Element [1, 2] of {nameof(simpleNumberRegister2Dim)}: {simpleNumberRegister2Dim[1, 2]}");
}
}

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPublishable>false</IsPublishable>
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\MewtocolNet\MewtocolNet.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,90 @@
using MewtocolNet.Logging;
using MewtocolNet.Registers;
using MewtocolNet;
namespace Examples.Polling;
internal class Program {
const string PLC_IP = "192.168.178.55";
static void Main(string[] args) => Task.Run(AsyncMain).Wait();
static async Task AsyncMain() {
Console.Clear();
//the library provides a logging tool, comment this out if needed
Logger.LogLevel = LogLevel.Change;
//create a new interface to the plc using ethernet / tcp ip
using var plc = Mewtocol.Ethernet(PLC_IP)
.WithPoller() // <= use this in the builder pattern to automatically poll registers
.WithInterfaceSettings(s => {
//this means registers at the same address
//or that are contained by overlapping arrays
//always get assigned the same poll level
s.PollLevelOverwriteMode = PollLevelOverwriteMode.Highest;
//this means combine all registers that are not farther
//than 8 words apart from another into one tcp message,
//this safes message frames but a to large number can block read write traffic
s.MaxOptimizationDistance = 8;
})
.WithCustomPollLevels(l => {
//this makes registers at polllevel 2 only run all 3 iterations
l.SetLevel(2, 3);
//this makes registers at polllevel 3 only run all 5 seconds
l.SetLevel(3, TimeSpan.FromSeconds(5));
})
.WithRegisters(b => {
b.Struct<short>("DT1000").Build();
b.Struct<Word>("DT1000").Build();
b.Struct<ushort>("DT1001").Build();
b.Struct<Word>("DT999").AsArray(3).Build();
//we dont want to poll the string register as fast as we can
//so we assign it to level 2 to run only all 3 iterations
b.String("DT1024", 32).PollLevel(2).Build();
//we want to poll this array only at the first iteration,
//and after this only if we need the data
b.Struct<Word>("DT2000").AsArray(3).PollLevel(PollLevel.FirstIteration).Build();
//we want to poll this string array all 5 seconds
b.String("DT2030", 5).AsArray(3).PollLevel(3).Build();
//this is a fairly large array, so we never poll it automatically
//only if we need the data
b.Struct<short>("DT2003").AsArray(3, 3).PollLevel(PollLevel.Never).Build();
})
.Build();
//this explains the underlying data structure for the poller
Console.WriteLine(plc.Explain());
await plc.ConnectAsync(async () => {
//we want to restart the program before the poller starts
await plc.RestartProgramAsync();
});
if (!plc.IsConnected) {
Console.WriteLine("Failed to connect to the plc...");
Environment.Exit(1);
}
Console.ReadLine();
}
}

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPublishable>false</IsPublishable>
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\MewtocolNet\MewtocolNet.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,31 @@
using MewtocolNet;
using MewtocolNet.Logging;
namespace Examples.ProgramReadWrite;
internal class Program {
static void Main(string[] args) => Task.Run(AsyncMain).Wait();
//MewtocolNet.ProgramParsing.PlcBinaryProgram.ParseFromFile(@"C:\Users\fwe\Documents\sps\FPXH_C30_Test1.fp").AnalyzeProgram();
static async Task AsyncMain () {
Logger.LogLevel = LogLevel.Error;
using (var plc = Mewtocol.Ethernet("192.168.178.55").Build()) {
await plc.ConnectAsync();
var prog = await plc.ReadProgramAsync();
if (prog != null) {
prog.AnalyzeProgram();
}
}
}
}

View File

@@ -0,0 +1,11 @@
<Application x:Class="Examples.WPF.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Examples.WPF"
xmlns:conv="clr-namespace:Examples.WPF.Converters"
StartupUri="MainWindow.xaml">
<Application.Resources>
<conv:NegationConverter x:Key="bInv"/>
<conv:ColorHashConverter x:Key="hashColor"/>
</Application.Resources>
</Application>

View File

@@ -0,0 +1,85 @@
using Examples.WPF.ViewModels;
using MewtocolNet;
using MewtocolNet.Logging;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Navigation;
namespace Examples.WPF;
public partial class App : Application {
public static AppViewModel ViewModel { get; private set; } = null!;
public static new App Current = null!;
public static new MainWindow MainWindow = null!;
public static ObservableCollection<TextBlock> LoggerItems = null!;
internal static event Action? LogEventProcessed;
protected override void OnStartup(StartupEventArgs e) {
ViewModel = new AppViewModel();
Current = this;
LoggerItems = new();
Logger.LogLevel = LogLevel.Verbose;
Logger.DefaultTargets = LoggerTargets.Trace;
Logger.OnNewLogMessage((d, l, m) => {
Application.Current.Dispatcher.BeginInvoke(() => {
Brush msgColor = null!;
switch (l) {
case LogLevel.Error:
msgColor = Brushes.Red;
break;
case LogLevel.Change:
msgColor = Brushes.Blue;
break;
case LogLevel.Verbose:
msgColor = Brushes.Gold;
break;
case LogLevel.Critical:
msgColor = Brushes.Gray;
break;
}
if (LoggerItems.Count > 1000) LoggerItems.RemoveAt(0);
var contRun = msgColor == null ? new Run(m) : new Run(m) {
Foreground = msgColor,
};
LoggerItems.Add(new TextBlock {
Inlines = {
new Run($"[{d:hh:mm:ss:ff}] ") {
Foreground = Brushes.LimeGreen,
},
contRun
}
});
LogEventProcessed?.Invoke();
}, System.Windows.Threading.DispatcherPriority.Background);
});
}
}

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,62 @@
using System;
using System.Drawing;
using System.Globalization;
using System.Windows.Data;
namespace Examples.WPF.Converters;
[ValueConversion(typeof(bool), typeof(bool))]
public class ColorHashConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
var hashCode = value.GetHashCode();
var randColor = GenerateRandomVibrantColor(new Random(hashCode));
System.Windows.Media.Brush outBrush = new System.Windows.Media.SolidColorBrush(new System.Windows.Media.Color {
R = randColor.R,
G = randColor.G,
B = randColor.B,
A = 255,
});
return outBrush;
}
private Color GenerateRandomVibrantColor(Random random) {
byte red = (byte)random.Next(256);
byte green = (byte)random.Next(256);
byte blue = (byte)random.Next(256);
Color color = Color.FromArgb(255, red, green, blue);
// Ensure the color is vibrant and colorful
while (!IsVibrantColor(color)) {
red = (byte)random.Next(256);
green = (byte)random.Next(256);
blue = (byte)random.Next(256);
color = Color.FromArgb(255,red, green, blue);
}
return color;
}
private bool IsVibrantColor(Color color) {
int minBrightness = 100;
int maxBrightness = 200;
int minSaturation = 150;
int brightness = (int)(color.GetBrightness() * 255);
int saturation = (int)(color.GetSaturation() * 255);
return brightness >= minBrightness && brightness <= maxBrightness && saturation >= minSaturation;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException();
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace Examples.WPF.Converters;
[ValueConversion(typeof(bool), typeof(bool))]
public class NegationConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => value != null ? !(bool)value : false;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => value != null ? !(bool)value : false;
}

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPublishable>false</IsPublishable>
<IsPackable>false</IsPackable>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\MewtocolNet\MewtocolNet.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,169 @@
<Window x:Class="Examples.WPF.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:local="clr-namespace:Examples.WPF"
d:DataContext="{d:DesignInstance local:MainWindow, IsDesignTimeCreatable=True}"
mc:Ignorable="d"
MinWidth="500"
MinHeight="400"
Height="850"
Width="1200"
Title="MewtocolNet WPF Demo">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
<RowDefinition MinHeight="30" Height="150"/>
</Grid.RowDefinitions>
<ContentControl x:Name="mainContent"/>
<GridSplitter Grid.Row="1"
HorizontalAlignment="Stretch"
Background="Gray"
ShowsPreview="true"
Height="5">
<GridSplitter.Template>
<ControlTemplate>
<Separator/>
</ControlTemplate>
</GridSplitter.Template>
</GridSplitter>
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<Border Background="LightGray"/>
<StackPanel HorizontalAlignment="Left"
Orientation="Horizontal">
<TextBlock Text="Logger"
Margin="5"/>
<Border Width="1"
Margin="5"
Background="Gray"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Rx"
Margin="5"/>
<Ellipse IsEnabled="{Binding AppViewModel.Plc.IsReceiving, Mode=OneWay}"
Fill="Lime"
Width="10"
Height="10">
<Ellipse.Style>
<Style TargetType="Ellipse">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value=".1"/>
</Trigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<TextBlock Text="Tx"
Margin="5"/>
<Ellipse Fill="Orange"
IsEnabled="{Binding AppViewModel.Plc.IsSending, Mode=OneWay}"
Width="10"
Height="10">
<Ellipse.Style>
<Style TargetType="Ellipse">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value=".1"/>
</Trigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<Border Width="1"
Margin="5"
Background="Gray"/>
<TextBlock Text="{Binding AppViewModel.Plc.BytesPerSecondDownstream, Mode=OneWay}"
VerticalAlignment="Center"/>
<Border Width="1"
Margin="5"
Background="Gray"/>
<TextBlock Text="{Binding AppViewModel.Plc.BytesPerSecondUpstream, Mode=OneWay}"
VerticalAlignment="Center"/>
<Border Width="1"
Margin="5"
Background="Gray"/>
<TextBlock Text="{Binding AppViewModel.Plc.QueuedMessages, StringFormat='{}Q: {0}', Mode=OneWay}"
VerticalAlignment="Center"/>
<StackPanel.Style>
<Style TargetType="StackPanel">
<Style.Triggers>
<DataTrigger Binding="{Binding AppViewModel.PlcIsNull}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
</StackPanel>
</StackPanel>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Content="Expand"
Margin="2"/>
<ToggleButton Content="Autoscroll"
IsChecked="True"
Margin="2"
x:Name="autoScrollBtn"/>
</StackPanel>
<ListBox Grid.Row="1"
Background="Black"
Foreground="White"
BorderThickness="0"
VirtualizingPanel.IsVirtualizing="true"
VirtualizingPanel.VirtualizationMode="Recycling"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ItemsSource="{Binding LoggerItems, Mode=OneWay}"
x:Name="loggerList">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
<Border Background="Black"
Grid.Row="2">
<TextBlock Text=">"
Foreground="White"
Margin="5,0,0,0"/>
</Border>
</Grid>
</Grid>
</Window>

View File

@@ -0,0 +1,57 @@
using Examples.WPF.ViewModels;
using Examples.WPF.Views;
using MewtocolNet;
using MewtocolNet.Logging;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
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 Examples.WPF;
public partial class MainWindow : Window {
public ObservableCollection<TextBlock> LoggerItems => App.LoggerItems;
public AppViewModel AppViewModel => App.ViewModel;
public MainWindow() {
InitializeComponent();
this.DataContext = this;
App.MainWindow = this;
mainContent.Content = new ConnectView();
loggerList.PreviewMouseWheel += (s, e) => {
autoScrollBtn.IsChecked = false;
};
App.LogEventProcessed += () => {
Application.Current.Dispatcher.BeginInvoke(() => {
if (autoScrollBtn?.IsChecked != null && autoScrollBtn.IsChecked.Value)
loggerList.ScrollIntoView(App.LoggerItems.Last());
}, System.Windows.Threading.DispatcherPriority.Send);
};
}
}

View File

@@ -0,0 +1,34 @@
using MewtocolNet;
using MewtocolNet.RegisterAttributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Examples.WPF.RegisterCollections;
public class TestRegisterCollection : RegisterCollection {
[Register("R11A")]
public bool? TestR11A { get; set; }
[Register("R11A")]
public bool TestR11A_Duplicate_NonNullable { get; set; }
[Register("R16B")]
public bool TestR16B { get; set; }
[Register("R902")]
public bool Test { get; set; }
[BitRegister("DT1000", 0), PollLevel(3)]
public bool? TestDT100_Word_Duplicate_SingleBit { get; set; }
[Register("DT1000")]
public Word TestDT100_Word_Duplicate { get; set; }
[BitRegister("DDT1010", 1)]
public bool? TestDDT1010_DWord_Duplicate_SingleBit { get; set; }
}

View File

@@ -0,0 +1,40 @@
using Examples.WPF.RegisterCollections;
using MewtocolNet;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Examples.WPF.ViewModels {
public class AppViewModel : ViewModelBase {
private IPlc? plc;
private TestRegisterCollection testRegCollection = null!;
public bool PlcIsNull => plc == null;
public bool PlcIsNotNull => plc != null;
public IPlc? Plc {
get => plc;
set {
plc = value;
OnPropChange();
OnPropChange(nameof(PlcIsNull));
OnPropChange(nameof(PlcIsNotNull));
}
}
public TestRegisterCollection TestRegCollection {
get => testRegCollection;
set {
testRegCollection = value;
OnPropChange();
}
}
}
}

View File

@@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using System.Configuration.Internal;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Threading;
using MewtocolNet;
using MewtocolNet.ComCassette;
namespace Examples.WPF.ViewModels;
internal class ConnectViewViewModel : ViewModelBase {
private bool hasComports = false;
private IEnumerable<int> baudRates = null!;
private IEnumerable<string> comPorts = null!;
private IEnumerable<CassetteInformation> foundCassettes = null!;
private string selectedIP = "192.168.115.210";
private string selectedPort = "9094";
private bool isConnecting;
public IEnumerable<int> BaudRates {
get => baudRates;
set {
baudRates = value;
OnPropChange();
}
}
public IEnumerable<string> ComPorts {
get => comPorts;
set {
comPorts = value;
OnPropChange();
}
}
public IEnumerable<CassetteInformation> FoundCassettes {
get => foundCassettes;
set {
foundCassettes = value;
OnPropChange();
}
}
public bool HasComports {
get => hasComports;
set {
hasComports = value;
OnPropChange();
}
}
public string SelectedIP {
get { return selectedIP; }
set {
selectedIP = value;
OnPropChange();
}
}
public string SelectedPort {
get { return selectedPort; }
set {
selectedPort = value;
OnPropChange();
}
}
public bool IsConnecting {
get { return isConnecting; }
set {
isConnecting = value;
OnPropChange();
}
}
private DispatcherTimer tm;
public ConnectViewViewModel() {
BaudRates = Mewtocol.GetUseableBaudRates();
ScanTimerTick(null, null!);
tm = new DispatcherTimer {
Interval = TimeSpan.FromSeconds(3),
};
tm.Tick += ScanTimerTick;
tm.Start();
}
private async void ScanTimerTick(object? sender, EventArgs e) {
ComPorts = Mewtocol.GetSerialPortNames();
HasComports = ComPorts != null && ComPorts.Count() > 0;
var found = await CassetteFinder.FindClientsAsync(timeoutMs: 1000);
if (FoundCassettes == null || !Enumerable.SequenceEqual(found, FoundCassettes))
FoundCassettes = found;
}
internal void SelectedCassette (CassetteInformation cassette) {
SelectedIP = cassette.IPAddress.ToString();
SelectedPort = cassette.Port.ToString();
}
internal void EndTimer() => tm.Stop();
}

View File

@@ -0,0 +1,36 @@
using Examples.WPF.RegisterCollections;
using MewtocolNet;
using MewtocolNet.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Examples.WPF.ViewModels;
public class PlcDataViewViewModel : ViewModelBase {
private ReconnectArgs plcCurrentReconnectArgs = null!;
public IPlc Plc => App.ViewModel.Plc!;
public TestRegisterCollection RegCollection => App.ViewModel.TestRegCollection;
public ReconnectArgs PlcCurrentReconnectArgs {
get => plcCurrentReconnectArgs;
set {
plcCurrentReconnectArgs = value;
OnPropChange();
}
}
public PlcDataViewViewModel () {
Plc.ReconnectTryStarted += (s, e) => PlcCurrentReconnectArgs = e;
Plc.Reconnected += (s, e) => PlcCurrentReconnectArgs = null!;
Plc.Disconnected += (s, e) => PlcCurrentReconnectArgs = null!;
}
}

View File

@@ -0,0 +1,18 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Examples.WPF.ViewModels;
public abstract class ViewModelBase : INotifyPropertyChanged {
public event PropertyChangedEventHandler? PropertyChanged;
public void PropChange(string _name) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(_name));
}
protected void OnPropChange([CallerMemberName] string propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

View File

@@ -0,0 +1,129 @@
<UserControl x:Class="Examples.WPF.Views.ConnectView"
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:Examples.WPF.Views"
xmlns:vm="clr-namespace:Examples.WPF.ViewModels"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance vm:ConnectViewViewModel, IsDesignTimeCreatable=True}"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Margin="10">
<StackPanel>
<StackPanel.Resources>
<Style TargetType="TextBox">
<Setter Property="Padding" Value="5"/>
</Style>
</StackPanel.Resources>
<StackPanel>
<TextBlock Text="Connect to a PLC"
FontSize="24"/>
<Label Content="Set your connection type"/>
<ComboBox SelectedIndex="0" x:Name="conTypeCombo">
<ComboBoxItem>Ethernet</ComboBoxItem>
<ComboBoxItem IsEnabled="{Binding HasComports}">Serial</ComboBoxItem>
</ComboBox>
</StackPanel>
<Separator/>
<StackPanel MinWidth="200">
<TextBlock Text="Cassettes"/>
<DataGrid ItemsSource="{Binding FoundCassettes}"
SelectionChanged="SelectedCassette"
AutoGenerateColumns="False"
MinHeight="150"
MaxHeight="200"
IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" Width=".5*"/>
<DataGridTextColumn Header="IP" Binding="{Binding IPAddress}" Width=".3*"/>
<DataGridTextColumn Header="Port" Binding="{Binding Port}" Width=".3*"/>
<DataGridCheckBoxColumn Header="DHCP" Binding="{Binding UsesDHCP}" Width="auto"/>
<DataGridTextColumn Header="MAC" Binding="{Binding MacAddressStr, Mode=OneWay}" Width="auto"/>
<DataGridTextColumn Header="Status" Binding="{Binding Status}" Width="auto"/>
</DataGrid.Columns>
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<DataTrigger Binding="{Binding Port}" Value="0">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
</DataGrid>
<TextBlock Text="Connection"/>
<StackPanel Orientation="Horizontal">
<Label Content="IP Address"
VerticalAlignment="Center"/>
<TextBox Text="{Binding SelectedIP}"
VerticalAlignment="Center"/>
<Label Content="Port"
VerticalAlignment="Center"/>
<TextBox Text="{Binding SelectedPort}"
VerticalAlignment="Center"/>
<Button Content="Connect"
Click="ClickedConnectEth"
VerticalAlignment="Center"
Padding="5"
Margin="10,0,0,0">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnecting}">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedIndex, ElementName=conTypeCombo}" Value="0">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
</StackPanel>
<StackPanel MinWidth="200"
HorizontalAlignment="Left">
<StackPanel Orientation="Vertical">
<Label Content="COM Port"/>
<ComboBox ItemsSource="{Binding ComPorts}"
SelectedIndex="0"/>
<Label Content="BaudRate"/>
<ComboBox ItemsSource="{Binding BaudRates}"
SelectedIndex="0"/>
</StackPanel>
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedIndex, ElementName=conTypeCombo}" Value="1">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
</StackPanel>
</StackPanel>
</Grid>
</UserControl>

View File

@@ -0,0 +1,161 @@
using Examples.WPF.RegisterCollections;
using Examples.WPF.ViewModels;
using MewtocolNet;
using MewtocolNet.ComCassette;
using MewtocolNet.Logging;
using MewtocolNet.Registers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
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 Examples.WPF.Views;
/// <summary>
/// Interaktionslogik für ConnectView.xaml
/// </summary>
public partial class ConnectView : UserControl {
private ConnectViewViewModel viewModel;
public ConnectView() {
InitializeComponent();
viewModel = new ConnectViewViewModel();
this.DataContext = viewModel;
Unloaded += (s, e) => viewModel.EndTimer();
}
private void SelectedCassette(object sender, SelectionChangedEventArgs e) {
var cassette = (CassetteInformation)((DataGrid)sender).SelectedItem;
if (cassette == null) return;
viewModel.SelectedCassette(cassette);
}
private void ClickedConnectEth(object sender, RoutedEventArgs e) {
Application.Current.Dispatcher.BeginInvoke(async () => {
viewModel.IsConnecting = true;
var parsedInt = int.Parse(viewModel.SelectedPort);
IRegister<short> heartbeatSetter = null!;
IRegister<bool> outputContactReference = null!;
IRegister<bool> testBoolReference = null!;
IRegister<Word> wordRefTest = null!;
//build a new interface
App.ViewModel.Plc = Mewtocol.Ethernet(viewModel.SelectedIP, parsedInt)
.WithPoller()
.WithInterfaceSettings(setting => {
setting.TryReconnectAttempts = 10;
setting.TryReconnectDelayMs = 2000;
setting.SendReceiveTimeoutMs = 1000;
setting.HeartbeatIntervalMs = 3000;
setting.MaxDataBlocksPerWrite = 128;
setting.MaxOptimizationDistance = 20;
})
.WithCustomPollLevels(lvl => {
lvl.SetLevel(2, 3);
lvl.SetLevel(3, TimeSpan.FromSeconds(5));
lvl.SetLevel(4, TimeSpan.FromSeconds(10));
})
.WithRegisterCollections(collector => {
App.ViewModel.TestRegCollection = collector.AddCollection<TestRegisterCollection>();
})
.WithRegisters(b => {
b.Bool("X4").Build();
b.Bool("Y4").Build(out outputContactReference);
b.Bool("R10A").PollLevel(PollLevel.FirstIteration).Build(out testBoolReference);
b.Struct<short>("DT1000").Build(out heartbeatSetter);
//these will be merged into one
b.Struct<Word>("DT1000").Build(out wordRefTest);
b.Struct<Word>("DT1000").Build(out wordRefTest);
b.Struct<ushort>("DT1001").PollLevel(2).Build();
b.Struct<Word>("DT1002").PollLevel(2).Build();
b.Struct<int>("DDT1010").PollLevel(2).Build();
b.Struct<uint>("DDT1012").PollLevel(2).Build();
b.Struct<DWord>("DDT1014").PollLevel(2).Build();
b.Struct<float>("DDT1016").PollLevel(2).Build();
b.Struct<TimeSpan>("DDT1018").PollLevel(2).Build();
b.Struct<DateAndTime>("DDT1020").PollLevel(2).Build();
b.Struct<DateAndTime>("DDT1022").PollLevel(2).Build();
b.String("DT1028", 32).PollLevel(3).Build();
b.String("DT1046", 5).PollLevel(4).Build();
b.Struct<Word>("DT1000").AsArray(5).PollLevel(1).Build();
})
.WithHeartbeatTask(async (plc) => {
var randShort = (short)new Random().Next(short.MinValue, short.MaxValue);
//write direct
//await heartbeatSetter.WriteAsync(randShort);
//or by anonymous
await plc.Register.Struct<short>("DT1000").WriteAsync(randShort);
//write a register without a reference
bool randBool = new Random().Next(0, 2) == 1;
await plc.Register.Bool("Y4").WriteAsync(randBool);
if (testBoolReference.Value != null)
await testBoolReference.WriteAsync(!testBoolReference.Value.Value);
await plc.Register.Struct<DateAndTime>("DDT1022").WriteAsync(DateAndTime.FromDateTime(DateTime.UtcNow));
})
.Build();
//connect to it
await App.ViewModel.Plc.ConnectAsync(async () => {
await App.ViewModel.Plc.RestartProgramAsync();
});
await App.ViewModel.Plc.AwaitFirstDataCycleAsync();
if (App.ViewModel.Plc.IsConnected) {
App.MainWindow.mainContent.Content = new PlcDataView();
}
viewModel.IsConnecting = false;
}, System.Windows.Threading.DispatcherPriority.Send);
}
}

View File

@@ -0,0 +1,350 @@
<UserControl x:Class="Examples.WPF.Views.PlcDataView"
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:vm="clr-namespace:Examples.WPF.ViewModels"
xmlns:local="clr-namespace:Examples.WPF.Views"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance vm:PlcDataViewViewModel, IsDesignTimeCreatable=True}"
d:DesignHeight="450"
d:DesignWidth="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Menu>
<MenuItem Header="PLC">
<MenuItem Header="Disconnect" IsEnabled="{Binding Plc.IsConnected}"
Click="ClickedDisconnect"/>
<MenuItem Header="Connect" IsEnabled="{Binding Plc.IsConnected, Converter={StaticResource bInv}}"
Click="ClickedConnect"/>
<MenuItem Header="Set Random DT1000" IsEnabled="{Binding Plc.IsConnected}"
Click="ClickedSetRandom"/>
<MenuItem Header="Queue test" IsEnabled="{Binding Plc.IsConnected}"
Click="ClickedAddQueueTest"/>
<MenuItem Header="Toggle OP mode" IsEnabled="{Binding Plc.IsConnected}"
Click="ClickedToggleRunMode"/>
</MenuItem>
</Menu>
<StackPanel Margin="10"
Grid.Row="1">
<TextBlock IsEnabled="{Binding Plc.IsConnected}">
<Run Text="{Binding Plc.PlcInfo.TypeName, Mode=OneWay}"
FontSize="24"
BaselineAlignment="Center"
FontWeight="SemiBold"/>
<Run Text="{Binding Plc.PlcInfo.CpuVersion, StringFormat='v{0}', Mode=OneWay}"
FontSize="24"
FontWeight="Light"/>
<Ellipse Width="10"
Height="10"
Fill="Lime"
IsEnabled="{Binding Plc.IsConnected}"/>
<Run>
<Run.Style>
<Style TargetType="Run">
<Style.Triggers>
<DataTrigger Binding="{Binding Plc.IsRunMode, Mode=OneWay}" Value="True">
<Setter Property="Text" Value="RUN MODE"/>
</DataTrigger>
<DataTrigger Binding="{Binding Plc.IsRunMode, Mode=OneWay}" Value="False">
<Setter Property="Text" Value="NO RUN MODE"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Run.Style>
</Run>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock IsEnabled="{Binding Plc.IsConnected, Converter={StaticResource bInv}}">
<Run Text="Disconnected"
FontSize="24"
BaselineAlignment="Center"
FontWeight="SemiBold"/>
<Ellipse Width="10"
Height="10"
Fill="Red"
IsEnabled="{Binding Plc.IsConnected}"/>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text="{Binding Plc.PlcInfo.TypeCode, StringFormat='#{0:X}', Mode=OneWay}"
FontSize="18"/>
<ItemsControl ItemsSource="{Binding Plc.PlcInfo.OperationModeTags, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="LightGray"
CornerRadius="5"
Margin="2"
Padding="5">
<TextBlock Text="{Binding}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl ItemsSource="{Binding Plc.PlcInfo.HardwareInformationTags, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="LightGray"
CornerRadius="5"
Margin="2"
Padding="5">
<TextBlock Text="{Binding}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock>
<Run Text="Reconnecting..."/>
<Run Text="{Binding PlcCurrentReconnectArgs.ReconnectTry, Mode=OneWay, StringFormat='{}{0}/'}"/>
<Run Text="{Binding PlcCurrentReconnectArgs.MaxAttempts, Mode=OneWay}"/> in
<Run Text="{Binding PlcCurrentReconnectArgs.RetryCountDownRemaining, Mode=OneWay}"/>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding PlcCurrentReconnectArgs, Mode=OneWay}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1.5*"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<GridSplitter Grid.Column="1"
Grid.Row="1"
Grid.RowSpan="3"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Background="Gray"
ShowsPreview="true"
Width="10">
<GridSplitter.Template>
<ControlTemplate>
<Rectangle Fill="Gray"
Width="2"
Margin="2"/>
</ControlTemplate>
</GridSplitter.Template>
</GridSplitter>
<TextBlock Text="Underlying Registers"
FontSize="18"
Margin="10"/>
<DataGrid Grid.Row="1"
Grid.RowSpan="3"
AutoGenerateColumns="False"
IsReadOnly="True"
ItemsSource="{Binding Plc.Registers, Mode=OneWay}">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding IsAutoGenerated}" Value="True">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="FP Address" Binding="{Binding PLCAddressName}"/>
<DataGridTextColumn Header="Type" Binding="{Binding UnderlyingSystemType.Name}"/>
<DataGridTemplateColumn Header="Value" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border HorizontalAlignment="Left"
Padding="2,0">
<TextBlock Text="{Binding ValueStr}"/>
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding ValueStr}" Value="True">
<Setter Property="Background" Value="Blue"/>
<Setter Property="TextElement.Foreground" Value="White"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Poll Level" Binding="{Binding PollLevel, Mode=OneWay}"/>
<DataGridTextColumn Header="Update Frequency" Binding="{Binding UpdateFreqHz, StringFormat='{}{0} Hz',Mode=OneWay}"/>
<DataGridTemplateColumn Width="15">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border Background="{Binding MemoryArea, Mode=OneWay, Converter={StaticResource hashColor}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<TextBlock Text="Memory Areas"
Grid.Column="2"
FontSize="18"
Margin="10"/>
<Border Grid.Column="2"
Grid.Row="1"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
BorderBrush="LightBlue"
BorderThickness="1.5">
<DataGrid IsReadOnly="True"
AutoGenerateColumns="False"
ItemsSource="{Binding Plc.MemoryAreas, Mode=OneWay}">
<DataGrid.Columns>
<DataGridTextColumn Header="Address Range" Binding="{Binding AddressRange, Mode=OneWay}"/>
<DataGridTemplateColumn Width="15">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border Background="{Binding Converter={StaticResource hashColor}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Words" Binding="{Binding UnderlyingWordsString, Mode=OneWay}"/>
</DataGrid.Columns>
</DataGrid>
</Border>
<TextBlock Text="Property Bindings"
Grid.Column="2"
Grid.Row="2"
FontSize="18"
Margin="10"/>
<Border Grid.Column="2"
Grid.Row="3"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
BorderBrush="LightBlue"
BorderThickness="1.5">
<ScrollViewer>
<StackPanel>
<TextBlock Text="Boolean internal relays"/>
<TextBlock>
<Run Text="R11A Nullable property: "/>
<Run Text="{Binding RegCollection.TestR11A}"
FontWeight="Bold"/>
</TextBlock>
<TextBlock>
<Run Text="R11A Duplicate non nullable property: "/>
<Run Text="{Binding RegCollection.TestR11A_Duplicate_NonNullable}"
FontWeight="Bold"/>
</TextBlock>
<TextBlock>
<Run Text="R16B"/>
<Run Text="{Binding RegCollection.TestR16B}"
FontWeight="Bold"/>
</TextBlock>
<TextBlock>
<Run Text="DT1000 Word duplicate: "/>
<Run Text="{Binding RegCollection.TestDT100_Word_Duplicate, Mode=OneWay}"
FontWeight="Bold"/>
</TextBlock>
<TextBlock>
<Run Text="DT1000 Word direct bit read (Idx 0): "/>
<Run Text="{Binding RegCollection.TestDT100_Word_Duplicate_SingleBit, Mode=OneWay}"
FontWeight="Bold"/>
</TextBlock>
<TextBlock>
<Run Text="DDT1010 DWord direct bit read (Idx 1): "/>
<Run Text="{Binding RegCollection.TestDDT1010_DWord_Duplicate_SingleBit, Mode=OneWay}"
FontWeight="Bold"/>
</TextBlock>
</StackPanel>
</ScrollViewer>
</Border>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,81 @@
using Examples.WPF.ViewModels;
using MewtocolNet.Registers;
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 Examples.WPF.Views;
public partial class PlcDataView : UserControl {
private PlcDataViewViewModel viewModel;
public PlcDataView() {
InitializeComponent();
viewModel = new PlcDataViewViewModel();
this.DataContext = viewModel;
}
private void ClickedDisconnect(object sender, RoutedEventArgs e) {
viewModel.Plc.Disconnect();
}
private async void ClickedConnect(object sender, RoutedEventArgs e) {
await viewModel.Plc.ConnectAsync();
}
private async void ClickedSetRandom(object sender, RoutedEventArgs e) {
var reg = (IRegister<ushort>?)viewModel.Plc.GetAllRegisters()?.FirstOrDefault(x => x.PLCAddressName == "DT1001");
if(reg != null) {
await reg.WriteAsync((ushort)new Random().Next(ushort.MinValue, ushort.MaxValue));
}
}
private async void ClickedAddQueueTest(object sender, RoutedEventArgs e) {
var tasks = new List<Task<short>>();
for (int i = 0; i < 100; i++) {
var t = viewModel.Plc.Register.Struct<short>("DT1000").ReadAsync();
tasks.Add(t);
}
var list = await Task.WhenAll(tasks);
Console.WriteLine();
}
private async void ClickedToggleRunMode(object sender, RoutedEventArgs e) {
await viewModel.Plc.ToggleOperationModeAsync();
}
}