From 3911bb9707f81fcb6d4c8ea11bf72f2f349e92b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Wei=C3=9F?= <72068105+Sandoun@users.noreply.github.com> Date: Thu, 16 Jun 2022 09:57:15 +0200 Subject: [PATCH] =?UTF-8?q?Projektdateien=20hinzuf=C3=BCgen.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/launch.json | 26 ++ .vscode/tasks.json | 42 +++ Builds/MewtocolNet.xml | 193 ++++++++++ Builds/net5.0/MewtocolNet.deps.json | 23 ++ Builds/net5.0/MewtocolNet.dll | Bin 0 -> 51200 bytes Builds/net5.0/MewtocolNet.xml | 193 ++++++++++ Examples/Examples.csproj | 12 + Examples/Program.cs | 39 ++ MewtocolNet.sln | 48 +++ MewtocolNet/Mewtocol/DynamicInterface.cs | 140 +++++++ MewtocolNet/Mewtocol/LinkedLists.cs | 46 +++ MewtocolNet/Mewtocol/MewtocolEvents.cs | 48 +++ MewtocolNet/Mewtocol/MewtocolHelpers.cs | 121 ++++++ MewtocolNet/Mewtocol/MewtocolInterface.cs | 135 +++++++ .../Mewtocol/MewtocolInterfaceExtensions.cs | 120 ++++++ .../Mewtocol/MewtocolInterfaceRequests.cs | 255 +++++++++++++ MewtocolNet/Mewtocol/Register.cs | 183 ++++++++++ MewtocolNet/Mewtocol/Responses.cs | 343 ++++++++++++++++++ MewtocolNet/MewtocolNet.csproj | 14 + 19 files changed, 1981 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 Builds/MewtocolNet.xml create mode 100644 Builds/net5.0/MewtocolNet.deps.json create mode 100644 Builds/net5.0/MewtocolNet.dll create mode 100644 Builds/net5.0/MewtocolNet.xml create mode 100644 Examples/Examples.csproj create mode 100644 Examples/Program.cs create mode 100644 MewtocolNet.sln create mode 100644 MewtocolNet/Mewtocol/DynamicInterface.cs create mode 100644 MewtocolNet/Mewtocol/LinkedLists.cs create mode 100644 MewtocolNet/Mewtocol/MewtocolEvents.cs create mode 100644 MewtocolNet/Mewtocol/MewtocolHelpers.cs create mode 100644 MewtocolNet/Mewtocol/MewtocolInterface.cs create mode 100644 MewtocolNet/Mewtocol/MewtocolInterfaceExtensions.cs create mode 100644 MewtocolNet/Mewtocol/MewtocolInterfaceRequests.cs create mode 100644 MewtocolNet/Mewtocol/Register.cs create mode 100644 MewtocolNet/Mewtocol/Responses.cs create mode 100644 MewtocolNet/MewtocolNet.csproj diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..b3a4f5c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/Examples/bin/Debug/net5.0/Examples.dll", + "args": [], + "cwd": "${workspaceFolder}/Examples", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..2e99677 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/Examples/Examples.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/Examples/Examples.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "${workspaceFolder}/Examples/Examples.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/Builds/MewtocolNet.xml b/Builds/MewtocolNet.xml new file mode 100644 index 0000000..a0dad5e --- /dev/null +++ b/Builds/MewtocolNet.xml @@ -0,0 +1,193 @@ + + + + MewtocolNet + + + + + Trys to connect to the PLC by the IP given in the constructor + + Gets called when a connection with a PLC was established + Gets called when an error or timeout during connection occurs + + + + + Attaches a continous reader that reads back the Registers and Contacts + + + + + Generic information about the connected PLC + + + + + Builds a new Interfacer for a PLC + + + + + + + Sends a command to the PLC and awaits results + + MEWTOCOL Formatted request string ex: %01#RT + Auto close of frame [true]%01#RT01\r [false]%01#RT + Returns the result + + + + Gets generic information about the PLC + + A PLCInfo class + + + + Reads bool values from the plc by the given Contact List + + A list of contacts + The PLCs station number + List of IBoolContact with unique copys of the given contacts + + + + Writes a boolen value to the given contact + + The contact to write + The boolean state to write + Station Number (optional) + A result struct + + + + Reads the given numeric register from PLC + + Type of number (short, ushort, int, uint, float) + The register to read + Station number to access + A result with the given NumberRegister containing the readback value and a result struct + + + + Reads the given numeric register from PLC + + Type of number (short, ushort, int, uint, float) + The register to write + Station number to access + A result with the given NumberRegister and a result struct + + + + Gets fired whenever a contact of the observed list changes its value + + + + + A class describing a register + + + + + Gets called whenever the value was changed + + + + + Defines a register containing a number + + The type of the numeric value + + + + The value of the register + + + + + Defines a register containing a number + + Memory start adress max 99999 + The format in which the variable is stored + + + + Result for a read/write operation + + The type of the numeric value + + + + Defines a register containing a string + + + + + Defines a register containing a string + + + + + The formatted result of a ascii command + + + + + Contains generic information about the plc + + + + + Gets operation mode from 2 digit hex number + + + + + Contact as bool contact + + + + + A class describing a PLC contact + + + + + Creates a new base Contact + + A prefix identifier eg. X,Y,R,L + The number of the PLC contact + + + + Creates a new base Contact + + A prefix identifier eg. X,Y,R,L + The number of the PLC contact + + + + Build contact from complete contact name + + Complete contact name e.g. Y1C, Y3D or X1 + + + + Builds the mewtocol ascii contact identifier + + The identifier e.g. Y0001 or Y000A or X001C + + + + Converts the class to a generic json compatible object + + + + + + Creates a copy of the contact + + + + diff --git a/Builds/net5.0/MewtocolNet.deps.json b/Builds/net5.0/MewtocolNet.deps.json new file mode 100644 index 0000000..e225eb1 --- /dev/null +++ b/Builds/net5.0/MewtocolNet.deps.json @@ -0,0 +1,23 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v5.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v5.0": { + "MewtocolNet/0.1.5": { + "runtime": { + "MewtocolNet.dll": {} + } + } + } + }, + "libraries": { + "MewtocolNet/0.1.5": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/Builds/net5.0/MewtocolNet.dll b/Builds/net5.0/MewtocolNet.dll new file mode 100644 index 0000000000000000000000000000000000000000..455e3b7544dca7297e0c1b75f7c019936416e6ca GIT binary patch literal 51200 zcmcG134B~t_5Xcu_DM3$OeXs_fu>BGHgqT5plP~7H`){m6w)-Awt*xQW+tU!GMHLK zfg+-~u}BMuD7dh=pddv7K~P&n{Qb0)q7_9DRB!>Y{J-CG-^)yrg7W`={((97oO91T z_uO;OUEiDOs&hY~97-uCKJUJ()OV2bZ=t~V2SdmX)P6Uh?)Uwq?mNcHpVYN(?@C7c z67g+`_MS*bdv9+%71HXTYt~14;+?U^f&%||OZD0%O06^;>YX2#++x>u zM4b@HGo~uF9~`&lUho3a2tG&fQL0dIQ`=1j?U%oZp%nQ1bEwT9p(y{)rWQ#S{`}Cp zhR6}6#?ujF{tT55-kZ-W)jB5ceHam?ygAuk(93h^##C%)3Uueg0LYWJT6Tkyzlc(s z8xzSy2Pm=a2oN^(2l(XtEkt)UCSu)j2+~%iEFU~B#4uQ>)S_X+LQbV$@kiTCPxig{*bD_7y(hahhU0xLbxICSPAj?3D9tBeW`X>UdX7w*he`6TaLITS7u71@s_=t-y}GXp%g znVHSRk(tTFnVH3;I5VBeASFU73XeBvA_{|bo1JPNTH}IFbpRuxGKwbo{q6&&p+^Ld zHh_`mnOvdFCyfWpnGzet=Sn)u7<5v zGaIT@q8*Xyj>4h-!~;O1aH>BEZ~HH0On+cMIvpRxkC6TVeuVS~@+0K`dp|<>zxSil z<@jKJbf|OL>x}Vhk@^wXFpYtYN}cE(1RaLuWV4~kOd`nr8N{wLG0{MeU2p9pIAqdC4sSwHXXO?7O zveQh?LoFGOUo$zMC`YatIe;OaVKF_mWA1ZAG2gnC>Ev|kR+FHG`Y`GgWP{1Q;9`6) zppGFFgd0B0#F5#>q&V{-CWFkvz{Zq-flVR`4H($AE?5#w-llaeq^_%3%*Kf1sX|2my21_Mo6kW>bxVz zUpKOxnYo3DBXbiIXXa)muFMyh6lZQ=GDtzFhLfP0gylWm7qE|egkW+7>O)_^9I=-n z2}AwRjq3f-4eR|-x`qi�nOLx3sbr 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 + + + MewtocolNet + + + + + Trys to connect to the PLC by the IP given in the constructor + + Gets called when a connection with a PLC was established + Gets called when an error or timeout during connection occurs + + + + + Attaches a continous reader that reads back the Registers and Contacts + + + + + Generic information about the connected PLC + + + + + Builds a new Interfacer for a PLC + + + + + + + Sends a command to the PLC and awaits results + + MEWTOCOL Formatted request string ex: %01#RT + Auto close of frame [true]%01#RT01\r [false]%01#RT + Returns the result + + + + Gets generic information about the PLC + + A PLCInfo class + + + + Reads bool values from the plc by the given Contact List + + A list of contacts + The PLCs station number + List of IBoolContact with unique copys of the given contacts + + + + Writes a boolen value to the given contact + + The contact to write + The boolean state to write + Station Number (optional) + A result struct + + + + Reads the given numeric register from PLC + + Type of number (short, ushort, int, uint, float) + The register to read + Station number to access + A result with the given NumberRegister containing the readback value and a result struct + + + + Reads the given numeric register from PLC + + Type of number (short, ushort, int, uint, float) + The register to write + Station number to access + A result with the given NumberRegister and a result struct + + + + Gets fired whenever a contact of the observed list changes its value + + + + + A class describing a register + + + + + Gets called whenever the value was changed + + + + + Defines a register containing a number + + The type of the numeric value + + + + The value of the register + + + + + Defines a register containing a number + + Memory start adress max 99999 + The format in which the variable is stored + + + + Result for a read/write operation + + The type of the numeric value + + + + Defines a register containing a string + + + + + Defines a register containing a string + + + + + The formatted result of a ascii command + + + + + Contains generic information about the plc + + + + + Gets operation mode from 2 digit hex number + + + + + Contact as bool contact + + + + + A class describing a PLC contact + + + + + Creates a new base Contact + + A prefix identifier eg. X,Y,R,L + The number of the PLC contact + + + + Creates a new base Contact + + A prefix identifier eg. X,Y,R,L + The number of the PLC contact + + + + Build contact from complete contact name + + Complete contact name e.g. Y1C, Y3D or X1 + + + + Builds the mewtocol ascii contact identifier + + The identifier e.g. Y0001 or Y000A or X001C + + + + Converts the class to a generic json compatible object + + + + + + Creates a copy of the contact + + + + diff --git a/Examples/Examples.csproj b/Examples/Examples.csproj new file mode 100644 index 0000000..8b8151e --- /dev/null +++ b/Examples/Examples.csproj @@ -0,0 +1,12 @@ + + + + + + + + Exe + net5.0 + + + diff --git a/Examples/Program.cs b/Examples/Program.cs new file mode 100644 index 0000000..ba6ddef --- /dev/null +++ b/Examples/Program.cs @@ -0,0 +1,39 @@ +using System; +using System.Threading.Tasks; +using System.Text.Json; +using MewtocolNet; + +namespace Examples { + class Program { + static void Main(string[] args) { + + Console.WriteLine("Starting test"); + + Task.Factory.StartNew(async () => { + + MewtocolInterface interf = new MewtocolInterface("10.237.191.3"); + + interf.AddRegister("Cooler Status",1204); + interf.AddRegister(1101, 4); + + interf.RegisterChanged += (o) => { + Console.WriteLine($"DT{o.MemoryAdress} {(o.Name != null ? $"({o.Name}) " : "")}changed to {o.GetValueString()}"); + }; + + await interf.ConnectAsync( + (plcinf) => { + + Console.WriteLine("Connected to PLC:\n" + plcinf.ToString()); + }, + () => { + Console.WriteLine("Failed connection"); + } + ).AttachContinousReader(50); + + + }); + + Console.ReadLine(); + } + } +} diff --git a/MewtocolNet.sln b/MewtocolNet.sln new file mode 100644 index 0000000..11f2096 --- /dev/null +++ b/MewtocolNet.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30114.105 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MewtocolNet", "MewtocolNet\MewtocolNet.csproj", "{8B7863E7-5E82-4990-9138-2C0C24629982}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Examples", "Examples\Examples.csproj", "{D1F2FA26-3752-44BA-9DCB-4BC2436C5957}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8B7863E7-5E82-4990-9138-2C0C24629982}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8B7863E7-5E82-4990-9138-2C0C24629982}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8B7863E7-5E82-4990-9138-2C0C24629982}.Debug|x64.ActiveCfg = Debug|Any CPU + {8B7863E7-5E82-4990-9138-2C0C24629982}.Debug|x64.Build.0 = Debug|Any CPU + {8B7863E7-5E82-4990-9138-2C0C24629982}.Debug|x86.ActiveCfg = Debug|Any CPU + {8B7863E7-5E82-4990-9138-2C0C24629982}.Debug|x86.Build.0 = Debug|Any CPU + {8B7863E7-5E82-4990-9138-2C0C24629982}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8B7863E7-5E82-4990-9138-2C0C24629982}.Release|Any CPU.Build.0 = Release|Any CPU + {8B7863E7-5E82-4990-9138-2C0C24629982}.Release|x64.ActiveCfg = Release|Any CPU + {8B7863E7-5E82-4990-9138-2C0C24629982}.Release|x64.Build.0 = Release|Any CPU + {8B7863E7-5E82-4990-9138-2C0C24629982}.Release|x86.ActiveCfg = Release|Any CPU + {8B7863E7-5E82-4990-9138-2C0C24629982}.Release|x86.Build.0 = Release|Any CPU + {D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Debug|x64.ActiveCfg = Debug|Any CPU + {D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Debug|x64.Build.0 = Debug|Any CPU + {D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Debug|x86.ActiveCfg = Debug|Any CPU + {D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Debug|x86.Build.0 = Debug|Any CPU + {D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Release|Any CPU.Build.0 = Release|Any CPU + {D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Release|x64.ActiveCfg = Release|Any CPU + {D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Release|x64.Build.0 = Release|Any CPU + {D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Release|x86.ActiveCfg = Release|Any CPU + {D1F2FA26-3752-44BA-9DCB-4BC2436C5957}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/MewtocolNet/Mewtocol/DynamicInterface.cs b/MewtocolNet/Mewtocol/DynamicInterface.cs new file mode 100644 index 0000000..088a1a6 --- /dev/null +++ b/MewtocolNet/Mewtocol/DynamicInterface.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MewtocolNet.Responses; + +namespace MewtocolNet { + public partial class MewtocolInterface { + + public event Action RegisterChanged; + + internal CancellationTokenSource cTokenAutoUpdater; + protected internal bool isWriting = false; + + + public bool ContinousReaderRunning { get; set; } + public List Registers { get; set; } = new List(); + public List Contacts { get; set; } = new List(); + + /// + /// Trys to connect to the PLC by the IP given in the constructor + /// + /// Gets called when a connection with a PLC was established + /// Gets called when an error or timeout during connection occurs + /// + public async Task ConnectAsync (Action OnConnected = null, Action OnFailed = null) { + + var plcinf = await GetPLCInfoAsync(); + + if(plcinf is not null) { + + if(OnConnected != null) OnConnected(plcinf); + + } else { + + if(OnFailed != null) OnFailed(); + + } + + return this; + + } + + #region Register Adding + + /// + /// Adds a PLC memory register to the watchlist + /// The registers can be read back by attaching + /// + /// to the end of a method + /// + /// + /// The type of the register translated from C# to IEC 61131-3 types + /// C# ------ IEC + /// short => INT/WORD + /// ushort => UINT + /// int => DOUBLE + /// uint => UDOUBLE + /// float => REAL + /// string => STRING + /// + /// The address of the register in the PLCs memory + /// The length of the string (Can be ignored for other types) + public void AddRegister (int _address, int _length = 1) { + + Type regType = typeof(T); + + if (regType == typeof(short)) { + Registers.Add(new NRegister(_address)); + } else if (regType == typeof(ushort)) { + Registers.Add(new NRegister(_address)); + } else if (regType == typeof(int)) { + Registers.Add(new NRegister(_address)); + } else if (regType == typeof(uint)) { + Registers.Add(new NRegister(_address)); + } else if (regType == typeof(float)) { + Registers.Add(new NRegister(_address)); + } else if (regType == typeof(string)) { + Registers.Add(new SRegister(_address, _length)); + } else { + throw new NotSupportedException($"The type {regType} is not allowed for Registers \n" + + $"Allowed are: short, ushort, int, uint, float and string"); + } + + } + + /// + /// Adds a PLC memory register to the watchlist + /// The registers can be read back by attaching + /// + /// to the end of a method + /// + /// + /// The type of the register translated from C# to IEC 61131-3 types + /// C# ------ IEC + /// short => INT/WORD + /// ushort => UINT + /// int => DOUBLE + /// uint => UDOUBLE + /// float => REAL + /// string => STRING + /// + /// A naming definition for QOL, doesn't effect PLC and is optional + /// The address of the register in the PLCs memory + /// The length of the string (Can be ignored for other types) + public void AddRegister(string _name, int _address, int _length = 1) { + + Type regType = typeof(T); + + if (regType == typeof(short)) { + Registers.Add(new NRegister(_address, _name)); + } else if (regType == typeof(ushort)) { + Registers.Add(new NRegister(_address, _name)); + } else if (regType == typeof(int)) { + Registers.Add(new NRegister(_address, _name)); + } else if (regType == typeof(uint)) { + Registers.Add(new NRegister(_address, _name)); + } else if (regType == typeof(float)) { + Registers.Add(new NRegister(_address, _name)); + } else if (regType == typeof(string)) { + Registers.Add(new SRegister(_address, _length, _name)); + } else { + throw new NotSupportedException($"The type {regType} is not allowed for Registers \n" + + $"Allowed are: short, ushort, int, uint, float and string"); + } + + } + + #endregion + + internal void InvokeRegisterChanged (Register reg) { + + RegisterChanged?.Invoke (reg); + + } + + } +} diff --git a/MewtocolNet/Mewtocol/LinkedLists.cs b/MewtocolNet/Mewtocol/LinkedLists.cs new file mode 100644 index 0000000..9924761 --- /dev/null +++ b/MewtocolNet/Mewtocol/LinkedLists.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; + +namespace MewtocolNet.Links { + + public class LinkedData { + + public static Dictionary ErrorCodes = new System.Collections.Generic.Dictionary { + + {21, "NACK error"}, + {22, "WACK error"}, + {23, "Station number overlap"}, + {24, "Transmission error"}, + {25, "Hardware error"}, + {26, "Station number setting error"}, + {27, "Frame over error"}, + {28, "No response error"}, + {29, "Buffer close error"}, + {30, "Timeout error"}, + {32, "Transmission impossible"}, + {33, "Communication stop"}, + {36, "No local station"}, + {38, "Other com error"}, + {40, "BCC error"}, + {41, "Format error"}, + {42, "Not supported error"}, + {43, "Procedure error"}, + {50, "Link setting error"}, + {51, "Simultanious operation error"}, + {52, "Sending disable error"}, + {53, "Busy error"}, + {60, "Paramter error"}, + {61, "Data error"}, + {62, "Registration error"}, + {63, "Mode error"}, + {66, "Adress error"}, + {67, "No data error"}, + {72, "Timeout"}, + {73, "Timeout"}, + + }; + + + } + +} \ No newline at end of file diff --git a/MewtocolNet/Mewtocol/MewtocolEvents.cs b/MewtocolNet/Mewtocol/MewtocolEvents.cs new file mode 100644 index 0000000..5289a4e --- /dev/null +++ b/MewtocolNet/Mewtocol/MewtocolEvents.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using System.Linq; +using MewtocolNet.Responses; + +namespace MewtocolNet.Events { + + public class MewtocolContactListener : IDisposable { + + /// + /// Gets fired whenever a contact of the observed list changes its value + /// + public event Action> ContactsChangedValue; + + //privates + private List lastContacts = new List(); + private CancellationTokenSource cToken = new CancellationTokenSource(); + + public static MewtocolContactListener ListenContactChanges (MewtocolInterface _interFace, List _observeContacts, int _refreshMS = 100, int _stationNumber = 1) { + + MewtocolContactListener listener = new MewtocolContactListener(); + _ = Task.Factory.StartNew( async () => { + //get contacts first time + listener.lastContacts = (List) await _interFace.ReadBoolContacts(_observeContacts, _stationNumber); + while(!listener.cToken.Token.IsCancellationRequested) { + //compare and update + var newContactData = (List) await _interFace.ReadBoolContacts(_observeContacts, _stationNumber); + var difference = newContactData.Where(p => listener.lastContacts.Any(l => p.Value != l.Value && p.Identifier == l.Identifier)); + + if(difference.Count() > 0) { + listener.ContactsChangedValue?.Invoke(difference.ToList()); + listener.lastContacts = newContactData; + } else { + } + await Task.Delay(_refreshMS); + } + }); + return listener; + } + + public void Dispose() { + cToken.Cancel(); + } + } + +} \ No newline at end of file diff --git a/MewtocolNet/Mewtocol/MewtocolHelpers.cs b/MewtocolNet/Mewtocol/MewtocolHelpers.cs new file mode 100644 index 0000000..38fa548 --- /dev/null +++ b/MewtocolNet/Mewtocol/MewtocolHelpers.cs @@ -0,0 +1,121 @@ +using System; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using MewtocolNet.Responses; + +namespace MewtocolNet { + public static class MewtocolHelpers { + public static Byte[] ToHexASCIIBytes (this string _str) { + ASCIIEncoding ascii = new ASCIIEncoding(); + Byte[] bytes = ascii.GetBytes(_str.ToUpper()); + return bytes; + } + + public static string BuildBCCFrame (this string asciiArr) { + Encoding ae = Encoding.ASCII; + byte[] b = ae.GetBytes(asciiArr); + byte xorTotalByte = 0; + for(int i = 0; i < b.Length; i++) + xorTotalByte ^= b[i]; + return asciiArr.Insert(asciiArr.Length, xorTotalByte.ToString("X2")); + } + + public static byte[] ParseDTBytes (this string _onString ,int _blockSize = 4) { + var res = new Regex(@"\%([0-9]{2})\$RD(.{"+_blockSize+"})").Match(_onString); + if(res.Success) { + string val = res.Groups[2].Value; + return val.HexStringToByteArray(); + } + return null; + } + + public static string ParseDTByteString (this string _onString, int _blockSize = 4) { + var res = new Regex(@"\%([0-9]{2})\$RD(.{" + _blockSize + "})").Match(_onString); + if (res.Success) { + string val = res.Groups[2].Value; + return val; + } + return null; + } + + public static string ParseDTString (this string _onString) { + var res = new Regex(@"\%([0-9]{2})\$RD.{8}(.*)...").Match(_onString); + if(res.Success) { + string val = res.Groups[2].Value; + return val.GetStringFromAsciiHex(); + } + return null; + } + + public static string BuildDTString (this string _inString, short _stringReservedSize) { + StringBuilder sb = new StringBuilder(); + //06000600 + short stringSize = (short)_inString.Length; + var sizeBytes = BitConverter.GetBytes(stringSize).ToHexString(); + var reservedSizeBytes = BitConverter.GetBytes(_stringReservedSize).ToHexString(); + //reserved string count bytes + sb.Append(reservedSizeBytes); + //string count actual bytes + sb.Append(sizeBytes); + //actual string content + sb.Append(_inString.GetAsciiHexFromString().PadRight(_stringReservedSize * 2, '0')); + + return sb.ToString(); + } + + + public static string GetStringFromAsciiHex (this string input) { + if (input.Length % 2 != 0) + throw new ArgumentException("input not a hex string"); + byte[] bytes = new byte[input.Length / 2]; + for (int i = 0; i < input.Length; i += 2) { + String hex = input.Substring(i, 2); + bytes[i/2] = Convert.ToByte(hex, 16); + } + return Encoding.ASCII.GetString(bytes); + } + + public static string GetAsciiHexFromString (this string input) { + var bytes = new ASCIIEncoding().GetBytes(input); + return bytes.ToHexString(); + } + + public static byte[] HexStringToByteArray(this string hex) { + return Enumerable.Range(0, hex.Length) + .Where(x => x % 2 == 0) + .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) + .ToArray(); + } + + public static string ToHexString (this byte[] arr) { + StringBuilder sb = new StringBuilder(); + foreach (var b in arr) { + sb.Append(b.ToString("X2")); + } + return sb.ToString(); + } + + public static string ToJsonString (this IEnumerable _contacts, bool formatPretty = false) { + return JsonSerializer.Serialize(_contacts, new JsonSerializerOptions { + WriteIndented = formatPretty, + }); + } + + public static bool IsSubclassOfRawGeneric(Type generic, Type toCheck) { + while (toCheck != null && toCheck != typeof(object)) { + var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck; + if (generic == cur) { + return true; + } + toCheck = toCheck.BaseType; + } + return false; + } + + } + +} \ No newline at end of file diff --git a/MewtocolNet/Mewtocol/MewtocolInterface.cs b/MewtocolNet/Mewtocol/MewtocolInterface.cs new file mode 100644 index 0000000..bba1a42 --- /dev/null +++ b/MewtocolNet/Mewtocol/MewtocolInterface.cs @@ -0,0 +1,135 @@ +using System; +using System.IO; +using System.Net.Sockets; +using System.Text; +using System.Text.RegularExpressions; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MewtocolNet.Responses; + +namespace MewtocolNet { + + public partial class MewtocolInterface { + + /// + /// Generic information about the connected PLC + /// + public PLCInfo PlcInfo {get;private set;} + private CancellationTokenSource tokenSource; + + private string ip {get;set;} + private int port {get;set;} + public int ConnectionTimeout {get;set;} = 2000; + + #region Initialization + /// + /// Builds a new Interfacer for a PLC + /// + /// + /// + public MewtocolInterface (string _ip, int _port = 9094) { + ip = _ip; + port = _port; + } + + + #endregion + + #region Low level command handling + + /// + /// Sends a command to the PLC and awaits results + /// + /// MEWTOCOL Formatted request string ex: %01#RT + /// Auto close of frame [true]%01#RT01\r [false]%01#RT + /// Returns the result + public async Task SendCommandAsync (string _msg) { + + _msg = _msg.BuildBCCFrame(); + _msg += "\r"; + + //send request + var response = await SendSingleBlock(_msg); + + if(response == null) { + return new CommandResult { + Success = false, + Error = "0000", + ErrorDescription = "null result" + }; + } + + //error catching + Regex errorcheck = new Regex(@"\%[0-9]{2}\!([0-9]{2})", RegexOptions.IgnoreCase); + Match m = errorcheck.Match(response.ToString()); + if (m.Success) { + string eCode = m.Groups[1].Value; + string eDes = Links.LinkedData.ErrorCodes[Convert.ToInt32(eCode)]; + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"Response is: {response}"); + Console.WriteLine($"Error on command {_msg.Replace("\r", "")} the PLC returned error code: {eCode}, {eDes}"); + Console.ResetColor(); + return new CommandResult { + Success = false, + Error = eCode, + ErrorDescription = eDes + }; + } + + return new CommandResult { + Success = true, + Error = "0000", + Response = response.ToString() + }; + + } + + private async Task SendSingleBlock (string _blockString) { + + if(isWriting) { + return null; + } + + tokenSource = new CancellationTokenSource(); + + using (TcpClient client = new TcpClient() { ReceiveBufferSize = 64, NoDelay = true, ExclusiveAddressUse = true }) { + + try { + await client.ConnectAsync(ip, port, tokenSource.Token); + } catch(SocketException) { + return null; + } + + using (NetworkStream stream = client.GetStream()) { + var message = _blockString.ToHexASCIIBytes(); + var messageAscii = BitConverter.ToString(message).Replace("-", " "); + //send request + isWriting = true; + using (var sendStream = new MemoryStream(message)) { + await sendStream.CopyToAsync(stream); + //log message sent + ASCIIEncoding enc = new ASCIIEncoding(); + string characters = enc.GetString(message); + } + //await result + StringBuilder response = new StringBuilder(); + byte[] responseBuffer = new byte[256]; + do { + int bytes = stream.Read(responseBuffer, 0, responseBuffer.Length); + response.Append(Encoding.UTF8.GetString(responseBuffer, 0, bytes)); + } + while (stream.DataAvailable); + isWriting = false; + return response.ToString(); + } + + } + + } + + + #endregion + } + +} \ No newline at end of file diff --git a/MewtocolNet/Mewtocol/MewtocolInterfaceExtensions.cs b/MewtocolNet/Mewtocol/MewtocolInterfaceExtensions.cs new file mode 100644 index 0000000..37c397a --- /dev/null +++ b/MewtocolNet/Mewtocol/MewtocolInterfaceExtensions.cs @@ -0,0 +1,120 @@ +using MewtocolNet.Responses; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace MewtocolNet { + + public static class MewtocolInterfaceExtensions { + + /// + /// Attaches a continous reader that reads back the Registers and Contacts + /// + public static Task AttachContinousReader (this Task interfaceTask, int _refreshTimeMS = 200) { + + interfaceTask.Wait(-1); + + var interf = interfaceTask.Result; + + if (interf.ContinousReaderRunning) + return Task.CompletedTask; + + interf.cTokenAutoUpdater = new CancellationTokenSource(); + + Console.WriteLine("Attaching cont reader"); + + Task.Factory.StartNew(async () => { + + var plcinf = await interf.GetPLCInfoAsync(); + if (plcinf == null) { + Console.WriteLine("PLC is not reachable"); + throw new Exception("PLC is not reachable"); + } + if (!plcinf.OperationMode.RunMode) { + Console.WriteLine("PLC is not running"); + throw new Exception("PLC is not running"); + } + + interf.ContinousReaderRunning = true; + + while (true) { + + //dont update when currently writing a var + if (interf.isWriting) { + continue; + } + + await Task.Delay(_refreshTimeMS); + foreach (var reg in interf.Registers) { + + if (reg is NRegister shortReg) { + var lastVal = shortReg.Value; + var readout = (await interf.ReadNumRegister(shortReg)).Register.Value; + if (lastVal != readout) { + shortReg.LastValue = readout; + interf.InvokeRegisterChanged(shortReg); + shortReg.TriggerNotifyChange(); + } + } + if (reg is NRegister ushortReg) { + var lastVal = ushortReg.Value; + var readout = (await interf.ReadNumRegister(ushortReg)).Register.Value; + if (lastVal != readout) { + ushortReg.LastValue = readout; + interf.InvokeRegisterChanged(ushortReg); + ushortReg.TriggerNotifyChange(); + } + } + if (reg is NRegister intReg) { + var lastVal = intReg.Value; + var readout = (await interf.ReadNumRegister(intReg)).Register.Value; + if (lastVal != readout) { + intReg.LastValue = readout; + interf.InvokeRegisterChanged(intReg); + intReg.TriggerNotifyChange(); + } + } + if (reg is NRegister uintReg) { + var lastVal = uintReg.Value; + var readout = (await interf.ReadNumRegister(uintReg)).Register.Value; + if (lastVal != readout) { + uintReg.LastValue = readout; + interf.InvokeRegisterChanged(uintReg); + uintReg.TriggerNotifyChange(); + } + } + if (reg is NRegister floatReg) { + var lastVal = floatReg.Value; + var readout = (await interf.ReadNumRegister(floatReg)).Register.Value; + if (lastVal != readout) { + floatReg.LastValue = readout; + interf.InvokeRegisterChanged(floatReg); + floatReg.TriggerNotifyChange(); + } + } else if (reg is SRegister stringReg) { + var lastVal = stringReg.Value; + var readout = (await interf.ReadStringRegister(stringReg)).Register.Value; + if (lastVal != readout) { + interf.InvokeRegisterChanged(stringReg); + stringReg.TriggerNotifyChange(); + } + + } + + } + + + } + + }, interf.cTokenAutoUpdater.Token); + + return Task.CompletedTask; + + } + + } + +} diff --git a/MewtocolNet/Mewtocol/MewtocolInterfaceRequests.cs b/MewtocolNet/Mewtocol/MewtocolInterfaceRequests.cs new file mode 100644 index 0000000..2d6aa1e --- /dev/null +++ b/MewtocolNet/Mewtocol/MewtocolInterfaceRequests.cs @@ -0,0 +1,255 @@ +using System; +using System.IO; +using System.Net.Sockets; +using System.Text; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using MewtocolNet.Responses; +using System.Linq; +using System.Globalization; + +namespace MewtocolNet { + + public partial class MewtocolInterface { + + #region High level command handling + + /// + /// Gets generic information about the PLC + /// + /// A PLCInfo class + public async Task GetPLCInfoAsync () { + var resu = await SendCommandAsync("%01#RT"); + if(!resu.Success) return null; + + var reg = new Regex(@"\%([0-9]{2})\$RT([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{4})..", RegexOptions.IgnoreCase); + Match m = reg.Match(resu.Response); + + if(m.Success) { + + string station = m.Groups[1].Value; + string cpu = m.Groups[2].Value; + string version = m.Groups[3].Value; + string capacity = m.Groups[4].Value; + string operation = m.Groups[5].Value; + + string errorflag = m.Groups[7].Value; + string error = m.Groups[8].Value; + + PLCInfo retInfo = new PLCInfo { + CpuInformation = PLCInfo.CpuInfo.BuildFromHexString(cpu, version, capacity), + OperationMode = PLCInfo.PLCMode.BuildFromHex(operation), + ErrorCode = error, + StationNumber = int.Parse(station ?? "0"), + }; + return retInfo; + + } + return null; + } + + /// + /// Reads bool values from the plc by the given Contact List + /// + /// A list of contacts + /// The PLCs station number + /// List of IBoolContact with unique copys of the given contacts + public async Task> ReadBoolContacts (List _contactsToRead, int _stationNumber = 1) { + + //re order by contact pfx for faster querying + _contactsToRead = _contactsToRead.OrderBy(x=>x.Prefix).ToList(); + + //return list + List returnContacts = new List(); + + //grouped by 8 each + List> nestedContacts = new List>(); + + //group into max 8 contacts list + List tempGroup = new List(); + for (int i = 0; i < _contactsToRead.Count; i++) { + tempGroup.Add(_contactsToRead[i]); + //each 8 contacts make a new list + if(i % 7 == 0 && i != 0 && i != _contactsToRead.Count) { + nestedContacts.Add(tempGroup); + tempGroup = new List(); + } + //if end of list and contacts cannot be broke down to 8 each group + if(i == _contactsToRead.Count - 1 && _contactsToRead.Count % 8 != 0) { + nestedContacts.Add(tempGroup); + tempGroup = new List(); + } + } + + //make task for each group + foreach (var group in nestedContacts) { + //regex for getting values + StringBuilder regexString = new StringBuilder(@"\%..\$RC"); + //append start %01#RCP2 + StringBuilder messageString = new StringBuilder(); + messageString.Append($"%{_stationNumber.ToString().PadLeft(2, '0')}#RCP"); + messageString.Append($"{group.Count}"); + //append each contact of group Y0000 Y0001 etc + foreach (var cont in group) { + messageString.Append(cont.BuildMewtocolIdent()); + regexString.Append(@"([0-9])"); + } + regexString.Append(@"(..)"); + //parse the result + var result = await SendCommandAsync(messageString.ToString()); + Regex regCheck = new Regex(regexString.ToString(), RegexOptions.IgnoreCase); + if(result.Success && regCheck.IsMatch(result.Response)) { + //parse result string + Match regMatch = regCheck.Match(result.Response); + // add to return list + for (int i = 0; i < group.Count; i++) { + Contact cont = group[i].ShallowCopy(); + Contact toadd = cont; + if( regMatch.Groups[i + 1].Value == "1" ) { + toadd.Value = true; + } else if( regMatch.Groups[i + 1].Value == "0" ) { + toadd.Value = false; + } + returnContacts.Add(toadd); + } + } + } + return returnContacts; + } + + /// + /// Writes a boolen value to the given contact + /// + /// The contact to write + /// The boolean state to write + /// Station Number (optional) + /// A result struct + public async Task WriteContact (Contact _contact, bool _value, int _stationNumber = 1) { + string stationNum = _stationNumber.ToString().PadLeft(2, '0'); + string dataArea = _contact.BuildMewtocolIdent(); + string dataString = _value ? "1" : "0"; + string requeststring = $"%{stationNum}#WCS{dataArea}{dataString}"; + var res = await SendCommandAsync(requeststring); + return res; + } + + /// + /// Reads the given numeric register from PLC + /// + /// Type of number (short, ushort, int, uint, float) + /// The register to read + /// Station number to access + /// A result with the given NumberRegister containing the readback value and a result struct + public async Task> ReadNumRegister (NRegister _toRead, int _stationNumber = 1) { + + Type numType = typeof(T); + + string requeststring = $"%{_stationNumber.ToString().PadLeft(2, '0')}#RD{_toRead.BuildMewtocolIdent()}"; + var result = await SendCommandAsync(requeststring); + + if (numType == typeof(short)) { + + var resultBytes = result.Response.ParseDTByteString(4); + var val = short.Parse(resultBytes, NumberStyles.HexNumber); + (_toRead as NRegister).LastValue = val; + + } else if (numType == typeof(ushort)) { + var resultBytes = result.Response.ParseDTBytes(4); + var val = BitConverter.ToInt16(resultBytes); + _toRead.Value = (T)Convert.ChangeType(val, typeof(T)); + } else if (numType == typeof(int)) { + var resultBytes = result.Response.ParseDTBytes(8); + var val = BitConverter.ToInt16(resultBytes); + _toRead.Value = (T)Convert.ChangeType(val, typeof(T)); + } else if (numType == typeof(uint)) { + var resultBytes = result.Response.ParseDTBytes(8); + var val = BitConverter.ToInt16(resultBytes); + _toRead.Value = (T)Convert.ChangeType(val, typeof(T)); + } else if (numType == typeof(float)) { + var resultBytes = result.Response.ParseDTBytes(8); + var val = BitConverter.ToSingle(resultBytes); + _toRead.Value = (T)Convert.ChangeType(val, typeof(T)); + } + + var finalRes = new NRegisterResult { + Result = result, + Register = _toRead + }; + + return finalRes; + } + + /// + /// Reads the given numeric register from PLC + /// + /// Type of number (short, ushort, int, uint, float) + /// The register to write + /// Station number to access + /// A result with the given NumberRegister and a result struct + public async Task> WriteNumRegister(NRegister _toWrite, T _value, int _stationNumber = 1) { + + byte[] toWriteVal; + Type numType = typeof(T); + + if (numType == typeof(short)) { + toWriteVal = BitConverter.GetBytes(Convert.ToInt16(_value)); + } else if (numType == typeof(ushort)) { + toWriteVal = BitConverter.GetBytes(Convert.ToUInt16(_value)); + } else if (numType == typeof(int)) { + toWriteVal = BitConverter.GetBytes(Convert.ToInt32(_value)); + } else if (numType == typeof(uint)) { + toWriteVal = BitConverter.GetBytes(Convert.ToUInt32(_value)); + } else if (numType == typeof(float)) { + toWriteVal = BitConverter.GetBytes(Convert.ToUInt32(_value)); + } else { + toWriteVal = null; + } + + string requeststring = $"%{_stationNumber.ToString().PadLeft(2, '0')}#WD{_toWrite.BuildMewtocolIdent()}{toWriteVal.ToHexString()}"; + var result = await SendCommandAsync(requeststring); + + return new NRegisterResult { + Result = result, + Register = _toWrite + }; + } + + + public async Task ReadStringRegister (SRegister _toRead, int _stationNumber = 1) { + string requeststring = $"%{_stationNumber.ToString().PadLeft(2, '0')}#RD{_toRead.BuildMewtocolIdent()}"; + var result = await SendCommandAsync(requeststring); + if (result.Success) + _toRead.SetValueFromPLC(result.Response.ParseDTString()); + return new SRegisterResult { + Result = result, + Register = _toRead + }; + } + + public async Task WriteStringRegister(SRegister _toWrite, string _value, int _stationNumber = 1) { + + if (_value == null) _value = ""; + if(_value.Length > _toWrite.ReservedSize) { + throw new ArgumentException("Write string size cannot be longer than reserved string size"); + } + + string stationNum = _stationNumber.ToString().PadLeft(2, '0'); + string dataArea = _toWrite.BuildMewtocolIdent(); + string dataString = _value.BuildDTString(_toWrite.ReservedSize); + string requeststring = $"%{stationNum}#WD{dataArea}{dataString}"; + + Console.WriteLine($"reserved: {_toWrite.MemoryLength}, size: {_value.Length}"); + + var result = await SendCommandAsync(requeststring); + return new SRegisterResult { + Result = result, + Register = _toWrite + }; + } + + #endregion + + } + +} diff --git a/MewtocolNet/Mewtocol/Register.cs b/MewtocolNet/Mewtocol/Register.cs new file mode 100644 index 0000000..2b11010 --- /dev/null +++ b/MewtocolNet/Mewtocol/Register.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MewtocolNet.Responses { + + /// + /// A class describing a register + /// + public abstract class Register : INotifyPropertyChanged { + + /// + /// Gets called whenever the value was changed + /// + public event Action ValueChanged; + public event PropertyChangedEventHandler PropertyChanged; + + public string Name { get; set; } + public int MemoryAdress { get; set; } + public int MemoryLength { get; set; } + public virtual string BuildMewtocolIdent() { + StringBuilder asciistring = new StringBuilder("D"); + asciistring.Append(MemoryAdress.ToString().PadLeft(5, '0')); + asciistring.Append((MemoryAdress + MemoryLength).ToString().PadLeft(5, '0')); + return asciistring.ToString(); + } + protected void TriggerChangedEvnt(object changed) { + ValueChanged?.Invoke(changed); + } + + public void TriggerNotifyChange () { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value")); + } + + public string GetValueString () { + + if (this is NRegister shortReg) { + return shortReg.Value.ToString(); + } + if (this is NRegister ushortReg) { + return ushortReg.Value.ToString(); + } + if (this is NRegister intReg) { + return intReg.Value.ToString(); + } + if (this is NRegister uintReg) { + return uintReg.Value.ToString(); + } + if (this is NRegister floatReg) { + return floatReg.Value.ToString(); + } + else if (this is SRegister stringReg) { + return stringReg.Value.ToString(); + + } + + return "Type of the register is not supported."; + + } + + } + /// + /// Defines a register containing a number + /// + /// The type of the numeric value + public class NRegister : Register { + + public T NeedValue; + public T LastValue; + + /// + /// The value of the register + /// + public T Value { + get => LastValue; + set { + NeedValue = value; + TriggerChangedEvnt(this); + } + } + + /// + /// Defines a register containing a number + /// + /// Memory start adress max 99999 + /// The format in which the variable is stored + public NRegister(int _adress, string _name = null) { + if (_adress > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999"); + MemoryAdress = _adress; + Name = _name; + Type numType = typeof(T); + if (numType == typeof(short)) { + MemoryLength = 0; + } else if (numType == typeof(ushort)) { + MemoryLength = 0; + } else if (numType == typeof(int)) { + MemoryLength = 1; + } else if (numType == typeof(uint)) { + MemoryLength = 1; + } else if (numType == typeof(float)) { + MemoryLength = 1; + } else { + throw new NotSupportedException($"The type {numType} is not allowed for Number Registers"); + } + } + + public override string ToString() { + return $"Adress: {MemoryAdress} Val: {Value}"; + } + } + + /// + /// Result for a read/write operation + /// + /// The type of the numeric value + public class NRegisterResult { + public CommandResult Result { get; set; } + public NRegister Register { get; set; } + + public override string ToString() { + string errmsg = Result.Success ? "" : $", Error [{Result.ErrorDescription}]"; + return $"Result [{Result.Success}], Register [{Register.ToString()}]{errmsg}"; + } + } + + /// + /// Defines a register containing a string + /// + public class SRegister : Register { + + private string lastVal = ""; + public string Value { + + get => lastVal; + + } + + public short ReservedSize { get; set; } + + /// + /// Defines a register containing a string + /// + public SRegister(int _adress, int _reservedStringSize, string _name = null) { + if (_adress > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999"); + Name = _name; + MemoryAdress = _adress; + ReservedSize = (short)_reservedStringSize; + MemoryLength = 1 + (_reservedStringSize) / 2; + } + + public override string ToString() { + return $"Adress: {MemoryAdress} Val: {Value}"; + } + + public override string BuildMewtocolIdent() { + StringBuilder asciistring = new StringBuilder("D"); + asciistring.Append(MemoryAdress.ToString().PadLeft(5, '0')); + asciistring.Append((MemoryAdress + MemoryLength).ToString().PadLeft(5, '0')); + return asciistring.ToString(); + } + + public void SetValueFromPLC (string val) { + lastVal = val; + TriggerChangedEvnt(this); + TriggerNotifyChange(); + } + + + } + + public class SRegisterResult { + public CommandResult Result { get; set; } + public SRegister Register { get; set; } + + public override string ToString() { + string errmsg = Result.Success ? "" : $", Error [{Result.ErrorDescription}]"; + return $"Result [{Result.Success}], Register [{Register.ToString()}]{errmsg}"; + } + } +} diff --git a/MewtocolNet/Mewtocol/Responses.cs b/MewtocolNet/Mewtocol/Responses.cs new file mode 100644 index 0000000..0550346 --- /dev/null +++ b/MewtocolNet/Mewtocol/Responses.cs @@ -0,0 +1,343 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Linq; +using System.Text; +using System.ComponentModel; + +namespace MewtocolNet.Responses { + + /// + /// The formatted result of a ascii command + /// + public struct CommandResult { + public bool Success {get;set;} + public string Response {get;set;} + public string Error {get;set;} + public string ErrorDescription {get;set;} + + public override string ToString() { + string errmsg = Success ? "" : ErrorDescription; + return $"Success: {Success}, Response: {Response} {errmsg}"; + } + } + + + + /// + /// Contains generic information about the plc + /// + public class PLCInfo { + + public class PLCMode { + public bool RunMode {get;set;} + public bool TestRunMode {get;set;} + public bool BreakExcecuting {get;set;} + public bool BreakValid {get;set;} + public bool OutputEnabled {get;set;} + public bool StepRunMode {get;set;} + public bool MessageExecuting {get;set;} + public bool RemoteMode {get;set;} + + /// + /// Gets operation mode from 2 digit hex number + /// + public static PLCMode BuildFromHex (string _hexString) { + + string lower = Convert.ToString(Convert.ToInt32(_hexString.Substring(0, 1)), 2).PadLeft(4, '0'); + string higher = Convert.ToString(Convert.ToInt32(_hexString.Substring(1, 1)), 2).PadLeft(4, '0'); + string combined = lower + higher; + + var retMode = new PLCMode(); + + for (int i = 0; i < 8; i++) { + char digit = combined[i]; + bool state = false; + if(digit.ToString() == "1") state = true; + switch (i) { + case 0 : + retMode.RunMode = state; + break; + case 1 : + retMode.TestRunMode = state; + break; + case 2 : + retMode.BreakExcecuting = state; + break; + case 3 : + retMode.BreakValid = state; + break; + case 4 : + retMode.OutputEnabled = state; + break; + case 5 : + retMode.StepRunMode = state; + break; + case 6 : + retMode.MessageExecuting = state; + break; + case 7 : + retMode.RemoteMode = state; + break; + } + } + + return retMode; + + } + } + + public class CpuInfo { + public enum CpuType { + FP0_FP1_2_7K, + FP0_FP1_5K_10K, + FP1_M_0_9K, + FP2_16K_32K, + FP3_C_10K, + FP3_C_16K, + FP5_16K, + FP5_24K, + FP_Sigma_X_H_30K_60K_120K + + } + + public CpuType Cputype {get;set;} + public int ProgramCapacity {get;set;} + public string CpuVersion {get;set;} + + + public static CpuInfo BuildFromHexString (string _cpuType, string _cpuVersion, string _progCapacity) { + + CpuInfo retInf = new CpuInfo(); + + switch (_cpuType) { + case "02": + retInf.Cputype = CpuType.FP5_16K; + break; + case "03": + retInf.Cputype = CpuType.FP3_C_10K; + break; + case "04": + retInf.Cputype = CpuType.FP1_M_0_9K; + break; + case "05": + retInf.Cputype = CpuType.FP0_FP1_2_7K; + break; + case "06": + retInf.Cputype = CpuType.FP0_FP1_5K_10K; + break; + case "12": + retInf.Cputype = CpuType.FP5_24K; + break; + case "13": + retInf.Cputype = CpuType.FP3_C_16K; + break; + case "20": + retInf.Cputype = CpuType.FP_Sigma_X_H_30K_60K_120K; + break; + case "50": + retInf.Cputype = CpuType.FP2_16K_32K; + break; + } + + retInf.ProgramCapacity = Convert.ToInt32(_progCapacity); + retInf.CpuVersion = _cpuVersion.Insert(1, "."); + return retInf; + + } + } + + public CpuInfo CpuInformation {get;set;} + public PLCMode OperationMode {get;set;} + public string ErrorCode {get;set;} + public int StationNumber { get;set;} + + public override string ToString () { + + return $"Type: {CpuInformation.Cputype},\n" + + $"Capacity: {CpuInformation.ProgramCapacity}k\n" + + $"CPU v: {CpuInformation.CpuVersion}\n" + + $"Station Num: {StationNumber}\n" + + $"--------------------------------\n" + + $"OP Mode: {(OperationMode.RunMode ? "Run" : "Prog")}\n" + + $"Error Code: {ErrorCode}"; + + } + + } + + + + /// + /// Contact as bool contact + /// + public interface IBoolContact { + public string Name {get;set;} + public string Identifier {get;} + public bool? Value {get;set;} + + } + + /// + /// A class describing a PLC contact + /// + public class Contact : IBoolContact { + public string Name {get;set;} + public PFX Prefix {get;set;} + public int Number {get;set;} + public ContactType Type {get;set;} + public string Endprefix {get;set;} + public string Asciistring {get => BuildMewtocolIdent();} + public string Identifier {get => Asciistring;} + public bool? Value {get;set;} = null; + public enum ContactType { + Unknown, + Input, + Output, + } + + public enum PFX { + X, + Y, + R + } + + /// + /// Creates a new base Contact + /// + /// A prefix identifier eg. X,Y,R,L + /// The number of the PLC contact + public Contact (PFX _prefix, int _number, string _name = "unknown") { + switch (_prefix) { + case PFX.X: + Type = ContactType.Input; + break; + case PFX.Y: + case PFX.R: + Type = ContactType.Output; + break; + } + + Prefix = _prefix; + Number = _number; + Name = _name; + } + + /// + /// Creates a new base Contact + /// + /// A prefix identifier eg. X,Y,R,L + /// The number of the PLC contact + public Contact (string _prefix, int _number, string _name = "unknown") { + PFX parsedPFX; + if(Enum.TryParse(_prefix, true, out parsedPFX)) { + switch (parsedPFX) { + case PFX.X: + Type = ContactType.Input; + break; + case PFX.Y: + case PFX.R: + Type = ContactType.Output; + break; + } + Prefix = parsedPFX; + Number = _number; + Name = _name; + } else { + throw new ArgumentException($"The prefix {_prefix} is no valid contact prefix"); + } + } + + /// + /// Build contact from complete contact name + /// + /// Complete contact name e.g. Y1C, Y3D or X1 + public Contact (string _contactName, string _name = "unknown") { + + string prefix = ""; + int number = 0; + string endpfx = null; + + Match regcheck = new Regex(@"(Y|X|R|L)([0-9]{1,3})?(.)?", RegexOptions.IgnoreCase).Match(_contactName); + if(regcheck.Success) { + + /* for (int i = 0; i < regcheck.Groups.Count; i++) { + var item = regcheck.Groups[i].Value; + Console.WriteLine(item); + } */ + + prefix = regcheck.Groups[1].Value; + number = regcheck.Groups[2]?.Value != string.Empty ? Convert.ToInt32(regcheck.Groups[2].Value) : -1; + endpfx = regcheck.Groups[3]?.Value; + } else { + throw new ArgumentException($"The contact {_contactName} is no valid contact"); + } + + PFX parsedPFX; + if(Enum.TryParse(prefix, true, out parsedPFX)) { + switch (parsedPFX) { + case PFX.X: + Type = ContactType.Input; + break; + case PFX.Y: + case PFX.R: + Type = ContactType.Output; + break; + } + Prefix = parsedPFX; + Number = number; + Endprefix = endpfx; + Name = _name; + + } else { + throw new ArgumentException($"The prefix {prefix} is no valid contact prefix"); + } + + Console.WriteLine(BuildMewtocolIdent()); + } + + /// + /// Builds the mewtocol ascii contact identifier + /// + /// The identifier e.g. Y0001 or Y000A or X001C + public string BuildMewtocolIdent () { + string contactstring = ""; + if(Endprefix == null) { + contactstring += Prefix; + contactstring += Number.ToString().PadLeft(4, '0'); + } else { + contactstring += Prefix; + if(Number == -1) { + contactstring += "000" + Endprefix; + } else { + contactstring += (Number.ToString() + Endprefix).PadLeft(4, '0'); + } + } + if(string.IsNullOrEmpty(contactstring)) { + return null; + } + return contactstring; + } + + /// + /// Converts the class to a generic json compatible object + /// + /// + public object ToGenericObject () { + return new { + Name = this.Name, + Identifier = this.Asciistring, + Value = this.Value + }; + } + /// + /// Creates a copy of the contact + /// + public Contact ShallowCopy() { + return (Contact) this.MemberwiseClone(); + } + + } + + +} \ No newline at end of file diff --git a/MewtocolNet/MewtocolNet.csproj b/MewtocolNet/MewtocolNet.csproj new file mode 100644 index 0000000..fa4db7c --- /dev/null +++ b/MewtocolNet/MewtocolNet.csproj @@ -0,0 +1,14 @@ + + + net5.0 + AppLogger + 0.1.5 + Felix Weiss + Womed + true + + + P:\QUELLEN\PRODUKTAKTE\DESIGN_V_V\Software SPS\Projekt FP-XH\MewtocolNet\Builds\MewtocolNet.xml + P:\QUELLEN\PRODUKTAKTE\DESIGN_V_V\Software SPS\Projekt FP-XH\MewtocolNet\Builds\ + +