mirror of
https://github.com/OpenLogics/MewtocolNet.git
synced 2025-12-06 03:01:24 +00:00
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:
@@ -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 => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
// }
|
||||
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void StopReconnecting () {
|
||||
|
||||
if (tSourceReconnecting != null && !tSourceReconnecting.Token.IsCancellationRequested)
|
||||
tSourceReconnecting.Cancel();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Message sending and queuing
|
||||
|
||||
//internally used send task
|
||||
internal async Task<MewtocolFrameResponse> SendCommandInternalAsync(string _msg, Action<double> 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<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();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user