Fixed canceling messages

This commit is contained in:
Felix Weiß
2023-08-02 17:35:09 +02:00
parent e5020253c9
commit 74cb3bd59f
21 changed files with 353 additions and 98 deletions

View File

@@ -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>

View 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;
}

View File

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

View File

@@ -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>

View File

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

View File

@@ -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) { }
}

View File

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

View File

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

View File

@@ -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>

View File

@@ -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;

View File

@@ -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() {

View File

@@ -61,8 +61,12 @@ namespace MewtocolNet {
private void StopHeartBeat () {
heartBeatTimer.Elapsed -= PollTimerTick;
heartBeatTimer.Dispose();
if(heartBeatTimer != null) {
heartBeatTimer.Elapsed -= PollTimerTick;
heartBeatTimer.Dispose();
}
}

View File

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

View File

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

View File

@@ -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>

View File

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

View File

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

View File

@@ -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;

View File

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

View File

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

View File

@@ -498,7 +498,7 @@ namespace MewtocolNet.UnderlyingRegisters {
}
return registers;
return registers.OrderBy(x => x.MemoryAddress).ThenByDescending(x => x.GetRegisterString());
}