Passed
Pull Request — master (#120)
by Olivier
02:26
created

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