From daecd73a6d6afe06171fbee01bac18c6e3b45394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Wei=C3=9F?= <72068105+Sandoun@users.noreply.github.com> Date: Thu, 13 Jul 2023 00:34:06 +0200 Subject: [PATCH] Add more xml doc - fixed dynamically sized registers auto dt area resize - add more anonymous register r/w - add more register builder type casting methods --- MewtocolNet/MewtocolInterface.cs | 4 +- .../MewtocolInterfaceRegisterHandling.cs | 2 +- MewtocolNet/MewtocolInterfaceRequests.cs | 106 +----- MewtocolNet/RegisterBuilding/RBuild.cs | 311 ++++++++++-------- .../RegisterBuilding/RegBuilderExtensions.cs | 2 +- .../RegisterBuilding/RegisterAssembler.cs | 138 ++++++++ MewtocolNet/Registers/BaseRegister.cs | 3 +- MewtocolNet/Registers/BoolRegister.cs | 68 ++-- MewtocolNet/Registers/BytesRegister.cs | 73 ++-- MewtocolNet/Registers/NumberRegister.cs | 39 +-- MewtocolNet/Registers/StringRegister.cs | 106 +++--- MewtocolNet/TypeConversion/Conversions.cs | 135 ++++---- MewtocolNet/UnderlyingRegisters/DTArea.cs | 18 + .../UnderlyingRegisters/MemoryAreaManager.cs | 16 + 14 files changed, 595 insertions(+), 426 deletions(-) create mode 100644 MewtocolNet/RegisterBuilding/RegisterAssembler.cs diff --git a/MewtocolNet/MewtocolInterface.cs b/MewtocolNet/MewtocolInterface.cs index be48263..147ee10 100644 --- a/MewtocolNet/MewtocolInterface.cs +++ b/MewtocolNet/MewtocolInterface.cs @@ -175,7 +175,7 @@ namespace MewtocolNet { /// public async Task DisconnectAsync () { - await pollCycleTask; + if(pollCycleTask != null) await pollCycleTask; Disconnect(); @@ -186,7 +186,7 @@ namespace MewtocolNet { if (!IsConnected) return; - if (!pollCycleTask.IsCompleted) pollCycleTask.Wait(); + if (pollCycleTask != null && !pollCycleTask.IsCompleted) pollCycleTask.Wait(); OnMajorSocketExceptionWhileConnected(); diff --git a/MewtocolNet/MewtocolInterfaceRegisterHandling.cs b/MewtocolNet/MewtocolInterfaceRegisterHandling.cs index 3a37c65..1c80a4f 100644 --- a/MewtocolNet/MewtocolInterfaceRegisterHandling.cs +++ b/MewtocolNet/MewtocolInterfaceRegisterHandling.cs @@ -255,7 +255,7 @@ namespace MewtocolNet { } var assembler = new RegisterAssembler(this); - var registers = assembler.Assemble(regBuild); + var registers = assembler.AssembleAll(regBuild); AddRegisters(registers.ToArray()); } diff --git a/MewtocolNet/MewtocolInterfaceRequests.cs b/MewtocolNet/MewtocolInterfaceRequests.cs index 4d562a8..a44fd16 100644 --- a/MewtocolNet/MewtocolInterfaceRequests.cs +++ b/MewtocolNet/MewtocolInterfaceRequests.cs @@ -94,9 +94,16 @@ namespace MewtocolNet { /// /// start address of the array /// /// - public async Task WriteByteRange (int start, byte[] byteArr) { + public async Task WriteByteRange (int start, byte[] byteArr, bool flipBytes = false) { + + string byteString; + + if(flipBytes) { + byteString = byteArr.BigToMixedEndian().ToHexString(); + } else { + byteString = byteArr.ToHexString(); + } - string byteString = byteArr.BigToMixedEndian().ToHexString(); var wordLength = byteArr.Length / 2; if (byteArr.Length % 2 != 0) wordLength++; @@ -120,7 +127,7 @@ namespace MewtocolNet { /// 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 ReadByteRangeNonBlocking (int start, int count, bool flipBytes = true, Action onProgress = null) { + public async Task ReadByteRangeNonBlocking (int start, int count, bool flipBytes = false, Action onProgress = null) { var byteList = new List(); @@ -167,99 +174,6 @@ namespace MewtocolNet { #endregion - #region Raw register reading / writing - - [Obsolete] - internal async Task ReadRawRegisterAsync (IRegisterInternal _toRead) { - - var toreadType = _toRead.GetType(); - - //returns a byte array 1 long and with the byte beeing 0 or 1 - if (toreadType == typeof(BoolRegister)) { - - string requeststring = $"%{GetStationNumber()}#RCS{_toRead.BuildMewtocolQuery()}"; - var result = await SendCommandAsync(requeststring); - if (!result.Success) return null; - - var resultBool = result.Response.ParseRCSingleBit(); - if (resultBool != null) { - - return resultBool.Value ? new byte[] { 1 } : new byte[] { 0 }; - - } - - } - - //returns a byte array 2 bytes or 4 bytes long depending on the data size - if (toreadType.IsGenericType && _toRead.GetType().GetGenericTypeDefinition() == typeof(NumberRegister<>)) { - - string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolQuery()}"; - var result = await SendCommandAsync(requeststring); - if (!result.Success) return null; - - if(_toRead.RegisterType == RegisterType.DT) { - - return result.Response.ParseDTByteString(4).HexStringToByteArray(); - - } else { - - return result.Response.ParseDTByteString(8).HexStringToByteArray(); - - } - - } - - //returns a byte array with variable size - if (toreadType == typeof(BytesRegister)) { - - string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolQuery()}"; - var result = await SendCommandAsync(requeststring); - if (!result.Success) return null; - - var resBytes = result.Response.ParseDTRawStringAsBytes(); - - return resBytes; - - } - - if (toreadType == typeof(StringRegister)) { - - string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolQuery()}"; - var result = await SendCommandAsync(requeststring); - if (!result.Success) return null; - - var resBytes = result.Response.ParseDTRawStringAsBytes(); - - return resBytes; - - } - - throw new Exception($"Failed to load the byte data for: {_toRead}"); - - } - - [Obsolete] - internal async Task WriteRawRegisterAsync (IRegisterInternal _toWrite, byte[] data) { - - var toWriteType = _toWrite.GetType(); - - //returns a byte array 1 long and with the byte beeing 0 or 1 - if (toWriteType == typeof(BoolRegister)) { - - string reqStr = $"%{GetStationNumber()}#WCS{_toWrite.BuildMewtocolQuery()}{(data[0] == 1 ? "1" : "0")}"; - var res = await SendCommandAsync(reqStr); - return res.Success; - - } - - string requeststring = $"%{GetStationNumber()}#WD{_toWrite.BuildMewtocolQuery()}{data.ToHexString()}"; - var result = await SendCommandAsync(requeststring); - return result.Success; - - } - - #endregion - #region Helpers internal string GetStationNumber() { diff --git a/MewtocolNet/RegisterBuilding/RBuild.cs b/MewtocolNet/RegisterBuilding/RBuild.cs index 3bd82b7..7547560 100644 --- a/MewtocolNet/RegisterBuilding/RBuild.cs +++ b/MewtocolNet/RegisterBuilding/RBuild.cs @@ -1,6 +1,4 @@ -using MewtocolNet.Exceptions; -using MewtocolNet.RegisterAttributes; -using MewtocolNet.Registers; +using MewtocolNet.RegisterAttributes; using MewtocolNet.UnderlyingRegisters; using System; using System.Collections; @@ -10,7 +8,6 @@ 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; @@ -76,6 +73,7 @@ namespace MewtocolNet.RegisterBuilding { //optional internal uint? byteSize; internal uint? bitSize; + internal int? stringSize; internal int pollLevel = 1; @@ -88,12 +86,15 @@ namespace MewtocolNet.RegisterBuilding { public SBase() { } - internal SBase(SData data) { + internal SBase(SData data, RBuild bldr) { Data = data; + builder = bldr; } internal SData Data { get; set; } + internal RBuild builder; + } internal struct ParseResult { @@ -311,6 +312,13 @@ namespace MewtocolNet.RegisterBuilding { } + /// + /// Starts the register builder for a new mewtocol address
+ /// Examples: + /// Address("DT100") | Address("R10A") | Address("DDT50", "MyRegisterName") + ///
+ /// Address name formatted as FP-Address like in FP-Winpro + /// Custom name for the register to referr to it later public SAddress Address (string plcAddrName, string name = null) { foreach (var method in parseMethods) { @@ -326,7 +334,8 @@ namespace MewtocolNet.RegisterBuilding { unfinishedList.Add(res.stepData); return new SAddress { - Data = res.stepData + Data = res.stepData, + builder = this, }; } else if(res.state == ParseResultState.FailedHard) { @@ -347,6 +356,22 @@ namespace MewtocolNet.RegisterBuilding { public class SAddress : SBase { + /// + /// Sets the register as a dotnet type for direct conversion + /// + /// Boolean R/X/Y registers + /// 16 bit signed integer + /// 16 bit un-signed integer + /// 32 bit signed integer + /// 32 bit un-signed integer + /// 32 bit floating point + /// 32 bit time from interpreted as + /// 16 or 32 bit enums + /// String of chars, the interface will automatically get the length + /// As an array of bits + /// As an array of bytes + /// + /// public TempRegister AsType () { if (!typeof(T).IsAllowedPlcCastingType()) { @@ -357,10 +382,11 @@ namespace MewtocolNet.RegisterBuilding { Data.dotnetVarType = typeof(T); - return new TempRegister(Data); + return new TempRegister(Data, builder); } + /// public TempRegister AsType (Type type) { if (!type.IsAllowedPlcCastingType()) { @@ -371,10 +397,70 @@ namespace MewtocolNet.RegisterBuilding { Data.dotnetVarType = type; - return new TempRegister(Data); + return new TempRegister(Data, builder); } + /// + /// Sets the register type as a predefined + /// + public TempRegister AsType (PlcVarType type) { + + Data.dotnetVarType = type.GetDefaultDotnetType(); + + return new TempRegister(Data, builder); + + } + + /// + /// Sets the register type from the plc type string
+ /// Supported types: + /// + /// BOOLBoolean R/X/Y registers + /// INT16 bit signed integer + /// UINT16 bit un-signed integer + /// DINT32 bit signed integer + /// UDINT32 bit un-signed integer + /// REAL32 bit floating point + /// TIME32 bit time interpreted as + /// STRINGString of chars, the interface will automatically get the length + /// STRING[N]String of chars, pre capped to N + /// WORD16 bit word interpreted as + /// DWORD32 bit double word interpreted as + /// + ///
+ 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); + + if (Enum.TryParse(type, out var parsed)) { + + Data.dotnetVarType = parsed.GetDefaultDotnetType(); + + } else if (stringMatch.Success) { + + Data.dotnetVarType = typeof(string); + Data.stringSize = int.Parse(stringMatch.Groups["len"].Value); + + } else if (arrayMatch.Success) { + + throw new NotSupportedException("Arrays are currently not supported"); + + } else { + + throw new NotSupportedException($"The mewtocol type '{type}' was not recognized"); + + } + + return new TempRegister(Data, builder); + + } + + /// + /// Gets the data DT area as a + /// + /// Bytes to assign public TempRegister AsBytes (uint byteLength) { if (Data.regType != RegisterType.DT) { @@ -386,10 +472,14 @@ namespace MewtocolNet.RegisterBuilding { Data.byteSize = byteLength; Data.dotnetVarType = typeof(byte[]); - return new TempRegister(Data); + return new TempRegister(Data, builder); } + /// + /// Gets the data DT area as a + /// + /// Number of bits to read public TempRegister AsBits (ushort bitCount = 16) { if (Data.regType != RegisterType.DT) { @@ -401,10 +491,13 @@ namespace MewtocolNet.RegisterBuilding { Data.bitSize = bitCount; Data.dotnetVarType = typeof(BitArray); - return new TempRegister(Data); + return new TempRegister(Data, builder); } + /// + /// Automatically finds the best type for the register + /// public TempRegister AutoType() { switch (Data.regType) { @@ -424,7 +517,7 @@ namespace MewtocolNet.RegisterBuilding { break; } - return new TempRegister(Data); + return new TempRegister(Data, builder); } @@ -436,8 +529,11 @@ namespace MewtocolNet.RegisterBuilding { public class TempRegister : SBase { - internal TempRegister(SData data) : base(data) {} + internal TempRegister(SData data, RBuild bldr) : base(data, bldr) {} + /// + /// Sets the poll level of the register + /// public TempRegister PollLevel (int level) { Data.pollLevel = level; @@ -446,14 +542,28 @@ namespace MewtocolNet.RegisterBuilding { } - public async Task WriteToAsync (T value) => throw new NotImplementedException(); + /// + /// Writes data to the register and bypasses the memory manager
+ ///
+ /// The value to write + /// True if success + public async Task WriteToAsync (T value) => await builder.WriteAnonymousAsync(this, value); + + /// + /// Reads data from the register and bypasses the memory manager
+ ///
+ /// The value read or null if failed + public async Task ReadFromAsync () => await builder.ReadAnonymousAsync(this); } public class TempRegister : SBase { - internal TempRegister(SData data) : base(data) { } + internal TempRegister(SData data, RBuild bldr) : base(data, bldr) { } + /// + /// Sets the poll level of the register + /// public TempRegister PollLevel (int level) { Data.pollLevel = level; @@ -462,7 +572,20 @@ namespace MewtocolNet.RegisterBuilding { } - internal TempRegister RegCollection (RegisterCollection col) { + /// + /// Writes data to the register and bypasses the memory manager
+ ///
+ /// The value to write + /// True if success + public async Task WriteToAsync(object value) => await builder.WriteAnonymousAsync(this, value); + + /// + /// Reads data from the register and bypasses the memory manager
+ ///
+ /// The value read or null if failed + public async Task ReadFromAsync () => await builder.ReadAnonymousAsync(this); + + internal TempRegister RegCollection(RegisterCollection col) { Data.regCollection = col; @@ -470,7 +593,7 @@ namespace MewtocolNet.RegisterBuilding { } - internal TempRegister BoundProp (PropertyInfo prop) { + internal TempRegister BoundProp(PropertyInfo prop) { Data.boundProperty = prop; @@ -478,7 +601,41 @@ namespace MewtocolNet.RegisterBuilding { } - public async Task WriteToAsync (object value) => throw new NotImplementedException(); + } + + #endregion + + #region Anonymous read/write bindings + + private async Task WriteAnonymousAsync (TempRegister reg, object value) { + + var assembler = new RegisterAssembler(attachedPLC); + var tempRegister = assembler.Assemble(reg.Data); + return await tempRegister.WriteToAnonymousAsync(value); + + } + + private async Task WriteAnonymousAsync(TempRegister reg, object value) { + + var assembler = new RegisterAssembler(attachedPLC); + var tempRegister = assembler.Assemble(reg.Data); + return await tempRegister.WriteToAnonymousAsync(value); + + } + + private async Task ReadAnonymousAsync (TempRegister reg) { + + var assembler = new RegisterAssembler(attachedPLC); + var tempRegister = assembler.Assemble(reg.Data); + return await tempRegister.ReadFromAnonymousAsync(); + + } + + private async Task ReadAnonymousAsync(TempRegister reg) { + + var assembler = new RegisterAssembler(attachedPLC); + var tempRegister = assembler.Assemble(reg.Data); + return (T)await tempRegister.ReadFromAnonymousAsync(); } @@ -486,124 +643,4 @@ namespace MewtocolNet.RegisterBuilding { } - internal class RegisterAssembler { - - internal RegisterCollection collectionTarget; - - internal MewtocolInterface onInterface; - - internal RegisterAssembler (MewtocolInterface interf) { - - onInterface = interf; - - } - - internal List Assemble (RBuild rBuildData) { - - List generatedInstances = new List(); - - foreach (var data in rBuildData.unfinishedList) { - - //parse all others where the type is known - Type registerClassType = data.dotnetVarType.GetDefaultRegisterHoldingType(); - - BaseRegister generatedInstance = null; - - if (data.dotnetVarType.IsEnum) { - - //------------------------------------------- - //as numeric register with enum target - - var underlying = Enum.GetUnderlyingType(data.dotnetVarType); - 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(data.dotnetVarType); - ConstructorInfo constr = myParameterizedSomeClass.GetConstructor(new Type[] { typeof(uint), typeof(string) }); - - var parameters = new object[] { data.memAddress, data.name }; - var instance = (BaseRegister)constr.Invoke(parameters); - - generatedInstance = instance; - - } else if (registerClassType.IsGenericType) { - - //------------------------------------------- - //as numeric register - - //create a new bregister instance - var flags = BindingFlags.Public | BindingFlags.Instance; - - //int _adress, Type _enumType = null, string _name = null - var parameters = new object[] { data.memAddress, data.name }; - var instance = (BaseRegister)Activator.CreateInstance(registerClassType, flags, null, parameters, null); - instance.pollLevel = data.pollLevel; - - generatedInstance = instance; - - } else if (registerClassType == typeof(BytesRegister) && data.byteSize != null) { - - //------------------------------------------- - //as byte range register - - BytesRegister instance = new BytesRegister(data.memAddress, (uint)data.byteSize, data.name); - - generatedInstance = instance; - - } else if (registerClassType == typeof(BytesRegister) && data.bitSize != null) { - - //------------------------------------------- - //as bit range register - - BytesRegister instance = new BytesRegister(data.memAddress, (ushort)data.bitSize, data.name); - - generatedInstance = instance; - - } else if (registerClassType == typeof(StringRegister)) { - - //------------------------------------------- - //as byte range register - var instance = (BaseRegister)new StringRegister(data.memAddress, data.name); - - generatedInstance = instance; - - } else if (data.regType.IsBoolean()) { - - //------------------------------------------- - //as boolean register - - var io = (IOType)(int)data.regType; - var spAddr = data.specialAddress; - var areaAddr = data.memAddress; - - var instance = new BoolRegister(io, spAddr, areaAddr, data.name); - - generatedInstance = instance; - - } - - //finalize set for every - - if(generatedInstance == null) - throw new MewtocolException("Failed to build register"); - - if (collectionTarget != null) - generatedInstance.WithRegisterCollection(collectionTarget); - - generatedInstance.attachedInterface = onInterface; - - generatedInstance.pollLevel = data.pollLevel; - - generatedInstances.Add(generatedInstance); - - } - - return generatedInstances; - - } - - } - } diff --git a/MewtocolNet/RegisterBuilding/RegBuilderExtensions.cs b/MewtocolNet/RegisterBuilding/RegBuilderExtensions.cs index 03267e0..7c32095 100644 --- a/MewtocolNet/RegisterBuilding/RegBuilderExtensions.cs +++ b/MewtocolNet/RegisterBuilding/RegBuilderExtensions.cs @@ -12,7 +12,7 @@ namespace MewtocolNet.RegisterBuilding { builder.Invoke(regBuilder); var assembler = new RegisterAssembler((MewtocolInterface)plc); - var registers = assembler.Assemble(regBuilder); + var registers = assembler.AssembleAll(regBuilder); var interf = (MewtocolInterface)plc; diff --git a/MewtocolNet/RegisterBuilding/RegisterAssembler.cs b/MewtocolNet/RegisterBuilding/RegisterAssembler.cs new file mode 100644 index 0000000..86d8e4b --- /dev/null +++ b/MewtocolNet/RegisterBuilding/RegisterAssembler.cs @@ -0,0 +1,138 @@ +using MewtocolNet.Exceptions; +using MewtocolNet.RegisterAttributes; +using MewtocolNet.Registers; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; +using static MewtocolNet.RegisterBuilding.RBuild; + +namespace MewtocolNet.RegisterBuilding { + internal class RegisterAssembler { + + internal RegisterCollection collectionTarget; + + internal MewtocolInterface onInterface; + + internal RegisterAssembler (MewtocolInterface interf) { + + onInterface = interf; + + } + + internal List AssembleAll (RBuild rBuildData) { + + List generatedInstances = new List(); + + foreach (var data in rBuildData.unfinishedList) { + + var generatedInstance = Assemble(data); + + generatedInstances.Add(generatedInstance); + + } + + return generatedInstances; + + } + + internal BaseRegister Assemble (SData data) { + + //parse all others where the type is known + Type registerClassType = data.dotnetVarType.GetDefaultRegisterHoldingType(); + + BaseRegister generatedInstance = null; + + if (data.dotnetVarType.IsEnum) { + + //------------------------------------------- + //as numeric register with enum target + + var underlying = Enum.GetUnderlyingType(data.dotnetVarType); + var enuSize = Marshal.SizeOf(underlying); + + if (enuSize > 4) + throw new NotSupportedException("Enums not based on 16 or 32 bit numbers are not supported"); + + var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + Type paramedClass = typeof(NumberRegister<>).MakeGenericType(data.dotnetVarType); + ConstructorInfo constr = paramedClass.GetConstructor(flags, null, new Type[] { typeof(uint), typeof(string) }, null); + + var parameters = new object[] { data.memAddress, data.name }; + var instance = (BaseRegister)constr.Invoke(parameters); + + generatedInstance = instance; + + } else if (registerClassType.IsGenericType) { + + //------------------------------------------- + //as numeric register + + //create a new bregister instance + var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + + //int _adress, Type _enumType = null, string _name = null + var parameters = new object[] { data.memAddress, data.name }; + var instance = (BaseRegister)Activator.CreateInstance(registerClassType, flags, null, parameters, null); + instance.pollLevel = data.pollLevel; + generatedInstance = instance; + + } else if (registerClassType == typeof(BytesRegister) && data.byteSize != null) { + + //------------------------------------------- + //as byte range register + + BytesRegister instance = new BytesRegister(data.memAddress, (uint)data.byteSize, data.name); + generatedInstance = instance; + + } else if (registerClassType == typeof(BytesRegister) && data.bitSize != null) { + + //------------------------------------------- + //as bit range register + + BytesRegister instance = new BytesRegister(data.memAddress, (ushort)data.bitSize, data.name); + generatedInstance = instance; + + } else if (registerClassType == typeof(StringRegister)) { + + //------------------------------------------- + //as byte range register + var instance = (BaseRegister)new StringRegister(data.memAddress, data.name) { + ReservedSize = (short)(data.stringSize ?? 0), + }; + generatedInstance = instance; + + } else if (data.regType.IsBoolean()) { + + //------------------------------------------- + //as boolean register + + var io = (IOType)(int)data.regType; + var spAddr = data.specialAddress; + var areaAddr = data.memAddress; + + var instance = new BoolRegister(io, spAddr, areaAddr, data.name); + + generatedInstance = instance; + + } + + //finalize set for every + + if (generatedInstance == null) + throw new MewtocolException("Failed to build register"); + + if (collectionTarget != null) + generatedInstance.WithRegisterCollection(collectionTarget); + + generatedInstance.attachedInterface = onInterface; + + generatedInstance.pollLevel = data.pollLevel; + + return generatedInstance; + + } + + } + +} diff --git a/MewtocolNet/Registers/BaseRegister.cs b/MewtocolNet/Registers/BaseRegister.cs index edd649c..ac36db9 100644 --- a/MewtocolNet/Registers/BaseRegister.cs +++ b/MewtocolNet/Registers/BaseRegister.cs @@ -169,7 +169,8 @@ namespace MewtocolNet.Registers { sb.AppendLine($"Perf. Reads: {successfulReads}, Writes: {successfulWrites}"); sb.AppendLine($"Register Type: {RegisterType}"); sb.AppendLine($"Address: {GetRegisterWordRangeString()}"); - if(GetSpecialAddress() != null) sb.AppendLine($"SPAddress: {GetSpecialAddress():X1}"); + 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()}"); diff --git a/MewtocolNet/Registers/BoolRegister.cs b/MewtocolNet/Registers/BoolRegister.cs index f54b62b..ffd332e 100644 --- a/MewtocolNet/Registers/BoolRegister.cs +++ b/MewtocolNet/Registers/BoolRegister.cs @@ -1,8 +1,11 @@ -using MewtocolNet.UnderlyingRegisters; +using MewtocolNet.Exceptions; +using MewtocolNet.RegisterBuilding; +using MewtocolNet.UnderlyingRegisters; using System; using System.ComponentModel; using System.Net; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace MewtocolNet.Registers { @@ -18,16 +21,11 @@ namespace MewtocolNet.Registers { /// public byte SpecialAddress => specialAddress; - /// - /// Creates a new boolean register - /// - /// The io type prefix - /// The special address - /// The area special address - /// The custom name - /// - /// - public BoolRegister(IOType _io, byte _spAddress = 0x0, uint _areaAdress = 0, string _name = null) { + [Obsolete("Creating registers directly is not supported use IPlc.Register instead")] + public BoolRegister() => + throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern"); + + internal BoolRegister(IOType _io, byte _spAddress = 0x0, uint _areaAdress = 0, string _name = null) { lastValue = null; @@ -50,7 +48,7 @@ namespace MewtocolNet.Registers { 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"); + throw new NotSupportedException("Special address cant be greater than 15 or 0xF"); base.CheckAddressOverflow(addressStart, addressLen); @@ -61,31 +59,47 @@ namespace MewtocolNet.Registers { /// public override async Task ReadAsync() { - if (!attachedInterface.IsConnected) return null; + if (!attachedInterface.IsConnected) + throw MewtocolException.NotConnectedSend(); - var read = await attachedInterface.ReadRawRegisterAsync(this); - if(read == null) return null; - - var parsed = PlcValueParser.Parse(this, read); - - SetValueFromPLC(parsed); - return parsed; + return null; } /// public override async Task WriteAsync(object data) { - if (!attachedInterface.IsConnected) return false; + if (!attachedInterface.IsConnected) + throw MewtocolException.NotConnectedSend(); - var encoded = PlcValueParser.Encode(this, (bool)data); + return false; - var res = await attachedInterface.WriteRawRegisterAsync(this, encoded); - if (res) { - SetValueFromPLC(data); - } + } - return res; + internal override async Task WriteToAnonymousAsync (object value) { + + if (!attachedInterface.IsConnected) + throw MewtocolException.NotConnectedSend(); + + var station = attachedInterface.GetStationNumber(); + string reqStr = $"%{station}#WCS{BuildMewtocolQuery()}{((bool)value ? "1" : "0")}"; + var res = await attachedInterface.SendCommandAsync(reqStr); + + return res.Success; + + } + + internal override async Task ReadFromAnonymousAsync() { + + if (!attachedInterface.IsConnected) + throw MewtocolException.NotConnectedSend(); + + var station = attachedInterface.GetStationNumber(); + string requeststring = $"%{station}#RCS{BuildMewtocolQuery()}"; + var result = await attachedInterface.SendCommandAsync(requeststring); + if (!result.Success) return null; + + return result.Response.ParseRCSingleBit(); } diff --git a/MewtocolNet/Registers/BytesRegister.cs b/MewtocolNet/Registers/BytesRegister.cs index 36fa4bd..bacd0b0 100644 --- a/MewtocolNet/Registers/BytesRegister.cs +++ b/MewtocolNet/Registers/BytesRegister.cs @@ -1,4 +1,5 @@ -using System; +using MewtocolNet.Exceptions; +using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; @@ -21,12 +22,13 @@ namespace MewtocolNet.Registers { internal uint ReservedBytesSize { get; set; } - internal ushort? ReservedBitSize { get; set; } + internal ushort? ReservedBitSize { get; set; } - /// - /// Defines a register containing bytes - /// - public BytesRegister(uint _address, uint _reservedByteSize, string _name = null) { + [Obsolete("Creating registers directly is not supported use IPlc.Register instead")] + public BytesRegister() => + throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern"); + + internal BytesRegister(uint _address, uint _reservedByteSize, string _name = null) { name = _name; memoryAddress = _address; @@ -123,7 +125,8 @@ namespace MewtocolNet.Registers { /// public override async Task ReadAsync() { - if (!attachedInterface.IsConnected) return null; + if (!attachedInterface.IsConnected) + throw MewtocolException.NotConnectedSend(); var res = await underlyingMemory.ReadRegisterAsync(this); if (!res) return null; @@ -134,26 +137,11 @@ namespace MewtocolNet.Registers { } - internal override object SetValueFromBytes(byte[] bytes) { - - AddSuccessRead(); - - object parsed; - if (ReservedBitSize != null) { - parsed = PlcValueParser.Parse(this, bytes); - } else { - parsed = PlcValueParser.Parse(this, bytes); - } - - SetValueFromPLC(parsed); - return parsed; - - } - /// public override async Task WriteAsync(object data) { - if (!attachedInterface.IsConnected) return false; + if (!attachedInterface.IsConnected) + throw MewtocolException.NotConnectedSend(); byte[] encoded; @@ -173,6 +161,43 @@ namespace MewtocolNet.Registers { } + internal override object SetValueFromBytes(byte[] bytes) { + + AddSuccessRead(); + + object parsed; + if (ReservedBitSize != null) { + parsed = PlcValueParser.Parse(this, bytes); + } else { + parsed = PlcValueParser.Parse(this, bytes); + } + + SetValueFromPLC(parsed); + return parsed; + + } + + internal override async Task WriteToAnonymousAsync(object value) { + + if (!attachedInterface.IsConnected) + throw MewtocolException.NotConnectedSend(); + + return await attachedInterface.WriteByteRange((int)MemoryAddress, (byte[])value); + + } + + internal override async Task ReadFromAnonymousAsync() { + + if (!attachedInterface.IsConnected) + throw MewtocolException.NotConnectedSend(); + + var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2, false); + if (res == null) return null; + + return res; + + } + } } diff --git a/MewtocolNet/Registers/NumberRegister.cs b/MewtocolNet/Registers/NumberRegister.cs index 08e10c1..8b9474c 100644 --- a/MewtocolNet/Registers/NumberRegister.cs +++ b/MewtocolNet/Registers/NumberRegister.cs @@ -17,12 +17,11 @@ namespace MewtocolNet.Registers { /// The type of the numeric value public class NumberRegister : BaseRegister { - /// - /// Defines a register containing a number - /// - /// Memory start adress max 99999 - /// Name of the register - public NumberRegister (uint _address, string _name = null) { + [Obsolete("Creating registers directly is not supported use IPlc.Register instead")] + public NumberRegister() => + throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern"); + + internal NumberRegister (uint _address, string _name = null) { memoryAddress = _address; name = _name; @@ -130,7 +129,8 @@ namespace MewtocolNet.Registers { /// public override async Task ReadAsync() { - if (!attachedInterface.IsConnected) return null; + if (!attachedInterface.IsConnected) + throw MewtocolException.NotConnectedSend(); var res = await underlyingMemory.ReadRegisterAsync(this); if (!res) return null; @@ -141,20 +141,11 @@ namespace MewtocolNet.Registers { } - internal override object SetValueFromBytes(byte[] bytes) { - - AddSuccessRead(); - - var parsed = PlcValueParser.Parse(this, bytes); - SetValueFromPLC(parsed); - return parsed; - - } - /// public override async Task WriteAsync(object data) { - if (!attachedInterface.IsConnected) return false; + if (!attachedInterface.IsConnected) + throw MewtocolException.NotConnectedSend(); var encoded = PlcValueParser.Encode(this, (T)data); var res = await underlyingMemory.WriteRegisterAsync(this, encoded); @@ -168,6 +159,16 @@ namespace MewtocolNet.Registers { } + internal override object SetValueFromBytes(byte[] bytes) { + + AddSuccessRead(); + + var parsed = PlcValueParser.Parse(this, bytes); + SetValueFromPLC(parsed); + return parsed; + + } + internal override async Task WriteToAnonymousAsync (object value) { if (!attachedInterface.IsConnected) @@ -183,7 +184,7 @@ namespace MewtocolNet.Registers { if (!attachedInterface.IsConnected) throw MewtocolException.NotConnectedSend(); - var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2); + var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2, false); if (res == null) return null; return PlcValueParser.Parse(this, res); diff --git a/MewtocolNet/Registers/StringRegister.cs b/MewtocolNet/Registers/StringRegister.cs index 58a5d1a..7dbee77 100644 --- a/MewtocolNet/Registers/StringRegister.cs +++ b/MewtocolNet/Registers/StringRegister.cs @@ -21,12 +21,13 @@ namespace MewtocolNet.Registers { internal uint WordsSize { get; set; } - private bool isCalibratedFromPlc = false; + internal bool isCalibratedFromPlc = false; - /// - /// Defines a register containing a string - /// - public StringRegister (uint _address, string _name = null) { + [Obsolete("Creating registers directly is not supported use IPlc.Register instead")] + public StringRegister() => + throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern"); + + internal StringRegister (uint _address, string _name = null) { name = _name; memoryAddress = _address; @@ -55,7 +56,7 @@ namespace MewtocolNet.Registers { /// public override void SetValueFromPLC (object val) { - if (!val.Equals(lastValue)) { + if (val == null || !val.Equals(lastValue)) { lastValue = (string)val; @@ -72,27 +73,7 @@ namespace MewtocolNet.Registers { /// public override uint GetRegisterAddressLen() => Math.Max(1, WordsSize); - /// - public override async Task ReadAsync() { - - if (!attachedInterface.IsConnected) return null; - - //get the string params first - - if(!isCalibratedFromPlc) await CalibrateFromPLC(); - - var read = await attachedInterface.ReadRawRegisterAsync(this); - if (read == null) return null; - - var parsed = PlcValueParser.Parse(this, read); - - SetValueFromPLC(parsed); - - return parsed; - - } - - private async Task CalibrateFromPLC () { + internal async Task CalibrateFromPLC () { Logger.Log($"Calibrating string ({PLCAddressName}) from PLC source", LogLevel.Verbose, attachedInterface); @@ -115,30 +96,79 @@ namespace MewtocolNet.Registers { } + /// + public override async Task ReadAsync() { + + if (!attachedInterface.IsConnected) + throw MewtocolException.NotConnectedSend(); + + if (!isCalibratedFromPlc) await CalibrateFromPLC(); + + var res = await underlyingMemory.ReadRegisterAsync(this); + if (!res) return null; + + var bytes = underlyingMemory.GetUnderlyingBytes(this); + + return SetValueFromBytes(bytes); + + } + /// public override async Task WriteAsync(object data) { - if (!attachedInterface.IsConnected) return false; + if (!attachedInterface.IsConnected) + throw MewtocolException.NotConnectedSend(); - if (!isCalibratedFromPlc) { - - //try to calibrate from plc - await CalibrateFromPLC(); - - } + if (!isCalibratedFromPlc) await CalibrateFromPLC(); var encoded = PlcValueParser.Encode(this, (string)data); - var res = await attachedInterface.WriteRawRegisterAsync(this, encoded); - + var res = await underlyingMemory.WriteRegisterAsync(this, encoded); + if (res) { + AddSuccessWrite(); SetValueFromPLC(data); - UsedSize = (short)((string)Value).Length; } - + return res; } + internal override object SetValueFromBytes(byte[] bytes) { + + AddSuccessRead(); + + var parsed = PlcValueParser.Parse(this, bytes); + SetValueFromPLC(parsed); + return parsed; + + } + + internal override async Task WriteToAnonymousAsync(object value) { + + if (!attachedInterface.IsConnected) + throw MewtocolException.NotConnectedSend(); + + if (!isCalibratedFromPlc) await CalibrateFromPLC(); + + var encoded = PlcValueParser.Encode(this, (string)value); + return await attachedInterface.WriteByteRange((int)MemoryAddress, encoded); + + } + + internal override async Task ReadFromAnonymousAsync() { + + if (!attachedInterface.IsConnected) + throw MewtocolException.NotConnectedSend(); + + if (!isCalibratedFromPlc) await CalibrateFromPLC(); + + var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2); + if (res == null) return null; + + return PlcValueParser.Parse(this, res); + + } + } } diff --git a/MewtocolNet/TypeConversion/Conversions.cs b/MewtocolNet/TypeConversion/Conversions.cs index 6e9a2e9..bf35b52 100644 --- a/MewtocolNet/TypeConversion/Conversions.cs +++ b/MewtocolNet/TypeConversion/Conversions.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Linq; using System.Text; using MewtocolNet.Helpers; +using MewtocolNet.Exceptions; namespace MewtocolNet.TypeConversion { @@ -35,119 +36,82 @@ namespace MewtocolNet.TypeConversion { new PlcTypeConversion(RegisterType.R) { HoldingRegisterType = typeof(BoolRegister), PlcVarType = PlcVarType.BOOL, - FromRaw = (reg, bytes) => { - - return (bool)(bytes[0] == 1); - }, - ToRaw = (reg, value) => { - - return new byte[] { (byte)(value ? 1 : 0) }; - - }, + FromRaw = (reg, bytes) => (bool)(bytes[0] == 1), + ToRaw = (reg, value) => new byte[] { (byte)(value ? 1 : 0) }, }, + //default bool X conversion new PlcTypeConversion(RegisterType.X) { HoldingRegisterType = typeof(BoolRegister), PlcVarType = PlcVarType.BOOL, - FromRaw = (reg, bytes) => { - - return bytes[0] == 1; - }, - ToRaw = (reg, value) => { - - return new byte[] { (byte)(value ? 1 : 0) }; - - }, + FromRaw = (reg, bytes) => (bool)(bytes[0] == 1), + ToRaw = (reg, value) => new byte[] { (byte)(value ? 1 : 0) }, }, + //default bool Y conversion new PlcTypeConversion(RegisterType.Y) { HoldingRegisterType = typeof(BoolRegister), PlcVarType = PlcVarType.BOOL, - FromRaw = (reg, bytes) => { - - return bytes[0] == 1; - }, - ToRaw = (reg, value) => { - - return new byte[] { (byte)(value ? 1 : 0) }; - - }, + FromRaw = (reg, bytes) => (bool)(bytes[0] == 1), + ToRaw = (reg, value) => new byte[] { (byte)(value ? 1 : 0) }, }, + //default short DT conversion new PlcTypeConversion(RegisterType.DT) { HoldingRegisterType = typeof(NumberRegister), PlcVarType = PlcVarType.INT, - FromRaw = (reg, bytes) => { - - return BitConverter.ToInt16(bytes, 0); - }, - ToRaw = (reg, value) => { - - return BitConverter.GetBytes(value); - - }, + FromRaw = (reg, bytes) => BitConverter.ToInt16(bytes, 0), + ToRaw = (reg, value) => BitConverter.GetBytes(value), }, + //default ushort DT conversion new PlcTypeConversion(RegisterType.DT) { HoldingRegisterType = typeof(NumberRegister), PlcVarType = PlcVarType.UINT, - FromRaw = (reg, bytes) => { - - return BitConverter.ToUInt16(bytes, 0); - }, - ToRaw = (reg, value) => { - - return BitConverter.GetBytes(value); - - }, + FromRaw = (reg, bytes) => BitConverter.ToUInt16(bytes, 0), + ToRaw = (reg, value) => BitConverter.GetBytes(value), }, + + //default ushort DT conversion + new PlcTypeConversion(RegisterType.DT) { + HoldingRegisterType = typeof(NumberRegister), + PlcVarType = PlcVarType.WORD, + FromRaw = (reg, bytes) => BitConverter.ToUInt16(bytes, 0), + ToRaw = (reg, value) => BitConverter.GetBytes(value), + }, + //default int DDT conversion new PlcTypeConversion(RegisterType.DDT) { HoldingRegisterType = typeof(NumberRegister), PlcVarType = PlcVarType.DINT, - FromRaw = (reg, bytes) => { - - return BitConverter.ToInt32(bytes, 0); - }, - ToRaw = (reg, value) => { - - return BitConverter.GetBytes(value); - - }, + FromRaw = (reg, bytes) => BitConverter.ToInt32(bytes, 0), + ToRaw = (reg, value) => BitConverter.GetBytes(value), }, + //default uint DDT conversion new PlcTypeConversion(RegisterType.DDT) { HoldingRegisterType = typeof(NumberRegister), PlcVarType = PlcVarType.UDINT, - FromRaw = (reg, bytes) => { - - return BitConverter.ToUInt32(bytes, 0); - }, - ToRaw = (reg, value) => { - - return BitConverter.GetBytes(value); - - }, + FromRaw = (reg, bytes) => BitConverter.ToUInt32(bytes, 0), + ToRaw = (reg, value) => BitConverter.GetBytes(value), }, + + //default uint DDT conversion + new PlcTypeConversion(RegisterType.DDT) { + HoldingRegisterType = typeof(NumberRegister), + PlcVarType = PlcVarType.DWORD, + FromRaw = (reg, bytes) => BitConverter.ToUInt32(bytes, 0), + ToRaw = (reg, value) => BitConverter.GetBytes(value), + }, + //default float DDT conversion new PlcTypeConversion(RegisterType.DDT) { HoldingRegisterType = typeof(NumberRegister), PlcVarType = PlcVarType.REAL, - FromRaw = (reg, bytes) => { - - //bytes = new byte[] { 0xCD, 0xCC, 0x8C, 0x40 }; - - float finalFloat = BitConverter.ToSingle(bytes, 0); - - return finalFloat; - - }, - ToRaw = (reg, value) => { - - return BitConverter.GetBytes(value); - - }, + FromRaw = (reg, bytes) => BitConverter.ToSingle(bytes, 0), + ToRaw = (reg, value) => BitConverter.GetBytes(value), }, + //default TimeSpan DDT conversion new PlcTypeConversion(RegisterType.DDT) { HoldingRegisterType = typeof(NumberRegister), @@ -167,18 +131,28 @@ namespace MewtocolNet.TypeConversion { }, }, - //default byte array DT Range conversion + + //default byte array DT Range conversion, direct pass through new PlcTypeConversion(RegisterType.DT_BYTE_RANGE) { HoldingRegisterType = typeof(BytesRegister), FromRaw = (reg, bytes) => bytes, ToRaw = (reg, value) => value, }, - //default string DT Range conversion + + //default string DT Range conversion Example bytes: (04 00 03 00 XX XX XX) + //first 4 bytes are reserved size (2 bytes) and used size (2 bytes) + //the remaining bytes are the ascii bytes for the string new PlcTypeConversion(RegisterType.DT_BYTE_RANGE) { HoldingRegisterType = typeof(StringRegister), PlcVarType = PlcVarType.STRING, FromRaw = (reg, bytes) => { + if(bytes == null || bytes.Length <= 4) { + + throw new MewtocolException("Failed to convert string bytes, response not long enough"); + + } + //get actual showed size short actualLen = BitConverter.ToInt16(bytes, 2); @@ -207,7 +181,8 @@ namespace MewtocolNet.TypeConversion { }, }, - //default bitn array DT conversion + + //default bit array <=> byte array conversion new PlcTypeConversion(RegisterType.DT_BYTE_RANGE) { HoldingRegisterType = typeof(BytesRegister), FromRaw = (reg, bytes) => { diff --git a/MewtocolNet/UnderlyingRegisters/DTArea.cs b/MewtocolNet/UnderlyingRegisters/DTArea.cs index dda1e80..e0a6005 100644 --- a/MewtocolNet/UnderlyingRegisters/DTArea.cs +++ b/MewtocolNet/UnderlyingRegisters/DTArea.cs @@ -75,6 +75,8 @@ namespace MewtocolNet.UnderlyingRegisters { internal async Task RequestByteReadAsync (ulong addStart, ulong addEnd) { + await CheckDynamicallySizedRegistersAsync(); + var station = mewInterface.GetStationNumber(); string requeststring = $"%{station}#RD{GetMewtocolIdent(addStart, addEnd)}"; @@ -143,6 +145,22 @@ namespace MewtocolNet.UnderlyingRegisters { } + private async Task CheckDynamicallySizedRegistersAsync () { + + //calibrating at runtime sized registers + var uncalibratedStringRegisters = linkedRegisters + .Where(x => x is StringRegister sreg && !sreg.isCalibratedFromPlc) + .Cast() + .ToList(); + + foreach (var register in uncalibratedStringRegisters) + await register.CalibrateFromPLC(); + + if (uncalibratedStringRegisters.Count > 0) + mewInterface.memoryManager.MergeAndSizeDataAreas(); + + } + private string GetMewtocolIdent () { StringBuilder asciistring = new StringBuilder("D"); diff --git a/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs b/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs index 983aab2..1216f34 100644 --- a/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs +++ b/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs @@ -253,6 +253,22 @@ namespace MewtocolNet.UnderlyingRegisters { } + internal void MergeAndSizeDataAreas () { + + //merge gaps that the algorithm didn't catch be rerunning the register attachment + + foreach (var pLevel in pollLevels) { + + var allDataAreaRegisters = pLevel.dataAreas.SelectMany(x => x.linkedRegisters).ToList(); + var dataAreas = new List(allDataAreaRegisters.Capacity); + + foreach (var reg in allDataAreaRegisters) + AddDTArea(reg); + + } + + } + internal async Task PollAllAreasAsync () { foreach (var pollLevel in pollLevels) {