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; using Npgsql; // πŸ‘ˆ PostgreSQL 라이브러리 μΆ”κ°€ 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); // 1. DB μ—°κ²° λ¬Έμžμ—΄ (λΉ„λ°€λ²ˆν˜Έλ₯Ό 본인 섀정에 맞게 μˆ˜μ •ν•˜μ„Έμš”) static string dbConnString = "Host=localhost;Username=postgres;Password=postgres;Database=opcdb"; 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 { } } } // 2. DB μ €μž₯ ν•¨μˆ˜ static async Task SaveToDatabase(string tagName, double val, string status) { try { using var conn = new NpgsqlConnection(dbConnString); await conn.OpenAsync(); string sql = "INSERT INTO opc_history (tag_name, tag_value, status_code) VALUES (@tag, @val, @status)"; using var cmd = new NpgsqlCommand(sql, conn); cmd.Parameters.AddWithValue("tag", tagName); cmd.Parameters.AddWithValue("val", val); cmd.Parameters.AddWithValue("status", status); await cmd.ExecuteNonQueryAsync(); Console.WriteLine("πŸ’Ύ DB μ €μž₯ μ™„λ£Œ."); } catch (Exception ex) { Console.WriteLine($"❌ DB μ €μž₯ μ‹€νŒ¨: {ex.Message}"); } } static async Task Main(string[] args) { LoadStatusCodes(); string serverHostName = "192.168.0.20"; 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"; Directory.CreateDirectory(Path.GetDirectoryName(pfxPath)!); Directory.CreateDirectory("pki/trusted/certs"); Directory.CreateDirectory("pki/issuers/certs"); Directory.CreateDirectory("pki/rejected/certs"); X509Certificate2? 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 }, 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]; var endpoint = new ConfiguredEndpoint(null, selected, endpointConfig); var identity = new UserIdentity(userName, Encoding.UTF8.GetBytes(password)); session = await Session.Create(config, endpoint, false, "OpcTestSession", 60000, identity, null); Console.WriteLine($"βœ… Connected! SessionID: {session.SessionId}"); // 3. 데이터 읽기 및 DB μ €μž₯ 루프 (ν…ŒμŠ€νŠΈμš©μœΌλ‘œ 5회 반볡) string nodeID = "ns=1;s=shinam:p-6102.hzset.fieldvalue"; for (int i = 0; i < 5; i++) { var result = await session.ReadValueAsync(nodeID); double val = Convert.ToDouble(result.Value); string status = result.StatusCode.ToString(); Console.WriteLine($"[{i+1}] TAG: {val} (Status: {status})"); // DB에 μ €μž₯ await SaveToDatabase("p-6102", val, status); await Task.Delay(2000); // 2초 λŒ€κΈ° } } catch (Exception ex) { Console.WriteLine($"❌ 였λ₯˜ λ°œμƒ: {ex.Message}"); } finally { if (session != null) { await session.CloseAsync(); session.Dispose(); } } Console.WriteLine("\nμž‘μ—… μ™„λ£Œ. μ—”ν„°λ₯Ό λˆ„λ₯΄μ„Έμš”..."); Console.ReadLine(); } } }