Passed
Pull Request — master (#210)
by
unknown
02:15
created

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

Complexity

Conditions 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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