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

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