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(); - - foreach (var reg in Registers) { - - var split = reg.Split(":"); - - if (split.Length <= 2) { - throw new FormatException($"Register name was not formatted correctly: {reg}, missing :PlcVarType:Value"); - } - - var mewtocolName = split[0]; - var mewtocolType = split[1]; - var value = split[2]; - - if (Enum.TryParse(mewtocolType, out var parsedT)) { - - var built = builder.FromPlcRegName(mewtocolName).AsPlcType(parsedT).Build(); - - if(built is BoolRegister) toWriteVals.Add(bool.Parse(value)); - else if(built is NumberRegister) toWriteVals.Add(short.Parse(value)); - else if(built is NumberRegister) toWriteVals.Add(ushort.Parse(value)); - else if(built is NumberRegister) toWriteVals.Add(int.Parse(value)); - else if(built is NumberRegister) toWriteVals.Add(uint.Parse(value)); - else if(built is NumberRegister) toWriteVals.Add(float.Parse(value)); - else if(built is NumberRegister) toWriteVals.Add(TimeSpan.Parse(value)); - - } - - } - - await plc.ConnectAsync(); - - int i = 0; - foreach (var reg in plc.GetAllRegisters()) { - - await reg.WriteAsync(toWriteVals[i]); - - i++; - - } - - AnsiConsole.WriteLine("All registers written"); - - } - -} \ No newline at end of file diff --git a/MewtocolNet.sln b/MewtocolNet.sln index a957c67..8a98cbe 100644 --- a/MewtocolNet.sln +++ b/MewtocolNet.sln @@ -11,6 +11,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DocBuilder", "DocBuilder\Do EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MewTerminal", "MewTerminal\MewTerminal.csproj", "{D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{323729B0-5FB2-4592-9FA6-220C46BBF84C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.BasicEthernet", "Examples.BasicEthernet\Examples.BasicEthernet.csproj", "{80806065-163D-43F3-90CD-9221F391793F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Examples.BasicRegisterReadWrite", "Examples.BasicRegisterReadWrite\Examples.BasicRegisterReadWrite.csproj", "{A24444C8-691D-44CB-8DE8-19618C6DE94B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Examples.Polling", "Examples.Polling\Examples.Polling.csproj", "{9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -69,10 +77,51 @@ Global {D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Release|x64.Build.0 = Release|Any CPU {D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Release|x86.ActiveCfg = Release|Any CPU {D1E751C6-296F-4CF1-AE28-C6D4388C7CF1}.Release|x86.Build.0 = Release|Any CPU + {80806065-163D-43F3-90CD-9221F391793F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {80806065-163D-43F3-90CD-9221F391793F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {80806065-163D-43F3-90CD-9221F391793F}.Debug|x64.ActiveCfg = Debug|Any CPU + {80806065-163D-43F3-90CD-9221F391793F}.Debug|x64.Build.0 = Debug|Any CPU + {80806065-163D-43F3-90CD-9221F391793F}.Debug|x86.ActiveCfg = Debug|Any CPU + {80806065-163D-43F3-90CD-9221F391793F}.Debug|x86.Build.0 = Debug|Any CPU + {80806065-163D-43F3-90CD-9221F391793F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {80806065-163D-43F3-90CD-9221F391793F}.Release|Any CPU.Build.0 = Release|Any CPU + {80806065-163D-43F3-90CD-9221F391793F}.Release|x64.ActiveCfg = Release|Any CPU + {80806065-163D-43F3-90CD-9221F391793F}.Release|x64.Build.0 = Release|Any CPU + {80806065-163D-43F3-90CD-9221F391793F}.Release|x86.ActiveCfg = Release|Any CPU + {80806065-163D-43F3-90CD-9221F391793F}.Release|x86.Build.0 = Release|Any CPU + {A24444C8-691D-44CB-8DE8-19618C6DE94B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A24444C8-691D-44CB-8DE8-19618C6DE94B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A24444C8-691D-44CB-8DE8-19618C6DE94B}.Debug|x64.ActiveCfg = Debug|Any CPU + {A24444C8-691D-44CB-8DE8-19618C6DE94B}.Debug|x64.Build.0 = Debug|Any CPU + {A24444C8-691D-44CB-8DE8-19618C6DE94B}.Debug|x86.ActiveCfg = Debug|Any CPU + {A24444C8-691D-44CB-8DE8-19618C6DE94B}.Debug|x86.Build.0 = Debug|Any CPU + {A24444C8-691D-44CB-8DE8-19618C6DE94B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A24444C8-691D-44CB-8DE8-19618C6DE94B}.Release|Any CPU.Build.0 = Release|Any CPU + {A24444C8-691D-44CB-8DE8-19618C6DE94B}.Release|x64.ActiveCfg = Release|Any CPU + {A24444C8-691D-44CB-8DE8-19618C6DE94B}.Release|x64.Build.0 = Release|Any CPU + {A24444C8-691D-44CB-8DE8-19618C6DE94B}.Release|x86.ActiveCfg = Release|Any CPU + {A24444C8-691D-44CB-8DE8-19618C6DE94B}.Release|x86.Build.0 = Release|Any CPU + {9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}.Debug|x64.ActiveCfg = Debug|Any CPU + {9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}.Debug|x64.Build.0 = Debug|Any CPU + {9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}.Debug|x86.ActiveCfg = Debug|Any CPU + {9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}.Debug|x86.Build.0 = Debug|Any CPU + {9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}.Release|Any CPU.Build.0 = Release|Any CPU + {9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}.Release|x64.ActiveCfg = Release|Any CPU + {9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}.Release|x64.Build.0 = Release|Any CPU + {9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}.Release|x86.ActiveCfg = Release|Any CPU + {9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {80806065-163D-43F3-90CD-9221F391793F} = {323729B0-5FB2-4592-9FA6-220C46BBF84C} + {A24444C8-691D-44CB-8DE8-19618C6DE94B} = {323729B0-5FB2-4592-9FA6-220C46BBF84C} + {9A36F2B1-FF9E-47BF-9931-3D3EB3C46B61} = {323729B0-5FB2-4592-9FA6-220C46BBF84C} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4ABB8137-CD8F-4691-9802-9ED371012F47} EndGlobalSection diff --git a/MewtocolNet/ComCassette/CassetteFinder.cs b/MewtocolNet/ComCassette/CassetteFinder.cs index d779168..7079bab 100644 --- a/MewtocolNet/ComCassette/CassetteFinder.cs +++ b/MewtocolNet/ComCassette/CassetteFinder.cs @@ -20,7 +20,7 @@ namespace MewtocolNet.ComCassette { var interfacesTasks = new List>>(); - var usableInterfaces = GetUseableNetInterfaces(); + var usableInterfaces = Mewtocol.GetUseableNetInterfaces(); if (ipSource == null) { @@ -71,36 +71,6 @@ namespace MewtocolNet.ComCassette { } - private static IEnumerable 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; - - } - - } - private static async Task> FindClientsForEndpoint(IPEndPoint from, int timeoutMs, string ipEndpointName) { var cassettesFound = new List(); diff --git a/MewtocolNet/CustomTypes/DWord.cs b/MewtocolNet/CustomTypes/DWord.cs index fd31914..d7c10ea 100644 --- a/MewtocolNet/CustomTypes/DWord.cs +++ b/MewtocolNet/CustomTypes/DWord.cs @@ -13,17 +13,13 @@ namespace MewtocolNet { internal uint value; - public uint Value { - get => value; - set { - this.value = value; - } - } + public uint Value => value; public DWord(uint bytes) { value = bytes; bitLength = Marshal.SizeOf(value) * 8; } + public DWord(byte[] bytes) { bytes = bytes.Take(4).ToArray(); value = BitConverter.ToUInt32(bytes, 0); @@ -62,17 +58,8 @@ namespace MewtocolNet { return (value & (1 << bitIndex)) != 0; } - set { - if (bitIndex > bitLength - 1) - throw new IndexOutOfRangeException($"The DWord bit index was out of range ({bitIndex}/{bitLength - 1})"); - - int mask = 1 << bitIndex; - this.value = value ? this.value |= (uint)mask : this.value &= (uint)~mask; - } } - public void ClearBits() => this.value = 0; - public override bool Equals(object obj) { if ((obj == null) || !this.GetType().Equals(obj.GetType())) { @@ -107,8 +94,6 @@ namespace MewtocolNet { } - public int GetIntialPlcByteSize() => 4; - } } diff --git a/MewtocolNet/CustomTypes/Word.cs b/MewtocolNet/CustomTypes/Word.cs index b10bd0d..d53e6b6 100644 --- a/MewtocolNet/CustomTypes/Word.cs +++ b/MewtocolNet/CustomTypes/Word.cs @@ -13,17 +13,13 @@ namespace MewtocolNet { internal ushort value; - public ushort Value { - get => value; - set { - this.value = value; - } - } + public ushort Value => value; public Word(ushort bytes) { value = bytes; bitLength = Marshal.SizeOf(value) * 8; } + public Word(byte[] bytes) { bytes = bytes.Take(2).ToArray(); value = BitConverter.ToUInt16(bytes, 0); @@ -53,7 +49,7 @@ namespace MewtocolNet { public static bool operator !=(Word a, Word b) => a.value != b.value; /// - /// Gets or sets the bit value at the given position + /// Gets the bit value at the given position /// public bool this[int bitIndex] { get { @@ -62,17 +58,8 @@ namespace MewtocolNet { return (value & (1 << bitIndex)) != 0; } - set { - if (bitIndex > bitLength - 1) - throw new IndexOutOfRangeException($"The word bit index was out of range ({bitIndex}/{bitLength - 1})"); - - int mask = 1 << bitIndex; - this.value = value ? this.value |= (ushort)mask : this.value &= (ushort)~mask; - } } - public void ClearBits() => this.value = 0; - public override bool Equals(object obj) { if ((obj == null) || !this.GetType().Equals(obj.GetType())) { diff --git a/MewtocolNet/IPlc.cs b/MewtocolNet/IPlc.cs index 64bc17d..1b8b310 100644 --- a/MewtocolNet/IPlc.cs +++ b/MewtocolNet/IPlc.cs @@ -57,11 +57,6 @@ namespace MewtocolNet { /// int ConnectTimeout { get; set; } - /// - /// Provides an anonymous interface for register reading and writing without memory management - /// - RBuildAnon Register { get; } - /// /// Tries to establish a connection with the device asynchronously /// diff --git a/MewtocolNet/IPlcEthernet.cs b/MewtocolNet/IPlcEthernet.cs index cebb130..fd4add0 100644 --- a/MewtocolNet/IPlcEthernet.cs +++ b/MewtocolNet/IPlcEthernet.cs @@ -22,7 +22,7 @@ namespace MewtocolNet { /// /// The host ip endpoint, leave it null to use an automatic interface /// - IPEndPoint HostEndpoint { get; set; } + IPEndPoint HostEndpoint { get; } /// /// Configures the serial interface diff --git a/MewtocolNet/Logging/Logger.cs b/MewtocolNet/Logging/Logger.cs index 7e5ae3b..48f6509 100644 --- a/MewtocolNet/Logging/Logger.cs +++ b/MewtocolNet/Logging/Logger.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; namespace MewtocolNet.Logging { @@ -12,8 +13,58 @@ namespace MewtocolNet.Logging { /// public static LogLevel LogLevel { get; set; } + /// + /// Defines the default output logger targets + /// + public static LoggerTargets DefaultTargets { get; set; } = LoggerTargets.Console; + internal static Action LogInvoked; + static Logger () { + + OnNewLogMessage((d, l, m) => { + + if(DefaultTargets.HasFlag(LoggerTargets.Console)) { + + switch (l) { + case LogLevel.Error: + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"{d:hh:mm:ss:ff} {m}"); + break; + case LogLevel.Info: + Console.WriteLine($"{d:hh:mm:ss:ff} {m}"); + break; + case LogLevel.Change: + Console.ForegroundColor = ConsoleColor.DarkBlue; + Console.WriteLine($"{d:hh:mm:ss:ff} {m}"); + break; + case LogLevel.Verbose: + Console.ForegroundColor = ConsoleColor.DarkYellow; + Console.WriteLine($"{d:hh:mm:ss:ff} {m}"); + break; + case LogLevel.Critical: + Console.ForegroundColor = ConsoleColor.DarkGray; + Console.WriteLine($"{d:hh:mm:ss:ff} {m}"); + break; + } + + Console.ResetColor(); + + } + + if (DefaultTargets.HasFlag(LoggerTargets.Trace)) { + + Trace.WriteLine($"{d:hh:mm:ss:ff} {m}"); + + } + + }); + + } + + //for static calling purposes only + internal static void Start() { } + /// /// Gets invoked whenever a new log message is ready /// diff --git a/MewtocolNet/Logging/LoggerTargets.cs b/MewtocolNet/Logging/LoggerTargets.cs new file mode 100644 index 0000000..b77007c --- /dev/null +++ b/MewtocolNet/Logging/LoggerTargets.cs @@ -0,0 +1,12 @@ +using System; + +namespace MewtocolNet.Logging { + [Flags] + public enum LoggerTargets { + + None = 0, + Console = 1, + Trace = 2, + + } +} diff --git a/MewtocolNet/Mewtocol.cs b/MewtocolNet/Mewtocol.cs index 3057d37..dc3177b 100644 --- a/MewtocolNet/Mewtocol.cs +++ b/MewtocolNet/Mewtocol.cs @@ -1,14 +1,18 @@ using MewtocolNet.RegisterAttributes; using MewtocolNet.RegisterBuilding; +using MewtocolNet.RegisterBuilding.BuilderPatterns; using MewtocolNet.SetupClasses; using System; using System.Collections.Generic; using System.IO.Ports; using System.Linq; using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; using System.Threading.Tasks; -namespace MewtocolNet { +namespace MewtocolNet +{ /// /// Builder helper for mewtocol interfaces @@ -24,11 +28,11 @@ namespace MewtocolNet { /// /// Plc station number 0xEE for direct communication /// - public static PostInit Ethernet(string ip, int port = 9094, int station = 0xEE) { + public static PostInitEth Ethernet(string ip, int port = 9094, int station = 0xEE) { var instance = new MewtocolInterfaceTcp(); instance.ConfigureConnection(ip, port, station); - return new PostInit { + return new PostInitEth { intf = instance }; @@ -41,11 +45,11 @@ namespace MewtocolNet { /// /// Plc station number 0xEE for direct communication /// - public static PostInit Ethernet(IPAddress ip, int port = 9094, int station = 0xEE) { + public static PostInitEth Ethernet(IPAddress ip, int port = 9094, int station = 0xEE) { var instance = new MewtocolInterfaceTcp(); instance.ConfigureConnection(ip, port, station); - return new PostInit { + return new PostInitEth { intf = instance }; @@ -63,8 +67,6 @@ namespace MewtocolNet { /// public static PostInit Serial(string portName, BaudRate baudRate = BaudRate._19200, DataBits dataBits = DataBits.Eight, Parity parity = Parity.Odd, StopBits stopBits = StopBits.One, int station = 0xEE) { - TestPortName(portName); - var instance = new MewtocolInterfaceSerial(); instance.ConfigureConnection(portName, (int)baudRate, (int)dataBits, parity, stopBits, station); return new PostInit { @@ -81,8 +83,6 @@ namespace MewtocolNet { /// public static PostInit SerialAuto(string portName, int station = 0xEE) { - TestPortName(portName); - var instance = new MewtocolInterfaceSerial(); instance.ConfigureConnection(portName, station); instance.ConfigureConnectionAuto(); @@ -92,12 +92,52 @@ namespace MewtocolNet { } - private static void TestPortName(string portName) { + /// + /// Lists all useable source endpoints of the device this is running on for usage with PLCs + /// + public static IEnumerable GetSourceEndpoints () { - var portnames = SerialPort.GetPortNames(); + foreach (var netIf in GetUseableNetInterfaces()) { - if (!portnames.Any(x => x == portName)) - throw new NotSupportedException($"The port {portName} is no valid port"); + var addressInfo = netIf.GetIPProperties().UnicastAddresses + .FirstOrDefault(x => x.Address.AddressFamily == AddressFamily.InterNetwork); + + yield return new IPEndPoint(addressInfo.Address, 9094); + + } + + } + + /// + /// Lists all useable network interfaces of the device this is running on for usage with PLCs + /// + public static IEnumerable 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; + + } } @@ -116,8 +156,8 @@ namespace MewtocolNet { /// Delay between poll requests public PollLevelConfigurator SetLevel(int level, TimeSpan interval) { - if (level <= 1) - throw new NotSupportedException($"The poll level {level} is not configurable"); + if (level == PollLevel.Always || level == PollLevel.Never || level == PollLevel.FirstIteration) + throw new NotSupportedException("The poll level is reserved for the library"); if (!levelConfigs.ContainsKey(level)) { levelConfigs.Add(level, new PollLevelConfig { @@ -133,8 +173,8 @@ namespace MewtocolNet { public PollLevelConfigurator SetLevel(int level, int skipNth) { - if (level <= 1) - throw new NotSupportedException($"The poll level {level} is not configurable"); + if (level == PollLevel.Always || level == PollLevel.Never || level == PollLevel.FirstIteration) + throw new NotSupportedException("The poll level is reserved for the library"); if (!levelConfigs.ContainsKey(level)) { levelConfigs.Add(level, new PollLevelConfig { @@ -174,6 +214,56 @@ namespace MewtocolNet { } + public class PostInitEth : PostInit { + + /// + /// Sets the source of the outgoing ethernet connection + /// + public PostInit FromSource (IPEndPoint endpoint) { + + if(endpoint == null) + throw new ArgumentNullException("Endpoint can't be null", nameof(endpoint)); + + if(intf is MewtocolInterfaceTcp imew) { + + imew.HostEndpoint = endpoint; + + } + + return this; + + } + + /// + /// Sets the source of the outgoing ethernet connection + /// + /// IP address of the source interface (Format: 127.0.0.1) + /// Port of the source interface + /// + /// + public PostInit FromSource(string ip, int port) { + + if (intf is MewtocolInterfaceTcp imew) { + + if(port < IPEndPoint.MinPort) + throw new ArgumentException($"Source port cant be smaller than {IPEndPoint.MinPort}", nameof(port)); + + if (port > IPEndPoint.MaxPort) + throw new ArgumentException($"Source port cant be larger than {IPEndPoint.MaxPort}", nameof(port)); + + if (!IPAddress.TryParse(ip, out var ipParsed)) + throw new ArgumentException("Failed to parse the source IP", nameof(ip)); + + imew.HostEndpoint = new IPEndPoint(ipParsed, port); + + } + + return this; + + } + + } + public class PostInit { internal T intf; @@ -269,18 +359,16 @@ namespace MewtocolNet { /// /// A builder for attaching register collections /// - public PostInit WithRegisters(Action builder) { + public PostInit WithRegisters(Action builder) { try { var plc = (MewtocolInterface)(object)intf; - var assembler = new RegisterAssembler(plc); - var regBuilder = new RBuildMult(plc); + var regBuilder = new RBuild(plc); builder.Invoke(regBuilder); - var registers = assembler.AssembleAll(regBuilder); - plc.AddRegisters(registers.ToArray()); + plc.AddRegisters(regBuilder.assembler.assembled.ToArray()); return this; diff --git a/MewtocolNet/MewtocolInterface.cs b/MewtocolNet/MewtocolInterface.cs index 0b2bcca..914f4be 100644 --- a/MewtocolNet/MewtocolInterface.cs +++ b/MewtocolNet/MewtocolInterface.cs @@ -55,7 +55,7 @@ namespace MewtocolNet { private protected AsyncQueue queue = new AsyncQueue(); private protected Stopwatch speedStopwatchUpstr; private protected Stopwatch speedStopwatchDownstr; - private protected Task firstPollTask = new Task(() => { }); + private protected Task firstPollTask; private protected bool wasInitialStatusReceived; private protected MewtocolVersion mewtocolVersion; @@ -144,6 +144,8 @@ namespace MewtocolNet { private protected MewtocolInterface() { + Logger.Start(); + memoryManager = new MemoryAreaManager(this); Connected += MewtocolInterface_Connected; diff --git a/MewtocolNet/MewtocolInterfaceRegisterHandling.cs b/MewtocolNet/MewtocolInterfaceRegisterHandling.cs index d4a3e3c..0840b71 100644 --- a/MewtocolNet/MewtocolInterfaceRegisterHandling.cs +++ b/MewtocolNet/MewtocolInterfaceRegisterHandling.cs @@ -2,6 +2,7 @@ using MewtocolNet.Logging; using MewtocolNet.RegisterAttributes; using MewtocolNet.RegisterBuilding; +using MewtocolNet.RegisterBuilding.BuilderPatterns; using MewtocolNet.Registers; using System; using System.Collections.Generic; @@ -38,9 +39,6 @@ namespace MewtocolNet { } } - /// - public RBuildAnon Register => new RBuildAnon(this); - #region Register Polling /// @@ -191,7 +189,7 @@ namespace MewtocolNet { if (registerCollections.Count != 0) throw new NotSupportedException("Register collections can only be build once"); - var regBuild = new RBuildMult(this); + var regBuild = new RBuildFromAttributes(this); foreach (var collection in collections) { @@ -218,7 +216,7 @@ namespace MewtocolNet { if (pollFreqAttr != null) pollLevel = pollFreqAttr.pollLevel; //add builder item - var stp1 = regBuild + regBuild .AddressFromAttribute(cAttribute.MewAddress, cAttribute.TypeDef, collection, prop, byteHint) .AsType(dotnetType.IsEnum ? dotnetType.UnderlyingSystemType : dotnetType) .PollLevel(pollLevel); @@ -242,8 +240,8 @@ namespace MewtocolNet { } var assembler = new RegisterAssembler(this); - var registers = assembler.AssembleAll(regBuild, true); - AddRegisters(registers.ToArray()); + + AddRegisters(assembler.assembled.ToArray()); } diff --git a/MewtocolNet/MewtocolInterfaceSerial.cs b/MewtocolNet/MewtocolInterfaceSerial.cs index a561a94..83f826b 100644 --- a/MewtocolNet/MewtocolInterfaceSerial.cs +++ b/MewtocolNet/MewtocolInterfaceSerial.cs @@ -78,6 +78,9 @@ namespace MewtocolNet { /// public void ConfigureConnection(string _portName, int _baudRate = 19200, int _dataBits = 8, Parity _parity = Parity.Odd, StopBits _stopBits = StopBits.One, int _station = 0xEE) { + if (IsConnected) + throw new NotSupportedException("Can't change the connection settings while the PLC is connected"); + PortName = _portName; SerialBaudRate = _baudRate; SerialDataBits = _dataBits; @@ -95,6 +98,9 @@ namespace MewtocolNet { internal void ConfigureConnectionAuto() { + if (IsConnected) + throw new NotSupportedException("Can't change the connection settings while the PLC is connected"); + autoSerial = true; } @@ -106,6 +112,10 @@ namespace MewtocolNet { /// private async Task ConnectAsyncPriv(Func callBack, Action onTryingConfig = null) { + var portnames = SerialPort.GetPortNames(); + if (!portnames.Any(x => x == PortName)) + throw new NotSupportedException($"The port {PortName} is no valid port"); + void OnTryConfig() { onTryingConfig(); } @@ -115,6 +125,8 @@ namespace MewtocolNet { try { + firstPollTask = new Task(() => { }); + Logger.Log($">> Intial connection start <<", LogLevel.Verbose, this); isConnectingStage = true; diff --git a/MewtocolNet/MewtocolInterfaceTcp.cs b/MewtocolNet/MewtocolInterfaceTcp.cs index e527784..fa00aa8 100644 --- a/MewtocolNet/MewtocolInterfaceTcp.cs +++ b/MewtocolNet/MewtocolInterfaceTcp.cs @@ -1,5 +1,6 @@ using MewtocolNet.Logging; using System; +using System.Linq; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; @@ -23,7 +24,7 @@ namespace MewtocolNet { public int Port { get; private set; } /// - public IPEndPoint HostEndpoint { get; set; } + public IPEndPoint HostEndpoint { get; internal set; } internal MewtocolInterfaceTcp() : base() { } @@ -32,6 +33,9 @@ namespace MewtocolNet { /// public void ConfigureConnection(string ip, int port = 9094, int station = 0xEE) { + if (IsConnected) + throw new NotSupportedException("Can't change the connection settings while the PLC is connected"); + if (!IPAddress.TryParse(ip, out ipAddr)) throw new NotSupportedException($"The ip: {ip} is no valid ip address"); @@ -48,6 +52,9 @@ namespace MewtocolNet { /// public void ConfigureConnection(IPAddress ip, int port = 9094, int station = 0xEE) { + if (IsConnected) + throw new NotSupportedException("Can't change the connection settings while the PLC is connected"); + ipAddr = ip; Port = port; @@ -65,11 +72,22 @@ namespace MewtocolNet { try { + firstPollTask = new Task(() => { }); + Logger.Log($">> Intial connection start <<", LogLevel.Verbose, this); isConnectingStage = true; if (HostEndpoint != null) { + var hasEndpoint = Mewtocol + .GetSourceEndpoints() + .Any(x => x.Address.ToString() == HostEndpoint.Address.ToString()); + + if (!hasEndpoint) + throw new NotSupportedException($"The specified source endpoint: " + + $"{HostEndpoint}, doesn't exist on the device, " + + $"use 'Mewtocol.GetSourceEndpoints()' to find applicable ones"); + client = new TcpClient(HostEndpoint) { ReceiveBufferSize = RecBufferSize, NoDelay = false, diff --git a/MewtocolNet/MewtocolNet.csproj b/MewtocolNet/MewtocolNet.csproj index 89d2219..7a357ce 100644 --- a/MewtocolNet/MewtocolNet.csproj +++ b/MewtocolNet/MewtocolNet.csproj @@ -37,7 +37,7 @@ - + diff --git a/MewtocolNet/PollLevel.cs b/MewtocolNet/PollLevel.cs new file mode 100644 index 0000000..9e23018 --- /dev/null +++ b/MewtocolNet/PollLevel.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MewtocolNet { + + public static class PollLevel { + + public const int Always = 1; + + public const int FirstIteration = 254; + + public const int Never = 255; + + } + +} diff --git a/MewtocolNet/RegisterBuilding/AddressTools.cs b/MewtocolNet/RegisterBuilding/AddressTools.cs new file mode 100644 index 0000000..ae07479 --- /dev/null +++ b/MewtocolNet/RegisterBuilding/AddressTools.cs @@ -0,0 +1,266 @@ +using MewtocolNet.PublicEnums; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text.RegularExpressions; + +namespace MewtocolNet.RegisterBuilding { + + internal static class AddressTools { + + internal protected struct ParseResult { + + internal ParseResultState state; + + internal string hardFailReason; + + internal StepData stepData; + + } + + //methods to test the input string on + static List> parseMethods = new List>() { + + (x) => TryBuildBoolean(x), + (x) => TryBuildNumericBased(x), + (x) => TryBuildByteRangeBased(x), + + }; + + //bool registers + private static ParseResult TryBuildBoolean(string plcAddrName) { + + //regex to find special register values + var patternBool = new Regex(@"(?X|Y|R)(?[0-9]{0,3})(?(?:[0-9]|[A-F]){1})?"); + + var match = patternBool.Match(plcAddrName); + + if (!match.Success) + return new ParseResult { + state = ParseResultState.FailedSoft + }; + + string prefix = match.Groups["prefix"].Value; + string area = match.Groups["area"].Value; + string special = match.Groups["special"].Value; + + IOType regType; + uint areaAdd = 0; + byte specialAdd = 0x0; + + //try cast the prefix + if (!Enum.TryParse(prefix, out regType)) { + + return new ParseResult { + state = ParseResultState.FailedHard, + hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for boolean registers" + }; + + } + + if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) { + + return new ParseResult { + state = ParseResultState.FailedHard, + hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong" + }; + + } + + //special address not given + if (string.IsNullOrEmpty(special) && !string.IsNullOrEmpty(area)) { + + var isAreaInt = uint.TryParse(area, NumberStyles.Number, CultureInfo.InvariantCulture, out var areaInt); + + if (isAreaInt && areaInt >= 0 && areaInt <= 9) { + + //area address is actually meant as special address but 0-9 + specialAdd = (byte)areaInt; + areaAdd = 0; + + + } else if (isAreaInt && areaInt > 9) { + + //area adress is meant to be the actual area address + areaAdd = areaInt; + specialAdd = 0; + + } else { + + return new ParseResult { + state = ParseResultState.FailedHard, + hardFailReason = $"Cannot parse '{plcAddrName}', the special address: '{special}' is wrong 1", + }; + + } + + } else { + + //special address parsed as hex num + if (!byte.TryParse(special, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out specialAdd)) { + + return new ParseResult { + state = ParseResultState.FailedHard, + hardFailReason = $"Cannot parse '{plcAddrName}', the special address: '{special}' is wrong 2", + }; + + } + + } + + return new ParseResult { + state = ParseResultState.Success, + stepData = new StepData { + regType = (RegisterType)(int)regType, + memAddress = areaAdd, + specialAddress = specialAdd, + } + }; + + } + + // one to two word registers + private static ParseResult TryBuildNumericBased(string plcAddrName) { + + var patternByte = new Regex(@"^(?DT|DDT)(?[0-9]{1,5})$"); + + var match = patternByte.Match(plcAddrName); + + if (!match.Success) + return new ParseResult { + state = ParseResultState.FailedSoft + }; + + string prefix = match.Groups["prefix"].Value; + string area = match.Groups["area"].Value; + + RegisterType regType; + uint areaAdd = 0; + + //try cast the prefix + if (!Enum.TryParse(prefix, out regType)) { + + return new ParseResult { + state = ParseResultState.FailedHard, + hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for numeric registers" + }; + + } + + if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) { + + return new ParseResult { + state = ParseResultState.FailedHard, + hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong" + }; + + } + + return new ParseResult { + state = ParseResultState.Success, + stepData = new StepData { + regType = regType, + memAddress = areaAdd, + } + }; + + } + + // one to two word registers + private static ParseResult TryBuildByteRangeBased(string plcAddrName) { + + var split = plcAddrName.Split('-'); + + if (split.Length > 2) + return new ParseResult { + state = ParseResultState.FailedHard, + hardFailReason = $"Cannot parse '{plcAddrName}', to many delimters '-'" + }; + + uint[] addresses = new uint[2]; + + for (int i = 0; i < split.Length; i++) { + + string addr = split[i]; + var patternByte = new Regex(@"(?DT|DDT)(?[0-9]{1,5})"); + + var match = patternByte.Match(addr); + + if (!match.Success) + return new ParseResult { + state = ParseResultState.FailedSoft + }; + + string prefix = match.Groups["prefix"].Value; + string area = match.Groups["area"].Value; + + RegisterType regType; + uint areaAdd = 0; + + //try cast the prefix + if (!Enum.TryParse(prefix, out regType)) { + + return new ParseResult { + state = ParseResultState.FailedHard, + hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for word range registers" + }; + + } + + if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) { + + return new ParseResult { + state = ParseResultState.FailedHard, + hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong" + }; + + } + + addresses[i] = areaAdd; + + } + + return new ParseResult { + state = ParseResultState.Success, + stepData = new StepData { + regType = RegisterType.DT_BYTE_RANGE, + wasAddressStringRangeBased = true, + dotnetVarType = typeof(byte[]), + memAddress = addresses[0], + byteSizeHint = (addresses[1] - addresses[0] + 1) * 2 + } + }; + + } + + internal static StepData ParseAddress(string plcAddrName, string name = null) { + + foreach (var method in parseMethods) { + + var res = method.Invoke(plcAddrName); + + if (res.state == ParseResultState.Success) { + + if (!string.IsNullOrEmpty(name)) res.stepData.name = name; + + res.stepData.originalParseStr = plcAddrName; + res.stepData.buildSource = RegisterBuildSource.Manual; + + return new StepData().Map(res.stepData); + + } else if (res.state == ParseResultState.FailedHard) { + + throw new Exception(res.hardFailReason); + + } + + } + + throw new Exception("Wrong input format"); + + } + + } + + +} diff --git a/MewtocolNet/RegisterBuilding/BuilderPatterns/RBuild.cs b/MewtocolNet/RegisterBuilding/BuilderPatterns/RBuild.cs new file mode 100644 index 0000000..444404d --- /dev/null +++ b/MewtocolNet/RegisterBuilding/BuilderPatterns/RBuild.cs @@ -0,0 +1,253 @@ +using MewtocolNet; +using MewtocolNet.PublicEnums; +using MewtocolNet.RegisterAttributes; +using MewtocolNet.Registers; +using System; +using System.Linq; +using System.Reflection; +using static MewtocolNet.RegisterBuilding.BuilderPatterns.RBuild; + +namespace MewtocolNet.RegisterBuilding.BuilderPatterns { + + /// + /// Contains useful tools for bunch register creation + /// + public class RBuild { + + internal RegisterAssembler assembler; + + public RBuild(MewtocolInterface plc) { + + assembler = new RegisterAssembler(plc); + + } + + public class SBaseRB : StepBase { + + internal RBuild builder; + + } + + #region String parse stage + + internal Register Assemble(StepBase stp) => assembler.Assemble(stp.Data); + + //struct constructor + + public StructStp Struct(string fpAddr, string name = null) where T : struct { + + var data = AddressTools.ParseAddress(fpAddr, name); + + data.dotnetVarType = typeof(T); + + return new StructStp(data) { + builder = this, + }; + + } + + //string constructor + + public StringStp String(string fpAddr, int sizeHint, string name = null) { + + var data = AddressTools.ParseAddress(fpAddr, name); + + data.dotnetVarType = typeof(string); + data.byteSizeHint = (uint)sizeHint; + + return new StringStp(data) { + builder = this, + }; + + } + + #endregion + + #region Typing stage + + //structs can lead to arrays + public class StructStp : ArrayStp where T : struct { + + internal StructStp(StepData data) { + + this.Data = data; + this.Map(StepBaseTyper.AsType(this, typeof(T))); + + } + + public void Build() => builder.Assemble(this); + + public void Build(out IRegister reference) => reference = (IRegister)builder.Assemble(this); + + public StructStpOut PollLevel(int level) { + + Data.pollLevel = level; + return new StructStpOut().Map(this); + + } + + } + + public class StructStpOut : SBaseRB where T : struct { + + public void Build() => builder.Assemble(this); + + public void Build(out IRegister reference) => reference = (IRegister)builder.Assemble(this); + + } + + //strings can lead to arrays + public class StringStp : ArrayStp where T : class { + + internal StringStp(StepData data) { + + this.Data = data; + this.Map(StepBaseTyper.AsType(this, typeof(T))); + + } + + public void Build() => builder.Assemble(this); + + public void Build(out IStringRegister reference) => reference = (IStringRegister)builder.Assemble(this); + + public StringOutStp PollLevel(int level) { + + Data.pollLevel = level; + return new StringOutStp().Map(this); + + } + + } + + public class StringOutStp : SBaseRB { + + public void Build() => builder.Assemble(this); + + public void Build(out IStringRegister reference) => reference = (IStringRegister)builder.Assemble(this); + + } + + //arrays + public class ArrayStp : SBaseRB { + + public TypedArr1D AsArray(int i) { + + Data.arrayIndicies = new int[] { i }; + SetSizing(); + return new TypedArr1D().Map(this); + + } + + public TypedArr2D AsArray(int i1, int i2) { + + Data.arrayIndicies = new int[] { i1, i2 }; + SetSizing(); + return new TypedArr2D().Map(this); + + } + + public TypedArr3D AsArray(int i1, int i2, int i3) { + + Data.arrayIndicies = new int[] { i1, i2, i3 }; + SetSizing(); + return new TypedArr3D().Map(this); + + } + + private void SetSizing() { + + var arr = Array.CreateInstance(Data.dotnetVarType, Data.arrayIndicies.ToArray()); + + Data.dotnetVarType = arr.GetType(); + + var itemCount = (uint)Data.arrayIndicies.Aggregate((a, x) => a * x); + + if (typeof(T) == typeof(string)) { + + var byteSize = Data.byteSizeHint.Value; + if (byteSize % 2 != 0) byteSize++; + Data.byteSizeHint = itemCount * (byteSize + 4); + + } else { + + var byteSize = (uint)typeof(T).DetermineTypeByteIntialSize(); + Data.byteSizeHint = itemCount * byteSize; + + } + + } + + } + + #endregion + + #region Typing size hint + + //1D array + + public class TypedArr1D : TypedArr1DOut { + + public TypedArr1DOut PollLevel(int level) { + + Data.pollLevel = level; + return new TypedArr1DOut().Map(this); + + } + + } + + public class TypedArr1DOut : SBaseRB { + + public IArrayRegister Build() => (IArrayRegister)builder.Assemble(this); + + public void Build(out IArrayRegister reference) => reference = (IArrayRegister)builder.Assemble(this); + + } + + //2D array + + public class TypedArr2D : TypedArr2DOut { + + public TypedArr2DOut PollLevel(int level) { + + Data.pollLevel = level; + return new TypedArr2DOut().Map(this); + + } + + } + + public class TypedArr2DOut : SBaseRB { + + public IArrayRegister2D Build() => (IArrayRegister2D)builder.Assemble(this); + + public void Build(out IArrayRegister2D reference) => reference = (IArrayRegister2D)builder.Assemble(this); + + } + + //3D array + + public class TypedArr3D : SBaseRB { + + public TypedArr3DOut PollLevel(int level) { + + Data.pollLevel = level; + return new TypedArr3DOut().Map(this); + + } + + } + + public class TypedArr3DOut : SBaseRB { + + public IArrayRegister3D Build() => (IArrayRegister3D)builder.Assemble(this); + + public void Build(out IArrayRegister3D reference) => reference = (IArrayRegister3D)builder.Assemble(this); + + } + + #endregion + + } + +} diff --git a/MewtocolNet/RegisterBuilding/BuilderPatterns/RBuildFromAttributes.cs b/MewtocolNet/RegisterBuilding/BuilderPatterns/RBuildFromAttributes.cs new file mode 100644 index 0000000..d2b8279 --- /dev/null +++ b/MewtocolNet/RegisterBuilding/BuilderPatterns/RBuildFromAttributes.cs @@ -0,0 +1,70 @@ +using MewtocolNet.PublicEnums; +using MewtocolNet.RegisterAttributes; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +namespace MewtocolNet.RegisterBuilding.BuilderPatterns { + + internal class RBuildFromAttributes { + + internal RegisterAssembler assembler; + + public RBuildFromAttributes(MewtocolInterface plc) { + + assembler = new RegisterAssembler(plc); + + } + + public class SBaseRBDyn : StepBase { + + internal RBuildFromAttributes builder; + + } + + //internal use only, adds a type definition (for use when building from attibute) + internal DynamicStp AddressFromAttribute(string dtAddr, string typeDef, RegisterCollection regCol, PropertyInfo prop, uint? bytesizeHint = null) { + + var stpData = AddressTools.ParseAddress(dtAddr); + + stpData.typeDef = typeDef; + stpData.buildSource = RegisterBuildSource.Attribute; + stpData.regCollection = regCol; + stpData.boundProperty = prop; + stpData.byteSizeHint = bytesizeHint; + + return new DynamicStp { + builder = this, + Data = stpData, + }; + + } + + //non generic + internal class DynamicStp : SBaseRBDyn { + + public DynamicRegister AsType() => new DynamicRegister().Map(StepBaseTyper.AsType(this)); + + public DynamicRegister AsType(Type type) => new DynamicRegister().Map(StepBaseTyper.AsType(this, type)); + + public DynamicRegister AsType(PlcVarType type) => new DynamicRegister().Map(StepBaseTyper.AsType(this, type)); + + public DynamicRegister AsType(string type) => new DynamicRegister().Map(StepBaseTyper.AsType(this, type)); + + public DynamicRegister AsTypeArray(params int[] indicies) => new DynamicRegister().Map(StepBaseTyper.AsTypeArray(this, indicies)); + + } + + internal class DynamicRegister : SBaseRBDyn { + + public void PollLevel (int level) { + + Data.pollLevel = level; + + } + + } + + } +} diff --git a/MewtocolNet/RegisterBuilding/RBuildAnon.cs b/MewtocolNet/RegisterBuilding/RBuildAnon.cs deleted file mode 100644 index 2c0198c..0000000 --- a/MewtocolNet/RegisterBuilding/RBuildAnon.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; -using MewtocolNet.Registers; -using System.Threading.Tasks; - -namespace MewtocolNet.RegisterBuilding { - - /// - /// Anonymous register builder - /// - public class RBuildAnon : RBuildBase { - - public RBuildAnon(MewtocolInterface plc) : base(plc) { } - - /// - public SAddress Address(string plcAddrName) { - - return new SAddress { - attachedPlc = this.attachedPLC, - addrString = plcAddrName - }; - - } - - public new class SAddress { - - protected internal MewtocolInterface attachedPlc; - protected internal string addrString; - protected internal string name; - - /// - /// Writes data to the register and bypasses the memory manager
- ///
- /// The value to write - /// True if success - public async Task WriteToAsync(T value) { - - throw new NotImplementedException(); - - //try { - - // var tempRegister = AssembleTemporaryRegister(); - // return await tempRegister.WriteAsync(value); - - //} catch { - - // throw; - - //} - - } - - /// - /// Reads data from the register and bypasses the memory manager
- ///
- /// The value read or null if failed - public async Task ReadFromAsync() { - - throw new NotImplementedException(); - - //try { - - // var tempRegister = AssembleTemporaryRegister(); - // return (T)await tempRegister.ReadAsync(); - - //} catch { - - // throw; - - //} - - } - - private Register AssembleTemporaryRegister() { - - var temp = new RBuildMult(attachedPlc).Address(addrString).AsType(); - - var assembler = new RegisterAssembler(attachedPlc); - return assembler.Assemble(temp.Data); - - } - - } - - } - - public class RBuildSingle : RBuildBase { - - public RBuildSingle(MewtocolInterface plc) : base(plc) { } - - /// - public SAddress Address(string plcAddrName, string name = null) { - - var data = ParseAddress(plcAddrName, name); - - return new SAddress { - Data = data, - builder = this, - }; - - } - - } - -} diff --git a/MewtocolNet/RegisterBuilding/RBuildBase.cs b/MewtocolNet/RegisterBuilding/RBuildBase.cs deleted file mode 100644 index be25c6d..0000000 --- a/MewtocolNet/RegisterBuilding/RBuildBase.cs +++ /dev/null @@ -1,607 +0,0 @@ -using MewtocolNet.PublicEnums; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Reflection; -using System.Text.RegularExpressions; - -namespace MewtocolNet.RegisterBuilding { - - public class RBuildBase { - - protected internal MewtocolInterface attachedPLC; - - public RBuildBase() { } - - internal RBuildBase(MewtocolInterface plc) => attachedPLC = plc; - - internal List unfinishedList = new List(); - - #region Parser stage - - //methods to test the input string on - protected static List> parseMethods = new List>() { - - (x) => TryBuildBoolean(x), - (x) => TryBuildNumericBased(x), - (x) => TryBuildByteRangeBased(x), - - }; - - public class SBase { - - public SBase() { } - - internal SBase(StepData data, RBuildBase bldr) { - Data = data; - builder = bldr; - } - - internal StepData Data; - - internal RBuildBase builder; - - } - - internal protected struct ParseResult { - - internal ParseResultState state; - - internal string hardFailReason; - - internal BaseStepData stepData; - - } - - //bool registers - private static ParseResult TryBuildBoolean(string plcAddrName) { - - //regex to find special register values - var patternBool = new Regex(@"(?X|Y|R)(?[0-9]{0,3})(?(?:[0-9]|[A-F]){1})?"); - - var match = patternBool.Match(plcAddrName); - - if (!match.Success) - return new ParseResult { - state = ParseResultState.FailedSoft - }; - - string prefix = match.Groups["prefix"].Value; - string area = match.Groups["area"].Value; - string special = match.Groups["special"].Value; - - IOType regType; - uint areaAdd = 0; - byte specialAdd = 0x0; - - //try cast the prefix - if (!Enum.TryParse(prefix, out regType)) { - - return new ParseResult { - state = ParseResultState.FailedHard, - hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for boolean registers" - }; - - } - - if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) { - - return new ParseResult { - state = ParseResultState.FailedHard, - hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong" - }; - - } - - //special address not given - if (string.IsNullOrEmpty(special) && !string.IsNullOrEmpty(area)) { - - var isAreaInt = uint.TryParse(area, NumberStyles.Number, CultureInfo.InvariantCulture, out var areaInt); - - if (isAreaInt && areaInt >= 0 && areaInt <= 9) { - - //area address is actually meant as special address but 0-9 - specialAdd = (byte)areaInt; - areaAdd = 0; - - - } else if (isAreaInt && areaInt > 9) { - - //area adress is meant to be the actual area address - areaAdd = areaInt; - specialAdd = 0; - - } else { - - return new ParseResult { - state = ParseResultState.FailedHard, - hardFailReason = $"Cannot parse '{plcAddrName}', the special address: '{special}' is wrong 1", - }; - - } - - } else { - - //special address parsed as hex num - if (!byte.TryParse(special, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out specialAdd)) { - - return new ParseResult { - state = ParseResultState.FailedHard, - hardFailReason = $"Cannot parse '{plcAddrName}', the special address: '{special}' is wrong 2", - }; - - } - - } - - return new ParseResult { - state = ParseResultState.Success, - stepData = new StepData { - regType = (RegisterType)(int)regType, - memAddress = areaAdd, - specialAddress = specialAdd, - } - }; - - } - - // one to two word registers - private static ParseResult TryBuildNumericBased(string plcAddrName) { - - var patternByte = new Regex(@"^(?DT|DDT)(?[0-9]{1,5})$"); - - var match = patternByte.Match(plcAddrName); - - if (!match.Success) - return new ParseResult { - state = ParseResultState.FailedSoft - }; - - string prefix = match.Groups["prefix"].Value; - string area = match.Groups["area"].Value; - - RegisterType regType; - uint areaAdd = 0; - - //try cast the prefix - if (!Enum.TryParse(prefix, out regType)) { - - return new ParseResult { - state = ParseResultState.FailedHard, - hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for numeric registers" - }; - - } - - if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) { - - return new ParseResult { - state = ParseResultState.FailedHard, - hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong" - }; - - } - - return new ParseResult { - state = ParseResultState.Success, - stepData = new StepData { - regType = regType, - memAddress = areaAdd, - } - }; - - } - - // one to two word registers - private static ParseResult TryBuildByteRangeBased(string plcAddrName) { - - var split = plcAddrName.Split('-'); - - if (split.Length > 2) - return new ParseResult { - state = ParseResultState.FailedHard, - hardFailReason = $"Cannot parse '{plcAddrName}', to many delimters '-'" - }; - - uint[] addresses = new uint[2]; - - for (int i = 0; i < split.Length; i++) { - - string addr = split[i]; - var patternByte = new Regex(@"(?DT|DDT)(?[0-9]{1,5})"); - - var match = patternByte.Match(addr); - - if (!match.Success) - return new ParseResult { - state = ParseResultState.FailedSoft - }; - - string prefix = match.Groups["prefix"].Value; - string area = match.Groups["area"].Value; - - RegisterType regType; - uint areaAdd = 0; - - //try cast the prefix - if (!Enum.TryParse(prefix, out regType)) { - - return new ParseResult { - state = ParseResultState.FailedHard, - hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for word range registers" - }; - - } - - if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) { - - return new ParseResult { - state = ParseResultState.FailedHard, - hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong" - }; - - } - - addresses[i] = areaAdd; - - } - - return new ParseResult { - state = ParseResultState.Success, - stepData = new StepData { - regType = RegisterType.DT_BYTE_RANGE, - wasAddressStringRangeBased = true, - dotnetVarType = typeof(byte[]), - memAddress = addresses[0], - byteSizeHint = (addresses[1] - addresses[0] + 1) * 2 - } - }; - - } - - #endregion - - #region Addressing stage - - internal StepData ParseAddress(string plcAddrName, string name = null) { - - foreach (var method in parseMethods) { - - var res = method.Invoke(plcAddrName); - - if (res.state == ParseResultState.Success) { - - if (!string.IsNullOrEmpty(name)) res.stepData.name = name; - - res.stepData.originalParseStr = plcAddrName; - res.stepData.buildSource = RegisterBuildSource.Manual; - - return new StepData().Map(res.stepData); - - } else if (res.state == ParseResultState.FailedHard) { - - throw new Exception(res.hardFailReason); - - } - - } - - throw new Exception("Wrong input format"); - - } - - #endregion - - #region Typing stage - - public class SAddress : SBase { - - /// - /// Sets the register as a dotnet type for direct conversion - /// - /// - /// - /// - internal TypedRegister AsType() { - - if (!typeof(T).IsAllowedPlcCastingType()) { - - throw new NotSupportedException($"The dotnet type {typeof(T)}, is not supported for PLC type casting"); - - } - - Data.dotnetVarType = typeof(T); - - return new TypedRegister().Map(this); - - } - - /// - /// Sets the register as a dotnet type for direct conversion - /// - /// - /// - /// - /// - internal TypedRegister AsType(Type type) { - - //was ranged syntax array build - if (Data.wasAddressStringRangeBased && type.IsArray && type.GetArrayRank() == 1) { - - //invoke generic AsTypeArray - MethodInfo method = typeof(SAddress).GetMethod(nameof(AsTypeArray)); - MethodInfo generic = method.MakeGenericMethod(type); - - var elementType = type.GetElementType(); - - if (type != typeof(byte[]) && !elementType.IsAllowedPlcCastingType()) { - - throw new NotSupportedException($"The dotnet type {elementType}, is not supported for PLC type casting"); - - } - - int byteSizePerItem = elementType.DetermineTypeByteIntialSize(); - - //check if it fits without remainder - if (Data.byteSizeHint % byteSizePerItem != 0) { - throw new NotSupportedException($"The array element type {elementType} doesn't fit into the adress range"); - } - - return (TypedRegister)generic.Invoke(this, new object[] { - //element count - new int[] { (int)((Data.byteSizeHint / byteSizePerItem)) } - }); - - } else if (Data.wasAddressStringRangeBased) { - - throw new NotSupportedException("DT range building is only allowed for 1 dimensional arrays"); - - } - - //for internal only, relay to AsType from string - if (Data.buildSource == RegisterBuildSource.Attribute) { - - if ((type.IsArray || type == typeof(string)) && Data.typeDef != null) { - - return AsType(Data.typeDef); - - } else if (type.IsArray && Data.typeDef == null) { - - throw new NotSupportedException("Typedef parameter is needed for array types"); - - } else if (Data.typeDef != null) { - - throw new NotSupportedException("Can't use the typedef parameter on non array or string types"); - - } - - } - - if (!type.IsAllowedPlcCastingType()) { - - throw new NotSupportedException($"The dotnet type {type}, is not supported for PLC type casting"); - - } - - Data.dotnetVarType = type; - - return new TypedRegister().Map(this); - - } - - /// - /// Sets the register type as a predefined - /// - internal TypedRegister AsType(PlcVarType type) { - - Data.dotnetVarType = type.GetDefaultDotnetType(); - - return new TypedRegister().Map(this); - - } - - /// - /// Sets the register type from the plc type string
- /// Supported types: - /// - /// BOOLBoolean R/X/Y registers - /// INT16 bit signed integer - /// UINT16 bit un-signed integer - /// DINT32 bit signed integer - /// UDINT32 bit un-signed integer - /// REAL32 bit floating point - /// TIME32 bit time interpreted as - /// STRINGString of chars, the interface will automatically get the length - /// STRING[N]String of chars, pre capped to N - /// WORD16 bit word interpreted as - /// DWORD32 bit double word interpreted as - /// - ///
- internal TypedRegister AsType(string type) { - - var regexString = new Regex(@"^STRING *\[(?[0-9]*)\]$", RegexOptions.IgnoreCase); - var regexArray = new Regex(@"^ARRAY *\[(?[0-9]*)..(?[0-9]*)(?:\,(?[0-9]*)..(?[0-9]*))?(?:\,(?[0-9]*)..(?[0-9]*))?\] *OF {1,}(?.*)$", RegexOptions.IgnoreCase); - - var stringMatch = regexString.Match(type); - var arrayMatch = regexArray.Match(type); - - if (Enum.TryParse(type, out var parsed)) { - - Data.dotnetVarType = parsed.GetDefaultDotnetType(); - - } else if (stringMatch.Success) { - - Data.dotnetVarType = typeof(string); - Data.byteSizeHint = uint.Parse(stringMatch.Groups["len"].Value); - - } else if (arrayMatch.Success) { - - //invoke generic AsTypeArray - - string arrTypeString = arrayMatch.Groups["t"].Value; - Type dotnetArrType = null; - - var stringMatchInArray = regexString.Match(arrTypeString); - - if (Enum.TryParse(arrTypeString, out var parsedArrType) && parsedArrType != PlcVarType.STRING) { - - dotnetArrType = parsedArrType.GetDefaultDotnetType(); - - - } else if (stringMatchInArray.Success) { - - dotnetArrType = typeof(string); - //Data.byteSizeHint = uint.Parse(stringMatch.Groups["len"].Value); - - } else { - - throw new NotSupportedException($"The FP type '{arrTypeString}' was not recognized"); - - } - - var indices = new List(); - - for (int i = 1; i < 4; i++) { - - var arrStart = arrayMatch.Groups[$"S{i}"]?.Value; - var arrEnd = arrayMatch.Groups[$"E{i}"]?.Value; - if (string.IsNullOrEmpty(arrStart) || string.IsNullOrEmpty(arrEnd)) break; - - var arrStartInt = int.Parse(arrStart); - var arrEndInt = int.Parse(arrEnd); - - indices.Add(arrEndInt - arrStartInt + 1); - - } - - var arr = Array.CreateInstance(dotnetArrType, indices.ToArray()); - var arrType = arr.GetType(); - - MethodInfo method = typeof(SAddress).GetMethod(nameof(AsTypeArray)); - MethodInfo generic = method.MakeGenericMethod(arrType); - - var tmp = (TypedRegister)generic.Invoke(this, new object[] { - indices.ToArray() - }); - - tmp.builder = builder; - tmp.Data = Data; - - return tmp; - - } else { - - throw new NotSupportedException($"The FP type '{type}' was not recognized"); - - } - - return new TypedRegister().Map(this); - - } - - /// - /// Sets the register as a (multidimensional) array targeting a PLC array - /// - /// - /// - /// - /// - /// Indicies for multi dimensional arrays, for normal arrays just one INT - /// - /// - /// One dimensional arrays:
- /// ARRAY [0..2] OF INT = AsTypeArray<short[]>(3)
- /// ARRAY [5..6] OF DWORD = AsTypeArray<DWord[]>(2)
- ///
- /// Multi dimensional arrays:
- /// ARRAY [0..2, 0..3, 0..4] OF INT = AsTypeArray<short[,,]>(3,4,5)
- /// ARRAY [5..6, 0..2] OF DWORD = AsTypeArray<DWord[,]>(2, 3)
- ///
- internal TypedRegister AsTypeArray(params int[] indicies) { - - if (!typeof(T).IsArray) - throw new NotSupportedException($"The type {typeof(T)} was no array"); - - var arrRank = typeof(T).GetArrayRank(); - var elBaseType = typeof(T).GetElementType(); - - if (arrRank > 3) - throw new NotSupportedException($"4+ dimensional arrays are not supported"); - - if (typeof(T) != typeof(byte[]) && !elBaseType.IsAllowedPlcCastingType()) - throw new NotSupportedException($"The dotnet type {typeof(T)}, is not supported for PLC array type casting"); - - if (arrRank != indicies.Length) - throw new NotSupportedException($"All dimensional array indicies must be set"); - - Data.dotnetVarType = typeof(T); - - int byteSizePerItem = elBaseType.DetermineTypeByteIntialSize(); - int calcedTotalByteSize = indicies.Aggregate((a, x) => a * x) * byteSizePerItem; - - Data.byteSizeHint = (uint)calcedTotalByteSize; - Data.arrayIndicies = indicies; - - if (Data.byteSizeHint % byteSizePerItem != 0) { - throw new NotSupportedException($"The array element type {elBaseType} doesn't fit into the adress range"); - } - - return new TypedRegister().Map(this); - - } - - } - - #endregion - - #region Typing size hint - - public class TypedRegister : SBase { - - internal OptionsRegister SizeHint(int hint) { - - Data.byteSizeHint = (uint)hint; - - return new OptionsRegister().Map(this); - - } - - internal OptionsRegister PollLevel(int level) { - - Data.pollLevel = level; - - return new OptionsRegister().Map(this); - - } - - } - - #endregion - - #region Options stage - - public class OptionsRegister : SBase { - - internal OptionsRegister() { } - - internal OptionsRegister(StepData data, RBuildBase bldr) : base(data, bldr) { } - - /// - /// Sets the poll level of the register - /// - public OptionsRegister PollLevel(int level) { - - Data.pollLevel = level; - - return this; - - } - - } - - #endregion - - } - -} diff --git a/MewtocolNet/RegisterBuilding/RBuildMult.cs b/MewtocolNet/RegisterBuilding/RBuildMult.cs deleted file mode 100644 index c2683f7..0000000 --- a/MewtocolNet/RegisterBuilding/RBuildMult.cs +++ /dev/null @@ -1,375 +0,0 @@ -using MewtocolNet.PublicEnums; -using MewtocolNet.RegisterAttributes; -using MewtocolNet.Registers; -using System; -using System.Reflection; -using static MewtocolNet.RegisterBuilding.RBuildMult; - -namespace MewtocolNet.RegisterBuilding { - - /// - /// Contains useful tools for bunch register creation - /// - public class RBuildMult : RBuildBase { - - public RBuildMult(MewtocolInterface plc) : base(plc) { } - - #region String parse stage - - //at runtime constructor - - /// - /// Starts the register builder for a new mewtocol address
- /// Examples: - /// Address("DT100") | Address("R10A") | Address("DDT50", "MyRegisterName") - ///
- /// Address name formatted as FP-Address like in FP-Winpro - /// Custom name for the register to referr to it later - public AddressStp Address(string dtAddr, string name = null) { - - var data = ParseAddress(dtAddr, name); - - unfinishedList.Add(data); - - return new AddressStp { - Data = data, - builder = this, - }; - - } - - //struct constructor - - public StructStp Struct(string dtAddr, string name = null) where T : struct { - - var data = ParseAddress(dtAddr, name); - - data.dotnetVarType = typeof(T); - - unfinishedList.Add(data); - - return new StructStp(data) { - builder = this, - }; - - } - - public StringStp String(string dtAddr, int sizeHint, string name = null) where T : class { - - var data = ParseAddress(dtAddr, name); - - data.dotnetVarType = typeof(T); - data.byteSizeHint = (uint)sizeHint; - - unfinishedList.Add(data); - - if (typeof(T).IsArray) { - - return new StringStp(data, true) { - Data = data, - builder = this, - }; - - } - - return new StringStp(data) { - builder = this, - }; - - } - - public ArrayStp Array(string dtAddr, string name = null) where T : class { - - var data = ParseAddress(dtAddr, name); - - data.dotnetVarType = typeof(T); - - unfinishedList.Add(data); - - if (typeof(T).IsArray) { - - return new ArrayStp(data, true) { - Data = data, - builder = this, - }; - - } - - return new ArrayStp(data) { - builder = this, - }; - - } - - //internal use only, adds a type definition (for use when building from attibute) - internal AddressStp AddressFromAttribute(string dtAddr, string typeDef, RegisterCollection regCol, PropertyInfo prop, uint? bytesizeHint = null) { - - var built = Address(dtAddr); - - built.Data.typeDef = typeDef; - built.Data.buildSource = RegisterBuildSource.Attribute; - built.Data.regCollection = regCol; - built.Data.boundProperty = prop; - built.Data.byteSizeHint = bytesizeHint; - - return built; - - } - - #endregion - - #region Typing stage - - //non generic - public new class AddressStp : RBuildBase.SAddress { - - public new TypedRegister AsType() => new TypedRegister().Map(base.AsType()); - - public new TypedRegister AsType(Type type) => new TypedRegister().Map(base.AsType(type)); - - public new TypedRegister AsType(PlcVarType type) => new TypedRegister().Map(base.AsType(type)); - - public new TypedRegister AsType(string type) => new TypedRegister().Map(base.AsType(type)); - - public new TypedRegister AsTypeArray(params int[] indicies) => new TypedRegister().Map(base.AsTypeArray(indicies)); - - } - - //structs - public class StructStp : RBuildBase.SAddress where T : struct { - - internal StructStp(StepData data) { - - this.Data = data; - - this.Map(AsType(typeof(T))); - - } - - internal StructStp(StepData data, bool arrayed) { - - this.Data = data; - - } - - /// - /// Outputs the generated - /// - public void Out(Action> registerOut) { - - Data.registerOut = new Action(o => registerOut((IRegister)o)); - - } - - public OutStruct PollLevel(int level) { - - Data.pollLevel = level; - - return new OutStruct().Map(this); - - } - - } - - //arrays - public class ArrayStp : RBuildBase.SAddress { - - internal ArrayStp(StepData data) { - - Data = data; - - this.Map(AsType(typeof(T))); - - } - - internal ArrayStp(StepData data, bool arrayed) { - - Data = data; - - } - - public TypedRegisterArray Indices(params int[] indices) { - - if (typeof(T).GetElementType() == typeof(string) && Data.byteSizeHint == null) { - - throw new NotSupportedException($"For string arrays use {nameof(ArrayStp.StrHint)} before setting the indices"); - - } - - Data.arrayIndicies = indices; - - return new TypedRegisterArray().Map(this); - - } - - public TypedRegisterStringArray StrHint(int hint) { - - Data.byteSizeHint = (uint)hint; - return new TypedRegisterStringArray().Map(this); - - } - - } - - //strings - public class StringStp : RBuildBase.SAddress where T : class { - - internal StringStp(StepData data) { - - this.Data = data; - - this.Map(AsType(typeof(T))); - - } - - internal StringStp(StepData data, bool arrayed) { - - this.Data = data; - - } - - /// - /// Outputs the generated - /// - public void Out(Action> registerOut) { - - Data.registerOut = new Action(o => registerOut((IStringRegister)o)); - - } - - } - - #endregion - - #region Typing size hint - - public new class TypedRegister : RBuildBase.TypedRegister { - - public new OptionsRegister SizeHint(int hint) => new OptionsRegister().Map(base.SizeHint(hint)); - - /// - public new OutRegister PollLevel(int level) => new OutRegister().Map(base.PollLevel(level)); - - /// - /// Outputs the generated - /// - public void Out(Action registerOut) { - - Data.registerOut = new Action(o => registerOut((IRegister)o)); - - } - - } - - public class TypedRegisterString : RBuildBase.TypedRegister where T : class { - - public new OptionsRegister SizeHint(int hint) => new OptionsRegister().Map(base.SizeHint(hint)); - - /// - public new OutRegister PollLevel(int level) => new OutRegister().Map(base.PollLevel(level)); - - /// - /// Outputs the generated - /// - public void Out(Action> registerOut) { - - Data.registerOut = new Action(o => registerOut((IStringRegister)o)); - - } - - } - - public class TypedRegisterArray : RBuildBase.TypedRegister { - - public new OutArray PollLevel(int level) => new OutArray().Map(base.PollLevel(level)); - - public void Out(Action> registerOut) { - - Data.registerOut = new Action(o => registerOut((IArrayRegister)o)); - - } - - } - - public class TypedRegisterStringArray : RBuildBase.TypedRegister { - - public OptionsRegisterArray Indices(params int[] indices) { - - Data.arrayIndicies = indices; - return new OptionsRegisterArray().Map(this); - - } - - public new OutArray PollLevel(int level) => new OutArray().Map(base.PollLevel(level)); - - } - - #endregion - - #region Options stage - - public new class OptionsRegister : RBuildBase.OptionsRegister { - - /// - public new OutRegister PollLevel(int level) => new OutRegister().Map(base.PollLevel(level)); - - /// - /// Outputs the generated - /// - public void Out(Action registerOut) { - - Data.registerOut = new Action(o => registerOut((IRegister)o)); - - } - - } - - public class OptionsRegisterArray : RBuildBase.OptionsRegister { - - /// - public new OutArray PollLevel(int level) => new OutArray().Map(base.PollLevel(level)); - - public void Out(Action> registerOut) { - - Data.registerOut = new Action(o => registerOut((IArrayRegister)o)); - - } - - } - - #endregion - - public class OutRegister : SBase { - - public void Out(Action registerOut) { - - Data.registerOut = new Action(o => registerOut((IRegister)o)); - - } - - } - - public class OutStruct : SBase where T : struct { - - public void Out(Action> registerOut) { - - Data.registerOut = new Action(o => registerOut((IRegister)o)); - - } - - } - - public class OutArray : SBase { - - public void Out(Action> registerOut) { - - Data.registerOut = new Action(o => registerOut((IArrayRegister)o)); - - } - - } - - - } - -} diff --git a/MewtocolNet/RegisterBuilding/RegBuilderExtensions.cs b/MewtocolNet/RegisterBuilding/RegBuilderExtensions.cs index 3812246..d1486f3 100644 --- a/MewtocolNet/RegisterBuilding/RegBuilderExtensions.cs +++ b/MewtocolNet/RegisterBuilding/RegBuilderExtensions.cs @@ -1,4 +1,5 @@ -using MewtocolNet.Registers; +using MewtocolNet.RegisterBuilding.BuilderPatterns; +using MewtocolNet.Registers; using System; using System.Linq; using System.Threading.Tasks; @@ -12,22 +13,22 @@ namespace MewtocolNet.RegisterBuilding { /// This waits for the memory manager to size all dynamic registers correctly /// /// The generated - public static IRegister AddRegister(this IPlc plc, Action builder) { + //public static IRegister AddRegister(this IPlc plc, Action builder) { - var assembler = new RegisterAssembler((MewtocolInterface)plc); - var regBuilder = new RBuildSingle((MewtocolInterface)plc); + // var assembler = new RegisterAssembler((MewtocolInterface)plc); + // var regBuilder = new RBuildSingle((MewtocolInterface)plc); - builder.Invoke(regBuilder); + // builder.Invoke(regBuilder); - var registers = assembler.AssembleAll(regBuilder); + // var registers = assembler.AssembleAll(regBuilder); - var interf = (MewtocolInterface)plc; + // var interf = (MewtocolInterface)plc; - interf.AddRegisters(registers.ToArray()); + // interf.AddRegisters(registers.ToArray()); - return registers.First(); + // return registers.First(); - } + //} /// /// Adds multiple registers to the plc stack at once
@@ -38,22 +39,22 @@ namespace MewtocolNet.RegisterBuilding { /// use /// for this case ///
- public static IPlc AddRegisters (this IPlc plc, Action builder) { + //public static IPlc AddRegisters (this IPlc plc, Action builder) { - var assembler = new RegisterAssembler((MewtocolInterface)plc); - var regBuilder = new RBuildMult((MewtocolInterface)plc); + // var assembler = new RegisterAssembler((MewtocolInterface)plc); + // var regBuilder = new RBuild((MewtocolInterface)plc); - builder.Invoke(regBuilder); + // builder.Invoke(regBuilder); - var registers = assembler.AssembleAll(regBuilder); + // var registers = assembler.AssembleAll(regBuilder); - var interf = (MewtocolInterface)plc; + // var interf = (MewtocolInterface)plc; - interf.AddRegisters(registers.ToArray()); + // interf.AddRegisters(registers.ToArray()); - return plc; + // return plc; - } + //} } diff --git a/MewtocolNet/RegisterBuilding/RegisterAssembler.cs b/MewtocolNet/RegisterBuilding/RegisterAssembler.cs index c70dbe3..7cdbe77 100644 --- a/MewtocolNet/RegisterBuilding/RegisterAssembler.cs +++ b/MewtocolNet/RegisterBuilding/RegisterAssembler.cs @@ -1,4 +1,5 @@ using MewtocolNet.RegisterAttributes; +using MewtocolNet.RegisterBuilding.BuilderPatterns; using MewtocolNet.Registers; using System; using System.Collections.Generic; @@ -13,33 +14,15 @@ namespace MewtocolNet.RegisterBuilding { internal MewtocolInterface onInterface; + internal List assembled = new List(); + internal RegisterAssembler(MewtocolInterface interf) { onInterface = interf; } - internal List AssembleAll(RBuildBase rBuildData, bool flagAutoGenerated = false) { - - List generatedInstances = new List(); - - foreach (var data in rBuildData.unfinishedList) { - - var generatedInstance = Assemble(data); - - generatedInstance.autoGenerated = flagAutoGenerated; - - data?.InvokeBuilt(generatedInstance); - - generatedInstances.Add(generatedInstance); - - } - - return generatedInstances; - - } - - internal Register Assemble(BaseStepData data) { + internal Register Assemble(StepData data) { Register generatedInstance = null; @@ -69,7 +52,7 @@ namespace MewtocolNet.RegisterBuilding { var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; - Type paramedClass = typeof(ArrayRegister<>).MakeGenericType(data.dotnetVarType); + Type paramedClass = typeof(ArrayRegister<>).MakeGenericType(data.dotnetVarType.GetElementType()); ConstructorInfo constr = paramedClass.GetConstructor(flags, null, new Type[] { typeof(uint), typeof(uint), @@ -86,10 +69,10 @@ namespace MewtocolNet.RegisterBuilding { generatedInstance = instance; - } else if (!data.regType.IsBoolean() && data.dotnetVarType.IsAllowedPlcCastingType()) { + } else if (!data.regType.IsBoolean() && data.dotnetVarType.IsAllowedPlcCastingType() && data.dotnetVarType != typeof(string)) { //------------------------------------------- - //as single register + //as struct register uint numericSize = (uint)data.dotnetVarType.DetermineTypeByteIntialSize(); @@ -100,18 +83,6 @@ namespace MewtocolNet.RegisterBuilding { throw new NotSupportedException($"Enums not based on 16 or 32 bit numbers are not supported"); } } - - if(data.dotnetVarType == typeof(string)) { - - if(data.byteSizeHint == null) - throw new NotSupportedException($"Can't create a STRING register without a string size hint"); - - if(data.byteSizeHint < 0) - throw new NotSupportedException($"Can't create a STRING register with a string size hint < 0"); - - numericSize = 4 + data.byteSizeHint.Value; - - } var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; @@ -130,6 +101,29 @@ namespace MewtocolNet.RegisterBuilding { generatedInstance = instance; + } else if (!data.regType.IsBoolean() && data.dotnetVarType.IsAllowedPlcCastingType()) { + + //------------------------------------------- + //as string register + + uint numericSize = 0; + + if (data.dotnetVarType == typeof(string)) { + + if (data.byteSizeHint == null) + throw new NotSupportedException($"Can't create a STRING register without a string size hint"); + + if (data.byteSizeHint < 0) + throw new NotSupportedException($"Can't create a STRING register with a string size hint < 0"); + + numericSize = data.byteSizeHint.Value; + + } + + var instance = (Register)new StringRegister(data.memAddress, numericSize, data.name); + + generatedInstance = instance; + } else if (data.regType.IsBoolean()) { //------------------------------------------- @@ -162,6 +156,10 @@ namespace MewtocolNet.RegisterBuilding { generatedInstance.underlyingSystemType = data.dotnetVarType; generatedInstance.pollLevel = data.pollLevel; + if (data.regCollection != null) + generatedInstance.autoGenerated = true; + + assembled.Add(generatedInstance); return generatedInstance; } diff --git a/MewtocolNet/RegisterBuilding/StepBaseTyper.cs b/MewtocolNet/RegisterBuilding/StepBaseTyper.cs new file mode 100644 index 0000000..6585c58 --- /dev/null +++ b/MewtocolNet/RegisterBuilding/StepBaseTyper.cs @@ -0,0 +1,234 @@ +using MewtocolNet.PublicEnums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; + +namespace MewtocolNet.RegisterBuilding { + + internal static class StepBaseTyper { + + /// + /// Sets the register as a dotnet type for direct conversion + /// + /// + /// + /// + internal static StepBase AsType(this StepBase b) { + + if (!typeof(T).IsAllowedPlcCastingType()) { + + throw new NotSupportedException($"The dotnet type {typeof(T)}, is not supported for PLC type casting"); + + } + + b.Data.dotnetVarType = typeof(T); + + return b; + + } + + /// + /// Sets the register as a dotnet type for direct conversion + /// + /// + /// + /// + /// + internal static StepBase AsType(this StepBase b, Type type) { + + //for internal only, relay to AsType from string + if (b.Data.buildSource == RegisterBuildSource.Attribute) { + + if ((type.IsArray || type == typeof(string)) && b.Data.typeDef != null) { + + return b.AsType(b.Data.typeDef); + + } else if (type.IsArray && b.Data.typeDef == null) { + + throw new NotSupportedException("Typedef parameter is needed for array types"); + + } else if (b.Data.typeDef != null) { + + throw new NotSupportedException("Can't use the typedef parameter on non array or string types"); + + } + + } + + if (!type.IsAllowedPlcCastingType()) { + + throw new NotSupportedException($"The dotnet type {type}, is not supported for PLC type casting"); + + } + + b.Data.dotnetVarType = type; + + return b; + + } + + /// + /// Sets the register type as a predefined + /// + internal static StepBase AsType(this StepBase b, PlcVarType type) { + + b.Data.dotnetVarType = type.GetDefaultDotnetType(); + + return b; + + } + + /// + /// Sets the register type from the plc type string
+ /// Supported types: + /// + /// BOOLBoolean R/X/Y registers + /// INT16 bit signed integer + /// UINT16 bit un-signed integer + /// DINT32 bit signed integer + /// UDINT32 bit un-signed integer + /// REAL32 bit floating point + /// TIME32 bit time interpreted as + /// STRINGString of chars, the interface will automatically get the length + /// STRING[N]String of chars, pre capped to N + /// WORD16 bit word interpreted as + /// DWORD32 bit double word interpreted as + /// + ///
+ internal static StepBase AsType(this StepBase b, string type) { + + var regexString = new Regex(@"^STRING *\[(?[0-9]*)\]$", RegexOptions.IgnoreCase); + var regexArray = new Regex(@"^ARRAY *\[(?[0-9]*)..(?[0-9]*)(?:\,(?[0-9]*)..(?[0-9]*))?(?:\,(?[0-9]*)..(?[0-9]*))?\] *OF {1,}(?.*)$", RegexOptions.IgnoreCase); + + var stringMatch = regexString.Match(type); + var arrayMatch = regexArray.Match(type); + + if (Enum.TryParse(type, out var parsed)) { + + b.Data.dotnetVarType = parsed.GetDefaultDotnetType(); + + } else if (stringMatch.Success) { + + b.Data.dotnetVarType = typeof(string); + b.Data.byteSizeHint = uint.Parse(stringMatch.Groups["len"].Value); + + } else if (arrayMatch.Success) { + + //invoke generic AsTypeArray + + string arrTypeString = arrayMatch.Groups["t"].Value; + Type dotnetArrType = null; + + var stringMatchInArray = regexString.Match(arrTypeString); + + if (Enum.TryParse(arrTypeString, out var parsedArrType) && parsedArrType != PlcVarType.STRING) { + + dotnetArrType = parsedArrType.GetDefaultDotnetType(); + + + } else if (stringMatchInArray.Success) { + + dotnetArrType = typeof(string); + //Data.byteSizeHint = uint.Parse(stringMatch.Groups["len"].Value); + + } else { + + throw new NotSupportedException($"The FP type '{arrTypeString}' was not recognized"); + + } + + var indices = new List(); + + for (int i = 1; i < 4; i++) { + + var arrStart = arrayMatch.Groups[$"S{i}"]?.Value; + var arrEnd = arrayMatch.Groups[$"E{i}"]?.Value; + if (string.IsNullOrEmpty(arrStart) || string.IsNullOrEmpty(arrEnd)) break; + + var arrStartInt = int.Parse(arrStart); + var arrEndInt = int.Parse(arrEnd); + + indices.Add(arrEndInt - arrStartInt + 1); + + } + + var arr = Array.CreateInstance(dotnetArrType, indices.ToArray()); + var arrType = arr.GetType(); + + MethodInfo method = typeof(StepBaseTyper).GetMethod(nameof(AsTypeArray)); + MethodInfo generic = method.MakeGenericMethod(arrType); + + var tmp = (StepBase)generic.Invoke(null, new object[] { + b, + indices.ToArray() + }); + + return tmp; + + } else { + + throw new NotSupportedException($"The FP type '{type}' was not recognized"); + + } + + return b; + + } + + /// + /// Sets the register as a (multidimensional) array targeting a PLC array + /// + /// + /// + /// + /// + /// Indicies for multi dimensional arrays, for normal arrays just one INT + /// + /// + /// One dimensional arrays:
+ /// ARRAY [0..2] OF INT = AsTypeArray<short[]>(3)
+ /// ARRAY [5..6] OF DWORD = AsTypeArray<DWord[]>(2)
+ ///
+ /// Multi dimensional arrays:
+ /// ARRAY [0..2, 0..3, 0..4] OF INT = AsTypeArray<short[,,]>(3,4,5)
+ /// ARRAY [5..6, 0..2] OF DWORD = AsTypeArray<DWord[,]>(2, 3)
+ ///
+ internal static StepBase AsTypeArray(this StepBase b, params int[] indicies) { + + if (!typeof(T).IsArray) + throw new NotSupportedException($"The type {typeof(T)} was no array"); + + var arrRank = typeof(T).GetArrayRank(); + var elBaseType = typeof(T).GetElementType(); + + if (arrRank > 3) + throw new NotSupportedException($"4+ dimensional arrays are not supported"); + + if (typeof(T) != typeof(byte[]) && !elBaseType.IsAllowedPlcCastingType()) + throw new NotSupportedException($"The dotnet type {typeof(T)}, is not supported for PLC array type casting"); + + if (arrRank != indicies.Length) + throw new NotSupportedException($"All dimensional array indicies must be set"); + + b.Data.dotnetVarType = typeof(T); + + int byteSizePerItem = elBaseType.DetermineTypeByteIntialSize(); + int calcedTotalByteSize = indicies.Aggregate((a, x) => a * x) * byteSizePerItem; + + b.Data.byteSizeHint = (uint)calcedTotalByteSize; + b.Data.arrayIndicies = indicies; + + if (b.Data.byteSizeHint % byteSizePerItem != 0) { + throw new NotSupportedException($"The array element type {elBaseType} doesn't fit into the adress range"); + } + + return b; + + } + + } + + +} diff --git a/MewtocolNet/RegisterBuilding/StepData.cs b/MewtocolNet/RegisterBuilding/StepData.cs index edb2d80..9c258e1 100644 --- a/MewtocolNet/RegisterBuilding/StepData.cs +++ b/MewtocolNet/RegisterBuilding/StepData.cs @@ -6,9 +6,15 @@ using System.Reflection; namespace MewtocolNet.RegisterBuilding { - internal class BaseStepData { + public class StepBase { - internal Action registerOut; + internal StepData Data; + + } + + public class StepBase : StepBase { } + + internal class StepData { internal RegisterBuildSource buildSource = RegisterBuildSource.Anonymous; @@ -33,40 +39,6 @@ namespace MewtocolNet.RegisterBuilding { internal string typeDef; - internal void InvokeBuilt(Register reg) { - - registerOut?.Invoke(reg); - - //var selftype = this.GetType(); - - //if ((selftype.IsGenericType && selftype.GetGenericTypeDefinition() == typeof(StepData<>))) { - - // var field = selftype.GetField("registerOut", BindingFlags.NonPublic | BindingFlags.Instance); - - // var generic = typeof(IRegister<>).MakeGenericType() - - // var action = Action.CreateDelegate(typeof(IRegister)); - - // field.SetValue(this,); - - //} - - } - - } - - internal class StepData : BaseStepData { - - //for referencing the output at builder level - //internal Action> registerOut; - - } - - internal class StepData : BaseStepData { - - //for referencing the output at builder level - //internal Action registerOut; - } } diff --git a/MewtocolNet/Registers/Base/IStringRegister.cs b/MewtocolNet/Registers/Base/IStringRegister.cs deleted file mode 100644 index 48da7a9..0000000 --- a/MewtocolNet/Registers/Base/IStringRegister.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Threading.Tasks; - -namespace MewtocolNet.Registers { - public interface IStringRegister : IRegister where T : class { - - /// - /// The current value of the register - /// - T Value { get; } - - /// - /// Reads the register value async from the plc - /// - /// The register value - Task ReadAsync(); - - /// - /// Writes the register content async to the plc - /// - /// True if successfully set - Task WriteAsync(T data); - - } - -} diff --git a/MewtocolNet/Registers/Base/Register.cs b/MewtocolNet/Registers/Base/Register.cs index 6ca98de..e5b114f 100644 --- a/MewtocolNet/Registers/Base/Register.cs +++ b/MewtocolNet/Registers/Base/Register.cs @@ -26,8 +26,9 @@ namespace MewtocolNet.Registers { internal Type underlyingSystemType; internal IMemoryArea underlyingMemory; internal bool autoGenerated; + internal bool isAnonymous; - internal object lastValue = null; + internal protected object lastValue = null; internal string name; internal uint memoryAddress; internal int pollLevel = 0; @@ -189,7 +190,7 @@ namespace MewtocolNet.Registers { public override string ToString() { var sb = new StringBuilder(); - sb.Append(GetMewName()); + sb.Append(GetRegisterWordRangeString()); if (Name != null) sb.Append($" ({Name})"); sb.Append($" [{this.GetType().Name}({underlyingSystemType.Name})]"); if (ValueObj != null) sb.Append($" Val: {GetValueString()}"); diff --git a/MewtocolNet/Registers/ArrayRegister.cs b/MewtocolNet/Registers/Classes/ArrayRegister.cs similarity index 50% rename from MewtocolNet/Registers/ArrayRegister.cs rename to MewtocolNet/Registers/Classes/ArrayRegister.cs index 7413841..96a2afd 100644 --- a/MewtocolNet/Registers/ArrayRegister.cs +++ b/MewtocolNet/Registers/Classes/ArrayRegister.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Data; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -10,7 +11,10 @@ namespace MewtocolNet.Registers { /// /// Defines a register containing a string /// - public class ArrayRegister : Register, IArrayRegister { + public class ArrayRegister : Register, IArrayRegister, IArrayRegister2D, IArrayRegister3D { + + internal int byteSizePerItem; + internal uint reservedByteSize; internal int[] indices; @@ -21,87 +25,124 @@ namespace MewtocolNet.Registers { /// public uint AddressLength => addressLength; - public T[] Value => (T[])ValueObj; + T[] IArrayRegister.Value => (T[])ValueObj; + + T[,] IArrayRegister2D.Value => (T[,])ValueObj; + + T[,,] IArrayRegister3D.Value => (T[,,])ValueObj; + + public int Count => ((Array)ValueObj).Length; + + public T this[int i1, int i2, int i3] => (T)((Array)ValueObj).GetValue(i1, i2, i3); + + public T this[int i1, int i2] => (T)((Array)ValueObj).GetValue(i1, i2); + + public T this[int i] => (T)((Array)ValueObj).GetValue(i); [Obsolete("Creating registers directly is not supported use IPlc.Register instead")] public ArrayRegister() => throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern"); - internal ArrayRegister(uint _address, uint _reservedByteSize, int[] _indicies, string _name = null) { + internal ArrayRegister(uint _address, uint _reservedByteSize, int[] _indices, string _name = null) { + + if (_reservedByteSize % 2 != 0) + throw new ArgumentException(nameof(_reservedByteSize), "Reserved byte size must be even"); name = _name; memoryAddress = _address; - indices = _indicies; + indices = _indices; - //calc mem length - //because one register is always 1 word (2 bytes) long, if the bytecount is uneven we get the trailing word too - var byteSize = _reservedByteSize; - if (byteSize % 2 != 0) byteSize++; + int itemCount = _indices.Aggregate((a, x) => a * x); + byteSizePerItem = (int)_reservedByteSize / itemCount; + reservedByteSize = _reservedByteSize; RegisterType = RegisterType.DT_BYTE_RANGE; - addressLength = Math.Max((byteSize / 2), 1); + addressLength = Math.Max((_reservedByteSize / 2), 1); CheckAddressOverflow(memoryAddress, addressLength); + underlyingSystemType = typeof(T).MakeArrayType(indices.Length); + lastValue = null; - } - public override string GetValueString() { + public IEnumerator GetEnumerator() => ((Array)ValueObj).OfType().GetEnumerator(); - if (ValueObj == null) return "null"; + IEnumerator IEnumerable.GetEnumerator() => ((Array)ValueObj).OfType().GetEnumerator(); - if(typeof(T) == typeof(byte[])) { + async Task IArrayRegister.ReadAsync() => (T[])(object)await ReadAsync(); - return ((byte[])ValueObj).ToHexString("-"); + async Task IArrayRegister2D.ReadAsync() => (T[,])(object)await ReadAsync(); - } + async Task IArrayRegister3D.ReadAsync() => (T[,,])(object)await ReadAsync(); - StringBuilder sb = new StringBuilder(); - var valueIenum = (IEnumerable)ValueObj; + async Task IArrayRegister.WriteAsync(T[] data) => await WriteAsync(data); - foreach (var el in valueIenum) { + async Task IArrayRegister2D.WriteAsync(T[,] data) => await WriteAsync(data); - sb.Append($"{el}, "); - - } - - return ArrayToString((Array)ValueObj); - - } + async Task IArrayRegister3D.WriteAsync(T[,,] data) => await WriteAsync(data); /// - public override string GetRegisterString() => "DT"; - - /// - public override uint GetRegisterAddressLen() => AddressLength; - - /// - public async Task WriteAsync(T value) { + private async Task WriteAsync(object value) { var encoded = PlcValueParser.EncodeArray(this, value); + var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded); if (res) { - //find the underlying memory - var matchingReg = attachedInterface.memoryManager.GetAllRegisters() - .FirstOrDefault(x => x.IsSameAddressAndType(this)); + if(isAnonymous) { - if (matchingReg != null) - matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, encoded); + //find the underlying memory + var matchingReg = attachedInterface.memoryManager.GetAllRegisters() + .FirstOrDefault(x => x.IsSameAddressAndType(this)); + + if (matchingReg != null) matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, encoded); + + } else { + + underlyingMemory.SetUnderlyingBytes(this, encoded); + + } AddSuccessWrite(); UpdateHoldingValue(value); } - return res; + } + + private async Task WriteEntriesAsync(int start, object value) { + + var encoded = PlcValueParser.EncodeArray(this, value); + + var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded); + + if (res) { + + if (isAnonymous) { + + //find the underlying memory + var matchingReg = attachedInterface.memoryManager.GetAllRegisters() + .FirstOrDefault(x => x.IsSameAddressAndType(this)); + + if (matchingReg != null) matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, encoded); + + } else { + + underlyingMemory.SetUnderlyingBytes(this, encoded); + + } + + AddSuccessWrite(); + UpdateHoldingValue(value); + + } } /// - async Task IArrayRegister.ReadAsync() { + private async Task ReadAsync() { var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2); if (res == null) throw new Exception(); @@ -112,15 +153,18 @@ namespace MewtocolNet.Registers { if (matchingReg != null) matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, res); - return (T[])SetValueFromBytes(res); + return SetValueFromBytes(res); } internal override object SetValueFromBytes(byte[] bytes) { + if (bytes.Length != reservedByteSize) + throw new ArgumentException(nameof(bytes), "Bytes were not equal the size of registers reserved byte size"); + AddSuccessRead(); - var parsed = PlcValueParser.ParseArray(this, indices, bytes); + var parsed = PlcValueParser.ParseArray(this, bytes); UpdateHoldingValue(parsed); return parsed; @@ -130,13 +174,22 @@ namespace MewtocolNet.Registers { /// internal override void UpdateHoldingValue(object val) { - bool changeTriggerBitArr = val is BitArray bitArr && - lastValue is BitArray bitArr2 && - (bitArr.ToBitString() != bitArr2.ToBitString()); + if (val == null && lastValue == null) return; - bool changeTriggerGeneral = (lastValue?.ToString() != val?.ToString()); + bool sequenceDifference = false; - if (changeTriggerBitArr || changeTriggerGeneral) { + if(val == null && lastValue != null) sequenceDifference = true; + if(val != null && lastValue == null) sequenceDifference = true; + if (val != null && lastValue != null) { + + var val1 = ((Array)val).OfType(); + var val2 = ((Array)lastValue).OfType(); + + sequenceDifference = !Enumerable.SequenceEqual(val1, val2); + + } + + if (sequenceDifference) { var beforeVal = lastValue; var beforeValStr = GetValueString(); @@ -150,6 +203,25 @@ namespace MewtocolNet.Registers { } + public override string GetValueString() { + + if (ValueObj == null) return "null"; + + if (underlyingSystemType == typeof(byte[])) { + + return ((byte[])ValueObj).ToHexString("-"); + + } + + return ArrayToString((Array)ValueObj); + + } + + /// + public override string GetRegisterString() => "DT"; + + /// + public override uint GetRegisterAddressLen() => AddressLength; private string ArrayToString(Array array) { @@ -195,6 +267,7 @@ namespace MewtocolNet.Registers { } + } } diff --git a/MewtocolNet/Registers/BoolRegister.cs b/MewtocolNet/Registers/Classes/BoolRegister.cs similarity index 100% rename from MewtocolNet/Registers/BoolRegister.cs rename to MewtocolNet/Registers/Classes/BoolRegister.cs diff --git a/MewtocolNet/Registers/Classes/StringRegister.cs b/MewtocolNet/Registers/Classes/StringRegister.cs new file mode 100644 index 0000000..586f66b --- /dev/null +++ b/MewtocolNet/Registers/Classes/StringRegister.cs @@ -0,0 +1,158 @@ +using MewtocolNet.Logging; +using System; +using System.Collections; +using System.Drawing; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace MewtocolNet.Registers { + + /// + /// Defines a register containing a number + /// + public class StringRegister : Register, IStringRegister { + + internal int reservedStringLength; + internal uint byteLength; + + internal uint addressLength; + + /// + /// The rgisters memory length + /// + public uint AddressLength => addressLength; + + public string Value => (string)ValueObj; + + [Obsolete("Creating registers directly is not supported use IPlc.Register instead")] + public StringRegister() => + throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern"); + + internal StringRegister(uint _address, uint _reservedByteSize, string _name = null) { + + memoryAddress = _address; + name = _name; + + reservedStringLength = (int)_reservedByteSize; + Resize(_reservedByteSize); + + RegisterType = RegisterType.DT_BYTE_RANGE; + + CheckAddressOverflow(memoryAddress, addressLength); + + lastValue = null; + + } + + private void Resize (uint reservedByteSize) { + + if (reservedByteSize % 2 != 0) reservedByteSize++; + reservedByteSize += 4; + + addressLength = reservedByteSize / 2; + byteLength = reservedByteSize; + + } + + /// + public override string GetAsPLC() => Value; + + /// + public override string GetValueString() => ValueObj?.ToString() ?? "null"; + + /// + public override uint GetRegisterAddressLen() => AddressLength; + + /// + public async Task WriteAsync(string value) { + + //trim the size if the input was larger + if(value.Length > reservedStringLength) { + value = value.Substring(0, reservedStringLength); + } + + var encoded = PlcValueParser.Encode(this, value); + var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded); + + if (res) { + + //find the underlying memory + var matchingReg = attachedInterface.memoryManager.GetAllRegisters() + .FirstOrDefault(x => x.IsSameAddressAndType(this)); + + if (matchingReg != null) + matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, encoded); + + AddSuccessWrite(); + UpdateHoldingValue(value); + + } + + } + + /// + public async Task ReadAsync() { + + var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2); + if (res == null) return null; + + var matchingReg = attachedInterface.memoryManager.GetAllRegisters() + .FirstOrDefault(x => x.IsSameAddressAndType(this)); + + if (matchingReg != null) { + + if (matchingReg is StringRegister sreg && this is StringRegister selfSreg) { + + sreg.addressLength = selfSreg.addressLength; + + } + + matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, res); + + } + + return (string)SetValueFromBytes(res); + + } + + internal override object SetValueFromBytes (byte[] bytes) { + + //if string correct the sizing of the byte hint was wrong + var reservedSize = BitConverter.ToInt16(bytes, 0); + if (reservedSize != byteLength - 4) + throw new NotSupportedException( + $"The STRING register at {GetMewName()} is not correctly sized, " + + $"the size should be STRING[{reservedSize}] instead of STRING[{byteLength - 4}]" + ); + + AddSuccessRead(); + + var parsed = PlcValueParser.Parse(this, bytes); + + UpdateHoldingValue(parsed); + return parsed; + + } + + internal override void UpdateHoldingValue(object val) { + + if (lastValue?.ToString() != val?.ToString()) { + + var beforeVal = lastValue; + var beforeValStr = GetValueString(); + + lastValue = val; + + TriggerNotifyChange(); + attachedInterface.InvokeRegisterChanged(this, beforeVal, beforeValStr); + + } + + } + + } + +} diff --git a/MewtocolNet/Registers/StructRegister.cs b/MewtocolNet/Registers/Classes/StructRegister.cs similarity index 77% rename from MewtocolNet/Registers/StructRegister.cs rename to MewtocolNet/Registers/Classes/StructRegister.cs index a4296f9..328662f 100644 --- a/MewtocolNet/Registers/StructRegister.cs +++ b/MewtocolNet/Registers/Classes/StructRegister.cs @@ -16,8 +16,6 @@ namespace MewtocolNet.Registers { /// The type of the numeric value public class StructRegister : Register, IRegister where T : struct { - internal uint byteLength; - internal uint addressLength; /// @@ -35,26 +33,20 @@ namespace MewtocolNet.Registers { memoryAddress = _address; name = _name; - Resize(_reservedByteSize); + + addressLength = _reservedByteSize / 2; + if (_reservedByteSize % 2 != 0) addressLength++; if (_reservedByteSize == 2) RegisterType = RegisterType.DT; if(_reservedByteSize == 4) RegisterType = RegisterType.DDT; - if (typeof(T) == typeof(string)) RegisterType = RegisterType.DT_BYTE_RANGE; CheckAddressOverflow(memoryAddress, addressLength); + underlyingSystemType = typeof(T); lastValue = null; } - private void Resize (uint reservedByteSize) { - - addressLength = reservedByteSize / 2; - if (reservedByteSize % 2 != 0) addressLength++; - byteLength = reservedByteSize; - - } - /// public override string GetAsPLC() { @@ -92,7 +84,7 @@ namespace MewtocolNet.Registers { public override uint GetRegisterAddressLen() => AddressLength; /// - public async Task WriteAsync(T value) { + public async Task WriteAsync(T value) { var encoded = PlcValueParser.Encode(this, (T)value); var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded); @@ -111,27 +103,19 @@ namespace MewtocolNet.Registers { } - return res; - } /// - public async Task ReadAsync() { + public async Task ReadAsync() { var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2); - if (res == null) return (T?)(object)null; + if (res == null) throw new Exception($"Failed to read the register {this}"); var matchingReg = attachedInterface.memoryManager.GetAllRegisters() .FirstOrDefault(x => x.IsSameAddressAndType(this)); if (matchingReg != null) { - //if (matchingReg is StructRegister sreg && this.GetType() == typeof(StructRegister)) { - - // sreg.addressLength = selfSreg.addressLength; - - //} - matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, res); } @@ -142,16 +126,6 @@ namespace MewtocolNet.Registers { internal override object SetValueFromBytes (byte[] bytes) { - //if string correct the sizing of the byte hint was wrong - if (typeof(T) == typeof(string)) { - var reservedSize = BitConverter.ToInt16(bytes, 0); - if (reservedSize != byteLength - 4) - throw new NotSupportedException( - $"The STRING register at {GetMewName()} is not correctly sized, " + - $"the size should be STRING[{reservedSize}] instead of STRING[{byteLength - 4}]" - ); - } - AddSuccessRead(); var parsed = PlcValueParser.Parse(this, bytes); diff --git a/MewtocolNet/Registers/IArrayRegister.cs b/MewtocolNet/Registers/IArrayRegister.cs new file mode 100644 index 0000000..64e283c --- /dev/null +++ b/MewtocolNet/Registers/IArrayRegister.cs @@ -0,0 +1,43 @@ +using System.Collections; +using System.Collections.ObjectModel; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Threading.Tasks; + +namespace MewtocolNet.Registers { + + /// + /// Provides an abstruct enumerable interface for one dimensional array registers + /// + public interface IArrayRegister : IReadOnlyList, IRegister { + + /// + /// Reads the register value async from the plc + /// + /// The register value + Task ReadAsync(); + + /// + /// Writes a whole array to the plc + /// + /// True if successfully set + Task WriteAsync(T[] data); + + ///// + ///// Writes a single item to the array, this saves bandwidth + ///// + ///// Index of the element to write + ///// The value to overwrite + ///// True if successfully set + //Task WriteEntryAsync(int i, T data); + + T[] Value { get; } + + /// + /// The current value of the register at the position + /// + new T this[int i] { get; } + + } + +} diff --git a/MewtocolNet/Registers/IArrayRegister2D.cs b/MewtocolNet/Registers/IArrayRegister2D.cs new file mode 100644 index 0000000..9e7f4c7 --- /dev/null +++ b/MewtocolNet/Registers/IArrayRegister2D.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MewtocolNet.Registers { + + /// + /// Provides an abstruct enumerable interface for two dimensional array registers + /// + public interface IArrayRegister2D : IReadOnlyList, IRegister { + + /// + /// Reads the register value async from the plc + /// + /// The register value + Task ReadAsync(); + + /// + /// Writes a whole array to the plc + /// + /// True if successfully set + Task WriteAsync(T[,] data); + + T[,] Value { get; } + + /// + /// The current value of the register at the position + /// + T this[int i1, int i2] { get; } + + } + +} diff --git a/MewtocolNet/Registers/IArrayRegister3D.cs b/MewtocolNet/Registers/IArrayRegister3D.cs new file mode 100644 index 0000000..52cd431 --- /dev/null +++ b/MewtocolNet/Registers/IArrayRegister3D.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MewtocolNet.Registers { + + /// + /// Provides an abstruct enumerable interface for three dimensional array registers + /// + public interface IArrayRegister3D : IReadOnlyList, IRegister { + + /// + /// Reads the register value async from the plc + /// + /// The register value + Task ReadAsync(); + + /// + /// Writes a whole array to the plc + /// + /// True if successfully set + Task WriteAsync(T[,,] data); + + T[,,] Value { get; } + + /// + /// The current value of the register at the position + /// + T this[int i1, int i2, int i3] { get; } + + } + +} diff --git a/MewtocolNet/Registers/Base/IRegisterT.cs b/MewtocolNet/Registers/IRegister.cs similarity index 85% rename from MewtocolNet/Registers/Base/IRegisterT.cs rename to MewtocolNet/Registers/IRegister.cs index 6ffa350..5a4b85d 100644 --- a/MewtocolNet/Registers/Base/IRegisterT.cs +++ b/MewtocolNet/Registers/IRegister.cs @@ -5,7 +5,7 @@ using MewtocolNet.Events; namespace MewtocolNet.Registers { /// - /// An interface for all register types + /// An interface for all struct register types /// public interface IRegister : IRegister where T : struct { @@ -18,13 +18,13 @@ namespace MewtocolNet.Registers { /// Reads the register value async from the plc /// /// The register value - Task ReadAsync(); + Task ReadAsync(); /// /// Writes the register content async to the plc /// /// True if successfully set - Task WriteAsync(T data); + Task WriteAsync(T data); } diff --git a/MewtocolNet/Registers/Base/IArrayRegister.cs b/MewtocolNet/Registers/IStringRegister.cs similarity index 75% rename from MewtocolNet/Registers/Base/IArrayRegister.cs rename to MewtocolNet/Registers/IStringRegister.cs index 7cdb364..999df0c 100644 --- a/MewtocolNet/Registers/Base/IArrayRegister.cs +++ b/MewtocolNet/Registers/IStringRegister.cs @@ -2,24 +2,24 @@ namespace MewtocolNet.Registers { - public interface IArrayRegister : IRegister { + public interface IStringRegister : IRegister { /// /// The current value of the register /// - T[] Value { get; } + string Value { get; } /// /// Reads the register value async from the plc /// /// The register value - Task ReadAsync(); + Task ReadAsync(); /// /// Writes the register content async to the plc /// /// True if successfully set - Task WriteAsync(T data); + Task WriteAsync(string data); } diff --git a/MewtocolNet/SetupClasses/PollLevelConfig.cs b/MewtocolNet/SetupClasses/PollLevelConfig.cs index 9e44f10..1d15cb0 100644 --- a/MewtocolNet/SetupClasses/PollLevelConfig.cs +++ b/MewtocolNet/SetupClasses/PollLevelConfig.cs @@ -5,6 +5,10 @@ namespace MewtocolNet.SetupClasses { internal class PollLevelConfig { + internal bool skipsAll; + + internal bool skipAllButFirst; + internal TimeSpan? delay; internal int? skipNth; diff --git a/MewtocolNet/TypeConversion/Conversions.cs b/MewtocolNet/TypeConversion/Conversions.cs index 1788939..31997bd 100644 --- a/MewtocolNet/TypeConversion/Conversions.cs +++ b/MewtocolNet/TypeConversion/Conversions.cs @@ -130,48 +130,48 @@ namespace MewtocolNet.TypeConversion { //default string DT Range conversion Example bytes: (04 00 03 00 XX XX XX) //first 4 bytes are reserved size (2 bytes) and used size (2 bytes) //the remaining bytes are the ascii bytes for the string - //new PlcTypeConversion(RegisterType.DT_BYTE_RANGE) { - // HoldingRegisterType = typeof(StructRegister), - // PlcVarType = PlcVarType.STRING, - // FromRaw = (reg, bytes) => { + new PlcTypeConversion(RegisterType.DT_BYTE_RANGE) { + HoldingRegisterType = typeof(StringRegister), + PlcVarType = PlcVarType.STRING, + FromRaw = (reg, bytes) => { - // if(bytes.Length == 4) return string.Empty; + if(bytes.Length == 4) return string.Empty; - // if(bytes == null || bytes.Length < 4) { + if(bytes == null || bytes.Length < 4) { - // throw new Exception("Failed to convert string bytes, response not long enough"); + throw new Exception("Failed to convert string bytes, response not long enough"); - // } + } - // //get actual showed size - // short actualLen = BitConverter.ToInt16(bytes, 2); + //get actual showed size + short actualLen = BitConverter.ToInt16(bytes, 2); - // //skip 4 bytes because they only describe the length - // string gotVal = Encoding.UTF8.GetString(bytes.Skip(4).Take(actualLen).ToArray()); + //skip 4 bytes because they only describe the length + string gotVal = Encoding.UTF8.GetString(bytes.Skip(4).Take(actualLen).ToArray()); - // return gotVal; + return gotVal; - // }, - // ToRaw = (reg, value) => { + }, + ToRaw = (reg, value) => { - // int padLen = value.Length; - // if(value.Length % 2 != 0) padLen++; + int padLen = value.Length; + if(value.Length % 2 != 0) padLen++; - // var strBytes = Encoding.UTF8.GetBytes(value.PadRight(padLen, '\0')); + var strBytes = Encoding.UTF8.GetBytes(value.PadRight(padLen, '\0')); - // List finalBytes = new List(); + List finalBytes = new List(); - // short reserved = (short)(reg.GetRegisterAddressLen() * 2 - 4); - // short used = (short)value.Length; + short reserved = (short)(reg.GetRegisterAddressLen() * 2 - 4); + short used = (short)value.Length; - // finalBytes.AddRange(BitConverter.GetBytes(reserved)); - // finalBytes.AddRange(BitConverter.GetBytes(used)); - // finalBytes.AddRange(strBytes); + finalBytes.AddRange(BitConverter.GetBytes(reserved)); + finalBytes.AddRange(BitConverter.GetBytes(used)); + finalBytes.AddRange(strBytes); - // return finalBytes.ToArray(); + return finalBytes.ToArray(); - // }, - //}, + }, + }, }; diff --git a/MewtocolNet/TypeConversion/PlcValueParser.cs b/MewtocolNet/TypeConversion/PlcValueParser.cs index f2dad34..2366bbe 100644 --- a/MewtocolNet/TypeConversion/PlcValueParser.cs +++ b/MewtocolNet/TypeConversion/PlcValueParser.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Text; namespace MewtocolNet { @@ -36,10 +37,10 @@ namespace MewtocolNet { } - internal static T ParseArray (Register register, int[] indices, byte[] bytes) { + internal static object ParseArray (ArrayRegister register, byte[] bytes) { //if byte array directly return the bytes - if (typeof(T) == typeof(byte[])) return (T)(object)bytes; + if (typeof(T) == typeof(byte[])) return bytes; IPlcTypeConverter converter; Type underlyingElementType; @@ -47,11 +48,11 @@ namespace MewtocolNet { //special case for enums if (typeof(T).IsEnum) { - underlyingElementType = typeof(T).GetElementType().GetEnumUnderlyingType(); + underlyingElementType = typeof(T).GetEnumUnderlyingType(); } else { - underlyingElementType = typeof(T).GetElementType(); + underlyingElementType = typeof(T); } @@ -61,17 +62,18 @@ namespace MewtocolNet { throw new Exception($"A converter for the type {underlyingElementType} doesn't exist"); //parse the array from one to n dimensions - var outArray = Array.CreateInstance(underlyingElementType, indices); + var outArray = Array.CreateInstance(typeof(T), register.indices); int sizePerItem = 0; + if(underlyingElementType == typeof(string)) { - throw new NotImplementedException(); + sizePerItem = register.byteSizePerItem; } else { sizePerItem = underlyingElementType.DetermineTypeByteIntialSize(); } - var iterateItems = indices.Aggregate((a, x) => a * x); - var indexer = new int[indices.Length]; + var iterateItems = register.indices.Aggregate((a, x) => a * x); + var indexer = new int[register.indices.Length]; for (int i = 0; i < iterateItems; i++) { int j = i * sizePerItem; @@ -79,9 +81,9 @@ namespace MewtocolNet { var currentItem = bytes.Skip(j).Take(sizePerItem).ToArray(); var value = converter.FromRawData(register, currentItem); - for (int remainder = i, k = indices.Length - 1; k >= 0; k--) { + for (int remainder = i, k = register.indices.Length - 1; k >= 0; k--) { - int currentDimension = indices[k]; + int currentDimension = register.indices[k]; indexer[k] = remainder % currentDimension; remainder = remainder / currentDimension; @@ -91,7 +93,7 @@ namespace MewtocolNet { } - return (T)(object)outArray; + return (object)outArray; } @@ -120,10 +122,10 @@ namespace MewtocolNet { } - internal static byte[] EncodeArray(Register register, T value) { + internal static byte[] EncodeArray(ArrayRegister register, object value) { //if byte array directly return the bytes - if (typeof(T) == typeof(byte[])) return (byte[])(object)value; + if (value.GetType() == typeof(byte[])) return (byte[])value; IPlcTypeConverter converter; Type underlyingElementType; @@ -131,11 +133,11 @@ namespace MewtocolNet { //special case for enums if (typeof(T).IsEnum) { - underlyingElementType = typeof(T).GetElementType().GetEnumUnderlyingType(); + underlyingElementType = typeof(T).GetEnumUnderlyingType(); } else { - underlyingElementType = typeof(T).GetElementType(); + underlyingElementType = typeof(T); } @@ -145,8 +147,9 @@ namespace MewtocolNet { throw new Exception($"A converter for the type {underlyingElementType} doesn't exist"); int sizePerItem = 0; + if (underlyingElementType == typeof(string)) { - throw new NotImplementedException(); + sizePerItem = register.byteSizePerItem; } else { sizePerItem = underlyingElementType.DetermineTypeByteIntialSize(); } @@ -158,12 +161,19 @@ namespace MewtocolNet { var encoded = converter.ToRawData(register, item); + if(encoded.Length > register.byteSizePerItem) + throw new ArgumentOutOfRangeException(nameof(value), "Input mismatched register target size"); + encoded.CopyTo(encodedData, i); i += sizePerItem; } + + if (encodedData.Length != register.reservedByteSize) + throw new ArgumentOutOfRangeException(nameof(value), "Input mismatched register target size"); + return encodedData; } diff --git a/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs b/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs index d477e13..967f941 100644 --- a/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs +++ b/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs @@ -106,12 +106,24 @@ namespace MewtocolNet.UnderlyingRegisters { private void TestPollLevelExistence(Register reg) { - if (!pollLevelConfigs.ContainsKey(1)) { - pollLevelConfigs.Add(1, new PollLevelConfig { + 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)) { pollLevels.Add(new PollLevel(wrAreaSize, dtAreaSize) { @@ -294,10 +306,14 @@ namespace MewtocolNet.UnderlyingRegisters { var sw = Stopwatch.StartNew(); + var lvlConfig = pollLevelConfigs[pollLevel.level]; + + if (lvlConfig.skipsAll) continue; + if (lvlConfig.skipAllButFirst && pollIteration > 0) continue; + //determine to skip poll levels, first iteration is always polled if (pollIteration > 0 && pollLevel.level > 1) { - var lvlConfig = pollLevelConfigs[pollLevel.level]; var skipIterations = lvlConfig.skipNth; var skipDelay = lvlConfig.delay; @@ -360,14 +376,32 @@ namespace MewtocolNet.UnderlyingRegisters { foreach (var pollLevel in pollLevels) { - sb.AppendLine($"\n> ==== Poll lvl {pollLevel.level} ===="); + if (pollLevel.level == MewtocolNet.PollLevel.Always) { + + sb.AppendLine($"\n> ==== Poll lvl ALWAYS ====\n"); + sb.AppendLine($"> Poll each iteration"); + + }else if (pollLevel.level == MewtocolNet.PollLevel.FirstIteration) { + + sb.AppendLine($"\n> ==== Poll lvl FIRST ITERATION ====\n"); + sb.AppendLine($"> Poll only on the first iteration"); + + } else if (pollLevel.level == MewtocolNet.PollLevel.Never) { + + sb.AppendLine($"\n> ==== Poll lvl NEVER ====\n"); + sb.AppendLine($"> Poll never"); + + } else { + + sb.AppendLine($"\n> ==== Poll lvl {pollLevel.level} ====\n"); + if (pollLevelConfigs.ContainsKey(pollLevel.level) && pollLevelConfigs[pollLevel.level].delay != null) { + sb.AppendLine($"> Poll each {pollLevelConfigs[pollLevel.level].delay?.TotalMilliseconds}ms"); + } else if (pollLevelConfigs.ContainsKey(pollLevel.level)) { + sb.AppendLine($"> Poll every {pollLevelConfigs[pollLevel.level].skipNth} iterations"); + } - sb.AppendLine(); - if (pollLevelConfigs.ContainsKey(pollLevel.level) && pollLevelConfigs[pollLevel.level].delay != null) { - sb.AppendLine($"> Poll each {pollLevelConfigs[pollLevel.level].delay?.TotalMilliseconds}ms"); - } else if (pollLevelConfigs.ContainsKey(pollLevel.level)) { - sb.AppendLine($"> Poll every {pollLevelConfigs[pollLevel.level].skipNth} iterations"); } + sb.AppendLine($"> Level read time: {pollLevel.lastReadTimeMs}ms"); sb.AppendLine($"> Optimization distance: {maxOptimizationDistance}"); sb.AppendLine(); diff --git a/MewtocolTests/AutomatedPropertyRegisters.cs b/MewtocolTests/AutomatedPropertyRegisters.cs index 90ce676..d4188db 100644 --- a/MewtocolTests/AutomatedPropertyRegisters.cs +++ b/MewtocolTests/AutomatedPropertyRegisters.cs @@ -14,124 +14,6 @@ namespace MewtocolTests { this.output = output; } - private void Test(IRegisterInternal reg, uint expectAddr, string expectPlcName) { - - Assert.NotNull(reg); - Assert.StartsWith("auto_prop_register_", reg.Name); - Assert.Null(reg.Value); - - Assert.Equal(expectAddr, reg.MemoryAddress); - Assert.Equal(expectPlcName, reg.GetMewName()); - - output.WriteLine(reg.ToString()); - - } - - //actual tests - - [Fact(DisplayName = "Boolean generation")] - public void BooleanGen() { - - var interf = Mewtocol.Ethernet("192.168.0.1") - .WithRegisterCollections(x => - x.AddCollection(new TestBoolRegisters()) - ).Build(); - - output.WriteLine(((MewtocolInterface)interf).memoryManager.ExplainLayout()); - - var register1 = interf.GetRegister("auto_prop_register_1"); - var register2 = interf.GetRegister("auto_prop_register_2"); - var register3 = interf.GetRegister("auto_prop_register_3"); - - Test((IRegisterInternal)register1, 0, "XD"); - Test((IRegisterInternal)register2, 85, "R85A"); - Test((IRegisterInternal)register3, 85, "R85B"); - - } - - [Fact(DisplayName = "Number 16 bit generation")] - public void N16BitGen () { - - var interf = Mewtocol.Ethernet("192.168.0.1") - .WithRegisterCollections(x => - x.AddCollection(new Nums16Bit()) - ).Build(); - - var register1 = interf.GetRegister("auto_prop_register_1"); - var register2 = interf.GetRegister("auto_prop_register_2"); - var register3 = interf.GetRegister("auto_prop_register_3"); - - //test generic properties - Test((IRegisterInternal)register1, 50, "DT50"); - Test((IRegisterInternal)register2, 342, "DT342"); - Test((IRegisterInternal)register3, 899, "DT899"); - - } - - [Fact(DisplayName = "Number 32 bit generation")] - public void N32BitGen () { - - var interf = Mewtocol.Ethernet("192.168.0.1") - .WithRegisterCollections(x => x - .AddCollection(new Nums32Bit()) - ).Build(); - - output.WriteLine(((MewtocolInterface)interf).memoryManager.ExplainLayout()); - - var register1 = interf.GetRegister("auto_prop_register_1"); - var register2 = interf.GetRegister("auto_prop_register_2"); - var register3 = interf.GetRegister("auto_prop_register_3"); - - //only one generated because same type - var register4 = interf.GetRegister("auto_prop_register_4"); - - var register6 = interf.GetRegister("auto_prop_register_5"); - var register7 = interf.GetRegister("auto_prop_register_6"); - - //test generic properties - Test((IRegisterInternal)register1, 7000, "DDT7000"); - Test((IRegisterInternal)register2, 7002, "DDT7002"); - Test((IRegisterInternal)register3, 7004, "DDT7004"); - - Test((IRegisterInternal)register4, 7006, "DDT7006"); - - Test((IRegisterInternal)register6, 7008, "DDT7008"); - Test((IRegisterInternal)register7, 7010, "DDT7010"); - - } - - [Fact(DisplayName = "String generation")] - public void StringGen() { - - var interf = Mewtocol.Ethernet("192.168.0.1") - .WithRegisterCollections(x => - x.AddCollection(new TestStringRegisters()) - ).Build(); - - var register1 = interf.GetRegister("auto_prop_register_1"); - - //test generic properties - Test((IRegisterInternal)register1, 7005, "DT7005"); - - } - - [Fact(DisplayName = "Byte Array generation")] - public void ByteArrGen() { - - var interf = Mewtocol.Ethernet("192.168.0.1") - .WithRegisterCollections(x => - x.AddCollection(new TestBitwiseRegisters()) - ).Build(); - - var register1 = interf.GetRegister("auto_prop_register_1"); - //var register2 = interf.GetRegister("auto_prop_register_2"); - - //test generic properties - Test((IRegisterInternal)register1, 7000, "DT7000"); - //Test((IRegisterInternal)register2, 7001, "DT7001"); - - } - } } \ No newline at end of file diff --git a/MewtocolTests/EncapsulatedTests/RegisterReadWriteTest.cs b/MewtocolTests/EncapsulatedTests/RegisterReadWriteTest.cs deleted file mode 100644 index 3ddf4a5..0000000 --- a/MewtocolTests/EncapsulatedTests/RegisterReadWriteTest.cs +++ /dev/null @@ -1,22 +0,0 @@ -using MewtocolNet; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace MewtocolTests.EncapsulatedTests; - -internal class RegisterReadWriteTest { - - public IRegister TargetRegister { get; set; } - - public object IntialValue { get; set; } - - public object IntermediateValue { get; set; } - - public object AfterWriteValue { get; set; } - - public string RegisterPlcAddressName { get; set; } - -} diff --git a/MewtocolTests/EncapsulatedTests/TestRegisterCollection.cs b/MewtocolTests/EncapsulatedTests/TestRegisterCollection.cs index 6558f2d..ebdfe01 100644 --- a/MewtocolTests/EncapsulatedTests/TestRegisterCollection.cs +++ b/MewtocolTests/EncapsulatedTests/TestRegisterCollection.cs @@ -4,7 +4,7 @@ using System.Collections; namespace MewtocolTests.EncapsulatedTests { - public enum CurrentState : short { + public enum CurrentState16 : short { Undefined = 0, State1 = 1, State2 = 2, @@ -51,7 +51,7 @@ namespace MewtocolTests.EncapsulatedTests { public ushort UInt16Type { get; set; } [Register("DT50")] - public CurrentState Enum16Type { get; set; } + public CurrentState16 Enum16Type { get; set; } } @@ -95,8 +95,6 @@ namespace MewtocolTests.EncapsulatedTests { public class TestBitwiseRegisters : RegisterCollection { - [Register("DT7000")] - public BitArray BitArr16 { get; set; } //[Register("DT7001")] //public BitArray BitArr32 { get; set; } diff --git a/MewtocolTests/SkippedChecks.cs b/MewtocolTests/SkippedChecks.cs deleted file mode 100644 index 20857ff..0000000 --- a/MewtocolTests/SkippedChecks.cs +++ /dev/null @@ -1,36 +0,0 @@ -using MewtocolNet; -using MewtocolNet.DocAttributes; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Xunit; -using Xunit.Abstractions; - -namespace MewtocolTests; - -public class SkippedChecks { - - private readonly ITestOutputHelper output; - - public SkippedChecks(ITestOutputHelper output) { - this.output = output; - } - - [Fact] - public void BuildBCCFrameGeneration() { - - var toSuccess = new List { - - typeof(PlcCodeTestedAttribute), - typeof(PlcEXRTAttribute), - typeof(PlcLegacyAttribute), - - }; - - Assert.NotNull(toSuccess); - - } - -} diff --git a/MewtocolTests/TestHelperExtensions.cs b/MewtocolTests/TestHelperExtensions.cs index 556b409..7a18933 100644 --- a/MewtocolTests/TestHelperExtensions.cs +++ b/MewtocolTests/TestHelperExtensions.cs @@ -66,18 +66,18 @@ namespace MewtocolTests { } - [Fact(DisplayName = nameof(MewtocolHelpers.ParseDTByteString))] + [Fact(DisplayName = nameof(MewtocolHelpers.ParseDTRawStringAsBytes))] public void ParseDTByteStringGeneration() { - var testList = new List() { - "1112", - "1C2C", - "FFFF", + var testList = new List() { + new byte[] {0x11, 0x12}, + new byte[] {0x1C, 0x2C}, + new byte[] {0xFF, 0xFF}, }; foreach (var item in testList) { - Assert.Equal(item, $"%01$RD{item}".BCC_Mew().ParseDTByteString()); + Assert.Equal(item, $"%01$RD{item.ToHexString()}".BCC_Mew().ParseDTRawStringAsBytes()); } diff --git a/MewtocolTests/TestLivePLC.cs b/MewtocolTests/TestLivePLC.cs index 16dfd65..5ffce23 100644 --- a/MewtocolTests/TestLivePLC.cs +++ b/MewtocolTests/TestLivePLC.cs @@ -37,59 +37,6 @@ namespace MewtocolTests }; - private List testRegisterRW = new() { - - new RegisterReadWriteTest { - TargetRegister = new BoolRegister(IOType.R, 0xA, 10), - RegisterPlcAddressName = "R10A", - IntermediateValue = false, - AfterWriteValue = true, - }, - new RegisterReadWriteTest { - TargetRegister = new NumberRegister(3000), - RegisterPlcAddressName = "DT3000", - IntermediateValue = (short)0, - AfterWriteValue = (short)-513, - }, - new RegisterReadWriteTest { - TargetRegister = new NumberRegister(3001), - RegisterPlcAddressName = "DT3001", - IntermediateValue = CurrentState.Undefined, - AfterWriteValue = CurrentState.State4, - }, - new RegisterReadWriteTest { - TargetRegister = new NumberRegister(3002), - RegisterPlcAddressName = "DDT3002", - IntermediateValue = CurrentState32.Undefined, - AfterWriteValue = CurrentState32.StateBetween, - }, - new RegisterReadWriteTest { - TargetRegister = new NumberRegister(3004), - RegisterPlcAddressName = "DDT3004", - IntermediateValue = TimeSpan.Zero, - AfterWriteValue = TimeSpan.FromSeconds(11), - }, - new RegisterReadWriteTest { - TargetRegister = new NumberRegister(3006), - RegisterPlcAddressName = "DDT3006", - IntermediateValue = TimeSpan.Zero, - AfterWriteValue = PlcFormat.ParsePlcTime("T#50m"), - }, - new RegisterReadWriteTest { - TargetRegister = new StringRegister(40), - RegisterPlcAddressName = "DT40", - IntermediateValue = "Hello", - AfterWriteValue = "TestV", - }, - new RegisterReadWriteTest { - TargetRegister = RegBuilder.Factory.FromPlcRegName("DT3008").AsBits(5).Build(), - RegisterPlcAddressName = "DT3008", - IntermediateValue = new BitArray(new bool[] { false, false, false, false, false }), - AfterWriteValue = new BitArray(new bool[] { false, true, false, false, false }), - }, - - }; - public TestLivePLC(ITestOutputHelper output) { this.output = output; @@ -141,58 +88,6 @@ namespace MewtocolTests } - [Fact(DisplayName = "Reading / Writing registers from PLC (Ethernet)")] - public async void TestRegisterReadWriteAsync() { - - Logger.LogLevel = LogLevel.Verbose; - Logger.OnNewLogMessage((d, l, m) => { - - output.WriteLine($"{d:HH:mm:ss:fff} {m}"); - - }); - - var plc = testPlcInformationData[0]; - - output.WriteLine($"\n\n --- Testing: {plc.PLCName} ---\n"); - - var client = Mewtocol.Ethernet(plc.PLCIP, plc.PLCPort).Build(); - - foreach (var testRW in testRegisterRW) { - - client.AddRegister(testRW.TargetRegister); - - } - - await client.ConnectAsync(); - Assert.True(client.IsConnected); - - //cycle run mode to reset registers to inital - await client.SetOperationModeAsync(false); - await client.SetOperationModeAsync(true); - - foreach (var testRW in testRegisterRW) { - - var testRegister = client.Registers.First(x => x.PLCAddressName == testRW.RegisterPlcAddressName); - - //test inital val - Assert.Null(testRegister.Value); - - await testRegister.ReadAsync(); - - Assert.Equal(testRW.IntermediateValue, testRegister.Value); - - await testRegister.WriteAsync(testRW.AfterWriteValue); - await testRegister.ReadAsync(); - - //test after write val - Assert.Equal(testRW.AfterWriteValue, testRegister.Value); - - } - - client.Disconnect(); - - } - } } diff --git a/MewtocolTests/TestPublicBuilderPattern.cs b/MewtocolTests/TestPublicBuilderPattern.cs new file mode 100644 index 0000000..a7eb134 --- /dev/null +++ b/MewtocolTests/TestPublicBuilderPattern.cs @@ -0,0 +1,89 @@ +using MewtocolNet; +using MewtocolNet.RegisterBuilding; +using MewtocolNet.RegisterBuilding.BuilderPatterns; +using MewtocolNet.Registers; +using MewtocolTests.EncapsulatedTests; +using System.Collections; +using Xunit; +using Xunit.Abstractions; + +namespace MewtocolTests; + +public class TestPublicBuilderPattern { + + private readonly ITestOutputHelper output; + + public TestPublicBuilderPattern(ITestOutputHelper output) => this.output = output; + + private void TestStruct (string buildAddr, uint expectAddr, uint expectByteSize) where T : struct { + + using var interf = (MewtocolInterface)Mewtocol.Ethernet("192.168.115.210").Build(); + var builder = new RBuild(interf); + + var comparer = new StructRegister(expectAddr, expectByteSize) { + attachedInterface = interf, + pollLevel = 1, + }; + + //test building to the internal list + builder.Struct(buildAddr).Build(); + var generated = builder.assembler.assembled.First(); + Assert.Equivalent(comparer, generated); + builder.assembler.assembled.Clear(); + output.WriteLine(generated.Explain()); + + //test building with direct out + builder.Struct(buildAddr).Build(out var testRef); + Assert.Equivalent(comparer, testRef); + builder.assembler.assembled.Clear(); + output.WriteLine(((Register)testRef).Explain()); + + comparer.pollLevel++; + + //test building to the internal list with poll level + builder.Struct(buildAddr).PollLevel(2).Build(); + var generated2 = builder.assembler.assembled.First(); + Assert.Equivalent(comparer, generated2); + builder.assembler.assembled.Clear(); + output.WriteLine(generated2.Explain()); + + //test building direct out with poll level + builder.Struct(buildAddr).PollLevel(2).Build(out var testRef2); + Assert.Equivalent(comparer, testRef2); + builder.assembler.assembled.Clear(); + output.WriteLine(((Register)testRef2).Explain()); + + } + + //16 bit structs + + [Fact(DisplayName = "[16 Bit] short")] + public void TestStruct_1() => TestStruct("DT100", 100, 2); + + [Fact(DisplayName = "[16 Bit] ushort")] + public void TestStruct_2() => TestStruct("DT101", 101, 2); + + [Fact(DisplayName = "[16 Bit] Word")] + public void TestStruct_3() => TestStruct("DT102", 102, 2); + + [Fact(DisplayName = "[16 Bit] Enum")] + public void TestStruct_4() => TestStruct("DT103", 103, 2); + + //32 bit structs + + [Fact(DisplayName = "[32 Bit] int")] + public void TestStruct_5() => TestStruct("DT104", 104, 4); + + [Fact(DisplayName = "[32 Bit] uint")] + public void TestStruct_6() => TestStruct("DT105", 105, 4); + + [Fact(DisplayName = "[32 Bit] DWord")] + public void TestStruct_7() => TestStruct("DT106", 106, 4); + + [Fact(DisplayName = "[32 Bit] Enum")] + public void TestStruct_8() => TestStruct("DT107", 107, 4); + + [Fact(DisplayName = "[32 Bit] TimeSpan")] + public void TestStruct_9() => TestStruct("DT108", 108, 4); + +} diff --git a/MewtocolTests/TestPublicBuilderPatternArrays.cs b/MewtocolTests/TestPublicBuilderPatternArrays.cs new file mode 100644 index 0000000..e938b0f --- /dev/null +++ b/MewtocolTests/TestPublicBuilderPatternArrays.cs @@ -0,0 +1,63 @@ +using MewtocolNet; +using MewtocolNet.RegisterBuilding; +using MewtocolNet.RegisterBuilding.BuilderPatterns; +using MewtocolNet.Registers; +using MewtocolTests.EncapsulatedTests; +using System.Collections; +using System.Collections.Generic; +using Xunit; +using Xunit.Abstractions; + +namespace MewtocolTests; + +public class TestPublicBuilderPatternArray { + + private readonly ITestOutputHelper output; + + public TestPublicBuilderPatternArray(ITestOutputHelper output) => this.output = output; + + private void TestArray1D (string buildAddr, int indices1, uint expectAddr, uint expectByteSize) where T : struct { + + using var interf = (MewtocolInterface)Mewtocol.Ethernet("192.168.115.210").Build(); + var builder = new RBuild(interf); + + var comparer = new ArrayRegister(expectAddr, expectByteSize, new int[] { indices1 }) { + attachedInterface = interf, + pollLevel = 1 + }; + + //test building to the internal list + builder.Struct(buildAddr).AsArray(indices1).Build(); + var generated = builder.assembler.assembled.First(); + + Assert.Equivalent(comparer, generated); + + builder.assembler.assembled.Clear(); + output.WriteLine(generated.Explain()); + + ////test building with direct out + //builder.Struct(buildAddr).AsArray(indices1).Build(out var testRef); + //Assert.Equivalent(comparer, testRef); + //builder.assembler.assembled.Clear(); + //output.WriteLine(((Register)testRef).Explain()); + + //comparer.pollLevel++; + + ////test building to the internal list with poll level + //builder.Struct(buildAddr).AsArray(indices1).PollLevel(2).Build(); + //var generated2 = builder.assembler.assembled.First(); + //Assert.Equivalent(comparer, generated2); + //builder.assembler.assembled.Clear(); + //output.WriteLine(generated2.Explain()); + + ////test building direct out with poll level + //builder.Struct(buildAddr).AsArray(indices1).PollLevel(2).Build(out var testRef2); + //Assert.Equivalent(comparer, testRef2); + //builder.assembler.assembled.Clear(); + //output.WriteLine(((Register)testRef2).Explain()); + + } + + //16 bit structs + +} diff --git a/MewtocolTests/TestRegisterBuilder.cs b/MewtocolTests/TestRegisterBuilder.cs index 729ac6c..64a99a6 100644 --- a/MewtocolTests/TestRegisterBuilder.cs +++ b/MewtocolTests/TestRegisterBuilder.cs @@ -1,5 +1,6 @@ using MewtocolNet; using MewtocolNet.RegisterBuilding; +using MewtocolNet.RegisterBuilding.BuilderPatterns; using MewtocolNet.Registers; using MewtocolTests.EncapsulatedTests; using System.Collections; @@ -123,137 +124,9 @@ public class TestRegisterBuilder { foreach (var item in dict) { - output.WriteLine($"Expected: {item.Key}"); - - var built = RegBuilder.Factory.FromPlcRegName(item.Key).AsPlcType(PlcVarType.BOOL).Build(); - - output.WriteLine($"{(built?.ToString(true) ?? "null")}\n"); - Assert.Equivalent(item.Value, built); } } - [Fact(DisplayName = "Parsing as Bool Register (Casted)")] - public void TestRegisterBuildingBoolCasted () { - - var expect = new BoolRegister(IOType.R, 0x1, 0); - var expect2 = new BoolRegister(IOType.Y, 0xA, 103); - - Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("R1").AsPlcType(PlcVarType.BOOL).Build()); - Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("R1").AsType().Build()); - - Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("Y103A").AsPlcType(PlcVarType.BOOL).Build()); - Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("Y103A").AsType().Build()); - - } - - [Fact(DisplayName = "Parsing as Bool Register (Auto)")] - public void TestRegisterBuildingBoolAuto () { - - var expect = new BoolRegister(IOType.R, 0x1, 0); - var expect2 = new BoolRegister(IOType.Y, 0xA, 103); - - Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("R1").Build()); - Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("R1").Build()); - - Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("Y103A").Build()); - Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("Y103A").Build()); - - } - - [Fact(DisplayName = "Parsing as Number Register (Casted)")] - public void TestRegisterBuildingNumericCasted () { - - var expect = new NumberRegister(303); - var expect2 = new NumberRegister(10002); - var expect3 = new NumberRegister(404); - var expect4 = new NumberRegister(400); - var expect5 = new NumberRegister(203); - var expect6 = new NumberRegister(204); - - Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").AsPlcType(PlcVarType.INT).Build()); - Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").AsType().Build()); - - Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("DDT10002").AsPlcType(PlcVarType.DINT).Build()); - Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("DDT10002").AsType().Build()); - - Assert.Equivalent(expect3, RegBuilder.Factory.FromPlcRegName("DDT404").AsPlcType(PlcVarType.REAL).Build()); - Assert.Equivalent(expect3, RegBuilder.Factory.FromPlcRegName("DDT404").AsType().Build()); - - Assert.Equivalent(expect4, RegBuilder.Factory.FromPlcRegName("DDT400").AsPlcType(PlcVarType.TIME).Build()); - Assert.Equivalent(expect4, RegBuilder.Factory.FromPlcRegName("DDT400").AsType().Build()); - - Assert.Equivalent(expect5, RegBuilder.Factory.FromPlcRegName("DT203").AsType().Build()); - Assert.Equivalent(expect6, RegBuilder.Factory.FromPlcRegName("DT204").AsType().Build()); - - } - - [Fact(DisplayName = "Parsing as Number Register (Auto)")] - public void TestRegisterBuildingNumericAuto () { - - var expect = new NumberRegister(201); - var expect2 = new NumberRegister(10002); - - Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT201").Build()); - Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("DDT10002").Build()); - - } - - [Fact(DisplayName = "Parsing as Bytes Register (Casted)")] - public void TestRegisterBuildingByteRangeCasted () { - - var expect = new BytesRegister(305, (uint)35); - - Assert.Equal((uint)18, expect.AddressLength); - Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT305").AsBytes(35).Build()); - - } - - [Fact(DisplayName = "Parsing as Bytes Register (Auto)")] - public void TestRegisterBuildingByteRangeAuto () { - - var expect = new BytesRegister(300, (uint)20 * 2); - var actual = (BytesRegister)RegBuilder.Factory.FromPlcRegName("DT300-DT319").Build(); - - Assert.Equal((uint)20, expect.AddressLength); - Assert.Equivalent(expect, actual); - - } - - [Fact(DisplayName = "Parsing as Bit Array")] - public void TestRegisterBuildingBitArray () { - - var expect1 = new BytesRegister(311, (ushort)5); - var expect2 = new BytesRegister(312, (ushort)16); - var expect3 = new BytesRegister(313, (ushort)32); - - var actual1 = (BytesRegister)RegBuilder.Factory.FromPlcRegName("DT311").AsBits(5).Build(); - var actual2 = (BytesRegister)RegBuilder.Factory.FromPlcRegName("DT312").AsBits(16).Build(); - var actual3 = (BytesRegister)RegBuilder.Factory.FromPlcRegName("DT313").AsBits(32).Build(); - - Assert.Equivalent(expect1, actual1); - Assert.Equivalent(expect2, actual2); - Assert.Equivalent(expect3, actual3); - - Assert.Equal((uint)1, actual1.AddressLength); - Assert.Equal((uint)1, actual2.AddressLength); - Assert.Equal((uint)2, actual3.AddressLength); - - } - - [Fact(DisplayName = "Parsing as String Register")] - public void TestRegisterBuildingString () { - - var expect1 = new StringRegister(314); - - var actual1 = (StringRegister)RegBuilder.Factory.FromPlcRegName("DT314").AsType().Build(); - - Assert.Equivalent(expect1, actual1); - - Assert.Equal((uint)0, actual1.WordsSize); - - } - - } diff --git a/MewtocolTests/TestRegisterInterface.cs b/MewtocolTests/TestRegisterInterface.cs index 674d7af..8e004ec 100644 --- a/MewtocolTests/TestRegisterInterface.cs +++ b/MewtocolTests/TestRegisterInterface.cs @@ -13,113 +13,22 @@ namespace MewtocolTests { this.output = output; } - [Fact(DisplayName = "Numeric mewtocol query building")] - public void NumericRegisterMewtocolIdentifiers() { - - List registers = new List { - new NumberRegister(50), - new NumberRegister(50), - new NumberRegister(50), - new NumberRegister(50), - new NumberRegister(50), - new NumberRegister(50), - new BytesRegister(50, (uint)30), - new BytesRegister(50, (uint)31), - }; - - List expectedIdents = new List { - "D0005000050", //single word register - "D0005000050", //single word register - "D0005000051", //double word register - "D0005000051", //double word register - "D0005000051", //double word register - "D0005000051", //double word register - "D0005000064", //variable len register even bytes - "D0005000065", //variable len register odd bytes - }; - - //test mewtocol idents - for (int i = 0; i < registers.Count; i++) { - - IRegisterInternal? reg = registers[i]; - string expect = expectedIdents[i]; - - Assert.Equal(expect, reg.BuildMewtocolQuery()); - - } - - } - - [Fact(DisplayName = "PLC register naming convention test")] - public void PLCRegisterIdentifiers() { - - List registers = new List { - //numeric ones - new NumberRegister(50, _name: null), - new NumberRegister(60, _name : null), - new NumberRegister(70, _name : null), - new NumberRegister(80, _name : null), - new NumberRegister(90, _name : null), - new NumberRegister(100, _name : null), - - //boolean - new BoolRegister(IOType.R, 0, 100), - new BoolRegister(IOType.R, 0, 0), - new BoolRegister(IOType.X, 5), - new BoolRegister(IOType.X, 0xA), - new BoolRegister(IOType.X, 0xF, 109), - new BoolRegister(IOType.Y, 0xC, 75), - - //string - new BytesRegister(999, 5), - }; - - List expcectedIdents = new List { - - //numeric ones - "DT50", - "DT60", - "DDT70", - "DDT80", - "DDT90", - "DDT100", - - //boolean - "R100", - "R0", - "X5", - "XA", - "X109F", - "Y75C", - - //string - "DT999" - - }; - - //test mewtocol idents - for (int i = 0; i < registers.Count; i++) { - - IRegisterInternal? reg = registers[i]; - string expect = expcectedIdents[i]; - - Assert.Equal(expect, reg.GetMewName()); - - } - - } - - [Fact(DisplayName = "Non allowed (Overflow address)")] - public void OverFlowRegisterAddress() { + [Fact(DisplayName = "Non allowed Struct Address (Overflow address)")] + public void OverFlowStructRegister() { var ex = Assert.Throws(() => { - new NumberRegister(100000, _name: null); + new StructRegister(100000, 2); }); output.WriteLine(ex.Message.ToString()); + } + + [Fact(DisplayName = "Non allowed Boolean Address (Overflow address )")] + public void OverFlowBoolRegister() { + var ex1 = Assert.Throws(() => { new BoolRegister(IOType.R, _areaAdress: 512); @@ -136,27 +45,6 @@ namespace MewtocolTests { output.WriteLine(ex2.Message.ToString()); - var ex3 = Assert.Throws(() => { - - new BytesRegister(100000, 5); - - }); - - output.WriteLine(ex3.Message.ToString()); - - } - - [Fact(DisplayName = "Non allowed (Wrong data type)")] - public void WrongDataTypeRegister() { - - var ex = Assert.Throws(() => { - - new NumberRegister(100, _name: null); - - }); - - output.WriteLine(ex.Message.ToString()); - } }