使用OpcUaHelper在C# WinForms中连接OPC UA服务器并读取数据

发布于:2025-06-30 ⋅ 阅读:(20) ⋅ 点赞:(0)

使用OpcUaHelper在C# WinForms中连接OPC UA服务器并读取数据

下面是一个完整的示例,展示如何使用OpcUaHelper库在C# WinForms应用程序中连接OPC UA服务器并读取数据。

1. 准备工作

首先,确保你已经安装了OpcUaHelper NuGet包。可以通过NuGet包管理器控制台安装:

Install-Package OpcUaHelper

2. 创建WinForms应用程序

2.1 设计界面

创建一个简单的WinForms窗体,包含以下控件:

  • 文本框:用于输入服务器URL
  • 连接/断开按钮
  • 节点ID文本框
  • 读取按钮
  • 数据显示区域(如DataGridView或ListBox)

2.2 完整代码示例

using OpcUaHelper;
using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace OpcUaWinFormsClient
{
    public partial class MainForm : Form
    {
        private OpcUaClient opcUaClient = new OpcUaClient();
        private bool isConnected = false;

        public MainForm()
        {
            InitializeComponent();
            UpdateUiState();
        }

        private void btnConnect_Click(object sender, EventArgs e)
        {
            if (!isConnected)
            {
                ConnectToServer();
            }
            else
            {
                DisconnectFromServer();
            }
        }

        private async void ConnectToServer()
        {
            string serverUrl = txtServerUrl.Text.Trim();
            
            if (string.IsNullOrEmpty(serverUrl))
            {
                MessageBox.Show("请输入有效的服务器URL");
                return;
            }

            try
            {
                btnConnect.Enabled = false;
                lblStatus.Text = "正在连接...";

                // 连接到服务器
                await opcUaClient.ConnectServerAsync(serverUrl);
                
                isConnected = true;
                lblStatus.Text = "已连接";
                MessageBox.Show("连接成功!");
            }
            catch (Exception ex)
            {
                lblStatus.Text = "连接失败";
                MessageBox.Show($"连接失败: {ex.Message}");
            }
            finally
            {
                btnConnect.Enabled = true;
                UpdateUiState();
            }
        }

        private void DisconnectFromServer()
        {
            try
            {
                opcUaClient.Disconnect();
                isConnected = false;
                lblStatus.Text = "已断开";
            }
            catch (Exception ex)
            {
                MessageBox.Show($"断开连接时出错: {ex.Message}");
            }
            finally
            {
                UpdateUiState();
            }
        }

        private void UpdateUiState()
        {
            btnConnect.Text = isConnected ? "断开连接" : "连接";
            btnRead.Enabled = isConnected;
            txtNodeId.Enabled = isConnected;
        }

        private async void btnRead_Click(object sender, EventArgs e)
        {
            string nodeId = txtNodeId.Text.Trim();
            
            if (string.IsNullOrEmpty(nodeId))
            {
                MessageBox.Show("请输入节点ID");
                return;
            }

            try
            {
                btnRead.Enabled = false;
                
                // 读取单个节点值
                var value = await opcUaClient.ReadNodeAsync(nodeId);
                
                // 显示读取结果
                lstResults.Items.Add($"节点: {nodeId}, 值: {value}, 类型: {value?.GetType()}");
                lstResults.TopIndex = lstResults.Items.Count - 1;
            }
            catch (Exception ex)
            {
                MessageBox.Show($"读取数据时出错: {ex.Message}");
            }
            finally
            {
                btnRead.Enabled = true;
            }
        }

        private async void btnBrowse_Click(object sender, EventArgs e)
        {
            try
            {
                treeNodes.Nodes.Clear();
                var rootNode = treeNodes.Nodes.Add("Root");
                
                // 浏览服务器节点
                await BrowseNodeAsync("i=84", rootNode); // Objects文件夹通常是i=84
            }
            catch (Exception ex)
            {
                MessageBox.Show($"浏览节点时出错: {ex.Message}");
            }
        }

        private async Task BrowseNodeAsync(string nodeId, TreeNode parentTreeNode)
        {
            var references = await opcUaClient.BrowseNodeReferenceAsync(nodeId);
            
            foreach (var reference in references)
            {
                var childNode = parentTreeNode.Nodes.Add($"{reference.DisplayName} ({reference.NodeId})");
                
                // 如果是文件夹/对象类型,添加一个虚拟子节点以便展开
                if (reference.NodeClass == Opc.Ua.NodeClass.Object || 
                    reference.NodeClass == Opc.Ua.NodeClass.Variable)
                {
                    childNode.Nodes.Add("加载中...");
                }
            }
        }

        private async void treeNodes_BeforeExpand(object sender, TreeViewCancelEventArgs e)
        {
            var node = e.Node;
            
            // 如果只有一个"加载中..."子节点,则实际加载子节点
            if (node.Nodes.Count == 1 && node.Nodes[0].Text == "加载中...")
            {
                try
                {
                    node.Nodes.Clear();
                    
                    // 从节点文本中提取NodeId
                    var nodeId = ExtractNodeIdFromTreeNode(node);
                    
                    if (!string.IsNullOrEmpty(nodeId))
                    {
                        await BrowseNodeAsync(nodeId, node);
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"加载子节点时出错: {ex.Message}");
                    node.Nodes.Add($"错误: {ex.Message}");
                }
            }
        }

        private string ExtractNodeIdFromTreeNode(TreeNode node)
        {
            // 从类似 "DisplayName (ns=2;s=MyVariable)" 的文本中提取 "ns=2;s=MyVariable"
            var text = node.Text;
            var start = text.IndexOf('(') + 1;
            var end = text.IndexOf(')');
            
            if (start > 0 && end > start)
            {
                return text.Substring(start, end - start);
            }
            
            return null;
        }

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (isConnected)
            {
                DisconnectFromServer();
            }
        }
    }
}

3. 功能说明

  1. 连接/断开服务器

    • 使用ConnectServerAsync方法异步连接
    • 使用Disconnect方法断开连接
  2. 读取节点数据

    • 使用ReadNodeAsync方法读取单个节点值
    • 支持所有OPC UA数据类型
  3. 浏览服务器节点

    • 使用BrowseNodeReferenceAsync方法浏览节点引用
    • 实现树形视图的延迟加载
  4. 错误处理

    • 对所有OPC UA操作进行适当的错误处理

4. 扩展功能

你可以根据需要扩展此示例,添加以下功能:

  1. 写入数据

    await opcUaClient.WriteNodeAsync(nodeId, value);
    
  2. 订阅数据变化

    // 创建订阅
    var subscription = new OpcUaHelper.Subscription(opcUaClient);
    
    // 添加监控项
    subscription.AddItem(nodeId);
    
    // 数据变化事件
    subscription.DataChangeReceived += (s, e) => 
    {
        // 处理数据变化
    };
    
    // 启动订阅
    await subscription.ApplyAsync();
    
  3. 调用方法

    var results = await opcUaClient.CallMethodByNodeIdAsync(objectNodeId, methodNodeId, inputArguments);
    

5. 注意事项

  1. 异步编程:所有OPC UA操作都应使用异步方法,避免阻塞UI线程。

  2. 错误处理:网络操作容易出错,确保有适当的错误处理和用户反馈。

  3. 安全性:生产环境中可能需要配置安全策略和用户凭据。

  4. 资源清理:确保在应用程序退出时正确断开连接。


网站公告

今日签到

点亮在社区的每一天
去签到