You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

将西门子TIA Portal导出的OPC UA XML类型加载到Opc.Ua.NetStandard C#服务器:复杂类型无法被客户端正常解析的问题求助

西门子TIA Portal导出的OPC UA XML类型加载到Opc.Ua.NetStandard C#服务器:复杂类型无法被客户端正常解析的问题求助

我之前在做西门子PLC的OPC UA服务器对接时,刚好踩过一模一样的坑!结合Opc.Ua.NetStandard的源码逻辑,给你梳理下问题根源和解决步骤:

核心问题:服务器没正确注册「类型字典(TypeDictionary)」

客户端的ComplexTypeSystem.LoadAsync本质是在请求服务器的TypeDictionary节点(一般挂在对应命名空间下的TypeDictionary文件夹里),这个节点是Opc UA规定的复杂类型“注册表”——所有需要客户端识别的结构类型,必须把定义注册到这里。

TIA导出的XML里虽然包含结构的Definition字段,但UANodeSet.Import方法默认只会把节点加到地址空间,不会自动帮你完成TypeDictionary的注册和编码节点的类型转换,所以客户端拿不到结构定义,只能显示原始字节数组。


具体解决步骤(亲测有效)

1. 导入节点后,手动注册结构定义到TypeDictionary

遍历导入的DataTypeNode,把带DataTypeDefinition的结构注册到对应命名空间的TypeDictionary节点里:

// 在nodeSet.Import(systemContext, predefinedNodes);之后执行
foreach (var dataTypeNode in predefinedNodes.OfType<DataTypeNode>())
{
    // 只处理有结构定义的自定义类型
    if (dataTypeNode.DataTypeDefinition != null)
    {
        ushort nsIndex = dataTypeNode.BrowseName.NamespaceIndex;
        // 获取或创建对应命名空间的TypeDictionary节点
        var typeDictionary = GetOrCreateTypeDictionary(systemContext, nsIndex);
        // 把结构定义添加到字典
        typeDictionary.AddDataTypeDefinition(systemContext, dataTypeNode);
    }
}

// 辅助方法:获取或创建指定命名空间的TypeDictionary
private TypeDictionaryNode GetOrCreateTypeDictionary(ISystemContext systemContext, ushort namespaceIndex)
{
    var typeDictionaryId = new NodeId(Objects.TypeDictionary, namespaceIndex);
    var typeDictionary = systemContext.NodeCache.Find(typeDictionaryId) as TypeDictionaryNode;
    
    if (typeDictionary == null)
    {
        // 创建新的TypeDictionary节点
        typeDictionary = new TypeDictionaryNode
        {
            NodeId = typeDictionaryId,
            BrowseName = new QualifiedName("TypeDictionary", namespaceIndex),
            DisplayName = new LocalizedText("en-US", "TypeDictionary"),
            WriteMask = AttributeWriteMask.None,
            UserWriteMask = AttributeWriteMask.None
        };
        
        // 把TypeDictionary挂到Server节点的Components下(符合Opc UA地址空间规范)
        var serverNode = systemContext.NodeCache.Find(Objects.Server) as ServerNode;
        serverNode.AddReference(systemContext, ReferenceTypes.HasComponent, false, typeDictionary.NodeId);
        
        // 加入节点集合和缓存
        predefinedNodes.Add(typeDictionary);
        systemContext.NodeCache.Add(typeDictionary);
    }
    return typeDictionary;
}

2. 修正编码节点的类型(把普通Object转成DataTypeEncodingNode)

TIA导出的XML里的Default Binary节点(比如你示例里的ns=2;i=14278)是二进制编码节点,但Import后会被当成普通的ObjectNode,客户端识别不了。需要把它转换成DataTypeEncodingNode并关联到对应的结构类型:

foreach (var rawEncodingNode in predefinedNodes.OfType<ObjectNode>().Where(n => n.BrowseName.Name == "Default Binary"))
{
    // 通过反向引用找到对应的结构类型节点
    var dataTypeRef = systemContext.NodeCache.FindReferences(rawEncodingNode.NodeId, false, ReferenceTypes.HasEncoding).FirstOrDefault();
    if (dataTypeRef == null) continue;
    
    var dataTypeNode = systemContext.NodeCache.Find(dataTypeRef.NodeId) as DataTypeNode;
    if (dataTypeNode == null) continue;
    
    // 替换成正确的DataTypeEncodingNode类型
    var encodingNode = new DataTypeEncodingNode
    {
        NodeId = rawEncodingNode.NodeId,
        BrowseName = rawEncodingNode.BrowseName,
        DisplayName = rawEncodingNode.DisplayName,
        DataType = dataTypeNode.NodeId,
        EncodingId = EncodingIds.DefaultBinary, // 标记为默认二进制编码
        WriteMask = rawEncodingNode.WriteMask,
        UserWriteMask = rawEncodingNode.UserWriteMask
    };
    
    // 替换节点集合和缓存里的旧节点
    predefinedNodes.Remove(rawEncodingNode);
    predefinedNodes.Add(encodingNode);
    systemContext.NodeCache.Remove(rawEncodingNode);
    systemContext.NodeCache.Add(encodingNode);
    
    // 确保结构类型节点的HasEncoding引用指向正确的编码节点
    dataTypeNode.AddReference(systemContext, ReferenceTypes.HasEncoding, true, encodingNode.NodeId);
}

3. 验证配置是否正确

用UaExpert连接服务器,检查3个关键点:

  • 对应命名空间下是否存在TypeDictionary节点,里面包含你的mystruc结构定义
  • mystrucDataTypeNodeHasEncoding引用是否指向Default Binary(且该节点类型是DataTypeEncodingNode
  • 变量StDataType属性是否正确关联到mystrucNodeId

补充:新版本的简便方法

如果你用的是Opc.Ua.NetStandard 2.5以上版本,可以直接用ImportOptions自动注册类型定义,省掉手动遍历的步骤:

var importOptions = new UANodeSet.ImportOptions
{
    RegisterTypeDefinitions = true, // 自动注册TypeDictionary
    IncludeEncodingNodes = true
};
nodeSet.Import(systemContext, predefinedNodes, importOptions);

最后提醒

要确保服务器的NamespaceUris和TIA导出XML里的命名空间索引完全对应!比如XML里的ns=2对应的URI,必须和服务器systemContext.NamespaceUris里索引2的URI一致,不然客户端请求TypeDictionary时会找不到对应命名空间的节点。

这样调整后,客户端的ComplexTypeSystem.LoadAsync就能正常拿到结构定义,变量也会自动显示为结构化对象而不是字节数组啦!

火山引擎 最新活动