From 4666d3071b7d47d11d6f565a0f00b57910c289a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Wei=C3=9F?= <72068105+Sandoun@users.noreply.github.com> Date: Fri, 14 Jul 2023 17:45:31 +0200 Subject: [PATCH] Array support first addition --- .../PlcCodeTestedAttribute.cs | 2 +- .../PlcEXRTAttribute.cs | 2 +- .../PlcLegacyAttribute.cs | 2 +- MewtocolNet/Documentation/docs.xml | 75 +++++++ MewtocolNet/Helpers/MewtocolHelpers.cs | 2 +- MewtocolNet/Mewtocol.cs | 2 +- MewtocolNet/MewtocolInterface.cs | 13 +- .../MewtocolInterfaceRegisterHandling.cs | 14 +- MewtocolNet/PublicEnums/PlcType.cs | 2 +- .../PublicEnums/RegisterBuildSource.cs | 11 + .../RegisterAttributes/RegisterAttribute.cs | 12 +- MewtocolNet/RegisterBuilding/RBuild.cs | 205 ++++++++++++++---- .../RegisterBuilding/RegisterAssembler.cs | 13 +- MewtocolNet/Registers/BaseRegister.cs | 47 ++-- MewtocolNet/Registers/NumberRegister.cs | 6 + MewtocolNet/UnderlyingRegisters/DTArea.cs | 14 +- .../LinkedRegisterGroup.cs | 18 ++ .../UnderlyingRegisters/MemoryAreaManager.cs | 190 ++++++++-------- 18 files changed, 444 insertions(+), 186 deletions(-) rename MewtocolNet/{DocAttributes => Documentation}/PlcCodeTestedAttribute.cs (87%) rename MewtocolNet/{DocAttributes => Documentation}/PlcEXRTAttribute.cs (87%) rename MewtocolNet/{DocAttributes => Documentation}/PlcLegacyAttribute.cs (87%) create mode 100644 MewtocolNet/Documentation/docs.xml create mode 100644 MewtocolNet/PublicEnums/RegisterBuildSource.cs create mode 100644 MewtocolNet/UnderlyingRegisters/LinkedRegisterGroup.cs diff --git a/MewtocolNet/DocAttributes/PlcCodeTestedAttribute.cs b/MewtocolNet/Documentation/PlcCodeTestedAttribute.cs similarity index 87% rename from MewtocolNet/DocAttributes/PlcCodeTestedAttribute.cs rename to MewtocolNet/Documentation/PlcCodeTestedAttribute.cs index 3883f15..a29c9f8 100644 --- a/MewtocolNet/DocAttributes/PlcCodeTestedAttribute.cs +++ b/MewtocolNet/Documentation/PlcCodeTestedAttribute.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace MewtocolNet.DocAttributes { +namespace MewtocolNet.Documentation { [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] internal class PlcCodeTestedAttribute : Attribute { diff --git a/MewtocolNet/DocAttributes/PlcEXRTAttribute.cs b/MewtocolNet/Documentation/PlcEXRTAttribute.cs similarity index 87% rename from MewtocolNet/DocAttributes/PlcEXRTAttribute.cs rename to MewtocolNet/Documentation/PlcEXRTAttribute.cs index 1975a52..ecffa7f 100644 --- a/MewtocolNet/DocAttributes/PlcEXRTAttribute.cs +++ b/MewtocolNet/Documentation/PlcEXRTAttribute.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace MewtocolNet.DocAttributes { +namespace MewtocolNet.Documentation { [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] internal class PlcEXRTAttribute : Attribute { diff --git a/MewtocolNet/DocAttributes/PlcLegacyAttribute.cs b/MewtocolNet/Documentation/PlcLegacyAttribute.cs similarity index 87% rename from MewtocolNet/DocAttributes/PlcLegacyAttribute.cs rename to MewtocolNet/Documentation/PlcLegacyAttribute.cs index 01367dc..555ef52 100644 --- a/MewtocolNet/DocAttributes/PlcLegacyAttribute.cs +++ b/MewtocolNet/Documentation/PlcLegacyAttribute.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace MewtocolNet.DocAttributes { +namespace MewtocolNet.Documentation { [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] internal class PlcLegacyAttribute : Attribute { diff --git a/MewtocolNet/Documentation/docs.xml b/MewtocolNet/Documentation/docs.xml new file mode 100644 index 0000000..a32571f --- /dev/null +++ b/MewtocolNet/Documentation/docs.xml @@ -0,0 +1,75 @@ + + + + + + + + + Boolean R/X/Y registers + + + + + + 16 bit signed integer + + + + + + 16 bit un-signed integer + + + + + + 16 bit word (2 bytes) + + + + + + 32 bit signed integer + + + + + + 32 bit un-signed integer + + + + + + 32 bit word (4 bytes) + + + + + + 32 bit floating point + + + + + + + 32 bit time from interpreted as + + + + + + + 16 or 32 bit enums, also supports flags + + + + + + String of chars, the interface will automatically get the length + + + + \ No newline at end of file diff --git a/MewtocolNet/Helpers/MewtocolHelpers.cs b/MewtocolNet/Helpers/MewtocolHelpers.cs index 493f44d..7f3d009 100644 --- a/MewtocolNet/Helpers/MewtocolHelpers.cs +++ b/MewtocolNet/Helpers/MewtocolHelpers.cs @@ -1,4 +1,4 @@ -using MewtocolNet.DocAttributes; +using MewtocolNet.Documentation; using MewtocolNet.Registers; using System; using System.Collections; diff --git a/MewtocolNet/Mewtocol.cs b/MewtocolNet/Mewtocol.cs index ada7bc7..1aaa327 100644 --- a/MewtocolNet/Mewtocol.cs +++ b/MewtocolNet/Mewtocol.cs @@ -125,7 +125,7 @@ namespace MewtocolNet { /// /// - public int MaxOptimizationDistance { get; set; } = 8; + public int MaxOptimizationDistance { get; set; } = 4; /// /// The max number of registers per request group diff --git a/MewtocolNet/MewtocolInterface.cs b/MewtocolNet/MewtocolInterface.cs index 5abde05..6b8fb62 100644 --- a/MewtocolNet/MewtocolInterface.cs +++ b/MewtocolNet/MewtocolInterface.cs @@ -158,10 +158,15 @@ namespace MewtocolNet { var asInternal = (BaseRegister)o; - Logger.Log($"{asInternal.GetMewName()} " + - $"{(o.Name != null ? $"({o.Name}) " : "")}" + - $"{asInternal.underlyingSystemType} " + - $"changed to \"{asInternal.GetValueString()}\"", LogLevel.Change, this); + var sb = new StringBuilder(); + sb.Append(asInternal.GetMewName()); + if(asInternal.Name != null) { + sb.Append(asInternal.autoGenerated ? $" (Auto)" : $" ({o.Name})"); + } + sb.Append($" {asInternal.underlyingSystemType.Name}"); + sb.Append($" changed to \"{asInternal.GetValueString()}\""); + + Logger.Log(sb.ToString(), LogLevel.Change, this); OnRegisterChangedUpdateProps((IRegisterInternal)o); diff --git a/MewtocolNet/MewtocolInterfaceRegisterHandling.cs b/MewtocolNet/MewtocolInterfaceRegisterHandling.cs index 28dc91e..ecb9288 100644 --- a/MewtocolNet/MewtocolInterfaceRegisterHandling.cs +++ b/MewtocolNet/MewtocolInterfaceRegisterHandling.cs @@ -190,7 +190,7 @@ namespace MewtocolNet { #endregion - #region Register Colleciton adding + #region Register Collection adding /// /// Adds the given register collection and all its registers with attributes to the register list @@ -219,18 +219,14 @@ namespace MewtocolNet { var pollFreqAttr = (PollLevelAttribute)attributes.FirstOrDefault(x => x.GetType() == typeof(PollLevelAttribute)); - if (!prop.PropertyType.IsAllowedPlcCastingType()) { - throw new MewtocolException($"The register attribute property type is not allowed ({prop.PropertyType})"); - } - var dotnetType = prop.PropertyType; int pollLevel = 1; if (pollFreqAttr != null) pollLevel = pollFreqAttr.pollLevel; //add builder item - regBuild - .Address(cAttribute.MewAddress) + var stp1 = regBuild + .AddressFromAttribute(cAttribute.MewAddress, cAttribute.TypeDef) .AsType(dotnetType.IsEnum ? dotnetType.UnderlyingSystemType : dotnetType) .PollLevel(pollLevel) .RegCollection(collection) @@ -255,7 +251,7 @@ namespace MewtocolNet { } var assembler = new RegisterAssembler(this); - var registers = assembler.AssembleAll(regBuild); + var registers = assembler.AssembleAll(regBuild, true); AddRegisters(registers.ToArray()); } @@ -290,7 +286,7 @@ namespace MewtocolNet { internal void InsertRegistersToMemoryStack (List registers) { - memoryManager.LinkRegisters(registers); + memoryManager.LinkAndMergeRegisters(registers); } diff --git a/MewtocolNet/PublicEnums/PlcType.cs b/MewtocolNet/PublicEnums/PlcType.cs index 2a3ce36..5f92a92 100644 --- a/MewtocolNet/PublicEnums/PlcType.cs +++ b/MewtocolNet/PublicEnums/PlcType.cs @@ -1,4 +1,4 @@ -using MewtocolNet.DocAttributes; +using MewtocolNet.Documentation; namespace MewtocolNet { diff --git a/MewtocolNet/PublicEnums/RegisterBuildSource.cs b/MewtocolNet/PublicEnums/RegisterBuildSource.cs new file mode 100644 index 0000000..3b62a58 --- /dev/null +++ b/MewtocolNet/PublicEnums/RegisterBuildSource.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MewtocolNet.PublicEnums { + public enum RegisterBuildSource { + Anonymous, + Manual, + Attribute, + } +} diff --git a/MewtocolNet/RegisterAttributes/RegisterAttribute.cs b/MewtocolNet/RegisterAttributes/RegisterAttribute.cs index 5546f82..4fd66c9 100644 --- a/MewtocolNet/RegisterAttributes/RegisterAttribute.cs +++ b/MewtocolNet/RegisterAttributes/RegisterAttribute.cs @@ -9,13 +9,19 @@ namespace MewtocolNet.RegisterAttributes { [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] public class RegisterAttribute : Attribute { - internal int AssignedBitIndex = -1; - internal string MewAddress = null; + internal string TypeDef = null; - public RegisterAttribute(string mewAddress) { + /// + /// Builds automatic data transfer between the property below this and + /// the plc register + /// + /// The FP-Address (DT, DDT, R, X, Y..) + /// The type definition from the PLC (STRING[n], ARRAY [0..2] OF ...) + public RegisterAttribute(string mewAddress, string plcTypeDef = null) { MewAddress = mewAddress; + TypeDef = plcTypeDef; } diff --git a/MewtocolNet/RegisterBuilding/RBuild.cs b/MewtocolNet/RegisterBuilding/RBuild.cs index 28087e0..1ca9264 100644 --- a/MewtocolNet/RegisterBuilding/RBuild.cs +++ b/MewtocolNet/RegisterBuilding/RBuild.cs @@ -1,16 +1,20 @@ -using MewtocolNet.RegisterAttributes; +using MewtocolNet.PublicEnums; +using MewtocolNet.RegisterAttributes; using MewtocolNet.UnderlyingRegisters; using System; using System.Collections; using System.Collections.Generic; +using System.Data; using System.Data.Common; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Xml.Linq; +using static MewtocolNet.RegisterBuilding.RBuild; namespace MewtocolNet.RegisterBuilding { @@ -63,6 +67,9 @@ namespace MewtocolNet.RegisterBuilding { internal class SData { + internal RegisterBuildSource buildSource = RegisterBuildSource.Anonymous; + + internal bool wasAddressStringRangeBased; internal string originalParseStr; internal string name; internal RegisterType regType; @@ -77,9 +84,12 @@ namespace MewtocolNet.RegisterBuilding { internal int pollLevel = 1; + //only for building from attributes internal RegisterCollection regCollection; internal PropertyInfo boundProperty; + internal string typeDef; + } public class SBase { @@ -304,6 +314,7 @@ namespace MewtocolNet.RegisterBuilding { state = ParseResultState.Success, stepData = new SData { regType = RegisterType.DT_BYTE_RANGE, + wasAddressStringRangeBased = true, dotnetVarType = typeof(byte[]), memAddress = addresses[0], byteSize = (addresses[1] - addresses[0] + 1) * 2 @@ -330,6 +341,7 @@ namespace MewtocolNet.RegisterBuilding { if (!string.IsNullOrEmpty(name)) res.stepData.name = name; res.stepData.originalParseStr = plcAddrName; + res.stepData.buildSource = RegisterBuildSource.Manual; unfinishedList.Add(res.stepData); @@ -350,6 +362,16 @@ namespace MewtocolNet.RegisterBuilding { } + //internal use only, adds a type definition (for use when building from attibute) + internal SAddress AddressFromAttribute (string plcAddrName, string typeDef) { + + var built = Address(plcAddrName); + built.Data.typeDef = typeDef; + built.Data.buildSource = RegisterBuildSource.Attribute; + return built; + + } + #endregion #region Type determination stage @@ -358,21 +380,10 @@ namespace MewtocolNet.RegisterBuilding { /// /// Sets the register as a dotnet type for direct conversion - /// - /// Boolean R/X/Y registers - /// 16 bit signed integer - /// 16 bit un-signed integer - /// 16 bit word (2 bytes) - /// 32 bit signed integer - /// 32 bit un-signed integer - /// 32 bit word (4 bytes) - /// 32 bit floating point - /// 32 bit time from interpreted as - /// 16 or 32 bit enums, also supports flags - /// String of chars, the interface will automatically get the length - /// As an array of bytes - /// /// + /// + /// + /// public TempRegister AsType () { if (!typeof(T).IsAllowedPlcCastingType()) { @@ -387,9 +398,77 @@ namespace MewtocolNet.RegisterBuilding { } - /// + /// + /// Sets the register as a dotnet type for direct conversion + /// + /// + /// + /// + /// public TempRegister AsType (Type type) { + //was ranged syntax array build + if (Data.wasAddressStringRangeBased && type.IsArray && type.GetArrayRank() == 1) { + + //invoke generic AsTypeArray + MethodInfo method = typeof(SAddress).GetMethod(nameof(AsTypeArray)); + MethodInfo generic = method.MakeGenericMethod(type); + + var elementType = type.GetElementType(); + + if (!elementType.IsAllowedPlcCastingType()) { + + throw new NotSupportedException($"The dotnet type {elementType}, is not supported for PLC type casting"); + + } + + bool isExtensionTypeDT = typeof(MewtocolExtensionTypeDT).IsAssignableFrom(elementType); + bool isExtensionTypeDDT = typeof(MewtocolExtensionTypeDDT).IsAssignableFrom(elementType); + + int byteSizePerItem = 0; + if(elementType.Namespace.StartsWith("System")) { + byteSizePerItem = Marshal.SizeOf(elementType); + } else if (isExtensionTypeDT) { + byteSizePerItem = 2; + } else if (isExtensionTypeDDT) { + byteSizePerItem = 4; + } + + //check if it fits without remainder + if(Data.byteSize % byteSizePerItem != 0) { + throw new NotSupportedException($"The array element type {elementType} doesn't fit into the adress range"); + } + + return (TempRegister)generic.Invoke(this, new object[] { + //element count + new int[] { (int)((Data.byteSize / byteSizePerItem) / 2) } + }); + + } else if(Data.wasAddressStringRangeBased) { + + throw new NotSupportedException("DT range building is only allowed for 1 dimensional arrays"); + + } + + //for internal only, relay to AsType from string + if (Data.buildSource == RegisterBuildSource.Attribute) { + + if ((type.IsArray || type == typeof(string)) && Data.typeDef != null) { + + return AsType(Data.typeDef); + + } else if ((type.IsArray || type == typeof(string)) && Data.typeDef == null) { + + throw new NotSupportedException("Typedef parameter is needed for array or string types"); + + } else if (Data.typeDef != null) { + + throw new NotSupportedException("Can't use the typedef parameter on non array or string types"); + + } + + } + if (!type.IsAllowedPlcCastingType()) { throw new NotSupportedException($"The dotnet type {type}, is not supported for PLC type casting"); @@ -430,7 +509,7 @@ namespace MewtocolNet.RegisterBuilding { /// DWORD32 bit double word interpreted as /// /// - public TempRegister AsType(string type) { + public TempRegister AsType (string type) { var stringMatch = Regex.Match(type, @"STRING *\[(?[0-9]*)\]", RegexOptions.IgnoreCase); var arrayMatch = Regex.Match(type, @"ARRAY *\[(?[0-9]*)..(?[0-9]*)(?:\,(?[0-9]*)..(?[0-9]*))?(?:\,(?[0-9]*)..(?[0-9]*))?\] *OF {1,}(?.*)", RegexOptions.IgnoreCase); @@ -446,11 +525,46 @@ namespace MewtocolNet.RegisterBuilding { } else if (arrayMatch.Success) { - throw new NotSupportedException("Arrays are currently not supported"); + //invoke generic AsTypeArray + + string arrTypeString = arrayMatch.Groups["t"].Value; + + if (Enum.TryParse(arrTypeString, out var parsedArrType)) { + + var dotnetArrType = parsedArrType.GetDefaultDotnetType(); + var indices = new List(); + + for (int i = 1; i < 4; i++) { + + var arrStart = arrayMatch.Groups[$"S{i}"]?.Value; + var arrEnd = arrayMatch.Groups[$"E{i}"]?.Value; + if (string.IsNullOrEmpty(arrStart) || string.IsNullOrEmpty(arrEnd)) break; + + var arrStartInt = int.Parse(arrStart); + var arrEndInt = int.Parse(arrEnd); + + indices.Add(arrEndInt - arrStartInt + 1); + + } + + var arr = Array.CreateInstance(dotnetArrType, indices.ToArray()); + var arrType = arr.GetType(); + + MethodInfo method = typeof(SAddress).GetMethod(nameof(AsTypeArray)); + MethodInfo generic = method.MakeGenericMethod(arrType); + + return (TempRegister)generic.Invoke(this, new object[] { + indices.ToArray() + }); + + } else { + + throw new NotSupportedException($"The FP type '{arrTypeString}' was not recognized"); + } } else { - throw new NotSupportedException($"The mewtocol type '{type}' was not recognized"); + throw new NotSupportedException($"The FP type '{type}' was not recognized"); } @@ -459,38 +573,41 @@ namespace MewtocolNet.RegisterBuilding { } /// - /// Gets the data DT area as a + /// Sets the register as a (multidimensional) array targeting a PLC array /// - /// Bytes to assign - public TempRegister AsBytes (uint byteLength) { + /// + /// + /// + /// + /// Indicies for multi dimensional arrays, for normal arrays just one INT + /// + /// + /// One dimensional arrays:
+ /// ARRAY [0..2] OF INT = AsTypeArray<short[]>(3)
+ /// ARRAY [5..6] OF DWORD = AsTypeArray<DWord[]>(2)
+ ///
+ /// Multi dimensional arrays:
+ /// ARRAY [0..2, 0..3, 0..4] OF INT = AsTypeArray<short[,,]>(3,4,5)
+ /// ARRAY [5..6, 0..2] OF DWORD = AsTypeArray<DWord[,]>(2, 3)
+ ///
+ public TempRegister AsTypeArray (params int[] indicies) { - if (Data.regType != RegisterType.DT) { + if (!typeof(T).IsArray) + throw new NotSupportedException($"The type {typeof(T)} was no array"); - throw new NotSupportedException($"Cant use the {nameof(AsBytes)} converter on a non {nameof(RegisterType.DT)} register"); + var arrRank = typeof(T).GetArrayRank(); + var elBaseType = typeof(T).GetElementType(); - } + if (arrRank > 3) + throw new NotSupportedException($"4+ dimensional arrays are not supported"); - Data.byteSize = byteLength; - Data.dotnetVarType = typeof(byte[]); + if (!elBaseType.IsAllowedPlcCastingType()) + throw new NotSupportedException($"The dotnet type {typeof(T)}, is not supported for PLC array type casting"); - return new TempRegister(Data, builder); + if (arrRank != indicies.Length) + throw new NotSupportedException($"All dimensional array indicies must be set"); - } - - /// - /// Gets the data DT area as a - /// - /// Number of bits to read - public TempRegister AsBits (ushort bitCount = 16) { - - if (Data.regType != RegisterType.DT) { - - throw new NotSupportedException($"Cant use the {nameof(AsBits)} converter on a non {nameof(RegisterType.DT)} register"); - - } - - Data.bitSize = bitCount; - Data.dotnetVarType = typeof(BitArray); + Data.dotnetVarType = typeof(T); return new TempRegister(Data, builder); diff --git a/MewtocolNet/RegisterBuilding/RegisterAssembler.cs b/MewtocolNet/RegisterBuilding/RegisterAssembler.cs index 8accdce..24da53f 100644 --- a/MewtocolNet/RegisterBuilding/RegisterAssembler.cs +++ b/MewtocolNet/RegisterBuilding/RegisterAssembler.cs @@ -21,16 +21,16 @@ namespace MewtocolNet.RegisterBuilding { } - internal List AssembleAll (RBuild rBuildData) { + internal List AssembleAll (RBuild rBuildData, bool flagAutoGenerated = false) { List generatedInstances = new List(); foreach (var data in rBuildData.unfinishedList) { var generatedInstance = Assemble(data); - + generatedInstance.autoGenerated = flagAutoGenerated; generatedInstances.Add(generatedInstance); - + } return generatedInstances; @@ -44,6 +44,13 @@ namespace MewtocolNet.RegisterBuilding { BaseRegister generatedInstance = null; + if(data.dotnetVarType.IsArray) { + + Console.WriteLine(); + return new ArrayRegister(0, 0); + + } + if (data.dotnetVarType.IsEnum) { //------------------------------------------- diff --git a/MewtocolNet/Registers/BaseRegister.cs b/MewtocolNet/Registers/BaseRegister.cs index 853454a..fc80712 100644 --- a/MewtocolNet/Registers/BaseRegister.cs +++ b/MewtocolNet/Registers/BaseRegister.cs @@ -24,6 +24,8 @@ namespace MewtocolNet.Registers { internal Type underlyingSystemType; internal IMemoryArea underlyingMemory; + internal bool autoGenerated; + internal object lastValue = null; internal string name; internal uint memoryAddress; @@ -128,6 +130,8 @@ namespace MewtocolNet.Registers { public virtual uint GetRegisterAddressLen() => throw new NotImplementedException(); + public virtual uint GetRegisterAddressEnd() => MemoryAddress + GetRegisterAddressLen() - 1; + public string GetRegisterWordRangeString() => $"{GetMewName()} - {MemoryAddress + GetRegisterAddressLen() - 1}"; #endregion @@ -156,6 +160,7 @@ namespace MewtocolNet.Registers { return this.MemoryAddress == toCompare.MemoryAddress && this.RegisterType == toCompare.RegisterType && + this.underlyingSystemType == toCompare.underlyingSystemType && this.GetRegisterAddressLen() == toCompare.GetRegisterAddressLen() && this.GetSpecialAddress() == toCompare.GetSpecialAddress(); @@ -199,20 +204,34 @@ namespace MewtocolNet.Registers { public virtual string Explain () { StringBuilder sb = new StringBuilder(); - sb.AppendLine($"MewName: {GetMewName()}"); - sb.AppendLine($"Name: {Name ?? "Not named"}"); - sb.AppendLine($"Value: {GetValueString()}"); - sb.AppendLine($"Perf. Reads: {successfulReads}, Writes: {successfulWrites}"); - sb.AppendLine($"Register Type: {RegisterType}"); - sb.AppendLine($"Underlying System Type: {underlyingSystemType}"); - sb.AppendLine($"Address: {GetRegisterWordRangeString()}"); - if (this is StringRegister sr) sb.AppendLine($"Reserved: {sr.ReservedSize}, Used: {sr.UsedSize}"); - if (GetSpecialAddress() != null) sb.AppendLine($"SPAddress: {GetSpecialAddress():X1}"); - if (GetType().IsGenericType) sb.AppendLine($"Type: NumberRegister<{GetType().GenericTypeArguments[0]}>"); - else sb.AppendLine($"Type: {GetType()}"); - if (containedCollection != null) sb.AppendLine($"In collection: {containedCollection.GetType()}"); - if (boundProperties != null && boundProperties.Count > 0) sb.AppendLine($"Bound props: {string.Join(", ", boundProperties)}"); - else sb.AppendLine("No bound properties"); + sb.Append($"Address: {GetRegisterWordRangeString()}\n"); + + if (GetType().IsGenericType) + sb.Append($"Type: {RegisterType}, NumberRegister<{GetType().GenericTypeArguments[0]}>\n"); + else + sb.AppendLine($"Type: {RegisterType}, {GetType().Name}\n"); + + sb.Append($"Name: {Name ?? "Not named"}\n"); + + if(Value != null) + sb.Append($"Value: {GetValueString()}\n"); + + sb.Append($"Reads: {successfulReads}, Writes: {successfulWrites}\n"); + sb.Append($"Underlying System Type: {underlyingSystemType}\n"); + + if (this is StringRegister sr) + sb.Append($"Reserved: {sr.ReservedSize}, Used: {sr.UsedSize}\n"); + + if (GetSpecialAddress() != null) + sb.Append($"SPAddress: {GetSpecialAddress():X1}\n"); + + if (containedCollection != null) + sb.Append($"In collection: {containedCollection.GetType()}\n"); + + if (boundProperties != null && boundProperties.Count > 0) + sb.Append($"Bound props: \n\t{string.Join(",\n\t", boundProperties)}"); + else + sb.Append("No bound properties"); return sb.ToString(); diff --git a/MewtocolNet/Registers/NumberRegister.cs b/MewtocolNet/Registers/NumberRegister.cs index 924ce39..24354c7 100644 --- a/MewtocolNet/Registers/NumberRegister.cs +++ b/MewtocolNet/Registers/NumberRegister.cs @@ -154,6 +154,12 @@ namespace MewtocolNet.Registers { var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2, false); if (res == null) return null; + var matchingReg = attachedInterface.memoryManager.GetAllRegisters() + .FirstOrDefault(x => x.IsSameAddressAndType(this)); + + if (matchingReg != null) + matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, res); + return SetValueFromBytes(res); } diff --git a/MewtocolNet/UnderlyingRegisters/DTArea.cs b/MewtocolNet/UnderlyingRegisters/DTArea.cs index adf4a0b..b3a24dc 100644 --- a/MewtocolNet/UnderlyingRegisters/DTArea.cs +++ b/MewtocolNet/UnderlyingRegisters/DTArea.cs @@ -17,9 +17,10 @@ namespace MewtocolNet.UnderlyingRegisters { internal byte[] underlyingBytes = new byte[2]; - internal List linkedRegisters = new List(); - - internal Dictionary> crossRegisterBindings = new Dictionary>(); + /// + /// List of register link groups that are managed in this memory area + /// + internal List managedRegisters = new List(); public ulong AddressStart => addressStart; public ulong AddressEnd => addressEnd; @@ -51,7 +52,7 @@ namespace MewtocolNet.UnderlyingRegisters { public void UpdateAreaRegisterValues() { - foreach (var register in this.linkedRegisters) { + foreach (var register in this.managedRegisters.SelectMany(x => x.Linked)) { var regStart = register.MemoryAddress; var addLen = (int)register.GetRegisterAddressLen(); @@ -120,7 +121,8 @@ namespace MewtocolNet.UnderlyingRegisters { private async Task CheckDynamicallySizedRegistersAsync () { //calibrating at runtime sized registers - var uncalibratedStringRegisters = linkedRegisters + var uncalibratedStringRegisters = managedRegisters + .SelectMany(x => x.Linked) .Where(x => x is StringRegister sreg && !sreg.isCalibratedFromPlc) .Cast() .ToList(); @@ -129,7 +131,7 @@ namespace MewtocolNet.UnderlyingRegisters { await register.CalibrateFromPLC(); if (uncalibratedStringRegisters.Count > 0) - mewInterface.memoryManager.MergeAndSizeDataAreas(); + mewInterface.memoryManager.LinkAndMergeRegisters(); } diff --git a/MewtocolNet/UnderlyingRegisters/LinkedRegisterGroup.cs b/MewtocolNet/UnderlyingRegisters/LinkedRegisterGroup.cs new file mode 100644 index 0000000..28110fa --- /dev/null +++ b/MewtocolNet/UnderlyingRegisters/LinkedRegisterGroup.cs @@ -0,0 +1,18 @@ +using MewtocolNet.Registers; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MewtocolNet.UnderlyingRegisters { + + internal class LinkedRegisterGroup { + + internal uint AddressStart; + + internal uint AddressEnd; + + internal List Linked = new List(); + + } + +} diff --git a/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs b/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs index ab7edcf..b654888 100644 --- a/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs +++ b/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs @@ -47,12 +47,12 @@ namespace MewtocolNet.UnderlyingRegisters { } - internal void LinkRegisters (List registers = null) { + internal void LinkAndMergeRegisters (List registers = null) { //for self calling if (registers == null) registers = GetAllRegisters().ToList(); - //pre combine + //pre combine per address var groupedByAdd = registers .GroupBy(x => new { x.MemoryAddress, @@ -60,39 +60,20 @@ namespace MewtocolNet.UnderlyingRegisters { spadd = x.GetSpecialAddress(), }); - var filteredRegisters = new List(); - var propertyLookupTable = new Dictionary(); - + //poll level merging foreach (var addressGroup in groupedByAdd) { - var ordered = addressGroup.OrderBy(x => x.pollLevel); - var highestPollLevel = ordered.Max(x => x.pollLevel); + //determine highest poll level for same addresses + var highestPollLevel = addressGroup.Max(x => x.pollLevel); - var distinctByUnderlyingType = - ordered.GroupBy(x => x.underlyingSystemType).ToList(); - - foreach (var underlyingTypeGroup in distinctByUnderlyingType) { - - foreach (var register in underlyingTypeGroup) { - - register.pollLevel = highestPollLevel; - - var alreadyAdded = filteredRegisters - .FirstOrDefault(x => x.underlyingSystemType == register.underlyingSystemType); - - if(alreadyAdded == null) { - filteredRegisters.Add(register); - } else { - alreadyAdded.WithBoundProperties(register.boundProperties); - } - - } - - } + //apply poll level to all registers in same group + foreach (var reg in addressGroup) + reg.pollLevel = highestPollLevel; } - foreach (var reg in filteredRegisters) { + //insert into area + foreach (var reg in registers) { TestPollLevelExistence(reg); @@ -111,6 +92,18 @@ namespace MewtocolNet.UnderlyingRegisters { } + //order + + foreach (var lvl in pollLevels) { + + foreach (var area in lvl.dataAreas) { + + area.managedRegisters = area.managedRegisters.OrderBy(x => x.AddressStart).ToList(); + + } + + } + } private void TestPollLevelExistence (BaseRegister reg) { @@ -271,17 +264,27 @@ namespace MewtocolNet.UnderlyingRegisters { insertReg.name = $"auto_{Guid.NewGuid().ToString("N")}"; } - Console.WriteLine($"Adding linked register: {insertReg}"); - targetArea.linkedRegisters.Add(insertReg); - return; + var existinglinkedGroup = targetArea.managedRegisters + .FirstOrDefault(x => x.AddressStart == insertReg.MemoryAddress && + x.AddressEnd == insertReg.GetRegisterAddressEnd()); - } + if (existinglinkedGroup == null) { + // make a new linked group + existinglinkedGroup = new LinkedRegisterGroup { + AddressStart = insertReg.MemoryAddress, + AddressEnd = insertReg.GetRegisterAddressEnd(), + }; + targetArea.managedRegisters.Add(existinglinkedGroup); + } - internal void MergeAndSizeDataAreas () { + //check if the linked group has duplicate type registers - //merge gaps that the algorithm didn't catch be rerunning the register attachment - - LinkRegisters(); + var dupedTypeReg = existinglinkedGroup.Linked.FirstOrDefault(x => x.IsSameAddressAndType(insertReg)); + if (dupedTypeReg != null) { + dupedTypeReg.WithBoundProperties(insertReg.boundProperties); + } else { + existinglinkedGroup.Linked.Add(insertReg); + } } @@ -350,75 +353,67 @@ namespace MewtocolNet.UnderlyingRegisters { foreach (var pollLevel in pollLevels) { - sb.AppendLine($"==== Poll lvl {pollLevel.level} ===="); + sb.AppendLine($"\n> ==== Poll lvl {pollLevel.level} ===="); sb.AppendLine(); if (pollLevelConfigs[pollLevel.level].delay != null) { - sb.AppendLine($"Poll each {pollLevelConfigs[pollLevel.level].delay?.TotalMilliseconds}ms"); + sb.AppendLine($"> Poll each {pollLevelConfigs[pollLevel.level].delay?.TotalMilliseconds}ms"); } else { - sb.AppendLine($"Poll every {pollLevelConfigs[pollLevel.level].skipNth} iterations"); + sb.AppendLine($"> Poll every {pollLevelConfigs[pollLevel.level].skipNth} iterations"); } - sb.AppendLine($"Level read time: {pollLevel.lastReadTimeMs}ms"); - sb.AppendLine($"Optimization distance: {maxOptimizationDistance}"); + sb.AppendLine($"> Level read time: {pollLevel.lastReadTimeMs}ms"); + sb.AppendLine($"> Optimization distance: {maxOptimizationDistance}"); sb.AppendLine(); - sb.AppendLine($"---- DT Areas: ----"); - foreach (var area in pollLevel.dataAreas) { + var areaHeader = $"AREA => {area} = {area.underlyingBytes.Length} bytes"; + sb.AppendLine($"* {new string('-', areaHeader.Length)}*"); + sb.AppendLine($"* {areaHeader}"); + sb.AppendLine($"* {new string('-', areaHeader.Length)}*"); + sb.AppendLine("*"); + sb.AppendLine($"* {(string.Join("\n* ", area.underlyingBytes.ToHexString(" ").SplitInParts(3 * 8)))}"); + sb.AppendLine("*"); + + int seperatorLen = 50; + + LinkedRegisterGroup prevGroup = null; + + foreach (var linkedG in area.managedRegisters) { + + if (prevGroup != null && (linkedG.AddressStart - prevGroup.AddressEnd - 1 > 0)) { + + var dist = linkedG.AddressStart - prevGroup.AddressEnd - 1; + + sb.AppendLine($"* {new string('=', seperatorLen + 3)}"); + sb.AppendLine($"* Byte spacer: {dist} Words"); + sb.AppendLine($"* {new string('=', seperatorLen + 3)}"); + + } + + sb.AppendLine($"* {new string('_', seperatorLen + 3)}"); + sb.AppendLine($"* || Linked group {linkedG.AddressStart} - {linkedG.AddressEnd}"); + sb.AppendLine($"* || {new string('=', seperatorLen)}"); + + foreach (var reg in linkedG.Linked) { + + string explained = reg.Explain(); + + sb.AppendLine($"* || {explained.Replace("\n", "\n* || ")}"); + + if (linkedG.Linked.Count > 1) { + sb.AppendLine($"* || {new string('-', seperatorLen)}"); + } + + } + + sb.AppendLine($"* {new string('=', seperatorLen + 3)}"); + + prevGroup = linkedG; + + } + sb.AppendLine(); - sb.AppendLine($"=> {area} = {area.underlyingBytes.Length} bytes"); - sb.AppendLine(); - sb.AppendLine(string.Join("\n", area.underlyingBytes.ToHexString(" ").SplitInParts(3 * 8))); - sb.AppendLine(); - - foreach (var reg in area.linkedRegisters) { - - sb.AppendLine($"{reg.Explain()}"); - - } - - } - - sb.AppendLine($"---- WR X Area ----"); - - foreach (var area in pollLevel.externalRelayInAreas) { - - sb.AppendLine(area.ToString()); - - foreach (var reg in area.linkedRegisters) { - - sb.AppendLine($"{reg.Explain()}"); - - } - - } - - sb.AppendLine($"---- WR Y Area ---"); - - foreach (var area in pollLevel.externalRelayOutAreas) { - - sb.AppendLine(area.ToString()); - - foreach (var reg in area.linkedRegisters) { - - sb.AppendLine($"{reg.Explain()}"); - - } - - } - - sb.AppendLine($"---- WR R Area ----"); - - foreach (var area in pollLevel.internalRelayAreas) { - - sb.AppendLine(area.ToString()); - - foreach (var reg in area.linkedRegisters) { - - sb.AppendLine($"{reg.Explain()}"); - - } } @@ -434,7 +429,8 @@ namespace MewtocolNet.UnderlyingRegisters { foreach (var lvl in pollLevels) { - registers.AddRange(lvl.dataAreas.SelectMany(x => x.linkedRegisters)); + registers.AddRange(lvl.dataAreas.SelectMany(x => x.managedRegisters).SelectMany(x => x.Linked)); + registers.AddRange(lvl.internalRelayAreas.SelectMany(x => x.linkedRegisters)); registers.AddRange(lvl.externalRelayInAreas.SelectMany(x => x.linkedRegisters)); registers.AddRange(lvl.externalRelayOutAreas.SelectMany(x => x.linkedRegisters));