一、概述
OPC UA (Open Platform Communications Unified Architecture) 是工业自动化领域的设备通讯标准,提供安全、可靠、平台无关的数据交换机制。本模块实现了OPC UA客户端功能,支持连接、浏览、读取、写入、订阅等核心操作。
二、通讯报文原理
2.1 OPC UA协议栈
应用层: OPC UA服务 (读取、写入、订阅、浏览)
传输层: TCP/IP协议
安全层: 安全策略(加密、签名)
编码层: 二进制编码/XML编码
2.2 通讯模式
OPC UA采用客户端-服务器架构:
客户端 ←→ OPC UA会话 ←→ 服务器
↓
安全通道
(加密+签名)
2.3 连接建立流程
1. 发现服务器端点
客户端 → GetEndpoints → 服务器 → EndpointDescription[]
2. 创建安全通道
客户端 → OpenSecureChannel → 服务器 → SecureChannel
3. 创建会话
客户端 → CreateSession → 服务器 → SessionId
4. 激活会话
客户端 → ActivateSession → 服务器 → 会话已激活
2.4 服务调用报文结构
请求报文:
Type: Request Message
- RequestHeader: 请求头(认证、时间戳等)
- 具体服务参数:NodeId、AttributeId等
响应报文:
Type: Response Message
- ResponseHeader: 响应头(状态码、时间戳)
- 具体服务结果:Value、StatusCode等
2.5 节点浏览机制
OPC UA使用节点(Node)和引用(Reference)构建地址空间:
Root Folder (i=85)
├── Objects (i=85)
│ └── Server (i=2253)
│ └── Arrays (ns=2;s=Arrays)
├── Types (i=86)
└── Views (i=87)
节点标识符(NodeId)格式:
i=85:数字标识符ns=2;s=MyVariable:命名空间索引2 + 字符串标识符ns=2;g=xxxxxxxx:命名空间索引2 + GUID标识符
2.6 订阅机制
OPC UA订阅采用发布-订阅模式:
1. 创建订阅 (Subscription)
客户端 → CreateSubscription → 服务器
2. 添加监控项 (MonitoredItem)
客户端 → CreateMonitoredItems → 服务器
3. 数据变化通知
服务器 → DataChangeNotification → 客户端
4. 保持连接
客户端 ↔ 服务器 (Publish请求/响应)
订阅参数:
PublishingInterval:发布间隔(默认1000ms)ClientHandle:客户端句柄SamplingInterval:采样间隔QueueSize:队列大小DiscardOldest:队列满时是否丢弃最旧值
三、调用的库和方法
3.1 核心库
OPCFoundation.NetStandard.Opc.Ua
- 版本:1.5.378.134
- 来源:官方NuGet包
- 作用:OPC UA协议实现
主要命名空间:
Opc.Ua:核心数据类型和定义Opc.Ua.Client:客户端功能Opc.Ua.Configuration:配置管理
3.2 核心类:OpcUaClient
3.2.1 BuildConfig 方法
功能:构建应用配置和证书管理
方法签名:
csharp
public void BuildConfig()
实现原理:
csharp
public void BuildConfig()
{
// 1. 创建应用实例
myAppInstance = new ApplicationInstance()
{
ApplicationType = ApplicationType.Client,
ApplicationName = "GKMLT_OPCUAClient",
};
// 2. 创建应用配置
myAppInstance.ApplicationConfiguration = new Opc.Ua.ApplicationConfiguration();
// 3. 进行安全配置
CreateClientConfiguration();
// 4. 配置证书验证
certificateValidator = new CertificateValidator();
myAppInstance.ApplicationConfiguration.CertificateValidator = certificateValidator;
certificateValidator.CertificateValidation += CertClient;
}
配置内容:
- 应用名称和URI
- 证书自动信任(开发环境)
- 传输配额(超时、消息大小等)
- 客户端会话超时
3.2.2 AnonmousConnect 方法
功能:匿名连接到OPC UA服务器
方法签名:
csharp
public static async Task<Session> AnonmousConnect(string serverUrl)
参数说明:
serverUrl:服务器Endpoint地址(如:opc.tcp://localhost:4840)
返回值:
Session:已建立的会话对象
实现代码:
csharp
public static async Task<Session> AnonmousConnect(string serverUrl)
{
// 1. 获取服务器端点
EndpointDescriptionCollection endpoints = GetEndpointDescription(serverUrl);
// 2. 选择端点(优先选择无安全策略)
EndpointDescription endpoint = SelectEndpoint(endpoints);
// 3. 配置端点
ConfiguredEndpoint configuredEndpoint = new ConfiguredEndpoint(null, endpoint, EndpointConfiguration.Create());
// 4. 创建匿名身份
userIdentity = new UserIdentity(new AnonymousIdentityToken());
// 5. 更新会话名称
string sessionName = myAppInstance.ApplicationName + " Session";
// 6. 创建并激活会话
Session session = await Session.Create(
myAppInstance.ApplicationConfiguration,
configuredEndpoint,
updateBeforeConnect: true,
sessionName,
60000,
userIdentity,
preferredLocales: null);
// 7. 返回会话
return session;
}
调用示例:
csharp
Session session = await OpcUaClient.AnonmousConnect("opc.tcp://localhost:4840");
if (session != null && session.Connected)
{
// 连接成功
}
3.2.3 UserNameConnect 方法
功能:使用用户名密码连接到OPC UA服务器
方法签名:
csharp
public static async Task<Session> UserNameConnect(string serverUrl, string username, string password)
参数说明:
serverUrl:服务器Endpoint地址username:用户名password:密码
实现代码:
csharp
public static async Task<Session> UserNameConnect(string serverUrl, string username, string password)
{
// 1-4步同匿名连接
// 5. 创建用户名密码身份
userIdentity = new Opc.Ua.UserIdentity(username, System.Text.Encoding.UTF8.GetBytes(password));
// 6-7步同匿名连接
Session session = await Session.Create(/* 参数 */);
return session;
}
调用示例:
csharp
Session session = await OpcUaClient.UserNameConnect(
"opc.tcp://localhost:4840",
"admin",
"password123");
3.2.4 BrowserNode 方法
功能:浏览指定节点的子节点
方法签名:
csharp
public static ReferenceDescriptionCollection BrowserNode(Session session, string nodeIdStr, bool recursive = false)
参数说明:
session:OPC UA会话nodeIdStr:节点ID字符串(如:i=85)recursive:是否递归浏览(暂未使用)
返回值:
ReferenceDescriptionCollection:子节点引用集合
实现代码:
csharp
public static ReferenceDescriptionCollection BrowserNode(Session session, string nodeIdStr, bool recursive = false)
{
// 1. 解析节点ID
NodeId nodeId = NodeId.Parse(nodeIdStr);
// 2. 构建浏览描述
BrowseDescription browseTo = new BrowseDescription();
browseTo.NodeId = nodeId;
browseTo.BrowseDirection = BrowseDirection.Forward;
browseTo.ReferenceTypeId = ReferenceTypeIds.HierarchicalReferences;
browseTo.IncludeSubtypes = true;
browseTo.NodeClassMask = 0;
browseTo.ResultMask = (uint)(BrowseResultMask.All | BrowseResultMask.ReferenceTypeId | BrowseResultMask.BrowseName);
// 3. 创建浏览请求
BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection();
nodesToBrowse.Add(browseTo);
// 4. 执行浏览
BrowseResultCollection results = session.Browse(null, null, nodesToBrowse, out BrowseResultCollection diagnosticInfos);
// 5. 提取引用集合
ReferenceDescriptionCollection references = new ReferenceDescriptionCollection();
if (results.Count > 0)
{
foreach (ReferenceDescription reference in results[0].References)
{
references.Add(reference);
}
}
return references;
}
调用示例:
csharp
var references = OpcUaClient.BrowserNode(session, "i=85", false);
foreach (var reference in references)
{
Console.WriteLine($"Node: {reference.DisplayName}, NodeId: {reference.NodeId}");
}
3.2.5 SessionSyncRead 方法
功能:同步读取节点值
方法签名:
csharp
public static OpcUaDataItem SessionSyncRead(Session session, NodeId nodeId)
参数说明:
session:OPC UA会话nodeId:要读取的节点ID
返回值:
OpcUaDataItem:包含节点值、类型、状态码、时间戳等
实现代码:
csharp
public static OpcUaDataItem SessionSyncRead(Session session, NodeId nodeId)
{
// 1. 创建读取值ID集合
ReadValueIdCollection nodesToRead = new ReadValueIdCollection();
ReadValueId nodeToRead = new ReadValueId();
nodeToRead.NodeId = nodeId;
nodeToRead.AttributeId = Attributes.Value;
nodesToRead.Add(nodeToRead);
// 2. 执行读取
DataValueCollection results = null;
DiagnosticInfoCollection diagnosticInfos = null;
session.Read(
default,
0,
TimestampsToReturn.Both,
nodesToRead,
out results,
out diagnosticInfos);
// 3. 提取结果
OpcUaDataItem dataItem = new OpcUaDataItem();
if (results.Count > 0)
{
DataValue value = results[0];
dataItem.NodeId = nodeId.ToString();
dataItem.Value = value.WrappedValue?.ToString() ?? "null";
dataItem.TypeInfo = value.WrappedValue?.Type?.ToString() ?? "Unknown";
dataItem.StatusCode = value.StatusCode?.ToString() ?? "Unknown";
dataItem.Timestamp = value.SourceTimestamp?.ToString("yyyy-MM-dd HH:mm:ss.fff") ?? "";
}
return dataItem;
}
调用示例:
csharp
var dataItem = OpcUaClient.SessionSyncRead(session, NodeId.Parse("ns=2;s=MyVariable"));
Console.WriteLine($"Value: {dataItem.Value}, Status: {dataItem.StatusCode}");
3.2.6 SessionSyncWrite 方法
功能:同步写入节点值
方法签名:
csharp
public static int SessionSyncWrite(Session session, NodeId nodeId, object value, string dataType, bool isArray = false)
参数说明:
session:OPC UA会话nodeId:要写入的节点IDvalue:要写入的值dataType:数据类型(String、Int32、Boolean等)isArray:是否为数组(暂未完全实现)
返回值:
int:状态码(0表示成功)
实现代码:
csharp
public static int SessionSyncWrite(Session session, NodeId nodeId, object value, string dataType, bool isArray = false)
{
// 1. 创建写入值集合
WriteValueCollection nodesToWrite = new WriteValueCollection();
// 2. 根据数据类型构造DataValue
DataValue dataValue = new DataValue();
Variant variant = new Variant();
System.Type type = null;
switch (dataType)
{
case "Boolean":
type = typeof(bool);
variant = new Variant(Convert.ToBoolean(value));
break;
case "Int32":
type = typeof(int);
variant = new Variant(Convert.ToInt32(value));
break;
case "Double":
type = typeof(double);
variant = new Variant(Convert.ToDouble(value));
break;
case "String":
type = typeof(string);
variant = new Variant(value.ToString());
break;
// 其他类型...
}
dataValue.WrappedValue = variant;
dataValue.Value = variant;
// 3. 创建写入值
WriteValue writeValue = new WriteValue();
writeValue.NodeId = nodeId;
writeValue.AttributeId = Attributes.Value;
writeValue.Value = dataValue;
nodesToWrite.Add(writeValue);
// 4. 执行写入
StatusCodeCollection results = null;
DiagnosticInfoCollection diagnosticInfos = null;
session.Write(
default,
nodesToWrite,
out results,
out diagnosticInfos);
// 5. 返回状态码
if (results.Count > 0)
{
return results[0].Code; // 0表示成功
}
return -1;
}
调用示例:
csharp
int result = OpcUaClient.SessionSyncWrite(
session,
NodeId.Parse("ns=2;s=MyVariable"),
123.45,
"Double");
if (result == 0)
{
Console.WriteLine("写入成功");
}
3.2.7 SessionSubscription 方法
功能:订阅节点数据变化
方法签名:
csharp
public static void SessionSubscription(Session session, NodeId nodeId, bool isSubscribe, SubscriptionCallback callback)
参数说明:
session:OPC UA会话nodeId:要订阅的节点IDisSubscribe:true=订阅,false=取消订阅callback:数据变化回调函数
实现代码:
csharp
public static void SessionSubscription(Session session, NodeId nodeId, bool isSubscribe, SubscriptionCallback callback)
{
// 1. 创建订阅
Subscription subscription = new Subscription(session.DefaultSubscription);
subscription.PublishingEnabled = true;
subscription.PublishingInterval = 1000; // 1秒
subscription.Priority = 0;
subscription.KeepAliveCount = 10;
subscription.LifetimeCount = 100;
subscription.MaxNotificationsPerPublish = 100;
subscription.MaxQueueSize = 100;
session.AddSubscription(subscription);
subscription.Create();
// 2. 创建监控项
MonitoredItem monitoredItem = new MonitoredItem(subscription.DefaultItem);
monitoredItem.StartNodeId = nodeId;
monitoredItem.AttributeId = Attributes.Value;
monitoredItem.SamplingInterval = 1000; // 1秒采样
monitoredItem.QueueSize = 1;
monitoredItem.DiscardOldest = true;
// 3. 添加通知事件
monitoredItem.Notification += new MonitoredItemNotificationEventHandler(MonitoredItem_Notification);
subscription.AddItem(monitoredItem);
subscription.ApplyChanges();
mi = monitoredItem;
subNodeId = nodeId;
subOpen = true;
}
// 数据变化通知处理
private static void MonitoredItem_Notification(MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs args)
{
MonitoredItemNotification notification = args.NotificationValue;
DataValue value = notification.Value.Value;
// 触发数据变更事件
OnDataChanged?.Invoke(
monitoredItem.StartNodeId.ToString(),
value.WrappedValue?.ToString() ?? "null",
value.StatusCode?.ToString() ?? "Unknown");
}
调用示例:
csharp
OpcUaClient.SessionSubscription(session, NodeId.Parse("ns=2;s=MyVariable"), true, (nodeId, value, status) =>
{
Console.WriteLine($"Node: {nodeId}, Value: {value}, Status: {status}");
});
3.3 数据模型
OpcUaDataItem 类
功能:封装OPC UA节点数据
csharp
public class OpcUaDataItem
{
public string NodeId { get; set; } // 节点ID
public string Value { get; set; } // 节点值
public string TypeInfo { get; set; } // 数据类型
public string StatusCode { get; set; } // 状态码
public string Timestamp { get; set; } // 时间戳
}
OpcUaNodeInfo 类
功能:封装节点浏览信息
csharp
public class OpcUaNodeInfo
{
public string NodeId { get; set; } // 节点ID
public string BrowseName { get; set; } // 浏览名称
public string DisplayName { get; set; } // 显示名称
public string NodeClass { get; set; } // 节点类型(Object、Variable等)
}
3.4 事件机制
OnDataChanged 事件
功能:订阅节点数据变化时触发
事件定义:
csharp
public static event Action<string, string, string> OnDataChanged;
事件参数:
string nodeId:节点IDstring value:节点值string statusCode:状态码
使用示例:
csharp
OpcUaClient.OnDataChanged += (nodeId, value, statusCode) =>
{
// 在UI线程更新显示
Dispatcher.Invoke(() =>
{
AddLogMessage($"[{DateTime.Now}] {nodeId} = {value}");
});
};
四、证书管理
4.1 证书配置
csharp
// 自动信任不受信任的证书(开发环境)
configuration.SecurityConfiguration.AutoAcceptUntrustedCertificates = true;
// 拒绝SHA1签名证书(可选)
configuration.SecurityConfiguration.RejectSHA1SignedCertificates = false;
// 证书存储位置
configuration.SecurityConfiguration.ApplicationCertificate.StoreType = CertificateStoreType.X509Store;
configuration.SecurityConfiguration.ApplicationCertificate.StorePath = "CurrentUser\\My";
4.2 证书验证回调
csharp
private static void CertClient(CertificateValidator sender, CertificateValidationEventArgs e)
{
if (e.Error.StatusCode == StatusCodes.BadCertificateUntrusted)
{
e.Accept = true; // 自动接受不受信任的证书
}
}
4.3 自签名证书创建
csharp
private static void CreateCertificateAndAddToStore(string applicationUri, string applicationName,
string storeType, string storePath)
{
List<string> localIps = GetLocalIpAddressAndDns();
var certificateBuilder = CertificateFactory.CreateCertificate(
applicationUri,
applicationName,
null,
localIps);
X509Certificate2 clientCertificate2 = certificateBuilder
.SetNotBefore(startTime)
.SetNotAfter(startTime.AddMonths(24))
.SetPrivateKeyMinimumKeySize(2048)
.CreateForSignature();
// 添加到证书存储
X509Utils.AddToStore(clientCertificate2, storeType, storePath);
}
五、安全策略
5.1 支持的安全策略
- None:无安全(仅开发环境)
- Basic128Rsa15:基础128位RSA加密
- Basic256:基础256位加密
- Basic256Sha256:256位加密+SHA256签名
5.2 用户认证方式
-
匿名认证:
csharpuserIdentity = new UserIdentity(new AnonymousIdentityToken()); -
用户名密码认证:
csharpuserIdentity = new UserIdentity(username, Encoding.UTF8.GetBytes(password)); -
证书认证:
csharpuserIdentity = new UserIdentity(certificate);
六、错误处理
6.1 常见状态码
| 状态码 | 含义 | 处理方式 |
|---|---|---|
| Good (0x00000000) | 成功 | 正常处理 |
| BadNodeIdUnknown (0x80030000) | 节点不存在 | 检查NodeId |
| BadAttributeInvalid (0x80050000) | 属性无效 | 检查AttributeId |
| BadUserAccessDenied (0x801F0000) | 访问拒绝 | 检查权限 |
| BadTimeout (0x800A0000) | 超时 | 增加超时时间 |
| BadSessionClosed (0x80060000) | 会话关闭 | 重新连接 |
6.2 异常处理示例
csharp
try
{
var session = await OpcUaClient.AnonmousConnect("opc.tcp://localhost:4840");
if (session.Connected)
{
var dataItem = OpcUaClient.SessionSyncRead(session, nodeId);
Console.WriteLine($"Value: {dataItem.Value}");
}
}
catch (ServiceResultException sre)
{
Console.WriteLine($"OPC UA错误: {sre.StatusCode}");
}
catch (Exception ex)
{
Console.WriteLine($"系统异常: {ex.Message}");
}
七、性能优化
7.1 批量操作
使用批量读取/写入提高效率:
csharp
// 批量读取
ReadValueIdCollection nodesToRead = new ReadValueIdCollection();
nodesToRead.Add(new ReadValueId { NodeId = nodeId1, AttributeId = Attributes.Value });
nodesToRead.Add(new ReadValueId { NodeId = nodeId2, AttributeId = Attributes.Value });
session.Read(default, 0, TimestampsToReturn.Both, nodesToRead, out results, out _);
7.2 订阅优化
- 调整发布间隔(PublishingInterval)
- 优化采样间隔(SamplingInterval)
- 设置合适的队列大小(QueueSize)
csharp
subscription.PublishingInterval = 1000; // 1秒
monitoredItem.SamplingInterval = 500; // 500毫秒
7.3 连接复用
避免频繁创建/销毁会话:
csharp
// 全局会话
private static Session _currentSession;
// 使用时检查连接
if (_currentSession == null || !_currentSession.Connected)
{
_currentSession = await OpcUaClient.AnonmousConnect(endpointUrl);
}
八、应用场景
8.1 工业自动化
- PLC数据采集
- SCADA系统集成
- MES系统对接
8.2 物联网平台
- 设备数据上报
- 远程监控与控制
- 数据分析平台
8.3 设备调试
- 节点浏览工具
- 数据读写测试
- 通讯诊断工具
九、总结
OPC UA通讯模块提供了完整的工业通讯解决方案:
- 安全性:支持加密、签名、证书验证
- 可靠性:会话管理、重连机制、错误恢复
- 高效性:批量操作、订阅机制、连接复用
- 易用性:简洁的API接口、完善的事件机制
- 标准化:符合OPC UA国际标准
该模块可广泛应用于工业自动化、物联网、设备调试等场景,为数据采集和设备控制提供可靠的通讯保障。