From f98d29fc36832ba31ec71c7ba7bc59aef7eced96 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 12 May 2026 00:57:09 -0400 Subject: [PATCH] refactor(dcl): OpcUaDataConnection uses OpcUaEndpointConfig via FromFlatDict --- .../Adapters/OpcUaDataConnection.cs | 65 +++++++++---------- 1 file changed, 29 insertions(+), 36 deletions(-) diff --git a/src/ScadaLink.DataConnectionLayer/Adapters/OpcUaDataConnection.cs b/src/ScadaLink.DataConnectionLayer/Adapters/OpcUaDataConnection.cs index d870416..4dd6d74 100644 --- a/src/ScadaLink.DataConnectionLayer/Adapters/OpcUaDataConnection.cs +++ b/src/ScadaLink.DataConnectionLayer/Adapters/OpcUaDataConnection.cs @@ -1,6 +1,8 @@ using Microsoft.Extensions.Logging; using ScadaLink.Commons.Interfaces.Protocol; +using ScadaLink.Commons.Serialization; using ScadaLink.Commons.Types; +using ScadaLink.Commons.Types.DataConnections; using ScadaLink.Commons.Types.Enums; namespace ScadaLink.DataConnectionLayer.Adapters; @@ -43,23 +45,23 @@ public class OpcUaDataConnection : IDataConnection public async Task ConnectAsync(IDictionary connectionDetails, CancellationToken cancellationToken = default) { - _endpointUrl = connectionDetails.TryGetValue("endpoint", out var url) - ? url - : connectionDetails.TryGetValue("EndpointUrl", out var url2) - ? url2 - : "opc.tcp://localhost:4840"; + var config = OpcUaEndpointConfigSerializer.FromFlatDict(connectionDetails); + + _endpointUrl = string.IsNullOrWhiteSpace(config.EndpointUrl) + ? "opc.tcp://localhost:4840" + : config.EndpointUrl; var options = new OpcUaConnectionOptions( - SessionTimeoutMs: ParseInt(connectionDetails, "SessionTimeoutMs", 60000), - OperationTimeoutMs: ParseInt(connectionDetails, "OperationTimeoutMs", 15000), - PublishingIntervalMs: ParseInt(connectionDetails, "PublishingIntervalMs", 1000), - KeepAliveCount: ParseInt(connectionDetails, "KeepAliveCount", 10), - LifetimeCount: ParseInt(connectionDetails, "LifetimeCount", 30), - MaxNotificationsPerPublish: ParseInt(connectionDetails, "MaxNotificationsPerPublish", 100), - SamplingIntervalMs: ParseInt(connectionDetails, "SamplingIntervalMs", 1000), - QueueSize: ParseInt(connectionDetails, "QueueSize", 10), - SecurityMode: connectionDetails.TryGetValue("SecurityMode", out var secMode) ? secMode : "None", - AutoAcceptUntrustedCerts: ParseBool(connectionDetails, "AutoAcceptUntrustedCerts", true)); + SessionTimeoutMs: config.SessionTimeoutMs, + OperationTimeoutMs: config.OperationTimeoutMs, + PublishingIntervalMs: config.PublishingIntervalMs, + KeepAliveCount: config.KeepAliveCount, + LifetimeCount: config.LifetimeCount, + MaxNotificationsPerPublish: config.MaxNotificationsPerPublish, + SamplingIntervalMs: config.SamplingIntervalMs, + QueueSize: config.QueueSize, + SecurityMode: config.SecurityMode.ToString(), + AutoAcceptUntrustedCerts: config.AutoAcceptUntrustedCerts); _status = ConnectionHealth.Connecting; @@ -71,49 +73,40 @@ public class OpcUaDataConnection : IDataConnection _disconnectFired = false; _logger.LogInformation("OPC UA connected to {Endpoint}", _endpointUrl); - // Heartbeat stale tag monitoring (optional) - await StartHeartbeatMonitorAsync(connectionDetails, cancellationToken); + await StartHeartbeatMonitorAsync(config.Heartbeat, cancellationToken); } - private async Task StartHeartbeatMonitorAsync(IDictionary connectionDetails, CancellationToken cancellationToken) + private async Task StartHeartbeatMonitorAsync(OpcUaHeartbeatConfig? heartbeat, CancellationToken cancellationToken) { - if (!connectionDetails.TryGetValue("HeartbeatTagPath", out var heartbeatTag) || string.IsNullOrWhiteSpace(heartbeatTag)) + if (heartbeat is null || string.IsNullOrWhiteSpace(heartbeat.TagPath)) return; - var maxSilenceSeconds = ParseInt(connectionDetails, "HeartbeatMaxSilence", 30); - _staleMonitor?.Dispose(); - _staleMonitor = new StaleTagMonitor(TimeSpan.FromSeconds(maxSilenceSeconds)); + _staleMonitor = new StaleTagMonitor(TimeSpan.FromSeconds(heartbeat.MaxSilenceSeconds)); _staleMonitor.Stale += () => { - _logger.LogWarning("OPC UA heartbeat tag '{Tag}' stale — no update in {Seconds}s", heartbeatTag, maxSilenceSeconds); + _logger.LogWarning("OPC UA heartbeat tag '{Tag}' stale — no update in {Seconds}s", + heartbeat.TagPath, heartbeat.MaxSilenceSeconds); RaiseDisconnected(); }; try { - _heartbeatSubscriptionId = await SubscribeAsync(heartbeatTag, (_, _) => _staleMonitor.OnValueReceived(), cancellationToken); + _heartbeatSubscriptionId = await SubscribeAsync(heartbeat.TagPath, + (_, _) => _staleMonitor.OnValueReceived(), cancellationToken); _staleMonitor.Start(); - _logger.LogInformation("OPC UA heartbeat monitor started for '{Tag}' with {Seconds}s max silence", heartbeatTag, maxSilenceSeconds); + _logger.LogInformation("OPC UA heartbeat monitor started for '{Tag}' with {Seconds}s max silence", + heartbeat.TagPath, heartbeat.MaxSilenceSeconds); } catch (Exception ex) { - _logger.LogWarning(ex, "Failed to subscribe to heartbeat tag '{Tag}' — stale monitor not active", heartbeatTag); + _logger.LogWarning(ex, "Failed to subscribe to heartbeat tag '{Tag}' — stale monitor not active", + heartbeat.TagPath); _staleMonitor.Dispose(); _staleMonitor = null; } } - internal static int ParseInt(IDictionary d, string key, int defaultValue) - { - return d.TryGetValue(key, out var str) && int.TryParse(str, out var val) ? val : defaultValue; - } - - internal static bool ParseBool(IDictionary d, string key, bool defaultValue) - { - return d.TryGetValue(key, out var str) && bool.TryParse(str, out var val) ? val : defaultValue; - } - private void OnClientConnectionLost() { RaiseDisconnected();