diff --git a/Examples.WPF/Views/ConnectView.xaml.cs b/Examples.WPF/Views/ConnectView.xaml.cs index b83915c..b3224f5 100644 --- a/Examples.WPF/Views/ConnectView.xaml.cs +++ b/Examples.WPF/Views/ConnectView.xaml.cs @@ -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 => { diff --git a/Examples.WPF/Views/PlcDataView.xaml b/Examples.WPF/Views/PlcDataView.xaml index 8df66b1..bf7574c 100644 --- a/Examples.WPF/Views/PlcDataView.xaml +++ b/Examples.WPF/Views/PlcDataView.xaml @@ -25,6 +25,8 @@ Click="ClickedConnect"/> + diff --git a/Examples.WPF/Views/PlcDataView.xaml.cs b/Examples.WPF/Views/PlcDataView.xaml.cs index d38b2c9..8a635bd 100644 --- a/Examples.WPF/Views/PlcDataView.xaml.cs +++ b/Examples.WPF/Views/PlcDataView.xaml.cs @@ -54,6 +54,24 @@ public partial class PlcDataView : UserControl { } + private async void ClickedAddQueueTest(object sender, RoutedEventArgs e) { + + var tasks = new List>(); + + for (int i = 0; i < 100; i++) { + + var t = viewModel.Plc.Register.Struct("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(); diff --git a/MewtocolNet/Events/ReconnectArgs.cs b/MewtocolNet/Events/ReconnectArgs.cs index 969d559..184e9f0 100644 --- a/MewtocolNet/Events/ReconnectArgs.cs +++ b/MewtocolNet/Events/ReconnectArgs.cs @@ -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(); } diff --git a/MewtocolNet/Helpers/AsyncQueue.cs b/MewtocolNet/Helpers/AsyncQueue.cs deleted file mode 100644 index 0e05257..0000000 --- a/MewtocolNet/Helpers/AsyncQueue.cs +++ /dev/null @@ -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 _lastTask = new WeakReference(null); - - - private List queuedTasks = new List(); - - //internal Task Enqueue(Func> asyncFunction) { - - // lock (_locker) { - - // var token = tSource.Token; - - // Task lastTask; - // Task resultTask; - - // if (_lastTask.TryGetTarget(out lastTask)) { - // resultTask = lastTask.ContinueWith(_ => asyncFunction(), token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Current).Unwrap(); - // } else { - // resultTask = Task.Run(asyncFunction, token); - // } - - // _lastTask.SetTarget(resultTask); - - // return resultTask; - - // } - - //} - - } - -} diff --git a/MewtocolNet/Helpers/MewtocolHelpers.cs b/MewtocolNet/Helpers/MewtocolHelpers.cs index b5c840e..b7640cf 100644 --- a/MewtocolNet/Helpers/MewtocolHelpers.cs +++ b/MewtocolNet/Helpers/MewtocolHelpers.cs @@ -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 { /// public static class MewtocolHelpers { + #region Async extensions + + internal static Task WhenCanceled(this CancellationToken cancellationToken) { + var tcs = new TaskCompletionSource(); + cancellationToken.Register(s => ((TaskCompletionSource)s).SetResult(true), tcs); + return tcs.Task; + } + + #endregion + #region Byte and string operation helpers public static T SetFlag(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)(?.*)(?..)").Match(_onString); + var res = new Regex(@"(?:\%|\<)([0-9a-fA-F]{2})\$(?:RD|RP|RC)(?.*)(?..)").Match(_onString); if (res.Success) { string val = res.Groups["data"].Value; diff --git a/MewtocolNet/IPlc.cs b/MewtocolNet/IPlc.cs index eb903d1..1bf2bb0 100644 --- a/MewtocolNet/IPlc.cs +++ b/MewtocolNet/IPlc.cs @@ -129,12 +129,14 @@ namespace MewtocolNet { void Disconnect(); /// - /// Sends a command to the PLC then awaits results
- /// The checksum and BCC are appended automatically + /// Stops a running reconnect task ///
- /// MEWTOCOL Formatted request string ex: %01#RT - /// Returns the result - //Task SendCommandAsync(string _msg, Action onReceiveProgress = null); + void StopReconnecting(); + + /// + /// Adds a task to each reconnect cycle that is run before each individual try + /// + void WithReconnectTask(Func callback); /// /// Changes the PLCs operation mode to the given one diff --git a/MewtocolNet/Mewtocol.cs b/MewtocolNet/Mewtocol.cs index f7076c3..98cac0d 100644 --- a/MewtocolNet/Mewtocol.cs +++ b/MewtocolNet/Mewtocol.cs @@ -401,11 +401,21 @@ namespace MewtocolNet } /// - /// 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 + /// + public PostInit WithReconnectTask(Func callback) { + + var plc = (MewtocolInterface)(object)intf; + + plc.onBeforeReconnectTryTask = callback; + + return this; + + } + + /// + /// Adds a task to each heartbeat cycle that is run before each individual cycle request /// - /// - /// public EndInitSetup WithHeartbeatTask(Func heartBeatAsync, bool executeInProg = false) { try { diff --git a/MewtocolNet/MewtocolInterface.cs b/MewtocolNet/MewtocolInterface.cs index 2c9755a..36e1bae 100644 --- a/MewtocolNet/MewtocolInterface.cs +++ b/MewtocolNet/MewtocolInterface.cs @@ -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 regularSendTask; - protected Queue> userInputSendTasks = new Queue>(); - + private protected Task reconnectTask; + private protected Task regularSendTask; private protected bool wasInitialStatusReceived; private protected MewtocolVersion mewtocolVersion; - private protected List lastMsgs = new List(); - #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 onBeforeReconnectTryTask; + #endregion #region Public Read Only Properties / Fields /// - public bool Disposed { get; private set; } + public int QueuedMessages => semaphoreSlim.CurrentCount; /// - public int QueuedMessages => userInputSendTasks.Count; + public bool Disposed { get; private set; } /// 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 { } /// - protected virtual Task ReconnectAsync(int conTimeout) => throw new NotImplementedException(); + protected virtual Task ReconnectAsync(int conTimeout, CancellationToken cancellationToken) => throw new NotImplementedException(); /// 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 { /// public virtual string GetConnectionInfo() => throw new NotImplementedException(); + #region Reconnecting + + /// + public void WithReconnectTask(Func 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 { } + /// + public void StopReconnecting () { + + if (tSourceReconnecting != null && !tSourceReconnecting.Token.IsCancellationRequested) + tSourceReconnecting.Cancel(); + + } + + #endregion + + #region Message sending and queuing + //internally used send task internal async Task SendCommandInternalAsync(string _msg, Action onReceiveProgress = null) { - if (regularSendTask != null && !regularSendTask.IsCompleted) { - - //queue self - Logger.LogCritical($"Queued {_msg}...", this); - return await EnqueueMessage(_msg, onReceiveProgress); - - } - - if (tSource.Token.IsCancellationRequested) return MewtocolFrameResponse.Canceled; + 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; - //send request - regularSendTask = SendTwoDirectionalFrameAsync(_msg, onReceiveProgress); + MewtocolFrameResponse responseData; try { - var timeoutAwaiter = await Task.WhenAny(regularSendTask, Task.Delay(sendReceiveTimeoutMs, tSource.Token)); + //send request + regularSendTask = SendTwoDirectionalFrameAsync(_msg, onReceiveProgress); + + var timeoutAwaiter = await Task.WhenAny(regularSendTask, Task.Delay(sendReceiveTimeoutMs, tSourceMessageCancel.Token)); if (timeoutAwaiter != regularSendTask) { @@ -466,77 +494,40 @@ namespace MewtocolNet { } + //canceled + if (regularSendTask.IsCanceled) { + + isMessageLocked = false; + regularSendTask = null; + + return MewtocolFrameResponse.Canceled; + + } + + responseData = regularSendTask.Result; + + tcpMessagesSentThisCycle++; + } catch (OperationCanceledException) { return MewtocolFrameResponse.Canceled; - } + } finally { - //canceled - if (regularSendTask.IsCanceled) { + //unlock + semaphoreSlim.Release(); - isMessageLocked = false; - regularSendTask = null; - - return MewtocolFrameResponse.Canceled; + OnPropChange(nameof(QueuedMessages)); } - MewtocolFrameResponse responseData = regularSendTask.Result; - - tcpMessagesSentThisCycle++; - 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 EnqueueMessage(string _msg, Action onReceiveProgress = null) { - - var t = new Task(() => SendCommandInternalAsync(_msg, onReceiveProgress).Result); - userInputSendTasks.Enqueue(t); - - OnPropChange(nameof(QueuedMessages)); - - await t; - - OnPropChange(nameof(QueuedMessages)); - - return t.Result; - - } - private protected async Task 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().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().ToList().ForEach(x => x.OnPlcDisconnected()); //generate a new cancellation token source - tSource = new CancellationTokenSource(); + tSourceMessageCancel = new CancellationTokenSource(); } diff --git a/MewtocolNet/MewtocolInterfaceRegisterHandling.cs b/MewtocolNet/MewtocolInterfaceRegisterHandling.cs index 1b86c64..017379c 100644 --- a/MewtocolNet/MewtocolInterfaceRegisterHandling.cs +++ b/MewtocolNet/MewtocolInterfaceRegisterHandling.cs @@ -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 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(); } diff --git a/MewtocolNet/MewtocolInterfaceRequests.cs b/MewtocolNet/MewtocolInterfaceRequests.cs index 5f907fe..10c3d7a 100644 --- a/MewtocolNet/MewtocolInterfaceRequests.cs +++ b/MewtocolNet/MewtocolInterfaceRequests.cs @@ -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); diff --git a/MewtocolNet/MewtocolInterfaceTcp.cs b/MewtocolNet/MewtocolInterfaceTcp.cs index 9ff754f..760d602 100644 --- a/MewtocolNet/MewtocolInterfaceTcp.cs +++ b/MewtocolNet/MewtocolInterfaceTcp.cs @@ -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"); diff --git a/MewtocolNet/RegisterBuilding/BuilderPatterns/RBuildMulti.cs b/MewtocolNet/RegisterBuilding/BuilderPatterns/RBuildMulti.cs index 21db4a4..c0d85af 100644 --- a/MewtocolNet/RegisterBuilding/BuilderPatterns/RBuildMulti.cs +++ b/MewtocolNet/RegisterBuilding/BuilderPatterns/RBuildMulti.cs @@ -78,6 +78,10 @@ namespace MewtocolNet.RegisterBuilding.BuilderPatterns { public class MultTypedArr1D : TypedArr1D { + public void Build() => builder.Assemble(this); + + public void Build(out IArrayRegister reference) => reference = (IArrayRegister)builder.Assemble(this); + public MultTypedArr1DOut PollLevel(int level) { Data.pollLevel = level; @@ -89,7 +93,7 @@ namespace MewtocolNet.RegisterBuilding.BuilderPatterns { public class MultTypedArr1DOut : TypedArr1DOut { - public IArrayRegister Build() => (IArrayRegister)builder.Assemble(this); + public void Build() => builder.Assemble(this); public void Build(out IArrayRegister reference) => reference = (IArrayRegister)builder.Assemble(this); @@ -99,6 +103,10 @@ namespace MewtocolNet.RegisterBuilding.BuilderPatterns { public class MultTypedArr2D : TypedArr2D { + public void Build() => builder.Assemble(this); + + public void Build(out IArrayRegister2D reference) => reference = (IArrayRegister2D)builder.Assemble(this); + public MultTypedArr2DOut PollLevel(int level) { Data.pollLevel = level; @@ -110,7 +118,7 @@ namespace MewtocolNet.RegisterBuilding.BuilderPatterns { public class MultTypedArr2DOut : TypedArr2DOut { - public IArrayRegister2D Build() => (IArrayRegister2D)builder.Assemble(this); + public void Build() => builder.Assemble(this); public void Build(out IArrayRegister2D reference) => reference = (IArrayRegister2D)builder.Assemble(this); @@ -120,6 +128,10 @@ namespace MewtocolNet.RegisterBuilding.BuilderPatterns { public class MultTypedArr3D : TypedArr3D { + public void Build() => builder.Assemble(this); + + public void Build(out IArrayRegister3D reference) => reference = (IArrayRegister3D)builder.Assemble(this); + public MultTypedArr3DOut PollLevel(int level) { Data.pollLevel = level; @@ -131,7 +143,7 @@ namespace MewtocolNet.RegisterBuilding.BuilderPatterns { public class MultTypedArr3DOut : TypedArr3DOut { - public IArrayRegister3D Build() => (IArrayRegister3D)builder.Assemble(this); + public void Build() => builder.Assemble(this); public void Build(out IArrayRegister3D reference) => reference = (IArrayRegister3D)builder.Assemble(this); diff --git a/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs b/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs index b7e9c19..fa0b24d 100644 --- a/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs +++ b/MewtocolNet/UnderlyingRegisters/MemoryAreaManager.cs @@ -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 registers = null) { diff --git a/MewtocolTests/AutomatedPropertyRegisters.cs b/MewtocolTests/AutomatedPropertyRegisters.cs deleted file mode 100644 index d4188db..0000000 --- a/MewtocolTests/AutomatedPropertyRegisters.cs +++ /dev/null @@ -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; - } - - } - -} \ No newline at end of file diff --git a/MewtocolTests/TestPublicBuilderPattern.cs b/MewtocolTests/TestPublicBuilderPattern.cs deleted file mode 100644 index a7eb134..0000000 --- a/MewtocolTests/TestPublicBuilderPattern.cs +++ /dev/null @@ -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 (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(expectAddr, expectByteSize) { - attachedInterface = interf, - pollLevel = 1, - }; - - //test building to the internal list - builder.Struct(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(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(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(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("DT100", 100, 2); - - [Fact(DisplayName = "[16 Bit] ushort")] - public void TestStruct_2() => TestStruct("DT101", 101, 2); - - [Fact(DisplayName = "[16 Bit] Word")] - public void TestStruct_3() => TestStruct("DT102", 102, 2); - - [Fact(DisplayName = "[16 Bit] Enum")] - public void TestStruct_4() => TestStruct("DT103", 103, 2); - - //32 bit structs - - [Fact(DisplayName = "[32 Bit] int")] - public void TestStruct_5() => TestStruct("DT104", 104, 4); - - [Fact(DisplayName = "[32 Bit] uint")] - public void TestStruct_6() => TestStruct("DT105", 105, 4); - - [Fact(DisplayName = "[32 Bit] DWord")] - public void TestStruct_7() => TestStruct("DT106", 106, 4); - - [Fact(DisplayName = "[32 Bit] Enum")] - public void TestStruct_8() => TestStruct("DT107", 107, 4); - - [Fact(DisplayName = "[32 Bit] TimeSpan")] - public void TestStruct_9() => TestStruct("DT108", 108, 4); - -} diff --git a/MewtocolTests/TestPublicBuilderPatternArrays.cs b/MewtocolTests/TestPublicBuilderPatternArrays.cs deleted file mode 100644 index e938b0f..0000000 --- a/MewtocolTests/TestPublicBuilderPatternArrays.cs +++ /dev/null @@ -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 (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(expectAddr, expectByteSize, new int[] { indices1 }) { - attachedInterface = interf, - pollLevel = 1 - }; - - //test building to the internal list - builder.Struct(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(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(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(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 - -}