Passed
Push — master ( 47920a...54c675 )
by Olivier
02:45
created

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

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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