Improve robustness of connection

This commit is contained in:
Peter Butzhammer
2024-04-26 09:59:43 +02:00
parent f5a51c074f
commit e52c81683b
2 changed files with 59 additions and 88 deletions

View File

@@ -213,7 +213,7 @@ internal class Sharp7Connector : IS7Connector
private void EnsureConnectionValid() private void EnsureConnectionValid()
{ {
if (disposed) if (disposed)
throw new ObjectDisposedException("S7Connector"); throw new ObjectDisposedException(nameof(Sharp7Connector));
if (sharp7 == null) if (sharp7 == null)
throw new InvalidOperationException("S7 driver is not initialized."); throw new InvalidOperationException("S7 driver is not initialized.");

View File

@@ -21,12 +21,13 @@ public class Sharp7Plc : IPlc
private static readonly MethodInfo getValueMethod = typeof(Sharp7Plc).GetMethods() private static readonly MethodInfo getValueMethod = typeof(Sharp7Plc).GetMethods()
.Single(m => m.Name == nameof(GetValue) && m.GetGenericArguments().Length == 1); .Single(m => m.Name == nameof(GetValue) && m.GetGenericArguments().Length == 1);
private readonly CompositeDisposable disposables = new(); private IDisposable notificationSubscription;
private readonly ConcurrentSubjectDictionary<string, byte[]> multiVariableSubscriptions = new(StringComparer.InvariantCultureIgnoreCase); private readonly ConcurrentSubjectDictionary<string, byte[]> multiVariableSubscriptions = new(StringComparer.InvariantCultureIgnoreCase);
private readonly List<long> performanceCounter = new(1000); private readonly List<long> performanceCounter = new(1000);
private readonly PlcConnectionSettings plcConnectionSettings; private readonly PlcConnectionSettings plcConnectionSettings;
private readonly CacheVariableNameParser variableNameParser = new CacheVariableNameParser(new VariableNameParser()); private readonly CacheVariableNameParser variableNameParser = new CacheVariableNameParser(new VariableNameParser());
private bool disposed; private bool disposed;
private int initialized;
private Sharp7Connector s7Connector; private Sharp7Connector s7Connector;
/// <summary> /// <summary>
@@ -197,92 +198,27 @@ public class Sharp7Plc : IPlc
/// </para> /// </para>
/// </summary> /// </summary>
/// <returns>Always true</returns> /// <returns>Always true</returns>
[Obsolete("Use InitializeConnection.")] [Obsolete($"Use {nameof(InitializeConnection)} or {nameof(TriggerConnection)}.")]
public async Task<bool> InitializeAsync() public async Task<bool> InitializeAsync()
{ {
await s7Connector.InitializeAsync(); await TriggerConnection();
#pragma warning disable 4014
Task.Run(async () =>
{
try
{
await s7Connector.Connect();
}
catch (Exception e)
{
Logger?.LogError(e, "Error while connecting to PLC");
}
});
#pragma warning restore 4014
RunNotifications();
return true; return true;
} }
/// <summary>
/// Initialize PLC connection and wait for connection to be established.
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
public async Task TriggerInitialize(CancellationToken token = default)
{
await s7Connector.InitializeAsync();
// Triger connection.
// The initial connection might fail. In this case a reconnect is initiated.
// So we ignore any errors and wait for ConnectionState Connected afterward.
_ = Task.Run(async () =>
{
try
{
await s7Connector.Connect();
}
catch (Exception e)
{
Logger?.LogError(e, "Error while connecting to PLC");
}
}, token);
await s7Connector.ConnectionState
.FirstAsync(c => c == Enums.ConnectionState.Connected)
.ToTask(token);
RunNotifications();
}
/// <summary> /// <summary>
/// Initialize PLC connection and wait for connection to be established. /// Initialize PLC connection and wait for connection to be established.
/// </summary> /// </summary>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
public async Task InitializeConnection(CancellationToken token = default) public async Task InitializeConnection(CancellationToken token = default) => await DoInitializeConnection(true, token);
{
await s7Connector.InitializeAsync();
// Triger connection. /// <summary>
// The initial connection might fail. In this case a reconnect is initiated. /// Initialize PLC and trigger connection. This method will not wait for the connection to be established.
// So we ignore any errors and wait for ConnectionState Connected afterward. /// </summary>
_ = Task.Run(async () => /// <param name="token"></param>
{ /// <returns></returns>
try public async Task TriggerConnection(CancellationToken token = default) => await DoInitializeConnection(false, token);
{
await s7Connector.Connect();
}
catch (Exception e)
{
Logger?.LogError(e, "Error while connecting to PLC");
}
}, token);
await s7Connector.ConnectionState
.FirstAsync(c => c == Enums.ConnectionState.Connected)
.ToTask(token);
RunNotifications();
}
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
@@ -291,7 +227,8 @@ public class Sharp7Plc : IPlc
if (disposing) if (disposing)
{ {
disposables.Dispose(); notificationSubscription?.Dispose();
notificationSubscription = null;
if (s7Connector != null) if (s7Connector != null)
{ {
@@ -304,11 +241,37 @@ public class Sharp7Plc : IPlc
} }
} }
private async Task<Unit> GetAllValues(bool connected, IS7Connector connector) private async Task DoInitializeConnection(bool waitForConnection, CancellationToken token)
{ {
if (!connected) if (Interlocked.Exchange(ref initialized, 1) == 1) return;
return Unit.Default;
await s7Connector.InitializeAsync();
// Triger connection.
// The initial connection might fail. In this case a reconnect is initiated.
// So we ignore any errors and wait for ConnectionState Connected afterward.
_ = Task.Run(async () =>
{
try
{
await s7Connector.Connect();
}
catch (Exception e)
{
Logger?.LogError(e, "Intiial PLC connection failed.");
}
}, token);
if (waitForConnection)
await s7Connector.ConnectionState
.FirstAsync(c => c == Enums.ConnectionState.Connected)
.ToTask(token);
StartNotificationLoop();
}
private async Task<Unit> GetAllValues(IS7Connector connector)
{
if (multiVariableSubscriptions.ExistingKeys.IsEmpty()) if (multiVariableSubscriptions.ExistingKeys.IsEmpty())
return Unit.Default; return Unit.Default;
@@ -356,15 +319,23 @@ public class Sharp7Plc : IPlc
} }
} }
private void RunNotifications() private void StartNotificationLoop()
{ {
ConnectionState.FirstAsync() if (notificationSubscription != null)
.Select(states => states == Enums.ConnectionState.Connected) // notification loop already running
.SelectMany(connected => GetAllValues(connected, s7Connector)) return;
var subscription =
ConnectionState
.FirstAsync(states => states == Enums.ConnectionState.Connected)
.SelectMany(_ => GetAllValues(s7Connector))
.RepeatAfterDelay(MultiVarRequestCycleTime) .RepeatAfterDelay(MultiVarRequestCycleTime)
.LogAndRetryAfterDelay(Logger, MultiVarRequestCycleTime, "Error while getting batch notifications from plc") .LogAndRetryAfterDelay(Logger, MultiVarRequestCycleTime, "Error while getting batch notifications from plc")
.Subscribe() .Subscribe();
.AddDisposableTo(disposables);
if (Interlocked.CompareExchange(ref notificationSubscription, subscription, null) != null)
// Subscription has already been created (race condition). Dispose new subscription.
subscription.Dispose();
} }
~Sharp7Plc() ~Sharp7Plc()