diff --git a/Examples.WPF/App.xaml b/Examples.WPF/App.xaml index a35b010..fe9a706 100644 --- a/Examples.WPF/App.xaml +++ b/Examples.WPF/App.xaml @@ -2,8 +2,9 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Examples.WPF" + xmlns:conv="clr-namespace:Examples.WPF.Converters" StartupUri="MainWindow.xaml"> - + diff --git a/Examples.WPF/Converters/NegationConverter.cs b/Examples.WPF/Converters/NegationConverter.cs new file mode 100644 index 0000000..4cba113 --- /dev/null +++ b/Examples.WPF/Converters/NegationConverter.cs @@ -0,0 +1,14 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace Examples.WPF.Converters; + +[ValueConversion(typeof(bool), typeof(bool))] +public class NegationConverter : IValueConverter { + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => value != null ? !(bool)value : false; + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => value != null ? !(bool)value : false; + +} diff --git a/Examples.WPF/Views/ConnectView.xaml.cs b/Examples.WPF/Views/ConnectView.xaml.cs index 2ade4ac..637c8e4 100644 --- a/Examples.WPF/Views/ConnectView.xaml.cs +++ b/Examples.WPF/Views/ConnectView.xaml.cs @@ -59,10 +59,30 @@ public partial class ConnectView : UserControl { s.TryReconnectAttempts = 30; s.TryReconnectDelayMs = 2000; }) + .WithCustomPollLevels(l => { + l.SetLevel(2, 3); + l.SetLevel(3, TimeSpan.FromSeconds(5)); + l.SetLevel(4, TimeSpan.FromSeconds(10)); + }) .WithRegisters(b => { - b.Struct("DT0").Build(); - b.Struct("DT0").AsArray(30).Build(); - b.Struct("DT1002").Build(); + + //b.Struct("DT0").Build(); + //b.Struct("DT0").AsArray(30).Build(); + + b.Struct("DT1000").Build(); + //b.Struct("DT1000").Build(); + b.Struct("DT1001").PollLevel(2).Build(); + b.Struct("DT1002").PollLevel(2).Build(); + + b.Struct("DDT1010").PollLevel(2).Build(); + b.Struct("DDT1012").PollLevel(2).Build(); + b.Struct("DDT1014").PollLevel(2).Build(); + b.Struct("DDT1016").PollLevel(2).Build(); + b.Struct("DDT1018").PollLevel(2).Build(); + + b.String("DT1024", 32).PollLevel(3).Build(); + b.String("DT1042", 5).PollLevel(4).Build(); + }) .Build(); diff --git a/Examples.WPF/Views/PlcDataView.xaml b/Examples.WPF/Views/PlcDataView.xaml index b7be31f..5dee500 100644 --- a/Examples.WPF/Views/PlcDataView.xaml +++ b/Examples.WPF/Views/PlcDataView.xaml @@ -9,14 +9,25 @@ d:DataContext="{d:DesignInstance vm:PlcDataViewViewModel, IsDesignTimeCreatable=True}" d:DesignHeight="450" d:DesignWidth="800"> - + + - + + + + + + + + @@ -71,15 +82,16 @@ - - - + + - + + diff --git a/Examples.WPF/Views/PlcDataView.xaml.cs b/Examples.WPF/Views/PlcDataView.xaml.cs index ac9e834..11e23f2 100644 --- a/Examples.WPF/Views/PlcDataView.xaml.cs +++ b/Examples.WPF/Views/PlcDataView.xaml.cs @@ -29,4 +29,16 @@ public partial class PlcDataView : UserControl { } + private void ClickedDisconnect(object sender, RoutedEventArgs e) { + + viewModel.Plc.Disconnect(); + + } + + private async void ClickedConnect(object sender, RoutedEventArgs e) { + + await viewModel.Plc.ConnectAsync(); + + } + } diff --git a/MewtocolNet/ComCassette/CassetteFinder.cs b/MewtocolNet/ComCassette/CassetteFinder.cs index 809dbdd..194ab8c 100644 --- a/MewtocolNet/ComCassette/CassetteFinder.cs +++ b/MewtocolNet/ComCassette/CassetteFinder.cs @@ -113,6 +113,8 @@ namespace MewtocolNet.ComCassette { while (!tSource.Token.IsCancellationRequested) { + if (tSource.Token.IsCancellationRequested) break; + var res = await udpClient.ReceiveAsync().WithCancellation(tSource.Token); if (res.Buffer == null) break; @@ -131,7 +133,9 @@ namespace MewtocolNet.ComCassette { } - } catch (OperationCanceledException) { } catch (SocketException) { } + } + catch (OperationCanceledException) { } + catch (SocketException) { } } diff --git a/MewtocolNet/Extensions/AsyncExtensions.cs b/MewtocolNet/Extensions/AsyncExtensions.cs index f7f5c8a..44f2562 100644 --- a/MewtocolNet/Extensions/AsyncExtensions.cs +++ b/MewtocolNet/Extensions/AsyncExtensions.cs @@ -11,7 +11,7 @@ namespace MewtocolNet { var tcs = new TaskCompletionSource(); using (cancellationToken.Register(s => ((TaskCompletionSource)s).TrySetResult(true), tcs)) { if (task != await Task.WhenAny(task, tcs.Task)) { - throw new OperationCanceledException(cancellationToken); + return default(T); } } diff --git a/MewtocolNet/Helpers/AsyncQueue.cs b/MewtocolNet/Helpers/AsyncQueue.cs index 6ca45fe..0e05257 100644 --- a/MewtocolNet/Helpers/AsyncQueue.cs +++ b/MewtocolNet/Helpers/AsyncQueue.cs @@ -10,7 +10,6 @@ namespace MewtocolNet.Helpers { readonly object _locker = new object(); readonly WeakReference _lastTask = new WeakReference(null); - internal CancellationTokenSource tSource = new CancellationTokenSource(); private List queuedTasks = new List(); @@ -37,37 +36,6 @@ namespace MewtocolNet.Helpers { //} - internal Task Enqueue(Func> asyncFunction) { - - lock (_locker) { - - var token = tSource.Token; - - Task lastTask; - Task resultTask; - - if (_lastTask.TryGetTarget(out lastTask)) { - resultTask = lastTask.ContinueWith(_ => asyncFunction(), token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Current).Unwrap(); - } else { - resultTask = Task.Run(asyncFunction, token); - } - - _lastTask.SetTarget(resultTask); - - return resultTask; - - } - - } - - internal void CancelAll () { - - tSource.Cancel(); - - Console.WriteLine(); - - } - } } diff --git a/MewtocolNet/IPlc.cs b/MewtocolNet/IPlc.cs index 95d65c7..c258061 100644 --- a/MewtocolNet/IPlc.cs +++ b/MewtocolNet/IPlc.cs @@ -89,14 +89,22 @@ namespace MewtocolNet { void Disconnect(); /// - /// Calculates the checksum automatically and sends a command to the PLC then awaits results + /// Sends a command to the PLC then awaits results
+ /// The checksum and BCC are appended automatically ///
/// MEWTOCOL Formatted request string ex: %01#RT - /// Append the checksum and bcc automatically - /// Timout to wait for a response /// Returns the result Task SendCommandAsync(string _msg, Action onReceiveProgress = null); + /// + /// Sends a command to the PLC and awaits its arrival
+ /// The checksum and BCC are appended automatically.
+ /// Warning! this command is only sent one directional, no error checking or other features included + ///
+ /// MEWTOCOL Formatted request string ex: %01#RT + /// Returns true if the message was received + Task SendNoResponseCommandAsync(string _msg); + /// /// Changes the PLCs operation mode to the given one /// diff --git a/MewtocolNet/MewtocolFrameResponse.cs b/MewtocolNet/MewtocolFrameResponse.cs index 9533d33..4302a13 100644 --- a/MewtocolNet/MewtocolFrameResponse.cs +++ b/MewtocolNet/MewtocolFrameResponse.cs @@ -19,6 +19,8 @@ namespace MewtocolNet public static MewtocolFrameResponse Canceled => new MewtocolFrameResponse(500, "Op was canceled by the library"); + public static MewtocolFrameResponse EmptySuccess => new MewtocolFrameResponse() { Success = true }; + public MewtocolFrameResponse(string response) { Success = true; diff --git a/MewtocolNet/MewtocolInterface.cs b/MewtocolNet/MewtocolInterface.cs index 93019a7..a24e345 100644 --- a/MewtocolNet/MewtocolInterface.cs +++ b/MewtocolNet/MewtocolInterface.cs @@ -38,6 +38,9 @@ namespace MewtocolNet { #region Private fields + //cancellation token for the messages + internal CancellationTokenSource tSource = new CancellationTokenSource(); + private protected Stream stream; private int tcpMessagesSentThisCycle = 0; @@ -64,18 +67,20 @@ namespace MewtocolNet { private protected bool wasInitialStatusReceived; private protected MewtocolVersion mewtocolVersion; - //private protected string[] lastMsgs = new string[10]; private protected List lastMsgs = new List(); #endregion #region Internal fields + internal protected System.Timers.Timer cyclicGenericUpdateCounter; internal event Action PolledCycle; internal volatile bool pollerTaskStopped = true; internal volatile bool pollerFirstCycle; internal bool usePoller = false; internal MemoryAreaManager memoryManager; + + private volatile bool isMessageLocked; private volatile bool isReceiving; private volatile bool isSending; @@ -356,6 +361,11 @@ namespace MewtocolNet { /// public async Task SendCommandAsync(string _msg, Action onReceiveProgress = null) { + if (isMessageLocked) + throw new NotSupportedException("Can't send multiple messages in parallel"); + + isMessageLocked = true; + await AwaitReconnectTaskAsync(); if (!IsConnected && !isConnectingStage) @@ -367,23 +377,35 @@ namespace MewtocolNet { //wait for the last send task to complete if (regularSendTask != null && !regularSendTask.IsCompleted) await regularSendTask; - regularSendTask = SendFrameAsync(_msg, onReceiveProgress); + regularSendTask = SendFrameAsync(_msg, onReceiveProgress, false); if (await Task.WhenAny(regularSendTask, Task.Delay(2000)) != regularSendTask) { + isMessageLocked = false; + // timeout logic return MewtocolFrameResponse.Timeout; } //canceled - if (regularSendTask.IsCanceled) return MewtocolFrameResponse.Canceled; + if (regularSendTask.IsCanceled) { + + isMessageLocked = false; + return MewtocolFrameResponse.Canceled; + + } tcpMessagesSentThisCycle++; queuedMessages--; //success - if (regularSendTask.Result.Success) return regularSendTask.Result; + if (regularSendTask.Result.Success) { + + isMessageLocked = false; + return regularSendTask.Result; + + } //no success if(reconnectTask == null) StartReconnectTask(); @@ -394,15 +416,63 @@ namespace MewtocolNet { //re-send the command if (IsConnected) { + isMessageLocked = false; return await SendCommandAsync(_msg, onReceiveProgress); } - return MewtocolFrameResponse.Timeout; + isMessageLocked = false; + return regularSendTask.Result; } - private protected async Task SendFrameAsync(string frame, Action onReceiveProgress = null) { + /// + public async Task SendNoResponseCommandAsync(string _msg) { + + if (isMessageLocked) + throw new NotSupportedException("Can't send multiple messages in parallel"); + + isMessageLocked = true; + + await AwaitReconnectTaskAsync(); + + if (!IsConnected && !isConnectingStage) + throw new NotSupportedException("The device must be connected to send a message"); + + //send request + queuedMessages++; + + //wait for the last send task to complete + if (regularSendTask != null && !regularSendTask.IsCompleted) await regularSendTask; + + regularSendTask = SendFrameAsync(_msg, null, true); + + if (await Task.WhenAny(regularSendTask, Task.Delay(2000)) != regularSendTask) { + + isMessageLocked = false; + + // timeout logic + return false; + + } + + //canceled + if (regularSendTask.IsCanceled) { + + isMessageLocked = false; + return false; + + } + + tcpMessagesSentThisCycle++; + queuedMessages--; + + isMessageLocked = false; + return true; + + } + + private protected async Task SendFrameAsync(string frame, Action onReceiveProgress, bool noResponse) { try { @@ -414,9 +484,11 @@ namespace MewtocolNet { IsSending = true; + if (tSource.Token.IsCancellationRequested) return MewtocolFrameResponse.Canceled; + //write inital command byte[] writeBuffer = Encoding.UTF8.GetBytes(frame); - stream.Write(writeBuffer, 0, writeBuffer.Length); + await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length, tSource.Token); IsSending = false; @@ -439,6 +511,13 @@ namespace MewtocolNet { OnOutMsg(frame); + if(noResponse) { + + Logger.Log($"[---------CMD END----------]", LogLevel.Critical, this); + return MewtocolFrameResponse.EmptySuccess; + + } + var readResult = await ReadCommandAsync(wordsCountRequested, onReceiveProgress); //did not receive bytes but no errors, the com port was not configured right @@ -487,6 +566,10 @@ namespace MewtocolNet { return new MewtocolFrameResponse(resString); + } catch (OperationCanceledException) { + + return MewtocolFrameResponse.Canceled; + } catch (Exception ex) { return new MewtocolFrameResponse(400, ex.Message.ToString(System.Globalization.CultureInfo.InvariantCulture)); @@ -515,7 +598,10 @@ namespace MewtocolNet { byte[] buffer = new byte[RecBufferSize]; IsReceiving = true; - int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, queue.tSource.Token); + + if (tSource.Token.IsCancellationRequested) break; + + int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, tSource.Token); IsReceiving = false; CalcDownstreamSpeed(bytesRead); @@ -536,7 +622,7 @@ namespace MewtocolNet { //request next frame var writeBuffer = Encoding.UTF8.GetBytes($"%{GetStationNumber()}**&\r"); IsSending = true; - await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length, queue.tSource.Token); + await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length, tSource.Token); IsSending = false; Logger.Log($">> Requested next frame", LogLevel.Critical, this); wasMultiFramedResponse = true; @@ -655,7 +741,7 @@ namespace MewtocolNet { if (IsConnected) { - queue.CancelAll(); + tSource.Cancel(); Logger.Log("The PLC connection timed out", LogLevel.Error, this); OnDisconnect(); @@ -668,7 +754,7 @@ namespace MewtocolNet { if (IsConnected) { - queue.CancelAll(); + tSource.Cancel(); Logger.Log("The PLC connection was closed", LogLevel.Error, this); OnDisconnect(); @@ -687,7 +773,7 @@ namespace MewtocolNet { private protected void OnSocketExceptionWhileConnected () { - queue.CancelAll(); + tSource.Cancel(); IsConnected = false; @@ -697,6 +783,14 @@ namespace MewtocolNet { Logger.Log("Connected to PLC", LogLevel.Info, this); + //start timer for register update data + cyclicGenericUpdateCounter = new System.Timers.Timer(1000); + cyclicGenericUpdateCounter.Elapsed += OnGenericUpdateTimerTick; + cyclicGenericUpdateCounter.Start(); + + //notify the registers + GetAllRegisters().Cast().ToList().ForEach(x => x.OnPlcConnected()); + IsConnected = true; Connected?.Invoke(this, new PlcConnectionArgs()); @@ -715,6 +809,13 @@ namespace MewtocolNet { } + private void OnGenericUpdateTimerTick(object sender, System.Timers.ElapsedEventArgs e) { + + GetAllRegisters().Cast() + .ToList().ForEach(x => x.OnInterfaceCyclicTimerUpdate((int)cyclicGenericUpdateCounter.Interval)); + + } + private protected virtual void OnDisconnect() { IsReceiving = false; @@ -730,6 +831,18 @@ namespace MewtocolNet { Disconnected?.Invoke(this, new PlcConnectionArgs()); KillPoller(); + if(cyclicGenericUpdateCounter != null) { + + cyclicGenericUpdateCounter.Elapsed -= OnGenericUpdateTimerTick; + cyclicGenericUpdateCounter.Dispose(); + + } + + GetAllRegisters().Cast().ToList().ForEach(x => x.OnPlcDisconnected()); + + //generate a new cancellation token source + tSource = new CancellationTokenSource(); + } private void SetUpstreamStopWatchStart() { diff --git a/MewtocolNet/MewtocolInterfaceRegisterHandling.cs b/MewtocolNet/MewtocolInterfaceRegisterHandling.cs index 43a740e..42bd923 100644 --- a/MewtocolNet/MewtocolInterfaceRegisterHandling.cs +++ b/MewtocolNet/MewtocolInterfaceRegisterHandling.cs @@ -61,9 +61,13 @@ namespace MewtocolNet { private void StopHeartBeat () { - heartBeatTimer.Elapsed -= PollTimerTick; - heartBeatTimer.Dispose(); + if(heartBeatTimer != null) { + heartBeatTimer.Elapsed -= PollTimerTick; + heartBeatTimer.Dispose(); + + } + } private void TestPollerStartNeeded () { diff --git a/MewtocolNet/MewtocolInterfaceRequests.cs b/MewtocolNet/MewtocolInterfaceRequests.cs index 78b343c..6537024 100644 --- a/MewtocolNet/MewtocolInterfaceRequests.cs +++ b/MewtocolNet/MewtocolInterfaceRequests.cs @@ -231,7 +231,9 @@ namespace MewtocolNet { if (result.Success && !string.IsNullOrEmpty(result.Response)) { var bytes = result.Response.ParseDTRawStringAsBytes(); - readBytes.AddRange(bytes); + + if(bytes != null) + readBytes.AddRange(bytes); } diff --git a/MewtocolNet/MewtocolInterfaceTcp.cs b/MewtocolNet/MewtocolInterfaceTcp.cs index 59c4184..0fe3a2b 100644 --- a/MewtocolNet/MewtocolInterfaceTcp.cs +++ b/MewtocolNet/MewtocolInterfaceTcp.cs @@ -126,17 +126,23 @@ namespace MewtocolNet { Logger.Log("The PLC connection timed out", LogLevel.Error, this); OnMajorSocketExceptionWhileConnecting(); return; + } + Logger.LogVerbose("TCP/IP Client connected", this); + if (HostEndpoint == null) { var ep = (IPEndPoint)client.Client.LocalEndPoint; - Logger.Log($"Connecting [AUTO] endpoint: {ep.Address.MapToIPv4()}:{ep.Port}", LogLevel.Info, this); + Logger.Log($"Connecting [AUTO] from: {ep.Address.MapToIPv4()}:{ep.Port} to {GetConnectionInfo()}", LogLevel.Info, this); } //get the stream stream = client.GetStream(); stream.ReadTimeout = 1000; + //try to abort any non read message + //await SendNoResponseCommandAsync($"%{GetStationNumber()}#AB"); + //get plc info var plcinf = await GetPLCInfoAsync(ConnectTimeout); @@ -179,9 +185,6 @@ namespace MewtocolNet { var result = client.BeginConnect(ipAddr, Port, null, null); var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(conTimeout)); - if (client.Connected) - Logger.LogVerbose("TCP/IP Client connected", this); - if (!success || !client.Connected) { Logger.Log("The PLC connection timed out", LogLevel.Error, this); @@ -189,6 +192,8 @@ namespace MewtocolNet { return; } + Logger.LogVerbose("TCP/IP Client connected", this); + if (HostEndpoint == null) { var ep = (IPEndPoint)client.Client.LocalEndPoint; Logger.Log($"Connecting [AUTO] endpoint: {ep.Address.MapToIPv4()}:{ep.Port}", LogLevel.Info, this); @@ -243,7 +248,7 @@ namespace MewtocolNet { if (client != null && client.Connected) { - client.Close(); + client.Dispose(); } diff --git a/MewtocolNet/Registers/Base/IRegister.cs b/MewtocolNet/Registers/Base/IRegister.cs index 9ebd047..44380db 100644 --- a/MewtocolNet/Registers/Base/IRegister.cs +++ b/MewtocolNet/Registers/Base/IRegister.cs @@ -29,6 +29,11 @@ namespace MewtocolNet.Registers { /// int PollLevel { get; } + /// + /// The update frequency of the register in Hz + /// + float UpdateFreqHz { get; } + /// /// Gets the register address name as in the plc /// diff --git a/MewtocolNet/Registers/Base/Register.cs b/MewtocolNet/Registers/Base/Register.cs index 9a50d39..57cf62b 100644 --- a/MewtocolNet/Registers/Base/Register.cs +++ b/MewtocolNet/Registers/Base/Register.cs @@ -4,6 +4,7 @@ using MewtocolNet.UnderlyingRegisters; using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -17,6 +18,8 @@ namespace MewtocolNet.Registers { /// public event RegisterChangedEventHandler ValueChanged; + public event PropertyChangedEventHandler PropertyChanged; + //links to internal RegisterCollection containedCollection; internal MewtocolInterface attachedInterface; @@ -36,8 +39,16 @@ namespace MewtocolNet.Registers { internal uint successfulReads = 0; internal uint successfulWrites = 0; + internal int updateCountTimerCycle; + internal float updateFreqHz; + internal bool wasOverlapFitted = false; + private Stopwatch timeSinceLastUpdate; + + private float updateFreqFastCount; + private float[] updateFreqAvgList; + /// internal RegisterCollection ContainedCollection => containedCollection; @@ -66,25 +77,110 @@ namespace MewtocolNet.Registers { public uint MemoryAddress => memoryAddress; /// - int IRegister.PollLevel => pollLevel; + public int PollLevel { + get => pollLevel; + internal set { + PollLevel = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PollLevel))); + } + } + + /// + public float UpdateFreqHz { + get => updateFreqHz; + private set { + updateFreqHz = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(UpdateFreqHz))); + } + } + + + internal Register() { } + + internal virtual void OnPlcConnected () { + + timeSinceLastUpdate = Stopwatch.StartNew(); + updateFreqAvgListIteration = 0; + updateFreqAvgList = new float[10]; + + + } + + internal virtual void OnPlcDisconnected () { + + timeSinceLastUpdate?.Stop(); + UpdateFreqHz = default; + updateFreqAvgList = new float[10]; + updateFreqFastCount = default; + + } + + internal virtual void OnInterfaceCyclicTimerUpdate (int cyclicTimerRateMs) { + + UpdateCountTimerTick(cyclicTimerRateMs); + + } #region Trigger update notify - public event PropertyChangedEventHandler PropertyChanged; - - public void TriggerNotifyChange() { + public void TriggerValueChange() { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ValueObj))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ValueStr))); } + private int updateFreqAvgListIteration = 0; + + protected void TriggerUpdateReceived () { + + updateCountTimerCycle++; + + if(updateCountTimerCycle >= 1) { + + var updateRateMultiplicator = (float)timeSinceLastUpdate.ElapsedMilliseconds / 1000; + updateFreqFastCount = ((float)updateCountTimerCycle) / updateRateMultiplicator; + + //populate with first value + if(updateFreqAvgListIteration == 0) { + + updateFreqAvgList = Enumerable.Repeat(0, updateFreqAvgList.Length).ToArray(); + updateFreqAvgListIteration = 1; + + } else if(updateFreqAvgListIteration == updateFreqAvgList.Length - 1) { + + updateFreqAvgList = Enumerable.Repeat(updateFreqAvgList.Average(), updateFreqAvgList.Length).ToArray(); + updateFreqAvgListIteration = 1; + + } else { + + updateFreqAvgList[updateFreqAvgListIteration] = updateFreqFastCount; + updateFreqAvgListIteration++; + + } + + //Reset + updateCountTimerCycle = 0; + timeSinceLastUpdate.Restart(); + + } + + } + + private void UpdateCountTimerTick(int cyclicTimerRateMs) { + + UpdateFreqHz = (float)Math.Round(updateFreqAvgList.Average(), 2); + + } + #endregion public virtual void ClearValue() => UpdateHoldingValue(null); internal virtual void UpdateHoldingValue(object val) { + TriggerUpdateReceived(); + bool nullDiff = false; if (val == null && lastValue != null) nullDiff = true; if (val != null && lastValue == null) nullDiff = true; @@ -96,7 +192,7 @@ namespace MewtocolNet.Registers { lastValue = val; - TriggerNotifyChange(); + TriggerValueChange(); attachedInterface.InvokeRegisterChanged(this, beforeVal, beforeValStr); } diff --git a/MewtocolNet/Registers/Classes/ArrayRegister.cs b/MewtocolNet/Registers/Classes/ArrayRegister.cs index 0d8c2b4..3fac936 100644 --- a/MewtocolNet/Registers/Classes/ArrayRegister.cs +++ b/MewtocolNet/Registers/Classes/ArrayRegister.cs @@ -43,7 +43,7 @@ namespace MewtocolNet.Registers { public ArrayRegister() => throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern"); - internal ArrayRegister(uint _address, uint _reservedByteSize, int[] _indices, string _name = null) { + internal ArrayRegister(uint _address, uint _reservedByteSize, int[] _indices, string _name = null) : base() { if (_reservedByteSize % 2 != 0) throw new ArgumentException(nameof(_reservedByteSize), "Reserved byte size must be even"); @@ -174,6 +174,8 @@ namespace MewtocolNet.Registers { /// internal override void UpdateHoldingValue(object val) { + TriggerUpdateReceived(); + if (val == null && lastValue == null) return; bool sequenceDifference = false; @@ -196,7 +198,7 @@ namespace MewtocolNet.Registers { lastValue = val; - TriggerNotifyChange(); + TriggerValueChange(); attachedInterface.InvokeRegisterChanged(this, beforeVal, beforeValStr); } diff --git a/MewtocolNet/Registers/Classes/BoolRegister.cs b/MewtocolNet/Registers/Classes/BoolRegister.cs index 7c9233a..59fa41c 100644 --- a/MewtocolNet/Registers/Classes/BoolRegister.cs +++ b/MewtocolNet/Registers/Classes/BoolRegister.cs @@ -18,7 +18,7 @@ namespace MewtocolNet.Registers { public BoolRegister() => throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern"); - internal BoolRegister(SingleBitPrefix _io, byte _spAddress = 0x0, uint _areaAdress = 0, string _name = null) { + internal BoolRegister(SingleBitPrefix _io, byte _spAddress = 0x0, uint _areaAdress = 0, string _name = null) : base() { lastValue = null; diff --git a/MewtocolNet/Registers/Classes/StringRegister.cs b/MewtocolNet/Registers/Classes/StringRegister.cs index e2088f0..6a8d348 100644 --- a/MewtocolNet/Registers/Classes/StringRegister.cs +++ b/MewtocolNet/Registers/Classes/StringRegister.cs @@ -31,7 +31,7 @@ namespace MewtocolNet.Registers { public StringRegister() => throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern"); - internal StringRegister(uint _address, uint _reservedByteSize, string _name = null) { + internal StringRegister(uint _address, uint _reservedByteSize, string _name = null) : base() { memoryAddress = _address; name = _name; @@ -122,10 +122,11 @@ namespace MewtocolNet.Registers { //if string correct the sizing of the byte hint was wrong var reservedSize = BitConverter.ToInt16(bytes, 0); - if (reservedSize != byteLength - 4) + + if (reservedStringLength != reservedSize) throw new NotSupportedException( $"The STRING register at {GetMewName()} is not correctly sized, " + - $"the size should be STRING[{reservedSize}] instead of STRING[{byteLength - 4}]" + $"the size should be STRING[{reservedSize}] instead of STRING[{reservedStringLength}]" ); AddSuccessRead(); @@ -139,6 +140,8 @@ namespace MewtocolNet.Registers { internal override void UpdateHoldingValue(object val) { + TriggerUpdateReceived(); + if (lastValue?.ToString() != val?.ToString()) { var beforeVal = lastValue; @@ -146,7 +149,7 @@ namespace MewtocolNet.Registers { lastValue = val; - TriggerNotifyChange(); + TriggerValueChange(); attachedInterface.InvokeRegisterChanged(this, beforeVal, beforeValStr); } diff --git a/MewtocolNet/Registers/Classes/StructRegister.cs b/MewtocolNet/Registers/Classes/StructRegister.cs index 36776dd..138581b 100644 --- a/MewtocolNet/Registers/Classes/StructRegister.cs +++ b/MewtocolNet/Registers/Classes/StructRegister.cs @@ -36,7 +36,7 @@ namespace MewtocolNet.Registers { throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern"); //struct for 16-32bit registers - internal StructRegister(uint _address, uint _reservedByteSize, string _name = null) { + internal StructRegister(uint _address, uint _reservedByteSize, string _name = null) : base() { memoryAddress = _address; specialAddress = 0x0; @@ -56,7 +56,7 @@ namespace MewtocolNet.Registers { } //struct for one bit registers - internal StructRegister(SingleBitPrefix _io, byte _spAddress = 0x0, uint _areaAdress = 0, string _name = null) { + internal StructRegister(SingleBitPrefix _io, byte _spAddress = 0x0, uint _areaAdress = 0, string _name = null) : base() { lastValue = null; @@ -173,22 +173,6 @@ namespace MewtocolNet.Registers { } - internal override void UpdateHoldingValue(object val) { - - if (lastValue?.ToString() != val?.ToString()) { - - var beforeVal = lastValue; - var beforeValStr = GetValueString(); - - lastValue = val; - - TriggerNotifyChange(); - attachedInterface.InvokeRegisterChanged(this, beforeVal, beforeValStr); - - } - - } - } } diff --git a/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs b/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs index af5be11..60be99b 100644 --- a/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs +++ b/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs @@ -498,7 +498,7 @@ namespace MewtocolNet.UnderlyingRegisters { } - return registers; + return registers.OrderBy(x => x.MemoryAddress).ThenByDescending(x => x.GetRegisterString()); }