asyncua.common.node.Node.delete()   A
last analyzed

Complexity

Conditions 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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