diff --git a/Examples.WPF/MainWindow.xaml b/Examples.WPF/MainWindow.xaml index 57867ca..a8b6a41 100644 --- a/Examples.WPF/MainWindow.xaml +++ b/Examples.WPF/MainWindow.xaml @@ -9,7 +9,7 @@ MinWidth="500" MinHeight="400" Height="850" - Width="800" + Width="1200" Title="MewtocolNet WPF Demo"> diff --git a/Examples.WPF/RegisterCollections/TestRegisterCollection.cs b/Examples.WPF/RegisterCollections/TestRegisterCollection.cs new file mode 100644 index 0000000..4ea16b7 --- /dev/null +++ b/Examples.WPF/RegisterCollections/TestRegisterCollection.cs @@ -0,0 +1,31 @@ +using MewtocolNet; +using MewtocolNet.RegisterAttributes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Examples.WPF.RegisterCollections; + +public class TestRegisterCollection : RegisterCollection { + + [Register("R11A")] + public bool? TestR11A { get; set; } + + [Register("R11A")] + public bool TestR11A_Duplicate_NonNullable { get; set; } + + [Register("R16B")] + public bool TestR16B { get; set; } + + [BitRegister("DT1000", 0), PollLevel(3)] + public bool? TestDT100_Word_Duplicate_SingleBit { get; set; } + + [Register("DT1000")] + public Word TestDT100_Word_Duplicate { get; set; } + + [BitRegister("DDT1010", 1)] + public bool? TestDDT1010_DWord_Duplicate_SingleBit { get; set; } + +} diff --git a/Examples.WPF/ViewModels/AppViewModel.cs b/Examples.WPF/ViewModels/AppViewModel.cs index 014db4a..4689fd0 100644 --- a/Examples.WPF/ViewModels/AppViewModel.cs +++ b/Examples.WPF/ViewModels/AppViewModel.cs @@ -1,4 +1,5 @@ -using MewtocolNet; +using Examples.WPF.RegisterCollections; +using MewtocolNet; using System; using System.Collections.Generic; using System.Linq; @@ -10,6 +11,7 @@ namespace Examples.WPF.ViewModels { public class AppViewModel : ViewModelBase { private IPlc? plc; + private TestRegisterCollection testRegCollection = null!; public bool PlcIsNull => plc == null; @@ -25,6 +27,14 @@ namespace Examples.WPF.ViewModels { } } + public TestRegisterCollection TestRegCollection { + get => testRegCollection; + set { + testRegCollection = value; + OnPropChange(); + } + } + } } diff --git a/Examples.WPF/ViewModels/PlcDataViewViewModel.cs b/Examples.WPF/ViewModels/PlcDataViewViewModel.cs index 7cacd07..1cef18a 100644 --- a/Examples.WPF/ViewModels/PlcDataViewViewModel.cs +++ b/Examples.WPF/ViewModels/PlcDataViewViewModel.cs @@ -1,4 +1,5 @@ -using MewtocolNet; +using Examples.WPF.RegisterCollections; +using MewtocolNet; using MewtocolNet.Events; using System; using System.Collections.Generic; @@ -14,6 +15,8 @@ public class PlcDataViewViewModel : ViewModelBase { public IPlc Plc => App.ViewModel.Plc!; + public TestRegisterCollection RegCollection => App.ViewModel.TestRegCollection; + public ReconnectArgs PlcCurrentReconnectArgs { get => plcCurrentReconnectArgs; set { diff --git a/Examples.WPF/Views/ConnectView.xaml.cs b/Examples.WPF/Views/ConnectView.xaml.cs index adc4f35..b438b2c 100644 --- a/Examples.WPF/Views/ConnectView.xaml.cs +++ b/Examples.WPF/Views/ConnectView.xaml.cs @@ -1,4 +1,5 @@ -using Examples.WPF.ViewModels; +using Examples.WPF.RegisterCollections; +using Examples.WPF.ViewModels; using MewtocolNet; using MewtocolNet.ComCassette; using MewtocolNet.Registers; @@ -55,31 +56,46 @@ public partial class ConnectView : UserControl { var parsedInt = int.Parse(viewModel.SelectedPort); IRegister heartbeatSetter = null!; + IRegister outputContactReference = null!; + IRegister testBoolReference = null!; + IRegister wordRefTest = null!; + //build a new interface App.ViewModel.Plc = Mewtocol.Ethernet(viewModel.SelectedIP, parsedInt) .WithPoller() .WithInterfaceSettings(setting => { - setting.TryReconnectAttempts = 0; + + setting.TryReconnectAttempts = 10; setting.TryReconnectDelayMs = 2000; setting.SendReceiveTimeoutMs = 1000; setting.HeartbeatIntervalMs = 3000; - setting.MaxDataBlocksPerWrite = 12; + setting.MaxDataBlocksPerWrite = 20; setting.MaxOptimizationDistance = 10; + }) .WithCustomPollLevels(lvl => { + lvl.SetLevel(2, 3); lvl.SetLevel(3, TimeSpan.FromSeconds(5)); lvl.SetLevel(4, TimeSpan.FromSeconds(10)); + + }) + .WithRegisterCollections(collector => { + + App.ViewModel.TestRegCollection = collector.AddCollection(); + }) .WithRegisters(b => { - //b.Struct("DT0").Build(); - //b.Struct("DT0").AsArray(30).Build(); - - b.Bool("R10A").Build(); + b.Bool("X4").Build(); + b.Bool("Y4").Build(out outputContactReference); + b.Bool("R10A").PollLevel(PollLevel.FirstIteration).Build(out testBoolReference); b.Struct("DT1000").Build(out heartbeatSetter); - b.Struct("DT1000").Build(); + + //these will be merged into one + b.Struct("DT1000").Build(out wordRefTest); + b.Struct("DT1000").Build(out wordRefTest); b.Struct("DT1001").PollLevel(2).Build(); b.Struct("DT1002").PollLevel(2).Build(); @@ -98,9 +114,16 @@ public partial class ConnectView : UserControl { await heartbeatSetter.WriteAsync((short)new Random().Next(short.MinValue, short.MaxValue)); + if (outputContactReference.Value != null) + await outputContactReference.WriteAsync(!outputContactReference.Value.Value); + + if(testBoolReference.Value != null) + await testBoolReference.WriteAsync(!testBoolReference.Value.Value); + }) .Build(); + //connect to it await App.ViewModel.Plc.ConnectAsync(); if (App.ViewModel.Plc.IsConnected) { diff --git a/Examples.WPF/Views/PlcDataView.xaml b/Examples.WPF/Views/PlcDataView.xaml index d0ca91c..d3951c0 100644 --- a/Examples.WPF/Views/PlcDataView.xaml +++ b/Examples.WPF/Views/PlcDataView.xaml @@ -145,25 +145,148 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MewtocolNet/CustomTypes/DWord.cs b/MewtocolNet/CustomTypes/DWord.cs index d7c10ea..2dcd7cd 100644 --- a/MewtocolNet/CustomTypes/DWord.cs +++ b/MewtocolNet/CustomTypes/DWord.cs @@ -53,10 +53,14 @@ namespace MewtocolNet { /// public bool this[int bitIndex] { get { - if (bitIndex > bitLength - 1) - throw new IndexOutOfRangeException($"The DWord bit index was out of range ({bitIndex}/{bitLength - 1})"); + + if (bitIndex > bitLength - 1 && bitLength != 0) + throw new IndexOutOfRangeException($"The word bit index was out of range ({bitIndex}/{bitLength - 1})"); + + if (bitLength == 0) return false; return (value & (1 << bitIndex)) != 0; + } } diff --git a/MewtocolNet/CustomTypes/Word.cs b/MewtocolNet/CustomTypes/Word.cs index d53e6b6..82c7ae3 100644 --- a/MewtocolNet/CustomTypes/Word.cs +++ b/MewtocolNet/CustomTypes/Word.cs @@ -53,10 +53,14 @@ namespace MewtocolNet { /// public bool this[int bitIndex] { get { - if (bitIndex > bitLength - 1) + + if (bitIndex > bitLength - 1 && bitLength != 0) throw new IndexOutOfRangeException($"The word bit index was out of range ({bitIndex}/{bitLength - 1})"); + if (bitLength == 0) return false; + return (value & (1 << bitIndex)) != 0; + } } diff --git a/MewtocolNet/Helpers/MewtocolHelpers.cs b/MewtocolNet/Helpers/MewtocolHelpers.cs index c0878fd..b5c840e 100644 --- a/MewtocolNet/Helpers/MewtocolHelpers.cs +++ b/MewtocolNet/Helpers/MewtocolHelpers.cs @@ -139,11 +139,11 @@ namespace MewtocolNet { /// /// /// A or null of failed - internal static byte[] ParseDTRawStringAsBytes(this string _onString) { + internal static byte[] ParseResponseStringAsBytes(this string _onString) { _onString = _onString.Replace("\r", ""); - var res = new Regex(@"\%([0-9a-fA-F]{2})\$(?:RD|RP)(?.*)(?..)").Match(_onString); + var res = new Regex(@"\%([0-9a-fA-F]{2})\$(?:RD|RP|RC)(?.*)(?..)").Match(_onString); if (res.Success) { string val = res.Groups["data"].Value; diff --git a/MewtocolNet/Mewtocol.cs b/MewtocolNet/Mewtocol.cs index e7ffe2a..8dc1d6c 100644 --- a/MewtocolNet/Mewtocol.cs +++ b/MewtocolNet/Mewtocol.cs @@ -10,6 +10,7 @@ using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Threading.Tasks; +using static MewtocolNet.Mewtocol; namespace MewtocolNet { @@ -210,21 +211,21 @@ namespace MewtocolNet internal List collections = new List(); - public RegCollector AddCollection(RegisterCollection collection) { + public T AddCollection(T collection) where T : RegisterCollection { collections.Add(collection); - return this; + return collection; } - public RegCollector AddCollection() where T : RegisterCollection { + public T AddCollection() where T : RegisterCollection { var instance = (RegisterCollection)Activator.CreateInstance(typeof(T)); collections.Add(instance); - return this; + return (T)instance; } @@ -354,7 +355,7 @@ namespace MewtocolNet /// /// A builder for attaching register collections /// - public PostRegisterSetup WithRegisterCollections(Action collector) { + public PostInit WithRegisterCollections(Action collector) { try { @@ -365,9 +366,7 @@ namespace MewtocolNet imew.WithRegisterCollections(res.collections); } - return new PostRegisterSetup { - postInit = this - }; + return this; } catch { @@ -380,7 +379,7 @@ namespace MewtocolNet /// /// A builder for attaching register collections /// - public PostRegisterSetup WithRegisters(Action builder) { + public PostInit WithRegisters(Action builder) { try { @@ -391,7 +390,31 @@ namespace MewtocolNet plc.AddRegisters(regBuilder.assembler.assembled.ToArray()); - return new PostRegisterSetup { + return this; + + } catch { + + throw; + + } + + } + + /// + /// Repeats the passed method each time the hearbeat is triggered, + /// use + /// + /// + /// + public EndInitSetup WithHeartbeatTask(Func heartBeatAsync, bool executeInProg = false) { + try { + + var plc = (MewtocolInterface)(object)this.intf; + + plc.heartbeatCallbackTask = heartBeatAsync; + plc.execHeartBeatCallbackTaskInProg = executeInProg; + + return new EndInitSetup { postInit = this, }; @@ -406,63 +429,22 @@ namespace MewtocolNet /// /// Builds and returns the final plc interface /// - public T Build() => intf; + public T Build() => (T)(object)((MewtocolInterface)(object)intf).Build(); } #endregion - public class PostRegisterSetup { - - internal PostInit postInit; - - /// - /// Repeats the passed method each time the hearbeat is triggered, - /// use - /// - /// - /// - public EndInitSetup WithHeartbeatTask(Func heartBeatAsync, bool executeInProg = false) { - - try { - - var plc = (MewtocolInterface)(object)postInit.intf; - - plc.heartbeatCallbackTask = heartBeatAsync; - plc.execHeartBeatCallbackTaskInProg = executeInProg; - - return new EndInitSetup { - postInit = this.postInit, - postRegSetupInit = this - }; - - } catch { - - throw; - - } - - } - - /// - /// Builds and returns the final plc interface - /// - public T Build() => postInit.intf; - - } - #region Interface building step 4 public class EndInitSetup { internal PostInit postInit; - internal PostRegisterSetup postRegSetupInit; - /// /// Builds and returns the final plc interface /// - public T Build() => postInit.intf; + public T Build() => (T)(object)((MewtocolInterface)(object)postInit.intf).Build(); } diff --git a/MewtocolNet/MewtocolInterface.cs b/MewtocolNet/MewtocolInterface.cs index 45de15e..50f7e69 100644 --- a/MewtocolNet/MewtocolInterface.cs +++ b/MewtocolNet/MewtocolInterface.cs @@ -193,6 +193,14 @@ namespace MewtocolNet { } + internal MewtocolInterface Build () { + + memoryManager.LinkAndMergeRegisters(); + + return this; + + } + private void MewtocolInterface_Connected(object sender, PlcConnectionArgs args) { IsConnected = true; @@ -226,8 +234,6 @@ namespace MewtocolNet { } - OnRegisterChangedUpdateProps(asInternal); - } /// @@ -245,7 +251,7 @@ namespace MewtocolNet { Logger.Log($"DIAG ERR: {PlcInfo.SelfDiagnosticError}", LogLevel.Verbose, this); Logger.Log($"CPU VER: {PlcInfo.CpuVersion}", LogLevel.Verbose, this); - if(alwaysGetMetadata) { + if(alwaysGetMetadata && PlcInfo.Metadata != null) { Logger.LogVerbose($"METADATA: {PlcInfo.Metadata.MetaDataVersion}", this); Logger.LogVerbose($"FP-WIN VERSION: {PlcInfo.Metadata.FPWinVersion}", this); diff --git a/MewtocolNet/MewtocolInterfaceRegisterHandling.cs b/MewtocolNet/MewtocolInterfaceRegisterHandling.cs index 0fb4d56..dcc9163 100644 --- a/MewtocolNet/MewtocolInterfaceRegisterHandling.cs +++ b/MewtocolNet/MewtocolInterfaceRegisterHandling.cs @@ -242,57 +242,6 @@ namespace MewtocolNet { #endregion - #region Smart register polling methods - - [Obsolete] - private async Task UpdateRCPRegisters() { - - //build booleans - //var rcpList = RegistersUnderlying.Where(x => x.GetType() == typeof(BoolRegister)) - // .Select(x => (BoolRegister)x) - // .ToArray(); - - ////one frame can only read 8 registers at a time - //int rcpFrameCount = (int)Math.Ceiling((double)rcpList.Length / 8); - //int rcpLastFrameRemainder = rcpList.Length <= 8 ? rcpList.Length : rcpList.Length % 8; - - //for (int i = 0; i < rcpFrameCount; i++) { - - // int toReadRegistersCount = 8; - - // if (i == rcpFrameCount - 1) toReadRegistersCount = rcpLastFrameRemainder; - - // var rcpString = new StringBuilder($"%{GetStationNumber()}#RCP{toReadRegistersCount}"); - - // for (int j = 0; j < toReadRegistersCount; j++) { - - // BoolRegister register = rcpList[i + j]; - // rcpString.Append(register.BuildMewtocolQuery()); - - // } - - // string rcpRequest = rcpString.ToString(); - // var result = await SendCommandAsync(rcpRequest); - // if (!result.Success) return; - - // var resultBitArray = result.Response.ParseRCMultiBit(); - - // for (int k = 0; k < resultBitArray.Length; k++) { - - // var register = rcpList[i + k]; - - // if ((bool)register.Value != resultBitArray[k]) { - // register.SetValueFromPLC(resultBitArray[k]); - // } - - // } - - //} - - } - - #endregion - #region Register Collection adding /// @@ -331,9 +280,10 @@ namespace MewtocolNet { //add builder item regBuild - .AddressFromAttribute(cAttribute.MewAddress, cAttribute.TypeDef, collection, prop, byteHint) + .AddressFromAttribute(cAttribute.MewAddress, cAttribute.TypeDef, collection, prop, cAttribute, byteHint) .AsType(dotnetType.IsEnum ? dotnetType.UnderlyingSystemType : dotnetType) - .PollLevel(pollLevel); + .PollLevel(pollLevel) + .Finalize(); } @@ -353,27 +303,7 @@ namespace MewtocolNet { } - var assembler = new RegisterAssembler(this); - - AddRegisters(assembler.assembled.ToArray()); - - } - - /// - /// Writes back the values changes of the underlying registers to the corrosponding property - /// - private void OnRegisterChangedUpdateProps(Register reg) { - - var collection = reg.ContainedCollection; - if (collection == null) return; - - var props = collection.GetType().GetProperties(); - - //set the specific bit array if needed - //prop.SetValue(collection, bitAr); - //collection.TriggerPropertyChanged(prop.Name); - - + AddRegisters(regBuild.assembler.assembled.ToArray()); } @@ -383,16 +313,7 @@ namespace MewtocolNet { internal void AddRegisters(params Register[] registers) { - InsertRegistersToMemoryStack(registers.ToList()); - - } - - internal void InsertRegistersToMemoryStack(List registers) { - - memoryManager.LinkAndMergeRegisters(registers); - - //run a second iteration - //memoryManager.LinkAndMergeRegisters(); + memoryManager.LinkAndMergeRegisters(registers.ToList()); } diff --git a/MewtocolNet/MewtocolInterfaceRequests.cs b/MewtocolNet/MewtocolInterfaceRequests.cs index 3c9bde9..1db3047 100644 --- a/MewtocolNet/MewtocolInterfaceRequests.cs +++ b/MewtocolNet/MewtocolInterfaceRequests.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using MewtocolNet.Helpers; using System.Reflection; +using System.Runtime.InteropServices.ComTypes; namespace MewtocolNet { @@ -98,7 +99,7 @@ namespace MewtocolNet { var metaMarker = new byte[] { 0x4D, 0x65, 0x74, 0x41 }; - var data = await ReadByteRangeNonBlocking(endAddress - 2 - (readBytes / 2), readBytes); + var data = await ReadAreaByteRangeAsync(endAddress - 2 - (readBytes / 2), readBytes); if (data != null && data.SearchBytePattern(metaMarker) == readBytes - 4) { @@ -204,7 +205,7 @@ namespace MewtocolNet { if (res.Success) { - var bytes = res.Response.ParseDTRawStringAsBytes(); + var bytes = res.Response.ParseResponseStringAsBytes(); var foundEndPattern = bytes.SearchBytePattern(new byte[] { 0xF8, 0xFF, 0xFF }); for (int j = 0; j < bytes.Length; j += 2) { @@ -243,7 +244,7 @@ namespace MewtocolNet { /// /// start address of the array /// /// - public async Task WriteByteRange(int start, byte[] byteArr) { + public async Task WriteAreaByteRange(int start, byte[] byteArr) { if (byteArr == null) throw new ArgumentNullException(nameof(byteArr)); @@ -271,7 +272,7 @@ namespace MewtocolNet { /// Number of bytes to get /// Gets invoked when the progress changes, contains the progress as a double from 0 - 1.0 /// A byte array of the requested DT area - public async Task ReadByteRangeNonBlocking(int start, int byteCount, Action onProgress = null) { + public async Task ReadAreaByteRangeAsync(int start, int byteCount, RegisterPrefix areaPrefix = RegisterPrefix.DT, Action onProgress = null) { //on odd bytes add one word var wordLength = byteCount / 2; @@ -287,19 +288,41 @@ namespace MewtocolNet { List readBytes = new List(); + int padLeftLen = 0; + string areaCodeStr = null; + + switch (areaPrefix) { + case RegisterPrefix.X: + areaCodeStr = $"RCCX"; + padLeftLen = 4; + break; + case RegisterPrefix.Y: + areaCodeStr = $"RCCY"; + padLeftLen = 4; + break; + case RegisterPrefix.R: + areaCodeStr = $"RCCR"; + padLeftLen = 4; + break; + case RegisterPrefix.DT: + case RegisterPrefix.DDT: + areaCodeStr = $"RDD"; + padLeftLen = 5; + break; + } + async Task ReadBlock(int wordStart, int wordEnd, Action readProg) { int blockSize = wordEnd - wordStart + 1; - string startStr = wordStart.ToString().PadLeft(5, '0'); - string endStr = wordEnd.ToString().PadLeft(5, '0'); - - string requeststring = $"%{GetStationNumber()}#RDD{startStr}{endStr}"; + string startStr = wordStart.ToString().PadLeft(padLeftLen, '0'); + string endStr = wordEnd.ToString().PadLeft(padLeftLen, '0'); + string requeststring = $"%{GetStationNumber()}#{areaCodeStr}{startStr}{endStr}"; var result = await SendCommandInternalAsync(requeststring, onReceiveProgress: readProg); if (result.Success && !string.IsNullOrEmpty(result.Response)) { - var bytes = result.Response.ParseDTRawStringAsBytes(); + var bytes = result.Response.ParseResponseStringAsBytes(); if (bytes != null) readBytes.AddRange(bytes); diff --git a/MewtocolNet/RegisterAttributes/BitRegisterAttribute.cs b/MewtocolNet/RegisterAttributes/BitRegisterAttribute.cs new file mode 100644 index 0000000..f8322dd --- /dev/null +++ b/MewtocolNet/RegisterAttributes/BitRegisterAttribute.cs @@ -0,0 +1,23 @@ +using System; + +namespace MewtocolNet.RegisterAttributes { + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] + public class BitRegisterAttribute : RegisterAttribute { + + internal int bitIndex; + + /// + /// 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 BitRegisterAttribute(string mewAddress, byte bitIndex) : base(mewAddress, null) { + + this.bitIndex = bitIndex; + + } + + } + +} diff --git a/MewtocolNet/RegisterAttributes/RegisterPropTarget.cs b/MewtocolNet/RegisterAttributes/RegisterPropTarget.cs index ddafcbb..adebb5a 100644 --- a/MewtocolNet/RegisterAttributes/RegisterPropTarget.cs +++ b/MewtocolNet/RegisterAttributes/RegisterPropTarget.cs @@ -8,15 +8,14 @@ namespace MewtocolNet.RegisterAttributes { //propinfo of the bound property internal PropertyInfo BoundProperty; - //general number of bits or bytes to read back to the prop - internal int? LinkLength; + internal RegisterAttribute PropertyAttribute; + + internal RegisterCollection ContainedCollection; public override string ToString() { var sb = new StringBuilder(); sb.Append($"{BoundProperty}"); - if (LinkLength != null) sb.Append($" -Len: {LinkLength}"); - return sb.ToString(); } diff --git a/MewtocolNet/RegisterBuilding/BuilderPatterns/RBuildFromAttributes.cs b/MewtocolNet/RegisterBuilding/BuilderPatterns/RBuildFromAttributes.cs index d2b8279..b580f00 100644 --- a/MewtocolNet/RegisterBuilding/BuilderPatterns/RBuildFromAttributes.cs +++ b/MewtocolNet/RegisterBuilding/BuilderPatterns/RBuildFromAttributes.cs @@ -24,7 +24,7 @@ namespace MewtocolNet.RegisterBuilding.BuilderPatterns { } //internal use only, adds a type definition (for use when building from attibute) - internal DynamicStp AddressFromAttribute(string dtAddr, string typeDef, RegisterCollection regCol, PropertyInfo prop, uint? bytesizeHint = null) { + internal DynamicStp AddressFromAttribute(string dtAddr, string typeDef, RegisterCollection regCol, PropertyInfo prop, RegisterAttribute propAttr, uint? bytesizeHint = null) { var stpData = AddressTools.ParseAddress(dtAddr); @@ -32,6 +32,7 @@ namespace MewtocolNet.RegisterBuilding.BuilderPatterns { stpData.buildSource = RegisterBuildSource.Attribute; stpData.regCollection = regCol; stpData.boundProperty = prop; + stpData.boundPropertyAttribute = propAttr; stpData.byteSizeHint = bytesizeHint; return new DynamicStp { @@ -58,9 +59,16 @@ namespace MewtocolNet.RegisterBuilding.BuilderPatterns { internal class DynamicRegister : SBaseRBDyn { - public void PollLevel (int level) { + public DynamicRegister PollLevel (int level) { Data.pollLevel = level; + return this; + + } + + public void Finalize () { + + builder.assembler.Assemble(Data); } diff --git a/MewtocolNet/RegisterBuilding/RegisterAssembler.cs b/MewtocolNet/RegisterBuilding/RegisterAssembler.cs index b7e9ade..2f0ae79 100644 --- a/MewtocolNet/RegisterBuilding/RegisterAssembler.cs +++ b/MewtocolNet/RegisterBuilding/RegisterAssembler.cs @@ -3,6 +3,7 @@ using MewtocolNet.RegisterBuilding.BuilderPatterns; using MewtocolNet.Registers; using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Runtime.InteropServices; @@ -142,23 +143,51 @@ namespace MewtocolNet.RegisterBuilding { if (generatedInstance == null) throw new ArgumentException("Failed to build register"); - if (collectionTarget != null) - generatedInstance.WithRegisterCollection(collectionTarget); - if (data.boundProperty != null) generatedInstance.WithBoundProperty(new RegisterPropTarget { BoundProperty = data.boundProperty, + PropertyAttribute = data.boundPropertyAttribute, + ContainedCollection = data.regCollection }); generatedInstance.attachedInterface = onInterface; generatedInstance.underlyingSystemType = data.dotnetVarType; generatedInstance.pollLevel = data.pollLevel; - if (data.regCollection != null) - generatedInstance.autoGenerated = true; + //set auto generated + generatedInstance.autoGenerated = data.buildSource == PublicEnums.RegisterBuildSource.Attribute; - assembled.Add(generatedInstance); - return generatedInstance; + //Check for a dupe, first in all registers, then in the local generation group + var foundDupe = onInterface.GetAllRegistersInternal().FirstOrDefault(x => x.IsSameAddressAndType(generatedInstance)) ?? + assembled.FirstOrDefault(x => x.IsSameAddressAndType(generatedInstance)); + + if (foundDupe != null) { + + if(data.boundProperty != null) + foundDupe.WithBoundProperty(new RegisterPropTarget { + BoundProperty = data.boundProperty, + ContainedCollection = data.regCollection, + PropertyAttribute = data.boundPropertyAttribute, + }); + + if(onInterface.memoryManager.pollLevelOrMode == PollLevelOverwriteMode.Highest) { + + foundDupe.pollLevel = Math.Max(foundDupe.pollLevel, generatedInstance.pollLevel); + + } else { + + foundDupe.pollLevel = Math.Min(foundDupe.pollLevel, generatedInstance.pollLevel); + + } + + return foundDupe; + + } else { + + assembled.Add(generatedInstance); + return generatedInstance; + + } } diff --git a/MewtocolNet/RegisterBuilding/StepBaseTyper.cs b/MewtocolNet/RegisterBuilding/StepBaseTyper.cs index 6585c58..789e8e5 100644 --- a/MewtocolNet/RegisterBuilding/StepBaseTyper.cs +++ b/MewtocolNet/RegisterBuilding/StepBaseTyper.cs @@ -1,4 +1,5 @@ using MewtocolNet.PublicEnums; +using MewtocolNet.RegisterAttributes; using System; using System.Collections.Generic; using System.Linq; @@ -38,6 +39,11 @@ namespace MewtocolNet.RegisterBuilding { /// internal static StepBase AsType(this StepBase b, Type type) { + //check for nullable props + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) { + type = Nullable.GetUnderlyingType(type); + } + //for internal only, relay to AsType from string if (b.Data.buildSource == RegisterBuildSource.Attribute) { @@ -63,7 +69,29 @@ namespace MewtocolNet.RegisterBuilding { } - b.Data.dotnetVarType = type; + bool isDDTDT = b.Data.regType == RegisterPrefix.DT || b.Data.regType == RegisterPrefix.DDT; + + bool isDeclaredNormalRegisterAttribite = b.Data.boundPropertyAttribute != null && + b.Data.boundPropertyAttribute.GetType().DeclaringType == typeof(RegisterAttribute); + + if (b.Data.boundPropertyAttribute is BitRegisterAttribute && type != typeof(bool)) { + + throw new NotSupportedException($"Only booleans are allowed as the target type for BitRegister attributes"); + + } else if (isDeclaredNormalRegisterAttribite && isDDTDT && type == typeof(bool)) { + + throw new NotSupportedException($"Single bit DT registers are only supported with the BitRegister attribute"); + + } + + //special case type defintions for register that use an other underlying type + if (b.Data.regType == RegisterPrefix.DT && type == typeof(bool)) { + b.Data.dotnetVarType = typeof(Word); + } else if (b.Data.regType == RegisterPrefix.DDT && type == typeof(bool)) { + b.Data.dotnetVarType = typeof(DWord); + } else { + b.Data.dotnetVarType = type; + } return b; diff --git a/MewtocolNet/RegisterBuilding/StepData.cs b/MewtocolNet/RegisterBuilding/StepData.cs index 287078d..10ed430 100644 --- a/MewtocolNet/RegisterBuilding/StepData.cs +++ b/MewtocolNet/RegisterBuilding/StepData.cs @@ -36,6 +36,7 @@ namespace MewtocolNet.RegisterBuilding { //only for building from attributes internal RegisterCollection regCollection; internal PropertyInfo boundProperty; + internal RegisterAttribute boundPropertyAttribute; internal string typeDef; diff --git a/MewtocolNet/Registers/Base/IRegister.cs b/MewtocolNet/Registers/Base/IRegister.cs index fe589e6..e4cbe4f 100644 --- a/MewtocolNet/Registers/Base/IRegister.cs +++ b/MewtocolNet/Registers/Base/IRegister.cs @@ -14,6 +14,12 @@ namespace MewtocolNet.Registers { /// event RegisterChangedEventHandler ValueChanged; + /// + /// Defines if the register was auto generated from a property.
+ /// If so it is not allowed to remove the register from the interface stack + ///
+ bool IsAutoGenerated { get; } + /// /// Type of the underlying register /// diff --git a/MewtocolNet/Registers/Base/Register.cs b/MewtocolNet/Registers/Base/Register.cs index f1598a3..d5dd69d 100644 --- a/MewtocolNet/Registers/Base/Register.cs +++ b/MewtocolNet/Registers/Base/Register.cs @@ -21,7 +21,6 @@ namespace MewtocolNet.Registers { public event PropertyChangedEventHandler PropertyChanged; //links to - internal RegisterCollection containedCollection; internal MewtocolInterface attachedInterface; internal List boundProperties = new List(); @@ -50,7 +49,7 @@ namespace MewtocolNet.Registers { private float[] updateFreqAvgList; /// - internal RegisterCollection ContainedCollection => containedCollection; + public bool IsAutoGenerated => autoGenerated; /// internal MewtocolInterface AttachedInterface => attachedInterface; @@ -94,7 +93,7 @@ namespace MewtocolNet.Registers { } } - public string MemoryAreaInfo => underlyingMemory.GetName(); + public string MemoryAreaInfo => underlyingMemory.ToString(); public string MemoryAreaHash => underlyingMemory.GetHashCode().ToString(); @@ -131,6 +130,61 @@ namespace MewtocolNet.Registers { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ValueObj))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ValueStr))); + UpdateBoundProperties(); + + } + + private void UpdateBoundProperties () { + + //set the bound property values of there is one + + foreach (var prop in boundProperties) { + + //nullable + var boundPropType = prop.BoundProperty.PropertyType; + + if (boundPropType.IsGenericType && boundPropType.GetGenericTypeDefinition() == typeof(Nullable<>)) { + boundPropType = Nullable.GetUnderlyingType(boundPropType); + } + + bool isBitRegisterAttribute = prop.PropertyAttribute is BitRegisterAttribute; + + if (boundPropType == underlyingSystemType) { + + //the bound prop is the same type as the one of the underlying register + prop.BoundProperty.SetValue(prop.ContainedCollection, ValueObj); + prop.ContainedCollection.TriggerPropertyChanged(prop.BoundProperty.Name); + + } else if(boundPropType == typeof(bool) && isBitRegisterAttribute && underlyingSystemType == typeof(Word)) { + + var bitRegAttr = ((BitRegisterAttribute)prop.PropertyAttribute).bitIndex; + + if(ValueObj != null) { + prop.BoundProperty.SetValue(prop.ContainedCollection, ((Word)ValueObj)[bitRegAttr]); + } else { + prop.BoundProperty.SetValue(prop.ContainedCollection, null); + } + + prop.ContainedCollection.TriggerPropertyChanged(prop.BoundProperty.Name); + + + } else if (boundPropType == typeof(bool) && isBitRegisterAttribute && underlyingSystemType == typeof(DWord)) { + + var bitRegAttr = ((BitRegisterAttribute)prop.PropertyAttribute).bitIndex; + + if (ValueObj != null) { + prop.BoundProperty.SetValue(prop.ContainedCollection, ((DWord)ValueObj)[bitRegAttr]); + } else { + prop.BoundProperty.SetValue(prop.ContainedCollection, null); + } + + prop.ContainedCollection.TriggerPropertyChanged(prop.BoundProperty.Name); + + + } + + } + } private int updateFreqAvgListIteration = 0; @@ -206,13 +260,11 @@ namespace MewtocolNet.Registers { internal virtual object SetValueFromBytes(byte[] bytes) => throw new NotImplementedException(); - internal void WithRegisterCollection(RegisterCollection collection) => containedCollection = collection; - internal void WithBoundProperty(RegisterPropTarget propInfo) => boundProperties.Add(propInfo); internal void WithBoundProperties(IEnumerable propInfos) { - foreach (var item in propInfos) + foreach (var item in propInfos.ToArray()) boundProperties.Add(item); } @@ -227,10 +279,6 @@ namespace MewtocolNet.Registers { public virtual string GetRegisterString() => RegisterType.ToString(); - public virtual string GetCombinedName() => $"{GetContainerName()}{(GetContainerName() != null ? "." : "")}{Name ?? "Unnamed"}"; - - public virtual string GetContainerName() => $"{(containedCollection != null ? $"{containedCollection.GetType().Name}" : null)}"; - public virtual string GetMewName() => $"{GetRegisterString()}{MemoryAddress}"; public virtual uint GetRegisterAddressLen() => throw new NotImplementedException(); @@ -271,6 +319,12 @@ namespace MewtocolNet.Registers { } + internal virtual bool IsSameAddressTypeAndPollLevel(Register toCompare) { + + return IsSameAddressAndType(toCompare) && PollLevel == toCompare.PollLevel; + + } + internal int AveragePollLevel(List testAgainst, PollLevelOverwriteMode mode) { var whereAddressFitsInto = this.CanFitAddressRange(testAgainst) @@ -353,9 +407,6 @@ namespace MewtocolNet.Registers { 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 diff --git a/MewtocolNet/Registers/Classes/ArrayRegister.cs b/MewtocolNet/Registers/Classes/ArrayRegister.cs index 3fac936..8977b5a 100644 --- a/MewtocolNet/Registers/Classes/ArrayRegister.cs +++ b/MewtocolNet/Registers/Classes/ArrayRegister.cs @@ -87,7 +87,7 @@ namespace MewtocolNet.Registers { var encoded = PlcValueParser.EncodeArray(this, value); - var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded); + var res = await attachedInterface.WriteAreaByteRange((int)MemoryAddress, encoded); if (res) { @@ -116,7 +116,7 @@ namespace MewtocolNet.Registers { var encoded = PlcValueParser.EncodeArray(this, value); - var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded); + var res = await attachedInterface.WriteAreaByteRange((int)MemoryAddress, encoded); if (res) { @@ -144,7 +144,7 @@ namespace MewtocolNet.Registers { /// private async Task ReadAsync() { - var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2); + var res = await attachedInterface.ReadAreaByteRangeAsync((int)MemoryAddress, (int)GetRegisterAddressLen() * 2); if (res == null) throw new Exception(); var matchingReg = attachedInterface.memoryManager.GetAllRegisters() diff --git a/MewtocolNet/Registers/Classes/BoolRegister.cs b/MewtocolNet/Registers/Classes/BoolRegister.cs index 0665066..593fc27 100644 --- a/MewtocolNet/Registers/Classes/BoolRegister.cs +++ b/MewtocolNet/Registers/Classes/BoolRegister.cs @@ -1,19 +1,25 @@ using System; +using System.Collections; +using System.Linq; using System.Text; +using System.Threading.Tasks; namespace MewtocolNet.Registers { /// /// Defines a register containing a boolean /// - public class BoolRegister : Register { + public class BoolRegister : Register, IRegister { internal byte specialAddress; + /// /// The registers memory adress if not a special register /// public byte SpecialAddress => specialAddress; + public bool? Value => (bool?)ValueObj; + [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"); @@ -74,14 +80,67 @@ namespace MewtocolNet.Registers { /// public override uint GetRegisterAddressLen() => 1; + /// + public async Task WriteAsync(bool value) { + + var res = await WriteSingleBitAsync(value); + + if (res) { + + //find the underlying memory + var matchingReg = attachedInterface.memoryManager.GetAllRegisters() + .FirstOrDefault(x => x.IsSameAddressAndType(this)); + + if (matchingReg != null) + matchingReg.underlyingMemory.SetUnderlyingBits(matchingReg, specialAddress, value); + + AddSuccessWrite(); + UpdateHoldingValue(value); + + } + + } + + private async Task WriteSingleBitAsync(bool val) { + + var rawAddr = $"{MemoryAddress}{SpecialAddress.ToString("X1")}".PadLeft(4, '0'); + + string addStr = $"{GetRegisterString()}{rawAddr}"; + string cmd = $"%{attachedInterface.GetStationNumber()}#WCS{addStr}{(val ? "1" : "0")}"; + var res = await attachedInterface.SendCommandInternalAsync(cmd); + + return res.Success; + + } + + /// + public async Task ReadAsync() { + + var res = await attachedInterface.ReadAreaByteRangeAsync((int)MemoryAddress, (int)GetRegisterAddressLen() * 2); + if (res == null) throw new Exception($"Failed to read the register {this}"); + + var matchingReg = attachedInterface.memoryManager.GetAllRegisters() + .FirstOrDefault(x => x.IsSameAddressAndType(this)); + + if (matchingReg != null) { + + matchingReg.underlyingMemory.SetUnderlyingBytes(matchingReg, res); + + } + + return (bool)SetValueFromBytes(res); + + } + internal override object SetValueFromBytes(byte[] bytes) { AddSuccessRead(); - var parsed = PlcValueParser.Parse(this, bytes); + var bitArrVal = new BitArray(bytes)[SpecialAddress]; - UpdateHoldingValue(parsed); - return parsed; + UpdateHoldingValue(bitArrVal); + + return bitArrVal; } diff --git a/MewtocolNet/Registers/Classes/StringRegister.cs b/MewtocolNet/Registers/Classes/StringRegister.cs index 81dd5fe..425aa86 100644 --- a/MewtocolNet/Registers/Classes/StringRegister.cs +++ b/MewtocolNet/Registers/Classes/StringRegister.cs @@ -75,7 +75,7 @@ namespace MewtocolNet.Registers { } var encoded = PlcValueParser.Encode(this, value); - var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded); + var res = await attachedInterface.WriteAreaByteRange((int)MemoryAddress, encoded); if (res) { @@ -96,7 +96,7 @@ namespace MewtocolNet.Registers { /// public async Task ReadAsync() { - var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2); + var res = await attachedInterface.ReadAreaByteRangeAsync((int)MemoryAddress, (int)GetRegisterAddressLen() * 2); if (res == null) return null; var matchingReg = attachedInterface.memoryManager.GetAllRegisters() diff --git a/MewtocolNet/Registers/Classes/StructRegister.cs b/MewtocolNet/Registers/Classes/StructRegister.cs index 138581b..0b90ba7 100644 --- a/MewtocolNet/Registers/Classes/StructRegister.cs +++ b/MewtocolNet/Registers/Classes/StructRegister.cs @@ -125,7 +125,7 @@ namespace MewtocolNet.Registers { public async Task WriteAsync(T value) { var encoded = PlcValueParser.Encode(this, (T)value); - var res = await attachedInterface.WriteByteRange((int)MemoryAddress, encoded); + var res = await attachedInterface.WriteAreaByteRange((int)MemoryAddress, encoded); if (res) { @@ -146,7 +146,7 @@ namespace MewtocolNet.Registers { /// public async Task ReadAsync() { - var res = await attachedInterface.ReadByteRangeNonBlocking((int)MemoryAddress, (int)GetRegisterAddressLen() * 2); + var res = await attachedInterface.ReadAreaByteRangeAsync((int)MemoryAddress, (int)GetRegisterAddressLen() * 2); if (res == null) throw new Exception($"Failed to read the register {this}"); var matchingReg = attachedInterface.memoryManager.GetAllRegisters() diff --git a/MewtocolNet/SetupClasses/InterfaceSettings.cs b/MewtocolNet/SetupClasses/InterfaceSettings.cs index fc0a0c6..874b26b 100644 --- a/MewtocolNet/SetupClasses/InterfaceSettings.cs +++ b/MewtocolNet/SetupClasses/InterfaceSettings.cs @@ -34,7 +34,7 @@ namespace MewtocolNet.SetupClasses { /// Defines how many WORD blocks the interface will send on a DT area write request before splitting up messages
/// Higher numbers will result in a longer send and receive thread blocking time /// - public int MaxDataBlocksPerWrite { get; set; } = 8; + public int MaxDataBlocksPerWrite { get; set; } = 20; /// /// The send and receive timout for messages in milliseconds diff --git a/MewtocolNet/UnderlyingRegisters/Areas/AreaBase.cs b/MewtocolNet/UnderlyingRegisters/Areas/AreaBase.cs index eb9215a..2dc33cc 100644 --- a/MewtocolNet/UnderlyingRegisters/Areas/AreaBase.cs +++ b/MewtocolNet/UnderlyingRegisters/Areas/AreaBase.cs @@ -1,4 +1,6 @@ using MewtocolNet.Registers; +using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; @@ -6,7 +8,7 @@ using System.Threading.Tasks; namespace MewtocolNet.UnderlyingRegisters { - public abstract class AreaBase { + public class AreaBase { private MewtocolInterface mewInterface; @@ -66,7 +68,7 @@ namespace MewtocolNet.UnderlyingRegisters { internal async Task RequestByteReadAsync(ulong addStart, ulong addEnd) { var byteCount = (addEnd - addStart + 1) * 2; - var result = await mewInterface.ReadByteRangeNonBlocking((int)addStart, (int)byteCount); + var result = await mewInterface.ReadAreaByteRangeAsync((int)addStart, (int)byteCount, registerType); if (result != null) { @@ -104,6 +106,18 @@ namespace MewtocolNet.UnderlyingRegisters { } + public void SetUnderlyingBits(Register reg, int bitIndex, bool value) { + + var underlyingBefore = GetUnderlyingBytes(reg); + + var bitArr = new BitArray(underlyingBefore); + + bitArr.CopyTo(underlyingBefore, 0); + + SetUnderlyingBytes(underlyingBefore, reg.MemoryAddress); + + } + private void SetUnderlyingBytes(byte[] bytes, ulong addStart) { int copyOffset = (int)((addStart - addressStart) * 2); @@ -117,11 +131,21 @@ namespace MewtocolNet.UnderlyingRegisters { } - public override string ToString() => $"DT{AddressStart}-{AddressEnd}"; + public override string ToString() { - public virtual string GetName() => $"{ToString()} ({managedRegisters.Count} Registers)"; + switch (registerType) { + case RegisterPrefix.X: + case RegisterPrefix.Y: + case RegisterPrefix.R: + return $"W{registerType}{AddressStart}-{AddressEnd} ({managedRegisters.Count} Registers)"; + case RegisterPrefix.DT: + case RegisterPrefix.DDT: + return $"DT{AddressStart}-{AddressEnd} ({managedRegisters.Count} Registers)"; + } - public string GetHash() => GetHashCode().ToString(); + return ""; + + } } diff --git a/MewtocolNet/UnderlyingRegisters/Areas/DTArea.cs b/MewtocolNet/UnderlyingRegisters/Areas/DTArea.cs deleted file mode 100644 index e6a567e..0000000 --- a/MewtocolNet/UnderlyingRegisters/Areas/DTArea.cs +++ /dev/null @@ -1,19 +0,0 @@ -using MewtocolNet.Registers; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace MewtocolNet.UnderlyingRegisters { - - public class DTArea : AreaBase, IMemoryArea { - - internal DTArea(MewtocolInterface mewIf) : base(mewIf) { } - - public override string ToString() => $"DT{AddressStart}-{AddressEnd}"; - - public override string GetName() => $"{ToString()} ({managedRegisters.Count} Registers)"; - - } - -} diff --git a/MewtocolNet/UnderlyingRegisters/Areas/WRArea.cs b/MewtocolNet/UnderlyingRegisters/Areas/WRArea.cs deleted file mode 100644 index d980672..0000000 --- a/MewtocolNet/UnderlyingRegisters/Areas/WRArea.cs +++ /dev/null @@ -1,19 +0,0 @@ -using MewtocolNet.Registers; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace MewtocolNet.UnderlyingRegisters { - - public class WRArea : AreaBase, IMemoryArea { - - internal WRArea(MewtocolInterface mewIf) : base(mewIf) { } - - public override string ToString() => $"DT{AddressStart}-{AddressEnd}"; - - public override string GetName() => $"{ToString()} ({managedRegisters.Count} Registers)"; - - } - -} diff --git a/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs b/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs index a1f5e44..1f22cd3 100644 --- a/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs +++ b/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs @@ -94,6 +94,9 @@ namespace MewtocolNet.UnderlyingRegisters { AddToArea(reg, reg.RegisterType); + //reset overlap fitted for all + reg.wasOverlapFitted = false; + } //order @@ -102,23 +105,20 @@ namespace MewtocolNet.UnderlyingRegisters { PollLevel lvl = pollLevels[i]; //poll level has no areas - if(lvl.dataAreas.Count == 0 && - lvl.externalRelayInAreas.Count == 0 && - lvl.externalRelayOutAreas.Count == 0 && - lvl.internalRelayAreas.Count == 0) { + if(lvl.GetAllAreas().Count() == 0) { pollLevels.Remove(lvl); continue; } - foreach (var area in lvl.dataAreas) { + foreach (var area in lvl.GetAllAreas()) { area.managedRegisters = area.managedRegisters.OrderBy(x => x.AddressStart).ToList(); } - lvl.dataAreas = lvl.dataAreas.OrderBy(x => x.AddressStart).ToList(); + //lvl.dataAreas = lvl.dataAreas.OrderBy(x => x.AddressStart).ToList(); } @@ -221,7 +221,7 @@ namespace MewtocolNet.UnderlyingRegisters { //create a new area if (targetArea == null) { - targetArea = new DTArea(mewInterface) { + targetArea = new AreaBase(mewInterface) { addressStart = regInsAddStart, addressEnd = regInsAddEnd, registerType = insertReg.RegisterType, @@ -253,10 +253,12 @@ namespace MewtocolNet.UnderlyingRegisters { //check if the linked group has duplicate type registers - var dupedTypeReg = existinglinkedGroup.Linked.FirstOrDefault(x => x.IsSameAddressAndType(insertReg)); + var dupedTypeReg = existinglinkedGroup.Linked + .FirstOrDefault(x => x.IsSameAddressAndType(insertReg) && x.PollLevel == insertReg.PollLevel); - if (dupedTypeReg != null && insertReg.autoGenerated) { + if (dupedTypeReg != null) { dupedTypeReg.WithBoundProperties(insertReg.boundProperties); + dupedTypeReg.autoGenerated = insertReg.autoGenerated; } else { existinglinkedGroup.Linked.Add(insertReg); existinglinkedGroup.Linked = existinglinkedGroup.Linked.OrderBy(x => x.MemoryAddress).ToList(); diff --git a/MewtocolTests/TestHelperExtensions.cs b/MewtocolTests/TestHelperExtensions.cs index 7a18933..1aeb69a 100644 --- a/MewtocolTests/TestHelperExtensions.cs +++ b/MewtocolTests/TestHelperExtensions.cs @@ -66,7 +66,7 @@ namespace MewtocolTests { } - [Fact(DisplayName = nameof(MewtocolHelpers.ParseDTRawStringAsBytes))] + [Fact(DisplayName = nameof(MewtocolHelpers.ParseResponseStringAsBytes))] public void ParseDTByteStringGeneration() { var testList = new List() { @@ -77,7 +77,7 @@ namespace MewtocolTests { foreach (var item in testList) { - Assert.Equal(item, $"%01$RD{item.ToHexString()}".BCC_Mew().ParseDTRawStringAsBytes()); + Assert.Equal(item, $"%01$RD{item.ToHexString()}".BCC_Mew().ParseResponseStringAsBytes()); }