mirror of
https://github.com/OpenLogics/MewtocolNet.git
synced 2025-12-06 03:01:24 +00:00
Fix multiple poller situational problems
This commit is contained in:
@@ -32,7 +32,7 @@ Console.WriteLine($"{filePath}");
|
|||||||
|
|
||||||
StringBuilder markdownBuilder = new StringBuilder();
|
StringBuilder markdownBuilder = new StringBuilder();
|
||||||
|
|
||||||
var plcNames = Enum.GetNames<PlcType>().OrderBy(x => x).ToArray();
|
var plcNames = Enum.GetNames<PlcType>().Where(x => x != PlcType.Unknown.ToString()).OrderBy(x => x).ToArray();
|
||||||
|
|
||||||
void WritePlcTypeTable(string[] names) {
|
void WritePlcTypeTable(string[] names) {
|
||||||
|
|
||||||
|
|||||||
@@ -67,9 +67,6 @@ internal class Program {
|
|||||||
|
|
||||||
plc.ConfigureConnection("192.168.178.55", 9094);
|
plc.ConfigureConnection("192.168.178.55", 9094);
|
||||||
await plc.ConnectAsync();
|
await plc.ConnectAsync();
|
||||||
//await plc.SendCommandAsync($"%EE#RR0000100");
|
|
||||||
//await plc.SendCommandAsync($"%EE#RCCR09030903");
|
|
||||||
await plc.SendCommandAsync($"%EE#RP0000000067");
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
9
Examples.WPF/App.xaml
Normal file
9
Examples.WPF/App.xaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<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"
|
||||||
|
StartupUri="MainWindow.xaml">
|
||||||
|
<Application.Resources>
|
||||||
|
|
||||||
|
</Application.Resources>
|
||||||
|
</Application>
|
||||||
85
Examples.WPF/App.xaml.cs
Normal file
85
Examples.WPF/App.xaml.cs
Normal 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);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
10
Examples.WPF/AssemblyInfo.cs
Normal file
10
Examples.WPF/AssemblyInfo.cs
Normal 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)
|
||||||
|
)]
|
||||||
14
Examples.WPF/Examples.WPF.csproj
Normal file
14
Examples.WPF/Examples.WPF.csproj
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net7.0-windows</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\MewtocolNet\MewtocolNet.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
162
Examples.WPF/MainWindow.xaml
Normal file
162
Examples.WPF/MainWindow.xaml
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
<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="600"
|
||||||
|
Width="800"
|
||||||
|
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"/>
|
||||||
|
|
||||||
|
<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>
|
||||||
57
Examples.WPF/MainWindow.xaml.cs
Normal file
57
Examples.WPF/MainWindow.xaml.cs
Normal 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);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
30
Examples.WPF/ViewModels/AppViewModel.cs
Normal file
30
Examples.WPF/ViewModels/AppViewModel.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
117
Examples.WPF/ViewModels/ConnectViewViewModel.cs
Normal file
117
Examples.WPF/ViewModels/ConnectViewViewModel.cs
Normal 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();
|
||||||
|
|
||||||
|
}
|
||||||
14
Examples.WPF/ViewModels/PlcDataViewViewModel.cs
Normal file
14
Examples.WPF/ViewModels/PlcDataViewViewModel.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using MewtocolNet;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Examples.WPF.ViewModels;
|
||||||
|
|
||||||
|
public class PlcDataViewViewModel : ViewModelBase {
|
||||||
|
|
||||||
|
public IPlc Plc => App.ViewModel.Plc!;
|
||||||
|
|
||||||
|
}
|
||||||
18
Examples.WPF/ViewModels/ViewModelBase.cs
Normal file
18
Examples.WPF/ViewModels/ViewModelBase.cs
Normal 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
129
Examples.WPF/Views/ConnectView.xaml
Normal file
129
Examples.WPF/Views/ConnectView.xaml
Normal 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>
|
||||||
78
Examples.WPF/Views/ConnectView.xaml.cs
Normal file
78
Examples.WPF/Views/ConnectView.xaml.cs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
using Examples.WPF.ViewModels;
|
||||||
|
using MewtocolNet;
|
||||||
|
using MewtocolNet.ComCassette;
|
||||||
|
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);
|
||||||
|
|
||||||
|
App.ViewModel.Plc = Mewtocol.Ethernet(viewModel.SelectedIP, parsedInt)
|
||||||
|
.WithPoller()
|
||||||
|
.WithRegisters(b => {
|
||||||
|
b.Struct<short>("DT0").Build();
|
||||||
|
b.Struct<short>("DT0").AsArray(30).Build();
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
await App.ViewModel.Plc.ConnectAsync();
|
||||||
|
|
||||||
|
if (App.ViewModel.Plc.IsConnected) {
|
||||||
|
|
||||||
|
App.MainWindow.mainContent.Content = new PlcDataView();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.IsConnecting = false;
|
||||||
|
|
||||||
|
}, System.Windows.Threading.DispatcherPriority.Send);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
76
Examples.WPF/Views/PlcDataView.xaml
Normal file
76
Examples.WPF/Views/PlcDataView.xaml
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<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 Margin="10">
|
||||||
|
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="auto"/>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<StackPanel>
|
||||||
|
|
||||||
|
<TextBlock>
|
||||||
|
|
||||||
|
<Run Text="{Binding Plc.PlcInfo.TypeName, Mode=OneWay}"
|
||||||
|
FontSize="24"
|
||||||
|
FontWeight="SemiBold"/>
|
||||||
|
|
||||||
|
<Run Text="{Binding Plc.PlcInfo.CpuVersion, StringFormat='v{0}', Mode=OneWay}"
|
||||||
|
FontSize="24"
|
||||||
|
FontWeight="Light"/>
|
||||||
|
|
||||||
|
</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>
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
32
Examples.WPF/Views/PlcDataView.xaml.cs
Normal file
32
Examples.WPF/Views/PlcDataView.xaml.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using Examples.WPF.ViewModels;
|
||||||
|
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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -23,7 +23,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoTools.ChmDataExtract",
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoTools.DocBuilder", "AutoTools.DocBuilder\AutoTools.DocBuilder.csproj", "{00ACA0AB-3988-4EF7-98A6-B39A36B136DA}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoTools.DocBuilder", "AutoTools.DocBuilder\AutoTools.DocBuilder.csproj", "{00ACA0AB-3988-4EF7-98A6-B39A36B136DA}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Examples.ProgramReadWrite", "Examples.ProgramReadWrite\Examples.ProgramReadWrite.csproj", "{51BDABAA-05B0-4802-AA37-243DAE22D5DC}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.ProgramReadWrite", "Examples.ProgramReadWrite\Examples.ProgramReadWrite.csproj", "{51BDABAA-05B0-4802-AA37-243DAE22D5DC}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Examples.WPF", "Examples.WPF\Examples.WPF.csproj", "{C8A486EA-6054-4B77-859E-BFEEA93658CF}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
@@ -143,6 +145,18 @@ Global
|
|||||||
{51BDABAA-05B0-4802-AA37-243DAE22D5DC}.Release|x64.Build.0 = Release|Any CPU
|
{51BDABAA-05B0-4802-AA37-243DAE22D5DC}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{51BDABAA-05B0-4802-AA37-243DAE22D5DC}.Release|x86.ActiveCfg = Release|Any CPU
|
{51BDABAA-05B0-4802-AA37-243DAE22D5DC}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{51BDABAA-05B0-4802-AA37-243DAE22D5DC}.Release|x86.Build.0 = Release|Any CPU
|
{51BDABAA-05B0-4802-AA37-243DAE22D5DC}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{C8A486EA-6054-4B77-859E-BFEEA93658CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C8A486EA-6054-4B77-859E-BFEEA93658CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C8A486EA-6054-4B77-859E-BFEEA93658CF}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{C8A486EA-6054-4B77-859E-BFEEA93658CF}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{C8A486EA-6054-4B77-859E-BFEEA93658CF}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{C8A486EA-6054-4B77-859E-BFEEA93658CF}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{C8A486EA-6054-4B77-859E-BFEEA93658CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C8A486EA-6054-4B77-859E-BFEEA93658CF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C8A486EA-6054-4B77-859E-BFEEA93658CF}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{C8A486EA-6054-4B77-859E-BFEEA93658CF}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{C8A486EA-6054-4B77-859E-BFEEA93658CF}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{C8A486EA-6054-4B77-859E-BFEEA93658CF}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -154,6 +168,7 @@ Global
|
|||||||
{5A9DE453-AD64-4F8D-8215-3BB26674D164} = {BAEF983A-EFF2-48DF-A74E-57084166BB4D}
|
{5A9DE453-AD64-4F8D-8215-3BB26674D164} = {BAEF983A-EFF2-48DF-A74E-57084166BB4D}
|
||||||
{00ACA0AB-3988-4EF7-98A6-B39A36B136DA} = {BAEF983A-EFF2-48DF-A74E-57084166BB4D}
|
{00ACA0AB-3988-4EF7-98A6-B39A36B136DA} = {BAEF983A-EFF2-48DF-A74E-57084166BB4D}
|
||||||
{51BDABAA-05B0-4802-AA37-243DAE22D5DC} = {323729B0-5FB2-4592-9FA6-220C46BBF84C}
|
{51BDABAA-05B0-4802-AA37-243DAE22D5DC} = {323729B0-5FB2-4592-9FA6-220C46BBF84C}
|
||||||
|
{C8A486EA-6054-4B77-859E-BFEEA93658CF} = {323729B0-5FB2-4592-9FA6-220C46BBF84C}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {4ABB8137-CD8F-4691-9802-9ED371012F47}
|
SolutionGuid = {4ABB8137-CD8F-4691-9802-9ED371012F47}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using MewtocolNet.Logging;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
@@ -16,6 +17,8 @@ namespace MewtocolNet.ComCassette {
|
|||||||
|
|
||||||
public static async Task<IEnumerable<CassetteInformation>> FindClientsAsync(string ipSource = null, int timeoutMs = 100) {
|
public static async Task<IEnumerable<CassetteInformation>> FindClientsAsync(string ipSource = null, int timeoutMs = 100) {
|
||||||
|
|
||||||
|
Logger.Log("Scanning for cassettes over UDP");
|
||||||
|
|
||||||
var from = new IPEndPoint(IPAddress.Any, 0);
|
var from = new IPEndPoint(IPAddress.Any, 0);
|
||||||
|
|
||||||
var interfacesTasks = new List<Task<List<CassetteInformation>>>();
|
var interfacesTasks = new List<Task<List<CassetteInformation>>>();
|
||||||
@@ -67,7 +70,9 @@ namespace MewtocolNet.ComCassette {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return decomposed;
|
Logger.Log($"Found {decomposed.Count} cassettes");
|
||||||
|
|
||||||
|
return decomposed.OrderBy(x => x.IPAddress.ToString());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,11 @@ namespace MewtocolNet.ComCassette {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public byte[] MacAddress { get; private set; }
|
public byte[] MacAddress { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mac address of the cassette formatted as a MAC string (XX:XX:XX:XX:XX)
|
||||||
|
/// </summary>
|
||||||
|
public string MacAddressStr => MacAddress.ToHexString(":");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The source endpoint the cassette is reachable from
|
/// The source endpoint the cassette is reachable from
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -177,6 +182,38 @@ namespace MewtocolNet.ComCassette {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(CassetteInformation a, CassetteInformation b) => EqualProps(a, b);
|
||||||
|
|
||||||
|
public static bool operator !=(CassetteInformation a, CassetteInformation b) => !EqualProps(a, b);
|
||||||
|
|
||||||
|
private static bool EqualProps (CassetteInformation a, CassetteInformation b) {
|
||||||
|
|
||||||
|
if (a is null && b is null) return true;
|
||||||
|
if (!(a is null) && b is null) return false;
|
||||||
|
if (!(b is null) && a is null) return false;
|
||||||
|
|
||||||
|
return a.Name == b.Name &&
|
||||||
|
a.UsesDHCP == b.UsesDHCP &&
|
||||||
|
a.IPAddress.ToString() == b.IPAddress.ToString() &&
|
||||||
|
a.SubnetMask.ToString() == b.SubnetMask.ToString() &&
|
||||||
|
a.GatewayAddress.ToString() == b.GatewayAddress.ToString() &&
|
||||||
|
a.MacAddressStr == b.MacAddressStr &&
|
||||||
|
a.FirmwareVersion == b.FirmwareVersion &&
|
||||||
|
a.Port == b.Port &&
|
||||||
|
a.Status == b.Status;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj) {
|
||||||
|
|
||||||
|
if ((obj == null) || !this.GetType().Equals(obj.GetType())) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return (CassetteInformation)obj == this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace MewtocolNet.Helpers {
|
namespace MewtocolNet.DataLists {
|
||||||
|
|
||||||
internal class CodeDescriptions {
|
internal class CodeDescriptions {
|
||||||
|
|
||||||
@@ -159,6 +159,40 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Splits a string by uppercase words
|
||||||
|
/// </summary>
|
||||||
|
internal static IEnumerable<string> SplitByAlternatingCase(this string str) {
|
||||||
|
|
||||||
|
var words = new List<string>();
|
||||||
|
var result = new StringBuilder();
|
||||||
|
|
||||||
|
for (int i = 0; i < str.Length; i++) {
|
||||||
|
|
||||||
|
char lastCh = str[Math.Max(0, i - 1)];
|
||||||
|
char ch = str[i];
|
||||||
|
|
||||||
|
if (char.IsUpper(ch) && char.IsLower(lastCh) && result.Length > 0) {
|
||||||
|
words.Add(result.ToString().Trim());
|
||||||
|
result.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Append(ch);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(result.ToString()))
|
||||||
|
words.Add(result.ToString().Trim());
|
||||||
|
|
||||||
|
return words;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Splits a string by uppercase words and joins them with the given seperator
|
||||||
|
/// </summary>
|
||||||
|
internal static string JoinSplitByUpperCase(this string str, string seperator = " ") => string.Join(seperator, str.SplitByAlternatingCase());
|
||||||
|
|
||||||
internal static string Ellipsis(this string str, int maxLength) {
|
internal static string Ellipsis(this string str, int maxLength) {
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(str) || str.Length <= maxLength)
|
if (string.IsNullOrEmpty(str) || str.Length <= maxLength)
|
||||||
@@ -366,7 +400,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
|
||||||
internal static bool WasTestedLive(this PlcType plcT) {
|
internal static bool WasTestedLive(this PlcType plcT) {
|
||||||
|
|
||||||
@@ -396,7 +430,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -444,6 +478,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -14,12 +14,12 @@ namespace MewtocolNet {
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whole name of the PLC
|
/// Whole name of the PLC
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string WholeName { get; internal set; }
|
public string WholeName { get; internal set; } = "Unknown PLC";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The family group of the PLC
|
/// The family group of the PLC
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Group { get; internal set; }
|
public string Group { get; internal set; } = "Unknown Group";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Memory size of the PLC
|
/// The Memory size of the PLC
|
||||||
@@ -29,17 +29,17 @@ namespace MewtocolNet {
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The subtype strings of the plc
|
/// The subtype strings of the plc
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string[] SubTypes { get; internal set; }
|
public string[] SubTypes { get; internal set; } = new string[] { "N/A" };
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Typecode of the parsed string
|
/// Typecode of the parsed string
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int TypeCode { get; internal set; }
|
public int TypeCode { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The encoded name, same as enum name
|
/// The encoded name, same as enum name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string EncodedName { get; internal set; }
|
public string EncodedName { get; internal set; } = "Unknown";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// True if the model is discontinued
|
/// True if the model is discontinued
|
||||||
@@ -55,6 +55,10 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
internal static ParsedPlcName PlcDeconstruct(string wholeStr) {
|
internal static ParsedPlcName PlcDeconstruct(string wholeStr) {
|
||||||
|
|
||||||
|
if(wholeStr == "Unknown") {
|
||||||
|
return new ParsedPlcName();
|
||||||
|
}
|
||||||
|
|
||||||
var reg = new Regex(@"(?<group>[A-Za-z0-9]*)_(?<size>[A-Za-z0-9]*)(?:__)?(?<additional>.*)");
|
var reg = new Regex(@"(?<group>[A-Za-z0-9]*)_(?<size>[A-Za-z0-9]*)(?:__)?(?<additional>.*)");
|
||||||
var match = reg.Match(wholeStr);
|
var match = reg.Match(wholeStr);
|
||||||
|
|
||||||
|
|||||||
@@ -18,11 +18,21 @@ namespace MewtocolNet {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
bool IsConnected { get; }
|
bool IsConnected { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This device is sending a message to the plc
|
||||||
|
/// </summary>
|
||||||
|
bool IsSending { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current transmission speed in bytes per second
|
/// The current transmission speed in bytes per second
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int BytesPerSecondUpstream { get; }
|
int BytesPerSecondUpstream { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This device is receiving a message from the plc
|
||||||
|
/// </summary>
|
||||||
|
bool IsReceiving { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current transmission speed in bytes per second
|
/// The current transmission speed in bytes per second
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -22,9 +22,11 @@ namespace MewtocolNet.Logging {
|
|||||||
|
|
||||||
static Logger () {
|
static Logger () {
|
||||||
|
|
||||||
|
var isConsoleApplication = Console.LargestWindowWidth != 0;
|
||||||
|
|
||||||
OnNewLogMessage((d, l, m) => {
|
OnNewLogMessage((d, l, m) => {
|
||||||
|
|
||||||
if(DefaultTargets.HasFlag(LoggerTargets.Console)) {
|
if(isConsoleApplication && DefaultTargets.HasFlag(LoggerTargets.Console)) {
|
||||||
|
|
||||||
switch (l) {
|
switch (l) {
|
||||||
case LogLevel.Error:
|
case LogLevel.Error:
|
||||||
@@ -88,5 +90,15 @@ namespace MewtocolNet.Logging {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static void LogError (string message, MewtocolInterface sender = null) => Log(message, LogLevel.Error, sender);
|
||||||
|
|
||||||
|
internal static void Log (string message, MewtocolInterface sender = null) => Log(message, LogLevel.Info, sender);
|
||||||
|
|
||||||
|
internal static void LogChange (string message, MewtocolInterface sender = null) => Log(message, LogLevel.Change, sender);
|
||||||
|
|
||||||
|
internal static void LogVerbose (string message, MewtocolInterface sender = null) => Log(message, LogLevel.Verbose, sender);
|
||||||
|
|
||||||
|
internal static void LogCritical (string message, MewtocolInterface sender = null) => Log(message, LogLevel.Critical, sender);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,72 @@ namespace MewtocolNet
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class Mewtocol {
|
public static class Mewtocol {
|
||||||
|
|
||||||
#region Build Order 1
|
#region Data Access
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lists all usable COM port names
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IEnumerable<string> GetSerialPortNames () => SerialPort.GetPortNames();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lists all usable serial baud rates
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IEnumerable<int> GetUseableBaudRates() => Enum.GetValues(typeof(BaudRate)).Cast<BaudRate>().Select(x => (int)x);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lists all useable source endpoints of the device this is running on for usage with PLCs
|
||||||
|
/// </summary>
|
||||||
|
public static IEnumerable<IPEndPoint> GetSourceEndpoints() {
|
||||||
|
|
||||||
|
foreach (var netIf in GetUseableNetInterfaces()) {
|
||||||
|
|
||||||
|
var addressInfo = netIf.GetIPProperties().UnicastAddresses
|
||||||
|
.FirstOrDefault(x => x.Address.AddressFamily == AddressFamily.InterNetwork);
|
||||||
|
|
||||||
|
yield return new IPEndPoint(addressInfo.Address, 9094);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lists all useable network interfaces of the device this is running on for usage with PLCs
|
||||||
|
/// </summary>
|
||||||
|
public static IEnumerable<NetworkInterface> GetUseableNetInterfaces() {
|
||||||
|
|
||||||
|
foreach (NetworkInterface netInterface in NetworkInterface.GetAllNetworkInterfaces()) {
|
||||||
|
|
||||||
|
bool isEthernet =
|
||||||
|
netInterface.NetworkInterfaceType == NetworkInterfaceType.Ethernet ||
|
||||||
|
netInterface.NetworkInterfaceType == NetworkInterfaceType.Ethernet3Megabit ||
|
||||||
|
netInterface.NetworkInterfaceType == NetworkInterfaceType.FastEthernetFx ||
|
||||||
|
netInterface.NetworkInterfaceType == NetworkInterfaceType.FastEthernetT ||
|
||||||
|
netInterface.NetworkInterfaceType == NetworkInterfaceType.GigabitEthernet;
|
||||||
|
|
||||||
|
bool isWlan = netInterface.NetworkInterfaceType == NetworkInterfaceType.Wireless80211;
|
||||||
|
|
||||||
|
bool isUsable = netInterface.OperationalStatus == OperationalStatus.Up;
|
||||||
|
|
||||||
|
if (!isUsable) continue;
|
||||||
|
if (!(isWlan || isEthernet)) continue;
|
||||||
|
|
||||||
|
IPInterfaceProperties ipProps = netInterface.GetIPProperties();
|
||||||
|
var hasUnicastInfo = ipProps.UnicastAddresses
|
||||||
|
.Any(x => x.Address.AddressFamily == AddressFamily.InterNetwork);
|
||||||
|
|
||||||
|
if (!hasUnicastInfo) continue;
|
||||||
|
|
||||||
|
yield return netInterface;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Interface building step 1
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Builds a ethernet based Mewtocol Interface
|
/// Builds a ethernet based Mewtocol Interface
|
||||||
@@ -92,58 +157,9 @@ namespace MewtocolNet
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Lists all useable source endpoints of the device this is running on for usage with PLCs
|
|
||||||
/// </summary>
|
|
||||||
public static IEnumerable<IPEndPoint> GetSourceEndpoints () {
|
|
||||||
|
|
||||||
foreach (var netIf in GetUseableNetInterfaces()) {
|
|
||||||
|
|
||||||
var addressInfo = netIf.GetIPProperties().UnicastAddresses
|
|
||||||
.FirstOrDefault(x => x.Address.AddressFamily == AddressFamily.InterNetwork);
|
|
||||||
|
|
||||||
yield return new IPEndPoint(addressInfo.Address, 9094);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Lists all useable network interfaces of the device this is running on for usage with PLCs
|
|
||||||
/// </summary>
|
|
||||||
public static IEnumerable<NetworkInterface> GetUseableNetInterfaces () {
|
|
||||||
|
|
||||||
foreach (NetworkInterface netInterface in NetworkInterface.GetAllNetworkInterfaces()) {
|
|
||||||
|
|
||||||
bool isEthernet =
|
|
||||||
netInterface.NetworkInterfaceType == NetworkInterfaceType.Ethernet ||
|
|
||||||
netInterface.NetworkInterfaceType == NetworkInterfaceType.Ethernet3Megabit ||
|
|
||||||
netInterface.NetworkInterfaceType == NetworkInterfaceType.FastEthernetFx ||
|
|
||||||
netInterface.NetworkInterfaceType == NetworkInterfaceType.FastEthernetT ||
|
|
||||||
netInterface.NetworkInterfaceType == NetworkInterfaceType.GigabitEthernet;
|
|
||||||
|
|
||||||
bool isWlan = netInterface.NetworkInterfaceType == NetworkInterfaceType.Wireless80211;
|
|
||||||
|
|
||||||
bool isUsable = netInterface.OperationalStatus == OperationalStatus.Up;
|
|
||||||
|
|
||||||
if (!isUsable) continue;
|
|
||||||
if (!(isWlan || isEthernet)) continue;
|
|
||||||
|
|
||||||
IPInterfaceProperties ipProps = netInterface.GetIPProperties();
|
|
||||||
var hasUnicastInfo = ipProps.UnicastAddresses
|
|
||||||
.Any(x => x.Address.AddressFamily == AddressFamily.InterNetwork);
|
|
||||||
|
|
||||||
if (!hasUnicastInfo) continue;
|
|
||||||
|
|
||||||
yield return netInterface;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Build Order 2
|
#region Interface building step 2
|
||||||
|
|
||||||
public class PollLevelConfigurator {
|
public class PollLevelConfigurator {
|
||||||
|
|
||||||
@@ -389,7 +405,7 @@ namespace MewtocolNet
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region BuildLevel 3
|
#region Interface building step 3
|
||||||
|
|
||||||
public class EndInit<T> {
|
public class EndInit<T> {
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using MewtocolNet.Helpers;
|
using MewtocolNet.DataLists;
|
||||||
|
|
||||||
namespace MewtocolNet {
|
namespace MewtocolNet
|
||||||
|
{
|
||||||
|
|
||||||
public struct MewtocolFrameResponse {
|
public struct MewtocolFrameResponse {
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ namespace MewtocolNet {
|
|||||||
internal volatile bool pollerFirstCycle;
|
internal volatile bool pollerFirstCycle;
|
||||||
internal bool usePoller = false;
|
internal bool usePoller = false;
|
||||||
internal MemoryAreaManager memoryManager;
|
internal MemoryAreaManager memoryManager;
|
||||||
|
private volatile bool isReceiving;
|
||||||
|
private volatile bool isSending;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -101,6 +103,15 @@ namespace MewtocolNet {
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int StationNumber => stationNumber;
|
public int StationNumber => stationNumber;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsSending {
|
||||||
|
get => isSending;
|
||||||
|
private set {
|
||||||
|
isSending = value;
|
||||||
|
OnPropChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int BytesPerSecondUpstream {
|
public int BytesPerSecondUpstream {
|
||||||
get { return bytesPerSecondUpstream; }
|
get { return bytesPerSecondUpstream; }
|
||||||
@@ -110,6 +121,15 @@ namespace MewtocolNet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsReceiving {
|
||||||
|
get => isReceiving;
|
||||||
|
private set {
|
||||||
|
isReceiving = value;
|
||||||
|
OnPropChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int BytesPerSecondDownstream {
|
public int BytesPerSecondDownstream {
|
||||||
get { return bytesPerSecondDownstream; }
|
get { return bytesPerSecondDownstream; }
|
||||||
@@ -148,14 +168,13 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
memoryManager = new MemoryAreaManager(this);
|
memoryManager = new MemoryAreaManager(this);
|
||||||
|
|
||||||
|
WatchPollerDemand();
|
||||||
|
|
||||||
Connected += MewtocolInterface_Connected;
|
Connected += MewtocolInterface_Connected;
|
||||||
RegisterChanged += OnRegisterChanged;
|
RegisterChanged += OnRegisterChanged;
|
||||||
|
|
||||||
void MewtocolInterface_Connected(object sender, PlcConnectionArgs args) {
|
void MewtocolInterface_Connected(object sender, PlcConnectionArgs args) {
|
||||||
|
|
||||||
if (usePoller)
|
|
||||||
AttachPoller();
|
|
||||||
|
|
||||||
IsConnected = true;
|
IsConnected = true;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -290,10 +309,14 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
SetUpstreamStopWatchStart();
|
SetUpstreamStopWatchStart();
|
||||||
|
|
||||||
|
IsSending = true;
|
||||||
|
|
||||||
//write inital command
|
//write inital command
|
||||||
byte[] writeBuffer = Encoding.UTF8.GetBytes(frame);
|
byte[] writeBuffer = Encoding.UTF8.GetBytes(frame);
|
||||||
stream.Write(writeBuffer, 0, writeBuffer.Length);
|
stream.Write(writeBuffer, 0, writeBuffer.Length);
|
||||||
|
|
||||||
|
IsSending = false;
|
||||||
|
|
||||||
//calculate the expected number of frames from the message request
|
//calculate the expected number of frames from the message request
|
||||||
int? wordsCountRequested = null;
|
int? wordsCountRequested = null;
|
||||||
if (onReceiveProgress != null) {
|
if (onReceiveProgress != null) {
|
||||||
@@ -391,7 +414,9 @@ namespace MewtocolNet {
|
|||||||
SetDownstreamStopWatchStart();
|
SetDownstreamStopWatchStart();
|
||||||
|
|
||||||
byte[] buffer = new byte[RecBufferSize];
|
byte[] buffer = new byte[RecBufferSize];
|
||||||
|
IsReceiving = true;
|
||||||
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
|
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
|
||||||
|
IsReceiving = false;
|
||||||
|
|
||||||
CalcDownstreamSpeed(bytesRead);
|
CalcDownstreamSpeed(bytesRead);
|
||||||
|
|
||||||
@@ -411,7 +436,9 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
//request next frame
|
//request next frame
|
||||||
var writeBuffer = Encoding.UTF8.GetBytes($"%{GetStationNumber()}**&\r");
|
var writeBuffer = Encoding.UTF8.GetBytes($"%{GetStationNumber()}**&\r");
|
||||||
|
IsSending = true;
|
||||||
await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length);
|
await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length);
|
||||||
|
IsSending = false;
|
||||||
Logger.Log($">> Requested next frame", LogLevel.Critical, this);
|
Logger.Log($">> Requested next frame", LogLevel.Critical, this);
|
||||||
wasMultiFramedResponse = true;
|
wasMultiFramedResponse = true;
|
||||||
|
|
||||||
@@ -527,6 +554,8 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
private protected virtual void OnDisconnect() {
|
private protected virtual void OnDisconnect() {
|
||||||
|
|
||||||
|
IsReceiving = false;
|
||||||
|
IsSending = false;
|
||||||
BytesPerSecondDownstream = 0;
|
BytesPerSecondDownstream = 0;
|
||||||
BytesPerSecondUpstream = 0;
|
BytesPerSecondUpstream = 0;
|
||||||
PollerCycleDurationMs = 0;
|
PollerCycleDurationMs = 0;
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ namespace MewtocolNet {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract partial class MewtocolInterface {
|
public abstract partial class MewtocolInterface {
|
||||||
|
|
||||||
|
internal Task heartbeatTask = Task.CompletedTask;
|
||||||
|
|
||||||
internal Task pollCycleTask;
|
internal Task pollCycleTask;
|
||||||
|
|
||||||
private List<RegisterCollection> registerCollections = new List<RegisterCollection>();
|
private List<RegisterCollection> registerCollections = new List<RegisterCollection>();
|
||||||
@@ -39,15 +41,48 @@ namespace MewtocolNet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private System.Timers.Timer heartBeatTimer = new System.Timers.Timer();
|
||||||
|
|
||||||
#region Register Polling
|
#region Register Polling
|
||||||
|
|
||||||
|
internal void WatchPollerDemand() {
|
||||||
|
|
||||||
|
memoryManager.MemoryLayoutChanged += () => TestPollerStartNeeded();
|
||||||
|
|
||||||
|
Connected += (s, e) => TestPollerStartNeeded();
|
||||||
|
|
||||||
|
Disconnected += (s, e) => {
|
||||||
|
|
||||||
|
heartBeatTimer.Elapsed -= PollTimerTick;
|
||||||
|
heartBeatTimer.Stop();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TestPollerStartNeeded () {
|
||||||
|
|
||||||
|
if (!IsConnected) return;
|
||||||
|
|
||||||
|
heartBeatTimer.Interval = 3000;
|
||||||
|
heartBeatTimer.Elapsed += PollTimerTick;
|
||||||
|
heartBeatTimer.Start();
|
||||||
|
|
||||||
|
if (!usePoller) return;
|
||||||
|
|
||||||
|
bool hasCyclic = memoryManager.HasCyclicPollableRegisters();
|
||||||
|
bool hasFirstCycle = memoryManager.HasSingleCyclePollableRegisters();
|
||||||
|
|
||||||
|
if (hasCyclic || hasFirstCycle) AttachPoller();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Kills the poller completely
|
/// Kills the poller completely
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal void KillPoller() {
|
internal void KillPoller() {
|
||||||
|
|
||||||
pollerTaskStopped = true;
|
pollerTaskStopped = true;
|
||||||
ClearRegisterVals();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,8 +91,7 @@ namespace MewtocolNet {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal void AttachPoller() {
|
internal void AttachPoller() {
|
||||||
|
|
||||||
if (!pollerTaskStopped)
|
if (!pollerTaskStopped) return;
|
||||||
return;
|
|
||||||
|
|
||||||
PollerCycleDurationMs = 0;
|
PollerCycleDurationMs = 0;
|
||||||
pollerFirstCycle = true;
|
pollerFirstCycle = true;
|
||||||
@@ -66,6 +100,15 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void PollTimerTick(object sender, System.Timers.ElapsedEventArgs e) {
|
||||||
|
|
||||||
|
heartbeatTask = Task.Run(async () => {
|
||||||
|
Logger.LogVerbose("Sending heartbeat", this);
|
||||||
|
await GetPLCInfoAsync();
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs a single poller cycle manually,
|
/// Runs a single poller cycle manually,
|
||||||
/// useful if you want to use a custom update frequency
|
/// useful if you want to use a custom update frequency
|
||||||
@@ -86,10 +129,11 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//polls all registers one by one (slow)
|
//performs one poll cycle, one cycle is defined as getting all regster values
|
||||||
|
//and (not every cycle) the status of the plc that is performed on a timer basis
|
||||||
internal async Task Poll() {
|
internal async Task Poll() {
|
||||||
|
|
||||||
Logger.Log("Poller is attaching", LogLevel.Info, this);
|
Logger.Log("Poller is attaching", this);
|
||||||
|
|
||||||
pollerTaskStopped = false;
|
pollerTaskStopped = false;
|
||||||
|
|
||||||
@@ -100,37 +144,40 @@ namespace MewtocolNet {
|
|||||||
pollCycleTask = OnMultiFrameCycle();
|
pollCycleTask = OnMultiFrameCycle();
|
||||||
await pollCycleTask;
|
await pollCycleTask;
|
||||||
|
|
||||||
|
InvokePolledCycleDone();
|
||||||
|
|
||||||
if (!IsConnected) {
|
if (!IsConnected) {
|
||||||
pollerTaskStopped = true;
|
pollerTaskStopped = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pollerFirstCycle = false;
|
pollerFirstCycle = false;
|
||||||
InvokePolledCycleDone();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnMultiFrameCycle() {
|
private async Task OnMultiFrameCycle() {
|
||||||
|
|
||||||
|
//await the timed task before starting a new poller cycle
|
||||||
|
if (!heartbeatTask.IsCompleted) await heartbeatTask;
|
||||||
|
|
||||||
var sw = Stopwatch.StartNew();
|
var sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
//await UpdateRCPRegisters();
|
|
||||||
|
|
||||||
//await UpdateDTRegisters();
|
|
||||||
|
|
||||||
await memoryManager.PollAllAreasAsync();
|
await memoryManager.PollAllAreasAsync();
|
||||||
|
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
PollerCycleDurationMs = (int)sw.ElapsedMilliseconds;
|
PollerCycleDurationMs = (int)sw.ElapsedMilliseconds;
|
||||||
|
|
||||||
|
if (!memoryManager.HasCyclicPollableRegisters()) KillPoller();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Smart register polling methods
|
#region Smart register polling methods
|
||||||
|
|
||||||
|
[Obsolete]
|
||||||
private async Task UpdateRCPRegisters() {
|
private async Task UpdateRCPRegisters() {
|
||||||
|
|
||||||
//build booleans
|
//build booleans
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -56,9 +57,17 @@ namespace MewtocolNet {
|
|||||||
operationMode = value;
|
operationMode = value;
|
||||||
OnPropChange();
|
OnPropChange();
|
||||||
OnPropChange(nameof(IsRunMode));
|
OnPropChange(nameof(IsRunMode));
|
||||||
|
OnPropChange(nameof(OperationModeTags));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A list of operation mode tags, derived from the OPMode flags
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<string> OperationModeTags {
|
||||||
|
get => OperationMode.ToString().Split(',').Select(x => x.JoinSplitByUpperCase().Trim());
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Hardware information flags about the PLC
|
/// Hardware information flags about the PLC
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -67,10 +76,17 @@ namespace MewtocolNet {
|
|||||||
internal set {
|
internal set {
|
||||||
hardwareInformation = value;
|
hardwareInformation = value;
|
||||||
OnPropChange();
|
OnPropChange();
|
||||||
|
OnPropChange(nameof(HardwareInformationTags));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A list of hardware info tags, derived from the HardwareInformation flags
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<string> HardwareInformationTags {
|
||||||
|
get => HardwareInformation.ToString().Split(',').Select(x => x.JoinSplitByUpperCase().Trim());
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Current error code of the PLC
|
/// Current error code of the PLC
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -91,15 +107,34 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
internal bool TryExtendFromEXRT(string msg) {
|
internal bool TryExtendFromEXRT(string msg) {
|
||||||
|
|
||||||
var regexEXRT = new Regex(@"\%EE\$EX00RT00(?<icnt>..)(?<mc>..)..(?<cap>..)(?<op>..)..(?<flg>..)(?<sdiag>....)(?<ver>..)(?<hwif>..)(?<nprog>.)(?<progsz>....)(?<hdsz>....)(?<sysregsz>....).*", RegexOptions.IgnoreCase);
|
var regexEXRT = new Regex(@"\%EE\$EX00RT00(?<icnt>..)(?<mc>..)..(?<cap>..)(?<op>..)..(?<flg>..)(?<sdiag>....)(?<ver>..)(?<hwif>..)(?<nprog>.)(?<csumpz>...)(?<psize>...).*", RegexOptions.IgnoreCase);
|
||||||
var match = regexEXRT.Match(msg);
|
var match = regexEXRT.Match(msg);
|
||||||
if (match.Success) {
|
if (match.Success) {
|
||||||
|
|
||||||
|
//overwrite the typecode
|
||||||
byte typeCodeByte = byte.Parse(match.Groups["mc"].Value, NumberStyles.HexNumber);
|
byte typeCodeByte = byte.Parse(match.Groups["mc"].Value, NumberStyles.HexNumber);
|
||||||
var overWriteBytes = BitConverter.GetBytes((int)this.TypeCode);
|
var overWriteBytes = BitConverter.GetBytes((int)this.TypeCode);
|
||||||
overWriteBytes[0] = typeCodeByte;
|
overWriteBytes[0] = typeCodeByte;
|
||||||
|
|
||||||
this.TypeCode = (PlcType)BitConverter.ToInt32(overWriteBytes, 0);
|
//get the long (4 bytes) prog size
|
||||||
|
if (match.Groups["psize"]?.Value != null) {
|
||||||
|
|
||||||
|
var padded = match.Groups["psize"].Value.PadLeft(4, '0');
|
||||||
|
|
||||||
|
overWriteBytes[1] = byte.Parse(padded.Substring(2, 2), NumberStyles.HexNumber);
|
||||||
|
overWriteBytes[2] = byte.Parse(padded.Substring(0, 2), NumberStyles.HexNumber);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var tempTypeCode = BitConverter.ToUInt32(overWriteBytes, 0);
|
||||||
|
|
||||||
|
if (Enum.IsDefined(typeof(PlcType), tempTypeCode)) {
|
||||||
|
|
||||||
|
this.TypeCode = (PlcType)tempTypeCode;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//overwrite the other vals that are also contained in EXRT
|
||||||
this.CpuVersion = match.Groups["ver"].Value.Insert(1, ".");
|
this.CpuVersion = match.Groups["ver"].Value.Insert(1, ".");
|
||||||
this.HardwareInformation = (HWInformation)byte.Parse(match.Groups["hwif"].Value, NumberStyles.HexNumber);
|
this.HardwareInformation = (HWInformation)byte.Parse(match.Groups["hwif"].Value, NumberStyles.HexNumber);
|
||||||
|
|
||||||
@@ -119,20 +154,29 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
byte typeCodeByte = byte.Parse(match.Groups["cputype"].Value, NumberStyles.HexNumber);
|
byte typeCodeByte = byte.Parse(match.Groups["cputype"].Value, NumberStyles.HexNumber);
|
||||||
byte capacity = byte.Parse(match.Groups["cap"].Value, NumberStyles.Number);
|
byte capacity = byte.Parse(match.Groups["cap"].Value, NumberStyles.Number);
|
||||||
var typeCodeFull = (PlcType)BitConverter.ToInt32(new byte[] { typeCodeByte, capacity, 0, 0}, 0);
|
var tempTypeCode = (PlcType)BitConverter.ToUInt32(new byte[] { typeCodeByte, capacity, 0, 0}, 0);
|
||||||
|
|
||||||
float definedProgCapacity = 0;
|
float definedProgCapacity = 0;
|
||||||
var composedNow = typeCodeFull.ToNameDecompose();
|
PlcType typeCodeFull;
|
||||||
|
|
||||||
if (composedNow != null) {
|
if (Enum.IsDefined(typeof(PlcType), tempTypeCode)) {
|
||||||
|
|
||||||
//already recognized the type code, use the capacity value encoded in the enum
|
typeCodeFull = (PlcType)tempTypeCode;
|
||||||
definedProgCapacity = composedNow.Size;
|
|
||||||
|
var composedNow = typeCodeFull.ToNameDecompose();
|
||||||
|
|
||||||
|
if (composedNow != null) {
|
||||||
|
|
||||||
|
//already recognized the type code, use the capacity value encoded in the enum
|
||||||
|
definedProgCapacity = composedNow.Size;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
typeCodeFull = PlcType.Unknown;
|
||||||
definedProgCapacity = int.Parse(match.Groups["cap"].Value);
|
definedProgCapacity = int.Parse(match.Groups["cap"].Value);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inf = new PLCInfo {
|
inf = new PLCInfo {
|
||||||
@@ -147,7 +191,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inf = default(PLCInfo);
|
inf = default;
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -192,7 +236,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int GetHashCode() => GetHashCode();
|
public override int GetHashCode() => base.GetHashCode();
|
||||||
|
|
||||||
private protected void OnPropChange([CallerMemberName] string propertyName = null) {
|
private protected void OnPropChange([CallerMemberName] string propertyName = null) {
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ namespace MewtocolNet {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public enum PlcType : uint {
|
public enum PlcType : uint {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fallback plc type
|
||||||
|
/// </summary>
|
||||||
|
Unknown = 0,
|
||||||
|
|
||||||
//NON SIMULATION TEST POSSIBLE
|
//NON SIMULATION TEST POSSIBLE
|
||||||
#region FP5 Family (Legacy)
|
#region FP5 Family (Legacy)
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ namespace MewtocolNet.UnderlyingRegisters {
|
|||||||
|
|
||||||
internal class MemoryAreaManager {
|
internal class MemoryAreaManager {
|
||||||
|
|
||||||
|
internal event Action MemoryLayoutChanged;
|
||||||
|
|
||||||
internal int maxOptimizationDistance = 8;
|
internal int maxOptimizationDistance = 8;
|
||||||
internal int maxRegistersPerGroup = -1;
|
internal int maxRegistersPerGroup = -1;
|
||||||
internal PollLevelOverwriteMode pollLevelOrMode = PollLevelOverwriteMode.Highest;
|
internal PollLevelOverwriteMode pollLevelOrMode = PollLevelOverwriteMode.Highest;
|
||||||
@@ -20,7 +22,27 @@ namespace MewtocolNet.UnderlyingRegisters {
|
|||||||
|
|
||||||
internal MewtocolInterface mewInterface;
|
internal MewtocolInterface mewInterface;
|
||||||
internal List<PollLevel> pollLevels;
|
internal List<PollLevel> pollLevels;
|
||||||
internal Dictionary<int, PollLevelConfig> pollLevelConfigs = new Dictionary<int, PollLevelConfig>();
|
|
||||||
|
internal Dictionary<int, PollLevelConfig> pollLevelConfigs = new Dictionary<int, PollLevelConfig>() {
|
||||||
|
{
|
||||||
|
MewtocolNet.PollLevel.Always,
|
||||||
|
new PollLevelConfig {
|
||||||
|
skipNth = 1,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MewtocolNet.PollLevel.FirstIteration,
|
||||||
|
new PollLevelConfig {
|
||||||
|
skipAllButFirst = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MewtocolNet.PollLevel.Never,
|
||||||
|
new PollLevelConfig {
|
||||||
|
skipsAll = true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private uint pollIteration = 0;
|
private uint pollIteration = 0;
|
||||||
|
|
||||||
@@ -36,11 +58,7 @@ namespace MewtocolNet.UnderlyingRegisters {
|
|||||||
|
|
||||||
wrAreaSize = wrSize;
|
wrAreaSize = wrSize;
|
||||||
dtAreaSize = dtSize;
|
dtAreaSize = dtSize;
|
||||||
pollLevels = new List<PollLevel> {
|
pollLevels = new List<PollLevel>();
|
||||||
new PollLevel(wrSize, dtSize) {
|
|
||||||
level = 1,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +107,20 @@ namespace MewtocolNet.UnderlyingRegisters {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//order
|
//order
|
||||||
foreach (var lvl in pollLevels) {
|
for (int i = 0; i < pollLevels.Count; i++) {
|
||||||
|
|
||||||
|
PollLevel lvl = pollLevels[i];
|
||||||
|
|
||||||
|
//poll level has no areas
|
||||||
|
if(lvl.dataAreas.Count == 0 &&
|
||||||
|
lvl.externalRelayInAreas.Count == 0 &&
|
||||||
|
lvl.externalRelayOutAreas.Count == 0 &&
|
||||||
|
lvl.internalRelayAreas.Count == 0) {
|
||||||
|
|
||||||
|
pollLevels.Remove(lvl);
|
||||||
|
continue;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var area in lvl.dataAreas) {
|
foreach (var area in lvl.dataAreas) {
|
||||||
|
|
||||||
@@ -101,28 +132,12 @@ namespace MewtocolNet.UnderlyingRegisters {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MemoryLayoutChanged?.Invoke();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TestPollLevelExistence(Register reg) {
|
private void TestPollLevelExistence(Register reg) {
|
||||||
|
|
||||||
if (!pollLevelConfigs.ContainsKey(MewtocolNet.PollLevel.Always)) {
|
|
||||||
pollLevelConfigs.Add(MewtocolNet.PollLevel.Always, new PollLevelConfig {
|
|
||||||
skipNth = 1,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pollLevelConfigs.ContainsKey(MewtocolNet.PollLevel.FirstIteration)) {
|
|
||||||
pollLevelConfigs.Add(MewtocolNet.PollLevel.FirstIteration, new PollLevelConfig {
|
|
||||||
skipAllButFirst = true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pollLevelConfigs.ContainsKey(MewtocolNet.PollLevel.Never)) {
|
|
||||||
pollLevelConfigs.Add(MewtocolNet.PollLevel.Never, new PollLevelConfig {
|
|
||||||
skipsAll = true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pollLevels.Any(x => x.level == reg.pollLevel)) {
|
if (!pollLevels.Any(x => x.level == reg.pollLevel)) {
|
||||||
|
|
||||||
pollLevels.Add(new PollLevel(wrAreaSize, dtAreaSize) {
|
pollLevels.Add(new PollLevel(wrAreaSize, dtAreaSize) {
|
||||||
@@ -354,13 +369,6 @@ namespace MewtocolNet.UnderlyingRegisters {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//get the plc status each n iterations
|
|
||||||
if (pollIteration % 5 == 0) {
|
|
||||||
|
|
||||||
await mewInterface.GetPLCInfoAsync();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pollIteration == uint.MaxValue) {
|
if (pollIteration == uint.MaxValue) {
|
||||||
pollIteration = uint.MinValue;
|
pollIteration = uint.MinValue;
|
||||||
} else {
|
} else {
|
||||||
@@ -494,6 +502,23 @@ namespace MewtocolNet.UnderlyingRegisters {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal bool HasSingleCyclePollableRegisters() {
|
||||||
|
|
||||||
|
bool hasCyclicPollableLevels = pollLevels.Any(x => x.level != MewtocolNet.PollLevel.FirstIteration);
|
||||||
|
|
||||||
|
return hasCyclicPollableLevels;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool HasCyclicPollableRegisters () {
|
||||||
|
|
||||||
|
bool hasCyclicPollableLevels = pollLevels
|
||||||
|
.Any(x => x.level != MewtocolNet.PollLevel.Never && x.level != MewtocolNet.PollLevel.FirstIteration && x.level != 0);
|
||||||
|
|
||||||
|
return hasCyclicPollableLevels;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
using MewtocolNet;
|
using MewtocolNet;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
using MewtocolNet.DataLists;
|
||||||
|
|
||||||
using MewtocolNet.Helpers;
|
namespace MewtocolTests
|
||||||
|
{
|
||||||
namespace MewtocolTests {
|
|
||||||
|
|
||||||
public class TestLinkedLists {
|
public class TestLinkedLists {
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user