using System.Diagnostics;
using Microsoft.Extensions.Logging;
using Opc.Ua;
using Opc.Ua.Client;
using RobotNet10.Script.IO;
namespace RobotNet10.ScriptEngine.IO;
///
/// Implementation of OPC UA connection using OPCFoundation.NetStandard.Opc.Ua library.
///
public class OpcUaConnection : IOpcUaConnection
{
private Session? _session;
private SessionReconnectHandler? _reconnectHandler;
private bool _disposed;
public string EndpointUrl { get; }
public bool IsConnected { get; private set; }
public OpcUaConnection(string endpointUrl)
{
EndpointUrl = endpointUrl;
IsConnected = false;
}
public async Task ConnectAsync()
{
await ConnectAsync(string.Empty, string.Empty);
}
public async Task ConnectAsync(string username, string password)
{
if (IsConnected)
return;
try
{
var applicationConfiguration = new ApplicationConfiguration
{
ApplicationName = "RobotNet10 ScriptEngine",
ApplicationUri = Utils.Format(@"urn:{0}:ScriptEngine", System.Net.Dns.GetHostName()),
ApplicationType = ApplicationType.Client,
SecurityConfiguration = new SecurityConfiguration
{
ApplicationCertificate = new CertificateIdentifier { StoreType = @"Directory", StorePath = @"%CommonApplicationData%\OPC Foundation\CertificateStores\MachineDefault" },
TrustedIssuerCertificates = new CertificateTrustList { StoreType = @"Directory", StorePath = @"%CommonApplicationData%\OPC Foundation\CertificateStores\UA Certificate Authorities" },
TrustedPeerCertificates = new CertificateTrustList { StoreType = @"Directory", StorePath = @"%CommonApplicationData%\OPC Foundation\CertificateStores\UA Applications" },
RejectedCertificateStore = new CertificateTrustList { StoreType = @"Directory", StorePath = @"%CommonApplicationData%\OPC Foundation\CertificateStores\RejectedCertificates" },
AutoAcceptUntrustedCertificates = true,
RejectSHA1SignedCertificates = false
},
TransportConfigurations = new TransportConfigurationCollection(),
ClientConfiguration = new ClientConfiguration
{
DefaultSessionTimeout = 60000
}
};
await applicationConfiguration.ValidateAsync(ApplicationType.Client);
// Discover endpoints first using new API
var endpointUrl = new Uri(EndpointUrl);
var telemetryContext = new SimpleTelemetryContext();
var discoveryClient = await DiscoveryClient.CreateAsync(applicationConfiguration, endpointUrl, DiagnosticsMasks.All, CancellationToken.None);
var endpoints = await discoveryClient.GetEndpointsAsync(null, CancellationToken.None);
await discoveryClient.CloseAsync(CancellationToken.None);
// Select endpoint - use the first available endpoint or find best match
if (endpoints == null || endpoints.Count == 0)
{
throw new InvalidOperationException($"No endpoints found for OPC UA server at {EndpointUrl}");
}
// Try to find a secure endpoint first, otherwise use the first one
var endpointDescription = endpoints.FirstOrDefault(e => e.SecurityMode != MessageSecurityMode.None) ?? endpoints[0];
var endpointConfiguration = EndpointConfiguration.Create(applicationConfiguration);
var configuredEndpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);
UserIdentity? userIdentity = null;
if (!string.IsNullOrEmpty(username))
{
var passwordBytes = string.IsNullOrEmpty(password) ? Array.Empty() : System.Text.Encoding.UTF8.GetBytes(password);
userIdentity = new UserIdentity(username, passwordBytes);
}
// Use ISessionFactory.CreateAsync instead of Session.CreateAsync
ISessionFactory sessionFactory = new DefaultSessionFactory(telemetryContext);
var session = await sessionFactory.CreateAsync(
applicationConfiguration,
configuredEndpoint,
updateBeforeConnect: false,
checkDomain: false,
"RobotNet10 ScriptEngine Session",
60000,
userIdentity,
preferredLocales: null,
CancellationToken.None);
// Cast ISession to Session
_session = session as Session ?? throw new InvalidOperationException("Failed to create OPC UA session");
_session.KeepAlive += (ISession session, KeepAliveEventArgs e) =>
{
if (e.CurrentState != ServerState.Unknown)
{
return;
}
if (_reconnectHandler != null && session is Session sessionImpl)
{
_reconnectHandler.BeginReconnect(sessionImpl, 5000, (sender, args) => { });
}
};
_reconnectHandler = new SessionReconnectHandler(telemetryContext, false, 5000);
_reconnectHandler.BeginReconnect(_session, 5000, (sender, e) => { });
IsConnected = true;
}
catch
{
Disconnect();
throw;
}
}
public async Task DisconnectAsync()
{
IsConnected = false;
_reconnectHandler?.Dispose();
_reconnectHandler = null;
if (_session != null)
{
try
{
await _session.CloseAsync(CancellationToken.None);
}
catch
{
// Ignore errors during close
}
_session.Dispose();
}
_session = null;
}
private void Disconnect()
{
// Synchronous wrapper for async disconnect
DisconnectAsync().GetAwaiter().GetResult();
}
public async Task