目录

C开发OPC-UA客户端

C#开发OPC UA客户端

使用C#开发OPC UA客户端能帮助你在工业自动化和物联网项目中与各种设备进行可靠的数据交换。提供两种主流的开发方式:使用官方OPC UA .NET Standard SDK和使用第三方库OpcUaHelper。这两种方式各有特点,适合不同的开发场景。先通过一个表格来快速了解它们的区别:

特性OPC UA .NET Standard SDK (官方)OpcUaHelper (第三方)
来源OPC基金会官方开源社区(基于官方SDK封装)
复杂度相对较低,提供更底层的控制和灵活性更高,封装了底层细节,API更简洁
学习曲线较陡峭,需理解OPC UA模型平缓,上手快速
功能全面,支持所有OPC UA特性覆盖常用功能(读、写、订阅、浏览)
跨平台支持(.NET Standard)依赖底层SDK
适用场景需要深度控制、自定义功能或学习底层机制快速开发常规客户端应用

使用官方 OPC UA .NET Standard SDK 开发

这是OPC基金会提供的官方库,功能全面,支持最新的OPC UA标准,适合需要深度控制或学习底层机制的场景。

开发准备

  1. 安装NuGet包:在Visual Studio中,通过NuGet包管理器安装官方SDK:

    Install-Package OPCFoundation.NetStandard.Opc.Ua
  2. 引入命名空间

    using Opc.Ua;
    using Opc.Ua.Client;
    using System;
    using System.Threading.Tasks;

基础代码示例

1. 配置应用程序并连接服务器

连接OPC UA服务器是第一步,需要配置应用程序实例和连接参数。

class Program
{
    private static ApplicationConfiguration config;
    private static Session session;

    static async Task Main(string[] args)
    {
        // 创建应用程序配置
        config = new ApplicationConfiguration()
        {
            ApplicationName = "My OPC UA Client",
            ApplicationUri = "urn:localhost:MyClient",
            ProductUri = "urn:MyProduct",
            SecurityConfiguration = new SecurityConfiguration
            {
                ApplicationCertificate = new CertificateIdentifier
                {
                    StoreType = "Directory",
                    StorePath = @"C:\Certificates\Client", // 证书存储路径
                    SubjectName = "CN=MyClient"
                }
            },
            TransportConfigurations = new TransportConfigurationCollection(),
            ClientConfiguration = new ClientConfiguration()
        };
        config.Validate(ApplicationType.Client); // 验证配置

        // 连接到服务器
        try
        {
            await ConnectToServer("opc.tcp://localhost:4840");
            Console.WriteLine("成功连接到服务器!");

            // ... 在这里执行读写操作

        }
        catch (Exception ex)
        {
            Console.WriteLine($"连接失败: {ex.Message}");
        }
        finally
        {
            session?.Close();
        }
    }

    private static async Task ConnectToServer(string serverUrl)
    {
        // 创建端点描述
        EndpointDescription endpointDescription = EndpointDescription.Create(serverUrl);
        EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(config);
        ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);

        // 创建会话
        session = await Session.Create(
            config,
            endpoint,
            false,
            "My Client",
            60000,
            new UserIdentity(new AnonymousIdentityToken()), // 匿名身份
            null
        );
    }
}

2. 读取节点数据

读取数据需要知道节点的唯一标识符 NodeId

static async Task ReadData()
{
    // 假设要读取的节点ID
    NodeId nodeIdToRead = new NodeId("ns=2;s=DemoSensor.Temperature"); 

    try
    {
        // 读取节点值
        DataValue dataValue = session.ReadValue(nodeIdToRead);
        Console.WriteLine($"节点值: {dataValue.Value}, 时间戳: {dataValue.SourceTimestamp}");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"读取失败: {ex.Message}");
    }
}

3. 写入节点数据

写入数据同样需要 NodeId 和要写入的值。

static async Task WriteData()
{
    // 假设要写入的节点ID
    NodeId nodeIdToWrite = new NodeId("ns=2;s=DemoActuator.SetPoint"); 
    double newValue = 42.5;

    try
    {
        // 创建DataValue对象并写入
        DataValue dataToWrite = new DataValue(new Variant(newValue));
        session.WriteValue(nodeIdToWrite, dataToWrite);
        Console.WriteLine("写入成功!");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"写入失败: {ex.Message}");
    }
}

4. 订阅与监控数据变化

订阅允许你在数据变化时自动接收通知,而不是不断轮询。

static void SubscribeToDataChanges()
{
    // 创建订阅
    Subscription subscription = new Subscription(session.DefaultSubscription) {
        PublishingInterval = 1000 // 发布间隔1秒
    };

    // 创建监控项
    NodeId nodeToMonitor = new NodeId("ns=2;s=DemoSensor.Pressure");
    MonitoredItem monitoredItem = new MonitoredItem(subscription.DefaultItem)
    {
        StartNodeId = nodeToMonitor,
        AttributeId = Attributes.Value,
        SamplingInterval = 1000, // 采样间隔1秒
        NotificationQueueSize = 10,
        DiscardOldest = true
    };

    // 添加通知事件处理程序
    monitoredItem.Notification += (MonitoredItem item, MonitoredItemNotificationEventArgs e) =>
    {
        foreach (var value in item.DequeueValues())
        {
            Console.WriteLine($"数据变化: {value.Value} at {value.SourceTimestamp}");
        }
    };

    // 将监控项添加到订阅中,并将订阅添加到会话
    subscription.AddItem(monitoredItem);
    session.AddSubscription(subscription);
    subscription.Create();
}

参考代码 OPC UA 的客户端开发示例,采用C#编写

使用 OpcUaHelper 库开发

如果你希望快速上手,简化开发流程,OpcUaHelper是一个很好的选择。它对官方SDK进行了封装,提供了更简洁的API。

开发准备

  1. 安装NuGet包

    Install-Package OpcUaHelper
  2. 引入命名空间

    using OpcUaHelper;
    using System;
    using System.Threading.Tasks;

基础代码示例

1. 连接服务器

private OpcUaClient opcUaClient = new OpcUaClient();

private async Task ConnectToServerSimple()
{
    try
    {
        // 直接连接服务器
        await opcUaClient.ConnectServer("opc.tcp://localhost:4840");

        // 如果需要用户名密码认证
        // opcUaClient.UserIdentity = new Opc.Ua.UserIdentity("username", "password");
        // await opcUaClient.ConnectServer("opc.tcp://localhost:4840");

        Console.WriteLine("成功连接到服务器!");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"连接失败: {ex.Message}");
    }
}

// 程序关闭时断开连接
private void FormClosingHandler(object sender, FormClosingEventArgs e)
{
    opcUaClient.Disconnect();
}

2. 读取节点数据

private void ReadDataSimple()
{
    try
    {
        // 读取已知类型的节点值
        string stringValue = opcUaClient.ReadNode<string>("ns=2;s=MyNodeID.StringValue");
        double doubleValue = opcUaClient.ReadNode<double>("ns=2;s=MyNodeID.DoubleValue");
        int intValue = opcUaClient.ReadNode<int>("ns=2;s=MyNodeID.IntValue");

        Console.WriteLine($"字符串值: {stringValue}");
        Console.WriteLine($"双精度值: {doubleValue}");
        Console.WriteLine($"整型值: {intValue}");

        // 批量读取多个节点
        string[] nodeArray = new string[] {
            "ns=2;s=MyNodeID.StringValue",
            "ns=2;s=MyNodeID.DoubleValue",
            "ns=2;s=MyNodeID.IntValue"
        };
        object[] values = opcUaClient.ReadNodes(nodeArray);
        foreach (var value in values)
        {
            Console.WriteLine($"批量读取值: {value}");
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"读取失败: {ex.Message}");
    }
}

3. 写入节点数据

private void WriteDataSimple()
{
    try
    {
        // 写入单个节点
        opcUaClient.WriteNode("ns=2;s=MyNodeID.DoubleValue", 42.5);
        Console.WriteLine("写入成功!");

        // 批量写入多个节点
        string[] nodeArray = new string[] {
            "ns=2;s=MyNodeID.StringValue",
            "ns=2;s=MyNodeID.DoubleValue",
            "ns=2;s=MyNodeID.IntValue"
        };
        object[] valuesToWrite = { "Hello", 42.5, 42 };
        opcUaClient.WriteNodes(nodeArray, valuesToWrite);
        Console.WriteLine("批量写入成功!");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"写入失败: {ex.Message}");
    }
}

4. 订阅数据变化

private void SubscribeToDataChangesSimple()
{
    try
    {
        // 添加订阅,指定节点和回调函数
        opcUaClient.AddSubscription("ns=2;s=DemoSensor.Temperature", OnDataChanged);
        Console.WriteLine("订阅成功!");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"订阅失败: {ex.Message}");
    }
}

// 数据变化时的回调函数
private void OnDataChanged(object value)
{
    Console.WriteLine($"数据发生变化: {value}");
}

开发注意事项

无论选择哪种方式,以下几点都需要注意:

  • 节点ID格式:OPC UA节点的标识符(NodeId)格式多样(字符串、数字、GUID等),使用时需确保与服务器定义的格式完全一致。通常格式为 ns=<namespaceindex>;<identifiertype>=<value>(如 ns=2;s=MySensor.Temperature)。在开发前,最好使用OPC UA浏览器(如UaExpert)或服务器提供的管理工具来浏览服务器地址空间,准确获取节点的ID和数据类型。
  • 异常处理:网络中断、权限不足、节点不存在等情况在工业环境中很常见。务必使用 try-catch 块封装你的OPC UA操作,并进行适当的错误处理和重试逻辑,以增强应用的稳定性。
  • 安全配置:OPC UA支持多种安全策略(Security Policies)和消息安全模式(Message Security Modes),从无安全到高强度的签名加密。生产环境不应使用无安全模式。同时,正确处理证书(应用程序实例证书和信任的服务器证书)是建立安全连接的关键,可能会比较繁琐。
  • 资源管理:OPC UA会话(Session)、订阅(Subscription)等对象可能占用服务器和网络资源。在应用程序关闭或不再需要时,记得调用 Close(), Disconnect()Dispose() 方法来及时释放这些资源,尤其是在长期运行的客户端应用中。
  • 异步操作:许多OPC UA操作,如连接、读取、写入,都有异步方法(如 Session.CreateAsync)。在UI应用程序中,使用异步编程可以避免阻塞用户界面,提升用户体验。