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 0000000..455e3b7
Binary files /dev/null and b/Builds/net5.0/MewtocolNet.dll differ
diff --git a/Builds/net5.0/MewtocolNet.xml b/Builds/net5.0/MewtocolNet.xml
new file mode 100644
index 0000000..a0dad5e
--- /dev/null
+++ b/Builds/net5.0/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/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