Completed
Pull Request — master (#58)
by
unknown
04:11
created

opcua.Node.read_raw_history()   A

Complexity

Conditions 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 9.561
Metric Value
dl 0
loc 14
ccs 1
cts 10
cp 0.1
rs 9.4286
cc 3
crap 9.561
1
"""
2
High level node object, to access node attribute
3
and browse address space
4
"""
5
6 1
from opcua import ua
7 1
from datetime import datetime
8
9
10 1
def create_folder(parent, *args):
11
    """
12
    create a child node folder
13
    arguments are nodeid, browsename
14
    or namespace index, name
15
    """
16 1
    nodeid, qname = _parse_add_args(*args)
17 1
    return Node(parent.server, _create_folder(parent.server, parent.nodeid, nodeid, qname))
18
19
20 1
def create_object(parent, *args):
21
    """
22
    create a child node object
23
    arguments are nodeid, browsename
24
    or namespace index, name
25
    """
26 1
    nodeid, qname = _parse_add_args(*args)
27 1
    return Node(parent.server, _create_object(parent.server, parent.nodeid, nodeid, qname))
28
29
30 1
def create_property(parent, *args):
31
    """
32
    create a child node property
33
    args are nodeid, browsename, value, [variant type]
34
    or idx, name, value, [variant type]
35
    """
36 1
    nodeid, qname = _parse_add_args(*args[:2])
37 1
    val = _to_variant(*args[2:])
38 1
    return Node(parent.server, _create_variable(parent.server, parent.nodeid, nodeid, qname, val, isproperty=True))
39
40
41 1
def create_variable(parent, *args):
42
    """
43
    create a child node variable
44
    args are nodeid, browsename, value, [variant type]
45
    or idx, name, value, [variant type]
46
    """
47 1
    nodeid, qname = _parse_add_args(*args[:2])
48 1
    val = _to_variant(*args[2:])
49 1
    return Node(parent.server, _create_variable(parent.server, parent.nodeid, nodeid, qname, val, isproperty=False))
50
51
52 1
def create_method(parent, *args):
53
    """
54
    create a child method object
55
    This is only possible on server side!!
56
    args are nodeid, browsename, method_to_be_called, [input argument types], [output argument types]
57
    or idx, name, method_to_be_called, [input argument types], [output argument types]
58
    if argument types is specified, child nodes advertising what arguments the method uses and returns will be created
59
    a callback is a method accepting the nodeid of the parent as first argument and variants after. returns a list of variants
60
    """
61 1
    nodeid, qname = _parse_add_args(*args[:2])
62 1
    callback = args[2]
63 1
    if len(args) > 3:
64 1
        inputs = args[3]
65 1
    if len(args) > 4:
66 1
        outputs = args[4]
67 1
    return _create_method(parent, nodeid, qname, callback, inputs, outputs)
68
69
70 1
def call_method(parent, methodid, *args):
71
    """
72
    Call an OPC-UA method. methodid is browse name of child method or the
73
    nodeid of method as a NodeId object
74
    arguments are variants or python object convertible to variants.
75
    which may be of different types
76
    returns a list of variants which are output of the method
77
    """
78 1
    if isinstance(methodid, str):
79 1
        methodid = parent.get_child(methodid).nodeid
80 1
    elif isinstance(methodid, Node):
81 1
        methodid = methodid.nodeid
82
83 1
    arguments = []
84 1
    for arg in args:
85 1
        if not isinstance(arg, ua.Variant):
86 1
            arg = ua.Variant(arg)
87 1
        arguments.append(arg)
88
89 1
    result = _call_method(parent.server, parent.nodeid, methodid, arguments)
90
91 1
    if len(result.OutputArguments) == 0:
92
        return None
93 1
    elif len(result.OutputArguments) == 1:
94 1
        return result.OutputArguments[0].Value
95
    else:
96
        return [var.Value for var in result.OutputArguments]
97
98
99 1
class Node(object):
100
101
    """
102
    High level node object, to access node attribute,
103
    browse and populate address space.
104
    Node objects are usefull as-is but they do not expose the entire possibilities of the OPC-UA protocol. Feel free to look at Node code and
105
    """
106
107 1
    def __init__(self, server, nodeid):
108 1
        self.server = server
109 1
        self.nodeid = None
110 1
        if isinstance(nodeid, ua.NodeId):
111 1
            self.nodeid = nodeid
112
        elif type(nodeid) in (str, bytes):
113
            self.nodeid = ua.NodeId.from_string(nodeid)
114
        elif isinstance(nodeid, int):
115
            self.nodeid = ua.NodeId(nodeid, 0)
116
        else:
117
            raise Exception("argument to node must be a NodeId object or a string defining a nodeid found {} of type {}".format(nodeid, type(nodeid)))
118
119 1
    def __eq__(self, other):
120 1
        if isinstance(other, Node) and self.nodeid == other.nodeid:
121 1
            return True
122 1
        return False
123
124 1
    def __str__(self):
125
        return "Node({})".format(self.nodeid)
126 1
    __repr__ = __str__
127
128 1
    def get_browse_name(self):
129
        """
130
        Get browse name of a node. A browse name is a QualifiedName object
131
        composed of a string(name) and a namespace index.
132
        """
133 1
        result = self.get_attribute(ua.AttributeIds.BrowseName)
134 1
        return result.Value.Value
135
136 1
    def get_display_name(self):
137
        """
138
        get description attribute of node
139
        """
140 1
        result = self.get_attribute(ua.AttributeIds.DisplayName)
141 1
        return result.Value.Value
142
143 1
    def get_data_type(self):
144
        """
145
        get data type of node
146
        """
147 1
        result = self.get_attribute(ua.AttributeIds.DataType)
148 1
        return result.Value.Value
149
150 1
    def get_node_class(self):
151
        """
152
        get node class attribute of node
153
        """
154 1
        result = self.get_attribute(ua.AttributeIds.NodeClass)
155 1
        return result.Value.Value
156
157 1
    def get_description(self):
158
        """
159
        get description attribute class of node
160
        """
161 1
        result = self.get_attribute(ua.AttributeIds.Description)
162 1
        return result.Value.Value
163
164 1
    def get_value(self):
165
        """
166
        Get value of a node as a python type. Only variables ( and properties) have values.
167
        An exception will be generated for other node types.
168
        """
169 1
        result = self.get_data_value()
170 1
        return result.Value.Value
171
172 1
    def get_data_value(self):
173
        """
174
        Get value of a node as a DataValue object. Only variables (and properties) have values.
175
        An exception will be generated for other node types.
176
        DataValue contain a variable value as a variant as well as server and source timestamps
177
        """
178 1
        return self.get_attribute(ua.AttributeIds.Value)
179
180 1
    def set_value(self, value, varianttype=None):
181
        """
182
        Set value of a node. Only variables(properties) have values.
183
        An exception will be generated for other node types.
184
        value argument is either:
185
        * a python built-in type, converted to opc-ua
186
        optionnaly using the variantype argument.
187
        * a ua.Variant, varianttype is then ignored
188
        * a ua.DataValue, you then have full control over data send to server
189
        """
190 1
        datavalue = None
191 1
        if isinstance(value, ua.DataValue):
192 1
            datavalue = value
193 1
        elif isinstance(value, ua.Variant):
194 1
            datavalue = ua.DataValue(value)
195
        else:
196 1
            datavalue = ua.DataValue(ua.Variant(value, varianttype))
197 1
        self.set_attribute(ua.AttributeIds.Value, datavalue)
198
199 1
    set_data_value = set_value
200
201 1
    def set_writable(self, writable=True):
202
        """
203
        Set node as writable by clients.
204
        A node is always writable on server side.
205
        """
206 1
        if writable:
207 1
            self.set_attribute(ua.AttributeIds.AccessLevel, ua.DataValue(ua.Variant(ua.AccessLevelMask.CurrentWrite, ua.VariantType.Byte)))
208 1
            self.set_attribute(ua.AttributeIds.UserAccessLevel, ua.DataValue(ua.Variant(ua.AccessLevelMask.CurrentWrite, ua.VariantType.Byte)))
209
        else:
210 1
            self.set_attribute(ua.AttributeIds.AccessLevel, ua.DataValue(ua.Variant(ua.AccessLevelMask.CurrentRead, ua.VariantType.Byte)))
211 1
            self.set_attribute(ua.AttributeIds.AccessLevel, ua.DataValue(ua.Variant(ua.AccessLevelMask.CurrentRead, ua.VariantType.Byte)))
212
213 1
    def set_read_only(self):
214
        """
215
        Set a node as read-only for clients.
216
        A node is always writable on server side.
217
        """
218
        return self.set_writable(False)
219
220 1
    def set_attribute(self, attributeid, datavalue):
221
        """
222
        Set an attribute of a node
223
        """
224 1
        attr = ua.WriteValue()
225 1
        attr.NodeId = self.nodeid
226 1
        attr.AttributeId = attributeid
227 1
        attr.Value = datavalue
228 1
        params = ua.WriteParameters()
229 1
        params.NodesToWrite = [attr]
230 1
        result = self.server.write(params)
231 1
        result[0].check()
232
233 1
    def get_attribute(self, attr):
234
        """
235
        Read one attribute of a node
236
        result code from server is checked and an exception is raised in case of error
237
        """
238 1
        rv = ua.ReadValueId()
239 1
        rv.NodeId = self.nodeid
240 1
        rv.AttributeId = attr
241 1
        params = ua.ReadParameters()
242 1
        params.NodesToRead.append(rv)
243 1
        result = self.server.read(params)
244 1
        result[0].StatusCode.check()
245 1
        return result[0]
246
247 1
    def get_attributes(self, attrs):
248
        """
249
        Read several attributes of a node
250
        list of DataValue is returned
251
        """
252
        params = ua.ReadParameters()
253
        for attr in attrs:
254
            rv = ua.ReadValueId()
255
            rv.NodeId = self.nodeid
256
            rv.AttributeId = attr
257
            params.NodesToRead.append(rv)
258
259
        results = self.server.read(params)
260
        return results
261
262 1
    def get_children(self, refs=ua.ObjectIds.HierarchicalReferences, nodeclassmask=ua.NodeClass.Unspecified):
263
        """
264
        Get all children of a node. By default hierarchical references and all node classes are returned.
265
        Other reference types may be given:
266
        References = 31
267
        NonHierarchicalReferences = 32
268
        HierarchicalReferences = 33
269
        HasChild = 34
270
        Organizes = 35
271
        HasEventSource = 36
272
        HasModellingRule = 37
273
        HasEncoding = 38
274
        HasDescription = 39
275
        HasTypeDefinition = 40
276
        GeneratesEvent = 41
277
        Aggregates = 44
278
        HasSubtype = 45
279
        HasProperty = 46
280
        HasComponent = 47
281
        HasNotifier = 48
282
        HasOrderedComponent = 49
283
        """
284 1
        references = self.get_children_descriptions(refs, nodeclassmask)
285 1
        nodes = []
286 1
        for desc in references:
287 1
            node = Node(self.server, desc.NodeId)
288 1
            nodes.append(node)
289 1
        return nodes
290
291 1
    def get_properties(self):
292
        """
293
        return properties of node.
294
        properties are child nodes with a reference of type HasProperty and a NodeClass of Variable
295
        """
296
        return self.get_children(refs=ua.ObjectIds.HasProperty, nodeclassmask=ua.NodeClass.Variable)
297
298 1
    def get_children_descriptions(self, refs=ua.ObjectIds.HierarchicalReferences, nodeclassmask=ua.NodeClass.Unspecified, includesubtypes=True):
299
        """
300
        return all attributes of child nodes as UA BrowseResult structs
301
        """
302 1
        desc = ua.BrowseDescription()
303 1
        desc.BrowseDirection = ua.BrowseDirection.Forward
304 1
        desc.ReferenceTypeId = ua.TwoByteNodeId(refs)
305 1
        desc.IncludeSubtypes = includesubtypes
306 1
        desc.NodeClassMask = nodeclassmask
307 1
        desc.ResultMask = ua.BrowseResultMask.All
308
309 1
        desc.NodeId = self.nodeid
310 1
        params = ua.BrowseParameters()
311 1
        params.View.Timestamp = ua.win_epoch_to_datetime(0)
312 1
        params.NodesToBrowse.append(desc)
313 1
        results = self.server.browse(params)
314 1
        return results[0].References
315
316 1
    def get_child(self, path):
317
        """
318
        get a child specified by its path from this node.
319
        A path might be:
320
        * a string representing a qualified name.
321
        * a qualified name
322
        * a list of string
323
        * a list of qualified names
324
        """
325 1
        if type(path) not in (list, tuple):
326 1
            path = [path]
327 1
        rpath = ua.RelativePath()
328 1
        for item in path:
329 1
            el = ua.RelativePathElement()
330 1
            el.ReferenceTypeId = ua.TwoByteNodeId(ua.ObjectIds.HierarchicalReferences)
331 1
            el.IsInverse = False
332 1
            el.IncludeSubtypes = True
333 1
            if isinstance(item, ua.QualifiedName):
334
                el.TargetName = item
335
            else:
336 1
                el.TargetName = ua.QualifiedName.from_string(item)
337 1
            rpath.Elements.append(el)
338 1
        bpath = ua.BrowsePath()
339 1
        bpath.StartingNode = self.nodeid
340 1
        bpath.RelativePath = rpath
341 1
        result = self.server.translate_browsepaths_to_nodeids([bpath])
342 1
        result = result[0]
343 1
        result.StatusCode.check()
344
        # FIXME: seems this method may return several nodes
345 1
        return Node(self.server, result.Targets[0].TargetId)
346
347 1
    def read_raw_history(self, starttime=None, endtime=None, numvalues=0, returnbounds=True):
348
        """
349
        Read raw history of a node
350
        result code from server is checked and an exception is raised in case of error
351
        """
352
        details = ua.ReadRawModifiedDetails()
353
        details.IsReadModified = False
354
        if starttime:
355
            details.StartTime = starttime
356
        if endtime:
357
            details.EndTime = endtime
358
        details.NumValuesPerNode = numvalues
359
        details.ReturnBounds = returnbounds
360
        return self.history_read(details)
361
362 1
    def history_read(self, details):
363
        """
364
        Read raw history of a node, low-level function
365
        result code from server is checked and an exception is raised in case of error
366
        """
367
        valueid = ua.HistoryReadValueId()
368
        valueid.NodeId = self.nodeid
369
        valueid.IndexRange = ''
370
371
        params = ua.HistoryReadParameters()
372
        params.HistoryReadDetails = ua.ExtensionObject.from_object(details)
373
        params.TimestampsToReturn = ua.TimestampsToReturn.Both
374
        params.ReleaseContinuationPoints = False
375
        params.NodesToRead.append(valueid)
376
        result = self.server.history_read(params)[0]
377
        return result.HistoryData
378
379
    # Convenience legacy methods
380 1
    add_folder = create_folder
381 1
    add_property = create_property
382 1
    add_object = create_object
383 1
    add_variable = create_variable
384 1
    add_method = create_method
385 1
    call_method = call_method
386
387
388 1
def _create_folder(server, parentnodeid, nodeid, qname):
389 1
    node = ua.AddNodesItem()
390 1
    node.RequestedNewNodeId = nodeid
391 1
    node.BrowseName = qname
392 1
    node.NodeClass = ua.NodeClass.Object
393 1
    node.ParentNodeId = parentnodeid
394 1
    node.ReferenceTypeId = ua.NodeId.from_string("i=35")
395 1
    node.TypeDefinition = ua.NodeId.from_string("i=61")
396 1
    attrs = ua.ObjectAttributes()
397 1
    attrs.Description = ua.LocalizedText(qname.Name)
398 1
    attrs.DisplayName = ua.LocalizedText(qname.Name)
399 1
    attrs.WriteMask = ua.OpenFileMode.Read
400 1
    attrs.UserWriteMask = ua.OpenFileMode.Read
401 1
    attrs.EventNotifier = 0
402 1
    node.NodeAttributes = attrs
403 1
    results = server.add_nodes([node])
404 1
    results[0].StatusCode.check()
405 1
    return nodeid
406
407
408 1
def _create_object(server, parentnodeid, nodeid, qname):
409 1
    node = ua.AddNodesItem()
410 1
    node.RequestedNewNodeId = nodeid
411 1
    node.BrowseName = qname
412 1
    node.NodeClass = ua.NodeClass.Object
413 1
    node.ParentNodeId = parentnodeid
414 1
    node.ReferenceTypeId = ua.NodeId.from_string("i=35")
415 1
    node.TypeDefinition = ua.NodeId(ua.ObjectIds.BaseObjectType)
416 1
    attrs = ua.ObjectAttributes()
417 1
    attrs.Description = ua.LocalizedText(qname.Name)
418 1
    attrs.DisplayName = ua.LocalizedText(qname.Name)
419 1
    attrs.EventNotifier = 0
420 1
    attrs.WriteMask = ua.OpenFileMode.Read
421 1
    attrs.UserWriteMask = ua.OpenFileMode.Read
422 1
    node.NodeAttributes = attrs
423 1
    results = server.add_nodes([node])
424 1
    results[0].StatusCode.check()
425 1
    return nodeid
426
427
428 1
def _to_variant(val, vtype=None):
429 1
    if isinstance(val, ua.Variant):
430 1
        return val
431
    else:
432 1
        return ua.Variant(val, vtype)
433
434
435 1
def _create_variable(server, parentnodeid, nodeid, qname, val, isproperty=False):
436 1
    node = ua.AddNodesItem()
437 1
    node.RequestedNewNodeId = nodeid
438 1
    node.BrowseName = qname
439 1
    node.NodeClass = ua.NodeClass.Variable
440 1
    node.ParentNodeId = parentnodeid
441 1
    if isproperty:
442 1
        node.ReferenceTypeId = ua.NodeId(ua.ObjectIds.HasProperty)
443 1
        node.TypeDefinition = ua.NodeId(ua.ObjectIds.PropertyType)
444
    else:
445 1
        node.ReferenceTypeId = ua.NodeId(ua.ObjectIds.HasComponent)
446 1
        node.TypeDefinition = ua.NodeId(ua.ObjectIds.BaseDataVariableType)
447 1
    attrs = ua.VariableAttributes()
448 1
    attrs.Description = ua.LocalizedText(qname.Name)
449 1
    attrs.DisplayName = ua.LocalizedText(qname.Name)
450 1
    attrs.DataType = _guess_uatype(val)
451 1
    attrs.Value = val
452 1
    attrs.ValueRank = 0
453 1
    attrs.WriteMask = ua.OpenFileMode.Read
454 1
    attrs.UserWriteMask = ua.OpenFileMode.Read
455 1
    attrs.Historizing = 0
456 1
    node.NodeAttributes = attrs
457 1
    results = server.add_nodes([node])
458 1
    results[0].StatusCode.check()
459 1
    return nodeid
460
461
462 1
def _create_method(parent, nodeid, qname, callback, inputs, outputs):
463 1
    node = ua.AddNodesItem()
464 1
    node.RequestedNewNodeId = nodeid
465 1
    node.BrowseName = qname
466 1
    node.NodeClass = ua.NodeClass.Method
467 1
    node.ParentNodeId = parent.nodeid
468 1
    node.ReferenceTypeId = ua.NodeId.from_string("i=47")
469
    #node.TypeDefinition = ua.NodeId(ua.ObjectIds.BaseObjectType)
470 1
    attrs = ua.MethodAttributes()
471 1
    attrs.Description = ua.LocalizedText(qname.Name)
472 1
    attrs.DisplayName = ua.LocalizedText(qname.Name)
473 1
    attrs.WriteMask = ua.OpenFileMode.Read
474 1
    attrs.UserWriteMask = ua.OpenFileMode.Read
475 1
    attrs.Executable = True
476 1
    attrs.UserExecutable = True
477 1
    node.NodeAttributes = attrs
478 1
    results = parent.server.add_nodes([node])
479 1
    results[0].StatusCode.check()
480 1
    method = Node(parent.server, nodeid)
481 1
    if inputs:
482 1
        create_property(method, ua.generate_nodeid(qname.NamespaceIndex), ua.QualifiedName("InputArguments", 0), [_vtype_to_argument(vtype) for vtype in inputs])
483 1
    if outputs:
484 1
        create_property(method, ua.generate_nodeid(qname.NamespaceIndex), ua.QualifiedName("OutputArguments", 0), [_vtype_to_argument(vtype) for vtype in outputs])
485 1
    parent.server.add_method_callback(method.nodeid, callback)
486 1
    return nodeid
487
488
489 1
def _call_method(server, parentnodeid, methodid, arguments):
490 1
    request = ua.CallMethodRequest()
491 1
    request.ObjectId = parentnodeid
492 1
    request.MethodId = methodid
493 1
    request.InputArguments = arguments
494 1
    methodstocall = [request]
495 1
    results = server.call(methodstocall)
496 1
    res = results[0]
497 1
    res.StatusCode.check()
498 1
    return res
499
500
501 1
def _vtype_to_argument(vtype):
502 1
    if isinstance(vtype, ua.Argument):
503
        return ua.ExtensionObject.from_object(vtype)
504
505 1
    arg = ua.Argument()
506 1
    v = ua.Variant(None, vtype)
507 1
    arg.DataType = _guess_uatype(v)
508 1
    return ua.ExtensionObject.from_object(arg)
509
510
511 1
def _guess_uatype(variant):
512 1
    if variant.VariantType == ua.VariantType.ExtensionObject:
513 1
        if variant.Value is None:
514
            raise Exception("Cannot guess DataType from Null ExtensionObject")
515 1
        if type(variant.Value) in (list, tuple):
516 1
            if len(variant.Value) == 0:
517
                raise Exception("Cannot guess DataType from Null ExtensionObject")
518 1
            extobj = variant.Value[0]
519
        else:
520
            extobj = variant.Value
521 1
        objectidname = ua.ObjectIdsInv[extobj.TypeId.Identifier]
522 1
        classname = objectidname.split("_")[0]
523 1
        return ua.NodeId(getattr(ua.ObjectIds, classname))
524
    else:
525 1
        return ua.NodeId(getattr(ua.ObjectIds, variant.VariantType.name))
526
527
528 1
def _parse_add_args(*args):
529 1
    if isinstance(args[0], ua.NodeId):
530 1
        return args[0], args[1]
531 1
    elif isinstance(args[0], str):
532 1
        return ua.NodeId.from_string(args[0]), ua.QualifiedName.from_string(args[1])
533 1
    elif isinstance(args[0], int):
534 1
        return ua.generate_nodeid(args[0]), ua.QualifiedName(args[1], args[0])
535
    else:
536
        raise TypeError("Add methods takes a nodeid and a qualifiedname as argument, received %s" % args)
537