Completed
Push — master ( 144955...e7a349 )
by Olivier
04:23
created

Node._make_relative_path()   A

Complexity

Conditions 3

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3.0052

Importance

Changes 0
Metric Value
cc 3
c 0
b 0
f 0
dl 0
loc 13
ccs 11
cts 12
cp 0.9167
crap 3.0052
rs 9.4285
1
"""
2
High level node object, to access node attribute
3
and browse address space
4
"""
5
6 1
from opcua import ua
7 1
from opcua.common import events
8 1
import opcua.common
9
10 1
def _check_results(results, reqlen = 1):
11 1
    assert len(results) == reqlen, results
12 1
    for r in results:
13 1
        r.check()
14
15 1
def _to_nodeid(nodeid):
16 1
    if isinstance(nodeid, int):
17 1
        return ua.TwoByteNodeId(nodeid)
18 1
    elif isinstance(nodeid, Node):
19 1
        return nodeid.nodeid
20
    elif isinstance(nodeid, ua.NodeId):
21
        return nodeid
22
    elif type(nodeid) in (str, bytes):
23
        return ua.NodeId.from_string(nodeid)
24
    else:
25
        raise ua.UaError("Could not resolve '{0}' to a type id".format(nodeid))
26
27 1
class Node(object):
28
29
    """
30
    High level node object, to access node attribute,
31
    browse and populate address space.
32
    Node objects are usefull as-is but they do not expose the entire
33
    OPC-UA protocol. Feel free to look at the code of this class and call
34
    directly UA services methods to optimize your code
35
    """
36
37 1
    def __init__(self, server, nodeid):
38 1
        self.server = server
39 1
        self.nodeid = None
40 1
        if isinstance(nodeid, Node):
41 1
            self.nodeid = nodeid.nodeid
42 1
        elif isinstance(nodeid, ua.NodeId):
43 1
            self.nodeid = nodeid
44 1
        elif type(nodeid) in (str, bytes):
45 1
            self.nodeid = ua.NodeId.from_string(nodeid)
46 1
        elif isinstance(nodeid, int):
47 1
            self.nodeid = ua.NodeId(nodeid, 0)
48
        else:
49
            raise ua.UaError("argument to node must be a NodeId object or a string defining a nodeid found {0} of type {1}".format(nodeid, type(nodeid)))
50
51 1
    def __eq__(self, other):
52 1
        if isinstance(other, Node) and self.nodeid == other.nodeid:
53 1
            return True
54 1
        return False
55
56 1
    def __ne__(self, other):
57
        return not self.__eq__(other)
58
59 1
    def __str__(self):
60 1
        return "Node({0})".format(self.nodeid)
61 1
    __repr__ = __str__
62
63 1
    def __hash__(self):
64 1
        return self.nodeid.__hash__()
65
66 1
    def get_browse_name(self):
67
        """
68
        Get browse name of a node. A browse name is a QualifiedName object
69
        composed of a string(name) and a namespace index.
70
        """
71 1
        result = self.get_attribute(ua.AttributeIds.BrowseName)
72 1
        return result.Value.Value
73
74 1
    def get_display_name(self):
75
        """
76
        get description attribute of node
77
        """
78 1
        result = self.get_attribute(ua.AttributeIds.DisplayName)
79 1
        return result.Value.Value
80
81 1
    def get_data_type(self):
82
        """
83
        get data type of node as NodeId
84
        """
85 1
        result = self.get_attribute(ua.AttributeIds.DataType)
86 1
        return result.Value.Value
87
88 1
    def get_data_type_as_variant_type(self):
89
        """
90
        get data type of node as VariantType
91
        This only works if node is a variable, otherwise type
92
        may not be convertible to VariantType
93
        """
94 1
        result = self.get_attribute(ua.AttributeIds.DataType)
95 1
        return opcua.common.ua_utils.data_type_to_variant_type(Node(self.server, result.Value.Value))
96
97 1
    def get_access_level(self):
98
        """
99
        Get the access level attribute of the node as a set of AccessLevel enum values.
100
        """
101
        result = self.get_attribute(ua.AttributeIds.AccessLevel)
102
        return ua.AccessLevel.parse_bitfield(result.Value.Value)
103
104 1
    def get_user_access_level(self):
105
        """
106
        Get the user access level attribute of the node as a set of AccessLevel enum values.
107
        """
108
        result = self.get_attribute(ua.AttributeIds.UserAccessLevel)
109
        return ua.AccessLevel.parse_bitfield(result.Value.Value)
110
111 1
    def get_event_notifier(self):
112
        """
113
        Get the event notifier attribute of the node as a set of EventNotifier enum values.
114
        """
115 1
        result = self.get_attribute(ua.AttributeIds.EventNotifier)
116 1
        return ua.EventNotifier.parse_bitfield(result.Value.Value)
117
118 1
    def set_event_notifier(self, values):
119
        """
120
        Set the event notifier attribute.
121
122
        :param values: an iterable of EventNotifier enum values.
123
        """
124 1
        event_notifier_bitfield = ua.EventNotifier.to_bitfield(values)
125 1
        self.set_attribute(ua.AttributeIds.EventNotifier, ua.DataValue(ua.Variant(event_notifier_bitfield, ua.VariantType.Byte)))
126
127 1
    def get_node_class(self):
128
        """
129
        get node class attribute of node
130
        """
131 1
        result = self.get_attribute(ua.AttributeIds.NodeClass)
132 1
        return result.Value.Value
133
134 1
    def get_description(self):
135
        """
136
        get description attribute class of node
137
        """
138 1
        result = self.get_attribute(ua.AttributeIds.Description)
139 1
        return result.Value.Value
140
141 1
    def get_value(self):
142
        """
143
        Get value of a node as a python type. Only variables ( and properties) have values.
144
        An exception will be generated for other node types.
145
        WARNING: on server side, this function returns a ref to object in ua database. Do not modify it if it is a mutable
146
        object unless you know what you are doing
147
        """
148 1
        result = self.get_data_value()
149 1
        return result.Value.Value
150
151 1
    def get_data_value(self):
152
        """
153
        Get value of a node as a DataValue object. Only variables (and properties) have values.
154
        An exception will be generated for other node types.
155
        DataValue contain a variable value as a variant as well as server and source timestamps
156
        """
157 1
        return self.get_attribute(ua.AttributeIds.Value)
158
159 1
    def set_array_dimensions(self, value):
160
        """
161
        Set attribute ArrayDimensions of node
162
        make sure it has the correct data type
163
        """
164 1
        v = ua.Variant(value, ua.VariantType.UInt32)
165 1
        self.set_attribute(ua.AttributeIds.ArrayDimensions, ua.DataValue(v))
166
167 1
    def get_array_dimensions(self):
168
        """
169
        Read and return ArrayDimensions attribute of node
170
        """
171 1
        res = self.get_attribute(ua.AttributeIds.ArrayDimensions)
172 1
        return res.Value.Value
173
174 1
    def set_value_rank(self, value):
175
        """
176
        Set attribute ArrayDimensions of node
177
        """
178 1
        v = ua.Variant(value, ua.VariantType.Int32)
179 1
        self.set_attribute(ua.AttributeIds.ValueRank, ua.DataValue(v))
180
181 1
    def get_value_rank(self):
182
        """
183
        Read and return ArrayDimensions attribute of node
184
        """
185 1
        res = self.get_attribute(ua.AttributeIds.ValueRank)
186 1
        return res.Value.Value
187
188 1
    def set_value(self, value, varianttype=None):
189
        """
190
        Set value of a node. Only variables(properties) have values.
191
        An exception will be generated for other node types.
192
        value argument is either:
193
        * a python built-in type, converted to opc-ua
194
        optionnaly using the variantype argument.
195
        * a ua.Variant, varianttype is then ignored
196
        * a ua.DataValue, you then have full control over data send to server
197
        WARNING: On server side, ref to object is directly saved in our UA db, if this is a mutable object
198
        and you modfy it afterward, then the object in db will be modified without any
199
        data change event generated
200
        """
201 1
        datavalue = None
202 1
        if isinstance(value, ua.DataValue):
203 1
            datavalue = value
204 1
        elif isinstance(value, ua.Variant):
205 1
            datavalue = ua.DataValue(value)
206
        else:
207 1
            datavalue = ua.DataValue(ua.Variant(value, varianttype))
208 1
        self.set_attribute(ua.AttributeIds.Value, datavalue)
209
210 1
    set_data_value = set_value
211
212 1
    def set_writable(self, writable=True):
213
        """
214
        Set node as writable by clients.
215
        A node is always writable on server side.
216
        """
217 1
        if writable:
218 1
            self.set_attr_bit(ua.AttributeIds.AccessLevel, ua.AccessLevel.CurrentWrite)
219 1
            self.set_attr_bit(ua.AttributeIds.UserAccessLevel, ua.AccessLevel.CurrentWrite)
220
        else:
221 1
            self.unset_attr_bit(ua.AttributeIds.AccessLevel, ua.AccessLevel.CurrentWrite)
222 1
            self.unset_attr_bit(ua.AttributeIds.UserAccessLevel, ua.AccessLevel.CurrentWrite)
223
224 1
    def set_attr_bit(self, attr, bit):
225 1
        val = self.get_attribute(attr)
226 1
        val.Value.Value = ua.ua_binary.set_bit(val.Value.Value, bit)
227 1
        self.set_attribute(attr, val)
228
229 1
    def unset_attr_bit(self, attr, bit):
230 1
        val = self.get_attribute(attr)
231 1
        val.Value.Value = ua.ua_binary.unset_bit(val.Value.Value, bit)
232 1
        self.set_attribute(attr, val)
233
234 1
    def set_read_only(self):
235
        """
236
        Set a node as read-only for clients.
237
        A node is always writable on server side.
238
        """
239
        return self.set_writable(False)
240
241 1
    def set_attribute(self, attributeid, datavalue):
242
        """
243
        Set an attribute of a node
244
        attributeid is a member of ua.AttributeIds
245
        datavalue is a ua.DataValue object
246
        """
247 1
        attr = ua.WriteValue()
248 1
        attr.NodeId = self.nodeid
249 1
        attr.AttributeId = attributeid
250 1
        attr.Value = datavalue
251 1
        params = ua.WriteParameters()
252 1
        params.NodesToWrite = [attr]
253 1
        result = self.server.write(params)
254 1
        result[0].check()
255
256 1
    def get_attribute(self, attr):
257
        """
258
        Read one attribute of a node
259
        result code from server is checked and an exception is raised in case of error
260
        """
261 1
        rv = ua.ReadValueId()
262 1
        rv.NodeId = self.nodeid
263 1
        rv.AttributeId = attr
264 1
        params = ua.ReadParameters()
265 1
        params.NodesToRead.append(rv)
266 1
        result = self.server.read(params)
267 1
        result[0].StatusCode.check()
268 1
        return result[0]
269
270 1
    def get_attributes(self, attrs):
271
        """
272
        Read several attributes of a node
273
        list of DataValue is returned
274
        """
275 1
        params = ua.ReadParameters()
276 1
        for attr in attrs:
277 1
            rv = ua.ReadValueId()
278 1
            rv.NodeId = self.nodeid
279 1
            rv.AttributeId = attr
280 1
            params.NodesToRead.append(rv)
281
282 1
        results = self.server.read(params)
283 1
        return results
284
285 1
    def get_children(self, refs=ua.ObjectIds.HierarchicalReferences, nodeclassmask=ua.NodeClass.Unspecified):
286
        """
287
        Get all children of a node. By default hierarchical references and all node classes are returned.
288
        Other reference types may be given:
289
        References = 31
290
        NonHierarchicalReferences = 32
291
        HierarchicalReferences = 33
292
        HasChild = 34
293
        Organizes = 35
294
        HasEventSource = 36
295
        HasModellingRule = 37
296
        HasEncoding = 38
297
        HasDescription = 39
298
        HasTypeDefinition = 40
299
        GeneratesEvent = 41
300
        Aggregates = 44
301
        HasSubtype = 45
302
        HasProperty = 46
303
        HasComponent = 47
304
        HasNotifier = 48
305
        HasOrderedComponent = 49
306
        """
307 1
        return self.get_referenced_nodes(refs, ua.BrowseDirection.Forward, nodeclassmask)
308
309 1
    def get_properties(self):
310
        """
311
        return properties of node.
312
        properties are child nodes with a reference of type HasProperty and a NodeClass of Variable
313
        """
314 1
        return self.get_children(refs=ua.ObjectIds.HasProperty, nodeclassmask=ua.NodeClass.Variable)
315
316 1
    def get_variables(self):
317
        """
318
        return variables of node.
319
        properties are child nodes with a reference of type HasComponent and a NodeClass of Variable
320
        """
321 1
        return self.get_children(refs=ua.ObjectIds.HasComponent, nodeclassmask=ua.NodeClass.Variable)
322
323 1
    def get_methods(self):
324
        """
325
        return methods of node.
326
        properties are child nodes with a reference of type HasComponent and a NodeClass of Method
327
        """
328 1
        return self.get_children(refs=ua.ObjectIds.HasComponent, nodeclassmask=ua.NodeClass.Method)
329
330 1
    def get_children_descriptions(self, refs=ua.ObjectIds.HierarchicalReferences, nodeclassmask=ua.NodeClass.Unspecified, includesubtypes=True):
331 1
        return self.get_references(refs, ua.BrowseDirection.Forward, nodeclassmask, includesubtypes)
332
333 1
    def get_encoding_refs(self):
334
        return self.get_referenced_nodes(ua.ObjectIds.HasEncoding, ua.BrowseDirection.Forward)
335
336 1
    def get_description_refs(self):
337
        return self.get_referenced_nodes(ua.ObjectIds.HasDescription, ua.BrowseDirection.Forward)
338
339 1
    def get_references(self, refs=ua.ObjectIds.References, direction=ua.BrowseDirection.Both, nodeclassmask=ua.NodeClass.Unspecified, includesubtypes=True):
340
        """
341
        returns references of the node based on specific filter defined with:
342
343
        refs = ObjectId of the Reference
344
        direction = Browse direction for references
345
        nodeclassmask = filter nodes based on specific class
346
        includesubtypes = If true subtypes of the reference (ref) are also included
347
        """
348 1
        desc = ua.BrowseDescription()
349 1
        desc.BrowseDirection = direction
350 1
        desc.ReferenceTypeId = _to_nodeid(refs)
351 1
        desc.IncludeSubtypes = includesubtypes
352 1
        desc.NodeClassMask = nodeclassmask
353 1
        desc.ResultMask = ua.BrowseResultMask.All
354
355 1
        desc.NodeId = self.nodeid
356 1
        params = ua.BrowseParameters()
357 1
        params.View.Timestamp = ua.get_win_epoch()
358 1
        params.NodesToBrowse.append(desc)
359 1
        params.RequestedMaxReferencesPerNode = 0
360 1
        results = self.server.browse(params)
361
362 1
        references = self._browse_next(results)
363 1
        return references
364
365 1
    def _browse_next(self, results):
366 1
        references = results[0].References
367 1
        while results[0].ContinuationPoint:
368
            params = ua.BrowseNextParameters()
369
            params.ContinuationPoints = [results[0].ContinuationPoint]
370
            params.ReleaseContinuationPoints = False
371
            results = self.server.browse_next(params)
372
            references.extend(results[0].References)
373 1
        return references
374
375 1
    def get_referenced_nodes(self, refs=ua.ObjectIds.References, direction=ua.BrowseDirection.Both, nodeclassmask=ua.NodeClass.Unspecified, includesubtypes=True):
376
        """
377
        returns referenced nodes based on specific filter
378
        Paramters are the same as for get_references
379
380
        """
381 1
        references = self.get_references(refs, direction, nodeclassmask, includesubtypes)
382 1
        nodes = []
383 1
        for desc in references:
384 1
            node = Node(self.server, desc.NodeId)
385 1
            nodes.append(node)
386 1
        return nodes
387
388 1
    def get_type_definition(self):
389
        """
390
        returns type definition of the node.
391
        """
392 1
        references = self.get_references(refs=ua.ObjectIds.HasTypeDefinition, direction=ua.BrowseDirection.Forward)
393 1
        if len(references) == 0:
394 1
            return None
395 1
        return references[0].NodeId
396
397 1
    def get_path(self, max_length=20, as_string=False):
398
        """
399
        Attempt to find path of node from root node and return it as a list of Nodes.
400
        There might several possible paths to a node, this function will return one
401
        Some nodes may be missing references, so this method may
402
        return an empty list
403
        Since address space may have circular references, a max length is specified
404
405
        """
406 1
        path = self._get_path(max_length)
407 1
        path = [Node(self.server, ref.NodeId) for ref in path]
408 1
        path.append(self)
409 1
        if as_string:
410 1
            path = [el.get_browse_name().to_string() for el in path]
411 1
        return path
412
413 1
    def _get_path(self, max_length=20):
414
        """
415
        Attempt to find path of node from root node and return it as a list of Nodes.
416
        There might several possible paths to a node, this function will return one
417
        Some nodes may be missing references, so this method may
418
        return an empty list
419
        Since address space may have circular references, a max length is specified
420
421
        """
422 1
        path = []
423 1
        node = self
424 1
        while True:
425 1
            refs = node.get_references(refs=ua.ObjectIds.HierarchicalReferences, direction=ua.BrowseDirection.Inverse)
426 1
            if len(refs) > 0:
427 1
                path.insert(0, refs[0])
428 1
                node = Node(self.server, refs[0].NodeId)
429 1
                if len(path) >= (max_length -1):
430 1
                    return path
431
            else:
432 1
                return path
433
434 1
    def get_parent(self):
435
        """
436
        returns parent of the node.
437
        A Node may have several parents, the first found is returned.
438
        This method uses reverse references, a node might be missing such a link,
439
        thus we will not find its parent.
440
        """
441 1
        refs = self.get_references(refs=ua.ObjectIds.HierarchicalReferences, direction=ua.BrowseDirection.Inverse)
442 1
        if len(refs) > 0:
443 1
            return Node(self.server, refs[0].NodeId)
444
        else:
445
            return None
446
447 1
    def get_child(self, path):
448
        """
449
        get a child specified by its path from this node.
450
        A path might be:
451
        * a string representing a qualified name.
452
        * a qualified name
453
        * a list of string
454
        * a list of qualified names
455
        """
456 1
        if type(path) not in (list, tuple):
457 1
            path = [path]
458 1
        rpath = self._make_relative_path(path)
459 1
        bpath = ua.BrowsePath()
460 1
        bpath.StartingNode = self.nodeid
461 1
        bpath.RelativePath = rpath
462 1
        result = self.server.translate_browsepaths_to_nodeids([bpath])
463 1
        result = result[0]
464 1
        result.StatusCode.check()
465
        # FIXME: seems this method may return several nodes
466 1
        return Node(self.server, result.Targets[0].TargetId)
467
468 1
    def _make_relative_path(self, path):
469 1
        rpath = ua.RelativePath()
470 1
        for item in path:
471 1
            el = ua.RelativePathElement()
472 1
            el.ReferenceTypeId = ua.TwoByteNodeId(ua.ObjectIds.HierarchicalReferences)
473 1
            el.IsInverse = False
474 1
            el.IncludeSubtypes = True
475 1
            if isinstance(item, ua.QualifiedName):
476
                el.TargetName = item
477
            else:
478 1
                el.TargetName = ua.QualifiedName.from_string(item)
479 1
            rpath.Elements.append(el)
480 1
        return rpath
481
482 1
    def read_raw_history(self, starttime=None, endtime=None, numvalues=0):
483
        """
484
        Read raw history of a node
485
        result code from server is checked and an exception is raised in case of error
486
        If numvalues is > 0 and number of events in period is > numvalues
487
        then result will be truncated
488
        """
489 1
        details = ua.ReadRawModifiedDetails()
490 1
        details.IsReadModified = False
491 1
        if starttime:
492 1
            details.StartTime = starttime
493
        else:
494 1
            details.StartTime = ua.get_win_epoch()
495 1
        if endtime:
496 1
            details.EndTime = endtime
497
        else:
498 1
            details.EndTime = ua.get_win_epoch()
499 1
        details.NumValuesPerNode = numvalues
500 1
        details.ReturnBounds = True
501 1
        result = self.history_read(details)
502 1
        return result.HistoryData.DataValues
503
504 1 View Code Duplication
    def history_read(self, details):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
505
        """
506
        Read raw history of a node, low-level function
507
        result code from server is checked and an exception is raised in case of error
508
        """
509 1
        valueid = ua.HistoryReadValueId()
510 1
        valueid.NodeId = self.nodeid
511 1
        valueid.IndexRange = ''
512
513 1
        params = ua.HistoryReadParameters()
514 1
        params.HistoryReadDetails = details
515 1
        params.TimestampsToReturn = ua.TimestampsToReturn.Both
516 1
        params.ReleaseContinuationPoints = False
517 1
        params.NodesToRead.append(valueid)
518 1
        result = self.server.history_read(params)[0]
519 1
        return result
520
521 1
    def read_event_history(self, starttime=None, endtime=None, numvalues=0, evtypes=ua.ObjectIds.BaseEventType):
522
        """
523
        Read event history of a source node
524
        result code from server is checked and an exception is raised in case of error
525
        If numvalues is > 0 and number of events in period is > numvalues
526
        then result will be truncated
527
        """
528
529 1
        details = ua.ReadEventDetails()
530 1
        if starttime:
531 1
            details.StartTime = starttime
532
        else:
533 1
            details.StartTime = ua.get_win_epoch()
534 1
        if endtime:
535 1
            details.EndTime = endtime
536
        else:
537 1
            details.EndTime = ua.get_win_epoch()
538 1
        details.NumValuesPerNode = numvalues
539
540 1
        if not isinstance(evtypes, (list, tuple)):
541 1
            evtypes = [evtypes]
542
543 1
        evtypes = [Node(self.server, evtype) for evtype in evtypes]
544
545 1
        evfilter = events.get_filter_from_event_type(evtypes)
546 1
        details.Filter = evfilter
547
548 1
        result = self.history_read_events(details)
549 1
        event_res = []
550 1
        for res in result.HistoryData.Events:
551 1
            event_res.append(events.Event.from_event_fields(evfilter.SelectClauses, res.EventFields))
552 1
        return event_res
553
554 1 View Code Duplication
    def history_read_events(self, details):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
555
        """
556
        Read event history of a node, low-level function
557
        result code from server is checked and an exception is raised in case of error
558
        """
559 1
        valueid = ua.HistoryReadValueId()
560 1
        valueid.NodeId = self.nodeid
561 1
        valueid.IndexRange = ''
562
563 1
        params = ua.HistoryReadParameters()
564 1
        params.HistoryReadDetails = details
565 1
        params.TimestampsToReturn = ua.TimestampsToReturn.Both
566 1
        params.ReleaseContinuationPoints = False
567 1
        params.NodesToRead.append(valueid)
568 1
        result = self.server.history_read(params)[0]
569 1
        return result
570
571 1
    def delete(self, delete_references=True, recursive=False):
572
        """
573
        Delete node from address space
574
        """
575
        results = opcua.common.manage_nodes.delete_nodes(self.server, [self], recursive, delete_references)
576
        _check_results(results)
577
578 1
    def _fill_delete_reference_item(self, rdesc, bidirectional = False):
579 1
        ditem = ua.DeleteReferencesItem()
580 1
        ditem.SourceNodeId = self.nodeid
581 1
        ditem.TargetNodeId = rdesc.NodeId
582 1
        ditem.ReferenceTypeId = rdesc.ReferenceTypeId
583 1
        ditem.IsForward = rdesc.IsForward
584 1
        ditem.DeleteBidirectional = bidirectional
585 1
        return ditem
586
587 1
    def delete_reference(self, target, reftype, forward=True, bidirectional=True):
588
        """
589
        Delete given node's references from address space
590
        """
591 1
        known_refs = self.get_references(reftype, includesubtypes=False)
592 1
        targetid = _to_nodeid(target)
593
594 1
        for r in known_refs:
595 1
            if r.NodeId == targetid and r.IsForward == forward:
596 1
                rdesc = r
597 1
                break
598
        else:
599 1
            raise ua.UaStatusCodeError(ua.StatusCodes.BadNotFound)
600
601 1
        ditem = self._fill_delete_reference_item(rdesc, bidirectional)
602 1
        self.server.delete_references([ditem])[0].check()
603
604 1
    def add_reference(self, target, reftype, forward=True, bidirectional=True):
605
        """
606
        Add reference to node
607
        """
608
609 1
        aitem = ua.AddReferencesItem()
610 1
        aitem.SourceNodeId = self.nodeid
611 1
        aitem.TargetNodeId = _to_nodeid(target)
612 1
        aitem.ReferenceTypeId = _to_nodeid(reftype)
613 1
        aitem.IsForward = forward
614
615 1
        params = [aitem]
616
617 1
        if bidirectional:
618 1
            aitem2 = ua.AddReferencesItem()
619 1
            aitem2.SourceNodeId = aitem.TargetNodeId
620 1
            aitem2.TargetNodeId = aitem.SourceNodeId
621 1
            aitem2.ReferenceTypeId = aitem.ReferenceTypeId
622 1
            aitem2.IsForward = not forward
623 1
            params.append(aitem2)
624
625 1
        results = self.server.add_references(params)
626 1
        _check_results(results, len(params))
627
628 1
    def set_modelling_rule(self, mandatory):
629
        """
630
        Add a modelling rule reference to Node.
631
        When creating a new object type, its variable and child nodes will not
632
        be instanciated if they do not have modelling rule
633
        if mandatory is None, the modelling rule is removed
634
        """
635
        # remove all existing modelling rule
636 1
        rules = self.get_references(ua.ObjectIds.HasModellingRule)
637 1
        self.server.delete_references(list(map(self._fill_delete_reference_item, rules)))
638
        # add new modelling rule as requested
639 1
        if mandatory is not None:
640 1
            rule = ua.ObjectIds.ModellingRule_Mandatory if mandatory else ua.ObjectIds.ModellingRule_Optional
641 1
            self.add_reference(rule, ua.ObjectIds.HasModellingRule, True, False)
642
643 1
    def add_folder(self, nodeid, bname):
644 1
        return  opcua.common.manage_nodes.create_folder(self, nodeid, bname)
645
646 1
    def add_object(self, nodeid, bname, objecttype=None):
647 1
        return opcua.common.manage_nodes.create_object(self, nodeid, bname, objecttype)
648
649 1
    def add_variable(self, nodeid, bname, val, varianttype=None, datatype=None):
650 1
        return opcua.common.manage_nodes.create_variable(self, nodeid, bname, val, varianttype, datatype)
651
652 1
    def add_object_type(self, nodeid, bname):
653 1
        return opcua.common.manage_nodes.create_object_type(self, nodeid, bname)
654
655 1
    def add_variable_type(self, nodeid, bname, datatype):
656
        return opcua.common.manage_nodes.create_variable_type(self, nodeid, bname, datatype)
657
658 1
    def add_data_type(self, nodeid, bname, description=None):
659 1
        return opcua.common.manage_nodes.create_data_type(self, nodeid, bname, description=None)
660
661 1
    def add_property(self, nodeid, bname, val, varianttype=None, datatype=None):
662 1
        return opcua.common.manage_nodes.create_property(self, nodeid, bname, val, varianttype, datatype)
663
664 1
    def add_method(self, *args):
665 1
        return opcua.common.manage_nodes.create_method(self, *args)
666
667 1
    def add_reference_type(self, nodeid, bname, symmetric=True, inversename=None):
668 1
        return opcua.common.manage_nodes.create_reference_type(self, nodeid, bname, symmetric, inversename)
669
670 1
    def call_method(self, methodid, *args):
671
        return opcua.common.methods.call_method(self, methodid, *args)
672