From 9bcffad77b54b2186b4d9cf216eb04fdc4033237 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20Wei=C3=9F?=
<72068105+Sandoun@users.noreply.github.com>
Date: Thu, 20 Jul 2023 23:28:58 +0200
Subject: [PATCH] Fix some of the old tests - add new examples - fix struct
builder - complete array interfaces
---
DocBuilder/Program.cs | 1 -
.../Examples.BasicEthernet.csproj | 14 +
Examples.BasicEthernet/Program.cs | 72 +++
.../Examples.BasicRegisterReadWrite.csproj | 14 +
Examples.BasicRegisterReadWrite/Program.cs | 145 +++++
Examples.Polling/Examples.Polling.csproj | 14 +
Examples.Polling/Program.cs | 90 +++
.../OnlineCommands/GetRegisterCommand.cs | 53 --
.../OnlineCommands/SetRegisterCommand.cs | 64 --
MewtocolNet.sln | 49 ++
MewtocolNet/ComCassette/CassetteFinder.cs | 32 +-
MewtocolNet/CustomTypes/DWord.cs | 19 +-
MewtocolNet/CustomTypes/Word.cs | 19 +-
MewtocolNet/IPlc.cs | 5 -
MewtocolNet/IPlcEthernet.cs | 2 +-
MewtocolNet/Logging/Logger.cs | 51 ++
MewtocolNet/Logging/LoggerTargets.cs | 12 +
MewtocolNet/Mewtocol.cs | 132 +++-
MewtocolNet/MewtocolInterface.cs | 4 +-
.../MewtocolInterfaceRegisterHandling.cs | 12 +-
MewtocolNet/MewtocolInterfaceSerial.cs | 12 +
MewtocolNet/MewtocolInterfaceTcp.cs | 20 +-
MewtocolNet/MewtocolNet.csproj | 2 +-
MewtocolNet/PollLevel.cs | 17 +
MewtocolNet/RegisterBuilding/AddressTools.cs | 266 ++++++++
.../BuilderPatterns/RBuild.cs | 253 ++++++++
.../BuilderPatterns/RBuildFromAttributes.cs | 70 ++
MewtocolNet/RegisterBuilding/RBuildAnon.cs | 104 ---
MewtocolNet/RegisterBuilding/RBuildBase.cs | 607 ------------------
MewtocolNet/RegisterBuilding/RBuildMult.cs | 375 -----------
.../RegisterBuilding/RegBuilderExtensions.cs | 39 +-
.../RegisterBuilding/RegisterAssembler.cs | 70 +-
MewtocolNet/RegisterBuilding/StepBaseTyper.cs | 234 +++++++
MewtocolNet/RegisterBuilding/StepData.cs | 44 +-
MewtocolNet/Registers/Base/IStringRegister.cs | 25 -
MewtocolNet/Registers/Base/Register.cs | 5 +-
.../Registers/{ => Classes}/ArrayRegister.cs | 165 +++--
.../Registers/{ => Classes}/BoolRegister.cs | 0
.../Registers/Classes/StringRegister.cs | 158 +++++
.../Registers/{ => Classes}/StructRegister.cs | 40 +-
MewtocolNet/Registers/IArrayRegister.cs | 43 ++
MewtocolNet/Registers/IArrayRegister2D.cs | 32 +
MewtocolNet/Registers/IArrayRegister3D.cs | 32 +
.../{Base/IRegisterT.cs => IRegister.cs} | 6 +-
.../IArrayRegister.cs => IStringRegister.cs} | 8 +-
MewtocolNet/SetupClasses/PollLevelConfig.cs | 4 +
MewtocolNet/TypeConversion/Conversions.cs | 54 +-
MewtocolNet/TypeConversion/PlcValueParser.cs | 42 +-
.../UnderlyingRegisters/MemoryAreaManager.cs | 52 +-
MewtocolTests/AutomatedPropertyRegisters.cs | 118 ----
.../RegisterReadWriteTest.cs | 22 -
.../TestRegisterCollection.cs | 6 +-
MewtocolTests/SkippedChecks.cs | 36 --
MewtocolTests/TestHelperExtensions.cs | 12 +-
MewtocolTests/TestLivePLC.cs | 105 ---
MewtocolTests/TestPublicBuilderPattern.cs | 89 +++
.../TestPublicBuilderPatternArrays.cs | 63 ++
MewtocolTests/TestRegisterBuilder.cs | 129 +---
MewtocolTests/TestRegisterInterface.cs | 128 +---
59 files changed, 2190 insertions(+), 2101 deletions(-)
create mode 100644 Examples.BasicEthernet/Examples.BasicEthernet.csproj
create mode 100644 Examples.BasicEthernet/Program.cs
create mode 100644 Examples.BasicRegisterReadWrite/Examples.BasicRegisterReadWrite.csproj
create mode 100644 Examples.BasicRegisterReadWrite/Program.cs
create mode 100644 Examples.Polling/Examples.Polling.csproj
create mode 100644 Examples.Polling/Program.cs
delete mode 100644 MewTerminal/Commands/OnlineCommands/GetRegisterCommand.cs
delete mode 100644 MewTerminal/Commands/OnlineCommands/SetRegisterCommand.cs
create mode 100644 MewtocolNet/Logging/LoggerTargets.cs
create mode 100644 MewtocolNet/PollLevel.cs
create mode 100644 MewtocolNet/RegisterBuilding/AddressTools.cs
create mode 100644 MewtocolNet/RegisterBuilding/BuilderPatterns/RBuild.cs
create mode 100644 MewtocolNet/RegisterBuilding/BuilderPatterns/RBuildFromAttributes.cs
delete mode 100644 MewtocolNet/RegisterBuilding/RBuildAnon.cs
delete mode 100644 MewtocolNet/RegisterBuilding/RBuildBase.cs
delete mode 100644 MewtocolNet/RegisterBuilding/RBuildMult.cs
create mode 100644 MewtocolNet/RegisterBuilding/StepBaseTyper.cs
delete mode 100644 MewtocolNet/Registers/Base/IStringRegister.cs
rename MewtocolNet/Registers/{ => Classes}/ArrayRegister.cs (50%)
rename MewtocolNet/Registers/{ => Classes}/BoolRegister.cs (100%)
create mode 100644 MewtocolNet/Registers/Classes/StringRegister.cs
rename MewtocolNet/Registers/{ => Classes}/StructRegister.cs (77%)
create mode 100644 MewtocolNet/Registers/IArrayRegister.cs
create mode 100644 MewtocolNet/Registers/IArrayRegister2D.cs
create mode 100644 MewtocolNet/Registers/IArrayRegister3D.cs
rename MewtocolNet/Registers/{Base/IRegisterT.cs => IRegister.cs} (85%)
rename MewtocolNet/Registers/{Base/IArrayRegister.cs => IStringRegister.cs} (75%)
delete mode 100644 MewtocolTests/EncapsulatedTests/RegisterReadWriteTest.cs
delete mode 100644 MewtocolTests/SkippedChecks.cs
create mode 100644 MewtocolTests/TestPublicBuilderPattern.cs
create mode 100644 MewtocolTests/TestPublicBuilderPatternArrays.cs
diff --git a/DocBuilder/Program.cs b/DocBuilder/Program.cs
index bbb7263..fb0a63c 100644
--- a/DocBuilder/Program.cs
+++ b/DocBuilder/Program.cs
@@ -7,7 +7,6 @@ using System.Reflection;
using System.Reflection.PortableExecutable;
using System.Text;
using MewtocolNet;
-using MewtocolNet.DocAttributes;
Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
diff --git a/Examples.BasicEthernet/Examples.BasicEthernet.csproj b/Examples.BasicEthernet/Examples.BasicEthernet.csproj
new file mode 100644
index 0000000..c7c4dd3
--- /dev/null
+++ b/Examples.BasicEthernet/Examples.BasicEthernet.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/Examples.BasicEthernet/Program.cs b/Examples.BasicEthernet/Program.cs
new file mode 100644
index 0000000..cb3d1f3
--- /dev/null
+++ b/Examples.BasicEthernet/Program.cs
@@ -0,0 +1,72 @@
+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.Verbose;
+ Logger.OnNewLogMessage((d, l, m) => Console.WriteLine($"{d}: {m}"));
+
+ //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();
+
+ }
+
+ //you can also find any applicable source endpoints by using:
+ foreach (var endpoint in Mewtocol.GetSourceEndpoints()) {
+
+ Console.WriteLine($"Usable endpoint: {endpoint}");
+
+ }
+
+ }
+
+}
diff --git a/Examples.BasicRegisterReadWrite/Examples.BasicRegisterReadWrite.csproj b/Examples.BasicRegisterReadWrite/Examples.BasicRegisterReadWrite.csproj
new file mode 100644
index 0000000..b8f82d1
--- /dev/null
+++ b/Examples.BasicRegisterReadWrite/Examples.BasicRegisterReadWrite.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/Examples.BasicRegisterReadWrite/Program.cs b/Examples.BasicRegisterReadWrite/Program.cs
new file mode 100644
index 0000000..2cdff82
--- /dev/null
+++ b/Examples.BasicRegisterReadWrite/Program.cs
@@ -0,0 +1,145 @@
+using MewtocolNet;
+using MewtocolNet.Logging;
+using MewtocolNet.Registers;
+
+namespace Examples.BasicRegisterReadWrite;
+
+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.Critical;
+
+ //here we collect our built registers
+ IRegister simpleNumberRegister = null!;
+ IRegister simpleNumberRegister2 = null!;
+
+ IRegister simpleWordRegister = null!;
+ IArrayRegister overlapWordRegister = null!;
+
+ IStringRegister stringRegister = null!;
+ IArrayRegister stringArrayRegister = null!;
+
+ IArrayRegister2D 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("DT1000").Build(out simpleNumberRegister);
+ b.Struct("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("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("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("DT2003").AsArray(3, 3).Build(out simpleNumberRegister2Dim);
+
+ })
+ .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]}");
+
+ }
+
+}
diff --git a/Examples.Polling/Examples.Polling.csproj b/Examples.Polling/Examples.Polling.csproj
new file mode 100644
index 0000000..c7c4dd3
--- /dev/null
+++ b/Examples.Polling/Examples.Polling.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/Examples.Polling/Program.cs b/Examples.Polling/Program.cs
new file mode 100644
index 0000000..4386c7c
--- /dev/null
+++ b/Examples.Polling/Program.cs
@@ -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("DT1000").Build();
+ b.Struct("DT1000").Build();
+ b.Struct("DT1001").Build();
+ b.Struct("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("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("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();
+
+ }
+
+
+}
diff --git a/MewTerminal/Commands/OnlineCommands/GetRegisterCommand.cs b/MewTerminal/Commands/OnlineCommands/GetRegisterCommand.cs
deleted file mode 100644
index d7094f6..0000000
--- a/MewTerminal/Commands/OnlineCommands/GetRegisterCommand.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using System;
-using System.Collections.Generic;
-using CommandLine;
-using MewtocolNet;
-using MewtocolNet.ComCassette;
-using MewtocolNet.Logging;
-using MewtocolNet.RegisterBuilding;
-using Spectre.Console;
-
-namespace MewTerminal.Commands.OnlineCommands;
-
-[Verb("rget", HelpText = "Gets the values of the given PLC registers")]
-internal class GetRegisterCommand : OnlineCommand {
-
- [Value(0, MetaName = "registers", Default = "DT0", HelpText = "The registers to read formatted as (DT0:INT)")]
- public IEnumerable Registers { get; set; }
-
- internal override async Task AfterSetup(IPlc plc) {
-
- var builder = RegBuilder.ForInterface(plc);
-
- foreach (var reg in Registers) {
-
- var split = reg.Split(":");
-
- if (split.Length <= 1) {
- throw new FormatException($"Register name was not formatted correctly: {reg}, missing :PlcVarType");
- }
-
- var mewtocolName = split[0];
- var mewtocolType = split[1];
-
- if (Enum.TryParse(mewtocolType, out var parsedT)) {
-
- builder.FromPlcRegName(mewtocolName).AsPlcType(parsedT).Build();
-
- }
-
- }
-
- await plc.ConnectAsync();
-
- foreach (var reg in plc.GetAllRegisters()) {
-
- await reg.ReadAsync();
-
- }
-
- AnsiConsole.Write(plc.GetAllRegisters().ToTable());
-
- }
-
-}
diff --git a/MewTerminal/Commands/OnlineCommands/SetRegisterCommand.cs b/MewTerminal/Commands/OnlineCommands/SetRegisterCommand.cs
deleted file mode 100644
index 44a6077..0000000
--- a/MewTerminal/Commands/OnlineCommands/SetRegisterCommand.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-using CommandLine;
-using MewtocolNet;
-using MewtocolNet.RegisterBuilding;
-using MewtocolNet.Registers;
-using Spectre.Console;
-
-namespace MewTerminal.Commands.OnlineCommands;
-
-[Verb("rset", HelpText = "Sets the values of the given PLC registers")]
-internal class SetRegisterCommand : OnlineCommand {
-
- [Value(0, MetaName = "registers", Default = "DT0", HelpText = "The registers to write formatted as (DT0:INT:VALUE)")]
- public IEnumerable Registers { get; set; }
-
- internal override async Task AfterSetup(IPlc plc) {
-
- var builder = RegBuilder.ForInterface(plc);
-
- var toWriteVals = new List