Closed bitkeeper closed 8 years ago
FYI the XML importer only offers basic functionality. Please feel free to work on it. :)
I don't know if making a custom datatype is currently supported in the code. Perhaps you could look at how custom events nodes are created and follow a similar model? Custom events are built from the BaseEventType, however they don't add anything to ObjectIds.
My guess is that custom ObjectIds would be the responsibility of the user to implement when customizing thier OPC UA server.
Can I ask why you need a custom datatype? I never really understood the use case for this feature.
I implemented the parser and I confirm that my goal was to import the standard address space so it might be missing features and the code is quite ugly... I haven't thought much about it but I am against extending the objectids class. This is already a performance bottleneck.... Maybe traversing the node structure is ok. This is a one time operation anyway. , try and let us know
On Tue, Jun 21, 2016, 20:33 Andrew notifications@github.com wrote:
FYI the XML importer only offers basic functionality.
I don't know if making a custom datatype is currently supported in the code. Perhaps you could look at how custom events nodes are created and follow a similar model? Custom events are built from the BaseEventType, however they don't add anything to ObjectIds.
My guess is that custom ObjectIds would be the responsibility of the user to implement when customizing thier OPC UA server.
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/FreeOpcUa/python-opcua/issues/221#issuecomment-227530590, or mute the thread https://github.com/notifications/unsubscribe/ACcfztAqz3trdeZv3TTclfd1RgYz747qks5qOC5rgaJpZM4I64z0 .
@zerox1212 : Defining the and importing custom datatype isn't a problem at all. The problem is using it because the only a lookup (from datatype name to nodeid ) in standard namespace is supported.
Typical use cases are own enumerations and structs. I would like to use a tool like UXModeler to design an address space, including custom object, data, list and event types (have now a working handcrafted alarm proto) .
@oroulet : Ok I will give the node traverse a try.
@bitkeeper It would be great if you could share your tool. I was thinking of the same thing and I started implementing it. However I didn't have time so I just have a python script that builds most of my address space in XML, then I have to edit a few things by hand (which sucks).
The next step I was going to tackle was to export the address space to XML so you could have a nice file based backup/restore option. Instead I just configure my address space once then dump it to a binary. This works but can't be edited.
@zerox1212 The modeling tool tool UaModeler just comes from Unfied Automation.
@bitkeeper does uamodeller exports the same XML format as the spec uses?
@oroulet yes no problem
Guys you review the following:
A nice place for fixing support for custom datatypes seams the function XmlImporter.to_data_type:
def to_data_type(self, nodeid):
if not nodeid:
return ua.NodeId(ua.ObjectIds.String)
if "=" in nodeid:
return ua.NodeId.from_string(nodeid)
elif hasattr(ua.ObjectIds, nodeid): # changed to check if nodeid is present
return ua.NodeId(getattr(ua.ObjectIds, nodeid))
else:
return self.find_type(nodeid) # else find it by browse the nodes
First I tried to implement find_type (as standalone function ) like:
def find_type(node, type_to_find, nodeclassmask = ua.ObjectIds.Organizes):
match = None
for child in node.get_children():
if child.get_browse_name().Name == type_to_find:
match = child
break
else:
match = find_type(child, type_to_find, nodeclassmask = ua.ObjectIds.HasSubtype)
if match:
break
return match
nodeid = find_type(server.get_root_node().get_child(["0:Types"]), "MyObjectType")
That works fine if I use it outside the XmlImporter, but it seems that the nodes inside XmlImporter contains a different structure and have a different type of object for the attribute server.
Which resulted in the following member function of XmlImporter:
def find_type(self, type_to_find):
"""
Finds NodeIs of Type based on the BrowseName.name.
:param type_to_find: string of the type to find.
:returns NodeId of found type_of_find
:raises AttributeError: In case type is not found
"""
match = None
for childid in self.server._aspace.keys():
n = self.server._aspace[childid]
for ref in n.references:
if ref.NodeClass & (NodeClass.ObjectType + NodeClass.VariableType + NodeClass.DataType):
if ref.BrowseName.Name == type_to_find:
match = childid
break
else:
continue
break
return ua.NodeId(match.Identifier, match.NamespaceIndex)
Is this the way to go ?
First a question: Is is correct that a datatype can be specifiied by a truncated qualified name without namespace? that looks like a really broken thing... yes to_data_type looks like the good place :-)
I see that we only passe the node_mgt service to xmlimporter, you should change this to pass the iserver object and update code accordingly. then you will have access to all methods. and can create a Node object Node(self.iserver, nodeid) then use method1 starting from DataType node (not Root node) to browse for all exixting type. method2 is broken since it addresse internal data without locking...
About the using the qname without namespace:
I can only find examples of xml address space where the value of datatype is without namespace:
<UAVariable NodeId="i=30007" BrowseName="MyCustomTypeVar" DataType="MyCustomString">
Also UAModeler generates it without the namespace. The XSD of the address space format can not clarify this because it can only indicate the type of value, not the content of the string.
Maybe the datatypes should be first resolved in the alias section of xml, the aliases does contain the namespaces:
<Aliases>
....
<Alias Alias="MyType1">ns=1;i=3002</Alias>
<Alias Alias="MyType2">ns=1;i=3003</Alias>
<Alias Alias="MyType3">ns=1;i=3004</Alias>
</Aliases>
Any experience with this ?
Using alias make sense!, much more than qname without index. How do you generate XML files? Do they come without alias?
On Tue, Jun 28, 2016, 22:50 Marcel notifications@github.com wrote:
About the using the qname without namespace:
I can only find examples of xml address space where the value of datatype is without namespace:
Also UAModeler generates it without the namespace. The XSD of the address space format can not clarify this because it can only indicate the type of value, not the content of the string. Maybe the datatypes should be first resolved in the alias section of xml, the aliases does contain the namespaces: ``` .... ``` Any experience with this ? — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/FreeOpcUa/python-opcua/issues/221#issuecomment-229179050, or mute the thread https://github.com/notifications/unsubscribe/ACcfziildjl916XsCgmBpx3nt9Er_dmpks5qQYkYgaJpZM4I64z0 .ns=1;i=3002 ns=1;i=3003 ns=1;i=3004
I use UaModeler from Unified Automation and it generates also the aliases.
Ok so lets support the aliases, I think also that this is the intention.
The XmlImporter already contains a function to_ref_type
to lookup the nodeid of an alias.
This makes it easy to integrate the alias support into XmlImporter
def to_data_type(self, nodeid):
if not nodeid:
return ua.NodeId(ua.ObjectIds.String)
if "=" in nodeid:
return ua.NodeId.from_string(nodeid)
elif hasattr(ua.ObjectIds, nodeid):
return ua.NodeId(getattr(ua.ObjectIds, nodeid))
elif ":" in nodeid: # add support for full qnames
return self.find_qname(nodeid) # new function
else: # added support for aliases
return self.to_ref_type(nodeid) # function already present
Which lead to support of:
<UAVariable DataType="ns=1;i=3004" ParentNodeId="ns=1;i=5002" NodeId="ns=1;i=6006" BrowseName="1:MyVar">
<UAVariable DataType="String" ParenitNodeId="ns=1;i=5002" NodeId="ns=1;i=6006" BrowseName="1:MyVar">
<UAVariable DataType="1:MyVarType" ParentNodeId="ns=1;i=5002" NodeId="ns=1;i=6006" BrowseName="1:MyVar">
<UAVariable DataType="MyVarType" ParentNodeId="ns=1;i=5002" NodeId="ns=1;i=6006" BrowseName="1:MyVar">
Good enough for me.
Example implementation of XmlImporter.find_qname, still based on self.server is node_mgt service. Rewriting the XmlImporter is a different issue, maybe performed by someone with a little more knowledge about the internals then me ..
def find_qname(self, qname_to_find):
"""Finds NodeIs of Type based on the QName.
:param type_to_find: string of the qname to find. Like '1:MyType'
:returns NodeId of found qname_to_find
:raises AttributeError: In case type is not found
"""
match = None
ns, name = qname_to_find.split(':')
for childid in self.server._aspace.keys():
n = self.server._aspace[childid]
for ref in n.references:
if ref.NodeClass & (NodeClass.ObjectType + NodeClass.VariableType + NodeClass.DataType):
if ref.BrowseName.to_string() == qname_to_find: # and ref.BrowseName.NamespaceIndex':
match = childid
break
else:
continue
break
return ua.NodeId(match.Identifier, match.NamespaceIndex)
Do you really need the qname stuff? it looks very strange, but it would be great if you could send a pull request for the alias stuff
UA Modeler looks nice. I'm testing it now.
The only thing I don't like is that String NodeIds will be pain to use because it won't create them automatically. It only auto generates integer NodeIds. I prefer strings because they are way more readable.
One thing I did notice. If you make a variable in UA Modeler and give it a value the XML importer might have problems with the "Value" format.
<Value>
<uax:Float>100</uax:Float>
</Value>
The python example XML does not have "uax:".
I'm looking forward to a PR.
@oroulet : I don't think it is needed:
The xsd (https://opcfoundation.org/UA/2011/03/UANodeSet.xsd) indicates that datatype is a nodeid.
<xs:complexType name="UAVariable">
<xs:complexContent>
<xs:extension base="UAInstance">
<xs:sequence>
<xs:element name="Value" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:any minOccurs="0" processContents="lax" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Translation" type="TranslationType" minOccurs="0" maxOccurs="unbounded"></xs:element>
</xs:sequence>
<xs:attribute name="DataType" type="NodeId" default="i=24"></xs:attribute>
<xs:attribute name="ValueRank" type="ValueRank" default="-1"></xs:attribute>
<xs:attribute name="ArrayDimensions" type="ArrayDimensions" default=""></xs:attribute>
<xs:attribute name="AccessLevel" type="AccessLevel" default="1"></xs:attribute>
<xs:attribute name="UserAccessLevel" type="AccessLevel" default="1"></xs:attribute>
<xs:attribute name="MinimumSamplingInterval" type="Duration" default="0"></xs:attribute>
<xs:attribute name="Historizing" type="xs:boolean" default="false"></xs:attribute>
</xs:extension>
</xs:complexContent>
</xs:complexType>
Which I think indicates that if name is used it is always an alias. Even the used basic types like string are mentioned in the alias list.
So lets get rid of the qname function.
I trying to import fragment below with XmlImporter.import_xml. It contains a custom datatype.
It fails with:
It looks the to_data_type is limited to using types of the standard address space. Ok no problem. Now I want add support for custom types, could you suggest the preferred way to get the nodeid: