ChimeraTK-ControlSystemAdapter-OPCUAAdapter 04.00.05
Loading...
Searching...
No Matches
ua_namespace.py
Go to the documentation of this file.
1#!/usr/bin/env/python
2# -*- coding: utf-8 -*-
3
4# This Source Code Form is subject to the terms of the Mozilla Public
5# License, v. 2.0. If a copy of the MPL was not distributed with this
6# file, You can obtain one at http://mozilla.org/MPL/2.0/.
7
8
22
23from __future__ import print_function
24import sys
25from time import struct_time, strftime, strptime, mktime
26from struct import pack as structpack
27
28import logging
29from ua_builtin_types import *;
30from ua_node_types import *;
31from ua_constants import *;
32from open62541_MacroHelper import open62541_MacroHelper
33
34
35logger = logging.getLogger(__name__)
36
37def getNextElementNode(xmlvalue):
38 if xmlvalue == None:
39 return None
40 xmlvalue = xmlvalue.nextSibling
41 while not xmlvalue == None and not xmlvalue.nodeType == xmlvalue.ELEMENT_NODE:
42 xmlvalue = xmlvalue.nextSibling
43 return xmlvalue
44
45
48
50 """ Class holding and managing a set of OPCUA nodes.
51
52 This class handles parsing XML description of namespaces, instantiating
53 nodes, linking references, graphing the namespace and compiling a binary
54 representation.
55
56 Note that nodes assigned to this class are not restricted to having a
57 single namespace ID. This class represents the entire physical address
58 space of the binary representation and all nodes that are to be included
59 in that segment of memory.
60 """
61 nodes = []
62 nodeids = {}
63 aliases = {}
64 __linkLater__ = []
65 __binaryIndirectPointers__ = []
66 name = ""
67 knownNodeTypes = ""
68 namespaceIdentifiers = {} # list of 'int':'string' giving different namespace an array-mapable name
69
70 def __init__(self, name):
71 self.nodesnodes = []
72 self.knownNodeTypesknownNodeTypes = ['variable', 'object', 'method', 'referencetype', \
73 'objecttype', 'variabletype', 'methodtype', \
74 'datatype', 'referencetype', 'aliases']
75 self.namename = name
80
81 def addNamespace(self, numericId, stringURL):
82 self.namespaceIdentifiersnamespaceIdentifiers[numericId] = stringURL
83
84 def linkLater(self, pointer):
85 """ Called by nodes or references who have parsed an XML reference to a
86 node represented by a string.
87
88 No return value
89
90 XML String representations of references have the form 'i=xy' or
91 'ns=1;s="This unique Node"'. Since during the parsing of this attribute
92 only a subset of nodes are known/parsed, this reference string cannot be
93 linked when encountered.
94
95 References register themselves with the namespace to have their target
96 attribute (string) parsed by linkOpenPointers() when all nodes are
97 created, so that target can be dereferenced an point to an actual node.
98 """
99 self.__linkLater____linkLater__.append(pointer)
100
102 """ Return the list of references registered for linking during the next call
103 of linkOpenPointers()
104 """
106
108 """ Returns the number of unlinked references that will be processed during
109 the next call of linkOpenPointers()
110 """
111 return len(self.__linkLater____linkLater__)
112
113 def buildAliasList(self, xmlelement):
114 """ Parses the <Alias> XML Element present in must XML NodeSet definitions.
115
116 No return value
117
118 Contents the Alias element are stored in a dictionary for further
119 dereferencing during pointer linkage (see linkOpenPointer()).
120 """
121 if not xmlelement.tagName == "Aliases":
122 logger.error("XMLElement passed is not an Aliaslist")
123 return
124 for al in xmlelement.childNodes:
125 if al.nodeType == al.ELEMENT_NODE:
126 if al.hasAttribute("Alias"):
127 aliasst = al.getAttribute("Alias")
128 if sys.version_info[0] < 3:
129 aliasnd = unicode(al.firstChild.data)
130 else:
131 aliasnd = al.firstChild.data
132 if not aliasst in self.aliasesaliases:
133 self.aliasesaliases[aliasst] = aliasnd
134 logger.debug("Added new alias \"" + str(aliasst) + "\" == \"" + str(aliasnd) + "\"")
135 else:
136 if self.aliasesaliases[aliasst] != aliasnd:
137 logger.error("Alias definitions for " + aliasst + " differ. Have " + self.aliasesaliases[aliasst] + " but XML defines " + aliasnd + ". Keeping current definition.")
138
139 def getNodeByBrowseName(self, idstring):
140 """ Returns the first node in the nodelist whose browseName matches idstring.
141 """
142 matches = []
143 for n in self.nodesnodes:
144 if idstring==str(n.browseName()):
145 matches.append(n)
146 if len(matches) > 1:
147 logger.error("Found multiple nodes with same ID!?")
148 if len(matches) == 0:
149 return None
150 else:
151 return matches[0]
152
153 def getNodeByIDString(self, idstring):
154 """ Returns the first node in the nodelist whose id string representation
155 matches idstring.
156 """
157 matches = []
158 for n in self.nodesnodes:
159 if idstring==str(n.id()):
160 matches.append(n)
161 if len(matches) > 1:
162 logger.error("Found multiple nodes with same ID!?")
163 if len(matches) == 0:
164 return None
165 else:
166 return matches[0]
167
168 def createNode(self, ndtype, xmlelement):
169 """ createNode is instantiates a node described by xmlelement, its type being
170 defined by the string ndtype.
171
172 No return value
173
174 If the xmlelement is an <Alias>, the contents will be parsed and stored
175 for later dereferencing during pointer linking (see linkOpenPointers).
176
177 Recognized types are:
178 * UAVariable
179 * UAObject
180 * UAMethod
181 * UAView
182 * UAVariableType
183 * UAObjectType
184 * UAMethodType
185 * UAReferenceType
186 * UADataType
187
188 For every recognized type, an appropriate node class is added to the node
189 list of the namespace. The NodeId of the given node is created and parsing
190 of the node attributes and elements is delegated to the parseXML() and
191 parseXMLSubType() functions of the instantiated class.
192
193 If the NodeID attribute is non-unique in the node list, the creation is
194 deferred and an error is logged.
195 """
196 if not isinstance(xmlelement, dom.Element):
197 logger.error( "Error: Can not create node from invalid XMLElement")
198 return
199
200 # An ID is mandatory for everything but aliases!
201 id = None
202 for idname in ['NodeId', 'NodeID', 'nodeid']:
203 if xmlelement.hasAttribute(idname):
204 id = xmlelement.getAttribute(idname)
205 if ndtype == 'aliases':
206 self.buildAliasList(xmlelement)
207 return
208 elif id == None:
209 logger.info( "Error: XMLElement has no id, node will not be created!")
210 return
211 else:
212 id = opcua_node_id_t(id)
213
214 if str(id) in self.nodeidsnodeids:
215 # Normal behavior: Do not allow duplicates, first one wins
216 #logger.error( "XMLElement with duplicate ID " + str(id) + " found, node will not be created!")
217 #return
218 # Open62541 behavior for header generation: Replace the duplicate with the new node
219 logger.info( "XMLElement with duplicate ID " + str(id) + " found, node will be replaced!")
220 nd = self.getNodeByIDString(str(id))
221 self.nodesnodes.remove(nd)
222 self.nodeidsnodeids.pop(str(nd.id()))
223
224 node = None
225 if (ndtype == 'variable'):
226 node = opcua_node_variable_t(id, self)
227 elif (ndtype == 'object'):
228 node = opcua_node_object_t(id, self)
229 elif (ndtype == 'method'):
230 node = opcua_node_method_t(id, self)
231 elif (ndtype == 'objecttype'):
232 node = opcua_node_objectType_t(id, self)
233 elif (ndtype == 'variabletype'):
234 node = opcua_node_variableType_t(id, self)
235 elif (ndtype == 'methodtype'):
236 node = opcua_node_methodType_t(id, self)
237 elif (ndtype == 'datatype'):
238 node = opcua_node_dataType_t(id, self)
239 elif (ndtype == 'referencetype'):
240 node = opcua_node_referenceType_t(id, self)
241 else:
242 logger.error( "No node constructor for type " + ndtype)
243
244 if node != None:
245 node.parseXML(xmlelement)
246
247 self.nodesnodes.append(node)
248 self.nodeidsnodeids[str(node.id())] = node
249
250 def removeNodeById(self, nodeId):
251 nd = self.getNodeByIDString(nodeId)
252 if nd == None:
253 return False
254
255 logger.debug("Removing nodeId " + str(nodeId))
256 self.nodesnodes.remove(nd)
257 if nd.getInverseReferences() != None:
258 for ref in nd.getInverseReferences():
259 src = ref.target();
260 src.removeReferenceToNode(nd)
261
262 return True
263
265 """ Appends a node to the list of nodes that should be contained in the
266 first 765 bytes (255 pointer slots a 3 bytes) in the binary
267 representation (indirect referencing space).
268
269 This function is reserved for references and dataType pointers.
270 """
274
276 """ Returns the slot/index of a pointer in the indirect referencing space
277 (first 765 Bytes) of the binary representation.
278 """
280 return -1
282
283
284 def parseXML(self, xmldoc):
285 """ Reads an XML Namespace definition and instantiates node.
286
287 No return value
288
289 parseXML open the file xmldoc using xml.dom.minidom and searches for
290 the first UANodeSet Element. For every Element encountered, createNode
291 is called to instantiate a node of the appropriate type.
292 """
293 typedict = {}
294 UANodeSet = dom.parse(xmldoc).getElementsByTagName("UANodeSet")
295 if len(UANodeSet) == 0:
296 logger.error( "Error: No NodeSets found")
297 return
298 if len(UANodeSet) != 1:
299 logger.error( "Error: Found more than 1 Nodeset in XML File")
300
301 UANodeSet = UANodeSet[0]
302 for nd in UANodeSet.childNodes:
303 if nd.nodeType != nd.ELEMENT_NODE:
304 continue
305
306 ndType = nd.tagName.lower()
307 if ndType[:2] == "ua":
308 ndType = ndType[2:]
309 elif not ndType in self.knownNodeTypesknownNodeTypes:
310 logger.warn("XML Element or NodeType " + ndType + " is unknown and will be ignored")
311 continue
312
313 if not ndType in typedict:
314 typedict[ndType] = 1
315 else:
316 typedict[ndType] = typedict[ndType] + 1
317
318 self.createNode(ndType, nd)
319 logger.debug("Currently " + str(len(self.nodesnodes)) + " nodes in address space. Type distribution for this run was: " + str(typedict))
320
322 """ Substitutes symbolic NodeIds in references for actual node instances.
323
324 No return value
325
326 References that have registered themselves with linkLater() to have
327 their symbolic NodeId targets ("ns=2;i=32") substituted for an actual
328 node will be iterated by this function. For each reference encountered
329 in the list of unlinked/open references, the target string will be
330 evaluated and searched for in the node list of this namespace. If found,
331 the target attribute of the reference will be substituted for the
332 found node.
333
334 If a reference fails to get linked, it will remain in the list of
335 unlinked references. The individual items in this list can be
336 retrieved using getUnlinkedPointers().
337 """
338 linked = []
339
340 logger.debug( str(self.unlinkedItemCount()) + " pointers need to get linked.")
341 for l in self.__linkLater____linkLater__:
342 targetLinked = False
343 if not l.target() == None and not isinstance(l.target(), opcua_node_t):
344 if isinstance(l.target(),str) or isinstance(l.target(),unicode):
345 # If is not a node ID, it should be an alias. Try replacing it
346 # with a proper node ID
347 if l.target() in self.aliasesaliases:
348 l.target(self.aliasesaliases[l.target()])
349 # If the link is a node ID, try to find it hopening that no ass has
350 # defined more than one kind of id for that sucker
351 if l.target()[:2] == "i=" or l.target()[:2] == "g=" or \
352 l.target()[:2] == "b=" or l.target()[:2] == "s=" or \
353 l.target()[:3] == "ns=" :
354 tgt = self.getNodeByIDString(str(l.target()))
355 if tgt == None:
356 logger.error("Failed to link pointer to target (node not found) " + l.target())
357 else:
358 l.target(tgt)
359 targetLinked = True
360 else:
361 logger.error("Failed to link pointer to target (target not Alias or Node) " + l.target())
362 else:
363 logger.error("Failed to link pointer to target (don't know dummy type + " + str(type(l.target())) + " +) " + str(l.target()))
364 else:
365 logger.error("Pointer has null target: " + str(l))
366
367
368 referenceLinked = False
369 if not l.referenceType() == None:
370 if l.referenceType() in self.aliasesaliases:
371 l.referenceType(self.aliasesaliases[l.referenceType()])
372 tgt = self.getNodeByIDString(str(l.referenceType()))
373 if tgt == None:
374 logger.error("Failed to link reference type to target (node not found) " + l.referenceType())
375 else:
376 l.referenceType(tgt)
377 referenceLinked = True
378 else:
379 referenceLinked = True
380
381 if referenceLinked == True and targetLinked == True:
382 linked.append(l)
383
384 # References marked as "not forward" must be inverted (removed from source node, assigned to target node and relinked)
385 logger.warn("Inverting reference direction for all references with isForward==False attribute (is this correct!?)")
386 for n in self.nodesnodes:
387 for r in n.getReferences():
388 if r.isForward() == False:
389 tgt = r.target()
390 if isinstance(tgt, opcua_node_t):
391 nref = opcua_referencePointer_t(n, parentNode=tgt)
392 nref.referenceType(r.referenceType())
393 tgt.addReference(nref)
394
395 # Create inverse references for all nodes
396 logger.debug("Updating all referencedBy fields in nodes for inverse lookups.")
397 for n in self.nodesnodes:
398 n.updateInverseReferences()
399
400 for l in linked:
401 self.__linkLater____linkLater__.remove(l)
402
403 if len(self.__linkLater____linkLater__) != 0:
404 logger.warn(str(len(self.__linkLater____linkLater__)) + " could not be linked.")
405
406 def sanitize(self):
407 remove = []
408 logger.debug("Sanitizing nodes and references...")
409 for n in self.nodesnodes:
410 if n.sanitize() == False:
411 remove.append(n)
412 if not len(remove) == 0:
413 logger.warn(str(len(remove)) + " nodes will be removed because they failed sanitation.")
414 # FIXME: Some variable ns=0 nodes fail because they don't have DataType fields...
415 # How should this be handles!?
416 logger.warn("Not actually removing nodes... it's unclear if this is valid or not")
417
418 def getRoot(self):
419 """ Returns the first node instance with the browseName "Root".
420 """
421 return self.getNodeByBrowseName("Root")
422
424 """ Calls buildEncoding() for all DataType nodes (opcua_node_dataType_t).
425
426 No return value
427 """
428 stat = {True: 0, False: 0}
429 for n in self.nodesnodes:
430 if isinstance(n, opcua_node_dataType_t):
431 n.buildEncoding()
432 stat[n.isEncodable()] = stat[n.isEncodable()] + 1
433 logger.debug("Type definitions built/passed: " + str(stat))
434
436 for n in self.nodesnodes:
437 if isinstance(n, opcua_node_variable_t):
438 n.allocateValue()
439
440 def printDot(self, filename="namespace.dot"):
441 """ Outputs a graphiz/dot description of all nodes in the namespace.
442
443 Output will written into filename to be parsed by dot/neato...
444
445 Note that for namespaces with more then 20 nodes the reference structure
446 will lead to a mostly illegible and huge graph. Use printDotGraphWalk()
447 for plotting specific portions of a large namespace.
448 """
449 file=open(filename, 'w+')
450
451 file.write("digraph ns {\n")
452 for n in self.nodesnodes:
453 file.write(n.printDot())
454 file.write("}\n")
455 file.close()
456
457 def getSubTypesOf(self, tdNodes = None, currentNode = None, hasSubtypeRefNode = None):
458 # If this is a toplevel call, collect the following information as defaults
459 if tdNodes == None:
460 tdNodes = []
461 if currentNode == None:
462 currentNode = self.getNodeByBrowseName("HasTypeDefinition")
463 tdNodes.append(currentNode)
464 if len(tdNodes) < 1:
465 return []
466 if hasSubtypeRefNode == None:
467 hasSubtypeRefNode = self.getNodeByBrowseName("HasSubtype")
468 if hasSubtypeRefNode == None:
469 return tdNodes
470
471 # collect all subtypes of this node
472 for ref in currentNode.getReferences():
473 if ref.isForward() and ref.referenceType().id() == hasSubtypeRefNode.id():
474 tdNodes.append(ref.target())
475 self.getTypeDefinitionNodes(tdNodes=tdNodes, currentNode = ref.target(), hasSubtypeRefNode=hasSubtypeRefNode)
476
477 return tdNodes
478
479
480 def printDotGraphWalk(self, depth=1, filename="out.dot", rootNode=None, followInverse = False, excludeNodeIds=[]):
481 """ Outputs a graphiz/dot description the nodes centered around rootNode.
482
483 References beginning from rootNode will be followed for depth steps. If
484 "followInverse = True" is passed, then inverse (not Forward) references
485 will also be followed.
486
487 Nodes can be excluded from the graph by passing a list of NodeIds as
488 string representation using excludeNodeIds (ex ["i=53", "ns=2;i=453"]).
489
490 Output is written into filename to be parsed by dot/neato/srfp...
491 """
492 iter = depth
493 processed = []
494 if rootNode == None or \
495 not isinstance(rootNode, opcua_node_t) or \
496 not rootNode in self.nodesnodes:
497 root = self.getRoot()
498 else:
499 root = rootNode
500
501 file=open(filename, 'w+')
502
503 if root == None:
504 return
505
506 file.write("digraph ns {\n")
507 file.write(root.printDot())
508 refs=[]
509 if followInverse == True:
510 refs = root.getReferences(); # + root.getInverseReferences()
511 else:
512 for ref in root.getReferences():
513 if ref.isForward():
514 refs.append(ref)
515 while iter > 0:
516 tmp = []
517 for ref in refs:
518 if isinstance(ref.target(), opcua_node_t):
519 tgt = ref.target()
520 if not str(tgt.id()) in excludeNodeIds:
521 if not tgt in processed:
522 file.write(tgt.printDot())
523 processed.append(tgt)
524 if ref.isForward() == False and followInverse == True:
525 tmp = tmp + tgt.getReferences(); # + tgt.getInverseReferences()
526 elif ref.isForward() == True :
527 tmp = tmp + tgt.getReferences();
528 refs = tmp
529 iter = iter - 1
530
531 file.write("}\n")
532 file.close()
533
535 rcind = -1
536 rind = -1
537 minweight = -1
538 minweightnd = None
539 for row in nmatrix:
540 rcind += 1
541 if row[0] == None:
542 continue
543 w = sum(row[1:])
544 if minweight < 0:
545 rind = rcind
546 minweight = w
547 minweightnd = row[0]
548 elif w < minweight:
549 rind = rcind
550 minweight = w
551 minweightnd = row[0]
552 return (rind, minweightnd, minweight)
553
555 # create a matrix represtantion of all node
556 #
557 nmatrix = []
558 for n in range(0,len(self.nodesnodes)):
559 nmatrix.append([None] + [0]*len(self.nodesnodes))
560
561 typeRefs = []
562 tn = self.getNodeByBrowseName("HasTypeDefinition")
563 if tn != None:
564 typeRefs.append(tn)
565 typeRefs = typeRefs + self.getSubTypesOf(currentNode=tn)
566 subTypeRefs = []
567 tn = self.getNodeByBrowseName("HasSubtype")
568 if tn != None:
569 subTypeRefs.append(tn)
570 subTypeRefs = subTypeRefs + self.getSubTypesOf(currentNode=tn)
571
572 logger.debug("Building connectivity matrix for node order optimization.")
573 # Set column 0 to contain the node
574 for node in self.nodesnodes:
575 nind = self.nodesnodes.index(node)
576 nmatrix[nind][0] = node
577
578 # Determine the dependencies of all nodes
579 logger.debug("Determining node interdependencies.")
580 for node in self.nodesnodes:
581 nind = self.nodesnodes.index(node)
582 #print "Examining node " + str(nind) + " " + str(node)
583 for ref in node.getReferences():
584 if isinstance(ref.target(), opcua_node_t):
585 tind = self.nodesnodes.index(ref.target())
586 # Typedefinition of this node has precedence over this node
587 if ref.referenceType() in typeRefs and ref.isForward():
588 nmatrix[nind][tind+1] += 200 # Very big weight for typedefs
589 # isSubTypeOf/typeDefinition of this node has precedence over this node
590 elif ref.referenceType() in subTypeRefs and not ref.isForward():
591 nmatrix[nind][tind+1] += 100 # Big weight for subtypes
592 # Else the target depends on us
593 elif ref.isForward():
594 nmatrix[tind][nind+1] += 1 # regular weight for dependencies
595
596 logger.debug("Using Djikstra topological sorting to determine printing order.")
597 reorder = []
598 while len(reorder) < len(self.nodesnodes):
599 (nind, node, w) = self.__reorder_getMinWeightNode__(nmatrix)
600 #print str(100*float(len(reorder))/len(self.nodes)) + "% " + str(w) + " " + str(node) + " " + str(node.browseName())
601 reorder.append(node)
602 for ref in node.getReferences():
603 if isinstance(ref.target(), opcua_node_t):
604 tind = self.nodesnodes.index(ref.target())
605 if ref.referenceType() in typeRefs and ref.isForward():
606 nmatrix[nind][tind+1] -= 200
607 elif ref.referenceType() in subTypeRefs and not ref.isForward():
608 nmatrix[nind][tind+1] -= 100
609 elif ref.isForward():
610 nmatrix[tind][nind+1] -= 1
611 nmatrix[nind][0] = None
612 self.nodesnodes = reorder
613 logger.debug("Nodes reordered.")
614 return
615
616 def printOpen62541Header(self, printedExternally=[], supressGenerationOfAttribute=[], outfilename=""):
617 unPrintedNodes = []
618 unPrintedRefs = []
619 code = []
620 header = []
621
622 # Reorder our nodes to produce a bare minimum of bootstrapping dependencies
623 logger.debug("Reordering nodes for minimal dependencies during printing.")
625
626 # Some macros (UA_EXPANDEDNODEID_MACRO()...) are easily created, but
627 # bulky. This class will help to offload some code.
628 codegen = open62541_MacroHelper(supressGenerationOfAttribute=supressGenerationOfAttribute)
629
630 # Populate the unPrinted-Lists with everything we have.
631 # Every Time a nodes printfunction is called, it will pop itself and
632 # all printed references from these lists.
633 for n in self.nodesnodes:
634 if not n in printedExternally:
635 unPrintedNodes.append(n)
636 else:
637 logger.debug("Node " + str(n.id()) + " is being ignored.")
638 for n in unPrintedNodes:
639 for r in n.getReferences():
640 if (r.target() != None) and (r.target().id() != None) and (r.parent() != None):
641 unPrintedRefs.append(r)
642
643 logger.debug(str(len(unPrintedNodes)) + " Nodes, " + str(len(unPrintedRefs)) + "References need to get printed.")
644 header.append("/* WARNING: This is a generated file.\n * Any manual changes will be overwritten.\n\n */")
645 code.append("/* WARNING: This is a generated file.\n * Any manual changes will be overwritten.\n\n */")
646
647 header.append('#ifndef '+outfilename.upper()+'_H_')
648 header.append('#define '+outfilename.upper()+'_H_')
649 header.append('#ifdef UA_NO_AMALGAMATION')
650 header.append( '#include "server/ua_server_internal.h"')
651 header.append( '#include "server/ua_nodes.h"')
652 header.append(' #include "ua_util.h"')
653 header.append(' #include "ua_types.h"')
654 header.append(' #include "ua_types_encoding_binary.h"')
655 header.append(' #include "ua_types_generated_encoding_binary.h"')
656 header.append(' #include "ua_transport_generated_encoding_binary.h"')
657 header.append('#else')
658 header.append(' #include "open62541.h"')
659 header.append('#endif')
660 header.append('')
661 header.append('/* Definition that (in userspace models) may be ')
662 header.append(' * - not included in the amalgamated header or')
663 header.append(' * - not part of public headers or')
664 header.append(' * - not exported in the shared object in combination with any of the above')
665 header.append(' * but are required for value encoding.')
666 header.append(' * NOTE: Userspace UA_(decode|encode)Binary /wo amalgamations requires UA_EXPORT to be appended to the appropriate definitions. */')
667 header.append('#ifndef UA_ENCODINGOFFSET_BINARY')
668 header.append('# define UA_ENCODINGOFFSET_BINARY 2')
669 header.append('#endif')
670 header.append('#ifndef NULL')
671 header.append(' #define NULL ((void *)0)')
672 header.append('#endif')
673 header.append('#ifndef UA_malloc')
674 header.append(' #define UA_malloc(_p_size) malloc(_p_size)')
675 header.append('#endif')
676 header.append('#ifndef UA_free')
677 header.append(' #define UA_free(_p_ptr) free(_p_ptr)')
678 header.append('#endif')
679
680 code.append('#include "'+outfilename+'.h"')
681 code.append("UA_INLINE UA_StatusCode "+outfilename+"(UA_Server *server) {")
682 code.append('UA_StatusCode retval = UA_STATUSCODE_GOOD; ')
683 code.append('if(retval == UA_STATUSCODE_GOOD){retval = UA_STATUSCODE_GOOD;} //ensure that retval is used');
684
685 # Before printing nodes, we need to request additional namespace arrays from the server
687 if nsid == 0 or nsid==1:
688 continue
689 else:
691 name = name.replace("\"","\\\"")
692 code.append("if (UA_Server_addNamespace(server, \"{0}\") != {1})\n return UA_STATUSCODE_BADUNEXPECTEDERROR;".format(name, nsid))
693
694 # Find all references necessary to create the namespace and
695 # "Bootstrap" them so all other nodes can safely use these referencetypes whenever
696 # they can locate both source and target of the reference.
697 logger.debug("Collecting all references used in the namespace.")
698 refsUsed = []
699 for n in self.nodesnodes:
700 # Since we are already looping over all nodes, use this chance to print NodeId defines
701 if n.id().ns != 0:
702 nc = n.nodeClass()
703 if nc != NODE_CLASS_OBJECT and nc != NODE_CLASS_VARIABLE and nc != NODE_CLASS_VIEW:
704 header = header + codegen.getNodeIdDefineString(n)
705
706 # Now for the actual references...
707 for r in n.getReferences():
708 # Only print valid references in namespace 0 (users will not want their refs bootstrapped)
709 if not r.referenceType() in refsUsed and r.referenceType() != None and r.referenceType().id().ns == 0:
710 refsUsed.append(r.referenceType())
711 logger.debug(str(len(refsUsed)) + " reference types are used in the namespace, which will now get bootstrapped.")
712 for r in refsUsed:
713 code = code + r.printOpen62541CCode(unPrintedNodes, unPrintedRefs);
714
715 header.append("extern UA_StatusCode "+outfilename+"(UA_Server *server);\n")
716 header.append("#endif /* "+outfilename.upper()+"_H_ */")
717
718 # Note to self: do NOT - NOT! - try to iterate over unPrintedNodes!
719 # Nodes remove themselves from this list when printed.
720 logger.debug("Printing all other nodes.")
721 for n in self.nodesnodes:
722 code = code + n.printOpen62541CCode(unPrintedNodes, unPrintedRefs, supressGenerationOfAttribute=supressGenerationOfAttribute)
723
724 if len(unPrintedNodes) != 0:
725 logger.warn("" + str(len(unPrintedNodes)) + " nodes could not be translated to code.")
726 else:
727 logger.debug("Printing suceeded for all nodes")
728
729 if len(unPrintedRefs) != 0:
730 logger.debug("Attempting to print " + str(len(unPrintedRefs)) + " unprinted references.")
731 tmprefs = []
732 for r in unPrintedRefs:
733 if not (r.target() not in unPrintedNodes) and not (r.parent() in unPrintedNodes):
734 if not isinstance(r.parent(), opcua_node_t):
735 logger.debug("Reference has no parent!")
736 elif not isinstance(r.parent().id(), opcua_node_id_t):
737 logger.debug("Parents nodeid is not a nodeID!")
738 else:
739 if (len(tmprefs) == 0):
740 code.append("// Creating leftover references:")
741 code = code + codegen.getCreateStandaloneReference(r.parent(), r)
742 code.append("")
743 tmprefs.append(r)
744 # Remove printed refs from list
745 for r in tmprefs:
746 unPrintedRefs.remove(r)
747 if len(unPrintedRefs) != 0:
748 logger.warn("" + str(len(unPrintedRefs)) + " references could not be translated to code.")
749 else:
750 logger.debug("Printing succeeded for all references")
751
752 code.append("return UA_STATUSCODE_GOOD;")
753 code.append("}")
754 return (header,code)
755
756
759
761 def __init__(self):
762 self.namespace = opcua_namespace("testing")
763
764 logger.debug("Phase 1: Reading XML file nodessets")
765 self.namespace.parseXML("Opc.Ua.NodeSet2.xml")
766 #self.namespace.parseXML("Opc.Ua.NodeSet2.Part4.xml")
767 #self.namespace.parseXML("Opc.Ua.NodeSet2.Part5.xml")
768 #self.namespace.parseXML("Opc.Ua.SimulationNodeSet2.xml")
769
770 logger.debug("Phase 2: Linking address space references and datatypes")
771 self.namespace.linkOpenPointers()
772 self.namespace.sanitize()
773
774 logger.debug("Phase 3: Comprehending DataType encoding rules")
775 self.namespace.buildEncodingRules()
776
777 logger.debug("Phase 4: Allocating variable value data")
778 self.namespace.allocateVariables()
779
780 bin = self.namespace.buildBinary()
781 f = open("binary.base64","w+")
782 f.write(bin.encode("base64"))
783 f.close()
784
785 allnodes = self.namespace.nodes;
786 ns = [self.namespace.getRoot()]
787
788 i = 0
789 #print "Starting depth search on " + str(len(allnodes)) + " nodes starting with from " + str(ns)
790 while (len(ns) < len(allnodes)):
791 i = i + 1;
792 tmp = [];
793 print("Iteration: " + str(i))
794 for n in ns:
795 tmp.append(n)
796 for r in n.getReferences():
797 if (not r.target() in tmp):
798 tmp.append(r.target())
799 print("...tmp, " + str(len(tmp)) + " nodes discovered")
800 ns = []
801 for n in tmp:
802 ns.append(n)
803 print("...done, " + str(len(ns)) + " nodes discovered")
804
805 logger.debug("Phase 5: Printing pretty graph")
806 self.namespace.printDotGraphWalk(depth=1, rootNode=self.namespace.getNodeByIDString("i=84"), followInverse=False, excludeNodeIds=["i=29", "i=22", "i=25"])
807 #self.namespace.printDot()
808
810 def __init__(self):
811 self.namespace = opcua_namespace("testing")
812
813 logger.debug("Phase 1: Reading XML file nodessets")
814 self.namespace.parseXML("Opc.Ua.NodeSet2.xml")
815 #self.namespace.parseXML("Opc.Ua.NodeSet2.Part4.xml")
816 #self.namespace.parseXML("Opc.Ua.NodeSet2.Part5.xml")
817 #self.namespace.parseXML("Opc.Ua.SimulationNodeSet2.xml")
818
819 logger.debug("Phase 2: Linking address space references and datatypes")
820 self.namespace.linkOpenPointers()
821 self.namespace.sanitize()
822
823 logger.debug("Phase 3: Calling C Printers")
824 code = self.namespace.printOpen62541Header()
825
826 codeout = open("./open62541_namespace.c", "w+")
827 for line in code:
828 codeout.write(line + "\n")
829 codeout.close()
830 return
831
832# Call testing routine if invoked standalone.
833# For better debugging, it is advised to import this file using an interactive
834# python shell and instantiating a namespace.
835#
836# import ua_types.py as ua; ns=ua.testing().namespace
837if __name__ == '__main__':
Namespace Organizer.
addNamespace(self, numericId, stringURL)
getBinaryIndirectPointerIndex(self, node)
buildAliasList(self, xmlelement)
registerBinaryIndirectPointer(self, node)
getSubTypesOf(self, tdNodes=None, currentNode=None, hasSubtypeRefNode=None)
printOpen62541Header(self, printedExternally=[], supressGenerationOfAttribute=[], outfilename="")
__reorder_getMinWeightNode__(self, nmatrix)
printDotGraphWalk(self, depth=1, filename="out.dot", rootNode=None, followInverse=False, excludeNodeIds=[])
getNodeByIDString(self, idstring)
createNode(self, ndtype, xmlelement)
printDot(self, filename="namespace.dot")
getNodeByBrowseName(self, idstring)
References are not really described by OPC-UA.
getNextElementNode(xmlvalue)
#define str(a)