From a9bd2962b494967c25b40d7fdca9bda06ccd6ba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Wei=C3=9F?= <72068105+Sandoun@users.noreply.github.com> Date: Tue, 27 Jun 2023 20:44:11 +0200 Subject: [PATCH] Added performance improvements for cyclic polling by using single frame building of multiple registers - cleaned and refactored codebase --- Examples/ExampleScenarios.cs | 101 ++++++- Examples/Program.cs | 19 +- MewtocolNet/DynamicInterface.cs | 200 +++++++++----- MewtocolNet/Exceptions/MewtocolException.cs | 6 +- MewtocolNet/IRegister.cs | 84 ++---- MewtocolNet/IRegisterInternal.cs | 51 +++- MewtocolNet/Logging/Logger.cs | 12 +- MewtocolNet/MewtocolHelpers.cs | 178 +++++------- MewtocolNet/MewtocolInterface.cs | 75 ++++-- MewtocolNet/MewtocolInterfaceRequests.cs | 20 +- MewtocolNet/RegisterBuildInfo.cs | 72 +++-- .../RegisterBuilding/FinalizerExtensions.cs | 43 +-- .../RegisterBuilding/RegisterBuilderStep.cs | 21 +- MewtocolNet/RegisterEnums.cs | 2 +- MewtocolNet/Registers/BaseRegister.cs | 127 +++++++++ MewtocolNet/Registers/BoolRegister.cs | 137 +++------- MewtocolNet/Registers/BoolRegisterResult.cs | 20 -- MewtocolNet/Registers/BytesRegister.cs | 138 +++------- MewtocolNet/Registers/BytesRegisterResult.cs | 19 -- MewtocolNet/Registers/NumberRegister.cs | 253 ++++++------------ MewtocolNet/Registers/NumberRegisterResult.cs | 35 --- MewtocolNet/Registers/StringRegister.cs | 87 ++++++ MewtocolNet/TCPMessageResult.cs | 12 + MewtocolNet/TypeConversion/Conversions.cs | 64 ++++- MewtocolNet/TypeConversion/PlcValueParser.cs | 5 +- MewtocolTests/AutomatedPropertyRegisters.cs | 111 ++------ .../ExpectedPlcInformationData.cs | 17 ++ .../RegisterReadWriteTest.cs | 20 ++ MewtocolTests/TestHelperExtensions.cs | 4 +- MewtocolTests/TestLivePLC.cs | 68 ++++- MewtocolTests/TestRegisterBuilder.cs | 22 +- MewtocolTests/TestRegisterInterface.cs | 28 +- PLC_Test/test_c30_fpx_h.pro | Bin 262144 -> 262144 bytes PLC_Test/test_c30_fpx_h.xml | 6 +- 34 files changed, 1099 insertions(+), 958 deletions(-) create mode 100644 MewtocolNet/Registers/BaseRegister.cs delete mode 100644 MewtocolNet/Registers/BoolRegisterResult.cs delete mode 100644 MewtocolNet/Registers/BytesRegisterResult.cs delete mode 100644 MewtocolNet/Registers/NumberRegisterResult.cs create mode 100644 MewtocolNet/Registers/StringRegister.cs create mode 100644 MewtocolNet/TCPMessageResult.cs create mode 100644 MewtocolTests/EncapsulatedTests/ExpectedPlcInformationData.cs create mode 100644 MewtocolTests/EncapsulatedTests/RegisterReadWriteTest.cs diff --git a/Examples/ExampleScenarios.cs b/Examples/ExampleScenarios.cs index df60f16..d4c82d6 100644 --- a/Examples/ExampleScenarios.cs +++ b/Examples/ExampleScenarios.cs @@ -7,20 +7,25 @@ using System.Collections; using MewtocolNet.RegisterBuilding; using System.Collections.Generic; using MewtocolNet.Registers; +using System.Diagnostics; +using System.Text; namespace Examples; public class ExampleScenarios { - public static bool MewtocolLoggerEnabled = false; - public void SetupLogger () { //attaching the logger - Logger.LogLevel = LogLevel.Verbose; - Logger.OnNewLogMessage((date, msg) => { - if(MewtocolLoggerEnabled) - Console.WriteLine($"{date.ToString("HH:mm:ss")} {msg}"); + Logger.LogLevel = LogLevel.Info; + Logger.OnNewLogMessage((date, level, msg) => { + + if (level == LogLevel.Error) Console.ForegroundColor = ConsoleColor.Red; + + Console.WriteLine($"{date.ToString("HH:mm:ss")} {msg}"); + + Console.ResetColor(); + }); } @@ -61,7 +66,7 @@ public class ExampleScenarios { foreach (var register in interf.Registers) { - Console.WriteLine($"{register.GetCombinedName()} / {register.GetRegisterPLCName()} - Value: {register.GetValueString()}"); + Console.WriteLine($"{register.ToString(true)}"); } @@ -186,10 +191,8 @@ public class ExampleScenarios { [Scenario("Read register test")] public async Task RunReadTest () { - Console.WriteLine("Starting auto enums and bitwise"); - //setting up a new PLC interface and register collection - MewtocolInterface interf = new MewtocolInterface("192.168.115.210").WithPoller(); + MewtocolInterface interf = new MewtocolInterface("192.168.115.210"); //auto add all built registers to the interface var builder = RegBuilder.ForInterface(interf); @@ -200,8 +203,9 @@ public class ExampleScenarios { var shortReg = builder.FromPlcRegName("DT35").AsPlcType(PlcVarType.INT).Build(); builder.FromPlcRegName("DDT36").AsPlcType(PlcVarType.DINT).Build(); + builder.FromPlcRegName("DT200").AsBytes(30).Build(); - //builder.FromPlcRegName("DDT38").AsPlcType(PlcVarType.TIME).Build(); + builder.FromPlcRegName("DDT38").AsPlcType(PlcVarType.TIME).Build(); //builder.FromPlcRegName("DT40").AsPlcType(PlcVarType.STRING).Build(); //connect @@ -214,12 +218,20 @@ public class ExampleScenarios { await interf.SetRegisterAsync(r0reg, !(bool)r0reg.Value); await interf.SetRegisterAsync(shortReg, (short)new Random().Next(0, 100)); + var sw = Stopwatch.StartNew(); + foreach (var reg in interf.Registers) { - Console.WriteLine($"Register {reg.GetRegisterPLCName()} val: {reg.Value}"); + await reg.ReadAsync(); + + Console.WriteLine($"Register {reg.ToString()}"); } + sw.Stop(); + + Console.WriteLine($"Total read time for registers: {sw.Elapsed.TotalMilliseconds:N0}ms"); + Console.WriteLine(); await Task.Delay(1000); @@ -228,4 +240,69 @@ public class ExampleScenarios { } + [Scenario("Test multi frame")] + public async Task MultiFrameTest() { + + var preLogLevel = Logger.LogLevel; + Logger.LogLevel = LogLevel.Error; + + //setting up a new PLC interface and register collection + MewtocolInterface interf = new MewtocolInterface("192.168.115.210"); + + //auto add all built registers to the interface + var builder = RegBuilder.ForInterface(interf); + var r0reg = builder.FromPlcRegName("R0").Build(); + builder.FromPlcRegName("R1").Build(); + builder.FromPlcRegName("R1F").Build(); + builder.FromPlcRegName("R60A").Build(); + builder.FromPlcRegName("R61A").Build(); + builder.FromPlcRegName("R62A").Build(); + builder.FromPlcRegName("R63A").Build(); + builder.FromPlcRegName("R64A").Build(); + builder.FromPlcRegName("R65A").Build(); + builder.FromPlcRegName("R66A").Build(); + builder.FromPlcRegName("R67A").Build(); + builder.FromPlcRegName("R68A").Build(); + builder.FromPlcRegName("R69A").Build(); + builder.FromPlcRegName("R70A").Build(); + builder.FromPlcRegName("R71A").Build(); + + //connect + await interf.ConnectAsync(); + + Console.WriteLine("Poller cycle started"); + var sw = Stopwatch.StartNew(); + + await interf.RunPollerCylceManual(false); + + sw.Stop(); + + Console.WriteLine("Poller cycle finished"); + + Console.WriteLine($"Single frame excec time: {sw.ElapsedMilliseconds:N0}ms"); + + interf.Disconnect(); + + await Task.Delay(1000); + + await interf.ConnectAsync(); + + sw = Stopwatch.StartNew(); + + Console.WriteLine("Poller cycle started"); + + await interf.RunPollerCylceManual(true); + + sw.Stop(); + + Console.WriteLine("Poller cycle finished"); + + Console.WriteLine($"Multi frame excec time: {sw.ElapsedMilliseconds:N0}ms"); + + Logger.LogLevel = preLogLevel; + + await Task.Delay(10000); + + } + } diff --git a/Examples/Program.cs b/Examples/Program.cs index 57c913f..3b2a56e 100644 --- a/Examples/Program.cs +++ b/Examples/Program.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading.Tasks; +using MewtocolNet.Logging; +using System.Text.RegularExpressions; namespace Examples; @@ -55,20 +57,23 @@ class Program { Console.WriteLine("\nEnter a number to excecute a example"); Console.ResetColor(); - Console.WriteLine("\nOther possible commands: \n\n" + - "'toggle logger' - toggle the built in mewtocol logger on/off\n" + - "'exit' - to close this program \n" + - "'clear' - to clear the output \n"); + Console.WriteLine("\nOther possible commands: \n"); + Console.WriteLine($"'logger ' - set loglevel to one of: {string.Join(", ", Enum.GetNames(typeof(LogLevel)).ToList())}"); + Console.WriteLine("'exit' - to close this program"); + Console.WriteLine("'clear' - to clear the output"); + Console.Write("> "); var line = Console.ReadLine(); - if (line == "toggle logger") { + var loggerMatch = Regex.Match(line, @"logger (?[a-zA-Z]{0,})"); - ExampleScenarios.MewtocolLoggerEnabled = !ExampleScenarios.MewtocolLoggerEnabled; + if (loggerMatch.Success && Enum.TryParse(loggerMatch.Groups["level"].Value, out var loglevel)) { - Console.WriteLine(ExampleScenarios.MewtocolLoggerEnabled ? "Logger enabled" : "Logger disabled"); + Logger.LogLevel = loglevel; + + Console.WriteLine($"Loglevel changed to: {loglevel}"); } else if (line == "exit") { diff --git a/MewtocolNet/DynamicInterface.cs b/MewtocolNet/DynamicInterface.cs index 1bd86c6..357bd52 100644 --- a/MewtocolNet/DynamicInterface.cs +++ b/MewtocolNet/DynamicInterface.cs @@ -7,6 +7,8 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Text; +using System.Threading; using System.Threading.Tasks; namespace MewtocolNet @@ -35,6 +37,7 @@ namespace MewtocolNet internal volatile bool pollerFirstCycle = false; internal bool usePoller = false; + internal bool pollerUseMultiFrame = false; #region Register Polling @@ -93,66 +96,140 @@ namespace MewtocolNet pollerFirstCycle = true; - Task.Factory.StartNew(async () => { + Task.Run(Poll); - Logger.Log("Poller is attaching", LogLevel.Info, this); + } - int iteration = 0; + /// + /// Runs a single poller cycle manually, + /// useful if you want to use a custom update frequency + /// + /// + public async Task RunPollerCylceManual (bool useMultiFrame = false) { - pollerTaskStopped = false; - pollerTaskRunning = true; - pollerIsPaused = false; + if (useMultiFrame) { + await OnMultiFrameCycle(); + } else { + await OnSingleFrameCycle(); + } - while (!pollerTaskStopped) { + } - while (pollerTaskRunning) { + //polls all registers one by one (slow) + internal async Task Poll () { - if (iteration >= Registers.Count + 1) { - iteration = 0; - //invoke cycle polled event - InvokePolledCycleDone(); - continue; - } + Logger.Log("Poller is attaching", LogLevel.Info, this); - if (iteration >= Registers.Count) { - await GetPLCInfoAsync(); - iteration++; - continue; - } + int iteration = 0; - var reg = Registers[iteration]; + pollerTaskStopped = false; + pollerTaskRunning = true; + pollerIsPaused = false; - if(reg.IsAllowedRegisterGenericType()) { + while (!pollerTaskStopped) { - var lastVal = reg.Value; + if (iteration >= Registers.Count + 1) { + iteration = 0; + //invoke cycle polled event + InvokePolledCycleDone(); + continue; + } - var rwReg = (IRegisterInternal)reg; + if(pollerUseMultiFrame) { + await OnMultiFrameCycle(); + } else { + await OnSingleFrameCycle(); + } - var readout = await rwReg.ReadAsync(this); + pollerFirstCycle = false; - if (lastVal != readout) { + iteration++; - rwReg.SetValueFromPLC(readout); - InvokeRegisterChanged(reg); - - } + pollerIsPaused = !pollerTaskRunning; - } + } - iteration++; - pollerFirstCycle = false; + pollerIsPaused = false; - await Task.Delay(pollerDelayMs); + } + + private async Task OnSingleFrameCycle () { + + foreach (var reg in Registers) { + + if (reg.IsAllowedRegisterGenericType()) { + + var lastVal = reg.Value; + + var rwReg = (IRegisterInternal)reg; + + var readout = await rwReg.ReadAsync(); + + if (lastVal != readout) { + + rwReg.SetValueFromPLC(readout); + InvokeRegisterChanged(reg); } - pollerIsPaused = !pollerTaskRunning; + } + + } + + await GetPLCInfoAsync(); + + } + + private async Task OnMultiFrameCycle () { + + await UpdateRCPRegisters(); + + await GetPLCInfoAsync(); + + } + + private async Task UpdateRCPRegisters () { + + //build booleans + var rcpList = Registers.Where(x => x.GetType() == typeof(BoolRegister)) + .Select(x => (BoolRegister)x) + .ToArray(); + + //one frame can only read 8 registers at a time + int rcpFrameCount = (int)Math.Ceiling((double)rcpList.Length / 8); + int rcpLastFrameRemainder = rcpList.Length <= 8 ? rcpList.Length : rcpList.Length % 8; + + for (int i = 0; i < rcpFrameCount; i++) { + + int toReadRegistersCount = 8; + + if(i == rcpFrameCount - 1) toReadRegistersCount = rcpLastFrameRemainder; + + var rcpString = new StringBuilder($"%{GetStationNumber()}#RCP{toReadRegistersCount}"); + + for (int j = 0; j < toReadRegistersCount; j++) { + + BoolRegister register = rcpList[i + j]; + rcpString.Append(register.BuildMewtocolQuery()); } - pollerIsPaused = false; + string rcpRequest = rcpString.ToString(); + var result = await SendCommandAsync(rcpRequest); + var resultBitArray = result.Response.ParseRCMultiBit(); - }); + for (int k = 0; k < resultBitArray.Length; k++) { + + var register = rcpList[i + k]; + + if((bool)register.Value != resultBitArray[k]) { + register.SetValueFromPLC(resultBitArray[k]); + InvokeRegisterChanged(register); + } + + } + + } } @@ -166,8 +243,6 @@ namespace MewtocolNet #region Register Colleciton adding - #region Register Collection - /// /// Attaches a register collection object to /// the interface that can be updated automatically. @@ -212,7 +287,7 @@ namespace MewtocolNet RegisterChanged += (reg) => { //register is used bitwise - if (reg.IsUsedBitwise()) { + if (reg.GetType() == typeof(BytesRegister)) { for (int i = 0; i < props.Length; i++) { @@ -314,8 +389,6 @@ namespace MewtocolNet #endregion - #endregion - #region Register Adding internal void AddRegister (RegisterBuildInfo buildInfo) { @@ -323,7 +396,7 @@ namespace MewtocolNet var builtRegister = buildInfo.Build(); //is bitwise and the register list already contains that area register - if(builtRegister.IsUsedBitwise() && CheckDuplicateRegister(builtRegister, out var existing)) { + if(builtRegister.GetType() == typeof(BytesRegister) && CheckDuplicateRegister(builtRegister, out var existing)) { return; @@ -335,11 +408,12 @@ namespace MewtocolNet if(CheckDuplicateNameRegister(builtRegister)) throw MewtocolException.DupeNameRegister(builtRegister); + builtRegister.attachedInterface = this; Registers.Add(builtRegister); } - public void AddRegister(IRegister register) { + public void AddRegister (BaseRegister register) { if (CheckDuplicateRegister(register)) throw MewtocolException.DupeRegister(register); @@ -347,29 +421,30 @@ namespace MewtocolNet if (CheckDuplicateNameRegister(register)) throw MewtocolException.DupeNameRegister(register); + register.attachedInterface = this; Registers.Add(register); } - private bool CheckDuplicateRegister (IRegister instance, out IRegister foundDupe) { + private bool CheckDuplicateRegister (IRegisterInternal instance, out IRegisterInternal foundDupe) { - foundDupe = Registers.FirstOrDefault(x => x.CompareIsDuplicate(instance)); + foundDupe = RegistersInternal.FirstOrDefault(x => x.CompareIsDuplicate(instance)); - return Registers.Contains(instance) || foundDupe != null; + return RegistersInternal.Contains(instance) || foundDupe != null; } - private bool CheckDuplicateRegister(IRegister instance) { + private bool CheckDuplicateRegister(IRegisterInternal instance) { - var foundDupe = Registers.FirstOrDefault(x => x.CompareIsDuplicate(instance)); + var foundDupe = RegistersInternal.FirstOrDefault(x => x.CompareIsDuplicate(instance)); - return Registers.Contains(instance) || foundDupe != null; + return RegistersInternal.Contains(instance) || foundDupe != null; } - private bool CheckDuplicateNameRegister(IRegister instance) { + private bool CheckDuplicateNameRegister(IRegisterInternal instance) { - return Registers.Any(x => x.CompareIsNameDuplicate(instance)); + return RegistersInternal.Any(x => x.CompareIsNameDuplicate(instance)); } @@ -387,25 +462,6 @@ namespace MewtocolNet } - /// - /// Gets a register that was added by its name - /// - /// The type of register - /// A casted register or the default value - public T GetRegister(string name) where T : IRegister { - try { - - var reg = Registers.FirstOrDefault(x => x.Name == name); - return (T)reg; - - } catch (InvalidCastException) { - - return default(T); - - } - - } - #endregion #region Register Reading @@ -413,9 +469,9 @@ namespace MewtocolNet /// /// Gets a list of all added registers /// - public List GetAllRegisters() { + public IEnumerable GetAllRegisters() { - return Registers; + return Registers.Cast(); } diff --git a/MewtocolNet/Exceptions/MewtocolException.cs b/MewtocolNet/Exceptions/MewtocolException.cs index 0601df4..46c948e 100644 --- a/MewtocolNet/Exceptions/MewtocolException.cs +++ b/MewtocolNet/Exceptions/MewtocolException.cs @@ -17,15 +17,15 @@ namespace MewtocolNet.Exceptions { System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - public static MewtocolException DupeRegister (IRegister register) { + internal static MewtocolException DupeRegister (IRegisterInternal register) { return new MewtocolException($"The mewtocol interface already contains this register: {register.GetRegisterPLCName()}"); } - public static MewtocolException DupeNameRegister (IRegister register) { + internal static MewtocolException DupeNameRegister (IRegisterInternal register) { - return new MewtocolException($"The mewtocol interface registers already contains a register with the name: {register.Name}"); + return new MewtocolException($"The mewtocol interface registers already contains a register with the name: {register.GetRegisterPLCName()}"); } diff --git a/MewtocolNet/IRegister.cs b/MewtocolNet/IRegister.cs index c442584..6bd6bca 100644 --- a/MewtocolNet/IRegister.cs +++ b/MewtocolNet/IRegister.cs @@ -23,6 +23,11 @@ namespace MewtocolNet { /// string Name { get; } + /// + /// Gets the register address name as in the plc + /// + string PLCAddressName { get; } + /// /// The current value of the register /// @@ -33,73 +38,6 @@ namespace MewtocolNet { /// int MemoryAddress { get; } - /// - /// Gets the special address of the register or -1 if it has none - /// - /// - byte? GetSpecialAddress(); - - /// - /// Indicates if the register is processed bitwise - /// - /// True if bitwise - bool IsUsedBitwise(); - - /// - /// Generates a string describing the starting memory address of the register - /// - string GetStartingMemoryArea(); - - /// - /// Gets the current value formatted as a readable string - /// - string GetValueString(); - - /// - /// Builds the identifier for the mewtocol query string - /// - /// - string BuildMewtocolQuery(); - - /// - /// Builds a register string that prepends the memory address fe. DDT or DT, X, Y etc - /// - string GetRegisterString(); - - /// - /// Builds a combined name for the attached property to uniquely identify the property register binding - /// - /// - string GetCombinedName(); - - /// - /// Gets the name of the class that contains the attached property - /// - /// - string GetContainerName(); - - /// - /// Builds a register name after the PLC convention
- /// Example DDT100, XA, X6, Y1, DT3300 - ///
- string GetRegisterPLCName(); - - /// - /// Clears the current value of the register and resets it to default - /// - void ClearValue(); - - /// - /// Triggers a notifychanged update event - /// - void TriggerNotifyChange(); - - /// - /// Gets the type of the class collection its attached property is in or null - /// - /// The class name or null if manually added - Type GetCollectionType(); - /// /// Builds a readable string with all important register informations /// @@ -110,6 +48,18 @@ namespace MewtocolNet { /// string ToString(bool detailed); + /// + /// Sets the register value in the plc async + /// + /// True if successful + Task SetValueAsync(); + + /// + /// Gets the register value from the plc async + /// + /// The value or null if failed + Task GetValueAsync(); + } } diff --git a/MewtocolNet/IRegisterInternal.cs b/MewtocolNet/IRegisterInternal.cs index 9b9c5a4..e5b7a3a 100644 --- a/MewtocolNet/IRegisterInternal.cs +++ b/MewtocolNet/IRegisterInternal.cs @@ -5,13 +5,60 @@ using System.Threading.Tasks; namespace MewtocolNet { internal interface IRegisterInternal { + event Action ValueChanged; + + //props + + MewtocolInterface AttachedInterface { get; } + + RegisterType RegisterType { get; } + + string Name { get; } + + object Value { get; } + + int MemoryAddress { get; } + + // setters + void WithCollectionType(Type colType); void SetValueFromPLC(object value); - Task ReadAsync(MewtocolInterface interf); + void ClearValue(); - Task WriteAsync(MewtocolInterface interf, object data); + // Accessors + + Type GetCollectionType(); + + string GetRegisterString(); + + string GetCombinedName(); + + string GetContainerName(); + + string GetRegisterPLCName(); + + byte? GetSpecialAddress(); + + string GetStartingMemoryArea(); + + string GetValueString(); + + string BuildMewtocolQuery(); + + + //others + + void TriggerNotifyChange(); + + Task ReadAsync(); + + Task WriteAsync(object data); + + string ToString(); + + string ToString(bool detailed); } diff --git a/MewtocolNet/Logging/Logger.cs b/MewtocolNet/Logging/Logger.cs index d9d68ae..53e9c01 100644 --- a/MewtocolNet/Logging/Logger.cs +++ b/MewtocolNet/Logging/Logger.cs @@ -12,15 +12,15 @@ namespace MewtocolNet.Logging { /// public static LogLevel LogLevel { get; set; } - internal static Action LogInvoked; + internal static Action LogInvoked; /// /// Gets invoked whenever a new log message is ready /// - public static void OnNewLogMessage(Action onMsg) { + public static void OnNewLogMessage(Action onMsg) { - LogInvoked += (t, m) => { - onMsg(t, m); + LogInvoked += (t, l, m) => { + onMsg(t, l, m); }; } @@ -29,9 +29,9 @@ namespace MewtocolNet.Logging { if ((int)loglevel <= (int)LogLevel) { if (sender == null) { - LogInvoked?.Invoke(DateTime.Now, message); + LogInvoked?.Invoke(DateTime.Now, loglevel, message); } else { - LogInvoked?.Invoke(DateTime.Now, $"[{sender.GetConnectionPortInfo()}] {message}"); + LogInvoked?.Invoke(DateTime.Now, loglevel, $"[{sender.GetConnectionPortInfo()}] {message}"); } } diff --git a/MewtocolNet/MewtocolHelpers.cs b/MewtocolNet/MewtocolHelpers.cs index 7da65b8..38c0285 100644 --- a/MewtocolNet/MewtocolHelpers.cs +++ b/MewtocolNet/MewtocolHelpers.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text; using System.Text.RegularExpressions; @@ -26,7 +27,7 @@ namespace MewtocolNet { /// /// Converts a string (after converting to upper case) to ascii bytes /// - internal static byte[] ToHexASCIIBytes(this string _str) { + internal static byte[] BytesFromHexASCIIString(this string _str) { ASCIIEncoding ascii = new ASCIIEncoding(); byte[] bytes = ascii.GetBytes(_str.ToUpper()); @@ -73,31 +74,59 @@ namespace MewtocolNet { } - internal static string ParseDTString(this string _onString) { + internal static BitArray ParseRCMultiBit(this string _onString) { - var res = new Regex(@"\%([0-9]{2})\$RD.{8}(.*)...").Match(_onString); + var res = new Regex(@"\%([0-9]{2})\$RC(?(?:0|1){0,8})(..)").Match(_onString); if (res.Success) { - string val = res.Groups[2].Value; - return val.GetStringFromAsciiHex()?.Trim(); + + string val = res.Groups["bits"].Value; + + return new BitArray(val.Select(c => c == '1').ToArray()); + } return null; } - internal static string ReverseByteOrder(this string _onString) { + /// + /// Parses a return string from the PLC as a raw byte array
+ /// Example: + /// + /// ↓Start ↓end + /// %01$RD0100020010\r + /// + /// This will return the byte array: + /// + /// [0x01, 0x00, 0x02, 0x00] + /// + ///
+ /// + /// A or null of failed + internal static byte[] ParseDTRawStringAsBytes (this string _onString) { - if (_onString == null) return null; + var res = new Regex(@"\%([0-9]{2})\$RD(?.*)(?..)..").Match(_onString); + if (res.Success) { - //split into 2 chars - var stringBytes = _onString.SplitInParts(2).ToList(); + string val = res.Groups["data"].Value; + var parts = val.SplitInParts(2).ToArray(); + var bytes = new byte[parts.Length]; - stringBytes.Reverse(); + for (int i = 0; i < bytes.Length; i++) { - return string.Join("", stringBytes); + bytes[i] = byte.Parse(parts[i], NumberStyles.HexNumber); + + } + + return bytes; + } + return null; } - internal static IEnumerable SplitInParts(this string s, int partLength) { + /// + /// Splits a string in even parts + /// + internal static IEnumerable SplitInParts(this string s, int partLength) { if (s == null) throw new ArgumentNullException(nameof(s)); @@ -109,63 +138,7 @@ namespace MewtocolNet { } - internal static string BuildDTString (this byte[] inBytes, short reservedSize) { - - StringBuilder sb = new StringBuilder(); - - //clamp string lenght - if (inBytes.Length > reservedSize) { - inBytes = inBytes.Take(reservedSize).ToArray(); - } - - //actual string content - var hexstring = inBytes.ToHexString(); - - var sizeBytes = BitConverter.GetBytes((short)(hexstring.Length / 2)).ToHexString(); - - if (hexstring.Length >= 2) { - - var remainderBytes = (hexstring.Length / 2) % 2; - - if (remainderBytes != 0) { - hexstring += "20"; - } - - } - - var reservedSizeBytes = BitConverter.GetBytes(reservedSize).ToHexString(); - - //reserved string count bytes - sb.Append(reservedSizeBytes); - //string count actual bytes - sb.Append(sizeBytes); - - - sb.Append(hexstring); - - return sb.ToString(); - } - - - internal static string GetStringFromAsciiHex(this string input) { - if (input.Length % 2 != 0) - return null; - 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); - } - - internal static string GetAsciiHexFromString(this string input) { - - var bytes = new ASCIIEncoding().GetBytes(input); - return bytes.ToHexString(); - - } - - internal static byte[] HexStringToByteArray(this string hex) { + internal static byte[] HexStringToByteArray (this string hex) { if (hex == null) return null; return Enumerable.Range(0, hex.Length) @@ -174,13 +147,39 @@ namespace MewtocolNet { .ToArray(); } - internal static string ToHexString(this byte[] arr) { + /// + /// Converts a byte array to a hexadecimal string + /// + /// Seperator between the hex numbers + /// The byte array + internal static string ToHexString (this byte[] arr, string seperator = "") { StringBuilder sb = new StringBuilder(); + for (int i = 0; i < arr.Length; i++) { byte b = arr[i]; sb.Append(b.ToString("X2")); + if(i < arr.Length - 1) sb.Append(seperator); } + + return sb.ToString(); + + } + + internal static string AsPLC (this TimeSpan timespan) { + + StringBuilder sb = new StringBuilder("T#"); + + int millis = timespan.Milliseconds; + int seconds = timespan.Seconds; + int minutes = timespan.Minutes; + int hours = timespan.Hours; + + if (hours > 0) sb.Append($"{hours}h"); + if (minutes > 0) sb.Append($"{minutes}m"); + if (seconds > 0) sb.Append($"{seconds}s"); + if (millis > 0) sb.Append($"{millis}ms"); + return sb.ToString(); } @@ -207,39 +206,6 @@ namespace MewtocolNet { } - internal static bool IsDoubleNumericRegisterType(this Type type) { - - //Type[] singles = new Type[] { - // typeof(short), - // typeof(ushort), - //}; - - Type[] doubles = new Type[] { - typeof(int), - typeof(uint), - typeof(float), - typeof(TimeSpan), - }; - - return doubles.Contains(type); - - } - - internal static bool IsNumericSupportedType(this Type type) { - - Type[] supported = new Type[] { - typeof(short), - typeof(ushort), - typeof(int), - typeof(uint), - typeof(float), - typeof(TimeSpan), - }; - - return supported.Contains(type); - - } - /// /// Checks if the register type is boolean /// @@ -267,7 +233,7 @@ namespace MewtocolNet { } - internal static bool CompareIsDuplicate (this IRegister reg1, IRegister compare) { + internal static bool CompareIsDuplicate (this IRegisterInternal reg1, IRegisterInternal compare) { bool valCompare = reg1.RegisterType == compare.RegisterType && reg1.MemoryAddress == compare.MemoryAddress && @@ -277,7 +243,7 @@ namespace MewtocolNet { } - internal static bool CompareIsNameDuplicate(this IRegister reg1, IRegister compare) { + internal static bool CompareIsNameDuplicate(this IRegisterInternal reg1, IRegisterInternal compare) { return ( reg1.Name != null || compare.Name != null) && reg1.Name == compare.Name; diff --git a/MewtocolNet/MewtocolInterface.cs b/MewtocolNet/MewtocolInterface.cs index 273dc1e..98a3aa4 100644 --- a/MewtocolNet/MewtocolInterface.cs +++ b/MewtocolNet/MewtocolInterface.cs @@ -1,3 +1,4 @@ +using MewtocolNet.Exceptions; using MewtocolNet.Logging; using MewtocolNet.Queue; using MewtocolNet.RegisterAttributes; @@ -113,7 +114,9 @@ namespace MewtocolNet { /// /// The registered data registers of the PLC /// - public List Registers { get; set; } = new List(); + public List Registers { get; private set; } = new List(); + + internal IEnumerable RegistersInternal => Registers.Cast(); private string ip; private int port; @@ -208,11 +211,13 @@ namespace MewtocolNet { RegisterChanged += (o) => { - string address = $"{o.GetRegisterString()}{o.GetStartingMemoryArea()}".PadRight(5, (char)32); + var asInternal = (IRegisterInternal)o; + + string address = $"{asInternal.GetRegisterString()}{asInternal.GetStartingMemoryArea()}".PadRight(5, (char)32); Logger.Log($"{address} " + $"{(o.Name != null ? $"({o.Name}) " : "")}" + - $"changed to \"{o.GetValueString()}\"", LogLevel.Change, this); + $"changed to \"{asInternal.GetValueString()}\"", LogLevel.Change, this); }; } @@ -305,9 +310,10 @@ namespace MewtocolNet { /// Attaches a poller to the interface that continously /// polls the registered data registers and writes the values to them /// - public MewtocolInterface WithPoller() { + public MewtocolInterface WithPoller(bool useMultiFrame = false) { usePoller = true; + pollerUseMultiFrame = useMultiFrame; return this; } @@ -397,7 +403,7 @@ namespace MewtocolNet { for (int i = 0; i < Registers.Count; i++) { - var reg = Registers[i]; + var reg = (IRegisterInternal)Registers[i]; reg.ClearValue(); } @@ -423,22 +429,38 @@ namespace MewtocolNet { queuedMessages++; - var response = await queue.Enqueue(() => SendSingleBlock(_msg)); + TCPMessageResult tcpResult = TCPMessageResult.Waiting; + string response = ""; - if (queuedMessages > 0) - queuedMessages--; + int lineFeedFails = 0; + + //recursively try to get a response on failed line feeds + while (tcpResult == TCPMessageResult.Waiting || tcpResult == TCPMessageResult.FailedLineFeed) { + + if (lineFeedFails >= 5) + throw new MewtocolException($"The message ${_msg} had {lineFeedFails} linefeed fails"); + + var tempResponse = await queue.Enqueue(() => SendSingleBlock(_msg)); + tcpResult = tempResponse.Item1; + response = tempResponse.Item2; + + if(tcpResult == TCPMessageResult.FailedLineFeed) { + lineFeedFails++; + Logger.Log($"Linefeed fail, retrying...", LogLevel.Error); + } + + if (queuedMessages > 0) + queuedMessages--; + + if (tcpResult == TCPMessageResult.FailedWithException) + throw new MewtocolException("The connection to the device was terminated"); - 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()); + Match m = errorcheck.Match(response); + if (m.Success) { string eCode = m.Groups[1].Value; string eDes = CodeDescriptions.Error[Convert.ToInt32(eCode)]; @@ -456,7 +478,7 @@ namespace MewtocolNet { return new CommandResult { Success = true, Error = "0000", - Response = response.ToString() + Response = response, }; } catch { @@ -469,16 +491,16 @@ namespace MewtocolNet { } - private async Task SendSingleBlock(string _blockString) { + private async Task<(TCPMessageResult, string)> SendSingleBlock(string _blockString) { if (client == null || !client.Connected) { await ConnectTCP(); } if (client == null || !client.Connected) - return null; + return (TCPMessageResult.NotConnected, null); - var message = _blockString.ToHexASCIIBytes(); + var message = _blockString.BytesFromHexASCIIString(); //time measuring if (speedStopwatchUpstr == null) { @@ -542,13 +564,16 @@ namespace MewtocolNet { } catch (IOException) { OnMajorSocketExceptionWhileConnected(); - return null; + return (TCPMessageResult.FailedWithException, null); } catch (SocketException) { OnMajorSocketExceptionWhileConnected(); - return null; + return (TCPMessageResult.FailedWithException, null); } - if (!string.IsNullOrEmpty(response.ToString())) { + + string resString = response.ToString(); + + if (!string.IsNullOrEmpty(resString) && resString != "\r" ) { Logger.Log($"<-- IN MSG: {response}", LogLevel.Critical, this); @@ -559,10 +584,12 @@ namespace MewtocolNet { if (perSecUpstream <= 10000) BytesPerSecondDownstream = (int)Math.Round(perSecUpstream, MidpointRounding.AwayFromZero); - return response.ToString(); + return (TCPMessageResult.Success, resString); } else { - return null; + + return (TCPMessageResult.FailedLineFeed, null); + } } diff --git a/MewtocolNet/MewtocolInterfaceRequests.cs b/MewtocolNet/MewtocolInterfaceRequests.cs index 6a3b9cc..3b0f4f9 100644 --- a/MewtocolNet/MewtocolInterfaceRequests.cs +++ b/MewtocolNet/MewtocolInterfaceRequests.cs @@ -159,10 +159,12 @@ namespace MewtocolNet { #region Raw register reading / writing - internal async Task ReadRawRegisterAsync (IRegister _toRead) { + internal async Task ReadRawRegisterAsync (IRegisterInternal _toRead) { + + var toreadType = _toRead.GetType(); //returns a byte array 1 long and with the byte beeing 0 or 1 - if (_toRead.GetType() == typeof(BoolRegister)) { + if (toreadType == typeof(BoolRegister)) { string requeststring = $"%{GetStationNumber()}#RCS{_toRead.BuildMewtocolQuery()}"; var result = await SendCommandAsync(requeststring); @@ -177,7 +179,7 @@ namespace MewtocolNet { } //returns a byte array 2 bytes or 4 bytes long depending on the data size - if (_toRead.GetType().GetGenericTypeDefinition() == typeof(NumberRegister<>)) { + if (toreadType.IsGenericType && _toRead.GetType().GetGenericTypeDefinition() == typeof(NumberRegister<>)) { string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolQuery()}"; var result = await SendCommandAsync(requeststring); @@ -198,7 +200,7 @@ namespace MewtocolNet { } //returns a byte array with variable size - if (_toRead.GetType() == typeof(BytesRegister<>)) { + if (toreadType == typeof(BytesRegister)) { string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolQuery()}"; var result = await SendCommandAsync(requeststring); @@ -206,7 +208,9 @@ namespace MewtocolNet { if (!result.Success) throw new Exception($"Failed to load the byte data for: {_toRead}"); - return result.Response.ParseDTString().ReverseByteOrder().HexStringToByteArray(); + var resBytes = result.Response.ParseDTRawStringAsBytes(); + + return resBytes; } @@ -214,7 +218,7 @@ namespace MewtocolNet { } - internal async Task WriteRawRegisterAsync (IRegister _toWrite, byte[] data) { + internal async Task WriteRawRegisterAsync (IRegisterInternal _toWrite, byte[] data) { //returns a byte array 1 long and with the byte beeing 0 or 1 if (_toWrite.GetType() == typeof(BoolRegister)) { @@ -235,7 +239,7 @@ namespace MewtocolNet { } //returns a byte array with variable size - if (_toWrite.GetType() == typeof(BytesRegister<>)) { + if (_toWrite.GetType() == typeof(BytesRegister)) { //string stationNum = GetStationNumber(); //string dataString = gotBytes.BuildDTString(_toWrite.ReservedSize); @@ -259,7 +263,7 @@ namespace MewtocolNet { var internalReg = (IRegisterInternal)register; - return await internalReg.WriteAsync(this, value); + return await internalReg.WriteAsync(value); } diff --git a/MewtocolNet/RegisterBuildInfo.cs b/MewtocolNet/RegisterBuildInfo.cs index c53074a..4ad24f6 100644 --- a/MewtocolNet/RegisterBuildInfo.cs +++ b/MewtocolNet/RegisterBuildInfo.cs @@ -17,52 +17,64 @@ namespace MewtocolNet internal Type dotnetCastType; internal Type collectionType; - internal IRegister Build () { + internal BaseRegister Build () { RegisterType regType = registerType ?? dotnetCastType.ToRegisterTypeDefault(); - PlcVarType plcType = dotnetCastType.ToPlcVarType(); - Type registerClassType = plcType.GetDefaultPlcVarType(); + Type registerClassType = dotnetCastType.GetDefaultRegisterHoldingType(); - if (regType.IsNumericDTDDT() && (dotnetCastType == typeof(bool) || dotnetCastType == typeof(BitArray))) { + bool isBytesRegister = !registerClassType.IsGenericType && registerClassType == typeof(BytesRegister); + + if (regType.IsNumericDTDDT() && (dotnetCastType == typeof(bool))) { //------------------------------------------- //as numeric register with boolean bit target - - var type = typeof(NumberRegister); - - var areaAddr = memoryAddress; - //create a new bregister instance var flags = BindingFlags.Public | BindingFlags.Instance; - //int _adress, string _name = null, bool isBitwise = false, Type _enumType = null - var parameters = new object[] { areaAddr, name, true, null }; - var instance = (IRegister)Activator.CreateInstance(type, flags, null, parameters, null); + //int _adress, int _reservedByteSize, string _name = null + var parameters = new object[] { memoryAddress, memorySizeBytes, name }; + var instance = (BaseRegister)Activator.CreateInstance(typeof(BytesRegister), flags, null, parameters, null); if (collectionType != null) - ((IRegisterInternal)instance).WithCollectionType(collectionType); + instance.WithCollectionType(collectionType); return instance; - } else if (regType.IsNumericDTDDT()) { + } else if (regType.IsNumericDTDDT() && !isBytesRegister) { //------------------------------------------- //as numeric register - var type = plcType.GetDefaultPlcVarType(); - var areaAddr = memoryAddress; //create a new bregister instance var flags = BindingFlags.Public | BindingFlags.Instance; - //int _adress, string _name = null, bool isBitwise = false, Type _enumType = null - var parameters = new object[] { areaAddr, name, false, null }; - var instance = (IRegister)Activator.CreateInstance(type, flags, null, parameters, null); + //int _adress, Type _enumType = null, string _name = null + var parameters = new object[] { areaAddr, null, name }; + var instance = (BaseRegister)Activator.CreateInstance(registerClassType, flags, null, parameters, null); if(collectionType != null) - ((IRegisterInternal)instance).WithCollectionType(collectionType); + instance.WithCollectionType(collectionType); + + return instance; + + } + + if(isBytesRegister) { + + //------------------------------------------- + //as byte range register + + //create a new bregister instance + var flags = BindingFlags.Public | BindingFlags.Instance; + //int _adress, int _reservedSize, string _name = null + var parameters = new object[] { memoryAddress, memorySizeBytes, name }; + var instance = (BaseRegister)Activator.CreateInstance(typeof(BytesRegister), flags, null, parameters, null); + + if (collectionType != null) + instance.WithCollectionType(collectionType); return instance; @@ -89,26 +101,6 @@ namespace MewtocolNet } - if(regType == RegisterType.DT_RANGE) { - - //------------------------------------------- - //as byte range register - - var type = plcType.GetDefaultPlcVarType(); - - //create a new bregister instance - var flags = BindingFlags.Public | BindingFlags.Instance; - //int _adress, int _reservedSize, string _name = null - var parameters = new object[] { memoryAddress, memorySizeBytes, name }; - var instance = (IRegister)Activator.CreateInstance(type, flags, null, parameters, null); - - if (collectionType != null) - ((IRegisterInternal)instance).WithCollectionType(collectionType); - - return instance; - - } - throw new Exception("Failed to build register"); } diff --git a/MewtocolNet/RegisterBuilding/FinalizerExtensions.cs b/MewtocolNet/RegisterBuilding/FinalizerExtensions.cs index a06e77b..4772469 100644 --- a/MewtocolNet/RegisterBuilding/FinalizerExtensions.cs +++ b/MewtocolNet/RegisterBuilding/FinalizerExtensions.cs @@ -14,10 +14,31 @@ namespace MewtocolNet.RegisterBuilding if (!step.wasCasted) step.AutoType(); + //fallbacks if no casting builder was given + step.GetFallbackDotnetType(); + + var builtReg = new RegisterBuildInfo { + + name = step.Name, + specialAddress = step.SpecialAddress, + memoryAddress = step.MemAddress, + memorySizeBytes = step.MemByteSize, + registerType = step.RegType, + dotnetCastType = step.dotnetVarType, + + }.Build(); + + step.AddToRegisterList(builtReg); + + return builtReg; + + } + + private static void GetFallbackDotnetType (this RegisterBuilderStep step) { + bool isBoolean = step.RegType.IsBoolean(); bool isTypeNotDefined = step.plcVarType == null && step.dotnetVarType == null; - //fallbacks if no casting builder was given if (isTypeNotDefined && step.RegType == RegisterType.DT) { step.dotnetVarType = typeof(short); @@ -31,35 +52,21 @@ namespace MewtocolNet.RegisterBuilding step.dotnetVarType = typeof(bool); - } else if (isTypeNotDefined && step.RegType == RegisterType.DT_RANGE) { + } else if (isTypeNotDefined && step.RegType == RegisterType.DT_BYTE_RANGE) { step.dotnetVarType = typeof(string); } - if(step.plcVarType != null) { + if (step.plcVarType != null) { step.dotnetVarType = step.plcVarType.Value.GetDefaultDotnetType(); } - var builtReg = new RegisterBuildInfo { - - name = step.Name, - specialAddress = step.SpecialAddress, - memoryAddress = step.MemAddress, - registerType = step.RegType, - dotnetCastType = step.dotnetVarType, - - }.Build(); - - step.AddToRegisterList(builtReg); - - return builtReg; - } - private static void AddToRegisterList (this RegisterBuilderStep step, IRegister instance) { + private static void AddToRegisterList (this RegisterBuilderStep step, BaseRegister instance) { if (step.forInterface == null) return; diff --git a/MewtocolNet/RegisterBuilding/RegisterBuilderStep.cs b/MewtocolNet/RegisterBuilding/RegisterBuilderStep.cs index 04883be..c334a72 100644 --- a/MewtocolNet/RegisterBuilding/RegisterBuilderStep.cs +++ b/MewtocolNet/RegisterBuilding/RegisterBuilderStep.cs @@ -13,6 +13,7 @@ namespace MewtocolNet.RegisterBuilding { internal string Name; internal RegisterType RegType; internal int MemAddress; + internal int MemByteSize; internal byte? SpecialAddress; internal PlcVarType? plcVarType; @@ -63,6 +64,24 @@ namespace MewtocolNet.RegisterBuilding { } + public RegisterBuilderStep AsBytes (int byteLength) { + + if (RegType != RegisterType.DT) { + + throw new NotSupportedException($"Cant use the AsByte converter on a non DT register"); + + } + + MemByteSize = byteLength; + dotnetVarType = typeof(byte[]); + plcVarType = null; + + wasCasted = true; + + return this; + + } + internal RegisterBuilderStep AutoType() { switch (RegType) { @@ -77,7 +96,7 @@ namespace MewtocolNet.RegisterBuilding { case RegisterType.DDT: dotnetVarType = typeof(int); break; - case RegisterType.DT_RANGE: + case RegisterType.DT_BYTE_RANGE: dotnetVarType = typeof(string); break; } diff --git a/MewtocolNet/RegisterEnums.cs b/MewtocolNet/RegisterEnums.cs index e23e89f..a4071a6 100644 --- a/MewtocolNet/RegisterEnums.cs +++ b/MewtocolNet/RegisterEnums.cs @@ -30,7 +30,7 @@ namespace MewtocolNet { /// /// Area of a byte sequence longer than 2 words /// - DT_RANGE = 5, + DT_BYTE_RANGE = 5, } diff --git a/MewtocolNet/Registers/BaseRegister.cs b/MewtocolNet/Registers/BaseRegister.cs new file mode 100644 index 0000000..13b0289 --- /dev/null +++ b/MewtocolNet/Registers/BaseRegister.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; +using System.Threading.Tasks; + +namespace MewtocolNet.Registers { + + public abstract class BaseRegister : IRegister, IRegisterInternal, INotifyPropertyChanged { + + /// + /// Gets called whenever the value was changed + /// + public event Action ValueChanged; + + internal MewtocolInterface attachedInterface; + internal object lastValue; + internal Type collectionType; + internal string name; + internal int memoryAddress; + + /// + public MewtocolInterface AttachedInterface => attachedInterface; + + /// + public object Value => lastValue; + + /// + public RegisterType RegisterType { get; protected set; } + + /// + public Type CollectionType => collectionType; + + /// + public string Name => name; + + /// + public string PLCAddressName => GetRegisterPLCName(); + + /// + public int MemoryAddress => memoryAddress; + + #region Trigger update notify + + public event PropertyChangedEventHandler PropertyChanged; + + internal void TriggerChangedEvnt(object changed) => ValueChanged?.Invoke(changed); + + public void TriggerNotifyChange() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value")); + + #endregion + + public virtual void ClearValue() => SetValueFromPLC(null); + + public virtual void SetValueFromPLC(object val) { + + lastValue = val; + TriggerChangedEvnt(this); + TriggerNotifyChange(); + + } + + public void WithCollectionType(Type colType) => collectionType = colType; + + #region Default accessors + + public Type GetCollectionType() => CollectionType; + + public RegisterType GetRegisterType() => RegisterType; + + public virtual string BuildMewtocolQuery() { + + StringBuilder asciistring = new StringBuilder("D"); + asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0')); + asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0')); + return asciistring.ToString(); + + } + + public virtual string GetStartingMemoryArea() => MemoryAddress.ToString(); + + public virtual byte? GetSpecialAddress() => null; + + public virtual string GetValueString() => Value?.ToString() ?? "null"; + + public virtual string GetRegisterString() => RegisterType.ToString(); + + public virtual string GetCombinedName() => $"{(CollectionType != null ? $"{CollectionType.Name}." : "")}{Name ?? "Unnamed"}"; + + public virtual string GetContainerName() => $"{(CollectionType != null ? $"{CollectionType.Name}" : "")}"; + + public virtual string GetRegisterPLCName() => $"{GetRegisterString()}{MemoryAddress}"; + + #endregion + + #region Read / Write + + public virtual async Task ReadAsync() => throw new NotImplementedException(); + + public virtual async Task WriteAsync(object data) => throw new NotImplementedException(); + + public virtual Task SetValueAsync() => throw new NotImplementedException(); + + public virtual Task GetValueAsync() => throw new NotImplementedException(); + + #endregion + + public override string ToString() => $"{GetRegisterPLCName()} - Value: {GetValueString()}"; + + public virtual string ToString(bool additional) { + + if (!additional) return this.ToString(); + + StringBuilder sb = new StringBuilder(); + sb.AppendLine($"PLC Naming: {GetRegisterPLCName()}"); + sb.AppendLine($"Name: {Name ?? "Not named"}"); + sb.AppendLine($"Value: {GetValueString()}"); + sb.AppendLine($"Register Type: {RegisterType}"); + sb.AppendLine($"Memory Address: {MemoryAddress}"); + + return sb.ToString(); + + } + + } + +} diff --git a/MewtocolNet/Registers/BoolRegister.cs b/MewtocolNet/Registers/BoolRegister.cs index 2ec09f4..3930814 100644 --- a/MewtocolNet/Registers/BoolRegister.cs +++ b/MewtocolNet/Registers/BoolRegister.cs @@ -8,45 +8,7 @@ namespace MewtocolNet.Registers { /// /// Defines a register containing a boolean /// - public class BoolRegister : IRegister, IRegisterInternal, INotifyPropertyChanged { - - /// - /// Gets called whenever the value was changed - /// - public event Action ValueChanged; - - /// - /// Triggers when a property on the register changes - /// - public event PropertyChangedEventHandler PropertyChanged; - - public RegisterType RegisterType { get; private set; } - - internal Type collectionType; - - /// - /// The type of collection the register is in or null of added manually - /// - public Type CollectionType => collectionType; - - internal bool lastValue; - - /// - /// The value of the register - /// - public object Value => lastValue; - - internal string name; - /// - /// The register name or null of not defined - /// - public string Name => name; - - internal int memoryAddress; - /// - /// The registers memory adress if not a special register - /// - public int MemoryAddress => memoryAddress; + public class BoolRegister : BaseRegister { internal byte specialAddress; /// @@ -77,6 +39,8 @@ namespace MewtocolNet.Registers { if (_spAddress > 0xF) throw new NotSupportedException("Special address cant be greater 15 or 0xF"); + lastValue = false; + memoryAddress = _areaAdress; specialAddress = _spAddress; name = _name; @@ -85,14 +49,41 @@ namespace MewtocolNet.Registers { } - public void WithCollectionType (Type colType) => collectionType = colType; + #region Read / Write - public byte? GetSpecialAddress() => SpecialAddress; + public override void SetValueFromPLC(object val) { - /// - /// Builds the register area name for the mewtocol protocol - /// - public string BuildMewtocolQuery() { + lastValue = (bool)val; + TriggerChangedEvnt(this); + TriggerNotifyChange(); + + } + + /// + public override async Task ReadAsync() { + + var read = await attachedInterface.ReadRawRegisterAsync(this); + var parsed = PlcValueParser.Parse(read); + + SetValueFromPLC(parsed); + return parsed; + + } + + /// + public override async Task WriteAsync(object data) { + + return await attachedInterface.WriteRawRegisterAsync(this, PlcValueParser.Encode((bool)data)); + + } + + #endregion + + /// + public override byte? GetSpecialAddress() => SpecialAddress; + + /// + public override string BuildMewtocolQuery() { //(R|X|Y)(area add [3] + special add [1]) StringBuilder asciistring = new StringBuilder(); @@ -109,37 +100,11 @@ namespace MewtocolNet.Registers { } - public void SetValueFromPLC(object val) { + /// + public override void ClearValue() => SetValueFromPLC(false); - lastValue = (bool)val; - TriggerChangedEvnt(this); - TriggerNotifyChange(); - - } - - public string GetStartingMemoryArea() { - - return MemoryAddress.ToString(); - - } - - public bool IsUsedBitwise() => false; - - public Type GetCollectionType() => CollectionType; - - public RegisterType GetRegisterType() => RegisterType; - - public string GetValueString() => Value.ToString(); - - public void ClearValue() => SetValueFromPLC(false); - - public string GetRegisterString() => RegisterType.ToString(); - - public string GetCombinedName() => $"{(CollectionType != null ? $"{CollectionType.Name}." : "")}{Name ?? "Unnamed"}"; - - public string GetContainerName() => $"{(CollectionType != null ? $"{CollectionType.Name}" : "")}"; - - public string GetRegisterPLCName() { + /// + public override string GetRegisterPLCName() { var spAdressEnd = SpecialAddress.ToString("X1"); @@ -159,13 +124,8 @@ namespace MewtocolNet.Registers { } - internal void TriggerChangedEvnt(object changed) => ValueChanged?.Invoke(changed); - - public void TriggerNotifyChange() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value")); - - public override string ToString() => $"{GetRegisterPLCName()} - Value: {GetValueString()}"; - - public string ToString(bool additional) { + /// + public override string ToString(bool additional) { if (!additional) return this.ToString(); @@ -181,19 +141,6 @@ namespace MewtocolNet.Registers { } - public async Task ReadAsync (MewtocolInterface interf) { - - var read = await interf.ReadRawRegisterAsync(this); - return PlcValueParser.Parse(read); - - } - - public async Task WriteAsync (MewtocolInterface interf, object data) { - - return await interf.WriteRawRegisterAsync(this, PlcValueParser.Encode((bool)data)); - - } - } } diff --git a/MewtocolNet/Registers/BoolRegisterResult.cs b/MewtocolNet/Registers/BoolRegisterResult.cs deleted file mode 100644 index 4016a48..0000000 --- a/MewtocolNet/Registers/BoolRegisterResult.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace MewtocolNet.Registers { - - /// - /// Result for a boolean register - /// - public class BoolRegisterResult { - - /// - /// The command result - /// - public CommandResult Result { get; set; } - - /// - /// The used register - /// - public BoolRegister Register { get; set; } - - } - -} diff --git a/MewtocolNet/Registers/BytesRegister.cs b/MewtocolNet/Registers/BytesRegister.cs index 8ac6240..d247800 100644 --- a/MewtocolNet/Registers/BytesRegister.cs +++ b/MewtocolNet/Registers/BytesRegister.cs @@ -9,150 +9,80 @@ namespace MewtocolNet.Registers { /// /// Defines a register containing a string /// - public class BytesRegister : IRegister, IRegisterInternal { + public class BytesRegister : BaseRegister { + internal int addressLength; /// - /// Gets called whenever the value was changed + /// The rgisters memory length /// - public event Action ValueChanged; - - /// - /// Triggers when a property on the register changes - /// - public event PropertyChangedEventHandler PropertyChanged; - - public RegisterType RegisterType { get; private set; } - - internal Type collectionType; - - /// - /// The type of collection the register is in or null of added manually - /// - public Type CollectionType => collectionType; - - internal string lastValue; - - /// - /// The value of the register - /// - public object Value => lastValue; - - internal string name; - /// - /// The register name or null of not defined - /// - public string Name => name; - - internal int memoryAdress; - /// - /// The registers memory adress if not a special register - /// - public int MemoryAddress => memoryAdress; - - internal int memoryLength; - /// - /// The registers memory length - /// - public int MemoryLength => memoryLength; + public int AddressLength => addressLength; internal short ReservedSize { get; set; } /// /// Defines a register containing a string /// - public BytesRegister(int _adress, int _reservedSize, string _name = null) { + public BytesRegister(int _address, int _reservedByteSize, string _name = null) { - if (_adress > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999"); + if (_address > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999"); name = _name; - memoryAdress = _adress; - ReservedSize = (short)_reservedSize; + memoryAddress = _address; + ReservedSize = (short)_reservedByteSize; - //calc mem length - var wordsize = (double)_reservedSize / 2; - if (wordsize % 2 != 0) { - wordsize++; - } + //calc mem length + //because one register is always 1 word (2 bytes) long, if the bytecount is uneven we get the trailing word too + var byteSize = _reservedByteSize; + if (_reservedByteSize % 2 != 0) byteSize++; - RegisterType = RegisterType.DT_RANGE; + RegisterType = RegisterType.DT_BYTE_RANGE; + addressLength = (byteSize / 2) - 1; - memoryLength = (int)Math.Round(wordsize + 1); } - public void WithCollectionType(Type colType) => collectionType = colType; + public override string GetValueString() => Value == null ? "null" : ((byte[])Value).ToHexString("-"); - /// - /// Builds the register identifier for the mewotocol protocol - /// - public string BuildMewtocolQuery() { + /// + public override string BuildMewtocolQuery() { StringBuilder asciistring = new StringBuilder("D"); asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0')); - asciistring.Append((MemoryAddress + MemoryLength).ToString().PadLeft(5, '0')); + asciistring.Append((MemoryAddress + AddressLength).ToString().PadLeft(5, '0')); return asciistring.ToString(); } - internal string BuildCustomIdent(int overwriteWordLength) { + /// + public override void SetValueFromPLC (object val) { - if (overwriteWordLength <= 0) - throw new Exception("overwriteWordLength cant be 0 or less"); - - StringBuilder asciistring = new StringBuilder("D"); - - asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0')); - asciistring.Append((MemoryAddress + overwriteWordLength - 1).ToString().PadLeft(5, '0')); - - return asciistring.ToString(); - } - - public byte? GetSpecialAddress() => null; - - public Type GetCollectionType() => CollectionType; - - public bool IsUsedBitwise() => false; - - public void SetValueFromPLC(object val) { - - lastValue = (string)val; + lastValue = (byte[])val; TriggerChangedEvnt(this); TriggerNotifyChange(); } - public string GetStartingMemoryArea() => MemoryAddress.ToString(); + /// + public override string GetRegisterString() => "DT"; - public string GetValueString() => Value?.ToString() ?? ""; + /// + public override void ClearValue() => SetValueFromPLC(null); - public string GetRegisterString() => "DT"; + /// + public override async Task ReadAsync() { - public string GetCombinedName() => $"{(CollectionType != null ? $"{CollectionType.Name}." : "")}{Name ?? "Unnamed"}"; + var read = await attachedInterface.ReadRawRegisterAsync(this); + var parsed = PlcValueParser.Parse(read); - public string GetContainerName() => $"{(CollectionType != null ? $"{CollectionType.Name}" : "")}"; - - public string GetRegisterPLCName() => $"{GetRegisterString()}{MemoryAddress}"; - - public void ClearValue() => SetValueFromPLC(null); - - internal void TriggerChangedEvnt(object changed) => ValueChanged?.Invoke(changed); - - public void TriggerNotifyChange() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value")); - - public override string ToString() => $"{GetRegisterPLCName()} - Value: {GetValueString()}"; - - public string ToString(bool additional) => $"{GetRegisterPLCName()} - Value: {GetValueString()}"; - - public async Task ReadAsync(MewtocolInterface interf) { - - var read = await interf.ReadRawRegisterAsync(this); - return PlcValueParser.Parse(read); + SetValueFromPLC(parsed); + return parsed; } - public async Task WriteAsync(MewtocolInterface interf, object data) { + /// + public override async Task WriteAsync(object data) { - return await interf.WriteRawRegisterAsync(this, (byte[])data); + return await attachedInterface.WriteRawRegisterAsync(this, (byte[])data); } diff --git a/MewtocolNet/Registers/BytesRegisterResult.cs b/MewtocolNet/Registers/BytesRegisterResult.cs deleted file mode 100644 index 314decf..0000000 --- a/MewtocolNet/Registers/BytesRegisterResult.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace MewtocolNet.Registers { - - /// - /// The results of a string register operation - /// - public class BytesRegisterResult { - - /// - /// The command result - /// - public CommandResult Result { get; set; } - /// - /// The register definition used - /// - public BytesRegister Register { get; set; } - - } - -} diff --git a/MewtocolNet/Registers/NumberRegister.cs b/MewtocolNet/Registers/NumberRegister.cs index b4f09c6..0b16135 100644 --- a/MewtocolNet/Registers/NumberRegister.cs +++ b/MewtocolNet/Registers/NumberRegister.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.ComponentModel; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; @@ -11,131 +12,64 @@ namespace MewtocolNet.Registers { /// Defines a register containing a number /// /// The type of the numeric value - public class NumberRegister : IRegister, IRegisterInternal { - - /// - /// Gets called whenever the value was changed - /// - public event Action ValueChanged; - - /// - /// Triggers when a property on the register changes - /// - public event PropertyChangedEventHandler PropertyChanged; - - public RegisterType RegisterType { get; private set; } - - internal Type collectionType; - - /// - /// The type of collection the register is in or null of added manually - /// - public Type CollectionType => collectionType; - - internal T lastValue; - - /// - /// The value of the register - /// - public object Value => lastValue; - - internal string name; - /// - /// The register name or null of not defined - /// - public string Name => name; - - internal int memoryAdress; - /// - /// The registers memory adress if not a special register - /// - public int MemoryAddress => memoryAdress; - - internal int memoryLength; - /// - /// The rgisters memory length - /// - public int MemoryLength => memoryLength; - - internal bool isUsedBitwise { get; set; } + public class NumberRegister : BaseRegister { internal Type enumType { get; set; } /// /// Defines a register containing a number /// - /// Memory start adress max 99999 + /// Memory start adress max 99999 /// Name of the register - public NumberRegister (int _adress, string _name = null) { + public NumberRegister (int _address, string _name = null) { - if (_adress > 99999) - throw new NotSupportedException("Memory adresses cant be greater than 99999"); + if (_address > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999"); - memoryAdress = _adress; + memoryAddress = _address; 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 if (numType == typeof(TimeSpan)) { - memoryLength = 1; - } else { - throw new NotSupportedException($"The type {numType} is not allowed for Number Registers"); - } - //set register type - if(memoryLength == 1) { - RegisterType = RegisterType.DDT; - } else { - RegisterType = RegisterType.DT; - } + Type numType = typeof(T); + + var allowedTypes = PlcValueParser.GetAllowDotnetTypes(); + if (!allowedTypes.Contains(numType)) + throw new NotSupportedException($"The type {numType} is not allowed for Number Registers"); + + var areaLen = (Marshal.SizeOf(numType) / 2) - 1; + RegisterType = areaLen >= 1 ? RegisterType.DDT : RegisterType.DT; + + lastValue = default(T); } - public NumberRegister (int _adress, string _name = null, bool isBitwise = false, Type _enumType = null) { + /// + /// Defines a register containing a number + /// + /// Memory start adress max 99999 + /// Enum type to parse as + /// Name of the register + public NumberRegister(int _address, Type _enumType, string _name = null) { - if (_adress > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999"); - memoryAdress = _adress; + if (_address > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999"); + + memoryAddress = _address; 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 if (numType == typeof(TimeSpan)) { - memoryLength = 1; - } else { + + var allowedTypes = PlcValueParser.GetAllowDotnetTypes(); + if (!allowedTypes.Contains(numType)) throw new NotSupportedException($"The type {numType} is not allowed for Number Registers"); - } - //set register type - if (memoryLength == 1) { - RegisterType = RegisterType.DDT; - } else { - RegisterType = RegisterType.DT; - } + var areaLen = (Marshal.SizeOf(numType) / 2) - 1; + RegisterType = areaLen >= 1 ? RegisterType.DDT : RegisterType.DT; - isUsedBitwise = isBitwise; enumType = _enumType; + lastValue = default(T); } - public void WithCollectionType(Type colType) => collectionType = colType; - - public void SetValueFromPLC(object val) { + /// + public override void SetValueFromPLC(object val) { lastValue = (T)val; TriggerChangedEvnt(this); @@ -143,20 +77,34 @@ namespace MewtocolNet.Registers { } - public byte? GetSpecialAddress() => null; + /// + public override string BuildMewtocolQuery() { - public string GetStartingMemoryArea() => MemoryAddress.ToString(); + StringBuilder asciistring = new StringBuilder("D"); + asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0')); - public Type GetCollectionType() => CollectionType; + int offsetAddress = 0; + if(RegisterType == RegisterType.DDT) + offsetAddress = 1; - public bool IsUsedBitwise() => isUsedBitwise; + asciistring.Append((MemoryAddress + offsetAddress).ToString().PadLeft(5, '0')); + return asciistring.ToString(); - public string GetValueString() { + } + + /// + public override string GetValueString() { + + if(typeof(T) == typeof(TimeSpan)) { + + return $"{Value} [{((TimeSpan)Value).AsPLC()}]"; + + } //is number or bitwise if (enumType == null) { - return $"{Value}{(isUsedBitwise ? $" [{GetBitwise().ToBitString()}]" : "")}"; + return $"{Value}"; } @@ -204,6 +152,27 @@ namespace MewtocolNet.Registers { } + /// + public override void ClearValue() => SetValueFromPLC(default(T)); + + /// + public override async Task ReadAsync() { + + var read = await attachedInterface.ReadRawRegisterAsync(this); + var parsed = PlcValueParser.Parse(read); + + SetValueFromPLC(parsed); + return parsed; + + } + + /// + public override async Task WriteAsync(object data) { + + return await attachedInterface.WriteRawRegisterAsync(this, PlcValueParser.Encode((T)data)); + + } + /// /// Gets the register bitwise if its a 16 or 32 bit int /// @@ -230,76 +199,6 @@ namespace MewtocolNet.Registers { } - public string BuildMewtocolQuery() { - - StringBuilder asciistring = new StringBuilder("D"); - asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0')); - asciistring.Append((MemoryAddress + MemoryLength).ToString().PadLeft(5, '0')); - return asciistring.ToString(); - - } - - public string GetRegisterString() { - - if (Value is short) { - return "DT"; - } - - if (Value is ushort) { - return "DT"; - } - - if (Value is int) { - return "DDT"; - } - - if (Value is uint) { - return "DDT"; - } - - if (Value is float) { - return "DDT"; - } - - if (Value is TimeSpan) { - return "DDT"; - } - - throw new NotSupportedException("Numeric type is not supported"); - - } - - public string GetCombinedName() => $"{(CollectionType != null ? $"{CollectionType.Name}." : "")}{Name ?? "Unnamed"}"; - - public string GetContainerName() => $"{(CollectionType != null ? $"{CollectionType.Name}" : "")}"; - - public string GetRegisterPLCName() => $"{GetRegisterString()}{MemoryAddress}"; - - public void ClearValue() => SetValueFromPLC(default(T)); - - internal void TriggerChangedEvnt(object changed) => ValueChanged?.Invoke(changed); - - public void TriggerNotifyChange() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value")); - - public RegisterType GetRegisterType() => RegisterType; - - public override string ToString() => $"{GetRegisterPLCName()} - Value: {GetValueString()}"; - - public string ToString(bool additional) => $"{GetRegisterPLCName()} - Value: {GetValueString()}"; - - public async Task ReadAsync(MewtocolInterface interf) { - - var read = await interf.ReadRawRegisterAsync(this); - return PlcValueParser.Parse(read); - - } - - public async Task WriteAsync(MewtocolInterface interf, object data) { - - return await interf.WriteRawRegisterAsync(this, PlcValueParser.Encode((T)data)); - - } - } } diff --git a/MewtocolNet/Registers/NumberRegisterResult.cs b/MewtocolNet/Registers/NumberRegisterResult.cs deleted file mode 100644 index 04bdec3..0000000 --- a/MewtocolNet/Registers/NumberRegisterResult.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace MewtocolNet.Registers { - - /// - /// Result for a read/write operation - /// - /// The type of the numeric value - public class NumberRegisterResult { - - /// - /// Command result - /// - public CommandResult Result { get; set; } - - /// - /// The used register - /// - public NumberRegister Register { get; set; } - - /// - /// Trys to get the value of there is one - /// - public bool TryGetValue(out T value) { - - if (Result.Success) { - value = (T)Register.Value; - return true; - } - value = default; - return false; - - } - - } - -} diff --git a/MewtocolNet/Registers/StringRegister.cs b/MewtocolNet/Registers/StringRegister.cs new file mode 100644 index 0000000..14248a3 --- /dev/null +++ b/MewtocolNet/Registers/StringRegister.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; +using System.Threading.Tasks; + +namespace MewtocolNet.Registers { + + /// + /// Defines a register containing a string + /// + public class StringRegister : BaseRegister { + + internal int addressLength; + /// + /// The rgisters memory length + /// + public int AddressLength => addressLength; + + internal short ReservedSize { get; set; } + + /// + /// Defines a register containing a string + /// + public StringRegister (int _adress, int _reservedByteSize, string _name = null) { + + if (_adress > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999"); + name = _name; + memoryAddress = _adress; + ReservedSize = (short)_reservedByteSize; + + //calc mem length + var wordsize = (double)_reservedByteSize / 2; + if (wordsize % 2 != 0) { + wordsize++; + } + + RegisterType = RegisterType.DT_BYTE_RANGE; + addressLength = (int)Math.Round(wordsize + 1); + + } + + /// + public override string BuildMewtocolQuery() { + + StringBuilder asciistring = new StringBuilder("D"); + + asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0')); + asciistring.Append((MemoryAddress + AddressLength).ToString().PadLeft(5, '0')); + + return asciistring.ToString(); + } + + /// + public override void SetValueFromPLC (object val) { + + lastValue = (byte[])val; + + TriggerChangedEvnt(this); + TriggerNotifyChange(); + + } + + /// + public override string GetRegisterString() => "DT"; + + /// + public override void ClearValue() => SetValueFromPLC(null); + + /// + public override async Task ReadAsync() { + + var read = await attachedInterface.ReadRawRegisterAsync(this); + return PlcValueParser.Parse(read); + + } + + /// + public override async Task WriteAsync(object data) { + + return await attachedInterface.WriteRawRegisterAsync(this, (byte[])data); + + } + + } + +} diff --git a/MewtocolNet/TCPMessageResult.cs b/MewtocolNet/TCPMessageResult.cs new file mode 100644 index 0000000..d3e23eb --- /dev/null +++ b/MewtocolNet/TCPMessageResult.cs @@ -0,0 +1,12 @@ +namespace MewtocolNet { + internal enum TCPMessageResult { + + Waiting, + Success, + NotConnected, + FailedWithException, + FailedLineFeed, + + } + +} \ No newline at end of file diff --git a/MewtocolNet/TypeConversion/Conversions.cs b/MewtocolNet/TypeConversion/Conversions.cs index db72369..41543e8 100644 --- a/MewtocolNet/TypeConversion/Conversions.cs +++ b/MewtocolNet/TypeConversion/Conversions.cs @@ -1,5 +1,6 @@ using MewtocolNet.Registers; using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Text; @@ -19,10 +20,13 @@ namespace MewtocolNet.TypeConversion { { PlcVarType.TIME, RegisterType.DDT }, { PlcVarType.WORD, RegisterType.DT }, { PlcVarType.DWORD, RegisterType.DDT }, - { PlcVarType.STRING, RegisterType.DT_RANGE }, + { PlcVarType.STRING, RegisterType.DT_BYTE_RANGE }, }; + /// + /// All conversions for reading dataf from and to the plc + /// internal static List items = new List { new PlcTypeConversion(RegisterType.R) { @@ -116,6 +120,64 @@ namespace MewtocolNet.TypeConversion { }, }, + new PlcTypeConversion(RegisterType.DDT) { + HoldingRegisterType = typeof(NumberRegister), + PlcVarType = PlcVarType.REAL, + FromRaw = bytes => { + + var val = BitConverter.ToUInt32(bytes, 0); + byte[] floatVals = BitConverter.GetBytes(val); + float finalFloat = BitConverter.ToSingle(floatVals, 0); + + return finalFloat; + + }, + ToRaw = value => { + + return BitConverter.GetBytes(value); + + }, + }, + new PlcTypeConversion(RegisterType.DDT) { + HoldingRegisterType = typeof(NumberRegister), + PlcVarType = PlcVarType.TIME, + FromRaw = bytes => { + + var vallong = BitConverter.ToUInt32(bytes, 0); + var valMillis = vallong * 10; + var ts = TimeSpan.FromMilliseconds(valMillis); + return ts; + + }, + ToRaw = value => { + + var tLong = (uint)(value.TotalMilliseconds / 10); + return BitConverter.GetBytes(tLong); + + }, + }, + new PlcTypeConversion(RegisterType.DT) { + HoldingRegisterType = typeof(BytesRegister), + FromRaw = bytes => bytes, + ToRaw = value => value, + }, + new PlcTypeConversion(RegisterType.DT) { + HoldingRegisterType = typeof(BytesRegister), + PlcVarType = PlcVarType.WORD, + FromRaw = bytes => { + + BitArray bitAr = new BitArray(bytes); + return bitAr; + + }, + ToRaw = value => { + + byte[] ret = new byte[(value.Length - 1) / 8 + 1]; + value.CopyTo(ret, 0); + return ret; + + }, + }, }; diff --git a/MewtocolNet/TypeConversion/PlcValueParser.cs b/MewtocolNet/TypeConversion/PlcValueParser.cs index 05a34a1..c6c5497 100644 --- a/MewtocolNet/TypeConversion/PlcValueParser.cs +++ b/MewtocolNet/TypeConversion/PlcValueParser.cs @@ -41,9 +41,12 @@ namespace MewtocolNet { public static RegisterType? GetDefaultRegisterType (Type type) => conversions.FirstOrDefault(x => x.GetDotnetType() == type)?.GetPlcRegisterType(); - public static Type GetDefaultPlcVarType (this PlcVarType type) => + public static Type GetDefaultRegisterHoldingType (this PlcVarType type) => conversions.FirstOrDefault(x => x.GetPlcVarType() == type)?.GetHoldingRegisterType(); + public static Type GetDefaultRegisterHoldingType (this Type type) => + conversions.FirstOrDefault(x => x.GetDotnetType() == type)?.GetHoldingRegisterType(); + public static Type GetDefaultDotnetType (this PlcVarType type) => conversions.FirstOrDefault(x => x.GetPlcVarType() == type)?.GetDotnetType(); diff --git a/MewtocolTests/AutomatedPropertyRegisters.cs b/MewtocolTests/AutomatedPropertyRegisters.cs index d0e4b1c..df6830a 100644 --- a/MewtocolTests/AutomatedPropertyRegisters.cs +++ b/MewtocolTests/AutomatedPropertyRegisters.cs @@ -91,7 +91,7 @@ namespace MewtocolTests { } - private void TestBasicGeneration(IRegister reg, string propName, object expectValue, int expectAddr, string expectPlcName) { + private void TestBasicGeneration(IRegisterInternal reg, string propName, object expectValue, int expectAddr, string expectPlcName) { Assert.NotNull(reg); Assert.Equal(propName, reg.Name); @@ -115,7 +115,7 @@ namespace MewtocolTests { var register = interf.GetRegister(nameof(TestRegisterCollection.TestBool1)); //test generic properties - TestBasicGeneration(register, nameof(TestRegisterCollection.TestBool1), false, 85, "R85"); + TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestBool1), false, 85, "R85"); } @@ -128,7 +128,7 @@ namespace MewtocolTests { var register = interf.GetRegister(nameof(TestRegisterCollection.TestBoolInputXD)); //test generic properties - TestBasicGeneration(register, nameof(TestRegisterCollection.TestBoolInputXD), false, 0, "XD"); + TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestBoolInputXD), false, 0, "XD"); } @@ -141,7 +141,7 @@ namespace MewtocolTests { var register = interf.GetRegister(nameof(TestRegisterCollection.TestInt16)); //test generic properties - TestBasicGeneration(register, nameof(TestRegisterCollection.TestInt16), (short)0, 899, "DT899"); + TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestInt16), (short)0, 899, "DT899"); } @@ -154,7 +154,7 @@ namespace MewtocolTests { var register = interf.GetRegister(nameof(TestRegisterCollection.TestUInt16)); //test generic properties - TestBasicGeneration(register, nameof(TestRegisterCollection.TestUInt16), (ushort)0, 342, "DT342"); + TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestUInt16), (ushort)0, 342, "DT342"); } @@ -167,7 +167,7 @@ namespace MewtocolTests { var register = interf.GetRegister(nameof(TestRegisterCollection.TestInt32)); //test generic properties - TestBasicGeneration(register, nameof(TestRegisterCollection.TestInt32), (int)0, 7001, "DDT7001"); + TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestInt32), (int)0, 7001, "DDT7001"); } @@ -180,7 +180,7 @@ namespace MewtocolTests { var register = interf.GetRegister(nameof(TestRegisterCollection.TestUInt32)); //test generic properties - TestBasicGeneration(register, nameof(TestRegisterCollection.TestUInt32), (uint)0, 765, "DDT765"); + TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestUInt32), (uint)0, 765, "DDT765"); } @@ -193,70 +193,7 @@ namespace MewtocolTests { var register = interf.GetRegister(nameof(TestRegisterCollection.TestFloat32)); //test generic properties - TestBasicGeneration(register, nameof(TestRegisterCollection.TestFloat32), (float)0, 7003, "DDT7003"); - - } - - [Fact(DisplayName = "String generation")] - public void StringGen() { - - var interf = new MewtocolInterface("192.168.0.1"); - interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller(); - - var register = interf.GetRegister(nameof(TestRegisterCollection.TestString2)); - - //test generic properties - TestBasicGeneration(register, nameof(TestRegisterCollection.TestString2), null!, 7005, "DT7005"); - - Assert.Equal(5, ((BytesRegister)register).ReservedSize); - Assert.Equal(4, ((BytesRegister)register).MemoryLength); - - } - - [Fact(DisplayName = "BitArray 16bit generation")] - public void BitArray16Gen() { - - var interf = new MewtocolInterface("192.168.0.1"); - interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller(); - - var register = interf.GetRegister($"Auto_Bitwise_DT7010"); - - //test generic properties - TestBasicGeneration(register, "Auto_Bitwise_DT7010", (short)0, 7010, "DT7010"); - - Assert.True(((NumberRegister)register).isUsedBitwise); - Assert.Equal(0, ((NumberRegister)register).MemoryLength); - - } - - [Fact(DisplayName = "BitArray 32bit generation")] - public void BitArray32Gen() { - - var interf = new MewtocolInterface("192.168.0.1"); - interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller(); - - var register = interf.GetRegister($"Auto_Bitwise_DDT8010"); - - //test generic properties - TestBasicGeneration(register, "Auto_Bitwise_DDT8010", (int)0, 8010, "DDT8010"); - - Assert.True(((NumberRegister)register).isUsedBitwise); - Assert.Equal(1, ((NumberRegister)register).MemoryLength); - - } - - [Fact(DisplayName = "BitArray single bool generation")] - public void BitArraySingleBool16Gen() { - - var interf = new MewtocolInterface("192.168.0.1"); - interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller(); - - var register = interf.GetRegister($"Auto_Bitwise_DT1204"); - - //test generic properties - TestBasicGeneration(register, "Auto_Bitwise_DT1204", (short)0, 1204, "DT1204"); - - Assert.True(((NumberRegister)register).isUsedBitwise); + TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestFloat32), (float)0, 7003, "DDT7003"); } @@ -269,35 +206,25 @@ namespace MewtocolTests { var register = interf.GetRegister(nameof(TestRegisterCollection.TestTime)); //test generic properties - TestBasicGeneration(register, nameof(TestRegisterCollection.TestTime), TimeSpan.Zero, 7012, "DDT7012"); + TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestTime), TimeSpan.Zero, 7012, "DDT7012"); } - [Fact(DisplayName = "Enum16 generation")] - public void Enum16Gen() { + //[Fact(DisplayName = "String generation")] + //public void StringGen() { - var interf = new MewtocolInterface("192.168.0.1"); - interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller(); + // var interf = new MewtocolInterface("192.168.0.1"); + // interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller(); - var register = interf.GetRegister(nameof(TestRegisterCollection.TestEnum16)); + // var register = interf.GetRegister(nameof(TestRegisterCollection.TestString2)); - //test generic properties - TestBasicGeneration(register, nameof(TestRegisterCollection.TestEnum16), (short)TestRegisterCollection.CurrentState.Undefined, 50, "DT50"); + // //test generic properties + // TestBasicGeneration(register, nameof(TestRegisterCollection.TestString2), null!, 7005, "DT7005"); - } + // Assert.Equal(5, ((BytesRegister)register).ReservedSize); + // Assert.Equal(4, ((BytesRegister)register).MemoryLength); - [Fact(DisplayName = "Enum32 generation")] - public void Enum32Gen() { - - var interf = new MewtocolInterface("192.168.0.1"); - interf.WithRegisterCollection(new TestRegisterCollection()).WithPoller(); - - var register = interf.GetRegister(nameof(TestRegisterCollection.TestEnum32)); - - //test generic properties - TestBasicGeneration(register, nameof(TestRegisterCollection.TestEnum32), (int)TestRegisterCollection.CurrentState.Undefined, 51, "DDT51"); - - } + //} } diff --git a/MewtocolTests/EncapsulatedTests/ExpectedPlcInformationData.cs b/MewtocolTests/EncapsulatedTests/ExpectedPlcInformationData.cs new file mode 100644 index 0000000..2a97059 --- /dev/null +++ b/MewtocolTests/EncapsulatedTests/ExpectedPlcInformationData.cs @@ -0,0 +1,17 @@ +using MewtocolNet; + +namespace MewtocolTests.EncapsulatedTests; + +public class ExpectedPlcInformationData { + + public string PLCName { get; set; } + + public string PLCIP { get; set; } + + public int PLCPort { get; set; } + + public CpuType Type { get; set; } + + public int ProgCapacity { get; set; } + +} diff --git a/MewtocolTests/EncapsulatedTests/RegisterReadWriteTest.cs b/MewtocolTests/EncapsulatedTests/RegisterReadWriteTest.cs new file mode 100644 index 0000000..529cc84 --- /dev/null +++ b/MewtocolTests/EncapsulatedTests/RegisterReadWriteTest.cs @@ -0,0 +1,20 @@ +using MewtocolNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MewtocolTests.EncapsulatedTests; + +internal class RegisterReadWriteTest { + + public IRegister TargetRegister { get; set; } + + public object IntialValue { get; set; } + + public object AfterWriteValue { get; set; } + + public string RegisterPlcAddressName { get; set; } + +} diff --git a/MewtocolTests/TestHelperExtensions.cs b/MewtocolTests/TestHelperExtensions.cs index 26ab943..7c58817 100644 --- a/MewtocolTests/TestHelperExtensions.cs +++ b/MewtocolTests/TestHelperExtensions.cs @@ -43,7 +43,7 @@ namespace MewtocolTests { } - [Fact(DisplayName = nameof(MewtocolHelpers.ToHexASCIIBytes))] + [Fact(DisplayName = nameof(MewtocolHelpers.BytesFromHexASCIIString))] public void ToHexASCIIBytesGeneration() { string test = "Hello, world!"; @@ -62,7 +62,7 @@ namespace MewtocolTests { 0x4C, 0x44, 0x21 - }, test.ToHexASCIIBytes()); + }, test.BytesFromHexASCIIString()); } diff --git a/MewtocolTests/TestLivePLC.cs b/MewtocolTests/TestLivePLC.cs index 420e5d4..04cb782 100644 --- a/MewtocolTests/TestLivePLC.cs +++ b/MewtocolTests/TestLivePLC.cs @@ -1,17 +1,21 @@ using MewtocolNet; using MewtocolNet.Logging; +using MewtocolNet.RegisterBuilding; +using MewtocolNet.Registers; +using MewtocolTests.EncapsulatedTests; using Xunit; using Xunit.Abstractions; -namespace MewtocolTests { +namespace MewtocolTests +{ public class TestLivePLC { private readonly ITestOutputHelper output; - private List testData = new() { + private List testPlcInformationData = new() { - new ExpectedTestData { + new ExpectedPlcInformationData { PLCName = "FPX-H C30T", PLCIP = "192.168.115.210", @@ -20,7 +24,7 @@ namespace MewtocolTests { ProgCapacity = 32, }, - new ExpectedTestData { + new ExpectedPlcInformationData { PLCName = "FPX-H C14R", PLCIP = "192.168.115.212", @@ -32,12 +36,29 @@ namespace MewtocolTests { }; + private List testRegisterRW = new() { + + new RegisterReadWriteTest { + TargetRegister = new BoolRegister(IOType.R, 0xA, 10), + RegisterPlcAddressName = "R10A", + IntialValue = false, + AfterWriteValue = true, + }, + new RegisterReadWriteTest { + TargetRegister = new NumberRegister(3000), + RegisterPlcAddressName = "DT3000", + IntialValue = (int)0, + AfterWriteValue = (int)-513, + }, + + }; + public TestLivePLC(ITestOutputHelper output) { this.output = output; Logger.LogLevel = LogLevel.Verbose; - Logger.OnNewLogMessage((d, m) => { + Logger.OnNewLogMessage((d, l, m) => { output.WriteLine($"Mewtocol Logger: {d} {m}"); @@ -48,7 +69,7 @@ namespace MewtocolTests { [Fact(DisplayName = "Connection cycle client to PLC")] public async void TestClientConnection() { - foreach (var plc in testData) { + foreach (var plc in testPlcInformationData) { output.WriteLine($"Testing: {plc.PLCName}"); @@ -69,7 +90,7 @@ namespace MewtocolTests { [Fact(DisplayName = "Reading basic information from PLC")] public async void TestClientReadPLCStatus() { - foreach (var plc in testData) { + foreach (var plc in testPlcInformationData) { output.WriteLine($"Testing: {plc.PLCName}\n"); @@ -90,19 +111,38 @@ namespace MewtocolTests { } - } + //[Fact(DisplayName = "Reading basic information from PLC")] + //public async void TestRegisterReadWriteAsync () { - public class ExpectedTestData { + // foreach (var plc in testPlcInformationData) { - public string PLCName { get; set; } + // output.WriteLine($"Testing: {plc.PLCName}\n"); - public string PLCIP { get; set; } + // var client = new MewtocolInterface(plc.PLCIP, plc.PLCPort); - public int PLCPort { get; set; } + // foreach (var testRW in testRegisterRW) { - public CpuType Type { get; set; } + // client.AddRegister(testRW.TargetRegister); - public int ProgCapacity { get; set; } + // } + + // await client.ConnectAsync(); + // Assert.True(client.IsConnected); + + // foreach (var testRW in testRegisterRW) { + + // client.AddRegister(testRW.TargetRegister); + + // } + + // Assert.Equal(client.PlcInfo.CpuInformation.Cputype, plc.Type); + // Assert.Equal(client.PlcInfo.CpuInformation.ProgramCapacity, plc.ProgCapacity); + + // client.Disconnect(); + + // } + + //} } diff --git a/MewtocolTests/TestRegisterBuilder.cs b/MewtocolTests/TestRegisterBuilder.cs index 24a63f3..4be5918 100644 --- a/MewtocolTests/TestRegisterBuilder.cs +++ b/MewtocolTests/TestRegisterBuilder.cs @@ -122,20 +122,12 @@ public class TestRegisterBuilder { foreach (var item in dict) { - try { + output.WriteLine($"Expected: {item.Key}"); - output.WriteLine($"Expected: {item.Key}"); + var built = RegBuilder.Factory.FromPlcRegName(item.Key).AsPlcType(PlcVarType.BOOL).Build(); - var built = RegBuilder.Factory.FromPlcRegName(item.Key).AsPlcType(PlcVarType.BOOL).Build(); - - output.WriteLine($"{(built?.ToString(true) ?? "null")}\n"); - Assert.Equivalent(item.Value, built); - - } catch (Exception ex) { - - output.WriteLine(ex.Message.ToString()); - - } + output.WriteLine($"{(built?.ToString(true) ?? "null")}\n"); + Assert.Equivalent(item.Value, built); } @@ -204,11 +196,9 @@ public class TestRegisterBuilder { [Fact(DisplayName = "Parsing as Bytes Register (Casted)")] public void TestRegisterBuildingByteRangeCasted() { - var expect = new BytesRegister(303, 5); + var expect = new BytesRegister(303, 5); - - Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").AsPlcType(PlcVarType.INT).Build()); - Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").AsType().Build()); + Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").AsBytes(5).Build()); } diff --git a/MewtocolTests/TestRegisterInterface.cs b/MewtocolTests/TestRegisterInterface.cs index 8a11726..1b0e3ab 100644 --- a/MewtocolTests/TestRegisterInterface.cs +++ b/MewtocolTests/TestRegisterInterface.cs @@ -16,13 +16,15 @@ namespace MewtocolTests { [Fact(DisplayName = "Numeric mewtocol query building")] public void NumericRegisterMewtocolIdentifiers() { - List registers = new List { - new NumberRegister(50, _name: null), - new NumberRegister(50, _name: null), - new NumberRegister(50, _name : null), - new NumberRegister(50, _name : null), - new NumberRegister(50, _name : null), - new NumberRegister(50, _name : null), + List registers = new List { + new NumberRegister(50), + new NumberRegister(50), + new NumberRegister(50), + new NumberRegister(50), + new NumberRegister(50), + new NumberRegister(50), + new BytesRegister(50, 30), + new BytesRegister(50, 31), }; List expectedIdents = new List { @@ -32,12 +34,14 @@ namespace MewtocolTests { "D0005000051", //double word register "D0005000051", //double word register "D0005000051", //double word register + "D0005000065", //variable len register even bytes + "D0005000066", //variable len register odd bytes }; //test mewtocol idents for (int i = 0; i < registers.Count; i++) { - IRegister? reg = registers[i]; + IRegisterInternal? reg = registers[i]; string expect = expectedIdents[i]; Assert.Equal(expect, reg.BuildMewtocolQuery()); @@ -49,7 +53,7 @@ namespace MewtocolTests { [Fact(DisplayName = "PLC register naming convention test")] public void PLCRegisterIdentifiers() { - List registers = new List { + List registers = new List { //numeric ones new NumberRegister(50, _name: null), new NumberRegister(60, _name : null), @@ -67,7 +71,7 @@ namespace MewtocolTests { new BoolRegister(IOType.Y, 0xC, 75), //string - new BytesRegister(999, 5), + new BytesRegister(999, 5), }; List expcectedIdents = new List { @@ -96,7 +100,7 @@ namespace MewtocolTests { //test mewtocol idents for (int i = 0; i < registers.Count; i++) { - IRegister? reg = registers[i]; + IRegisterInternal? reg = registers[i]; string expect = expcectedIdents[i]; Assert.Equal(expect, reg.GetRegisterPLCName()); @@ -134,7 +138,7 @@ namespace MewtocolTests { var ex3 = Assert.Throws(() => { - new BytesRegister(100000, 5); + new BytesRegister(100000, 5); }); diff --git a/PLC_Test/test_c30_fpx_h.pro b/PLC_Test/test_c30_fpx_h.pro index 5b9447ff957c588719ffb9874c21de6929339927..564b2a649c500559ce1cd06107dd065b159329d3 100644 GIT binary patch delta 6799 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 NNlXhmcmy0R{6G9y-Xs73 delta 6422 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 diff --git a/PLC_Test/test_c30_fpx_h.xml b/PLC_Test/test_c30_fpx_h.xml index 9a30f61..52b5a75 100644 --- a/PLC_Test/test_c30_fpx_h.xml +++ b/PLC_Test/test_c30_fpx_h.xml @@ -2,17 +2,17 @@ - + - + - +