Completed
Pull Request — master (#203)
by
unknown
03:46
created

Node.read_event_history()   C

Complexity

Conditions 7

Size

Total Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 7

Importance

Changes 0
Metric Value
cc 7
c 0
b 0
f 0
dl 0
loc 38
ccs 17
cts 17
cp 1
crap 7
rs 5.5
1
"""
2
High level node object, to access node attribute
3
and browse address space
4
"""
5
6 1
from opcua import ua
7
from opcua.common import events
8 1
9
10
class Node(object):
11 1
12
    """
13
    High level node object, to access node attribute,
14
    browse and populate address space.
15
    Node objects are usefull as-is but they do not expose the entire
16
    OPC-UA protocol. Feel free to look at the code of this class and call
17
    directly UA services methods to optimize your code
18
    """
19
20
    def __init__(self, server, nodeid):
21 1
        self.server = server
22 1
        self.nodeid = None
23 1
        if isinstance(nodeid, Node):
24 1
            self.nodeid = nodeid.nodeid
25 1
        elif isinstance(nodeid, ua.NodeId):
26 1
            self.nodeid = nodeid
27 1
        elif type(nodeid) in (str, bytes):
28 1
            self.nodeid = ua.NodeId.from_string(nodeid)
29 1
        elif isinstance(nodeid, int):
30
            self.nodeid = ua.NodeId(nodeid, 0)
31
        else:
32
            raise ua.UaError("argument to node must be a NodeId object or a string defining a nodeid found {} of type {}".format(nodeid, type(nodeid)))
33 1
34 1
    def __eq__(self, other):
35 1
        if isinstance(other, Node) and self.nodeid == other.nodeid:
36 1
            return True
37
        return False
38 1
39
    def __ne__(self, other):
40
        return not self.__eq__(other)
41 1
42
    def __str__(self):
43 1
        return "Node({})".format(self.nodeid)
44
    __repr__ = __str__
45 1
46 1
    def __hash__(self):
47
        return self.nodeid.__hash__()
48 1
49
    def get_browse_name(self):
50
        """
51
        Get browse name of a node. A browse name is a QualifiedName object
52
        composed of a string(name) and a namespace index.
53 1
        """
54 1
        result = self.get_attribute(ua.AttributeIds.BrowseName)
55
        return result.Value.Value
56 1
57
    def get_display_name(self):
58
        """
59
        get description attribute of node
60 1
        """
61 1
        result = self.get_attribute(ua.AttributeIds.DisplayName)
62
        return result.Value.Value
63 1
64
    def get_data_type(self):
65
        """
66
        get data type of node
67 1
        """
68 1
        result = self.get_attribute(ua.AttributeIds.DataType)
69
        return result.Value.Value
70 1
71
    def get_node_class(self):
72
        """
73
        get node class attribute of node
74
        """
75
        result = self.get_attribute(ua.AttributeIds.NodeClass)
76
        return result.Value.Value
77 1
78
    def get_description(self):
79
        """
80
        get description attribute class of node
81
        """
82
        result = self.get_attribute(ua.AttributeIds.Description)
83
        return result.Value.Value
84 1
85
    def get_value(self):
86
        """
87
        Get value of a node as a python type. Only variables ( and properties) have values.
88
        An exception will be generated for other node types.
89 1
        """
90 1
        result = self.get_data_value()
91
        return result.Value.Value
92 1
93
    def get_data_value(self):
94
        """
95
        Get value of a node as a DataValue object. Only variables (and properties) have values.
96
        An exception will be generated for other node types.
97
        DataValue contain a variable value as a variant as well as server and source timestamps
98 1
        """
99
        return self.get_attribute(ua.AttributeIds.Value)
100 1
101
    def set_array_dimensions(self, value):
102
        """
103
        Set attribute ArrayDimensions of node
104
        make sure it has the correct data type
105 1
        """
106 1
        v = ua.Variant(value, ua.VariantType.UInt32)
107
        self.set_attribute(ua.AttributeIds.ArrayDimensions, ua.DataValue(v))
108 1
109
    def get_array_dimensions(self):
110
        """
111
        Read and return ArrayDimensions attribute of node
112 1
        """
113 1
        res = self.get_attribute(ua.AttributeIds.ArrayDimensions)
114
        return res.Value.Value
115 1
116
    def set_value_rank(self, value):
117
        """
118
        Set attribute ArrayDimensions of node
119 1
        """
120 1
        v = ua.Variant(value, ua.VariantType.Int32)
121
        self.set_attribute(ua.AttributeIds.ValueRank, ua.DataValue(v))
122 1
123
    def get_value_rank(self):
124
        """
125
        Read and return ArrayDimensions attribute of node
126 1
        """
127 1
        res = self.get_attribute(ua.AttributeIds.ValueRank)
128
        return res.Value.Value
129 1
130
    def set_value(self, value, varianttype=None):
131
        """
132
        Set value of a node. Only variables(properties) have values.
133
        An exception will be generated for other node types.
134
        value argument is either:
135
        * a python built-in type, converted to opc-ua
136
        optionnaly using the variantype argument.
137
        * a ua.Variant, varianttype is then ignored
138
        * a ua.DataValue, you then have full control over data send to server
139 1
        """
140 1
        datavalue = None
141 1
        if isinstance(value, ua.DataValue):
142 1
            datavalue = value
143 1
        elif isinstance(value, ua.Variant):
144
            datavalue = ua.DataValue(value)
145 1
        else:
146 1
            datavalue = ua.DataValue(ua.Variant(value, varianttype))
147
        self.set_attribute(ua.AttributeIds.Value, datavalue)
148 1
149
    set_data_value = set_value
150 1
151
    def set_writable(self, writable=True):
152
        """
153
        Set node as writable by clients.
154
        A node is always writable on server side.
155 1
        """
156 1
        if writable:
157 1
            self.set_attr_bit(ua.AttributeIds.AccessLevel, ua.AccessLevel.CurrentWrite)
158
            self.set_attr_bit(ua.AttributeIds.UserAccessLevel, ua.AccessLevel.CurrentWrite)
159 1
        else:
160 1
            self.unset_attr_bit(ua.AttributeIds.AccessLevel, ua.AccessLevel.CurrentWrite)
161
            self.unset_attr_bit(ua.AttributeIds.UserAccessLevel, ua.AccessLevel.CurrentWrite)
162 1
163 1
    def set_attr_bit(self, attr, bit):
164 1
        val = self.get_attribute(attr)
165 1
        val.Value.Value = ua.set_bit(val.Value.Value, bit)
166
        self.set_attribute(attr, val)
167 1
168 1
    def unset_attr_bit(self, attr, bit):
169 1
        val = self.get_attribute(attr)
170 1
        val.Value.Value = ua.unset_bit(val.Value.Value, bit)
171
        self.set_attribute(attr, val)
172 1
173
    def set_read_only(self):
174
        """
175
        Set a node as read-only for clients.
176
        A node is always writable on server side.
177
        """
178
        return self.set_writable(False)
179 1
180
    def set_attribute(self, attributeid, datavalue):
181
        """
182
        Set an attribute of a node
183
        attributeid is a member of ua.AttributeIds
184
        datavalue is a ua.DataValue object
185 1
        """
186 1
        attr = ua.WriteValue()
187 1
        attr.NodeId = self.nodeid
188 1
        attr.AttributeId = attributeid
189 1
        attr.Value = datavalue
190 1
        params = ua.WriteParameters()
191 1
        params.NodesToWrite = [attr]
192 1
        result = self.server.write(params)
193
        result[0].check()
194 1
195
    def get_attribute(self, attr):
196
        """
197
        Read one attribute of a node
198
        result code from server is checked and an exception is raised in case of error
199 1
        """
200 1
        rv = ua.ReadValueId()
201 1
        rv.NodeId = self.nodeid
202 1
        rv.AttributeId = attr
203 1
        params = ua.ReadParameters()
204 1
        params.NodesToRead.append(rv)
205 1
        result = self.server.read(params)
206 1
        result[0].StatusCode.check()
207
        return result[0]
208 1
209
    def get_attributes(self, attrs):
210
        """
211
        Read several attributes of a node
212
        list of DataValue is returned
213
        """
214
        params = ua.ReadParameters()
215
        for attr in attrs:
216
            rv = ua.ReadValueId()
217
            rv.NodeId = self.nodeid
218
            rv.AttributeId = attr
219
            params.NodesToRead.append(rv)
220
221
        results = self.server.read(params)
222
        return results
223 1
224
    def get_children(self, refs=ua.ObjectIds.HierarchicalReferences, nodeclassmask=ua.NodeClass.Unspecified):
225
        """
226
        Get all children of a node. By default hierarchical references and all node classes are returned.
227
        Other reference types may be given:
228
        References = 31
229
        NonHierarchicalReferences = 32
230
        HierarchicalReferences = 33
231
        HasChild = 34
232
        Organizes = 35
233
        HasEventSource = 36
234
        HasModellingRule = 37
235
        HasEncoding = 38
236
        HasDescription = 39
237
        HasTypeDefinition = 40
238
        GeneratesEvent = 41
239
        Aggregates = 44
240
        HasSubtype = 45
241
        HasProperty = 46
242
        HasComponent = 47
243
        HasNotifier = 48
244
        HasOrderedComponent = 49
245 1
        """
246
        return self.get_referenced_nodes(refs, ua.BrowseDirection.Forward, nodeclassmask)
247 1
248
    def get_properties(self):
249
        """
250
        return properties of node.
251
        properties are child nodes with a reference of type HasProperty and a NodeClass of Variable
252 1
        """
253
        return self.get_children(refs=ua.ObjectIds.HasProperty, nodeclassmask=ua.NodeClass.Variable)
254 1
255
    def get_children_descriptions(self, refs=ua.ObjectIds.HierarchicalReferences, nodeclassmask=ua.NodeClass.Unspecified, includesubtypes=True):
256
        return self.get_references(refs, ua.BrowseDirection.Forward, nodeclassmask, includesubtypes)
257 1
258
    def get_references(self, refs=ua.ObjectIds.References, direction=ua.BrowseDirection.Both, nodeclassmask=ua.NodeClass.Unspecified, includesubtypes=True):
259
        """
260
        returns references of the node based on specific filter defined with:
261
262
        refs = ObjectId of the Reference
263
        direction = Browse direction for references
264
        nodeclassmask = filter nodes based on specific class
265
        includesubtypes = If true subtypes of the reference (ref) are also included
266 1
        """
267 1
        desc = ua.BrowseDescription()
268 1
        desc.BrowseDirection = direction
269 1
        desc.ReferenceTypeId = ua.TwoByteNodeId(refs)
270 1
        desc.IncludeSubtypes = includesubtypes
271 1
        desc.NodeClassMask = nodeclassmask
272
        desc.ResultMask = ua.BrowseResultMask.All
273 1
274 1
        desc.NodeId = self.nodeid
275 1
        params = ua.BrowseParameters()
276 1
        params.View.Timestamp = ua.win_epoch_to_datetime(0)
277 1
        params.NodesToBrowse.append(desc)
278 1
        results = self.server.browse(params)
279
        return results[0].References
280 1
281
    def get_referenced_nodes(self, refs=ua.ObjectIds.References, direction=ua.BrowseDirection.Both, nodeclassmask=ua.NodeClass.Unspecified, includesubtypes=True):
282
        """
283
        returns referenced nodes based on specific filter
284
        Paramters are the same as for get_references
285
286 1
        """
287 1
        references = self.get_references(refs, direction, nodeclassmask, includesubtypes)
288 1
        nodes = []
289 1
        for desc in references:
290 1
            node = Node(self.server, desc.NodeId)
291 1
            nodes.append(node)
292
        return nodes
293 1
294
    def get_type_definition(self):
295
        """
296
        returns type definition of the node.
297 1
        """
298 1
        references = self.get_references(refs=ua.ObjectIds.HasTypeDefinition, direction=ua.BrowseDirection.Forward)
299 1
        if len(references) == 0:
300 1
            return ua.ObjectIds.BaseObjectType
301
        return references[0].NodeId.Identifier
302 1
303
    def get_parent(self):
304
        """
305
        returns parent of the node.
306 1
        """
307
        refs = self.get_references(refs=ua.ObjectIds.HierarchicalReferences, direction=ua.BrowseDirection.Inverse)
308 1
309
        return Node(self.server, refs[0].NodeId)
310 1
311
    def get_child(self, path):
312
        """
313
        get a child specified by its path from this node.
314
        A path might be:
315
        * a string representing a qualified name.
316
        * a qualified name
317
        * a list of string
318
        * a list of qualified names
319 1
        """
320 1
        if type(path) not in (list, tuple):
321 1
            path = [path]
322 1
        rpath = self._make_relative_path(path)
323 1
        bpath = ua.BrowsePath()
324 1
        bpath.StartingNode = self.nodeid
325 1
        bpath.RelativePath = rpath
326 1
        result = self.server.translate_browsepaths_to_nodeids([bpath])
327 1
        result = result[0]
328
        result.StatusCode.check()
329 1
        # FIXME: seems this method may return several nodes
330
        return Node(self.server, result.Targets[0].TargetId)
331 1
332 1
    def _make_relative_path(self, path):
333 1
        rpath = ua.RelativePath()
334 1
        for item in path:
335 1
            el = ua.RelativePathElement()
336 1
            el.ReferenceTypeId = ua.TwoByteNodeId(ua.ObjectIds.HierarchicalReferences)
337 1
            el.IsInverse = False
338 1
            el.IncludeSubtypes = True
339
            if isinstance(item, ua.QualifiedName):
340
                el.TargetName = item
341 1
            else:
342 1
                el.TargetName = ua.QualifiedName.from_string(item)
343 1
            rpath.Elements.append(el)
344
        return rpath
345 1
346
    def read_raw_history(self, starttime=None, endtime=None, numvalues=0):
347
        """
348
        Read raw history of a node
349
        result code from server is checked and an exception is raised in case of error
350
        If numvalues is > 0 and number of events in period is > numvalues
351
        then result will be truncated
352 1
        """
353 1
        details = ua.ReadRawModifiedDetails()
354 1
        details.IsReadModified = False
355 1
        if starttime:
356
            details.StartTime = starttime
357 1
        else:
358 1
            details.StartTime = ua.DateTimeMinValue
359 1
        if endtime:
360
            details.EndTime = endtime
361 1
        else:
362 1
            details.EndTime = ua.DateTimeMinValue
363 1
        details.NumValuesPerNode = numvalues
364 1
        details.ReturnBounds = True
365 1
        result = self.history_read(details)
366
        return result.HistoryData.DataValues
367 1
368 View Code Duplication
    def history_read(self, details):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
369
        """
370
        Read raw history of a node, low-level function
371
        result code from server is checked and an exception is raised in case of error
372 1
        """
373 1
        valueid = ua.HistoryReadValueId()
374 1
        valueid.NodeId = self.nodeid
375
        valueid.IndexRange = ''
376 1
377 1
        params = ua.HistoryReadParameters()
378 1
        params.HistoryReadDetails = details
379 1
        params.TimestampsToReturn = ua.TimestampsToReturn.Both
380 1
        params.ReleaseContinuationPoints = False
381 1
        params.NodesToRead.append(valueid)
382 1
        result = self.server.history_read(params)[0]
383
        return result
384 1
385
    def read_event_history(self, starttime=None, endtime=None, numvalues=0, evtypes=ua.ObjectIds.BaseEventType):
386
        """
387
        Read event history of a source node 
388
        result code from server is checked and an exception is raised in case of error
389
        If numvalues is > 0 and number of events in period is > numvalues
390
        then result will be truncated
391
        """
392
393
        details = ua.ReadEventDetails()
394
        if starttime:
395
            details.StartTime = starttime
396 1
        else:
397 1
            details.StartTime = ua.DateTimeMinValue
398 1
        if endtime:
399
            details.EndTime = endtime
400 1
        else:
401 1
            details.EndTime = ua.DateTimeMinValue
402 1
        details.NumValuesPerNode = numvalues
403
404 1
        if not type(evtypes) in (list, tuple):
405 1
            evtypes = [evtypes]
406
407 1
        # FIXME not a very nice way to make sure events.get_filter gets a list of nodes...
408
        evtype_nodes = []
409 1
        for evtype in evtypes:
410 1
            if not isinstance(evtype, Node):
411
                evtype_nodes.append(Node(self.server, ua.NodeId(evtype)))  # make sure we have a list of Node objects
412 1
            else:
413
                evtype_nodes.append(evtype)
414
415
        evfilter = events.get_filter_from_event_type(Node(self.server, evtype_nodes))
416
        details.Filter = evfilter
417 1
418 1
        result = self.history_read_events(details)
419 1
        event_res = []
420
        for res in result.HistoryData.Events:
421 1
            event_res.append(events.Event.from_event_fields(evfilter.SelectClauses, res.EventFields))
422 1
        return event_res
423 1
424 1 View Code Duplication
    def history_read_events(self, details):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
425 1
        """
426 1
        Read event history of a node, low-level function
427 1
        result code from server is checked and an exception is raised in case of error
428
        """
429
        valueid = ua.HistoryReadValueId()
430
        valueid.NodeId = self.nodeid
431
        valueid.IndexRange = ''
432
433
        params = ua.HistoryReadParameters()
434 1
        params.HistoryReadDetails = details
435 1
        params.TimestampsToReturn = ua.TimestampsToReturn.Both
436 1
        params.ReleaseContinuationPoints = False
437
        params.NodesToRead.append(valueid)
438 1
        result = self.server.history_read(params)[0]
439 1
        return result
440 1
441
    # Hack for convenience methods
442 1
    # local import is ugly but necessary for python2 support
443 1
    # feel fri to propose something better but I want to split all those
444 1
    # create methods from Node
445
446 1
    def add_folder(*args, **kwargs):
447 1
        from opcua.common import manage_nodes
448 1
        return manage_nodes.create_folder(*args, **kwargs)
449
450 1
    def add_object(*args, **kwargs):
451 1
        from opcua.common import manage_nodes
452 1
        return manage_nodes.create_object(*args, **kwargs)
453
454 1
    def add_variable(*args, **kwargs):
455 1
        from opcua.common import manage_nodes
456 1
        return manage_nodes.create_variable(*args, **kwargs)
457
458 1
    def add_property(*args, **kwargs):
459 1
        from opcua.common import manage_nodes
460 1
        return manage_nodes.create_property(*args, **kwargs)
461
462
    def add_method(*args, **kwargs):
463
        from opcua.common import manage_nodes
464
        return manage_nodes.create_method(*args, **kwargs)
465
466
    def add_subtype(*args, **kwargs):
467
        from opcua.common import manage_nodes
468
        return manage_nodes.create_subtype(*args, **kwargs)
469
470
    def call_method(*args, **kwargs):
471
        from opcua.common import methods
472
        return methods.call_method(*args, **kwargs)
473