mirror of
https://github.com/OpenLogics/MewtocolNet.git
synced 2025-12-06 03:01:24 +00:00
Fixed canceling messages
This commit is contained in:
@@ -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">
|
||||
<Application.Resources>
|
||||
|
||||
<conv:NegationConverter x:Key="bInv"/>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
|
||||
14
Examples.WPF/Converters/NegationConverter.cs
Normal file
14
Examples.WPF/Converters/NegationConverter.cs
Normal file
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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<short>("DT0").Build();
|
||||
b.Struct<short>("DT0").AsArray(30).Build();
|
||||
b.Struct<Word>("DT1002").Build();
|
||||
|
||||
//b.Struct<short>("DT0").Build();
|
||||
//b.Struct<short>("DT0").AsArray(30).Build();
|
||||
|
||||
b.Struct<short>("DT1000").Build();
|
||||
//b.Struct<Word>("DT1000").Build();
|
||||
b.Struct<ushort>("DT1001").PollLevel(2).Build();
|
||||
b.Struct<Word>("DT1002").PollLevel(2).Build();
|
||||
|
||||
b.Struct<int>("DDT1010").PollLevel(2).Build();
|
||||
b.Struct<uint>("DDT1012").PollLevel(2).Build();
|
||||
b.Struct<DWord>("DDT1014").PollLevel(2).Build();
|
||||
b.Struct<float>("DDT1016").PollLevel(2).Build();
|
||||
b.Struct<TimeSpan>("DDT1018").PollLevel(2).Build();
|
||||
|
||||
b.String("DT1024", 32).PollLevel(3).Build();
|
||||
b.String("DT1042", 5).PollLevel(4).Build();
|
||||
|
||||
})
|
||||
.Build();
|
||||
|
||||
|
||||
@@ -9,14 +9,25 @@
|
||||
d:DataContext="{d:DesignInstance vm:PlcDataViewViewModel, IsDesignTimeCreatable=True}"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800">
|
||||
<Grid Margin="10">
|
||||
<Grid>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel>
|
||||
<Menu>
|
||||
<MenuItem Header="PLC">
|
||||
<MenuItem Header="Disconnect" IsEnabled="{Binding Plc.IsConnected}"
|
||||
Click="ClickedDisconnect"/>
|
||||
<MenuItem Header="Connect" IsEnabled="{Binding Plc.IsConnected, Converter={StaticResource bInv}}"
|
||||
Click="ClickedConnect"/>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
<StackPanel Margin="10"
|
||||
Grid.Row="1">
|
||||
|
||||
<TextBlock>
|
||||
|
||||
@@ -71,15 +82,16 @@
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<DataGrid Grid.Row="1"
|
||||
<DataGrid Grid.Row="2"
|
||||
AutoGenerateColumns="False"
|
||||
IsReadOnly="True"
|
||||
ItemsSource="{Binding Plc.Registers, Mode=OneWay}">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Address" Binding="{Binding PLCAddressName}"/>
|
||||
<DataGridTextColumn Header="Name" Binding="{Binding UnderlyingSystemType.Name}"/>
|
||||
<DataGridTextColumn Header="FP Address" Binding="{Binding PLCAddressName}"/>
|
||||
<DataGridTextColumn Header="Type" Binding="{Binding UnderlyingSystemType.Name}"/>
|
||||
<DataGridTextColumn Header="Value" Binding="{Binding ValueStr}"/>
|
||||
<!--<DataGridTextColumn Header="Value" Binding="{Binding PollLevel, Mode=OneWay}"/>-->
|
||||
<DataGridTextColumn Header="Poll Level" Binding="{Binding PollLevel, Mode=OneWay}"/>
|
||||
<DataGridTextColumn Header="Update Frequency" Binding="{Binding UpdateFreqHz, StringFormat='{}{0} Hz',Mode=OneWay}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) { }
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace MewtocolNet {
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs)) {
|
||||
if (task != await Task.WhenAny(task, tcs.Task)) {
|
||||
throw new OperationCanceledException(cancellationToken);
|
||||
return default(T);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ namespace MewtocolNet.Helpers {
|
||||
readonly object _locker = new object();
|
||||
readonly WeakReference<Task> _lastTask = new WeakReference<Task>(null);
|
||||
|
||||
internal CancellationTokenSource tSource = new CancellationTokenSource();
|
||||
|
||||
private List<Task> queuedTasks = new List<Task>();
|
||||
|
||||
@@ -37,37 +36,6 @@ namespace MewtocolNet.Helpers {
|
||||
|
||||
//}
|
||||
|
||||
internal Task<T> Enqueue<T>(Func<Task<T>> asyncFunction) {
|
||||
|
||||
lock (_locker) {
|
||||
|
||||
var token = tSource.Token;
|
||||
|
||||
Task lastTask;
|
||||
Task<T> 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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -89,14 +89,22 @@ namespace MewtocolNet {
|
||||
void Disconnect();
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the checksum automatically and sends a command to the PLC then awaits results
|
||||
/// Sends a command to the PLC then awaits results<br/>
|
||||
/// The checksum and BCC are appended automatically
|
||||
/// </summary>
|
||||
/// <param name="_msg">MEWTOCOL Formatted request string ex: %01#RT</param>
|
||||
/// <param name="withTerminator">Append the checksum and bcc automatically</param>
|
||||
/// <param name="timeoutMs">Timout to wait for a response</param>
|
||||
/// <returns>Returns the result</returns>
|
||||
Task<MewtocolFrameResponse> SendCommandAsync(string _msg, Action<double> onReceiveProgress = null);
|
||||
|
||||
/// <summary>
|
||||
/// Sends a command to the PLC and awaits its arrival<br/>
|
||||
/// The checksum and BCC are appended automatically.<br/>
|
||||
/// <b>Warning!</b> this command is only sent one directional, no error checking or other features included
|
||||
/// </summary>
|
||||
/// <param name="_msg">MEWTOCOL Formatted request string ex: %01#RT</param>
|
||||
/// <returns>Returns true if the message was received</returns>
|
||||
Task<bool> SendNoResponseCommandAsync(string _msg);
|
||||
|
||||
/// <summary>
|
||||
/// Changes the PLCs operation mode to the given one
|
||||
/// </summary>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<string> lastMsgs = new List<string>();
|
||||
|
||||
#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 {
|
||||
/// <inheritdoc/>
|
||||
public async Task<MewtocolFrameResponse> SendCommandAsync(string _msg, Action<double> 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<MewtocolFrameResponse> SendFrameAsync(string frame, Action<double> onReceiveProgress = null) {
|
||||
/// <inheritdoc/>
|
||||
public async Task<bool> 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<MewtocolFrameResponse> SendFrameAsync(string frame, Action<double> 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<Register>().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<Register>()
|
||||
.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<Register>().ToList().ForEach(x => x.OnPlcDisconnected());
|
||||
|
||||
//generate a new cancellation token source
|
||||
tSource = new CancellationTokenSource();
|
||||
|
||||
}
|
||||
|
||||
private void SetUpstreamStopWatchStart() {
|
||||
|
||||
@@ -61,8 +61,12 @@ namespace MewtocolNet {
|
||||
|
||||
private void StopHeartBeat () {
|
||||
|
||||
heartBeatTimer.Elapsed -= PollTimerTick;
|
||||
heartBeatTimer.Dispose();
|
||||
if(heartBeatTimer != null) {
|
||||
|
||||
heartBeatTimer.Elapsed -= PollTimerTick;
|
||||
heartBeatTimer.Dispose();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,11 @@ namespace MewtocolNet.Registers {
|
||||
/// </summary>
|
||||
int PollLevel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The update frequency of the register in Hz
|
||||
/// </summary>
|
||||
float UpdateFreqHz { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the register address name as in the plc
|
||||
/// </summary>
|
||||
|
||||
@@ -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 {
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal RegisterCollection ContainedCollection => containedCollection;
|
||||
|
||||
@@ -66,25 +77,110 @@ namespace MewtocolNet.Registers {
|
||||
public uint MemoryAddress => memoryAddress;
|
||||
|
||||
/// <inheritdoc/>
|
||||
int IRegister.PollLevel => pollLevel;
|
||||
public int PollLevel {
|
||||
get => pollLevel;
|
||||
internal set {
|
||||
PollLevel = value;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PollLevel)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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<float>(0, updateFreqAvgList.Length).ToArray();
|
||||
updateFreqAvgListIteration = 1;
|
||||
|
||||
} else if(updateFreqAvgListIteration == updateFreqAvgList.Length - 1) {
|
||||
|
||||
updateFreqAvgList = Enumerable.Repeat<float>(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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
/// <inheritdoc/>
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -498,7 +498,7 @@ namespace MewtocolNet.UnderlyingRegisters {
|
||||
|
||||
}
|
||||
|
||||
return registers;
|
||||
return registers.OrderBy(x => x.MemoryAddress).ThenByDescending(x => x.GetRegisterString());
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user