Passed
Pull Request — master (#238)
by Olivier
02:17
created

asyncua.common.node.Node.get_description()   A

Complexity

Conditions 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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