Add reconnect task cancellation

- fix missing build methods for array registers
- add reconnect injection task
- add slim semaphore for async message single thread handling
This commit is contained in:
Felix Weiß
2023-08-15 19:31:45 +02:00
parent 354c4d6428
commit 0668ad2f86
17 changed files with 216 additions and 352 deletions

View File

@@ -70,8 +70,8 @@ public partial class ConnectView : UserControl {
setting.TryReconnectDelayMs = 2000;
setting.SendReceiveTimeoutMs = 1000;
setting.HeartbeatIntervalMs = 3000;
setting.MaxDataBlocksPerWrite = 20;
setting.MaxOptimizationDistance = 10;
setting.MaxDataBlocksPerWrite = 128;
setting.MaxOptimizationDistance = 20;
})
.WithCustomPollLevels(lvl => {

View File

@@ -25,6 +25,8 @@
Click="ClickedConnect"/>
<MenuItem Header="Set Random DT1000" IsEnabled="{Binding Plc.IsConnected}"
Click="ClickedSetRandom"/>
<MenuItem Header="Queue test" IsEnabled="{Binding Plc.IsConnected}"
Click="ClickedAddQueueTest"/>
<MenuItem Header="Toggle OP mode" IsEnabled="{Binding Plc.IsConnected}"
Click="ClickedToggleRunMode"/>
</MenuItem>

View File

@@ -54,6 +54,24 @@ public partial class PlcDataView : UserControl {
}
private async void ClickedAddQueueTest(object sender, RoutedEventArgs e) {
var tasks = new List<Task<short>>();
for (int i = 0; i < 100; i++) {
var t = viewModel.Plc.Register.Struct<short>("DT1000").ReadAsync();
tasks.Add(t);
}
var list = await Task.WhenAll(tasks);
Console.WriteLine();
}
private async void ClickedToggleRunMode(object sender, RoutedEventArgs e) {
await viewModel.Plc.ToggleOperationModeAsync();

View File

@@ -15,6 +15,8 @@ namespace MewtocolNet.Events {
public event PropertyChangedEventHandler PropertyChanged;
public event Action Reconnected;
public int ReconnectTry { get; internal set; }
public int MaxAttempts { get; internal set; }
@@ -29,6 +31,16 @@ namespace MewtocolNet.Events {
}
}
private bool isReconnected;
public bool IsReconnected {
get { return isReconnected; }
set {
isReconnected = value;
OnPropChange();
}
}
private System.Timers.Timer countDownTimer;
internal ReconnectArgs(int currentAttempt, int totalAttempts, TimeSpan delayBetween) {
@@ -62,7 +74,9 @@ namespace MewtocolNet.Events {
internal void ConnectionSuccess () {
IsReconnected = true;
StopTimer();
Reconnected?.Invoke();
}

View File

@@ -1,41 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace MewtocolNet.Helpers {
internal class AsyncQueue {
readonly object _locker = new object();
readonly WeakReference<Task> _lastTask = new WeakReference<Task>(null);
private List<Task> queuedTasks = new List<Task>();
//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;
// }
//}
}
}

View File

@@ -10,6 +10,8 @@ using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Threading;
namespace MewtocolNet {
@@ -18,6 +20,16 @@ namespace MewtocolNet {
/// </summary>
public static class MewtocolHelpers {
#region Async extensions
internal static Task WhenCanceled(this CancellationToken cancellationToken) {
var tcs = new TaskCompletionSource<bool>();
cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).SetResult(true), tcs);
return tcs.Task;
}
#endregion
#region Byte and string operation helpers
public static T SetFlag<T>(this Enum value, T flag, bool set) {
@@ -143,7 +155,7 @@ namespace MewtocolNet {
_onString = _onString.Replace("\r", "");
var res = new Regex(@"\%([0-9a-fA-F]{2})\$(?:RD|RP|RC)(?<data>.*)(?<csum>..)").Match(_onString);
var res = new Regex(@"(?:\%|\<)([0-9a-fA-F]{2})\$(?:RD|RP|RC)(?<data>.*)(?<csum>..)").Match(_onString);
if (res.Success) {
string val = res.Groups["data"].Value;

View File

@@ -129,12 +129,14 @@ namespace MewtocolNet {
void Disconnect();
/// <summary>
/// Sends a command to the PLC then awaits results<br/>
/// The checksum and BCC are appended automatically
/// Stops a running reconnect task
/// </summary>
/// <param name="_msg">MEWTOCOL Formatted request string ex: %01#RT</param>
/// <returns>Returns the result</returns>
//Task<MewtocolFrameResponse> SendCommandAsync(string _msg, Action<double> onReceiveProgress = null);
void StopReconnecting();
/// <summary>
/// Adds a task to each reconnect cycle that is run before each individual try
/// </summary>
void WithReconnectTask(Func<int, Task> callback);
/// <summary>
/// Changes the PLCs operation mode to the given one

View File

@@ -401,11 +401,21 @@ namespace MewtocolNet
}
/// <summary>
/// Repeats the passed method each time the hearbeat is triggered,
/// use
/// Adds a task to each reconnect cycle that is run before each individual try
/// </summary>
public PostInit<T> WithReconnectTask(Func<int, Task> callback) {
var plc = (MewtocolInterface)(object)intf;
plc.onBeforeReconnectTryTask = callback;
return this;
}
/// <summary>
/// Adds a task to each heartbeat cycle that is run before each individual cycle request
/// </summary>
/// <param name="heartBeatAsync"></param>
/// <returns></returns>
public EndInitSetup<T> WithHeartbeatTask(Func<IPlc,Task> heartBeatAsync, bool executeInProg = false) {
try {

View File

@@ -50,8 +50,12 @@ namespace MewtocolNet {
#region Private fields
//thread locker for messages
private SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);
//cancellation token for the messages
internal CancellationTokenSource tSource = new CancellationTokenSource();
internal CancellationTokenSource tSourceMessageCancel = new CancellationTokenSource();
internal CancellationTokenSource tSourceReconnecting;
private protected Stream stream;
@@ -71,17 +75,12 @@ namespace MewtocolNet {
private protected Stopwatch speedStopwatchUpstr;
private protected Stopwatch speedStopwatchDownstr;
protected Task firstPollTask;
protected Task reconnectTask;
protected Task<MewtocolFrameResponse> regularSendTask;
protected Queue<Task<MewtocolFrameResponse>> userInputSendTasks = new Queue<Task<MewtocolFrameResponse>>();
private protected Task reconnectTask;
private protected Task<MewtocolFrameResponse> regularSendTask;
private protected bool wasInitialStatusReceived;
private protected MewtocolVersion mewtocolVersion;
private protected List<string> lastMsgs = new List<string>();
#endregion
#region Internal fields
@@ -95,7 +94,7 @@ namespace MewtocolNet {
//configuration
private protected bool isMessageLocked;
private volatile protected bool isMessageLocked;
private volatile bool isReceiving;
private volatile bool isSending;
@@ -107,15 +106,17 @@ namespace MewtocolNet {
internal bool usePoller = false;
internal bool alwaysGetMetadata = true;
internal Func<int, Task> onBeforeReconnectTryTask;
#endregion
#region Public Read Only Properties / Fields
/// <inheritdoc/>
public bool Disposed { get; private set; }
public int QueuedMessages => semaphoreSlim.CurrentCount;
/// <inheritdoc/>
public int QueuedMessages => userInputSendTasks.Count;
public bool Disposed { get; private set; }
/// <inheritdoc/>
public bool IsConnected {
@@ -204,7 +205,7 @@ namespace MewtocolNet {
RegisterChanged += OnRegisterChanged;
PropertyChanged += (s, e) => {
if (e.PropertyName == nameof(PlcInfo)) {
if (e.PropertyName == nameof(PlcInfo) && PlcInfo != null) {
PlcInfo.PropertyChanged += (s1, e1) => {
if (e1.PropertyName == nameof(PlcInfo.IsRunMode))
OnPropChange(nameof(IsRunMode));
@@ -317,7 +318,7 @@ namespace MewtocolNet {
}
/// <inheritdoc/>
protected virtual Task ReconnectAsync(int conTimeout) => throw new NotImplementedException();
protected virtual Task ReconnectAsync(int conTimeout, CancellationToken cancellationToken) => throw new NotImplementedException();
/// <inheritdoc/>
public async Task AwaitFirstDataCycleAsync() {
@@ -347,7 +348,7 @@ namespace MewtocolNet {
if (IsConnected) {
tSource.Cancel();
tSourceMessageCancel.Cancel();
isMessageLocked = false;
Logger.Log("The PLC connection was closed manually", LogLevel.Error, this);
@@ -372,10 +373,21 @@ namespace MewtocolNet {
/// <inheritdoc/>
public virtual string GetConnectionInfo() => throw new NotImplementedException();
#region Reconnecting
/// <inheritdoc/>
public void WithReconnectTask(Func<int, Task> callback) {
onBeforeReconnectTryTask = callback;
}
internal void StartReconnectTask() {
if (reconnectTask == null) {
tSourceReconnecting = new CancellationTokenSource();
reconnectTask = Task.Run(async () => {
int retryCount = 1;
@@ -389,7 +401,12 @@ namespace MewtocolNet {
}
while (!IsConnected && tryReconnectAttempts > 0 && retryCount < tryReconnectAttempts + 1) {
while (!IsConnected && tryReconnectAttempts > 0 && retryCount < tryReconnectAttempts + 1 && !tSourceReconnecting.Token.IsCancellationRequested) {
if (tSourceReconnecting.Token.IsCancellationRequested) break;
if(onBeforeReconnectTryTask != null)
await onBeforeReconnectTryTask(retryCount);
Logger.Log($"Reconnecting {retryCount}/{tryReconnectAttempts} ...", this);
@@ -399,12 +416,14 @@ namespace MewtocolNet {
//stop the heartbeat timer for the time of retries
StopHeartBeat();
var eArgs = new ReconnectArgs(retryCount, tryReconnectAttempts, TimeSpan.FromMilliseconds(tryReconnectDelayMs));
var eArgs = new ReconnectArgs(retryCount, tryReconnectAttempts, TimeSpan.FromMilliseconds(tryReconnectDelayMs + ConnectTimeout));
ReconnectTryStarted?.Invoke(this, eArgs);
Reconnected += (s, e) => eArgs.ConnectionSuccess();
await ReconnectAsync(tryReconnectDelayMs);
await ReconnectAsync(tryReconnectDelayMs, tSourceReconnecting.Token);
if (tSourceReconnecting.Token.IsCancellationRequested) break;
if (IsConnected) return;
@@ -431,30 +450,39 @@ namespace MewtocolNet {
}
//internally used send task
internal async Task<MewtocolFrameResponse> SendCommandInternalAsync(string _msg, Action<double> onReceiveProgress = null) {
/// <inheritdoc/>
public void StopReconnecting () {
if (regularSendTask != null && !regularSendTask.IsCompleted) {
//queue self
Logger.LogCritical($"Queued {_msg}...", this);
return await EnqueueMessage(_msg, onReceiveProgress);
if (tSourceReconnecting != null && !tSourceReconnecting.Token.IsCancellationRequested)
tSourceReconnecting.Cancel();
}
if (tSource.Token.IsCancellationRequested) return MewtocolFrameResponse.Canceled;
#endregion
#region Message sending and queuing
//internally used send task
internal async Task<MewtocolFrameResponse> SendCommandInternalAsync(string _msg, Action<double> onReceiveProgress = null) {
if (tSourceMessageCancel.Token.IsCancellationRequested) return MewtocolFrameResponse.Canceled;
if (!IsConnected && !isConnectingStage && !isReconnectingStage)
throw new NotSupportedException("The device must be connected to send a message");
//thread lock the current cycle
await semaphoreSlim.WaitAsync();
isMessageLocked = true;
MewtocolFrameResponse responseData;
try {
//send request
regularSendTask = SendTwoDirectionalFrameAsync(_msg, onReceiveProgress);
try {
var timeoutAwaiter = await Task.WhenAny(regularSendTask, Task.Delay(sendReceiveTimeoutMs, tSource.Token));
var timeoutAwaiter = await Task.WhenAny(regularSendTask, Task.Delay(sendReceiveTimeoutMs, tSourceMessageCancel.Token));
if (timeoutAwaiter != regularSendTask) {
@@ -466,12 +494,6 @@ namespace MewtocolNet {
}
} catch (OperationCanceledException) {
return MewtocolFrameResponse.Canceled;
}
//canceled
if (regularSendTask.IsCanceled) {
@@ -482,61 +504,30 @@ namespace MewtocolNet {
}
MewtocolFrameResponse responseData = regularSendTask.Result;
responseData = regularSendTask.Result;
tcpMessagesSentThisCycle++;
} catch (OperationCanceledException) {
return MewtocolFrameResponse.Canceled;
} finally {
//unlock
semaphoreSlim.Release();
OnPropChange(nameof(QueuedMessages));
}
isMessageLocked = false;
regularSendTask = null;
//run the remaining tasks if no poller is used
if(!PollerActive && !tSource.Token.IsCancellationRequested) {
while(userInputSendTasks.Count > 1) {
if (PollerActive || tSource.Token.IsCancellationRequested) break;
await RunOneOpenQueuedTask();
}
}
return responseData;
}
protected async Task RunOneOpenQueuedTask() {
if (userInputSendTasks != null && userInputSendTasks.Count > 0) {
var t = userInputSendTasks.Dequeue();
t.Start();
await t;
}
await Task.CompletedTask;
}
protected async Task<MewtocolFrameResponse> EnqueueMessage(string _msg, Action<double> onReceiveProgress = null) {
var t = new Task<MewtocolFrameResponse>(() => SendCommandInternalAsync(_msg, onReceiveProgress).Result);
userInputSendTasks.Enqueue(t);
OnPropChange(nameof(QueuedMessages));
await t;
OnPropChange(nameof(QueuedMessages));
return t.Result;
}
private protected async Task<MewtocolFrameResponse> SendOneDirectionalFrameAsync (string frame) {
try {
@@ -549,11 +540,11 @@ namespace MewtocolNet {
IsSending = true;
if (tSource.Token.IsCancellationRequested) return MewtocolFrameResponse.Canceled;
if (tSourceMessageCancel.Token.IsCancellationRequested) return MewtocolFrameResponse.Canceled;
//write inital command
byte[] writeBuffer = Encoding.UTF8.GetBytes(frame);
await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length, tSource.Token);
await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length, tSourceMessageCancel.Token);
IsSending = false;
@@ -586,11 +577,11 @@ namespace MewtocolNet {
IsSending = true;
if (tSource.Token.IsCancellationRequested) return MewtocolFrameResponse.Canceled;
if (tSourceMessageCancel.Token.IsCancellationRequested) return MewtocolFrameResponse.Canceled;
//write inital command
byte[] writeBuffer = Encoding.UTF8.GetBytes(frame);
await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length, tSource.Token);
await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length, tSourceMessageCancel.Token);
IsSending = false;
@@ -649,7 +640,10 @@ namespace MewtocolNet {
}
if (j > 0) split[j] = split[j].Replace($"%{GetStationNumber()}", "");
if (j > 0)
split[j] = split[j]
.Replace($"%{GetStationNumber()}", "")
.Replace($"<{GetStationNumber()}", "");
}
@@ -694,9 +688,9 @@ namespace MewtocolNet {
byte[] buffer = new byte[RecBufferSize];
IsReceiving = true;
if (tSource.Token.IsCancellationRequested) break;
if (tSourceMessageCancel.Token.IsCancellationRequested) break;
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, tSource.Token);
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, tSourceMessageCancel.Token);
IsReceiving = false;
CalcDownstreamSpeed(bytesRead);
@@ -707,17 +701,22 @@ namespace MewtocolNet {
var commandRes = ParseBufferFrame(received);
needsRead = commandRes == CommandState.LineFeed || commandRes == CommandState.RequestedNextFrame;
OnInMsgPart(Encoding.UTF8.GetString(received));
var tempMsgStr = Encoding.UTF8.GetString(received);
OnInMsgPart(tempMsgStr);
//add complete response to collector without empty bytes
totalResponse.AddRange(received.Where(x => x != (byte)0x0));
if (commandRes == CommandState.RequestedNextFrame) {
string cmdPrefix = tempMsgStr.StartsWith("<") ? "<" : "%";
//request next frame
var writeBuffer = Encoding.UTF8.GetBytes($"%{GetStationNumber()}**&\r");
var writeBuffer = Encoding.UTF8.GetBytes($"{cmdPrefix}{GetStationNumber()}**&\r");
IsSending = true;
await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length, tSource.Token);
await stream.WriteAsync(writeBuffer, 0, writeBuffer.Length, tSourceMessageCancel.Token);
IsSending = false;
Logger.Log($">> Requested next frame", LogLevel.Critical, this);
wasMultiFramedResponse = true;
@@ -783,7 +782,7 @@ namespace MewtocolNet {
private protected int CheckForErrorMsg(string msg) {
//error catching
Regex errorcheck = new Regex(@"\%..\!([0-9]{2})", RegexOptions.IgnoreCase);
Regex errorcheck = new Regex(@"...\!([0-9]{2})", RegexOptions.IgnoreCase);
Match m = errorcheck.Match(msg);
if (m.Success) {
@@ -801,7 +800,6 @@ namespace MewtocolNet {
Logger.Log($"[---------CMD START--------]", LogLevel.Critical, this);
var formatted = $"S -> : {outMsg.Replace("\r", "(CR)")}";
AddToLastMsgs(formatted);
Logger.Log(formatted, LogLevel.Critical, this);
}
@@ -809,7 +807,6 @@ namespace MewtocolNet {
private protected void OnInMsgPart(string inPart) {
var formatted = $"<< IN PART: {inPart.Replace("\r", "(CR)")}";
AddToLastMsgs(formatted);
Logger.Log(formatted, LogLevel.Critical, this);
}
@@ -817,7 +814,6 @@ namespace MewtocolNet {
private protected void OnInMsg(string inMsg) {
var formatted = $"R <- : {inMsg.Replace("\r", "(CR)")}";
AddToLastMsgs(formatted);
Logger.Log(formatted, LogLevel.Critical, this);
OnEndMsg();
@@ -829,20 +825,11 @@ namespace MewtocolNet {
}
private protected void AddToLastMsgs (string msgTxt) {
lastMsgs.Add(msgTxt);
if(lastMsgs.Count >= 51) {
lastMsgs.RemoveAt(0);
}
}
private protected void OnMajorSocketExceptionWhileConnecting() {
if (IsConnected) {
tSource.Cancel();
tSourceMessageCancel.Cancel();
isMessageLocked = false;
Logger.Log("The PLC connection timed out", LogLevel.Error, this);
@@ -854,7 +841,7 @@ namespace MewtocolNet {
private protected void OnSocketExceptionWhileConnected() {
tSource.Cancel();
tSourceMessageCancel.Cancel();
bytesPerSecondDownstream = 0;
bytesPerSecondUpstream = 0;
@@ -874,6 +861,8 @@ namespace MewtocolNet {
}
#endregion
private protected virtual void OnConnected(PLCInfo plcinf) {
Logger.Log("Connected to PLC", LogLevel.Info, this);
@@ -911,7 +900,7 @@ namespace MewtocolNet {
//GetAllRegisters().Cast<Register>().ToList().ForEach(x => x.OnPlcDisconnected());
//generate a new cancellation token source
tSource = new CancellationTokenSource();
tSourceMessageCancel = new CancellationTokenSource();
IsConnected = true;
isReconnectingStage = false;
@@ -948,7 +937,7 @@ namespace MewtocolNet {
GetAllRegisters().Cast<Register>().ToList().ForEach(x => x.OnPlcDisconnected());
//generate a new cancellation token source
tSource = new CancellationTokenSource();
tSourceMessageCancel = new CancellationTokenSource();
}

View File

@@ -20,6 +20,8 @@ namespace MewtocolNet {
private bool heartbeatNeedsRun = false;
private bool heartbeatTimerRunning = false;
private protected Task firstPollTask;
internal Task heartbeatTask = Task.CompletedTask;
internal Func<IPlc, Task> heartbeatCallbackTask;
@@ -234,16 +236,16 @@ namespace MewtocolNet {
}
await RunOneOpenQueuedTask();
});
sw.Stop();
if (firstPollTask != null && !firstPollTask.IsCompleted) {
firstPollTask.RunSynchronously();
firstPollTask = null;
Logger.Log("poll cycle first done");
}
pollerFirstCycleCompleted = true;
@@ -413,7 +415,6 @@ namespace MewtocolNet {
var reg = internals[i];
reg.ClearValue();
//reg.TriggerNotifyChange();
}

View File

@@ -33,7 +33,7 @@ namespace MewtocolNet {
MewtocolFrameResponse resRT = await SendCommandInternalAsync("%EE#RT");
if (!resRT.Success || tSource.Token.IsCancellationRequested) return null;
if (!resRT.Success || tSourceMessageCancel.Token.IsCancellationRequested) return null;
MewtocolFrameResponse? resEXRT = null;
@@ -316,7 +316,7 @@ namespace MewtocolNet {
int blockSize = wordEnd - wordStart + 1;
string startStr = wordStart.ToString().PadLeft(padLeftLen, '0');
string endStr = wordEnd.ToString().PadLeft(padLeftLen, '0');
string requeststring = $"%{GetStationNumber()}#{areaCodeStr}{startStr}{endStr}";
string requeststring = $"<{GetStationNumber()}#{areaCodeStr}{startStr}{endStr}";
var result = await SendCommandInternalAsync(requeststring, onReceiveProgress: readProg);

View File

@@ -5,6 +5,7 @@ using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using MewtocolNet.Helpers;
namespace MewtocolNet {
@@ -174,7 +175,7 @@ namespace MewtocolNet {
}
protected override async Task ReconnectAsync (int conTimeout) {
protected override async Task ReconnectAsync (int conTimeout, CancellationToken cancellationToken) {
try {
@@ -188,10 +189,15 @@ namespace MewtocolNet {
BuildTcpClient();
var result = client.BeginConnect(ipAddr, Port, null, null);
var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(ConnectTimeout));
var conTask = client.ConnectAsync(ipAddr, Port);
if (!success || !client.Connected) {
if (await Task.WhenAny(conTask, Task.Delay(ConnectTimeout), cancellationToken.WhenCanceled()) != conTask) {
return;
}
if (!client.Connected) {
Logger.Log("The PLC connection timed out", LogLevel.Error, this);
OnMajorSocketExceptionWhileConnecting();
@@ -216,6 +222,8 @@ namespace MewtocolNet {
regularSendTask = null;
reconnectTask = Task.CompletedTask;
if (cancellationToken.IsCancellationRequested) return;
if (await SendCommandInternalAsync($"%{GetStationNumber()}#RT") != null) {
Logger.Log("Reconnect successfull");

View File

@@ -78,6 +78,10 @@ namespace MewtocolNet.RegisterBuilding.BuilderPatterns {
public class MultTypedArr1D<T> : TypedArr1D<T> {
public void Build() => builder.Assemble(this);
public void Build(out IArrayRegister<T> reference) => reference = (IArrayRegister<T>)builder.Assemble(this);
public MultTypedArr1DOut<T> PollLevel(int level) {
Data.pollLevel = level;
@@ -89,7 +93,7 @@ namespace MewtocolNet.RegisterBuilding.BuilderPatterns {
public class MultTypedArr1DOut<T> : TypedArr1DOut<T> {
public IArrayRegister<T> Build() => (IArrayRegister<T>)builder.Assemble(this);
public void Build() => builder.Assemble(this);
public void Build(out IArrayRegister<T> reference) => reference = (IArrayRegister<T>)builder.Assemble(this);
@@ -99,6 +103,10 @@ namespace MewtocolNet.RegisterBuilding.BuilderPatterns {
public class MultTypedArr2D<T> : TypedArr2D<T> {
public void Build() => builder.Assemble(this);
public void Build(out IArrayRegister2D<T> reference) => reference = (IArrayRegister2D<T>)builder.Assemble(this);
public MultTypedArr2DOut<T> PollLevel(int level) {
Data.pollLevel = level;
@@ -110,7 +118,7 @@ namespace MewtocolNet.RegisterBuilding.BuilderPatterns {
public class MultTypedArr2DOut<T> : TypedArr2DOut<T> {
public IArrayRegister2D<T> Build() => (IArrayRegister2D<T>)builder.Assemble(this);
public void Build() => builder.Assemble(this);
public void Build(out IArrayRegister2D<T> reference) => reference = (IArrayRegister2D<T>)builder.Assemble(this);
@@ -120,6 +128,10 @@ namespace MewtocolNet.RegisterBuilding.BuilderPatterns {
public class MultTypedArr3D<T> : TypedArr3D<T> {
public void Build() => builder.Assemble(this);
public void Build(out IArrayRegister3D<T> reference) => reference = (IArrayRegister3D<T>)builder.Assemble(this);
public MultTypedArr3DOut<T> PollLevel(int level) {
Data.pollLevel = level;
@@ -131,7 +143,7 @@ namespace MewtocolNet.RegisterBuilding.BuilderPatterns {
public class MultTypedArr3DOut<T> : TypedArr3DOut<T> {
public IArrayRegister3D<T> Build() => (IArrayRegister3D<T>)builder.Assemble(this);
public void Build() => builder.Assemble(this);
public void Build(out IArrayRegister3D<T> reference) => reference = (IArrayRegister3D<T>)builder.Assemble(this);

View File

@@ -52,6 +52,18 @@ namespace MewtocolNet.UnderlyingRegisters {
mewInterface = mewIf;
Setup(wrSize, dtSize);
mewInterface.Connected += (s, e) => {
pollIteration = 0;
};
mewInterface.Reconnected += (s, e) => {
pollIteration = 0;
};
}
// Later on pass memory area sizes here
@@ -63,11 +75,7 @@ namespace MewtocolNet.UnderlyingRegisters {
}
internal async Task OnPlcConnected () {
await Task.CompletedTask;
}
internal async Task OnPlcConnected () => await Task.CompletedTask;
internal void LinkAndMergeRegisters(List<Register> registers = null) {

View File

@@ -1,19 +0,0 @@
using MewtocolNet;
using MewtocolNet.Registers;
using MewtocolTests.EncapsulatedTests;
using Xunit;
using Xunit.Abstractions;
namespace MewtocolTests {
public partial class AutomatedPropertyRegisters {
private readonly ITestOutputHelper output;
public AutomatedPropertyRegisters(ITestOutputHelper output) {
this.output = output;
}
}
}

View File

@@ -1,89 +0,0 @@
using MewtocolNet;
using MewtocolNet.RegisterBuilding;
using MewtocolNet.RegisterBuilding.BuilderPatterns;
using MewtocolNet.Registers;
using MewtocolTests.EncapsulatedTests;
using System.Collections;
using Xunit;
using Xunit.Abstractions;
namespace MewtocolTests;
public class TestPublicBuilderPattern {
private readonly ITestOutputHelper output;
public TestPublicBuilderPattern(ITestOutputHelper output) => this.output = output;
private void TestStruct<T> (string buildAddr, uint expectAddr, uint expectByteSize) where T : struct {
using var interf = (MewtocolInterface)Mewtocol.Ethernet("192.168.115.210").Build();
var builder = new RBuild(interf);
var comparer = new StructRegister<T>(expectAddr, expectByteSize) {
attachedInterface = interf,
pollLevel = 1,
};
//test building to the internal list
builder.Struct<T>(buildAddr).Build();
var generated = builder.assembler.assembled.First();
Assert.Equivalent(comparer, generated);
builder.assembler.assembled.Clear();
output.WriteLine(generated.Explain());
//test building with direct out
builder.Struct<T>(buildAddr).Build(out var testRef);
Assert.Equivalent(comparer, testRef);
builder.assembler.assembled.Clear();
output.WriteLine(((Register)testRef).Explain());
comparer.pollLevel++;
//test building to the internal list with poll level
builder.Struct<T>(buildAddr).PollLevel(2).Build();
var generated2 = builder.assembler.assembled.First();
Assert.Equivalent(comparer, generated2);
builder.assembler.assembled.Clear();
output.WriteLine(generated2.Explain());
//test building direct out with poll level
builder.Struct<T>(buildAddr).PollLevel(2).Build(out var testRef2);
Assert.Equivalent(comparer, testRef2);
builder.assembler.assembled.Clear();
output.WriteLine(((Register)testRef2).Explain());
}
//16 bit structs
[Fact(DisplayName = "[16 Bit] short")]
public void TestStruct_1() => TestStruct<short>("DT100", 100, 2);
[Fact(DisplayName = "[16 Bit] ushort")]
public void TestStruct_2() => TestStruct<ushort>("DT101", 101, 2);
[Fact(DisplayName = "[16 Bit] Word")]
public void TestStruct_3() => TestStruct<Word>("DT102", 102, 2);
[Fact(DisplayName = "[16 Bit] Enum")]
public void TestStruct_4() => TestStruct<CurrentState16>("DT103", 103, 2);
//32 bit structs
[Fact(DisplayName = "[32 Bit] int")]
public void TestStruct_5() => TestStruct<int>("DT104", 104, 4);
[Fact(DisplayName = "[32 Bit] uint")]
public void TestStruct_6() => TestStruct<uint>("DT105", 105, 4);
[Fact(DisplayName = "[32 Bit] DWord")]
public void TestStruct_7() => TestStruct<DWord>("DT106", 106, 4);
[Fact(DisplayName = "[32 Bit] Enum")]
public void TestStruct_8() => TestStruct<CurrentState32>("DT107", 107, 4);
[Fact(DisplayName = "[32 Bit] TimeSpan")]
public void TestStruct_9() => TestStruct<TimeSpan>("DT108", 108, 4);
}

View File

@@ -1,63 +0,0 @@
using MewtocolNet;
using MewtocolNet.RegisterBuilding;
using MewtocolNet.RegisterBuilding.BuilderPatterns;
using MewtocolNet.Registers;
using MewtocolTests.EncapsulatedTests;
using System.Collections;
using System.Collections.Generic;
using Xunit;
using Xunit.Abstractions;
namespace MewtocolTests;
public class TestPublicBuilderPatternArray {
private readonly ITestOutputHelper output;
public TestPublicBuilderPatternArray(ITestOutputHelper output) => this.output = output;
private void TestArray1D<T> (string buildAddr, int indices1, uint expectAddr, uint expectByteSize) where T : struct {
using var interf = (MewtocolInterface)Mewtocol.Ethernet("192.168.115.210").Build();
var builder = new RBuild(interf);
var comparer = new ArrayRegister<T>(expectAddr, expectByteSize, new int[] { indices1 }) {
attachedInterface = interf,
pollLevel = 1
};
//test building to the internal list
builder.Struct<T>(buildAddr).AsArray(indices1).Build();
var generated = builder.assembler.assembled.First();
Assert.Equivalent(comparer, generated);
builder.assembler.assembled.Clear();
output.WriteLine(generated.Explain());
////test building with direct out
//builder.Struct<T>(buildAddr).AsArray(indices1).Build(out var testRef);
//Assert.Equivalent(comparer, testRef);
//builder.assembler.assembled.Clear();
//output.WriteLine(((Register)testRef).Explain());
//comparer.pollLevel++;
////test building to the internal list with poll level
//builder.Struct<T>(buildAddr).AsArray(indices1).PollLevel(2).Build();
//var generated2 = builder.assembler.assembled.First();
//Assert.Equivalent(comparer, generated2);
//builder.assembler.assembled.Clear();
//output.WriteLine(generated2.Explain());
////test building direct out with poll level
//builder.Struct<T>(buildAddr).AsArray(indices1).PollLevel(2).Build(out var testRef2);
//Assert.Equivalent(comparer, testRef2);
//builder.assembler.assembled.Clear();
//output.WriteLine(((Register)testRef2).Explain());
}
//16 bit structs
}