400 lines
14 KiB
Markdown
400 lines
14 KiB
Markdown
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) 테스트
|
||
✅ 실시간 데이터 수신 테스트 |