OPC UA 드라이버 예제
OPC UA (OPC Unified Architecture) 드라이버 구현 가이드입니다.
📋 개요
OPC UA는 산업 4.0 시대의 표준 통신 프로토콜입니다.
학습 시간: 60분
난이도: ⭐⭐⭐ 고급
🎯 OPC UA 특징
- 플랫폼 독립적: Windows, Linux, 임베디드
- 보안: 암호화, 인증, 권한 관리
- 정보 모델: 계층적 주소 공간
- 메서드 호출: RPC 지원
- 이벤트/알람: Pub/Sub
🔧 구현
프로젝트 생성
cafe init --name OpcUaDriver --template driver-full --git
cd OpcUaDriver/src
# OPC Foundation 라이브러리
dotnet add package OPCFoundation.NetStandard.Opc.Ua
dotnet add package OPCFoundation.NetStandard.Opc.Ua.Client
Driver 클래스
using Caffeine.Core.Abstractions;
using Caffeine.Core.Models;
using Microsoft.Extensions.Logging;
using Opc.Ua;
using Opc.Ua.Client;
using Opc.Ua.Configuration;
namespace OpcUaDriver;
public class OpcUaDriver : IDriver
{
private readonly ILogger<OpcUaDriver> _logger;
private readonly string _endpointUrl;
private readonly ApplicationConfiguration _appConfig;
private Session? _session;
private Subscription? _subscription;
public OpcUaDriver(
string endpointUrl = "opc.tcp://localhost:4840",
ILogger<OpcUaDriver>? logger = null)
{
_endpointUrl = endpointUrl;
_logger = logger ?? NullLogger<OpcUaDriver>.Instance;
_appConfig = CreateApplicationConfiguration();
}
public async Task<bool> ConnectAsync(CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("Connecting to OPC UA server: {Url}", _endpointUrl);
// 1. Discover endpoints
var endpointDescription = CoreClientUtils.SelectEndpoint(
_endpointUrl,
useSecurity: false);
// 2. Create endpoint configuration
var endpointConfiguration = EndpointConfiguration.Create(_appConfig);
var endpoint = new ConfiguredEndpoint(
null,
endpointDescription,
endpointConfiguration);
// 3. Create session
_session = await Session.Create(
_appConfig,
endpoint,
updateBeforeConnect: false,
checkDomain: false,
sessionName: "Caffeine OPC UA Driver",
sessionTimeout: 60000,
userIdentity: new UserIdentity(new AnonymousIdentityToken()),
preferredLocales: null,
ct: cancellationToken);
_logger.LogInformation("✅ Connected to OPC UA server");
// 4. Setup subscription (optional)
CreateSubscription();
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "OPC UA connection failed");
return false;
}
}
public async Task DisconnectAsync()
{
if (_subscription != null)
{
_subscription.Delete(deleteInServer: true);
_subscription = null;
}
if (_session != null)
{
await _session.CloseAsync();
_session.Dispose();
_session = null;
}
_logger.LogInformation("Disconnected from OPC UA server");
}
public async Task<TagValue?> ReadTagAsync(
string tagName,
CancellationToken cancellationToken = default)
{
if (_session == null)
{
throw new InvalidOperationException("Not connected");
}
try
{
// NodeId 파싱: "ns=2;s=Temperature"
var nodeId = NodeId.Parse(tagName);
// Read value
var value = await _session.ReadValueAsync(nodeId, cancellationToken);
return new TagValue(
tagName,
value.Value ?? 0,
ConvertStatusCode(value.StatusCode));
}
catch (Exception ex)
{
_logger.LogError(ex, "Read failed: {TagName}", tagName);
return new TagValue(tagName, 0, TagQuality.Bad);
}
}
public async Task<bool> WriteTagAsync(
string tagName,
object value,
CancellationToken cancellationToken = default)
{
if (_session == null)
{
throw new InvalidOperationException("Not connected");
}
try
{
var nodeId = NodeId.Parse(tagName);
var writeValue = new WriteValue
{
NodeId = nodeId,
AttributeId = Attributes.Value,
Value = new DataValue(new Variant(value))
};
var writeValueCollection = new WriteValueCollection { writeValue };
var results = await _session.WriteAsync(
requestHeader: null,
nodesToWrite: writeValueCollection,
ct: cancellationToken);
var success = StatusCode.IsGood(results.Results[0]);
if (success)
{
_logger.LogInformation("✅ Write success: {TagName} = {Value}",
tagName, value);
}
return success;
}
catch (Exception ex)
{
_logger.LogError(ex, "Write failed: {TagName}", tagName);
return false;
}
}
// Subscription for real-time monitoring
private void CreateSubscription()
{
_subscription = new Subscription(_session!.DefaultSubscription)
{
PublishingInterval = 1000,
PublishingEnabled = true,
LifetimeCount = 1000,
KeepAliveCount = 10,
Priority = 100
};
_session.AddSubscription(_subscription);
_subscription.Create();
}
public void SubscribeToNode(
string nodeId,
Action<MonitoredItem, MonitoredItemNotificationEventArgs> callback)
{
if (_subscription == null)
{
throw new InvalidOperationException("Subscription not created");
}
var monitoredItem = new MonitoredItem(_subscription.DefaultItem)
{
DisplayName = nodeId,
StartNodeId = NodeId.Parse(nodeId),
SamplingInterval = 1000,
QueueSize = 10,
DiscardOldest = true
};
monitoredItem.Notification += callback;
_subscription.AddItem(monitoredItem);
_subscription.ApplyChanges();
}
// Helper Methods
private ApplicationConfiguration CreateApplicationConfiguration()
{
var config = new ApplicationConfiguration
{
ApplicationName = "Caffeine OPC UA Driver",
ApplicationType = ApplicationType.Client,
SecurityConfiguration = new SecurityConfiguration
{
ApplicationCertificate = new CertificateIdentifier(),
TrustedPeerCertificates = new CertificateTrustList(),
TrustedIssuerCertificates = new CertificateTrustList(),
RejectedCertificateStore = new CertificateTrustList(),
AutoAcceptUntrustedCertificates = true
},
TransportConfigurations = new TransportConfigurationCollection(),
TransportQuotas = new TransportQuotas
{
OperationTimeout = 15000
},
ClientConfiguration = new ClientConfiguration
{
DefaultSessionTimeout = 60000
}
};
config.Validate(ApplicationType.Client).GetAwaiter().GetResult();
return config;
}
private TagQuality ConvertStatusCode(StatusCode statusCode)
{
if (StatusCode.IsGood(statusCode))
return TagQuality.Good;
else if (StatusCode.IsUncertain(statusCode))
return TagQuality.Uncertain;
else
return TagQuality.Bad;
}
}
🧪 테스트
OPC UA 시뮬레이터
추천 도구:
- Prosys OPC UA Simulation Server
- KEPServerEX
- OPC UA .NET Sample Server
📚 고급 기능
Browse 노드
public async Task<List<ReferenceDescription>> BrowseAsync(NodeId nodeId)
{
var browser = new Browser(_session!)
{
BrowseDirection = BrowseDirection.Forward,
ReferenceTypeId = ReferenceTypeIds.HierarchicalReferences,
IncludeSubtypes = true,
NodeClassMask = 0,
ResultMask = (uint)BrowseResultMask.All
};
var references = await browser.BrowseAsync(nodeId);
return references.ToList();
}
이벤트 구독
public void SubscribeToEvents(NodeId eventTypeId, Action<EventFieldList> callback)
{
var monitoredItem = new MonitoredItem(_subscription!.DefaultItem)
{
StartNodeId = ObjectIds.Server,
AttributeId = Attributes.EventNotifier,
MonitoringMode = MonitoringMode.Reporting,
SamplingInterval = 0
};
monitoredItem.Notification += (item, e) =>
{
foreach (var eventField in (EventFieldList[])e.NotificationValue)
{
callback(eventField);
}
};
_subscription.AddItem(monitoredItem);
_subscription.ApplyChanges();
}
📚 참고 자료
작성일: 2026-01-28
버전: 2.0