OPC UA Helper类实现,用于连接OPC UA服务器(如PLC),读取变量值,并支持订阅、写入等高级功能。
实现代码
1. OPC UA Helper 核心类
csharp
using Opc.Ua;
using Opc.Ua.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace OpcUaHelper
{
public class OpcUaClient : IDisposable
{
private Session _session;
private ApplicationConfiguration _config;
private Subscription _subscription;
private MonitoredItemNotificationEventHandler _notificationHandler;
private readonly Dictionary<NodeId, MonitoredItem> _monitoredItems = new Dictionary<NodeId, MonitoredItem>();
private readonly object _lock = new object();
// 事件定义
public event EventHandler<OpcUaDataChangeEventArgs> DataChanged;
public event EventHandler<OpcUaStatusEventArgs> ConnectionStatusChanged;
public event EventHandler<OpcUaErrorEventArgs> ErrorOccurred;
// 连接状态
public bool IsConnected => _session != null && _session.Connected;
public string ServerStatus => _session?.Status.ToString() ?? "Disconnected";
/// <summary>
/// 初始化OPC UA客户端
/// </summary>
public OpcUaClient()
{
Initialize();
}
private void Initialize()
{
try
{
// 创建应用配置
_config = new ApplicationConfiguration
{
ApplicationName = "OpcUaHelper",
ApplicationType = ApplicationType.Client,
SecurityConfiguration = new SecurityConfiguration
{
ApplicationCertificate = new CertificateIdentifier
{
StoreType = "X509Store",
StorePath = "CurrentUser\\My",
SubjectName = "CN=OpcUaHelper"
},
TrustedPeerCertificates = new CertificateTrustList
{
StoreType = "Directory",
StorePath = "Trusted"
},
NonceLength = 32,
AutoAcceptUntrustedCertificates = true
},
TransportConfigurations = new TransportConfigurationCollection(),
ClientConfiguration = new ClientConfiguration
{
DefaultSessionTimeout = 60000,
MinSubscriptionLifetime = 10000
},
DisableHiResClock = true
};
// 验证配置
_config.Validate(ApplicationType.Client).Wait();
}
catch (Exception ex)
{
OnErrorOccurred($"初始化失败: {ex.Message}");
throw;
}
}
/// <summary>
/// 连接到OPC UA服务器
/// </summary>
public async Task ConnectAsync(string serverUrl, int timeout = 5000)
{
if (IsConnected) return;
try
{
OnConnectionStatusChanged("Connecting...");
// 创建端点配置
var endpoint = new ConfiguredEndpoint(null, new Uri(serverUrl), EndpointConfiguration.Create(_config));
endpoint.UpdateBeforeConnect = true;
// 创建会话
var userIdentity = new AnonymousIdentity();
_session = await Session.Create(
_config,
endpoint,
false,
"OpcUaHelper",
60000,
userIdentity,
null
);
// 设置会话状态变化回调
_session.SessionClosing += (sender, args) =>
OnConnectionStatusChanged("Disconnected: Session closing");
OnConnectionStatusChanged("Connected");
}
catch (Exception ex)
{
OnErrorOccurred($"连接失败: {ex.Message}");
OnConnectionStatusChanged("Disconnected");
throw;
}
}
/// <summary>
/// 断开连接
/// </summary>
public void Disconnect()
{
if (!IsConnected) return;
try
{
// 删除订阅
if (_subscription != null)
{
_session.RemoveSubscription(_subscription);
_subscription.Delete(true);
_subscription = null;
}
// 关闭会话
_session.Close();
_session.Dispose();
_session = null;
OnConnectionStatusChanged("Disconnected");
}
catch (Exception ex)
{
OnErrorOccurred($"断开连接失败: {ex.Message}");
}
}
/// <summary>
/// 读取单个节点值
/// </summary>
public Variant ReadNodeValue(string nodeId)
{
if (!IsConnected) throw new InvalidOperationException("Not connected to server");
try
{
var node = new NodeId(nodeId);
var value = _session.ReadValue(node);
return value.WrappedValue;
}
catch (Exception ex)
{
OnErrorOccurred($"读取节点失败 [{nodeId}]: {ex.Message}");
throw;
}
}
/// <summary>
/// 读取多个节点值
/// </summary>
public Dictionary<string, Variant> ReadMultipleNodes(IEnumerable<string> nodeIds)
{
if (!IsConnected) throw new InvalidOperationException("Not connected to server");
try
{
var nodesToRead = new ReadValueIdCollection();
foreach (var nodeId in nodeIds)
{
nodesToRead.Add(new ReadValueId
{
NodeId = new NodeId(nodeId),
AttributeId = Attributes.Value
});
}
var results = new DataValueCollection();
var diagnosticInfos = new DiagnosticInfoCollection();
_session.Read(
null,
0,
TimestampsToReturn.Both,
nodesToRead,
out results,
out diagnosticInfos
);
var dict = new Dictionary<string, Variant>();
for (int i = 0; i < nodesToRead.Count; i++)
{
dict[nodeIds.ElementAt(i)] = results[i].WrappedValue;
}
return dict;
}
catch (Exception ex)
{
OnErrorOccurred($"批量读取失败: {ex.Message}");
throw;
}
}
/// <summary>
/// 写入单个节点值
/// </summary>
public void WriteNodeValue(string nodeId, Variant value)
{
if (!IsConnected) throw new InvalidOperationException("Not connected to server");
try
{
var writeValue = new WriteValue
{
NodeId = new NodeId(nodeId),
AttributeId = Attributes.Value,
Value = new DataValue(value)
};
var writeValues = new WriteValueCollection { writeValue };
var results = new StatusCodeCollection();
var diagnosticInfos = new DiagnosticInfoCollection();
_session.Write(
null,
writeValues,
out results,
out diagnosticInfos
);
if (StatusCode.IsBad(results[0]))
{
throw new Exception($"写入失败: {results[0]}");
}
}
catch (Exception ex)
{
OnErrorOccurred($"写入节点失败 [{nodeId}]: {ex.Message}");
throw;
}
}
/// <summary>
/// 订阅节点变化
/// </summary>
public void SubscribeToNode(string nodeId, int publishingInterval = 1000)
{
if (!IsConnected) throw new InvalidOperationException("Not connected to server");
lock (_lock)
{
try
{
// 创建订阅(如果不存在)
if (_subscription == null)
{
_subscription = new Subscription(_session.DefaultSubscription)
{
PublishingInterval = publishingInterval,
KeepAliveCount = 10,
LifetimeCount = 100,
Priority = 100
};
_session.AddSubscription(_subscription);
_subscription.Create();
// 设置通知处理程序
_notificationHandler = (monitoredItem, args) =>
{
if (args != null && args.EventArgs is MonitoredItemNotificationEventArgs notificationArgs)
{
var notification = notificationArgs.NotificationValue as MonitoredItemNotification;
if (notification != null)
{
var nodeIdStr = monitoredItem.ResolvedNodeId.ToString();
var value = notification.Value.WrappedValue;
var timestamp = notification.Value.ServerTimestamp;
OnDataChanged(nodeIdStr, value, timestamp);
}
}
};
}
// 创建监控项
var node = new NodeId(nodeId);
var monitoredItem = new MonitoredItem(_subscription.DefaultItem)
{
NodeId = node,
DisplayName = nodeId,
SamplingInterval = publishingInterval
};
monitoredItem.Notification += _notificationHandler;
_subscription.AddItem(monitoredItem);
_subscription.ApplyChanges();
_monitoredItems[node] = monitoredItem;
}
catch (Exception ex)
{
OnErrorOccurred($"订阅节点失败 [{nodeId}]: {ex.Message}");
throw;
}
}
}
/// <summary>
/// 取消订阅节点
/// </summary>
public void UnsubscribeNode(string nodeId)
{
if (!IsConnected || _subscription == null) return;
lock (_lock)
{
try
{
var node = new NodeId(nodeId);
if (_monitoredItems.TryGetValue(node, out var monitoredItem))
{
monitoredItem.Notification -= _notificationHandler;
_subscription.RemoveItem(monitoredItem);
_monitoredItems.Remove(node);
_subscription.ApplyChanges();
}
}
catch (Exception ex)
{
OnErrorOccurred($"取消订阅失败 [{nodeId}]: {ex.Message}");
}
}
}
/// <summary>
/// 浏览服务器节点
/// </summary>
public ReferenceDescriptionCollection BrowseNode(string nodeId = null)
{
if (!IsConnected) throw new InvalidOperationException("Not connected to server");
try
{
var node = nodeId == null ? ObjectIds.RootFolder : new NodeId(nodeId);
var references = _session.FetchReferences(
node,
ReferenceTypeIds.HierarchicalReferences,
true,
true
);
return references;
}
catch (Exception ex)
{
OnErrorOccurred($"浏览节点失败 [{nodeId}]: {ex.Message}");
throw;
}
}
/// <summary>
/// 获取节点数据类型
/// </summary>
public NodeId GetNodeDataType(string nodeId)
{
if (!IsConnected) throw new InvalidOperationException("Not connected to server");
try
{
var node = new NodeId(nodeId);
var attributes = _session.ReadAttributes(
new[] { node },
new[] { Attributes.DataType, Attributes.ValueRank }
);
if (attributes.Count > 0 && attributes[0].StatusCode == StatusCodes.Good)
{
return attributes[0].Value as NodeId;
}
return null;
}
catch (Exception ex)
{
OnErrorOccurred($"获取节点类型失败 [{nodeId}]: {ex.Message}");
throw;
}
}
#region 事件触发方法
protected virtual void OnDataChanged(string nodeId, Variant value, DateTime timestamp)
{
DataChanged?.Invoke(this, new OpcUaDataChangeEventArgs(nodeId, value, timestamp));
}
protected virtual void OnConnectionStatusChanged(string status)
{
ConnectionStatusChanged?.Invoke(this, new OpcUaStatusEventArgs(status));
}
protected virtual void OnErrorOccurred(string message)
{
ErrorOccurred?.Invoke(this, new OpcUaErrorEventArgs(message));
}
#endregion
#region IDisposable 实现
public void Dispose()
{
try
{
Disconnect();
_config?.Dispose();
}
catch
{
// 忽略清理时的错误
}
}
#endregion
}
#region 事件参数类
public class OpcUaDataChangeEventArgs : EventArgs
{
public string NodeId { get; }
public Variant Value { get; }
public DateTime Timestamp { get; }
public OpcUaDataChangeEventArgs(string nodeId, Variant value, DateTime timestamp)
{
NodeId = nodeId;
Value = value;
Timestamp = timestamp;
}
}
public class OpcUaStatusEventArgs : EventArgs
{
public string Status { get; }
public OpcUaStatusEventArgs(string status)
{
Status = status;
}
}
public class OpcUaErrorEventArgs : EventArgs
{
public string Message { get; }
public OpcUaErrorEventArgs(string message)
{
Message = message;
}
}
#endregion
}
2. 使用示例 (Windows Forms)
csharp
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using Opc.Ua;
using OpcUaHelper;
namespace OpcUaClientApp
{
public partial class MainForm : Form
{
private OpcUaClient _opcClient;
private readonly Dictionary<string, Label> _valueLabels = new Dictionary<string, Label>();
public MainForm()
{
InitializeComponent();
InitializeOpcClient();
}
private void InitializeOpcClient()
{
_opcClient = new OpcUaClient();
_opcClient.DataChanged += OpcClient_DataChanged;
_opcClient.ConnectionStatusChanged += OpcClient_ConnectionStatusChanged;
_opcClient.ErrorOccurred += OpcClient_ErrorOccurred;
}
private async void btnConnect_Click(object sender, EventArgs e)
{
try
{
statusLabel.Text = "连接中...";
await _opcClient.ConnectAsync(txtServerUrl.Text);
// 连接成功后读取初始值
ReadInitialValues();
// 订阅变量
SubscribeToVariables();
}
catch (Exception ex)
{
MessageBox.Show($"连接失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void btnDisconnect_Click(object sender, EventArgs e)
{
_opcClient.Disconnect();
statusLabel.Text = "已断开连接";
}
private void ReadInitialValues()
{
try
{
// 读取单个变量
var tempValue = _opcClient.ReadNodeValue("ns=2;s=Temperature");
UpdateValueDisplay("Temperature", tempValue);
// 批量读取
var nodeIds = new List<string>
{
"ns=2;s=Pressure",
"ns=2;s=FlowRate",
"ns=2;s=MotorStatus"
};
var values = _opcClient.ReadMultipleNodes(nodeIds);
foreach (var kvp in values)
{
UpdateValueDisplay(kvp.Key, kvp.Value);
}
}
catch (Exception ex)
{
MessageBox.Show($"读取初始值失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void SubscribeToVariables()
{
// 订阅变量变化
_opcClient.SubscribeToNode("ns=2;s=Temperature");
_opcClient.SubscribeToNode("ns=2;s=Pressure");
_opcClient.SubscribeToNode("ns=2;s=FlowRate");
_opcClient.SubscribeToNode("ns=2;s=MotorStatus");
}
private void OpcClient_DataChanged(object sender, OpcUaDataChangeEventArgs e)
{
// 在UI线程更新显示
if (InvokeRequired)
{
Invoke(new Action(() => UpdateValueDisplay(e.NodeId, e.Value)));
}
else
{
UpdateValueDisplay(e.NodeId, e.Value);
}
}
private void UpdateValueDisplay(string nodeId, Variant value)
{
string displayValue;
if (value.Value == null)
{
displayValue = "null";
}
else if (value.TypeInfo.BuiltInType == BuiltInType.DateTime)
{
displayValue = ((DateTime)value.Value).ToString("yyyy-MM-dd HH:mm:ss");
}
else if (value.TypeInfo.BuiltInType == BuiltInType.Boolean)
{
displayValue = (bool)value.Value ? "ON" : "OFF";
}
else
{
displayValue = value.Value.ToString();
}
// 更新对应的标签
if (_valueLabels.TryGetValue(nodeId, out var label))
{
label.Text = displayValue;
}
else
{
// 首次遇到此节点,创建新标签
var newNodePanel = CreateValuePanel(nodeId, displayValue);
flowLayoutPanel.Controls.Add(newNodePanel);
}
}
private Panel CreateValuePanel(string nodeId, string initialValue)
{
var panel = new FlowLayoutPanel
{
FlowDirection = FlowDirection.LeftToRight,
AutoSize = true,
WrapContents = false,
Margin = new Padding(5)
};
var nameLabel = new Label
{
Text = nodeId,
Width = 150,
TextAlign = System.Drawing.ContentAlignment.MiddleLeft,
AutoSize = false
};
var valueLabel = new Label
{
Name = $"lbl_{nodeId.Replace("=", "_").Replace(";", "_")}",
Text = initialValue,
Width = 100,
TextAlign = System.Drawing.ContentAlignment.MiddleLeft,
AutoSize = false,
BorderStyle = BorderStyle.FixedSingle,
BackColor = System.Drawing.Color.LightYellow
};
panel.Controls.Add(nameLabel);
panel.Controls.Add(valueLabel);
_valueLabels[nodeId] = valueLabel;
return panel;
}
private void OpcClient_ConnectionStatusChanged(object sender, OpcUaStatusEventArgs e)
{
if (InvokeRequired)
{
Invoke(new Action(() => statusLabel.Text = e.Status));
}
else
{
statusLabel.Text = e.Status;
}
}
private void OpcClient_ErrorOccurred(object sender, OpcUaErrorEventArgs e)
{
if (InvokeRequired)
{
Invoke(new Action(() =>
MessageBox.Show(e.Message, "OPC UA 错误", MessageBoxButtons.OK, MessageBoxIcon.Error)));
}
else
{
MessageBox.Show(e.Message, "OPC UA 错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void btnWrite_Click(object sender, EventArgs e)
{
try
{
// 写入单个变量
_opcClient.WriteNodeValue("ns=2;s=MotorStatus", new Variant(true));
MessageBox.Show("写入成功", "信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
MessageBox.Show($"写入失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
_opcClient?.Dispose();
}
}
}
3. 使用示例 (控制台应用)
csharp
using System;
using System.Threading;
using System.Threading.Tasks;
using OpcUaHelper;
namespace OpcUaConsoleApp
{
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("OPC UA 客户端示例");
Console.WriteLine("------------------");
using (var opcClient = new OpcUaClient())
{
// 注册事件处理程序
opcClient.DataChanged += (s, e) =>
Console.WriteLine($"[{e.Timestamp:HH:mm:ss}] {e.NodeId}: {e.Value}");
opcClient.ConnectionStatusChanged += (s, e) =>
Console.WriteLine($"状态: {e.Status}");
opcClient.ErrorOccurred += (s, e) =>
Console.WriteLine($"错误: {e.Message}");
try
{
// 连接到服务器
Console.Write("输入服务器URL (例如: opc.tcp://localhost:4840): ");
string serverUrl = Console.ReadLine();
await opcClient.ConnectAsync(serverUrl);
// 读取单个变量
Console.WriteLine("\n读取单个变量:");
var value = opcClient.ReadNodeValue("ns=2;s=Temperature");
Console.WriteLine($"温度: {value}");
// 批量读取
Console.WriteLine("\n批量读取变量:");
var nodeIds = new[] { "ns=2;s=Pressure", "ns=2;s=FlowRate", "ns=2;s=MotorStatus" };
var values = opcClient.ReadMultipleNodes(nodeIds);
foreach (var kvp in values)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
// 订阅变量
Console.WriteLine("\n订阅变量变化 (按任意键退出)...");
opcClient.SubscribeToNode("ns=2;s=Temperature");
opcClient.SubscribeToNode("ns=2;s=Pressure");
// 等待用户输入退出
Console.ReadKey();
// 取消订阅
opcClient.UnsubscribeNode("ns=2;s=Temperature");
opcClient.UnsubscribeNode("ns=2;s=Pressure");
// 写入变量
Console.WriteLine("\n写入变量:");
opcClient.WriteNodeValue("ns=2;s=MotorStatus", new Variant(true));
Console.WriteLine("电机状态已设置为ON");
}
catch (Exception ex)
{
Console.WriteLine($"发生错误: {ex.Message}");
}
finally
{
opcClient.Disconnect();
}
}
Console.WriteLine("按任意键退出...");
Console.ReadKey();
}
}
}
参考代码 OPC Ua Helper(OPCUA服务器连接PLC获取PLC的相应变量值) www.youwenfan.com/contentcst/44316.html
功能特点
1. 核心功能
- 连接管理:支持连接/断开OPC UA服务器
- 数据读取:支持单点读取和批量读取
- 数据写入:支持写入节点值
- 订阅机制:实时监控变量变化
- 节点浏览:浏览服务器节点结构
- 类型获取:获取节点数据类型
2. 事件系统
DataChanged:变量值变化时触发ConnectionStatusChanged:连接状态变化时触发ErrorOccurred:发生错误时触发
3. 高级特性
- 自动重连:内置会话状态监控
- 证书处理:自动接受未信任证书
- 线程安全:所有操作线程安全
- 资源释放:实现IDisposable接口
- 错误处理:详细的错误日志
使用说明
1. 环境要求
- .NET Framework 4.6.1+ 或 .NET Core 3.1+
- 安装NuGet包:
Install-Package Opc.Ua.Client
2. 连接PLC步骤
-
获取PLC的OPC UA服务器地址:
- 西门子S7-1200/1500:通常为
opc.tcp://<PLC_IP>:4840 - 三菱PLC:通常为
opc.tcp://<PLC_IP>:4840 - 罗克韦尔PLC:通常为
opc.tcp://<PLC_IP>:4880
- 西门子S7-1200/1500:通常为
-
配置PLC的OPC UA服务器:
- 启用OPC UA服务器功能
- 配置访问权限和安全策略
- 记录节点ID(如
ns=2;s=Temperature)
-
使用OpcUaHelper连接:
csharpvar client = new OpcUaClient(); await client.ConnectAsync("opc.tcp://192.168.1.10:4840");
3. 常用节点ID格式
| 格式类型 | 示例 | 说明 |
|---|---|---|
| 数值标识符 | ns=2;i=1234 |
命名空间索引=2,数值标识符=1234 |
| 字符串标识符 | ns=2;s=Temperature |
命名空间索引=2,字符串标识符=Temperature |
| GUID标识符 | ns=2;g=12345678-1234-1234-1234-123456789012 |
命名空间索引=2,GUID标识符 |
| 字节字符串 | ns=2;b=M/RbKBsRVkePCePcx24oRA== |
命名空间索引=2,Base64编码的字节字符串 |
4. 数据类型处理
csharp
// 读取变量
Variant value = client.ReadNodeValue("ns=2;s=Temperature");
// 处理不同类型
switch (value.TypeInfo.BuiltInType)
{
case BuiltInType.Int16:
short sValue = (short)value.Value;
break;
case BuiltInType.Float:
float fValue = (float)value.Value;
break;
case BuiltInType.Boolean:
bool bValue = (bool)value.Value;
break;
case BuiltInType.DateTime:
DateTime dtValue = (DateTime)value.Value;
break;
default:
string strValue = value.ToString();
break;
}
实际应用案例
1. 西门子S7-1500 PLC监控
csharp
// 连接PLC
await client.ConnectAsync("opc.tcp://192.168.0.1:4840");
// 读取DB块变量
var temp = client.ReadNodeValue("ns=3;s=\"DB_OPC\".Temperature");
var pressure = client.ReadNodeValue("ns=3;s=\"DB_OPC\".Pressure");
// 写入控制变量
client.WriteNodeValue("ns=3;s=\"DB_OPC\".MotorControl", new Variant(true));
// 订阅关键变量
client.SubscribeToNode("ns=3;s=\"DB_OPC\".AlarmStatus");
2. 三菱PLC数据采集
csharp
// 连接PLC
await client.ConnectAsync("opc.tcp://192.168.1.10:4840");
// 读取寄存器
var speed = client.ReadNodeValue("ns=2;s=D100");
var position = client.ReadNodeValue("ns=2;s=D102");
// 写入设定值
client.WriteNodeValue("ns=2;s=D200", new Variant(1500)); // 设定速度
3. 工厂设备监控系统
csharp
// 创建监控面板
var form = new MonitoringForm();
// 添加变量监控
form.AddMonitoredVariable("生产线1温度", "ns=2;s=Line1.Temperature");
form.AddMonitoredVariable("生产线1压力", "ns=2;s=Line1.Pressure");
form.AddMonitoredVariable("生产线1速度", "ns=2;s=Line1.Speed");
// 添加报警监控
client.SubscribeToNode("ns=2;s=System.Alarm", 500);
client.DataChanged += (s, e) => {
if (e.NodeId == "ns=2;s=System.Alarm" && (bool)e.Value.Value)
{
form.ShowAlarm("系统报警!");
}
};
常见问题解决
1. 连接问题
- 错误 :
BadSecurityChecksFailed- 解决:检查服务器证书设置,确保客户端信任服务器证书
- 错误 :
BadNotConnected- 解决:检查服务器地址和端口是否正确,确保服务器正在运行
2. 读取/写入问题
- 错误 :
BadNodeIdUnknown- 解决:检查节点ID是否正确,使用BrowseNode方法验证
- 错误 :
BadTypeMismatch- 解决:确保写入的值类型与变量类型匹配
3. 性能优化
- 批量读取 :使用
ReadMultipleNodes代替多次单点读取 - 订阅间隔:根据需求调整订阅的publishingInterval
- 连接复用:保持长连接而不是频繁连接/断开
扩展功能
1. 添加历史数据访问
csharp
public HistoryReadResult ReadHistory(string nodeId, DateTime startTime, DateTime endTime)
{
var details = new ReadRawModifiedDetails
{
StartTime = startTime,
EndTime = endTime,
NumValuesPerNode = 0,
ReturnBounds = true
};
var historyReadValueId = new HistoryReadValueId
{
NodeId = new NodeId(nodeId)
};
var results = _session.HistoryRead(
null,
new ExtensionObject(details),
new HistoryReadValueIdCollection { historyReadValueId },
out var historyData,
out _
);
return historyData[0];
}
2. 添加方法调用
csharp
public Variant CallMethod(string objectId, string methodId, params Variant[] inputArguments)
{
var objectNode = new NodeId(objectId);
var methodNode = new NodeId(methodId);
var result = _session.Call(
new CallMethodRequest
{
ObjectId = objectNode,
MethodId = methodNode,
InputArguments = new VariantCollection(inputArguments)
}
);
if (result.OutputArguments.Count > 0)
{
return result.OutputArguments[0];
}
return Variant.Null;
}
3. 添加安全认证
csharp
public async Task ConnectWithUsernamePassword(string serverUrl, string username, string password)
{
var endpoint = new ConfiguredEndpoint(null, new Uri(serverUrl), EndpointConfiguration.Create(_config));
var identity = new UserIdentity(username, password);
_session = await Session.Create(
_config,
endpoint,
false,
"OpcUaHelper",
60000,
identity,
null
);
}
总结
这个OPC UA Helper类提供了连接PLC、读取变量、写入变量、订阅变化等完整功能,具有以下优势:
- 简单易用:封装了复杂的OPC UA协议细节
- 功能全面:支持所有基本OPC UA操作
- 事件驱动:通过事件机制实现实时数据更新
- 线程安全:可在多线程环境中安全使用
- 扩展性强:易于添加新功能