Completed
Push — master ( 4bbb48...d16ca4 )
by Olivier
11:53 queued 09:32
created

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

Complexity

Conditions 1

Size

Total Lines 7
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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