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

如何用C# OPC UA Client读写服务器自定义对象并映射至C#类

一次性读取OPC UA对象节点并映射到C#自定义类

问题背景

拥有一台搭载OPC UA Server的西门子PLC,已通过OPC Foundation ReferenceClient定位到名为Data的对象节点。当前仅能逐个读取其下子节点(如pressure),尝试直接读取Data对象节点时触发错误:Opc.Ua.ServiceResultException: 'BadAttributeIdInvalid',需求是一次性读取整个Data对象并映射到自定义C#类DataClass。

错误原因

OPC UA的对象节点(Object Node)本身没有Value属性,session.ReadValue()方法默认请求读取节点的Value属性(AttributeId=13),因此直接读取对象节点会返回属性ID无效的错误。

解决方案

要实现批量读取并映射,需分三步操作:

  1. 遍历Data对象节点的所有子变量节点,收集它们的NodeId
  2. 批量读取这些变量节点的Value值
  3. 将读取结果映射到自定义C#类实例中

步骤1:遍历对象节点的子变量节点

编写方法获取指定对象下的所有变量节点(包含嵌套节点,比如Startup_Time下的子节点):

using Opc.Ua;
using Opc.Ua.Client;
using System.Collections.Generic;

private List<NodeId> GetAllVariableNodeIds(Session session, NodeId parentNodeId)
{
    var variableNodeIds = new List<NodeId>();
    var browseDesc = new BrowseDescription
    {
        NodeId = parentNodeId,
        BrowseDirection = BrowseDirection.Forward,
        ReferenceTypeId = ReferenceTypeIds.HasComponent,
        IncludeSubtypes = true,
        NodeClassMask = (uint)NodeClass.Variable | (uint)NodeClass.Object,
        ResultMask = (uint)BrowseResultMask.NodeId | (uint)BrowseResultMask.NodeClass
    };

    var browseResult = session.Browse(null, null, 0, browseDesc);
    foreach (var reference in browseResult.References)
    {
        if (reference.NodeClass == NodeClass.Variable)
        {
            variableNodeIds.Add(reference.NodeId);
        }
        else if (reference.NodeClass == NodeClass.Object)
        {
            // 递归遍历嵌套对象节点
            variableNodeIds.AddRange(GetAllVariableNodeIds(session, reference.NodeId));
        }
    }
    return variableNodeIds;
}

步骤2:批量读取变量节点值

使用session.Read()方法批量读取所有收集到的节点值:

var dataNodeId = new NodeId("ns=4;i=2");
var variableNodeIds = GetAllVariableNodeIds(session, dataNodeId);

// 构造读取请求
var readValues = new ReadValueIdCollection();
foreach (var nodeId in variableNodeIds)
{
    readValues.Add(new ReadValueId
    {
        NodeId = nodeId,
        AttributeId = Attributes.Value
    });
}

// 执行批量读取
DataValueCollection results;
DiagnosticInfoCollection diagnosticInfos;
session.Read(null, 0, TimestampsToReturn.Both, readValues, out results, out diagnosticInfos);

步骤3:映射到自定义C#类

将读取结果映射到DataClass,注意处理嵌套的Startup_Time类型:

public DataClass MapToDataClass(DataValueCollection results, List<NodeId> nodeIds)
{
    var data = new DataClass();
    var nodeValueMap = new Dictionary<NodeId, object>();

    // 先把所有节点值存入字典,方便查找
    for (int i = 0; i < results.Count; i++)
    {
        nodeValueMap[nodeIds[i]] = results[i].GetValue(results[i].WrappedValue.TypeInfo);
    }

    // 替换为实际通过ReferenceClient查询到的各字段对应NodeId
    data.pressure = (short)nodeValueMap[new NodeId("ns=4;i=17")];
    data.Startup_Counter = (short)nodeValueMap[new NodeId("ns=4;i=18")];
    
    // 处理嵌套的Startup_Time
    var startupTime = new Startup_Time();
    startupTime.DAY = (byte)nodeValueMap[new NodeId("ns=4;i=19")];
    startupTime.HOUR = (byte)nodeValueMap[new NodeId("ns=4;i=20")];
    startupTime.MINUTE = (byte)nodeValueMap[new NodeId("ns=4;i=21")];
    startupTime.MONTH = (byte)nodeValueMap[new NodeId("ns=4;i=22")];
    startupTime.NANOSECOND = (uint)nodeValueMap[new NodeId("ns=4;i=23")];
    startupTime.SECOND = (byte)nodeValueMap[new NodeId("ns=4;i=24")];
    startupTime.WEEKDAY = (byte)nodeValueMap[new NodeId("ns=4;i=25")];
    startupTime.YEAR = (ushort)nodeValueMap[new NodeId("ns=4;i=26")];
    data.Startup_Time = startupTime;

    data.temperature = (short)nodeValueMap[new NodeId("ns=4;i=27")];
    data.Value1 = (short)nodeValueMap[new NodeId("ns=4;i=28")];

    return data;
}

完整调用示例

// 假设session已成功建立并连接
var dataNodeId = new NodeId("ns=4;i=2");
var variableNodeIds = GetAllVariableNodeIds(session, dataNodeId);

DataValueCollection results;
DiagnosticInfoCollection diagnosticInfos;
session.Read(null, 0, TimestampsToReturn.Both, variableNodeIds.Select(n => new ReadValueId { NodeId = n, AttributeId = Attributes.Value }).ToList(), out results, out diagnosticInfos);

DataClass data = MapToDataClass(results, variableNodeIds);

优化建议

  • 提前将各字段对应的NodeId存储在配置文件中,避免硬编码
  • 对于复杂类型映射,可以使用反射实现自动化映射,减少重复代码
  • 批量读取时注意节点数量限制(部分OPC UA服务器对单次读取的节点数有上限)

内容的提问来源于stack exchange,提问作者Caleb McKay

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 16:55:34