Passed
Push — master ( f08c39...c2cd4c )
by Olivier
05:23 queued 02:32
created

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

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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