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="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:local="clr-namespace:Examples.WPF"
|
xmlns:local="clr-namespace:Examples.WPF"
|
||||||
|
xmlns:conv="clr-namespace:Examples.WPF.Converters"
|
||||||
StartupUri="MainWindow.xaml">
|
StartupUri="MainWindow.xaml">
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
|
<conv:NegationConverter x:Key="bInv"/>
|
||||||
</Application.Resources>
|
</Application.Resources>
|
||||||
</Application>
|
</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.TryReconnectAttempts = 30;
|
||||||
s.TryReconnectDelayMs = 2000;
|
s.TryReconnectDelayMs = 2000;
|
||||||
})
|
})
|
||||||
|
.WithCustomPollLevels(l => {
|
||||||
|
l.SetLevel(2, 3);
|
||||||
|
l.SetLevel(3, TimeSpan.FromSeconds(5));
|
||||||
|
l.SetLevel(4, TimeSpan.FromSeconds(10));
|
||||||
|
})
|
||||||
.WithRegisters(b => {
|
.WithRegisters(b => {
|
||||||
b.Struct<short>("DT0").Build();
|
|
||||||
b.Struct<short>("DT0").AsArray(30).Build();
|
//b.Struct<short>("DT0").Build();
|
||||||
b.Struct<Word>("DT1002").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();
|
.Build();
|
||||||
|
|
||||||
|
|||||||
@@ -9,14 +9,25 @@
|
|||||||
d:DataContext="{d:DesignInstance vm:PlcDataViewViewModel, IsDesignTimeCreatable=True}"
|
d:DataContext="{d:DesignInstance vm:PlcDataViewViewModel, IsDesignTimeCreatable=True}"
|
||||||
d:DesignHeight="450"
|
d:DesignHeight="450"
|
||||||
d:DesignWidth="800">
|
d:DesignWidth="800">
|
||||||
<Grid Margin="10">
|
<Grid>
|
||||||
|
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="auto"/>
|
||||||
<RowDefinition Height="auto"/>
|
<RowDefinition Height="auto"/>
|
||||||
<RowDefinition/>
|
<RowDefinition/>
|
||||||
</Grid.RowDefinitions>
|
</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>
|
<TextBlock>
|
||||||
|
|
||||||
@@ -71,15 +82,16 @@
|
|||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<DataGrid Grid.Row="1"
|
<DataGrid Grid.Row="2"
|
||||||
AutoGenerateColumns="False"
|
AutoGenerateColumns="False"
|
||||||
IsReadOnly="True"
|
IsReadOnly="True"
|
||||||
ItemsSource="{Binding Plc.Registers, Mode=OneWay}">
|
ItemsSource="{Binding Plc.Registers, Mode=OneWay}">
|
||||||
<DataGrid.Columns>
|
<DataGrid.Columns>
|
||||||
<DataGridTextColumn Header="Address" Binding="{Binding PLCAddressName}"/>
|
<DataGridTextColumn Header="FP Address" Binding="{Binding PLCAddressName}"/>
|
||||||
<DataGridTextColumn Header="Name" Binding="{Binding UnderlyingSystemType.Name}"/>
|
<DataGridTextColumn Header="Type" Binding="{Binding UnderlyingSystemType.Name}"/>
|
||||||
<DataGridTextColumn Header="Value" Binding="{Binding ValueStr}"/>
|
<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.Columns>
|
||||||
</DataGrid>
|
</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) {
|
while (!tSource.Token.IsCancellationRequested) {
|
||||||
|
|
||||||
|
if (tSource.Token.IsCancellationRequested) break;
|
||||||
|
|
||||||
var res = await udpClient.ReceiveAsync().WithCancellation(tSource.Token);
|
var res = await udpClient.ReceiveAsync().WithCancellation(tSource.Token);
|
||||||
|
|
||||||
if (res.Buffer == null) break;
|
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>();
|
var tcs = new TaskCompletionSource<bool>();
|
||||||
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs)) {
|
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs)) {
|
||||||
if (task != await Task.WhenAny(task, tcs.Task)) {
|
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 object _locker = new object();
|
||||||
readonly WeakReference<Task> _lastTask = new WeakReference<Task>(null);
|
readonly WeakReference<Task> _lastTask = new WeakReference<Task>(null);
|
||||||
|
|
||||||
internal CancellationTokenSource tSource = new CancellationTokenSource();
|
|
||||||
|
|
||||||
private List<Task> queuedTasks = new List<Task>();
|
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();
|
void Disconnect();
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
/// <param name="_msg">MEWTOCOL Formatted request string ex: %01#RT</param>
|
/// <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>
|
/// <returns>Returns the result</returns>
|
||||||
Task<MewtocolFrameResponse> SendCommandAsync(string _msg, Action<double> onReceiveProgress = null);
|
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>
|
/// <summary>
|
||||||
/// Changes the PLCs operation mode to the given one
|
/// Changes the PLCs operation mode to the given one
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ namespace MewtocolNet
|
|||||||
|
|
||||||
public static MewtocolFrameResponse Canceled => new MewtocolFrameResponse(500, "Op was canceled by the library");
|
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) {
|
public MewtocolFrameResponse(string response) {
|
||||||
|
|
||||||
Success = true;
|
Success = true;
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
#region Private fields
|
#region Private fields
|
||||||
|
|
||||||
|
//cancellation token for the messages
|
||||||
|
internal CancellationTokenSource tSource = new CancellationTokenSource();
|
||||||
|
|
||||||
private protected Stream stream;
|
private protected Stream stream;
|
||||||
|
|
||||||
private int tcpMessagesSentThisCycle = 0;
|
private int tcpMessagesSentThisCycle = 0;
|
||||||
@@ -64,18 +67,20 @@ namespace MewtocolNet {
|
|||||||
private protected bool wasInitialStatusReceived;
|
private protected bool wasInitialStatusReceived;
|
||||||
private protected MewtocolVersion mewtocolVersion;
|
private protected MewtocolVersion mewtocolVersion;
|
||||||
|
|
||||||
//private protected string[] lastMsgs = new string[10];
|
|
||||||
private protected List<string> lastMsgs = new List<string>();
|
private protected List<string> lastMsgs = new List<string>();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Internal fields
|
#region Internal fields
|
||||||
|
|
||||||
|
internal protected System.Timers.Timer cyclicGenericUpdateCounter;
|
||||||
internal event Action PolledCycle;
|
internal event Action PolledCycle;
|
||||||
internal volatile bool pollerTaskStopped = true;
|
internal volatile bool pollerTaskStopped = true;
|
||||||
internal volatile bool pollerFirstCycle;
|
internal volatile bool pollerFirstCycle;
|
||||||
internal bool usePoller = false;
|
internal bool usePoller = false;
|
||||||
internal MemoryAreaManager memoryManager;
|
internal MemoryAreaManager memoryManager;
|
||||||
|
|
||||||
|
private volatile bool isMessageLocked;
|
||||||
private volatile bool isReceiving;
|
private volatile bool isReceiving;
|
||||||
private volatile bool isSending;
|
private volatile bool isSending;
|
||||||
|
|
||||||
@@ -356,6 +361,11 @@ namespace MewtocolNet {
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task<MewtocolFrameResponse> SendCommandAsync(string _msg, Action<double> onReceiveProgress = null) {
|
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();
|
await AwaitReconnectTaskAsync();
|
||||||
|
|
||||||
if (!IsConnected && !isConnectingStage)
|
if (!IsConnected && !isConnectingStage)
|
||||||
@@ -367,23 +377,35 @@ namespace MewtocolNet {
|
|||||||
//wait for the last send task to complete
|
//wait for the last send task to complete
|
||||||
if (regularSendTask != null && !regularSendTask.IsCompleted) await regularSendTask;
|
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) {
|
if (await Task.WhenAny(regularSendTask, Task.Delay(2000)) != regularSendTask) {
|
||||||
|
|
||||||
|
isMessageLocked = false;
|
||||||
|
|
||||||
// timeout logic
|
// timeout logic
|
||||||
return MewtocolFrameResponse.Timeout;
|
return MewtocolFrameResponse.Timeout;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//canceled
|
//canceled
|
||||||
if (regularSendTask.IsCanceled) return MewtocolFrameResponse.Canceled;
|
if (regularSendTask.IsCanceled) {
|
||||||
|
|
||||||
|
isMessageLocked = false;
|
||||||
|
return MewtocolFrameResponse.Canceled;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
tcpMessagesSentThisCycle++;
|
tcpMessagesSentThisCycle++;
|
||||||
queuedMessages--;
|
queuedMessages--;
|
||||||
|
|
||||||
//success
|
//success
|
||||||
if (regularSendTask.Result.Success) return regularSendTask.Result;
|
if (regularSendTask.Result.Success) {
|
||||||
|
|
||||||
|
isMessageLocked = false;
|
||||||
|
return regularSendTask.Result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
//no success
|
//no success
|
||||||
if(reconnectTask == null) StartReconnectTask();
|
if(reconnectTask == null) StartReconnectTask();
|
||||||
@@ -394,15 +416,63 @@ namespace MewtocolNet {
|
|||||||
//re-send the command
|
//re-send the command
|
||||||
if (IsConnected) {
|
if (IsConnected) {
|
||||||
|
|
||||||
|
isMessageLocked = false;
|
||||||
return await SendCommandAsync(_msg, onReceiveProgress);
|
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 {
|
try {
|
||||||
|
|
||||||
@@ -414,9 +484,11 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
IsSending = true;
|
IsSending = true;
|
||||||
|
|
||||||
|
if (tSource.Token.IsCancellationRequested) return MewtocolFrameResponse.Canceled;
|
||||||
|
|
||||||
//write inital command
|
//write inital command
|
||||||
byte[] writeBuffer = Encoding.UTF8.GetBytes(frame);
|
byte[] writeBuffer = Encoding.UTF8.GetBytes(frame);
|
||||||
stream.Write(writeBuffer, 0, writeBuffer.Length);
|
await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length, tSource.Token);
|
||||||
|
|
||||||
IsSending = false;
|
IsSending = false;
|
||||||
|
|
||||||
@@ -439,6 +511,13 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
OnOutMsg(frame);
|
OnOutMsg(frame);
|
||||||
|
|
||||||
|
if(noResponse) {
|
||||||
|
|
||||||
|
Logger.Log($"[---------CMD END----------]", LogLevel.Critical, this);
|
||||||
|
return MewtocolFrameResponse.EmptySuccess;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
var readResult = await ReadCommandAsync(wordsCountRequested, onReceiveProgress);
|
var readResult = await ReadCommandAsync(wordsCountRequested, onReceiveProgress);
|
||||||
|
|
||||||
//did not receive bytes but no errors, the com port was not configured right
|
//did not receive bytes but no errors, the com port was not configured right
|
||||||
@@ -487,6 +566,10 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
return new MewtocolFrameResponse(resString);
|
return new MewtocolFrameResponse(resString);
|
||||||
|
|
||||||
|
} catch (OperationCanceledException) {
|
||||||
|
|
||||||
|
return MewtocolFrameResponse.Canceled;
|
||||||
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
|
|
||||||
return new MewtocolFrameResponse(400, ex.Message.ToString(System.Globalization.CultureInfo.InvariantCulture));
|
return new MewtocolFrameResponse(400, ex.Message.ToString(System.Globalization.CultureInfo.InvariantCulture));
|
||||||
@@ -515,7 +598,10 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
byte[] buffer = new byte[RecBufferSize];
|
byte[] buffer = new byte[RecBufferSize];
|
||||||
IsReceiving = true;
|
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;
|
IsReceiving = false;
|
||||||
|
|
||||||
CalcDownstreamSpeed(bytesRead);
|
CalcDownstreamSpeed(bytesRead);
|
||||||
@@ -536,7 +622,7 @@ namespace MewtocolNet {
|
|||||||
//request next frame
|
//request next frame
|
||||||
var writeBuffer = Encoding.UTF8.GetBytes($"%{GetStationNumber()}**&\r");
|
var writeBuffer = Encoding.UTF8.GetBytes($"%{GetStationNumber()}**&\r");
|
||||||
IsSending = true;
|
IsSending = true;
|
||||||
await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length, queue.tSource.Token);
|
await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length, tSource.Token);
|
||||||
IsSending = false;
|
IsSending = false;
|
||||||
Logger.Log($">> Requested next frame", LogLevel.Critical, this);
|
Logger.Log($">> Requested next frame", LogLevel.Critical, this);
|
||||||
wasMultiFramedResponse = true;
|
wasMultiFramedResponse = true;
|
||||||
@@ -655,7 +741,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
if (IsConnected) {
|
if (IsConnected) {
|
||||||
|
|
||||||
queue.CancelAll();
|
tSource.Cancel();
|
||||||
|
|
||||||
Logger.Log("The PLC connection timed out", LogLevel.Error, this);
|
Logger.Log("The PLC connection timed out", LogLevel.Error, this);
|
||||||
OnDisconnect();
|
OnDisconnect();
|
||||||
@@ -668,7 +754,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
if (IsConnected) {
|
if (IsConnected) {
|
||||||
|
|
||||||
queue.CancelAll();
|
tSource.Cancel();
|
||||||
|
|
||||||
Logger.Log("The PLC connection was closed", LogLevel.Error, this);
|
Logger.Log("The PLC connection was closed", LogLevel.Error, this);
|
||||||
OnDisconnect();
|
OnDisconnect();
|
||||||
@@ -687,7 +773,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
private protected void OnSocketExceptionWhileConnected () {
|
private protected void OnSocketExceptionWhileConnected () {
|
||||||
|
|
||||||
queue.CancelAll();
|
tSource.Cancel();
|
||||||
|
|
||||||
IsConnected = false;
|
IsConnected = false;
|
||||||
|
|
||||||
@@ -697,6 +783,14 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
Logger.Log("Connected to PLC", LogLevel.Info, this);
|
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;
|
IsConnected = true;
|
||||||
|
|
||||||
Connected?.Invoke(this, new PlcConnectionArgs());
|
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() {
|
private protected virtual void OnDisconnect() {
|
||||||
|
|
||||||
IsReceiving = false;
|
IsReceiving = false;
|
||||||
@@ -730,6 +831,18 @@ namespace MewtocolNet {
|
|||||||
Disconnected?.Invoke(this, new PlcConnectionArgs());
|
Disconnected?.Invoke(this, new PlcConnectionArgs());
|
||||||
KillPoller();
|
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() {
|
private void SetUpstreamStopWatchStart() {
|
||||||
|
|||||||
@@ -61,8 +61,12 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
private void StopHeartBeat () {
|
private void StopHeartBeat () {
|
||||||
|
|
||||||
heartBeatTimer.Elapsed -= PollTimerTick;
|
if(heartBeatTimer != null) {
|
||||||
heartBeatTimer.Dispose();
|
|
||||||
|
heartBeatTimer.Elapsed -= PollTimerTick;
|
||||||
|
heartBeatTimer.Dispose();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -231,7 +231,9 @@ namespace MewtocolNet {
|
|||||||
if (result.Success && !string.IsNullOrEmpty(result.Response)) {
|
if (result.Success && !string.IsNullOrEmpty(result.Response)) {
|
||||||
|
|
||||||
var bytes = result.Response.ParseDTRawStringAsBytes();
|
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);
|
Logger.Log("The PLC connection timed out", LogLevel.Error, this);
|
||||||
OnMajorSocketExceptionWhileConnecting();
|
OnMajorSocketExceptionWhileConnecting();
|
||||||
return;
|
return;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.LogVerbose("TCP/IP Client connected", this);
|
||||||
|
|
||||||
if (HostEndpoint == null) {
|
if (HostEndpoint == null) {
|
||||||
var ep = (IPEndPoint)client.Client.LocalEndPoint;
|
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
|
//get the stream
|
||||||
stream = client.GetStream();
|
stream = client.GetStream();
|
||||||
stream.ReadTimeout = 1000;
|
stream.ReadTimeout = 1000;
|
||||||
|
|
||||||
|
//try to abort any non read message
|
||||||
|
//await SendNoResponseCommandAsync($"%{GetStationNumber()}#AB");
|
||||||
|
|
||||||
//get plc info
|
//get plc info
|
||||||
var plcinf = await GetPLCInfoAsync(ConnectTimeout);
|
var plcinf = await GetPLCInfoAsync(ConnectTimeout);
|
||||||
|
|
||||||
@@ -179,9 +185,6 @@ namespace MewtocolNet {
|
|||||||
var result = client.BeginConnect(ipAddr, Port, null, null);
|
var result = client.BeginConnect(ipAddr, Port, null, null);
|
||||||
var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(conTimeout));
|
var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(conTimeout));
|
||||||
|
|
||||||
if (client.Connected)
|
|
||||||
Logger.LogVerbose("TCP/IP Client connected", this);
|
|
||||||
|
|
||||||
if (!success || !client.Connected) {
|
if (!success || !client.Connected) {
|
||||||
|
|
||||||
Logger.Log("The PLC connection timed out", LogLevel.Error, this);
|
Logger.Log("The PLC connection timed out", LogLevel.Error, this);
|
||||||
@@ -189,6 +192,8 @@ namespace MewtocolNet {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.LogVerbose("TCP/IP Client connected", this);
|
||||||
|
|
||||||
if (HostEndpoint == null) {
|
if (HostEndpoint == null) {
|
||||||
var ep = (IPEndPoint)client.Client.LocalEndPoint;
|
var ep = (IPEndPoint)client.Client.LocalEndPoint;
|
||||||
Logger.Log($"Connecting [AUTO] endpoint: {ep.Address.MapToIPv4()}:{ep.Port}", LogLevel.Info, this);
|
Logger.Log($"Connecting [AUTO] endpoint: {ep.Address.MapToIPv4()}:{ep.Port}", LogLevel.Info, this);
|
||||||
@@ -243,7 +248,7 @@ namespace MewtocolNet {
|
|||||||
|
|
||||||
if (client != null && client.Connected) {
|
if (client != null && client.Connected) {
|
||||||
|
|
||||||
client.Close();
|
client.Dispose();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ namespace MewtocolNet.Registers {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
int PollLevel { get; }
|
int PollLevel { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The update frequency of the register in Hz
|
||||||
|
/// </summary>
|
||||||
|
float UpdateFreqHz { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the register address name as in the plc
|
/// Gets the register address name as in the plc
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using MewtocolNet.UnderlyingRegisters;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -17,6 +18,8 @@ namespace MewtocolNet.Registers {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public event RegisterChangedEventHandler ValueChanged;
|
public event RegisterChangedEventHandler ValueChanged;
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
//links to
|
//links to
|
||||||
internal RegisterCollection containedCollection;
|
internal RegisterCollection containedCollection;
|
||||||
internal MewtocolInterface attachedInterface;
|
internal MewtocolInterface attachedInterface;
|
||||||
@@ -36,8 +39,16 @@ namespace MewtocolNet.Registers {
|
|||||||
internal uint successfulReads = 0;
|
internal uint successfulReads = 0;
|
||||||
internal uint successfulWrites = 0;
|
internal uint successfulWrites = 0;
|
||||||
|
|
||||||
|
internal int updateCountTimerCycle;
|
||||||
|
internal float updateFreqHz;
|
||||||
|
|
||||||
internal bool wasOverlapFitted = false;
|
internal bool wasOverlapFitted = false;
|
||||||
|
|
||||||
|
private Stopwatch timeSinceLastUpdate;
|
||||||
|
|
||||||
|
private float updateFreqFastCount;
|
||||||
|
private float[] updateFreqAvgList;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
internal RegisterCollection ContainedCollection => containedCollection;
|
internal RegisterCollection ContainedCollection => containedCollection;
|
||||||
|
|
||||||
@@ -66,25 +77,110 @@ namespace MewtocolNet.Registers {
|
|||||||
public uint MemoryAddress => memoryAddress;
|
public uint MemoryAddress => memoryAddress;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <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
|
#region Trigger update notify
|
||||||
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
public void TriggerValueChange() {
|
||||||
|
|
||||||
public void TriggerNotifyChange() {
|
|
||||||
|
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ValueObj)));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ValueObj)));
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ValueStr)));
|
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
|
#endregion
|
||||||
|
|
||||||
public virtual void ClearValue() => UpdateHoldingValue(null);
|
public virtual void ClearValue() => UpdateHoldingValue(null);
|
||||||
|
|
||||||
internal virtual void UpdateHoldingValue(object val) {
|
internal virtual void UpdateHoldingValue(object val) {
|
||||||
|
|
||||||
|
TriggerUpdateReceived();
|
||||||
|
|
||||||
bool nullDiff = false;
|
bool nullDiff = false;
|
||||||
if (val == null && lastValue != null) nullDiff = true;
|
if (val == null && lastValue != null) nullDiff = true;
|
||||||
if (val != null && lastValue == null) nullDiff = true;
|
if (val != null && lastValue == null) nullDiff = true;
|
||||||
@@ -96,7 +192,7 @@ namespace MewtocolNet.Registers {
|
|||||||
|
|
||||||
lastValue = val;
|
lastValue = val;
|
||||||
|
|
||||||
TriggerNotifyChange();
|
TriggerValueChange();
|
||||||
attachedInterface.InvokeRegisterChanged(this, beforeVal, beforeValStr);
|
attachedInterface.InvokeRegisterChanged(this, beforeVal, beforeValStr);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ namespace MewtocolNet.Registers {
|
|||||||
public ArrayRegister() =>
|
public ArrayRegister() =>
|
||||||
throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern");
|
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)
|
if (_reservedByteSize % 2 != 0)
|
||||||
throw new ArgumentException(nameof(_reservedByteSize), "Reserved byte size must be even");
|
throw new ArgumentException(nameof(_reservedByteSize), "Reserved byte size must be even");
|
||||||
@@ -174,6 +174,8 @@ namespace MewtocolNet.Registers {
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
internal override void UpdateHoldingValue(object val) {
|
internal override void UpdateHoldingValue(object val) {
|
||||||
|
|
||||||
|
TriggerUpdateReceived();
|
||||||
|
|
||||||
if (val == null && lastValue == null) return;
|
if (val == null && lastValue == null) return;
|
||||||
|
|
||||||
bool sequenceDifference = false;
|
bool sequenceDifference = false;
|
||||||
@@ -196,7 +198,7 @@ namespace MewtocolNet.Registers {
|
|||||||
|
|
||||||
lastValue = val;
|
lastValue = val;
|
||||||
|
|
||||||
TriggerNotifyChange();
|
TriggerValueChange();
|
||||||
attachedInterface.InvokeRegisterChanged(this, beforeVal, beforeValStr);
|
attachedInterface.InvokeRegisterChanged(this, beforeVal, beforeValStr);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace MewtocolNet.Registers {
|
|||||||
public BoolRegister() =>
|
public BoolRegister() =>
|
||||||
throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern");
|
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;
|
lastValue = null;
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ namespace MewtocolNet.Registers {
|
|||||||
public StringRegister() =>
|
public StringRegister() =>
|
||||||
throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern");
|
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;
|
memoryAddress = _address;
|
||||||
name = _name;
|
name = _name;
|
||||||
@@ -122,10 +122,11 @@ namespace MewtocolNet.Registers {
|
|||||||
|
|
||||||
//if string correct the sizing of the byte hint was wrong
|
//if string correct the sizing of the byte hint was wrong
|
||||||
var reservedSize = BitConverter.ToInt16(bytes, 0);
|
var reservedSize = BitConverter.ToInt16(bytes, 0);
|
||||||
if (reservedSize != byteLength - 4)
|
|
||||||
|
if (reservedStringLength != reservedSize)
|
||||||
throw new NotSupportedException(
|
throw new NotSupportedException(
|
||||||
$"The STRING register at {GetMewName()} is not correctly sized, " +
|
$"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();
|
AddSuccessRead();
|
||||||
@@ -139,6 +140,8 @@ namespace MewtocolNet.Registers {
|
|||||||
|
|
||||||
internal override void UpdateHoldingValue(object val) {
|
internal override void UpdateHoldingValue(object val) {
|
||||||
|
|
||||||
|
TriggerUpdateReceived();
|
||||||
|
|
||||||
if (lastValue?.ToString() != val?.ToString()) {
|
if (lastValue?.ToString() != val?.ToString()) {
|
||||||
|
|
||||||
var beforeVal = lastValue;
|
var beforeVal = lastValue;
|
||||||
@@ -146,7 +149,7 @@ namespace MewtocolNet.Registers {
|
|||||||
|
|
||||||
lastValue = val;
|
lastValue = val;
|
||||||
|
|
||||||
TriggerNotifyChange();
|
TriggerValueChange();
|
||||||
attachedInterface.InvokeRegisterChanged(this, beforeVal, beforeValStr);
|
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");
|
throw new NotSupportedException("Direct register instancing is not supported, use the builder pattern");
|
||||||
|
|
||||||
//struct for 16-32bit registers
|
//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;
|
memoryAddress = _address;
|
||||||
specialAddress = 0x0;
|
specialAddress = 0x0;
|
||||||
@@ -56,7 +56,7 @@ namespace MewtocolNet.Registers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//struct for one bit 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;
|
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