Experion 서버 연결 테스트 코드를 작성해드리겠습니다!
🚀 빠른 테스트 프로젝트 생성
bashcd ~/projects
mkdir OpcConnectionTest
cd OpcConnectionTest

# 콘솔 프로젝트 생성
dotnet new console

# .NET 8.0 고정
dotnet new globaljson --sdk-version 8.0.123 --force

# OPC UA 패키지 설치
dotnet add package OPCFoundation.NetStandard.Opc.Ua
dotnet add package OPCFoundation.NetStandard.Opc.Ua.Client

📝 Program.cs
csharpusing Opc.Ua;
using Opc.Ua.Client;
using Opc.Ua.Configuration;

namespace OpcConnectionTest
{
    class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine("=== Experion OPC UA Connection Test ===\n");

            // Experion 서버 정보 (VMware 환경)
            string primaryEndpoint = "opc.tcp://192.168.xxx.xxx:4840";  // Primary 서버 IP
            string secondaryEndpoint = "opc.tcp://192.168.xxx.xxx:4840"; // Secondary 서버 IP (있다면)

            Console.WriteLine($"Primary Server: {primaryEndpoint}");
            Console.WriteLine($"Secondary Server: {secondaryEndpoint}\n");

            try
            {
                // 1. Application Configuration 생성
                var config = new ApplicationConfiguration()
                {
                    ApplicationName = "OPC UA Test Client",
                    ApplicationType = ApplicationType.Client,
                    ApplicationUri = "urn:localhost:OpcUaTestClient",

                    SecurityConfiguration = new SecurityConfiguration
                    {
                        ApplicationCertificate = new CertificateIdentifier
                        {
                            StoreType = "Directory",
                            StorePath = "OPC Foundation/CertificateStores/MachineDefault"
                        },
                        AutoAcceptUntrustedCertificates = true, // 테스트용
                        RejectSHA1SignedCertificates = false
                    },

                    TransportConfigurations = new TransportConfigurationCollection(),
                    TransportQuotas = new TransportQuotas { OperationTimeout = 15000 },

                    ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60000 },

                    TraceConfiguration = new TraceConfiguration
                    {
                        OutputFilePath = "OpcUaTest.log",
                        TraceMasks = 0 // 로그 레벨 (0: 없음, 1: Error, 511: 전부)
                    }
                };

                await config.Validate(ApplicationType.Client);

                // 2. Primary 서버 검색
                Console.WriteLine("Step 1: Discovering Primary Server...");
                var endpoints = await DiscoverEndpoints(primaryEndpoint);

                if (endpoints == null || endpoints.Count == 0)
                {
                    Console.WriteLine("❌ No endpoints found on Primary server!");
                    return;
                }

                Console.WriteLine($"✅ Found {endpoints.Count} endpoint(s)");
                foreach (var ep in endpoints)
                {
                    Console.WriteLine($"  - {ep.EndpointUrl} ({ep.SecurityMode})");
                }

                // 3. Primary 서버 연결
                Console.WriteLine("\nStep 2: Connecting to Primary Server...");
                var session = await CreateSession(config, endpoints[0]);

                if (session == null)
                {
                    Console.WriteLine("❌ Failed to create session!");
                    return;
                }

                Console.WriteLine($"✅ Connected! Session ID: {session.SessionId}");

                // 4. 서버 정보 읽기
                Console.WriteLine("\nStep 3: Reading Server Information...");
                await ReadServerInfo(session);

                // 5. Redundancy 정보 읽기 (Experion Redundant 확인)
                Console.WriteLine("\nStep 4: Checking Redundancy Support...");
                await CheckRedundancy(session);

                // 6. 샘플 태그 읽기 (있다면)
                Console.WriteLine("\nStep 5: Reading Sample Tags...");
                await ReadSampleTags(session);

                // 7. 연결 종료
                Console.WriteLine("\nStep 6: Closing connection...");
                session.Close();
                Console.WriteLine("✅ Connection closed successfully!");

                // Secondary 서버도 테스트 (선택)
                if (!string.IsNullOrEmpty(secondaryEndpoint) && secondaryEndpoint != primaryEndpoint)
                {
                    Console.WriteLine("\n\n=== Testing Secondary Server ===");
                    await TestSecondaryServer(config, secondaryEndpoint);
                }

                Console.WriteLine("\n✅ All tests completed!");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"\n❌ Error: {ex.Message}");
                Console.WriteLine($"Stack: {ex.StackTrace}");
            }

            Console.WriteLine("\nPress any key to exit...");
            Console.ReadKey();
        }

        // Endpoint 검색
        static async Task<EndpointDescriptionCollection> DiscoverEndpoints(string serverUrl)
        {
            try
            {
                var endpointConfiguration = EndpointConfiguration.Create();
                endpointConfiguration.OperationTimeout = 10000;

                using (var client = DiscoveryClient.Create(new Uri(serverUrl), endpointConfiguration))
                {
                    var endpoints = await client.GetEndpointsAsync(null);
                    return endpoints;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Discovery failed: {ex.Message}");
                return null;
            }
        }

        // 세션 생성
        static async Task<ISession> CreateSession(ApplicationConfiguration config, EndpointDescription endpoint)
        {
            try
            {
                var endpointConfiguration = EndpointConfiguration.Create(config);
                var configuredEndpoint = new ConfiguredEndpoint(null, endpoint, endpointConfiguration);

                var session = await Session.Create(
                    config,
                    configuredEndpoint,
                    false,
                    config.ApplicationName,
                    60000,
                    new UserIdentity(new AnonymousIdentityToken()), // 익명 인증
                    null
                );

                return session;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Session creation failed: {ex.Message}");
                return null;
            }
        }

        // 서버 정보 읽기
        static async Task ReadServerInfo(ISession session)
        {
            try
            {
                // Server Status 노드 읽기
                var nodeId = new NodeId(Objects.Server_ServerStatus);
                var value = await Task.Run(() => session.ReadValue(nodeId));

                Console.WriteLine($"Server Status: {value.Value}");

                // Server State 읽기
                var stateNodeId = new NodeId(Variables.Server_ServerStatus_State);
                var stateValue = await Task.Run(() => session.ReadValue(stateNodeId));
                Console.WriteLine($"Server State: {stateValue.Value}");

                // Current Time 읽기
                var timeNodeId = new NodeId(Variables.Server_ServerStatus_CurrentTime);
                var timeValue = await Task.Run(() => session.ReadValue(timeNodeId));
                Console.WriteLine($"Server Current Time: {timeValue.Value}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Failed to read server info: {ex.Message}");
            }
        }

        // Redundancy 지원 확인
        static async Task CheckRedundancy(ISession session)
        {
            try
            {
                // ServiceLevel 읽기 (Primary: 200+, Secondary: <200)
                var serviceLevelNode = new NodeId(Variables.Server_ServiceLevel);
                var serviceLevel = await Task.Run(() => session.ReadValue(serviceLevelNode));
                
                byte level = Convert.ToByte(serviceLevel.Value);
                Console.WriteLine($"Service Level: {level}");

                if (level >= 200)
                {
                    Console.WriteLine("✅ This is PRIMARY server");
                }
                else if (level > 0)
                {
                    Console.WriteLine("⚠️  This is SECONDARY server");
                }
                else
                {
                    Console.WriteLine("ℹ️  Standalone server (no redundancy)");
                }

                // ServerRedundancy 노드 확인
                var redundancyNode = new NodeId(Objects.Server_ServerRedundancy);
                var redundancyValue = await Task.Run(() => session.ReadValue(redundancyNode));
                Console.WriteLine($"Redundancy Support: {redundancyValue.StatusCode}");

            }
            catch (Exception ex)
            {
                Console.WriteLine($"Redundancy check failed: {ex.Message}");
                Console.WriteLine("ℹ️  Server may not support redundancy");
            }
        }

        // 샘플 태그 읽기
        static async Task ReadSampleTags(ISession session)
        {
            try
            {
                // Browse root folder
                var browser = new Browser(session)
                {
                    BrowseDirection = BrowseDirection.Forward,
                    ReferenceTypeId = ReferenceTypeIds.HierarchicalReferences,
                    IncludeSubtypes = true,
                    NodeClassMask = (int)NodeClass.Variable | (int)NodeClass.Object,
                    ResultMask = (uint)BrowseResultMask.All
                };

                // Objects 폴더 탐색
                var references = browser.Browse(ObjectIds.ObjectsFolder);

                Console.WriteLine($"Found {references.Count} nodes in Objects folder:");
                int count = 0;
                foreach (var reference in references)
                {
                    Console.WriteLine($"  - {reference.DisplayName} ({reference.NodeClass})");
                    
                    if (++count >= 10) // 처음 10개만 표시
                    {
                        Console.WriteLine("  ... (more)");
                        break;
                    }
                }

                // 특정 태그가 있다면 읽기 (NodeId를 알고 있을 때)
                // var tagNodeId = new NodeId("ns=2;s=YourTagName");
                // var tagValue = await Task.Run(() => session.ReadValue(tagNodeId));
                // Console.WriteLine($"Tag Value: {tagValue.Value}");

            }
            catch (Exception ex)
            {
                Console.WriteLine($"Tag reading failed: {ex.Message}");
            }
        }

        // Secondary 서버 테스트
        static async Task TestSecondaryServer(ApplicationConfiguration config, string serverUrl)
        {
            try
            {
                var endpoints = await DiscoverEndpoints(serverUrl);
                
                if (endpoints == null || endpoints.Count == 0)
                {
                    Console.WriteLine("❌ Secondary server not reachable");
                    return;
                }

                Console.WriteLine($"✅ Secondary server found with {endpoints.Count} endpoint(s)");

                var session = await CreateSession(config, endpoints[0]);
                if (session != null)
                {
                    Console.WriteLine("✅ Secondary server connection successful!");
                    
                    // ServiceLevel 확인
                    var serviceLevelNode = new NodeId(Variables.Server_ServiceLevel);
                    var serviceLevel = await Task.Run(() => session.ReadValue(serviceLevelNode));
                    Console.WriteLine($"Secondary Service Level: {serviceLevel.Value}");

                    session.Close();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Secondary test failed: {ex.Message}");
            }
        }
    }
}

⚙️ 설정 수정 포인트
Line 15-16: Experion 서버 IP 주소 변경
csharpstring primaryEndpoint = "opc.tcp://192.168.1.10:4840";  // ← 실제 IP
string secondaryEndpoint = "opc.tcp://192.168.1.11:4840"; // ← 실제 IP

🚀 실행
bashcd ~/projects/OpcConnectionTest

# 빌드
dotnet build

# 실행
dotnet run
```

---

## 📊 예상 출력
```
=== Experion OPC UA Connection Test ===

Primary Server: opc.tcp://192.168.1.10:4840
Secondary Server: opc.tcp://192.168.1.11:4840

Step 1: Discovering Primary Server...
✅ Found 2 endpoint(s)
  - opc.tcp://192.168.1.10:4840 (SignAndEncrypt)
  - opc.tcp://192.168.1.10:4840 (None)

Step 2: Connecting to Primary Server...
✅ Connected! Session ID: ns=1;i=123456

Step 3: Reading Server Information...
Server State: Running
Server Current Time: 2026-02-08 15:30:45

Step 4: Checking Redundancy Support...
Service Level: 255
✅ This is PRIMARY server
Redundancy Support: Good

Step 5: Reading Sample Tags...
Found 15 nodes in Objects folder:
  - Server (Object)
  - DeviceSet (Object)
  - ...

Step 6: Closing connection...
✅ Connection closed successfully!

✅ All tests completed!

🔧 문제 해결
1. "Connection refused" 에러
bash# 방화벽 확인
sudo ufw status
sudo ufw allow 4840/tcp

# Experion 서버 ping 확인
ping 192.168.1.10
2. "Certificate validation failed"
csharp// Line 25에서 이미 설정됨
AutoAcceptUntrustedCertificates = true
3. IP 주소 확인
bash# VMware에서 Experion 서버 IP 확인
# Windows 개발 PC에서
ping experion-server-name

📝 다음 단계
연결 성공하면:

✅ Tag 목록 가져오기
✅ Tag 구독 (Subscription) 테스트
✅ 실시간 데이터 수신 테스트