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());
}