using Opc.Ua; using Opc.Ua.Client; using Opc.Ua.Configuration; using System.Security.Cryptography.X509Certificates; namespace OpcConnectionTest { class Program { [Obsolete] static async Task Main(string[] args) { Console.WriteLine("=== Experion OPC UA Connection Test ===\n"); // ⚠️ Experion 서버 IP 주소 수정 string endpoint = "opc.tcp://192.168.0.20:4840"; string pfxPath = Path.Combine(Directory.GetCurrentDirectory(), "pki/own/certs/OpcTestClient.pfx"); // PFX 파일 존재 여부 먼저 확인 if (!File.Exists(pfxPath)) { Console.WriteLine($"❌ PFX 파일을 찾을 수 없습니다: {pfxPath}"); Console.WriteLine("\nPress Enter to exit..."); Console.ReadLine(); return; } // PFX를 직접 로드 (Exportable 플래그 필수 — OPC UA 서명에 개인키 접근 필요) X509Certificate2 clientCertificate = new X509Certificate2( pfxPath, "", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet); Console.WriteLine($"Server: {endpoint}"); Console.WriteLine($"Certificate: {clientCertificate.Subject}\n"); ISession? session = null; try { // 1. Configuration 생성 var config = new ApplicationConfiguration() { ApplicationName = "OPC Test Client", ApplicationType = ApplicationType.Client, ApplicationUri = "urn:OpcTestClient", SecurityConfiguration = new SecurityConfiguration { ApplicationCertificate = new CertificateIdentifier { StoreType = "Directory", StorePath = Path.GetFullPath("pki/own"), SubjectName = "CN=OpcTestClient", // PFX를 직접 주입 — Directory Store 탐색 없이 이 인증서를 사용 Certificate = clientCertificate }, TrustedPeerCertificates = new CertificateTrustList { StoreType = "Directory", StorePath = Path.GetFullPath("pki/trusted") }, TrustedIssuerCertificates = new CertificateTrustList { StoreType = "Directory", StorePath = Path.GetFullPath("pki/issuers") }, RejectedCertificateStore = new CertificateTrustList { StoreType = "Directory", StorePath = Path.GetFullPath("pki/rejected") }, AutoAcceptUntrustedCertificates = true, RejectSHA1SignedCertificates = false, AddAppCertToTrustedStore = true }, TransportConfigurations = new TransportConfigurationCollection(), TransportQuotas = new TransportQuotas { OperationTimeout = 15000 }, ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60000 } }; await config.ValidateAsync(ApplicationType.Client); // 인증서 로드 확인 (Find 대신 직접 참조) var cert = config.SecurityConfiguration.ApplicationCertificate.Certificate; if (cert == null) { Console.WriteLine("❌ [Internal Error] 인증서 주입에 실패했습니다!"); return; } Console.WriteLine($"✅ 인증서 로드 성공: {cert.Subject}"); Console.WriteLine($" 유효기간: {cert.NotBefore:yyyy-MM-dd} ~ {cert.NotAfter:yyyy-MM-dd}"); Console.WriteLine($" 개인키 포함: {cert.HasPrivateKey}"); if (!cert.HasPrivateKey) { Console.WriteLine("❌ 개인키가 없습니다. PFX 파일 또는 KeyStorageFlags를 확인하세요."); return; } Console.WriteLine("\nStep 1: Discovering endpoints..."); // 2. Endpoint 검색 var endpointConfig = EndpointConfiguration.Create(config); endpointConfig.OperationTimeout = 10000; var discoveryClient = await DiscoveryClient.CreateAsync( new Uri(endpoint), endpointConfig, config, DiagnosticsMasks.None, CancellationToken.None); var endpoints = await discoveryClient.GetEndpointsAsync(null); discoveryClient.Dispose(); if (endpoints == null || endpoints.Count == 0) { Console.WriteLine("❌ No endpoints found!"); return; } Console.WriteLine($"✅ Found {endpoints.Count} endpoint(s)"); foreach (var ep in endpoints) { Console.WriteLine($" - {ep.EndpointUrl} ({ep.SecurityMode})"); } Console.WriteLine("\nStep 2: Creating session..."); // 3. ConfiguredEndpoint 생성 var selectedEndpoint = endpoints[0]; var configuredEndpoint = new ConfiguredEndpoint( null, selectedEndpoint, EndpointConfiguration.Create(config)); // 4. Session 생성 session = await Session.Create( config, configuredEndpoint, false, "OPC Test Session", 60000, new UserIdentity(new AnonymousIdentityToken()), null); Console.WriteLine($"✅ Connected!"); Console.WriteLine($" Session ID: {session.SessionId}"); Console.WriteLine("\nStep 3: Reading server info..."); await ReadServerInfoAsync(session); Console.WriteLine("\nStep 4: Checking redundancy..."); await CheckRedundancyAsync(session); Console.WriteLine("\nStep 5: Browsing nodes..."); await BrowseNodesAsync(session); Console.WriteLine("\n✅ All tests completed!"); } catch (Exception ex) { Console.WriteLine($"\n❌ Error: {ex.Message}"); Console.WriteLine($"Type: {ex.GetType().Name}"); if (ex.InnerException != null) { Console.WriteLine($"Inner: {ex.InnerException.Message}"); } } finally { if (session != null) { try { Console.WriteLine("\nClosing session..."); await session.CloseAsync(); session.Dispose(); Console.WriteLine("✅ Session closed"); } catch { } } } Console.WriteLine("\nPress Enter to exit..."); Console.ReadLine(); } static async Task ReadServerInfoAsync(ISession session) { try { // Server State var state = await session.ReadValueAsync( new NodeId(Variables.Server_ServerStatus_State)); Console.WriteLine($" State: {state.Value}"); // Server Time var time = await session.ReadValueAsync( new NodeId(Variables.Server_ServerStatus_CurrentTime)); Console.WriteLine($" Time: {time.Value}"); } catch (Exception ex) { Console.WriteLine($" Error: {ex.Message}"); } } static async Task CheckRedundancyAsync(ISession session) { try { var serviceLevel = await session.ReadValueAsync( new NodeId(Variables.Server_ServiceLevel)); byte level = Convert.ToByte(serviceLevel.Value); Console.WriteLine($" Service Level: {level}"); if (level >= 200) Console.WriteLine(" ✅ PRIMARY server"); else if (level > 0) Console.WriteLine(" ⚠️ SECONDARY server"); else Console.WriteLine(" ℹ️ STANDALONE"); } catch (Exception ex) { Console.WriteLine($" Error: {ex.Message}"); } } static async Task BrowseNodesAsync(ISession session) { try { var browser = new Browser(session) { BrowseDirection = BrowseDirection.Forward, ReferenceTypeId = ReferenceTypeIds.HierarchicalReferences, IncludeSubtypes = true, NodeClassMask = (int)(NodeClass.Object | NodeClass.Variable) }; var references = await browser.BrowseAsync(ObjectIds.ObjectsFolder); Console.WriteLine($" Found {references.Count} top-level nodes:"); int count = 0; foreach (var r in references) { Console.WriteLine($" {r.DisplayName} ({r.NodeClass})"); if (++count >= 5) { Console.WriteLine($" ... and {references.Count - 5} more"); break; } } } catch (Exception ex) { Console.WriteLine($" Error: {ex.Message}"); } } } }