`BadParentNodeIdInvalid` when importing xml with user-defined UAVariableType.
Problem
When importing Opc.Ua.AMLBaseTypes.NodeSet2.xml, it raised BadParentNodeIdInvalid with information:
failure adding node NodeData(nodeid:NumericNodeId(ns=1;i=1010))
Analysis
The node 1010 is defined at Line 175
<UAVariable NodeId="ns=1;i=1010" BrowseName="1:ID" ParentNodeId="ns=1;i=3002" DataType="String">
Its parent node is a user-defined UAVariableType, defined at Line 282
<UAVariableType NodeId="ns=1;i=3002" BrowseName="1:AMLOpcUaConnectionType">
Detailed Steps & Version Information:
# Name: opcua
# Version: 0.98.11
import sys
from opcua import ua, Server, instantiate
from opcua.common.xmlexporter import XmlExporter
server = Server()
node = server.import_xml("Opc.Ua.AMLBaseTypes.NodeSet2.xml")
Same problem happend when import xml from GUI opcua-modeler Ver. 0.5.9
same with Opc.Ua.Robotics.NodeSet2.xml !!!
Call Stack Analysis
I found the problem is caused by xmlimporter.py#L174
# in method add_variable(self, obj):
node = self._get_node(obj)
Input:
-
obj-
parent: NumericNodeId(ns=1;i=3002) -
parentlink: None,
-
Output
-
node-
ParentNodeId: TwoByteNodeId(i=0).
-
The method _get_node skipped the assignment of node.ParentNodeId when obj.parentlink is None, see xmlimporter.py#L148
# in method _get_node(self, obj):
if obj.parent and obj.parentlink:
node.ParentNodeId = self._migrate_ns(obj.parent)
node.ReferenceTypeId = self._migrate_ns(obj.parentlink)
obj.parentlink is None is caused by method _parse_refs. If the ParentNodeId is not in Refereces, the parentlink will remain None.
def _parse_refs(self, el, obj):
parent, parentlink = obj.parent, None
for ref in el:
struct = RefStruct()
struct.forward = "IsForward" not in ref.attrib or ref.attrib["IsForward"] not in ("false", "False")
struct.target = ref.text
struct.reftype = ref.attrib["ReferenceType"]
obj.refs.append(struct)
if ref.attrib["ReferenceType"] == "HasTypeDefinition":
obj.typedef = ref.text
elif not struct.forward:
parent, parentlink = struct.target, struct.reftype
if obj.parent == parent:
obj.parentlink = parentlink
if not obj.parent or not obj.parentlink:
obj.parent, obj.parentlink = parent, parentlink
self.logger.info("Could not detect backward reference to parent for node '%s'", obj.nodeid)
This case can be found in Opc.Ua.AMLBaseTypes.NodeSet2.xml
<UAVariable NodeId="ns=1;i=1010" BrowseName="1:ID" ParentNodeId="ns=1;i=3002" DataType="String">
<DisplayName>ID</DisplayName>
<References>
<Reference ReferenceType="HasTypeDefinition">i=68</Reference>
<Reference ReferenceType="HasModellingRule">i=80</Reference>
</References>
</UAVariable>
It seems like its a OPCFoundation issue
@BigeYoung is it solved or not? i am interested!
@BigeYoung is it solved or not? i am interested!
Yes, I solved it. See the links below for more details. https://github.com/OPCFoundation/UA-Nodeset/issues/60 https://github.com/OPCFoundation/UA-Nodeset/pull/61
@BigeYoung is it solved or not? i am interested!
Oh, I've also checked your file. It seems your issue is not the same as mine.
# -*- coding: UTF-8 -*-
from lxml import etree as ET
file_path = "Opc.Ua.Robotics.NodeSet2.xml"
nodelist = {}
tree = ET.parse(file_path)
xml = tree.getroot()
for child in xml:
ParentNodeId = child.attrib.get("ParentNodeId", None)
References = child.find("{http://opcfoundation.org/UA/2011/03/UANodeSet.xsd}References")
if ParentNodeId and References:
unfound = True
for Ref in References:
if Ref.text == ParentNodeId:
unfound = False
break
if unfound:
nodelist[child.attrib.get("NodeId")] = [ParentNodeId]
The nodelist is still empty, it means that every nodes in this file have references to thier parent.
@BigeYoung is it solved or not? i am interested!
The good news is, your problem is much more easier to be solved.
The file Opc.Ua.Robotics.NodeSet2.xml required another model named Opc.Ua.Di.NodeSet2.xml, see RequiredModel at Opc.Ua.Robotics.NodeSet2.xml#L34
<RequiredModel ModelUri="http://opcfoundation.org/UA/DI/" Version="1.02" PublicationDate="2019-05-01T00:00:00Z" />
So you need to import Opc.Ua.Di.NodeSet2.xml before you import Opc.Ua.Robotics.NodeSet2.xml.
from opcua import ua, Server
from opcua.common.xmlimporter import XmlImporter
server = Server()
server.import_xml("Opc.Ua.Di.NodeSet2.xml")
server.import_xml("Opc.Ua.Robotics.NodeSet2.xml")
I've tested it and hope it works for you!
@BigeYoung : Yes for sure it works! Thanks :) i have rarely used theose special-nodeset
According to opcfoundation-org, it is still a FreeOpcUa issue. See https://github.com/OPCFoundation/UA-Nodeset/issues/60#issuecomment-640907962
Their answer seems trivial. Don't reference both direction to save XML file size? All this does is make XML parsing even more complicated and probably slower as well.
when i am importing OPC UA devices, OPCUA machinery , and OPCUA pump , gertting this error : Parsing value of type 'QualifiedName' not implemented
here is the code i am using :
Create server
server = Server()
Set server endpoint
url = "opc.tcp://10.220.1.163:4140" server.set_endpoint(url)
Set server name
server.set_server_name("OPC UA Server")
server.import_xml("Opc.Ua.Di.NodeSet2.xml") server.import_xml("Opc.Ua.Machinery.Nodeset2.xml") server.import_xml("Opc.Ua.Pumps.NodeSet2.xml") server.import_xml("NetschPump.xml")
server.start()
can somebody tell me what is the error ? i have been trying to solve since 2 days, but did not find any clue. your advices and solution would be appreciated.
You need to switch to opcua-asyncio which also has a sync-wrapper, with very few changes in API. Because this library is not supported anymore. In asyncua we have fixed a lot of bug with the xml importer.