You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用OPC Foundation .NET浏览OPC UA服务器的数组节点?

问题描述

我修改了Stack Overflow上的代码,编写了测试程序以浏览Siemens S7-1200 OPC UA服务器的节点,代码如下:

using (var session = Session.Create(config, new ConfiguredEndpoint(null, selectedEndpoint, EndpointConfiguration.Create(config)), false, "", 60000, null, null).GetAwaiter().GetResult())
{
    Console.WriteLine("Step 3 - Browse the server namespace.");
    ReferenceDescriptionCollection refs;
    byte[] bts;
    session.Browse(null, null, ObjectIds.ObjectsFolder, 0u, BrowseDirection.Forward, ReferenceTypeIds.HierarchicalReferences, true, (uint)NodeClass.Variable | (uint)NodeClass.Object | (uint)NodeClass.Method, out bts, out refs);
    Console.WriteLine("DisplayName: BrowseName, NodeClass");
    foreach (var rd in refs.Where(r => r.DisplayName == "ServerInterfaces"))
    {
        Console.WriteLine("{0}: {1}, {2}", rd.DisplayName, rd.BrowseName, rd.NodeClass);
        ReferenceDescriptionCollection refs2;
        byte[] bts2;
        session.Browse(null, null, ExpandedNodeId.ToNodeId(rd.NodeId, session.NamespaceUris), 0u, BrowseDirection.Forward, ReferenceTypeIds.HierarchicalReferences, true, (uint)NodeClass.Variable | (uint)NodeClass.Object | (uint)NodeClass.Method, out bts2, out refs2);
        foreach (var nextRd in refs2)
        {
            ReferenceDescriptionCollection refs3;
            byte[] bts3;
            Console.WriteLine("NameSpace+ {0}: {1}, {2}", nextRd.DisplayName, nextRd.BrowseName, nextRd.NodeClass);
            session.Browse(null, null, ExpandedNodeId.ToNodeId(nextRd.NodeId, session.NamespaceUris), 0u, BrowseDirection.Forward, ReferenceTypeIds.HierarchicalReferences, true, (uint)NodeClass.Variable | (uint)NodeClass.Object | (uint)NodeClass.Method, out bts3, out refs3);
            foreach (var nextRd2 in refs3.Where(n => n.DisplayName != "Icon"))
            {
                NodeId nodeId = new NodeId(nextRd2.NodeId.ToString());
                var value = session.ReadValue(nodeId);
                Console.WriteLine("Node+ {0}: {1}, {2}, NodeId = {3}, Value = {4}", nextRd2.DisplayName, nextRd2.BrowseName, nextRd2.NodeClass, nextRd2.NodeId, value);
            }
        }
    }
}

服务器中有一个INT类型数组,当前程序输出仅显示数组节点本身(Value为null),无法列出数组元素:

ServerInterfaces: 3:ServerInterfaces, Object
NameSpace+ Server interface_1: 4:Server interface_1, Object
Node+ read: 4:read, Variable, NodeId = ns=4;i=2, Value = (null)
Node+ IntValue1: 4:IntValue1, Variable, NodeId = ns=4;i=7, Value = 100

期望输出是能展示数组的每个元素及其值:

ServerInterfaces: 3:ServerInterfaces, Object
NameSpace+ Server interface_1: 4:Server interface_1, Object
Node+ read: 4:read, Variable, NodeId = ns=4;i=2, Value = (null)
Node+ read: 4:read[0], Variable, NodeId = ns=4;i=3, Value = 1
Node+ read: 4:read[1], Variable, NodeId = ns=4;i=4, Value = 1
Node+ read: 4:read[2], Variable, NodeId = ns=4;i=5, Value = 1
Node+ read: 4:read[3], Variable, NodeId = ns=4;i=6, Value = 1
Node+ IntValue1: 4:IntValue1, Variable, NodeId = ns=4;i=7, Value = 100

此外,另一程序可正常展示自动生成的数组元素,但自定义的'read'数组仍无法显示元素,请问如何修改代码实现期望输出?


解决方案

1. 核心修改思路

OPC UA中数组变量的处理需要分两种场景:一是直接读取数组完整值并解析元素,二是针对部分服务器将数组元素作为子节点暴露的情况,额外浏览数组节点的子节点。针对S7-1200自定义数组的特性,还可以利用其NodeId的生成规则直接定位元素节点。

2. 修改后的核心代码

替换原代码中遍历refs3的循环部分:

foreach (var nextRd2 in refs3.Where(n => n.DisplayName != "Icon"))
{
    // 优化NodeId转换方式,避免字符串解析错误
    NodeId nodeId = ExpandedNodeId.ToNodeId(nextRd2.NodeId, session.NamespaceUris);
    DataValue dataValue = session.ReadValue(nodeId);
    var value = dataValue.Value;

    Console.WriteLine("Node+ {0}: {1}, {2}, NodeId = {3}, Value = {4}", 
        nextRd2.DisplayName, nextRd2.BrowseName, nextRd2.NodeClass, nextRd2.NodeId, value);

    // 处理数组类型变量
    if (value != null && value.GetType().IsArray)
    {
        Array arrayValue = (Array)value;
        // 按S7-1200自定义数组的NodeId规则生成元素NodeId
        // 数组NodeId为ns=4;i=2时,元素[0]对应ns=4;i=3,以此类推
        int baseId = int.Parse(nodeId.Identifier.ToString()) + 1;
        for (int i = 0; i < arrayValue.Length; i++)
        {
            Console.WriteLine("Node+ {0}: {1}[{2}], Variable, NodeId = ns=4;i={3}, Value = {4}", 
                nextRd2.DisplayName, nextRd2.BrowseName, i, baseId + i, arrayValue.GetValue(i));
        }
    }
    else
    {
        // 尝试浏览数组节点的子节点,兼容自动生成数组的场景
        ReferenceDescriptionCollection arrayRefs;
        byte[] arrayBts;
        session.Browse(null, null, nodeId, 0u, BrowseDirection.Forward, ReferenceTypeIds.HierarchicalReferences, true, (uint)NodeClass.Variable, out arrayBts, out arrayRefs);
        
        foreach (var elemRef in arrayRefs)
        {
            NodeId elemNodeId = ExpandedNodeId.ToNodeId(elemRef.NodeId, session.NamespaceUris);
            var elemValue = session.ReadValue(elemNodeId).Value;
            Console.WriteLine("Node+ {0}: {1}, Variable, NodeId = {2}, Value = {3}", 
                elemRef.DisplayName, elemRef.BrowseName, elemRef.NodeId, elemValue);
        }
    }
}

3. 关键修改说明

  • 数组值解析:判断变量值是否为数组类型,强制转换后遍历输出每个元素,解决原代码中直接输出数组对象显示null的问题。
  • S7-1200自定义数组适配:利用S7-1200 OPC UA服务器的NodeId生成规则,在数组节点Id基础上递增得到元素节点Id,确保自定义数组元素能被正确定位。
  • 子节点浏览兼容:保留浏览数组子节点的逻辑,适配自动生成数组的场景,确保两种数组类型都能正常显示元素。
  • NodeId优化:将new NodeId(nextRd2.NodeId.ToString())替换为ExpandedNodeId.ToNodeId,避免字符串解析导致的NodeId错误。

内容的提问来源于stack exchange,提问作者Victor Pieper

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 23:50:32