OPC UA Helper: 连接PLC获取变量值

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步骤

  1. 获取PLC的OPC UA服务器地址

    • 西门子S7-1200/1500:通常为opc.tcp://<PLC_IP>:4840
    • 三菱PLC:通常为opc.tcp://<PLC_IP>:4840
    • 罗克韦尔PLC:通常为opc.tcp://<PLC_IP>:4880
  2. 配置PLC的OPC UA服务器

    • 启用OPC UA服务器功能
    • 配置访问权限和安全策略
    • 记录节点ID(如ns=2;s=Temperature
  3. 使用OpcUaHelper连接

    csharp 复制代码
    var 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、读取变量、写入变量、订阅变化等完整功能,具有以下优势:

  1. 简单易用:封装了复杂的OPC UA协议细节
  2. 功能全面:支持所有基本OPC UA操作
  3. 事件驱动:通过事件机制实现实时数据更新
  4. 线程安全:可在多线程环境中安全使用
  5. 扩展性强:易于添加新功能
相关推荐
我命由我123453 小时前
U 盘里出现的文件 BOOTEX.LOG
运维·服务器·经验分享·笔记·学习·硬件工程·学习方法
IMPYLH3 小时前
Linux 的 paste 命令
linux·运维·服务器·bash
Kk.08023 小时前
Linux(十三)fork + exec进程创建
linux·运维·服务器
ytdbc3 小时前
hclp第三次
网络
zfoo-framework3 小时前
记录文件描述符达到上限问题解决
linux·运维·服务器
2601_949539453 小时前
15万级家用混动SUV电池与续航技术入门科普
运维·网络
呱呱巨基3 小时前
网络基础概念
linux·网络·c++·笔记·学习
岳来3 小时前
docker network 创建 host 和 none 网络模式
网络·docker·容器·docker network
cui_ruicheng3 小时前
Linux进程控制(下):实现简易 Shell 命令行解释器
linux·运维·服务器