using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Security.Cryptography.X509Certificates; using System.Text.Json; using Opc.Ua; using Opc.Ua.Client; namespace OpcConnectionTest { public class StatusCodeInfo { public string Name { get; set; } = ""; public string Hex { get; set; } = ""; public ulong Decimal { get; set; } public string Description { get; set; } = ""; } class Program { static Dictionary _statusCodeMap = new(StringComparer.OrdinalIgnoreCase); static void LoadStatusCodes() { string path = Path.Combine(Directory.GetCurrentDirectory(), "statuscode.json"); if (File.Exists(path)) { try { var json = File.ReadAllText(path); var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; var list = JsonSerializer.Deserialize>(json, options); if (list != null) { foreach (var item in list) _statusCodeMap[item.Hex] = item; Console.WriteLine($"✅ {_statusCodeMap.Count}개의 에러 코드 정의 로드 완료."); } } catch { } } } static async Task Main(string[] args) { LoadStatusCodes(); // 1. 핵심 변수 (서버 이름 사칭 방지) string serverHostName = "192.168.0.20"; //DESKTOP-8VS6SD2 string clientHostName = "dbsvr"; string endpointUrl = $"opc.tcp://{serverHostName}:4840"; string applicationUri = $"urn:{clientHostName}:OpcTestClient"; string pfxPath = Path.Combine(Directory.GetCurrentDirectory(), "pki/own/certs/OpcTestClient.pfx"); string pfxPassword = ""; string userName = "mngr"; string password = "mngr"; // 필수 폴더들 생성 (TrustedIssuer 포함) Directory.CreateDirectory(Path.GetDirectoryName(pfxPath)!); Directory.CreateDirectory("pki/trusted/certs"); Directory.CreateDirectory("pki/issuers/certs"); // ← 이 경로가 필요함 Directory.CreateDirectory("pki/rejected/certs"); X509Certificate2? clientCert = null; if (!File.Exists(pfxPath)) { var builder = CertificateFactory.CreateCertificate( applicationUri, "OpcTestClient", $"CN=OpcTestClient, O=MyCompany", new List { "localhost", clientHostName, "192.168.0.5", "192.168.0.102" } ); clientCert = builder.CreateForRSA(); File.WriteAllBytes(pfxPath, clientCert.Export(X509ContentType.Pfx, pfxPassword)); Console.WriteLine($"✨ 새 인증서 생성됨: {applicationUri}"); } else { clientCert = new X509Certificate2(pfxPath, pfxPassword, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet); } var config = new ApplicationConfiguration { ApplicationName = "OpcTestClient", ApplicationType = ApplicationType.Client, ApplicationUri = applicationUri, SecurityConfiguration = new SecurityConfiguration { ApplicationCertificate = new CertificateIdentifier { Certificate = clientCert }, // ⚠️ 에러 발생했던 지점: 아래 3개 경로가 모두 명시되어야 함 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, AddAppCertToTrustedStore = true }, TransportQuotas = new TransportQuotas { OperationTimeout = 15000 }, ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60000 } }; await config.ValidateAsync(ApplicationType.Client); config.CertificateValidator.CertificateValidation += (v, e) => { if (e.Error.StatusCode != StatusCodes.Good) e.Accept = true; }; ISession? session = null; try { Console.WriteLine($"Step 1: Connecting to {endpointUrl}..."); var endpointConfig = EndpointConfiguration.Create(config); using (var discovery = await DiscoveryClient.CreateAsync(config, new Uri(endpointUrl), DiagnosticsMasks.All, CancellationToken.None)) { var endpoints = await discovery.GetEndpointsAsync(null); var selected = endpoints.OrderByDescending(e => e.SecurityLevel) .FirstOrDefault(e => e.SecurityPolicyUri.Contains("Basic256Sha256")) ?? endpoints[0]; Console.WriteLine($"🔍 정책 선택됨: {selected.SecurityPolicyUri}"); var endpoint = new ConfiguredEndpoint(null, selected, endpointConfig); Console.WriteLine("Step 2: Creating session..."); var identity = new UserIdentity(userName, Encoding.UTF8.GetBytes(password)); #pragma warning disable CS0618 session = await Session.Create(config, endpoint, false, "OpcTestSession", 60000, identity, null); #pragma warning restore CS0618 } Console.WriteLine($"✅ Connected! SessionID: {session.SessionId}"); var result = await session.ReadValueAsync("ns=1;s=shinam:p-6102.hzset.fieldvalue"); Console.WriteLine($"🟢 TAG VALUE: {result.Value}"); } catch (Exception ex) { if (ex is ServiceResultException sre) { string hexCode = $"0x{((uint)sre.StatusCode):X8}"; Console.WriteLine($"\n❌ OPC UA 서비스 오류: {hexCode}"); if (_statusCodeMap.TryGetValue(hexCode, out var info)) { Console.WriteLine($"👉 에러명: {info.Name}"); Console.WriteLine($"👉 설 명: {info.Description}"); } } else Console.WriteLine($"❌ 오류: {ex.Message}"); } finally { if (session != null) { await session.CloseAsync(); session.Dispose(); } } Console.WriteLine("\n엔터를 누르면 종료됩니다..."); Console.ReadLine(); } } }