diff --git a/MewtocolNet/Exceptions/MewtocolException.cs b/MewtocolNet/Exceptions/MewtocolException.cs index 46c948e..445fd78 100644 --- a/MewtocolNet/Exceptions/MewtocolException.cs +++ b/MewtocolNet/Exceptions/MewtocolException.cs @@ -19,13 +19,20 @@ namespace MewtocolNet.Exceptions { internal static MewtocolException DupeRegister (IRegisterInternal register) { - return new MewtocolException($"The mewtocol interface already contains this register: {register.GetRegisterPLCName()}"); + return new MewtocolException($"The mewtocol interface already contains this register: {register.GetMewName()}"); } internal static MewtocolException DupeNameRegister (IRegisterInternal register) { - return new MewtocolException($"The mewtocol interface registers already contains a register with the name: {register.GetRegisterPLCName()}"); + return new MewtocolException($"The mewtocol interface registers already contains a register with the name: {register.GetMewName()}"); + + } + + internal static MewtocolException OverlappingRegister (IRegisterInternal registerA, IRegisterInternal registerB) { + + throw new MewtocolException($"The register: {registerA.GetRegisterWordRangeString()} " + + $"has overlapping addresses with: {registerB.GetRegisterWordRangeString()}"); } diff --git a/MewtocolNet/Helpers/MewtocolHelpers.cs b/MewtocolNet/Helpers/MewtocolHelpers.cs index 91fbcfb..6a144c9 100644 --- a/MewtocolNet/Helpers/MewtocolHelpers.cs +++ b/MewtocolNet/Helpers/MewtocolHelpers.cs @@ -14,50 +14,6 @@ namespace MewtocolNet { /// public static class MewtocolHelpers { - #region Value PLC Humanizers - - /// - /// Gets the TimeSpan as a PLC representation string fe. - /// - /// T#1h10m30s20ms - /// - /// - /// - /// - public static string AsPLCTime (this TimeSpan timespan) { - - if (timespan == null || timespan == TimeSpan.Zero) - return $"T#0s"; - - 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(); - - } - - /// - /// Turns a bit array into a 0 and 1 string - /// - public static string ToBitString(this BitArray arr) { - - var bits = new bool[arr.Length]; - arr.CopyTo(bits, 0); - return string.Join("", bits.Select(x => x ? "1" : "0")); - - } - - #endregion - #region Byte and string operation helpers /// diff --git a/MewtocolNet/Helpers/PlcFormat.cs b/MewtocolNet/Helpers/PlcFormat.cs new file mode 100644 index 0000000..e080859 --- /dev/null +++ b/MewtocolNet/Helpers/PlcFormat.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace MewtocolNet { + + public static class PlcFormat { + + /// + /// Gets the TimeSpan as a PLC representation string fe. + /// + /// T#1h10m30s20ms + /// + /// + /// + /// + public static string ToPlcTime (this TimeSpan timespan) { + + if (timespan == null || timespan == TimeSpan.Zero) + return $"T#0s"; + + StringBuilder sb = new StringBuilder("T#"); + + int millis = timespan.Milliseconds; + int seconds = timespan.Seconds; + int minutes = timespan.Minutes; + int hours = timespan.Hours; + int days = timespan.Days; + + if (days > 0) sb.Append($"{days}d"); + 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(); + + } + + public static TimeSpan ParsePlcTime (string plcTimeFormat) { + + var reg = new Regex(@"(?:T|t)#(?:(?[0-9]{1,2})d)?(?:(?[0-9]{1,2})h)?(?:(?[0-9]{1,2})m)?(?:(?[0-9]{1,2})s)?(?:(?[0-9]{1,3})ms)?"); + var match = reg.Match(plcTimeFormat); + + if(match.Success) { + + var days = match.Groups["d"].Value; + var hours = match.Groups["h"].Value; + var minutes = match.Groups["m"].Value; + var seconds = match.Groups["s"].Value; + var milliseconds = match.Groups["ms"].Value; + + TimeSpan retTime = TimeSpan.Zero; + + if (!string.IsNullOrEmpty(days)) retTime += TimeSpan.FromDays(int.Parse(days)); + if (!string.IsNullOrEmpty(hours)) retTime += TimeSpan.FromHours(int.Parse(hours)); + if (!string.IsNullOrEmpty(minutes)) retTime += TimeSpan.FromMinutes(int.Parse(minutes)); + if (!string.IsNullOrEmpty(seconds)) retTime += TimeSpan.FromSeconds(int.Parse(seconds)); + if (!string.IsNullOrEmpty(milliseconds)) retTime += TimeSpan.FromMilliseconds(int.Parse(milliseconds)); + + if ((retTime.TotalMilliseconds % 10) != 0) + throw new NotSupportedException("Plc times can't have a millisecond component lower than 10ms"); + + return retTime; + + } + + return TimeSpan.Zero; + + } + + /// + /// Turns a bit array into a 0 and 1 string + /// + public static string ToBitString(this BitArray arr) { + + var bits = new bool[arr.Length]; + arr.CopyTo(bits, 0); + return string.Join("", bits.Select(x => x ? "1" : "0")); + + } + + } + +} diff --git a/MewtocolNet/IPlc.cs b/MewtocolNet/IPlc.cs index 1386b93..b92a434 100644 --- a/MewtocolNet/IPlc.cs +++ b/MewtocolNet/IPlc.cs @@ -77,7 +77,14 @@ namespace MewtocolNet { /// Append the checksum and bcc automatically /// Timout to wait for a response /// Returns the result - Task SendCommandAsync(string _msg, bool withTerminator = true, int timeoutMs = -1); + Task SendCommandAsync(string _msg, bool withTerminator = true, int timeoutMs = -1, Action onReceiveProgress = null); + + /// + /// Changes the PLCs operation mode to the given one + /// + /// True for run mode, false for prog mode + /// The success state of the write operation + Task SetOperationModeAsync(bool setRun); /// /// Use this to await the first poll iteration after connecting, diff --git a/MewtocolNet/MewtocolInterface.cs b/MewtocolNet/MewtocolInterface.cs index 2c14b27..a1ac273 100644 --- a/MewtocolNet/MewtocolInterface.cs +++ b/MewtocolNet/MewtocolInterface.cs @@ -143,6 +143,7 @@ namespace MewtocolNet { private protected MewtocolInterface () { Connected += MewtocolInterface_Connected; + RegisterChanged += OnRegisterChanged; void MewtocolInterface_Connected(PLCInfo obj) { @@ -153,16 +154,15 @@ namespace MewtocolNet { } - RegisterChanged += (o) => { + } - var asInternal = (IRegisterInternal)o; + private void OnRegisterChanged(IRegister o) { - string address = $"{asInternal.GetRegisterString()}{asInternal.GetStartingMemoryArea()}".PadRight(5, (char)32); + var asInternal = (IRegisterInternal)o; - Logger.Log($"{address} " + - $"{(o.Name != null ? $"({o.Name}) " : "")}" + - $"changed to \"{asInternal.GetValueString()}\"", LogLevel.Change, this); - }; + Logger.Log($"{asInternal.GetMewName()} " + + $"{(o.Name != null ? $"({o.Name}) " : "")}" + + $"changed to \"{asInternal.GetValueString()}\"", LogLevel.Change, this); } @@ -195,15 +195,15 @@ namespace MewtocolNet { } /// - public virtual string GetConnectionInfo() => throw new NotImplementedException(); + public virtual string GetConnectionInfo () => throw new NotImplementedException(); /// - public async Task SendCommandAsync(string _msg, bool withTerminator = true, int timeoutMs = -1) { + public async Task SendCommandAsync (string _msg, bool withTerminator = true, int timeoutMs = -1, Action onReceiveProgress = null) { //send request queuedMessages++; - var tempResponse = queue.Enqueue(async () => await SendFrameAsync(_msg, withTerminator, withTerminator)); + var tempResponse = queue.Enqueue(async () => await SendFrameAsync(_msg, withTerminator, withTerminator, onReceiveProgress)); if (await Task.WhenAny(tempResponse, Task.Delay(timeoutMs)) != tempResponse) { // timeout logic @@ -217,7 +217,7 @@ namespace MewtocolNet { } - private protected async Task SendFrameAsync (string frame, bool useBcc = true, bool useCr = true) { + private protected async Task SendFrameAsync (string frame, bool useBcc = true, bool useCr = true, Action onReceiveProgress = null) { try { @@ -236,13 +236,27 @@ namespace MewtocolNet { byte[] writeBuffer = Encoding.UTF8.GetBytes(frame); stream.Write(writeBuffer, 0, writeBuffer.Length); + //calculate the expected number of frames from the message request + int? wordsCountRequested = null; + if(onReceiveProgress != null) { + + var match = Regex.Match(frame, @"RDD(?[0-9]{5})(?[0-9]{5})"); + + if (match.Success) { + var from = int.Parse(match.Groups["from"].Value); + var to = int.Parse(match.Groups["to"].Value); + wordsCountRequested = (to - from) + 1; + } + + } + //calc upstream speed CalcUpstreamSpeed(writeBuffer.Length); Logger.Log($"[---------CMD START--------]", LogLevel.Critical, this); Logger.Log($"--> OUT MSG: {frame.Replace("\r", "(CR)")}", LogLevel.Critical, this); - var readResult = await ReadCommandAsync(); + var readResult = await ReadCommandAsync(wordsCountRequested, onReceiveProgress); //did not receive bytes but no errors, the com port was not configured right if (readResult.Item1.Length == 0) { @@ -294,7 +308,7 @@ namespace MewtocolNet { } - private protected async Task<(byte[], bool)> ReadCommandAsync () { + private protected async Task<(byte[], bool)> ReadCommandAsync (int? wordsCountRequested = null, Action onReceiveProgress = null) { //read total List totalResponse = new List(); @@ -303,6 +317,7 @@ namespace MewtocolNet { try { bool needsRead = false; + int readFrames = 0; do { @@ -327,6 +342,15 @@ namespace MewtocolNet { if (commandRes == CommandState.RequestedNextFrame) { + //calc frame progress + if(onReceiveProgress != null && wordsCountRequested != null) { + + var frameBytesCount = tempMsg.Length - 6; + double prog = (double)frameBytesCount / wordsCountRequested.Value; + onReceiveProgress(prog); + + } + //request next frame var writeBuffer = Encoding.UTF8.GetBytes("%01**&\r"); await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length); @@ -334,6 +358,8 @@ namespace MewtocolNet { } + readFrames++; + } while (needsRead); } catch (OperationCanceledException) { } @@ -342,7 +368,7 @@ namespace MewtocolNet { } - private protected CommandState ParseBufferFrame(byte[] received) { + private protected CommandState ParseBufferFrame (byte[] received) { const char CR = '\r'; const char DELIMITER = '&'; @@ -405,8 +431,8 @@ namespace MewtocolNet { private protected virtual void OnConnected (PLCInfo plcinf) { - Logger.Log("Connected", LogLevel.Info, this); - Logger.Log($"\n\n{plcinf.ToString()}\n\n", LogLevel.Verbose, this); + Logger.Log("Connected to PLC", LogLevel.Info, this); + Logger.Log($"{plcinf.ToString()}", LogLevel.Verbose, this); IsConnected = true; @@ -440,17 +466,6 @@ namespace MewtocolNet { } - private protected void ClearRegisterVals() { - - for (int i = 0; i < RegistersUnderlying.Count; i++) { - - var reg = (IRegisterInternal)RegistersUnderlying[i]; - reg.ClearValue(); - - } - - } - private void SetUpstreamStopWatchStart () { if (speedStopwatchUpstr == null) { diff --git a/MewtocolNet/MewtocolInterfaceRegisterHandling.cs b/MewtocolNet/MewtocolInterfaceRegisterHandling.cs index 51bb07f..8c188bf 100644 --- a/MewtocolNet/MewtocolInterfaceRegisterHandling.cs +++ b/MewtocolNet/MewtocolInterfaceRegisterHandling.cs @@ -168,7 +168,6 @@ namespace MewtocolNet { if((bool)register.Value != resultBitArray[k]) { register.SetValueFromPLC(resultBitArray[k]); - InvokeRegisterChanged(register); } } @@ -192,7 +191,6 @@ namespace MewtocolNet { if (lastVal != readout) { rwReg.SetValueFromPLC(readout); - InvokeRegisterChanged(reg); } } @@ -218,16 +216,21 @@ namespace MewtocolNet { string propName = prop.Name; foreach (var attr in attributes) { - if (attr is RegisterAttribute cAttribute && prop.PropertyType.IsAllowedPlcCastingType()) { + if (attr is RegisterAttribute cAttribute) { + + if(!prop.PropertyType.IsAllowedPlcCastingType()) { + throw new MewtocolException($"The register attribute property type is not allowed ({prop.PropertyType})"); + } var dotnetType = prop.PropertyType; AddRegister(new RegisterBuildInfo { + mewAddress = cAttribute.MewAddress, memoryAddress = cAttribute.MemoryArea, specialAddress = cAttribute.SpecialAddress, memorySizeBytes = cAttribute.ByteLength, registerType = cAttribute.RegisterType, - dotnetCastType = dotnetType, + dotnetCastType = dotnetType.IsEnum ? dotnetType.UnderlyingSystemType : dotnetType, collectionType = collection.GetType(), name = prop.Name, }); @@ -345,6 +348,9 @@ namespace MewtocolNet { #region Register Adding + /// + public void AddRegister(IRegister register) => AddRegister(register as BaseRegister); + /// public void AddRegister(BaseRegister register) { @@ -354,14 +360,14 @@ namespace MewtocolNet { if (CheckDuplicateNameRegister(register)) throw MewtocolException.DupeNameRegister(register); + if (CheckOverlappingRegister(register, out var regB)) + throw MewtocolException.OverlappingRegister(register, regB); + register.attachedInterface = this; RegistersUnderlying.Add(register); } - /// - public void AddRegister(IRegister register) => AddRegister(register as BaseRegister); - internal void AddRegister (RegisterBuildInfo buildInfo) { var builtRegister = buildInfo.Build(); @@ -379,6 +385,9 @@ namespace MewtocolNet { if(CheckDuplicateNameRegister(builtRegister)) throw MewtocolException.DupeNameRegister(builtRegister); + if (CheckOverlappingRegister(builtRegister, out var regB)) + throw MewtocolException.OverlappingRegister(builtRegister, regB); + builtRegister.attachedInterface = this; RegistersUnderlying.Add(builtRegister); @@ -406,6 +415,38 @@ namespace MewtocolNet { } + private bool CheckOverlappingRegister (IRegisterInternal instance, out IRegisterInternal regB) { + + //ignore bool registers, they have their own address spectrum + regB = null; + if (instance is BoolRegister) return false; + + uint addressFrom = instance.MemoryAddress; + uint addressTo = addressFrom + instance.GetRegisterAddressLen(); + + var foundOverlapping = RegistersInternal.FirstOrDefault(x => { + + //ignore bool registers, they have their own address spectrum + if (x is BoolRegister) return false; + + uint addressF = x.MemoryAddress; + uint addressT = addressF + x.GetRegisterAddressLen(); + + bool matchingBaseAddress = addressFrom < addressT && addressF < addressTo; + + return matchingBaseAddress; + + }); + + if (foundOverlapping != null) { + regB = foundOverlapping; + return true; + } + + return false; + + } + #endregion #region Register accessing @@ -428,6 +469,18 @@ namespace MewtocolNet { #region Event Invoking + private protected void ClearRegisterVals() { + + for (int i = 0; i < RegistersUnderlying.Count; i++) { + + var reg = (IRegisterInternal)RegistersUnderlying[i]; + reg.ClearValue(); + + } + + } + + internal void PropertyRegisterWasSet(string propName, object value) { _ = SetRegisterAsync(GetRegister(propName), value); diff --git a/MewtocolNet/MewtocolInterfaceRequests.cs b/MewtocolNet/MewtocolInterfaceRequests.cs index 94015f3..6e3f7d7 100644 --- a/MewtocolNet/MewtocolInterfaceRequests.cs +++ b/MewtocolNet/MewtocolInterfaceRequests.cs @@ -64,12 +64,8 @@ namespace MewtocolNet { #region Operation mode changing - /// - /// Changes the PLCs operation mode to the given one - /// - /// The mode to change to - /// The success state of the write operation - public async Task SetOperationMode (bool setRun) { + /// + public async Task SetOperationModeAsync (bool setRun) { string modeChar = setRun ? "R" : "P"; @@ -77,7 +73,7 @@ namespace MewtocolNet { var result = await SendCommandAsync(requeststring); if (result.Success) { - Logger.Log($"operation mode was changed to {(setRun ? "Run" : "Prog")}", LogLevel.Info, this); + Logger.Log($"Operation mode was changed to {(setRun ? "Run" : "Prog")}", LogLevel.Info, this); } else { Logger.Log("Operation mode change failed", LogLevel.Error, this); } @@ -116,14 +112,15 @@ namespace MewtocolNet { } /// - /// Reads the bytes from the start adress for counts byte length + /// Reads the bytes from the start adress for counts byte length, + /// doesn't block the receive thread /// /// Start adress /// Number of bytes to get /// Flips bytes from big to mixed endian /// Gets invoked when the progress changes, contains the progress as a double /// A byte array or null of there was an error - public async Task ReadByteRange(int start, int count, bool flipBytes = true, Action onProgress = null) { + public async Task ReadByteRangeNonBlocking (int start, int count, bool flipBytes = true, Action onProgress = null) { var byteList = new List(); @@ -131,12 +128,13 @@ namespace MewtocolNet { if (count % 2 != 0) wordLength++; + int blockSize = 8; //read blocks of max 4 words per msg - for (int i = 0; i < wordLength; i += 8) { + for (int i = 0; i < wordLength; i += blockSize) { int curWordStart = start + i; - int curWordEnd = curWordStart + 7; + int curWordEnd = curWordStart + blockSize - 1; string startStr = curWordStart.ToString().PadLeft(5, '0'); string endStr = (curWordEnd).ToString().PadLeft(5, '0'); @@ -146,7 +144,7 @@ namespace MewtocolNet { if (result.Success && !string.IsNullOrEmpty(result.Response)) { - var bytes = result.Response.ParseDTByteString(8 * 4).HexStringToByteArray(); + var bytes = result.Response.ParseDTByteString(blockSize * 4).HexStringToByteArray(); if (bytes == null) return null; @@ -246,38 +244,15 @@ namespace MewtocolNet { //returns a byte array 1 long and with the byte beeing 0 or 1 if (toWriteType == typeof(BoolRegister)) { - string requeststring = $"%{GetStationNumber()}#WCS{_toWrite.BuildMewtocolQuery()}{(data[0] == 1 ? "1" : "0")}"; - var result = await SendCommandAsync(requeststring); - return result.Success; + string reqStr = $"%{GetStationNumber()}#WCS{_toWrite.BuildMewtocolQuery()}{(data[0] == 1 ? "1" : "0")}"; + var res = await SendCommandAsync(reqStr); + return res.Success; } - //writes a byte array 2 bytes or 4 bytes long depending on the data size - if (toWriteType.IsGenericType && toWriteType.GetGenericTypeDefinition() == typeof(NumberRegister<>)) { - - string requeststring = $"%{GetStationNumber()}#WD{_toWrite.BuildMewtocolQuery()}{data.ToHexString()}"; - var result = await SendCommandAsync(requeststring); - return result.Success; - - } - - //returns a byte array with variable size - if (toWriteType == typeof(BytesRegister)) { - - throw new NotImplementedException("Not imp"); - - } - - //writes to the string area - if (toWriteType == typeof(StringRegister)) { - - string requeststring = $"%{GetStationNumber()}#WD{_toWrite.BuildMewtocolQuery()}{data.ToHexString()}"; - var result = await SendCommandAsync(requeststring); - return result.Success; - - } - - return false; + string requeststring = $"%{GetStationNumber()}#WD{_toWrite.BuildMewtocolQuery()}{data.ToHexString()}"; + var result = await SendCommandAsync(requeststring); + return result.Success; } diff --git a/MewtocolNet/RegisterAttributes/RegisterAttribute.cs b/MewtocolNet/RegisterAttributes/RegisterAttribute.cs index cd79645..a08b29a 100644 --- a/MewtocolNet/RegisterAttributes/RegisterAttribute.cs +++ b/MewtocolNet/RegisterAttributes/RegisterAttribute.cs @@ -1,4 +1,5 @@ -using System; +using MewtocolNet.RegisterBuilding; +using System; namespace MewtocolNet.RegisterAttributes { @@ -10,31 +11,39 @@ namespace MewtocolNet.RegisterAttributes { internal RegisterType? RegisterType; - internal int MemoryArea = 0; - internal int ByteLength = 2; + internal uint MemoryArea = 0; + internal uint ByteLength = 2; internal byte SpecialAddress = 0x0; internal BitCount BitCount; internal int AssignedBitIndex = -1; + internal string MewAddress = null; + + public RegisterAttribute(string mewAddress) { + + MewAddress = mewAddress; + + } + /// /// Attribute for string type or numeric registers /// /// The area in the plcs memory - public RegisterAttribute(int memoryArea) { + public RegisterAttribute(uint memoryArea) { MemoryArea = memoryArea; } - public RegisterAttribute(int memoryArea, int byteLength) { + public RegisterAttribute(uint memoryArea, uint byteLength) { MemoryArea = memoryArea; ByteLength = byteLength; } - public RegisterAttribute(int memoryArea, BitCount bitCount) { + public RegisterAttribute(uint memoryArea, BitCount bitCount) { MemoryArea = memoryArea; BitCount = bitCount; @@ -44,7 +53,7 @@ namespace MewtocolNet.RegisterAttributes { } - public RegisterAttribute(int memoryArea, BitCount bitCount, int bitIndex) { + public RegisterAttribute(uint memoryArea, BitCount bitCount, int bitIndex) { MemoryArea = memoryArea; BitCount = bitCount; @@ -68,7 +77,7 @@ namespace MewtocolNet.RegisterAttributes { /// /// Attribute for boolean registers /// - public RegisterAttribute(IOType type, int memoryArea, byte spAdress = 0x0) { + public RegisterAttribute(IOType type, uint memoryArea, byte spAdress = 0x0) { MemoryArea = memoryArea; RegisterType = (RegisterType)(int)type; diff --git a/MewtocolNet/RegisterBuilding/BuilderStep.cs b/MewtocolNet/RegisterBuilding/BuilderStep.cs new file mode 100644 index 0000000..3c08159 --- /dev/null +++ b/MewtocolNet/RegisterBuilding/BuilderStep.cs @@ -0,0 +1,267 @@ +using MewtocolNet.Registers; +using System; +using System.Collections; +using System.Runtime.InteropServices; + +namespace MewtocolNet.RegisterBuilding { + + public static class BuilderStepExtensions { + + public static BuilderStep AsType (this BuilderStepBase baseStep) { + + if (!typeof(T).IsAllowedPlcCastingType()) { + + throw new NotSupportedException($"The dotnet type {typeof(T)}, is not supported for PLC type casting"); + + } + + var castStp = new BuilderStep(); + + if (baseStep.SpecialAddress != null) castStp.SpecialAddress = baseStep.SpecialAddress; + + castStp.Name = baseStep.Name; + castStp.RegType = baseStep.RegType; + castStp.MemAddress = baseStep.MemAddress; + castStp.MemByteSize = baseStep.MemByteSize; + castStp.dotnetVarType = typeof(T); + castStp.plcVarType = null; + castStp.wasCasted = true; + + return castStp; + + } + + public static BuilderStep AsType (this BuilderStepBase baseStep, Type type) { + + if (!type.IsAllowedPlcCastingType()) { + + throw new NotSupportedException($"The dotnet type {type}, is not supported for PLC type casting"); + + } + + var castStp = new BuilderStep(); + + if (baseStep.SpecialAddress != null) castStp.SpecialAddress = baseStep.SpecialAddress; + + castStp.Name = baseStep.Name; + castStp.RegType = baseStep.RegType; + castStp.MemAddress = baseStep.MemAddress; + castStp.MemByteSize = baseStep.MemByteSize; + castStp.dotnetVarType = type; + castStp.plcVarType = null; + castStp.wasCasted = true; + + return castStp; + + } + + public static IRegister Build (this BuilderStepBase step) { + + //if no casting method in builder was called => autocast the type from the RegisterType + if (!step.wasCasted && step.MemByteSize == null) step.AutoType(); + + //fallbacks if no casting builder was given + BuilderStepBase.GetFallbackDotnetType(step); + + BaseRegister builtReg; + + var bInfo = new RegisterBuildInfo { + + name = step.Name, + specialAddress = step.SpecialAddress, + memoryAddress = step.MemAddress, + memorySizeBytes = step.MemByteSize, + memorySizeBits = step.MemBitSize, + registerType = step.RegType, + dotnetCastType = step.dotnetVarType, + + }; + + builtReg = bInfo.Build(); + + BuilderStepBase.AddToRegisterList(step, builtReg); + + return builtReg; + + } + + public static IRegister Build(this BuilderStep step) { + + //fallbacks if no casting builder was given + BuilderStepBase.GetFallbackDotnetType(step); + + BaseRegister builtReg; + + var bInfo = new RegisterBuildInfo { + + name = step.Name, + specialAddress = step.SpecialAddress, + memoryAddress = step.MemAddress, + memorySizeBytes = step.MemByteSize, + memorySizeBits = step.MemBitSize, + registerType = step.RegType, + dotnetCastType = step.dotnetVarType, + + }; + + if (step.dotnetVarType.IsEnum) { + + builtReg = bInfo.Build(); + + } else { + + builtReg = bInfo.Build(); + + } + + BuilderStepBase.AddToRegisterList(step, builtReg); + + return builtReg; + + } + + } + + public abstract class BuilderStepBase { + + internal MewtocolInterface forInterface; + + internal bool wasCasted = false; + + internal string OriginalInput; + + internal string Name; + internal RegisterType RegType; + + internal uint MemAddress; + + internal uint? MemByteSize; + internal ushort? MemBitSize; + internal byte? SpecialAddress; + + internal PlcVarType? plcVarType; + internal Type dotnetVarType; + + internal BuilderStepBase AutoType() { + + switch (RegType) { + case RegisterType.X: + case RegisterType.Y: + case RegisterType.R: + dotnetVarType = typeof(bool); + break; + case RegisterType.DT: + dotnetVarType = typeof(short); + break; + case RegisterType.DDT: + dotnetVarType = typeof(int); + break; + case RegisterType.DT_BYTE_RANGE: + dotnetVarType = typeof(string); + break; + } + + plcVarType = null; + + wasCasted = true; + + return this; + + } + + internal static void GetFallbackDotnetType (BuilderStepBase step) { + + bool isBoolean = step.RegType.IsBoolean(); + bool isTypeNotDefined = step.plcVarType == null && step.dotnetVarType == null; + + if (isTypeNotDefined && step.RegType == RegisterType.DT) { + + step.dotnetVarType = typeof(short); + + } + if (isTypeNotDefined && step.RegType == RegisterType.DDT) { + + step.dotnetVarType = typeof(int); + + } else if (isTypeNotDefined && isBoolean) { + + step.dotnetVarType = typeof(bool); + + } else if (isTypeNotDefined && step.RegType == RegisterType.DT_BYTE_RANGE) { + + step.dotnetVarType = typeof(string); + + } + + if (step.plcVarType != null) { + + step.dotnetVarType = step.plcVarType.Value.GetDefaultDotnetType(); + + } + + } + + internal static void AddToRegisterList(BuilderStepBase step, BaseRegister instance) { + + if (step.forInterface == null) return; + + step.forInterface.AddRegister(instance); + + } + + } + + public class BuilderStep : BuilderStepBase { } + + public class BuilderStep : BuilderStepBase { + + public BuilderStep AsPlcType (PlcVarType varType) { + + dotnetVarType = null; + plcVarType = varType; + + wasCasted = true; + + return this; + + } + + public BuilderStep AsBytes (uint byteLength) { + + if (RegType != RegisterType.DT) { + + throw new NotSupportedException($"Cant use the {nameof(AsBytes)} converter on a non {nameof(RegisterType.DT)} register"); + + } + + MemByteSize = byteLength; + dotnetVarType = typeof(byte[]); + plcVarType = null; + + wasCasted = true; + + return this; + + } + + public BuilderStep AsBits(ushort bitCount = 16) { + + if (RegType != RegisterType.DT) { + + throw new NotSupportedException($"Cant use the {nameof(AsBits)} converter on a non {nameof(RegisterType.DT)} register"); + + } + + MemBitSize = bitCount; + dotnetVarType = typeof(BitArray); + plcVarType = null; + + wasCasted = true; + + return this; + + } + + } + +} diff --git a/MewtocolNet/RegisterBuilding/FinalizerExtensions.cs b/MewtocolNet/RegisterBuilding/FinalizerExtensions.cs deleted file mode 100644 index 683232c..0000000 --- a/MewtocolNet/RegisterBuilding/FinalizerExtensions.cs +++ /dev/null @@ -1,78 +0,0 @@ -using MewtocolNet.Registers; -using System; -using System.Linq; -using System.Reflection; - -namespace MewtocolNet.RegisterBuilding { - - public static class FinalizerExtensions { - - public static IRegister Build(this RegisterBuilderStep step) { - - //if no casting method in builder was called => autocast the type from the RegisterType - 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; - - if (isTypeNotDefined && step.RegType == RegisterType.DT) { - - step.dotnetVarType = typeof(short); - - } - if (isTypeNotDefined && step.RegType == RegisterType.DDT) { - - step.dotnetVarType = typeof(int); - - } else if (isTypeNotDefined && isBoolean) { - - step.dotnetVarType = typeof(bool); - - } else if (isTypeNotDefined && step.RegType == RegisterType.DT_BYTE_RANGE) { - - step.dotnetVarType = typeof(string); - - } - - if (step.plcVarType != null) { - - step.dotnetVarType = step.plcVarType.Value.GetDefaultDotnetType(); - - } - - } - - private static void AddToRegisterList(this RegisterBuilderStep step, BaseRegister instance) { - - if (step.forInterface == null) return; - - step.forInterface.AddRegister(instance); - - } - - } - -} diff --git a/MewtocolNet/RegisterBuilding/ParseResult.cs b/MewtocolNet/RegisterBuilding/ParseResult.cs index b851848..909d9af 100644 --- a/MewtocolNet/RegisterBuilding/ParseResult.cs +++ b/MewtocolNet/RegisterBuilding/ParseResult.cs @@ -6,7 +6,7 @@ public string hardFailReason; - public RegisterBuilderStep stepData; + public BuilderStep stepData; } diff --git a/MewtocolNet/RegisterBuilding/RegBuilder.cs b/MewtocolNet/RegisterBuilding/RegBuilder.cs index 5e54978..580a7ae 100644 --- a/MewtocolNet/RegisterBuilding/RegBuilder.cs +++ b/MewtocolNet/RegisterBuilding/RegBuilder.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Data.Common; +using System.Diagnostics; using System.Globalization; using System.Text.RegularExpressions; @@ -18,6 +19,7 @@ namespace MewtocolNet.RegisterBuilding { (x) => TryBuildBoolean(x), (x) => TryBuildNumericBased(x), + (x) => TryBuildByteRangeBased(x), }; @@ -32,7 +34,7 @@ namespace MewtocolNet.RegisterBuilding { public static RegBuilder Factory { get; private set; } = new RegBuilder(); - public RegisterBuilderStep FromPlcRegName (string plcAddrName, string name = null) { + public BuilderStep FromPlcRegName (string plcAddrName, string name = null) { foreach (var method in parseMethods) { @@ -45,6 +47,7 @@ namespace MewtocolNet.RegisterBuilding { res.stepData.OriginalInput = plcAddrName; res.stepData.forInterface = forInterface; + return res.stepData; } else if(res.state == ParseResultState.FailedHard) { @@ -77,7 +80,7 @@ namespace MewtocolNet.RegisterBuilding { string special = match.Groups["special"].Value; IOType regType; - int areaAdd = 0; + uint areaAdd = 0; byte specialAdd = 0x0; //try cast the prefix @@ -90,7 +93,7 @@ namespace MewtocolNet.RegisterBuilding { } - if(!string.IsNullOrEmpty(area) && !int.TryParse(area, out areaAdd) ) { + if(!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd) ) { return new ParseResult { state = ParseResultState.FailedHard, @@ -102,7 +105,7 @@ namespace MewtocolNet.RegisterBuilding { //special address not given if(string.IsNullOrEmpty(special) && !string.IsNullOrEmpty(area)) { - var isAreaInt = int.TryParse(area, NumberStyles.Number, CultureInfo.InvariantCulture, out var areaInt); + var isAreaInt = uint.TryParse(area, NumberStyles.Number, CultureInfo.InvariantCulture, out var areaInt); if (isAreaInt && areaInt >= 0 && areaInt <= 9) { @@ -142,7 +145,11 @@ namespace MewtocolNet.RegisterBuilding { return new ParseResult { state = ParseResultState.Success, - stepData = new RegisterBuilderStep ((RegisterType)(int)regType, areaAdd, specialAdd), + stepData = new BuilderStep { + RegType = (RegisterType)(int)regType, + MemAddress = areaAdd, + SpecialAddress = specialAdd, + } }; } @@ -150,7 +157,7 @@ namespace MewtocolNet.RegisterBuilding { // one to two word registers private static ParseResult TryBuildNumericBased (string plcAddrName) { - var patternByte = new Regex(@"(?DT|DDT)(?[0-9]{1,5})"); + var patternByte = new Regex(@"^(?DT|DDT)(?[0-9]{1,5})$"); var match = patternByte.Match(plcAddrName); @@ -159,12 +166,11 @@ namespace MewtocolNet.RegisterBuilding { state = ParseResultState.FailedSoft }; - string prefix = match.Groups["prefix"].Value; string area = match.Groups["area"].Value; RegisterType regType; - int areaAdd = 0; + uint areaAdd = 0; //try cast the prefix if (!Enum.TryParse(prefix, out regType)) { @@ -176,7 +182,7 @@ namespace MewtocolNet.RegisterBuilding { } - if (!string.IsNullOrEmpty(area) && !int.TryParse(area, out areaAdd)) { + if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) { return new ParseResult { state = ParseResultState.FailedHard, @@ -187,7 +193,76 @@ namespace MewtocolNet.RegisterBuilding { return new ParseResult { state = ParseResultState.Success, - stepData = new RegisterBuilderStep(regType, areaAdd), + stepData = new BuilderStep { + RegType = regType, + MemAddress = areaAdd, + }, + }; + + } + + // one to two word registers + private static ParseResult TryBuildByteRangeBased (string plcAddrName) { + + var split = plcAddrName.Split('-'); + + if(split.Length > 2) + return new ParseResult { + state = ParseResultState.FailedHard, + hardFailReason = $"Cannot parse '{plcAddrName}', to many delimters '-'" + }; + + uint[] addresses = new uint[2]; + + for (int i = 0; i < split.Length; i++) { + + string addr = split[i]; + var patternByte = new Regex(@"(?DT|DDT)(?[0-9]{1,5})"); + + var match = patternByte.Match(addr); + + if (!match.Success) + return new ParseResult { + state = ParseResultState.FailedSoft + }; + + string prefix = match.Groups["prefix"].Value; + string area = match.Groups["area"].Value; + + RegisterType regType; + uint areaAdd = 0; + + //try cast the prefix + if (!Enum.TryParse(prefix, out regType) || regType != RegisterType.DT) { + + return new ParseResult { + state = ParseResultState.FailedHard, + hardFailReason = $"Cannot parse '{plcAddrName}', the prefix is not allowed for word range registers" + }; + + } + + if (!string.IsNullOrEmpty(area) && !uint.TryParse(area, out areaAdd)) { + + return new ParseResult { + state = ParseResultState.FailedHard, + hardFailReason = $"Cannot parse '{plcAddrName}', the area address: '{area}' is wrong" + }; + + } + + addresses[i] = areaAdd; + + } + + return new ParseResult { + state = ParseResultState.Success, + stepData = new BuilderStep { + RegType = RegisterType.DT_BYTE_RANGE, + dotnetVarType = typeof(byte[]), + MemAddress = addresses[0], + MemByteSize = (addresses[1] - addresses[0] + 1) * 2, + } }; } diff --git a/MewtocolNet/RegisterBuilding/RegisterBuildInfo.cs b/MewtocolNet/RegisterBuilding/RegisterBuildInfo.cs index 7e88f4a..e6b8de3 100644 --- a/MewtocolNet/RegisterBuilding/RegisterBuildInfo.cs +++ b/MewtocolNet/RegisterBuilding/RegisterBuildInfo.cs @@ -1,15 +1,22 @@ -using MewtocolNet.Registers; +using MewtocolNet.Exceptions; +using MewtocolNet.Registers; using System; using System.Collections; +using System.Data.Common; using System.Reflection; +using System.Runtime.InteropServices; namespace MewtocolNet.RegisterBuilding { internal struct RegisterBuildInfo { + internal string mewAddress; + internal string name; - internal int memoryAddress; - internal int memorySizeBytes; + internal uint memoryAddress; + + internal uint? memorySizeBytes; + internal ushort? memorySizeBits; internal byte? specialAddress; internal RegisterType? registerType; @@ -18,10 +25,37 @@ namespace MewtocolNet.RegisterBuilding { internal BaseRegister Build () { + //Has mew address use this before the default checks + if (mewAddress != null) return BuildFromMewAddress(); + + //parse enums + if (dotnetCastType.IsEnum) { + + //------------------------------------------- + //as numeric register with enum target + + var underlying = Enum.GetUnderlyingType(dotnetCastType); + var enuSize = Marshal.SizeOf(underlying); + + if (enuSize > 4) + throw new NotSupportedException("Enums not based on 16 or 32 bit numbers are not supported"); + + Type myParameterizedSomeClass = typeof(NumberRegister<>).MakeGenericType(dotnetCastType); + ConstructorInfo constr = myParameterizedSomeClass.GetConstructor(new Type[] { typeof(uint), typeof(string) }); + + var parameters = new object[] { memoryAddress, name }; + var instance = (BaseRegister)constr.Invoke(parameters); + + if (collectionType != null) + instance.WithCollectionType(collectionType); + + return instance; + + } + + //parse all others where the type is known RegisterType regType = registerType ?? dotnetCastType.ToRegisterTypeDefault(); - Type registerClassType = dotnetCastType.GetDefaultRegisterHoldingType(); - bool isBytesRegister = !registerClassType.IsGenericType && registerClassType == typeof(BytesRegister); bool isStringRegister = !registerClassType.IsGenericType && registerClassType == typeof(StringRegister); @@ -30,7 +64,7 @@ namespace MewtocolNet.RegisterBuilding { //------------------------------------------- //as numeric register with boolean bit target //create a new bregister instance - var instance = new BytesRegister(memoryAddress, memorySizeBytes, name); + var instance = new BytesRegister(memoryAddress, memorySizeBytes.Value, name); if (collectionType != null) instance.WithCollectionType(collectionType); @@ -42,13 +76,11 @@ namespace MewtocolNet.RegisterBuilding { //------------------------------------------- //as numeric register - var areaAddr = memoryAddress; - //create a new bregister instance var flags = BindingFlags.Public | BindingFlags.Instance; //int _adress, Type _enumType = null, string _name = null - var parameters = new object[] { areaAddr, null, name }; + var parameters = new object[] { memoryAddress, name }; var instance = (BaseRegister)Activator.CreateInstance(registerClassType, flags, null, parameters, null); if(collectionType != null) @@ -62,7 +94,14 @@ namespace MewtocolNet.RegisterBuilding { //------------------------------------------- //as byte range register - var instance = new BytesRegister(memoryAddress, memorySizeBytes, name); + + BytesRegister instance; + + if(memorySizeBits != null) { + instance = new BytesRegister(memoryAddress, memorySizeBits.Value, name); + } else { + instance = new BytesRegister(memoryAddress, memorySizeBytes.Value, name); + } if (collectionType != null) instance.WithCollectionType(collectionType); @@ -106,6 +145,12 @@ namespace MewtocolNet.RegisterBuilding { } + private BaseRegister BuildFromMewAddress () { + + return (BaseRegister)RegBuilder.Factory.FromPlcRegName(mewAddress, name).AsType(dotnetCastType).Build(); + + } + } } diff --git a/MewtocolNet/RegisterBuilding/RegisterBuilderStep.cs b/MewtocolNet/RegisterBuilding/RegisterBuilderStep.cs deleted file mode 100644 index c334a72..0000000 --- a/MewtocolNet/RegisterBuilding/RegisterBuilderStep.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System; - -namespace MewtocolNet.RegisterBuilding { - - public class RegisterBuilderStep { - - internal MewtocolInterface forInterface; - - internal bool wasCasted = false; - - internal string OriginalInput; - - internal string Name; - internal RegisterType RegType; - internal int MemAddress; - internal int MemByteSize; - internal byte? SpecialAddress; - - internal PlcVarType? plcVarType; - internal Type dotnetVarType; - - public RegisterBuilderStep() => throw new NotSupportedException("Cant make a new instance of RegisterBuilderStep, use the builder pattern"); - - internal RegisterBuilderStep(RegisterType regType, int memAddr) { - - RegType = regType; - MemAddress = memAddr; - - } - - internal RegisterBuilderStep(RegisterType regType, int memAddr, byte specialAddr) { - - RegType = regType; - MemAddress = memAddr; - SpecialAddress = specialAddr; - - } - - public RegisterBuilderStep AsPlcType(PlcVarType varType) { - - dotnetVarType = null; - plcVarType = varType; - - wasCasted = true; - - return this; - - } - - public RegisterBuilderStep AsType() { - - if (!typeof(T).IsAllowedPlcCastingType()) { - - throw new NotSupportedException($"The dotnet type {typeof(T)}, is not supported for PLC type casting"); - - } - - dotnetVarType = typeof(T); - plcVarType = null; - - wasCasted = true; - - return this; - - } - - 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) { - case RegisterType.X: - case RegisterType.Y: - case RegisterType.R: - dotnetVarType = typeof(bool); - break; - case RegisterType.DT: - dotnetVarType = typeof(short); - break; - case RegisterType.DDT: - dotnetVarType = typeof(int); - break; - case RegisterType.DT_BYTE_RANGE: - dotnetVarType = typeof(string); - break; - } - - plcVarType = null; - - wasCasted = true; - - return this; - - } - - } - -} diff --git a/MewtocolNet/Registers/BaseRegister.cs b/MewtocolNet/Registers/BaseRegister.cs index 5a74bad..ac3f8d5 100644 --- a/MewtocolNet/Registers/BaseRegister.cs +++ b/MewtocolNet/Registers/BaseRegister.cs @@ -13,10 +13,10 @@ namespace MewtocolNet.Registers { public event Action ValueChanged; internal MewtocolInterface attachedInterface; - internal object lastValue; + internal object lastValue = null; internal Type collectionType; internal string name; - internal int memoryAddress; + internal uint memoryAddress; /// public MewtocolInterface AttachedInterface => attachedInterface; @@ -34,18 +34,16 @@ namespace MewtocolNet.Registers { public string Name => name; /// - public string PLCAddressName => GetRegisterPLCName(); + public string PLCAddressName => GetMewName(); /// - public int MemoryAddress => memoryAddress; + public uint 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")); + public void TriggerNotifyChange() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value))); #endregion @@ -53,9 +51,14 @@ namespace MewtocolNet.Registers { public virtual void SetValueFromPLC(object val) { - lastValue = val; - TriggerChangedEvnt(this); - TriggerNotifyChange(); + if(lastValue?.ToString() != val?.ToString()) { + + lastValue = val; + + TriggerNotifyChange(); + attachedInterface.InvokeRegisterChanged(this); + + } } @@ -90,7 +93,11 @@ namespace MewtocolNet.Registers { public virtual string GetContainerName() => $"{(CollectionType != null ? $"{CollectionType.Name}" : "")}"; - public virtual string GetRegisterPLCName() => $"{GetRegisterString()}{MemoryAddress}"; + public virtual string GetMewName() => $"{GetRegisterString()}{MemoryAddress}"; + + public virtual uint GetRegisterAddressLen() => throw new NotImplementedException(); + + public string GetRegisterWordRangeString() => $"{GetMewName()} - {MemoryAddress + GetRegisterAddressLen() - 1}"; #endregion @@ -102,14 +109,24 @@ namespace MewtocolNet.Registers { #endregion - public override string ToString() => $"{GetRegisterPLCName()}{(Name != null ? $" ({Name})" : "")} - Value: {GetValueString()}"; + protected virtual void CheckAddressOverflow (uint addressStart, uint addressLen) { + + if (addressStart < 0) + throw new NotSupportedException("The area address can't be negative"); + + if (addressStart + addressLen > 99999) + throw new NotSupportedException($"Memory adresses cant be greater than 99999 (DT{addressStart}-{addressStart + addressLen})"); + + } + + public override string ToString() => $"{GetMewName()}{(Name != null ? $" ({Name})" : "")} - Value: {GetValueString()}"; public virtual string ToString(bool additional) { if (!additional) return this.ToString(); StringBuilder sb = new StringBuilder(); - sb.AppendLine($"PLC Naming: {GetRegisterPLCName()}"); + sb.AppendLine($"PLC Naming: {GetMewName()}"); sb.AppendLine($"Name: {Name ?? "Not named"}"); sb.AppendLine($"Value: {GetValueString()}"); sb.AppendLine($"Register Type: {RegisterType}"); diff --git a/MewtocolNet/Registers/BoolRegister.cs b/MewtocolNet/Registers/BoolRegister.cs index cf1af8c..60a2a24 100644 --- a/MewtocolNet/Registers/BoolRegister.cs +++ b/MewtocolNet/Registers/BoolRegister.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using System.Net; using System.Text; using System.Threading.Tasks; @@ -25,21 +26,9 @@ namespace MewtocolNet.Registers { /// The custom name /// /// - public BoolRegister(IOType _io, byte _spAddress = 0x0, int _areaAdress = 0, string _name = null) { + public BoolRegister(IOType _io, byte _spAddress = 0x0, uint _areaAdress = 0, string _name = null) { - if (_areaAdress < 0) - throw new NotSupportedException("The area address cant be negative"); - - if (_io == IOType.R && _areaAdress >= 512) - throw new NotSupportedException("R area addresses cant be greater than 511"); - - if ((_io == IOType.X || _io == IOType.Y) && _areaAdress >= 110) - throw new NotSupportedException("XY area addresses cant be greater than 110"); - - if (_spAddress > 0xF) - throw new NotSupportedException("Special address cant be greater 15 or 0xF"); - - lastValue = false; + lastValue = null; memoryAddress = _areaAdress; specialAddress = _spAddress; @@ -47,18 +36,27 @@ namespace MewtocolNet.Registers { RegisterType = (RegisterType)(int)_io; + CheckAddressOverflow(memoryAddress, 0); + + } + + protected override void CheckAddressOverflow(uint addressStart, uint addressLen) { + + if ((int)RegisterType == (int)IOType.R && addressStart >= 512) + throw new NotSupportedException("R area addresses cant be greater than 511"); + + if (((int)RegisterType == (int)IOType.X || (int)RegisterType == (int)IOType.Y) && addressStart >= 110) + throw new NotSupportedException("XY area addresses cant be greater than 110"); + + if (specialAddress > 0xF) + throw new NotSupportedException("Special address cant be greater 15 or 0xF"); + + base.CheckAddressOverflow(addressStart, addressLen); + } #region Read / Write - public override void SetValueFromPLC(object val) { - - lastValue = (bool)val; - TriggerChangedEvnt(this); - TriggerNotifyChange(); - - } - /// public override async Task ReadAsync() { @@ -111,7 +109,7 @@ namespace MewtocolNet.Registers { public override void ClearValue() => SetValueFromPLC(false); /// - public override string GetRegisterPLCName() { + public override string GetMewName() { var spAdressEnd = SpecialAddress.ToString("X1"); @@ -131,13 +129,16 @@ namespace MewtocolNet.Registers { } + /// + public override uint GetRegisterAddressLen () => 1; + /// public override string ToString(bool additional) { if (!additional) return this.ToString(); StringBuilder sb = new StringBuilder(); - sb.AppendLine($"PLC Naming: {GetRegisterPLCName()}"); + sb.AppendLine($"PLC Naming: {GetMewName()}"); sb.AppendLine($"Name: {Name ?? "Not named"}"); sb.AppendLine($"Value: {GetValueString()}"); sb.AppendLine($"Register Type: {RegisterType}"); diff --git a/MewtocolNet/Registers/BytesRegister.cs b/MewtocolNet/Registers/BytesRegister.cs index bd2483f..3425c30 100644 --- a/MewtocolNet/Registers/BytesRegister.cs +++ b/MewtocolNet/Registers/BytesRegister.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Text; @@ -12,35 +13,75 @@ namespace MewtocolNet.Registers { /// public class BytesRegister : BaseRegister { - internal int addressLength; + internal uint addressLength; /// /// The rgisters memory length /// - public int AddressLength => addressLength; + public uint AddressLength => addressLength; - internal short ReservedSize { get; set; } + internal uint ReservedBytesSize { get; private set; } + + internal ushort? ReservedBitSize { get; private set; } /// - /// Defines a register containing a string + /// Defines a register containing bytes /// - public BytesRegister(int _address, int _reservedByteSize, string _name = null) { + public BytesRegister(uint _address, uint _reservedByteSize, string _name = null) { - if (_address > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999"); name = _name; memoryAddress = _address; - ReservedSize = (short)_reservedByteSize; + ReservedBytesSize = _reservedByteSize; //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++; + var byteSize = ReservedBytesSize; + if (ReservedBytesSize % 2 != 0) byteSize++; RegisterType = RegisterType.DT_BYTE_RANGE; - addressLength = (byteSize / 2) - 1; + addressLength = Math.Max((byteSize / 2), 1); + + CheckAddressOverflow(memoryAddress, addressLength); + + lastValue = null; } - public override string GetValueString() => Value == null ? "null" : ((byte[])Value).ToHexString("-"); + public BytesRegister(uint _address, ushort _reservedBitSize, string _name = null) { + + name = _name; + memoryAddress = _address; + ReservedBytesSize = (uint)Math.Max(1, _reservedBitSize / 8); + ReservedBitSize = _reservedBitSize; + + //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 = ReservedBytesSize; + if (ReservedBytesSize % 2 != 0) byteSize++; + + RegisterType = RegisterType.DT_BYTE_RANGE; + addressLength = Math.Max((byteSize / 2), 1); + + CheckAddressOverflow(memoryAddress, addressLength); + + lastValue = null; + + } + + public override string GetValueString() { + + if (Value == null) return "null"; + + if(Value != null && Value is BitArray bitArr) { + + return bitArr.ToBitString(); + + } else { + + return ((byte[])Value).ToHexString("-"); + + } + + } /// public override string BuildMewtocolQuery() { @@ -48,7 +89,7 @@ namespace MewtocolNet.Registers { StringBuilder asciistring = new StringBuilder("D"); asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0')); - asciistring.Append((MemoryAddress + AddressLength).ToString().PadLeft(5, '0')); + asciistring.Append((MemoryAddress + AddressLength - 1).ToString().PadLeft(5, '0')); return asciistring.ToString(); } @@ -56,10 +97,20 @@ namespace MewtocolNet.Registers { /// public override void SetValueFromPLC (object val) { - lastValue = (byte[])val; + bool changeTriggerBitArr = val is BitArray bitArr && + lastValue is BitArray bitArr2 && + (bitArr.ToBitString() != bitArr2.ToBitString()); - TriggerChangedEvnt(this); - TriggerNotifyChange(); + bool changeTriggerGeneral = (lastValue?.ToString() != val?.ToString()); + + if (changeTriggerBitArr || changeTriggerGeneral) { + + lastValue = val; + + TriggerNotifyChange(); + attachedInterface.InvokeRegisterChanged(this); + + } } @@ -69,6 +120,9 @@ namespace MewtocolNet.Registers { /// public override void ClearValue() => SetValueFromPLC(null); + /// + public override uint GetRegisterAddressLen() => AddressLength; + /// public override async Task ReadAsync() { @@ -77,7 +131,13 @@ namespace MewtocolNet.Registers { var read = await attachedInterface.ReadRawRegisterAsync(this); if (read == null) return null; - var parsed = PlcValueParser.Parse(this, read); + object parsed; + + if(ReservedBitSize != null) { + parsed = PlcValueParser.Parse(this, read); + } else { + parsed = PlcValueParser.Parse(this, read); + } SetValueFromPLC(parsed); return parsed; @@ -89,8 +149,17 @@ namespace MewtocolNet.Registers { if (!attachedInterface.IsConnected) return false; - var res = await attachedInterface.WriteRawRegisterAsync(this, (byte[])data); + byte[] encoded; + + if (ReservedBitSize != null) { + encoded = PlcValueParser.Encode(this, (BitArray)data); + } else { + encoded = PlcValueParser.Encode(this, (byte[])data); + } + + var res = await attachedInterface.WriteRawRegisterAsync(this, encoded); if (res) SetValueFromPLC(data); + return res; } diff --git a/MewtocolNet/Registers/Interfaces/IRegister.cs b/MewtocolNet/Registers/Interfaces/IRegister.cs index 18aa8d8..c75937b 100644 --- a/MewtocolNet/Registers/Interfaces/IRegister.cs +++ b/MewtocolNet/Registers/Interfaces/IRegister.cs @@ -1,4 +1,5 @@ -using System; +using MewtocolNet.Registers; +using System; using System.Text; using System.Threading.Tasks; @@ -37,7 +38,7 @@ namespace MewtocolNet { /// /// The plc memory address of the register /// - int MemoryAddress { get; } + uint MemoryAddress { get; } /// /// Gets the value of the register as the plc representation string @@ -67,7 +68,6 @@ namespace MewtocolNet { /// True if successfully set Task WriteAsync(object data); - } } diff --git a/MewtocolNet/Registers/Interfaces/IRegisterInternal.cs b/MewtocolNet/Registers/Interfaces/IRegisterInternal.cs index e5b7a3a..01b24d3 100644 --- a/MewtocolNet/Registers/Interfaces/IRegisterInternal.cs +++ b/MewtocolNet/Registers/Interfaces/IRegisterInternal.cs @@ -17,7 +17,7 @@ namespace MewtocolNet { object Value { get; } - int MemoryAddress { get; } + uint MemoryAddress { get; } // setters @@ -37,7 +37,7 @@ namespace MewtocolNet { string GetContainerName(); - string GetRegisterPLCName(); + string GetMewName(); byte? GetSpecialAddress(); @@ -47,6 +47,9 @@ namespace MewtocolNet { string BuildMewtocolQuery(); + uint GetRegisterAddressLen(); + + string GetRegisterWordRangeString(); //others diff --git a/MewtocolNet/Registers/NumberRegister.cs b/MewtocolNet/Registers/NumberRegister.cs index 2f8a4ab..6d1d433 100644 --- a/MewtocolNet/Registers/NumberRegister.cs +++ b/MewtocolNet/Registers/NumberRegister.cs @@ -14,66 +14,63 @@ namespace MewtocolNet.Registers { /// The type of the numeric value public class NumberRegister : BaseRegister { - internal Type enumType { get; set; } - /// /// Defines a register containing a number /// /// Memory start adress max 99999 /// Name of the register - public NumberRegister (int _address, string _name = null) { - - if (_address > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999"); + public NumberRegister (uint _address, string _name = null) { memoryAddress = _address; name = _name; Type numType = typeof(T); + uint areaLen = 0; - var allowedTypes = PlcValueParser.GetAllowDotnetTypes(); - if (!allowedTypes.Contains(numType)) - throw new NotSupportedException($"The type {numType} is not allowed for Number Registers"); + if (typeof(T).IsEnum) { - var areaLen = (Marshal.SizeOf(numType) / 2) - 1; - RegisterType = areaLen >= 1 ? RegisterType.DDT : RegisterType.DT; + //for enums - lastValue = default(T); + var underlyingType = typeof(T).GetEnumUnderlyingType(); //the numeric type + areaLen = (uint)(Marshal.SizeOf(underlyingType) / 2) - 1; - } + if (areaLen == 0) RegisterType = RegisterType.DT; + if (areaLen == 1) RegisterType = RegisterType.DDT; + if (areaLen >= 2) RegisterType = RegisterType.DT_BYTE_RANGE; - /// - /// 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) { + lastValue = null; + Console.WriteLine(); - if (_address > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999"); + } else { - memoryAddress = _address; - name = _name; + //for all others known pre-defined numeric structs - 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 allowedTypes = PlcValueParser.GetAllowDotnetTypes(); - if (!allowedTypes.Contains(numType)) - throw new NotSupportedException($"The type {numType} is not allowed for Number Registers"); + areaLen = (uint)(Marshal.SizeOf(numType) / 2) - 1; + RegisterType = areaLen >= 1 ? RegisterType.DDT : RegisterType.DT; - var areaLen = (Marshal.SizeOf(numType) / 2) - 1; - RegisterType = areaLen >= 1 ? RegisterType.DDT : RegisterType.DT; + lastValue = null; - enumType = _enumType; - lastValue = default(T); + } + + CheckAddressOverflow(memoryAddress, areaLen); } /// public override void SetValueFromPLC(object val) { - lastValue = (T)val; - TriggerChangedEvnt(this); - TriggerNotifyChange(); + if (lastValue?.ToString() != val?.ToString()) { + + lastValue = (T)val; + + TriggerNotifyChange(); + attachedInterface.InvokeRegisterChanged(this); + + } } @@ -93,71 +90,42 @@ namespace MewtocolNet.Registers { } /// - public override string GetAsPLC() => ((TimeSpan)Value).AsPLCTime(); + public override string GetAsPLC() { - /// - public override string GetValueString() { - - if(typeof(T) == typeof(TimeSpan)) { - - return $"{Value} [{((TimeSpan)Value).AsPLCTime()}]"; - - } - - //is number or bitwise - if (enumType == null) { - - return $"{Value}"; - - } - - //is enum - var dict = new Dictionary(); - - foreach (var name in Enum.GetNames(enumType)) { - - int enumKey = (int)Enum.Parse(enumType, name); - if (!dict.ContainsKey(enumKey)) { - dict.Add(enumKey, name); - } - - } - - if (enumType != null && Value is short shortVal) { - - if (dict.ContainsKey(shortVal)) { - - return $"{Value} ({dict[shortVal]})"; - - } else { - - return $"{Value} (Missing Enum)"; - - } - - } - - if (enumType != null && Value is int intVal) { - - if (dict.ContainsKey(intVal)) { - - return $"{Value} ({dict[intVal]})"; - - } else { - - return $"{Value} (Missing Enum)"; - - } - - } + if (typeof(T) == typeof(TimeSpan)) return ((TimeSpan)Value).ToPlcTime(); return Value.ToString(); } + /// + public override string GetValueString() { + + if(Value != null && typeof(T) == typeof(TimeSpan)) { + + return $"{Value} [{((TimeSpan)Value).ToPlcTime()}]"; + + } + + if (Value != null && typeof(T).IsEnum) { + + var underlying = Enum.GetUnderlyingType(typeof(T)); + object val = Convert.ChangeType(Value, underlying); + + return $"{Value} [{val}]"; + + } + + return Value?.ToString() ?? "null"; + + } + /// public override void ClearValue() => SetValueFromPLC(default(T)); + /// + public override uint GetRegisterAddressLen() => (uint)(RegisterType == RegisterType.DT ? 1 : 2); + /// public override async Task ReadAsync() { diff --git a/MewtocolNet/Registers/StringRegister.cs b/MewtocolNet/Registers/StringRegister.cs index 9147049..25aea63 100644 --- a/MewtocolNet/Registers/StringRegister.cs +++ b/MewtocolNet/Registers/StringRegister.cs @@ -1,6 +1,9 @@ -using System; +using MewtocolNet.Exceptions; +using MewtocolNet.Logging; +using System; using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Net; using System.Text; using System.Threading.Tasks; @@ -16,20 +19,23 @@ namespace MewtocolNet.Registers { internal short UsedSize { get; set; } - internal int WordsSize { get; set; } + internal uint WordsSize { get; set; } - private bool isCalibrated = false; + private bool isCalibratedFromPlc = false; /// /// Defines a register containing a string /// - public StringRegister (int _address, string _name = null) { + public StringRegister (uint _address, string _name = null) { - if (_address > 99999) throw new NotSupportedException("Memory adresses cant be greater than 99999"); name = _name; memoryAddress = _address; RegisterType = RegisterType.DT_BYTE_RANGE; + CheckAddressOverflow(memoryAddress, 0); + + lastValue = null; + } /// @@ -38,7 +44,7 @@ namespace MewtocolNet.Registers { StringBuilder asciistring = new StringBuilder("D"); asciistring.Append(MemoryAddress.ToString().PadLeft(5, '0')); - asciistring.Append((MemoryAddress + WordsSize - 1).ToString().PadLeft(5, '0')); + asciistring.Append((MemoryAddress + Math.Max(1, WordsSize) - 1).ToString().PadLeft(5, '0')); return asciistring.ToString(); } @@ -49,10 +55,14 @@ namespace MewtocolNet.Registers { /// public override void SetValueFromPLC (object val) { - lastValue = (string)val; + if (!val.Equals(lastValue)) { - TriggerChangedEvnt(this); - TriggerNotifyChange(); + lastValue = (string)val; + + TriggerNotifyChange(); + attachedInterface.InvokeRegisterChanged(this); + + } } @@ -62,6 +72,9 @@ namespace MewtocolNet.Registers { /// public override void ClearValue() => SetValueFromPLC(""); + /// + public override uint GetRegisterAddressLen() => Math.Max(1, WordsSize); + /// public override async Task ReadAsync() { @@ -69,25 +82,39 @@ namespace MewtocolNet.Registers { //get the string params first - if(!isCalibrated) await Calibrate(); + if(!isCalibratedFromPlc) await CalibrateFromPLC(); var read = await attachedInterface.ReadRawRegisterAsync(this); if (read == null) return null; - return PlcValueParser.Parse(this, read); + var parsed = PlcValueParser.Parse(this, read); + + SetValueFromPLC(parsed); + + return parsed; } - private async Task Calibrate () { + private async Task CalibrateFromPLC () { + + Logger.Log($"Calibrating string ({PLCAddressName}) from PLC source", LogLevel.Verbose, attachedInterface); //get the string describer bytes - var bytes = await attachedInterface.ReadByteRange(MemoryAddress, 4, false); + var bytes = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, 4, false); + + if (bytes == null || bytes.Length == 0 || bytes.All(x => x == 0x0)) { + + throw new MewtocolException($"The string register ({PLCAddressName}) doesn't exist in the PLC program"); + + } ReservedSize = BitConverter.ToInt16(bytes, 0); UsedSize = BitConverter.ToInt16(bytes, 2); - WordsSize = 2 + (ReservedSize + 1) / 2; + WordsSize = Math.Max(0, (uint)(2 + (ReservedSize + 1) / 2)); - isCalibrated = true; + CheckAddressOverflow(memoryAddress, WordsSize); + + isCalibratedFromPlc = true; } @@ -96,10 +123,18 @@ namespace MewtocolNet.Registers { if (!attachedInterface.IsConnected) return false; + if (!isCalibratedFromPlc) { + + //try to calibrate from plc + await CalibrateFromPLC(); + + } + var res = await attachedInterface.WriteRawRegisterAsync(this, PlcValueParser.Encode(this, (string)data)); + if (res) { - UsedSize = (short)((string)Value).Length; SetValueFromPLC(data); + UsedSize = (short)((string)Value).Length; } return res; diff --git a/MewtocolNet/TypeConversion/Conversions.cs b/MewtocolNet/TypeConversion/Conversions.cs index 53d62e4..ef513b8 100644 --- a/MewtocolNet/TypeConversion/Conversions.cs +++ b/MewtocolNet/TypeConversion/Conversions.cs @@ -26,10 +26,11 @@ namespace MewtocolNet.TypeConversion { }; /// - /// All conversions for reading dataf from and to the plc + /// All conversions for reading data from and to the plc, excluding Enum types /// internal static List items = new List { + //default bool R conversion new PlcTypeConversion(RegisterType.R) { HoldingRegisterType = typeof(BoolRegister), PlcVarType = PlcVarType.BOOL, @@ -43,6 +44,7 @@ namespace MewtocolNet.TypeConversion { }, }, + //default bool X conversion new PlcTypeConversion(RegisterType.X) { HoldingRegisterType = typeof(BoolRegister), PlcVarType = PlcVarType.BOOL, @@ -56,6 +58,7 @@ namespace MewtocolNet.TypeConversion { }, }, + //default bool Y conversion new PlcTypeConversion(RegisterType.Y) { HoldingRegisterType = typeof(BoolRegister), PlcVarType = PlcVarType.BOOL, @@ -69,6 +72,7 @@ namespace MewtocolNet.TypeConversion { }, }, + //default short DT conversion new PlcTypeConversion(RegisterType.DT) { HoldingRegisterType = typeof(NumberRegister), PlcVarType = PlcVarType.INT, @@ -82,6 +86,7 @@ namespace MewtocolNet.TypeConversion { }, }, + //default ushort DT conversion new PlcTypeConversion(RegisterType.DT) { HoldingRegisterType = typeof(NumberRegister), PlcVarType = PlcVarType.UINT, @@ -95,6 +100,7 @@ namespace MewtocolNet.TypeConversion { }, }, + //default int DDT conversion new PlcTypeConversion(RegisterType.DDT) { HoldingRegisterType = typeof(NumberRegister), PlcVarType = PlcVarType.DINT, @@ -108,6 +114,7 @@ namespace MewtocolNet.TypeConversion { }, }, + //default uint DDT conversion new PlcTypeConversion(RegisterType.DDT) { HoldingRegisterType = typeof(NumberRegister), PlcVarType = PlcVarType.UDINT, @@ -121,6 +128,7 @@ namespace MewtocolNet.TypeConversion { }, }, + //default float DDT conversion new PlcTypeConversion(RegisterType.DDT) { HoldingRegisterType = typeof(NumberRegister), PlcVarType = PlcVarType.REAL, @@ -139,6 +147,7 @@ namespace MewtocolNet.TypeConversion { }, }, + //default TimeSpan DDT conversion new PlcTypeConversion(RegisterType.DDT) { HoldingRegisterType = typeof(NumberRegister), PlcVarType = PlcVarType.TIME, @@ -157,11 +166,13 @@ namespace MewtocolNet.TypeConversion { }, }, - new PlcTypeConversion(RegisterType.DT) { + //default byte array DT Range conversion + new PlcTypeConversion(RegisterType.DT_BYTE_RANGE) { HoldingRegisterType = typeof(BytesRegister), FromRaw = (reg, bytes) => bytes, ToRaw = (reg, value) => value, }, + //default string DT Range conversion new PlcTypeConversion(RegisterType.DT_BYTE_RANGE) { HoldingRegisterType = typeof(StringRegister), PlcVarType = PlcVarType.STRING, @@ -195,12 +206,16 @@ namespace MewtocolNet.TypeConversion { }, }, - new PlcTypeConversion(RegisterType.DT) { + //default bitn array DT conversion + new PlcTypeConversion(RegisterType.DT_BYTE_RANGE) { HoldingRegisterType = typeof(BytesRegister), - PlcVarType = PlcVarType.WORD, FromRaw = (reg, bytes) => { + var byteReg = (BytesRegister)reg; + BitArray bitAr = new BitArray(bytes); + bitAr.Length = (int)byteReg.ReservedBitSize; + return bitAr; }, @@ -208,6 +223,15 @@ namespace MewtocolNet.TypeConversion { byte[] ret = new byte[(value.Length - 1) / 8 + 1]; value.CopyTo(ret, 0); + + if(ret.Length % 2 != 0) { + + var lst = ret.ToList(); + lst.Add(0); + ret = lst.ToArray(); + + } + return ret; }, diff --git a/MewtocolNet/TypeConversion/PlcValueParser.cs b/MewtocolNet/TypeConversion/PlcValueParser.cs index ab873d8..cf8c887 100644 --- a/MewtocolNet/TypeConversion/PlcValueParser.cs +++ b/MewtocolNet/TypeConversion/PlcValueParser.cs @@ -12,9 +12,22 @@ namespace MewtocolNet { private static List conversions => Conversions.items; - public static T Parse(IRegister register, byte[] bytes) { + internal static T Parse (IRegister register, byte[] bytes) { - var converter = conversions.FirstOrDefault(x => x.GetDotnetType() == typeof(T)); + IPlcTypeConverter converter; + + //special case for enums + if(typeof(T).IsEnum) { + + var underlyingNumberType = typeof(T).GetEnumUnderlyingType(); + + converter = conversions.FirstOrDefault(x => x.GetDotnetType() == underlyingNumberType); + + } else { + + converter = conversions.FirstOrDefault(x => x.GetDotnetType() == typeof(T)); + + } if (converter == null) throw new MewtocolException($"A converter for the dotnet type {typeof(T)} doesn't exist"); @@ -23,9 +36,22 @@ namespace MewtocolNet { } - public static byte[] Encode (IRegister register, T value) { + internal static byte[] Encode (IRegister register, T value) { - var converter = conversions.FirstOrDefault(x => x.GetDotnetType() == typeof(T)); + IPlcTypeConverter converter; + + //special case for enums + if (typeof(T).IsEnum) { + + var underlyingNumberType = typeof(T).GetEnumUnderlyingType(); + + converter = conversions.FirstOrDefault(x => x.GetDotnetType() == underlyingNumberType); + + } else { + + converter = conversions.FirstOrDefault(x => x.GetDotnetType() == typeof(T)); + + } if (converter == null) throw new MewtocolException($"A converter for the dotnet type {typeof(T)} doesn't exist"); diff --git a/MewtocolNet/TypeConversion/PlcVarTypeConversions.cs b/MewtocolNet/TypeConversion/PlcVarTypeConversions.cs index 2ff6817..79ce5f4 100644 --- a/MewtocolNet/TypeConversion/PlcVarTypeConversions.cs +++ b/MewtocolNet/TypeConversion/PlcVarTypeConversions.cs @@ -22,18 +22,24 @@ namespace MewtocolNet { internal static bool IsAllowedPlcCastingType() { + if (typeof(T).IsEnum) return true; + return allowedCastingTypes.Contains(typeof(T)); } internal static bool IsAllowedPlcCastingType(this Type type) { + if (type.IsEnum) return true; + return allowedCastingTypes.Contains(type); } internal static RegisterType ToRegisterTypeDefault(this Type type) { + if (type.IsEnum) return RegisterType.DT; + var found = PlcValueParser.GetDefaultRegisterType(type); if (found != null) { diff --git a/MewtocolTests/AutomatedPropertyRegisters.cs b/MewtocolTests/AutomatedPropertyRegisters.cs index 9a30f15..416fcdc 100644 --- a/MewtocolTests/AutomatedPropertyRegisters.cs +++ b/MewtocolTests/AutomatedPropertyRegisters.cs @@ -1,13 +1,12 @@ using MewtocolNet; -using MewtocolNet.RegisterAttributes; using MewtocolNet.Registers; -using System.Collections; +using MewtocolTests.EncapsulatedTests; using Xunit; using Xunit.Abstractions; namespace MewtocolTests { - public class AutomatedPropertyRegisters { + public partial class AutomatedPropertyRegisters { private readonly ITestOutputHelper output; @@ -15,90 +14,14 @@ namespace MewtocolTests { this.output = output; } - public class TestRegisterCollection : RegisterCollection { - - //corresponds to a R100 boolean register in the PLC - //can also be written as R1000 because the last one is a special address - [Register(IOType.R, memoryArea: 85, spAdress: 0)] - public bool TestBool1 { get; private set; } - - //corresponds to a XD input of the PLC - [Register(IOType.X, (byte)0xD)] - public bool TestBoolInputXD { get; private set; } - - //corresponds to a DT1101 - DT1104 string register in the PLC with (STRING[4]) - //[Register(1101, 4)] - //public string TestString1 { get; private set; } - - //corresponds to a DT7000 16 bit int register in the PLC - [Register(899)] - public short TestInt16 { get; private set; } - - [Register(342)] - public ushort TestUInt16 { get; private set; } - - //corresponds to a DTD7001 - DTD7002 32 bit int register in the PLC - [Register(7001)] - public int TestInt32 { get; private set; } - - [Register(765)] - public uint TestUInt32 { get; private set; } - - //corresponds to a DTD7001 - DTD7002 32 bit float register in the PLC (REAL) - [Register(7003)] - public float TestFloat32 { get; private set; } - - //corresponds to a DT7005 - DT7009 string register in the PLC with (STRING[5]) - [Register(7005, 5)] - public string TestString2 { get; private set; } - - //corresponds to a DT7010 as a 16bit word/int and parses the word as single bits - [Register(7010)] - public BitArray TestBitRegister { get; private set; } - - [Register(8010, BitCount.B32)] - public BitArray TestBitRegister32 { get; private set; } - - //corresponds to a DT1204 as a 16bit word/int takes the bit at index 9 and writes it back as a boolean - [Register(1204, BitCount.B16, 9)] - public bool BitValue { get; private set; } - - [Register(1204, BitCount.B32, 5)] - public bool FillTest { get; private set; } - - //corresponds to a DT7012 - DT7013 as a 32bit time value that gets parsed as a timespan (TIME) - //the smallest value to communicate to the PLC is 10ms - [Register(7012)] - public TimeSpan TestTime { get; private set; } - - public enum CurrentState { - Undefined = 0, - State1 = 1, - State2 = 2, - //State3 = 3, - State4 = 4, - State5 = 5, - StateBetween = 100, - State6 = 6, - State7 = 7, - } - - [Register(50)] - public CurrentState TestEnum16 { get; private set; } - - [Register(51, BitCount.B32)] - public CurrentState TestEnum32 { get; private set; } - - } - - private void TestBasicGeneration(IRegisterInternal reg, string propName, object expectValue, int expectAddr, string expectPlcName) { + private void Test(IRegisterInternal reg, string propName, uint expectAddr, string expectPlcName) { Assert.NotNull(reg); Assert.Equal(propName, reg.Name); - Assert.Equal(expectValue, reg.Value); + Assert.Null(reg.Value); Assert.Equal(expectAddr, reg.MemoryAddress); - Assert.Equal(expectPlcName, reg.GetRegisterPLCName()); + Assert.Equal(expectPlcName, reg.GetMewName()); output.WriteLine(reg.ToString()); @@ -106,107 +29,86 @@ namespace MewtocolTests { //actual tests - [Fact(DisplayName = "Boolean R generation")] + [Fact(DisplayName = "Boolean generation")] public void BooleanGen() { var interf = Mewtocol.Ethernet("192.168.0.1"); - interf.AddRegisterCollection(new TestRegisterCollection()).WithPoller(); + interf.AddRegisterCollection(new TestBoolRegisters()); - var register = interf.GetRegister(nameof(TestRegisterCollection.TestBool1)); + var register1 = interf.GetRegister(nameof(TestBoolRegisters.RType)); + var register2 = interf.GetRegister(nameof(TestBoolRegisters.XType)); - //test generic properties - TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestBool1), false, 85, "R85"); + var register3 = interf.GetRegister(nameof(TestBoolRegisters.RType_MewString)); + + Test((IRegisterInternal)register1, nameof(TestBoolRegisters.RType), 85, "R85A"); + Test((IRegisterInternal)register2, nameof(TestBoolRegisters.XType), 0, "XD"); + + Test((IRegisterInternal)register3, nameof(TestBoolRegisters.RType_MewString), 85, "R85B"); } - [Fact(DisplayName = "Boolean input XD generation")] - public void BooleanInputGen() { + [Fact(DisplayName = "Number 16 bit generation")] + public void N16BitGen () { var interf = Mewtocol.Ethernet("192.168.0.1"); - interf.AddRegisterCollection(new TestRegisterCollection()).WithPoller(); + interf.AddRegisterCollection(new Nums16Bit()); - var register = interf.GetRegister(nameof(TestRegisterCollection.TestBoolInputXD)); + var register1 = interf.GetRegister(nameof(Nums16Bit.Int16Type)); + var register2 = interf.GetRegister(nameof(Nums16Bit.UInt16Type)); + var register3 = interf.GetRegister(nameof(Nums16Bit.Enum16Type)); + + var register4 = interf.GetRegister(nameof(Nums16Bit.Int16Type_MewString)); + var register5 = interf.GetRegister(nameof(Nums16Bit.Enum16Type_MewString)); //test generic properties - TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestBoolInputXD), false, 0, "XD"); + Test((IRegisterInternal)register1, nameof(Nums16Bit.Int16Type), 899, "DT899"); + Test((IRegisterInternal)register2, nameof(Nums16Bit.UInt16Type), 342, "DT342"); + Test((IRegisterInternal)register3, nameof(Nums16Bit.Enum16Type), 50, "DT50"); + + Test((IRegisterInternal)register4, nameof(Nums16Bit.Int16Type_MewString), 900, "DT900"); + Test((IRegisterInternal)register5, nameof(Nums16Bit.Enum16Type_MewString), 51, "DT51"); } - [Fact(DisplayName = "Int16 generation")] - public void Int16Gen() { + [Fact(DisplayName = "Number 32 bit generation")] + public void N32BitGen () { var interf = Mewtocol.Ethernet("192.168.0.1"); - interf.AddRegisterCollection(new TestRegisterCollection()).WithPoller(); + interf.AddRegisterCollection(new Nums32Bit()); - var register = interf.GetRegister(nameof(TestRegisterCollection.TestInt16)); + var register1 = interf.GetRegister(nameof(Nums32Bit.Int32Type)); + var register2 = interf.GetRegister(nameof(Nums32Bit.UInt32Type)); + var register3 = interf.GetRegister(nameof(Nums32Bit.Enum32Type)); + var register4 = interf.GetRegister(nameof(Nums32Bit.FloatType)); + var register5 = interf.GetRegister(nameof(Nums32Bit.TimeSpanType)); + + var register6 = interf.GetRegister(nameof(Nums32Bit.Enum32Type_MewString)); + var register7 = interf.GetRegister(nameof(Nums32Bit.TimeSpanType_MewString)); //test generic properties - TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestInt16), (short)0, 899, "DT899"); + Test((IRegisterInternal)register1, nameof(Nums32Bit.Int32Type), 7001, "DDT7001"); + Test((IRegisterInternal)register2, nameof(Nums32Bit.UInt32Type), 765, "DDT765"); + Test((IRegisterInternal)register3, nameof(Nums32Bit.Enum32Type), 51, "DDT51"); + Test((IRegisterInternal)register4, nameof(Nums32Bit.FloatType), 7003, "DDT7003"); + Test((IRegisterInternal)register5, nameof(Nums32Bit.TimeSpanType), 7012, "DDT7012"); + + Test((IRegisterInternal)register6, nameof(Nums32Bit.Enum32Type_MewString), 53, "DDT53"); + Test((IRegisterInternal)register7, nameof(Nums32Bit.TimeSpanType_MewString), 7014, "DDT7014"); } - [Fact(DisplayName = "UInt16 generation")] - public void UInt16Gen() { + [Fact(DisplayName = "String generation")] + public void StringGen() { var interf = Mewtocol.Ethernet("192.168.0.1"); - interf.AddRegisterCollection(new TestRegisterCollection()).WithPoller(); + interf.AddRegisterCollection(new TestStringRegisters()); - var register = interf.GetRegister(nameof(TestRegisterCollection.TestUInt16)); + var register1 = interf.GetRegister(nameof(TestStringRegisters.StringType)); + var register2 = interf.GetRegister(nameof(TestStringRegisters.StringType_MewString)); //test generic properties - TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestUInt16), (ushort)0, 342, "DT342"); - - } - - [Fact(DisplayName = "Int32 generation")] - public void Int32Gen() { - - var interf = Mewtocol.Ethernet("192.168.0.1"); - interf.AddRegisterCollection(new TestRegisterCollection()).WithPoller(); - - var register = interf.GetRegister(nameof(TestRegisterCollection.TestInt32)); - - //test generic properties - TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestInt32), (int)0, 7001, "DDT7001"); - - } - - [Fact(DisplayName = "UInt32 generation")] - public void UInt32Gen() { - - var interf = Mewtocol.Ethernet("192.168.0.1"); - interf.AddRegisterCollection(new TestRegisterCollection()).WithPoller(); - - var register = interf.GetRegister(nameof(TestRegisterCollection.TestUInt32)); - - //test generic properties - TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestUInt32), (uint)0, 765, "DDT765"); - - } - - [Fact(DisplayName = "Float32 generation")] - public void Float32Gen() { - - var interf = Mewtocol.Ethernet("192.168.0.1"); - interf.AddRegisterCollection(new TestRegisterCollection()).WithPoller(); - - var register = interf.GetRegister(nameof(TestRegisterCollection.TestFloat32)); - - //test generic properties - TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestFloat32), (float)0, 7003, "DDT7003"); - - } - - [Fact(DisplayName = "TimeSpan generation")] - public void TimespanGen() { - - var interf = Mewtocol.Ethernet("192.168.0.1"); - interf.AddRegisterCollection(new TestRegisterCollection()).WithPoller(); - - var register = interf.GetRegister(nameof(TestRegisterCollection.TestTime)); - - //test generic properties - TestBasicGeneration((IRegisterInternal)register, nameof(TestRegisterCollection.TestTime), TimeSpan.Zero, 7012, "DDT7012"); + Test((IRegisterInternal)register1, nameof(TestStringRegisters.StringType), 7005, "DT7005"); + Test((IRegisterInternal)register2, nameof(TestStringRegisters.StringType_MewString), 7050, "DT7050"); } diff --git a/MewtocolTests/EncapsulatedTests/RegisterReadWriteTest.cs b/MewtocolTests/EncapsulatedTests/RegisterReadWriteTest.cs index 529cc84..3ddf4a5 100644 --- a/MewtocolTests/EncapsulatedTests/RegisterReadWriteTest.cs +++ b/MewtocolTests/EncapsulatedTests/RegisterReadWriteTest.cs @@ -13,7 +13,9 @@ internal class RegisterReadWriteTest { public object IntialValue { get; set; } - public object AfterWriteValue { get; set; } + public object IntermediateValue { get; set; } + + public object AfterWriteValue { get; set; } public string RegisterPlcAddressName { get; set; } diff --git a/MewtocolTests/EncapsulatedTests/TestRegisterCollection.cs b/MewtocolTests/EncapsulatedTests/TestRegisterCollection.cs new file mode 100644 index 0000000..e222dc9 --- /dev/null +++ b/MewtocolTests/EncapsulatedTests/TestRegisterCollection.cs @@ -0,0 +1,115 @@ +using MewtocolNet; +using MewtocolNet.RegisterAttributes; +using System.Collections; + +namespace MewtocolTests.EncapsulatedTests { + + public enum CurrentState : short { + Undefined = 0, + State1 = 1, + State2 = 2, + //State3 = 3, <= leave empty for test purposes + State4 = 4, + State5 = 5, + StateBetween = 100, + State6 = 6, + State7 = 7, + } + + public enum CurrentState32 : int { + Undefined = 0, + State1 = 1, + State2 = 2, + //State3 = 3, <= leave empty for test purposes + State4 = 4, + State5 = 5, + StateBetween = 100, + State6 = 6, + State7 = 7, + } + + public class TestBoolRegisters : RegisterCollection { + + [Register(IOType.R, memoryArea: 85, spAdress: 0xA)] + public bool RType { get; set; } + + [Register(IOType.X, (byte)0xD)] + public bool XType { get; set; } + + [Register("R85B")] + public bool RType_MewString { get; set; } + + } + + public class Nums16Bit : RegisterCollection { + + + [Register(899)] + public short Int16Type { get; set; } + + [Register(342)] + public ushort UInt16Type { get; set; } + + [Register(50)] + public CurrentState Enum16Type { get; set; } + + [Register("DT900")] + public short Int16Type_MewString { get; set; } + + [Register("DT51")] + public CurrentState Enum16Type_MewString { get; set; } + + } + + public class Nums32Bit : RegisterCollection { + + [Register(7001)] + public int Int32Type { get; set; } + + [Register(765)] + public uint UInt32Type { get; set; } + + [Register(51)] + public CurrentState32 Enum32Type { get; set; } + + [Register(7003)] + public float FloatType { get; set; } + + [Register(7012)] + public TimeSpan TimeSpanType { get; set; } + + [Register("DDT53")] + public CurrentState32 Enum32Type_MewString { get; set; } + + [Register("DDT7014")] + public TimeSpan TimeSpanType_MewString { get; set; } + + } + + public class TestStringRegisters : RegisterCollection { + + [Register(7005, 5)] + public string? StringType { get; set; } + + [Register("DT7050")] + public string? StringType_MewString { get; set; } + + } + + public class TestBitwiseRegisters : RegisterCollection { + + [Register(7010)] + public BitArray TestBitRegister { get; set; } + + [Register(8010, BitCount.B32)] + public BitArray TestBitRegister32 { get; set; } + + [Register(1204, BitCount.B16, 9)] + public bool BitValue { get; set; } + + [Register(1204, BitCount.B32, 5)] + public bool FillTest { get; set; } + + } + +} \ No newline at end of file diff --git a/MewtocolTests/TestHelperExtensions.cs b/MewtocolTests/TestHelperExtensions.cs index 1739279..556b409 100644 --- a/MewtocolTests/TestHelperExtensions.cs +++ b/MewtocolTests/TestHelperExtensions.cs @@ -13,7 +13,7 @@ namespace MewtocolTests { this.output = output; } - [Fact(DisplayName = nameof(MewtocolHelpers.ToBitString))] + [Fact(DisplayName = nameof(PlcFormat.ToBitString))] public void ToBitStringGeneration() { var bitarr = new BitArray(16); @@ -91,6 +91,25 @@ namespace MewtocolTests { } + [Fact(DisplayName = nameof(PlcFormat.ParsePlcTime))] + public void ParsePlcTime () { + + Assert.Equal(new TimeSpan(5, 30, 30, 15, 10), PlcFormat.ParsePlcTime("T#5d30h30m15s10ms")); + Assert.Equal(new TimeSpan(0, 30, 30, 15, 10), PlcFormat.ParsePlcTime("T#30h30m15s10ms")); + Assert.Equal(new TimeSpan(0, 1, 30, 15, 10), PlcFormat.ParsePlcTime("T#1h30m15s10ms")); + Assert.Equal(new TimeSpan(0, 0, 5, 30, 10), PlcFormat.ParsePlcTime("T#5m30s10ms")); + Assert.Throws(() => PlcFormat.ParsePlcTime("T#5m30s5ms")); + + } + + [Fact(DisplayName = nameof(PlcFormat.ToPlcTime))] + public void ToPlcTime() { + + Assert.Equal("T#1d6h5m30s10ms", PlcFormat.ToPlcTime(new TimeSpan(0, 30, 5, 30, 10))); + Assert.Equal("T#6d5h30m10s", PlcFormat.ToPlcTime(new TimeSpan(6, 5, 30, 10))); + + } + } } \ No newline at end of file diff --git a/MewtocolTests/TestLivePLC.cs b/MewtocolTests/TestLivePLC.cs index 1a94956..3a04ac5 100644 --- a/MewtocolTests/TestLivePLC.cs +++ b/MewtocolTests/TestLivePLC.cs @@ -3,6 +3,7 @@ using MewtocolNet.Logging; using MewtocolNet.RegisterBuilding; using MewtocolNet.Registers; using MewtocolTests.EncapsulatedTests; +using System.Collections; using Xunit; using Xunit.Abstractions; @@ -41,15 +42,51 @@ namespace MewtocolTests new RegisterReadWriteTest { TargetRegister = new BoolRegister(IOType.R, 0xA, 10), RegisterPlcAddressName = "R10A", - IntialValue = false, + IntermediateValue = false, AfterWriteValue = true, }, new RegisterReadWriteTest { TargetRegister = new NumberRegister(3000), RegisterPlcAddressName = "DT3000", - IntialValue = (short)0, + IntermediateValue = (short)0, AfterWriteValue = (short)-513, }, + new RegisterReadWriteTest { + TargetRegister = new NumberRegister(3001), + RegisterPlcAddressName = "DT3001", + IntermediateValue = CurrentState.Undefined, + AfterWriteValue = CurrentState.State4, + }, + new RegisterReadWriteTest { + TargetRegister = new NumberRegister(3002), + RegisterPlcAddressName = "DDT3002", + IntermediateValue = CurrentState32.Undefined, + AfterWriteValue = CurrentState32.StateBetween, + }, + new RegisterReadWriteTest { + TargetRegister = new NumberRegister(3004), + RegisterPlcAddressName = "DDT3004", + IntermediateValue = TimeSpan.Zero, + AfterWriteValue = TimeSpan.FromSeconds(11), + }, + new RegisterReadWriteTest { + TargetRegister = new NumberRegister(3006), + RegisterPlcAddressName = "DDT3006", + IntermediateValue = TimeSpan.Zero, + AfterWriteValue = PlcFormat.ParsePlcTime("T#50m"), + }, + new RegisterReadWriteTest { + TargetRegister = new StringRegister(40), + RegisterPlcAddressName = "DT40", + IntermediateValue = "Hello", + AfterWriteValue = "TestV", + }, + new RegisterReadWriteTest { + TargetRegister = RegBuilder.Factory.FromPlcRegName("DT3008").AsBits(5).Build(), + RegisterPlcAddressName = "DT3008", + IntermediateValue = new BitArray(new bool[] { false, false, false, false, false }), + AfterWriteValue = new BitArray(new bool[] { false, true, false, false, false }), + }, }; @@ -57,16 +94,9 @@ namespace MewtocolTests this.output = output; - Logger.LogLevel = LogLevel.Critical; - Logger.OnNewLogMessage((d, l, m) => { - - output.WriteLine($"Mewtocol Logger: {d} {m}"); - - }); - } - [Fact(DisplayName = "Connection cycle client to PLC")] + [Fact(DisplayName = "Connection cycle client to PLC (Ethernet)")] public async void TestClientConnection() { foreach (var plc in testPlcInformationData) { @@ -87,7 +117,7 @@ namespace MewtocolTests } - [Fact(DisplayName = "Reading basic information from PLC")] + [Fact(DisplayName = "Reading basic status from PLC (Ethernet)")] public async void TestClientReadPLCStatus() { foreach (var plc in testPlcInformationData) { @@ -111,42 +141,56 @@ namespace MewtocolTests } - [Fact(DisplayName = "Reading basic information from PLC")] + [Fact(DisplayName = "Reading / Writing registers from PLC (Ethernet)")] public async void TestRegisterReadWriteAsync() { - foreach (var plc in testPlcInformationData) { + Logger.LogLevel = LogLevel.Verbose; + Logger.OnNewLogMessage((d, l, m) => { - output.WriteLine($"Testing: {plc.PLCName}\n"); + output.WriteLine($"{d:HH:mm:ss:fff} {m}"); - var client = Mewtocol.Ethernet(plc.PLCIP, plc.PLCPort); + }); - foreach (var testRW in testRegisterRW) { + var plc = testPlcInformationData[0]; - client.AddRegister(testRW.TargetRegister); + output.WriteLine($"\n\n --- Testing: {plc.PLCName} ---\n"); - } + var client = Mewtocol.Ethernet(plc.PLCIP, plc.PLCPort); - await client.ConnectAsync(); - Assert.True(client.IsConnected); + foreach (var testRW in testRegisterRW) { - foreach (var testRW in testRegisterRW) { - - var testRegister = client.Registers.First(x => x.PLCAddressName == testRW.RegisterPlcAddressName); - - //test inital val - Assert.Equal(testRW.IntialValue, testRegister.Value); - - await testRegister.WriteAsync(testRW.AfterWriteValue); - - //test after write val - Assert.Equal(testRW.AfterWriteValue, testRegister.Value); - - } - - client.Disconnect(); + client.AddRegister(testRW.TargetRegister); } + await client.ConnectAsync(); + Assert.True(client.IsConnected); + + //cycle run mode to reset registers to inital + await client.SetOperationModeAsync(false); + await client.SetOperationModeAsync(true); + + foreach (var testRW in testRegisterRW) { + + var testRegister = client.Registers.First(x => x.PLCAddressName == testRW.RegisterPlcAddressName); + + //test inital val + Assert.Null(testRegister.Value); + + await testRegister.ReadAsync(); + + Assert.Equal(testRW.IntermediateValue, testRegister.Value); + + await testRegister.WriteAsync(testRW.AfterWriteValue); + await testRegister.ReadAsync(); + + //test after write val + Assert.Equal(testRW.AfterWriteValue, testRegister.Value); + + } + + client.Disconnect(); + } } diff --git a/MewtocolTests/TestRegisterBuilder.cs b/MewtocolTests/TestRegisterBuilder.cs index 4be5918..729ac6c 100644 --- a/MewtocolTests/TestRegisterBuilder.cs +++ b/MewtocolTests/TestRegisterBuilder.cs @@ -1,6 +1,7 @@ using MewtocolNet; using MewtocolNet.RegisterBuilding; using MewtocolNet.Registers; +using MewtocolTests.EncapsulatedTests; using System.Collections; using Xunit; using Xunit.Abstractions; @@ -162,12 +163,14 @@ public class TestRegisterBuilder { } [Fact(DisplayName = "Parsing as Number Register (Casted)")] - public void TestRegisterBuildingNumericCasted() { + public void TestRegisterBuildingNumericCasted () { - var expect = new NumberRegister(303, null); - var expect2 = new NumberRegister(10002, null); - var expect3 = new NumberRegister(400, null); - //var expect4 = new NRegister(103, null, true); + var expect = new NumberRegister(303); + var expect2 = new NumberRegister(10002); + var expect3 = new NumberRegister(404); + var expect4 = new NumberRegister(400); + var expect5 = new NumberRegister(203); + var expect6 = new NumberRegister(204); Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").AsPlcType(PlcVarType.INT).Build()); Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").AsType().Build()); @@ -175,32 +178,82 @@ public class TestRegisterBuilder { Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("DDT10002").AsPlcType(PlcVarType.DINT).Build()); Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("DDT10002").AsType().Build()); - Assert.Equivalent(expect3, RegBuilder.Factory.FromPlcRegName("DDT400").AsPlcType(PlcVarType.TIME).Build()); - Assert.Equivalent(expect3, RegBuilder.Factory.FromPlcRegName("DDT400").AsType().Build()); + Assert.Equivalent(expect3, RegBuilder.Factory.FromPlcRegName("DDT404").AsPlcType(PlcVarType.REAL).Build()); + Assert.Equivalent(expect3, RegBuilder.Factory.FromPlcRegName("DDT404").AsType().Build()); - //Assert.Equivalent(expect4, RegBuilder.FromPlcRegName("DT103").AsType().Build()); + Assert.Equivalent(expect4, RegBuilder.Factory.FromPlcRegName("DDT400").AsPlcType(PlcVarType.TIME).Build()); + Assert.Equivalent(expect4, RegBuilder.Factory.FromPlcRegName("DDT400").AsType().Build()); + + Assert.Equivalent(expect5, RegBuilder.Factory.FromPlcRegName("DT203").AsType().Build()); + Assert.Equivalent(expect6, RegBuilder.Factory.FromPlcRegName("DT204").AsType().Build()); } [Fact(DisplayName = "Parsing as Number Register (Auto)")] - public void TestRegisterBuildingNumericAuto() { + public void TestRegisterBuildingNumericAuto () { - var expect = new NumberRegister(303, null); - var expect2 = new NumberRegister(10002, null); + var expect = new NumberRegister(201); + var expect2 = new NumberRegister(10002); - Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").Build()); + Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT201").Build()); Assert.Equivalent(expect2, RegBuilder.Factory.FromPlcRegName("DDT10002").Build()); } [Fact(DisplayName = "Parsing as Bytes Register (Casted)")] - public void TestRegisterBuildingByteRangeCasted() { + public void TestRegisterBuildingByteRangeCasted () { - var expect = new BytesRegister(303, 5); - - Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT303").AsBytes(5).Build()); + var expect = new BytesRegister(305, (uint)35); + Assert.Equal((uint)18, expect.AddressLength); + Assert.Equivalent(expect, RegBuilder.Factory.FromPlcRegName("DT305").AsBytes(35).Build()); } + [Fact(DisplayName = "Parsing as Bytes Register (Auto)")] + public void TestRegisterBuildingByteRangeAuto () { + + var expect = new BytesRegister(300, (uint)20 * 2); + var actual = (BytesRegister)RegBuilder.Factory.FromPlcRegName("DT300-DT319").Build(); + + Assert.Equal((uint)20, expect.AddressLength); + Assert.Equivalent(expect, actual); + + } + + [Fact(DisplayName = "Parsing as Bit Array")] + public void TestRegisterBuildingBitArray () { + + var expect1 = new BytesRegister(311, (ushort)5); + var expect2 = new BytesRegister(312, (ushort)16); + var expect3 = new BytesRegister(313, (ushort)32); + + var actual1 = (BytesRegister)RegBuilder.Factory.FromPlcRegName("DT311").AsBits(5).Build(); + var actual2 = (BytesRegister)RegBuilder.Factory.FromPlcRegName("DT312").AsBits(16).Build(); + var actual3 = (BytesRegister)RegBuilder.Factory.FromPlcRegName("DT313").AsBits(32).Build(); + + Assert.Equivalent(expect1, actual1); + Assert.Equivalent(expect2, actual2); + Assert.Equivalent(expect3, actual3); + + Assert.Equal((uint)1, actual1.AddressLength); + Assert.Equal((uint)1, actual2.AddressLength); + Assert.Equal((uint)2, actual3.AddressLength); + + } + + [Fact(DisplayName = "Parsing as String Register")] + public void TestRegisterBuildingString () { + + var expect1 = new StringRegister(314); + + var actual1 = (StringRegister)RegBuilder.Factory.FromPlcRegName("DT314").AsType().Build(); + + Assert.Equivalent(expect1, actual1); + + Assert.Equal((uint)0, actual1.WordsSize); + + } + + } diff --git a/MewtocolTests/TestRegisterInterface.cs b/MewtocolTests/TestRegisterInterface.cs index b2b8b50..674d7af 100644 --- a/MewtocolTests/TestRegisterInterface.cs +++ b/MewtocolTests/TestRegisterInterface.cs @@ -23,8 +23,8 @@ namespace MewtocolTests { new NumberRegister(50), new NumberRegister(50), new NumberRegister(50), - new BytesRegister(50, 30), - new BytesRegister(50, 31), + new BytesRegister(50, (uint)30), + new BytesRegister(50, (uint)31), }; List expectedIdents = new List { @@ -103,7 +103,7 @@ namespace MewtocolTests { IRegisterInternal? reg = registers[i]; string expect = expcectedIdents[i]; - Assert.Equal(expect, reg.GetRegisterPLCName()); + Assert.Equal(expect, reg.GetMewName()); }