diff --git a/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/Domain/IScadaClient.cs b/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/Domain/IScadaClient.cs index ca85e85..53b5622 100644 --- a/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/Domain/IScadaClient.cs +++ b/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/Domain/IScadaClient.cs @@ -16,6 +16,12 @@ namespace ZB.MOM.WW.LmxProxy.Host.Domain /// Gets the current connection state. ConnectionState ConnectionState { get; } + /// Gets the UTC time when the current connection was established. + DateTime ConnectedSince { get; } + + /// Gets the number of times the client has reconnected since startup. + int ReconnectCount { get; } + /// Occurs when the connection state changes. event EventHandler ConnectionStateChanged; diff --git a/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/MxAccess/MxAccessClient.Connection.cs b/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/MxAccess/MxAccessClient.Connection.cs index 4b6e6b7..3dba659 100644 --- a/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/MxAccess/MxAccessClient.Connection.cs +++ b/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/MxAccess/MxAccessClient.Connection.cs @@ -97,6 +97,9 @@ namespace ZB.MOM.WW.LmxProxy.Host.MxAccess get { lock (_lock) { return _connectedSince; } } } + /// Gets the number of times the client has reconnected since startup. + public int ReconnectCount => _reconnectCount; + // ── Internal synchronous methods ────────── private void ConnectInternal() @@ -278,7 +281,8 @@ namespace ZB.MOM.WW.LmxProxy.Host.MxAccess try { await ConnectAsync(ct); - Log.Information("Reconnected to MxAccess successfully"); + Interlocked.Increment(ref _reconnectCount); + Log.Information("Reconnected to MxAccess successfully (reconnect #{Count})", _reconnectCount); } catch (OperationCanceledException) { diff --git a/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/MxAccess/MxAccessClient.cs b/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/MxAccess/MxAccessClient.cs index 50dfa22..1729747 100644 --- a/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/MxAccess/MxAccessClient.cs +++ b/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/MxAccess/MxAccessClient.cs @@ -53,6 +53,9 @@ namespace ZB.MOM.WW.LmxProxy.Host.MxAccess // Probe state — updated by OnDataChange callback, read by monitor loop private DateTime _lastProbeValueTime; + // Reconnect counter + private int _reconnectCount; + // Stored subscriptions for reconnect replay private readonly Dictionary> _storedSubscriptions = new Dictionary>(StringComparer.OrdinalIgnoreCase); diff --git a/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/Status/StatusModels.cs b/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/Status/StatusModels.cs index ee3a748..142bc6f 100644 --- a/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/Status/StatusModels.cs +++ b/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/Status/StatusModels.cs @@ -20,6 +20,8 @@ namespace ZB.MOM.WW.LmxProxy.Host.Status public string State { get; set; } = ""; public string NodeName { get; set; } = ""; public string GalaxyName { get; set; } = ""; + public DateTime? ConnectedSince { get; set; } + public int ReconnectCount { get; set; } } public class SubscriptionStatus diff --git a/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/Status/StatusReportService.cs b/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/Status/StatusReportService.cs index dcaf454..e8116ae 100644 --- a/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/Status/StatusReportService.cs +++ b/lmxproxy/src/ZB.MOM.WW.LmxProxy.Host/Status/StatusReportService.cs @@ -83,7 +83,9 @@ namespace ZB.MOM.WW.LmxProxy.Host.Status statusData.Connection = new ConnectionStatus { IsConnected = _scadaClient.IsConnected, - State = _scadaClient.ConnectionState.ToString() + State = _scadaClient.ConnectionState.ToString(), + ConnectedSince = _scadaClient.IsConnected ? _scadaClient.ConnectedSince : (DateTime?)null, + ReconnectCount = _scadaClient.ReconnectCount }; // Subscription stats @@ -180,6 +182,10 @@ namespace ZB.MOM.WW.LmxProxy.Host.Status sb.AppendLine("

Connection

"); sb.AppendLine($"

Connected: {statusData.Connection.IsConnected}

"); sb.AppendLine($"

State: {statusData.Connection.State}

"); + if (statusData.Connection.ConnectedSince.HasValue) + sb.AppendLine($"

Connected Since: {statusData.Connection.ConnectedSince.Value:yyyy-MM-dd HH:mm:ss} UTC

"); + if (statusData.Connection.ReconnectCount > 0) + sb.AppendLine($"

Reconnects: {statusData.Connection.ReconnectCount}

"); if (!string.IsNullOrEmpty(statusData.Connection.NodeName)) sb.AppendLine($"

Node: {statusData.Connection.NodeName}

"); if (!string.IsNullOrEmpty(statusData.Connection.GalaxyName)) diff --git a/lmxproxy/tests/ZB.MOM.WW.LmxProxy.Host.Tests/Health/HealthCheckServiceTests.cs b/lmxproxy/tests/ZB.MOM.WW.LmxProxy.Host.Tests/Health/HealthCheckServiceTests.cs index fc6102c..0db7170 100644 --- a/lmxproxy/tests/ZB.MOM.WW.LmxProxy.Host.Tests/Health/HealthCheckServiceTests.cs +++ b/lmxproxy/tests/ZB.MOM.WW.LmxProxy.Host.Tests/Health/HealthCheckServiceTests.cs @@ -19,6 +19,8 @@ namespace ZB.MOM.WW.LmxProxy.Host.Tests.Health { public bool IsConnected { get; set; } = true; public ConnectionState ConnectionState { get; set; } = ConnectionState.Connected; + public DateTime ConnectedSince => DateTime.UtcNow; + public int ReconnectCount => 0; public event EventHandler? ConnectionStateChanged; public Task ConnectAsync(CancellationToken ct = default) => Task.CompletedTask; public Task DisconnectAsync(CancellationToken ct = default) => Task.CompletedTask; diff --git a/lmxproxy/tests/ZB.MOM.WW.LmxProxy.Host.Tests/Status/StatusReportServiceTests.cs b/lmxproxy/tests/ZB.MOM.WW.LmxProxy.Host.Tests/Status/StatusReportServiceTests.cs index b1a12f3..4e1bcde 100644 --- a/lmxproxy/tests/ZB.MOM.WW.LmxProxy.Host.Tests/Status/StatusReportServiceTests.cs +++ b/lmxproxy/tests/ZB.MOM.WW.LmxProxy.Host.Tests/Status/StatusReportServiceTests.cs @@ -20,6 +20,8 @@ namespace ZB.MOM.WW.LmxProxy.Host.Tests.Status { public bool IsConnected { get; set; } = true; public ConnectionState ConnectionState { get; set; } = ConnectionState.Connected; + public DateTime ConnectedSince => DateTime.UtcNow; + public int ReconnectCount => 0; public event EventHandler? ConnectionStateChanged; public Task ConnectAsync(CancellationToken ct = default) => Task.CompletedTask; public Task DisconnectAsync(CancellationToken ct = default) => Task.CompletedTask; diff --git a/lmxproxy/tests/ZB.MOM.WW.LmxProxy.Host.Tests/Subscriptions/SubscriptionManagerTests.cs b/lmxproxy/tests/ZB.MOM.WW.LmxProxy.Host.Tests/Subscriptions/SubscriptionManagerTests.cs index f3b83fc..6607327 100644 --- a/lmxproxy/tests/ZB.MOM.WW.LmxProxy.Host.Tests/Subscriptions/SubscriptionManagerTests.cs +++ b/lmxproxy/tests/ZB.MOM.WW.LmxProxy.Host.Tests/Subscriptions/SubscriptionManagerTests.cs @@ -17,6 +17,8 @@ namespace ZB.MOM.WW.LmxProxy.Host.Tests.Subscriptions { public bool IsConnected => true; public ConnectionState ConnectionState => ConnectionState.Connected; + public DateTime ConnectedSince => DateTime.UtcNow; + public int ReconnectCount => 0; public event EventHandler? ConnectionStateChanged; public Task ConnectAsync(CancellationToken ct = default) => Task.CompletedTask; public Task DisconnectAsync(CancellationToken ct = default) => Task.CompletedTask;