Completed
Push — master ( cc12f5...212d98 )
by Olivier
05:14
created

NodeId.__init__()   C

Complexity

Conditions 7

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 7.0422

Importance

Changes 0
Metric Value
cc 7
c 0
b 0
f 0
dl 0
loc 23
ccs 19
cts 21
cp 0.9048
crap 7.0422
rs 5.5
1
"""
2
implement ua datatypes
3
"""
4
5 1
import logging
6 1
from enum import Enum, IntEnum
7 1
from calendar import timegm
8 1
import sys
9 1
import os
10 1
import uuid
11 1
import re
12 1
import itertools
13 1
from datetime import datetime, timedelta, MAXYEAR, tzinfo
14
15 1
from opcua.ua import status_codes
16 1
from opcua.ua import ObjectIds
17 1
from opcua.ua.uaerrors import UaError
18 1
from opcua.ua.uaerrors import UaStatusCodeError
19 1
from opcua.ua.uaerrors import UaStringParsingError
20
21 1
logger = logging.getLogger(__name__)
22
23 1
if sys.version_info.major > 2:
24 1
    unicode = str
25
26
27 1
EPOCH_AS_FILETIME = 116444736000000000  # January 1, 1970 as MS file time
28 1
HUNDREDS_OF_NANOSECONDS = 10000000
29 1
FILETIME_EPOCH_AS_DATETIME = datetime(1601, 1, 1)
30
31
32 1
class UTC(tzinfo):
33
    """
34
    UTC
35
    """
36
37 1
    def utcoffset(self, dt):
38
        return timedelta(0)
39
40 1
    def tzname(self, dt):
41
        return "UTC"
42
43 1
    def dst(self, dt):
44 1
        return timedelta(0)
45
46
47
48
# method copied from David Buxton <[email protected]> sample code
49 1
def datetime_to_win_epoch(dt):
50 1
    if (dt.tzinfo is None) or (dt.tzinfo.utcoffset(dt) is None):
51 1
        dt = dt.replace(tzinfo=UTC())
52 1
    ft = EPOCH_AS_FILETIME + (timegm(dt.timetuple()) * HUNDREDS_OF_NANOSECONDS)
53 1
    return ft + (dt.microsecond * 10)
54
55
56 1
def get_win_epoch():
57 1
    return win_epoch_to_datetime(0)
58
59
60 1
def win_epoch_to_datetime(epch):
61 1
    try:
62 1
        return FILETIME_EPOCH_AS_DATETIME + timedelta(microseconds=epch // 10)
63
    except OverflowError:
64
        # FILETIMEs after 31 Dec 9999 can't be converted to datetime
65
        logger.warning("datetime overflow: %s", epch)
66
        return datetime(MAXYEAR, 12, 31, 23, 59, 59, 999999)
67
68
69 1
class _FrozenClass(object):
70
    """
71
    Make it impossible to add members to a class.
72
    Not pythonic at all but we found out it prevents many many
73
    bugs in use of protocol structures
74
    """
75 1
    _freeze = False
76
77 1
    def __setattr__(self, key, value):
78
        if self._freeze and not hasattr(self, key):
79
            raise TypeError("Error adding member '{0}' to class '{1}', class is frozen, members are {2}".format(
80
                key, self.__class__.__name__, self.__dict__.keys()))
81
        object.__setattr__(self, key, value)
82
83
84 1
if "PYOPCUA_TYPO_CHECK" in os.environ:
85
    # typo check is cpu consuming, but it will make debug easy.
86
    # set PYOPCUA_TYPO_CHECK will make all uatype classes inherit from _FrozenClass
87
    FrozenClass = _FrozenClass
88
else:
89 1
    FrozenClass = object
90
91
92 1
class ValueRank(IntEnum):
93
    """
94
    Defines dimensions of a variable.
95
    This enum does not support all cases since ValueRank support any n>0
96
    but since it is an IntEnum it can be replace by a normal int
97
    """
98 1
    ScalarOrOneDimension = -3
99 1
    Any = -2
100 1
    Scalar = -1
101 1
    OneOrMoreDimensions = 0
102 1
    OneDimension = 1
103
    # the next names are not in spec but so common we express them here
104 1
    TwoDimensions = 2
105 1
    ThreeDimensions = 3
106 1
    FourDimensions = 4
107
108
109 1
class _MaskEnum(IntEnum):
110 1
    @classmethod
111
    def parse_bitfield(cls, the_int):
112
        """ Take an integer and interpret it as a set of enum values. """
113 1
        if not isinstance(the_int, int):
114
            raise ValueError("Argument should be an int, we received {} fo type {}".format(the_int, type(the_int)))
115
116 1
        return {cls(b) for b in cls._bits(the_int)}
117
118 1
    @classmethod
119
    def to_bitfield(cls, collection):
120
        """ Takes some enum values and creates an integer from them. """
121
        # make sure all elements are of the correct type (use itertools.tee in case we get passed an
122
        # iterator)
123 1
        iter1, iter2 = itertools.tee(iter(collection))
124 1
        assert all(isinstance(x, cls) for x in iter1)
125
126 1
        return sum(x.mask for x in iter2)
127
128 1
    @property
129
    def mask(self):
130 1
        return 1 << self.value
131
132 1
    @staticmethod
133
    def _bits(n):
134
        """ Iterate over the bits in n.
135
136
            e.g. bits(44) yields at 2, 3, 5
137
        """
138 1
        assert n >= 0  # avoid infinite recursion
139
140 1
        pos = 0
141 1
        while n:
142 1
            if n & 0x1:
143 1
                yield pos
144 1
            n = n // 2
145 1
            pos += 1
146
147
148 1
class AccessLevel(_MaskEnum):
149
    """
150
    Bit index to indicate what the access level is.
151
152
    Spec Part 3, appears multiple times, e.g. paragraph 5.6.2 Variable NodeClass
153
    """
154 1
    CurrentRead = 0
155 1
    CurrentWrite = 1
156 1
    HistoryRead = 2
157 1
    HistoryWrite = 3
158 1
    SemanticChange = 4
159 1
    StatusWrite = 5
160 1
    TimestampWrite = 6
161
162
163 1
class WriteMask(_MaskEnum):
164
    """
165
    Bit index to indicate which attribute of a node is writable
166
167
    Spec Part 3, Paragraph 5.2.7 WriteMask
168
    """
169 1
    AccessLevel = 0
170 1
    ArrayDimensions = 1
171 1
    BrowseName = 2
172 1
    ContainsNoLoops = 3
173 1
    DataType = 4
174 1
    Description = 5
175 1
    DisplayName = 6
176 1
    EventNotifier = 7
177 1
    Executable = 8
178 1
    Historizing = 9
179 1
    InverseName = 10
180 1
    IsAbstract = 11
181 1
    MinimumSamplingInterval = 12
182 1
    NodeClass = 13
183 1
    NodeId = 14
184 1
    Symmetric = 15
185 1
    UserAccessLevel = 16
186 1
    UserExecutable = 17
187 1
    UserWriteMask = 18
188 1
    ValueRank = 19
189 1
    WriteMask = 20
190 1
    ValueForVariableType = 21
191
192
193 1
class EventNotifier(_MaskEnum):
194
    """
195
    Bit index to indicate how a node can be used for events.
196
197
    Spec Part 3, appears multiple times, e.g. Paragraph 5.4 View NodeClass
198
    """
199 1
    SubscribeToEvents = 0
200
    # Reserved        = 1
201 1
    HistoryRead = 2
202 1
    HistoryWrite = 3
203
204
205 1
class StatusCode(FrozenClass):
206
    """
207
    :ivar value:
208
    :vartype value: int
209
    :ivar name:
210
    :vartype name: string
211
    :ivar doc:
212
    :vartype doc: string
213
    """
214
215 1
    ua_types = [("value", "UInt32")]
216
217 1
    def __init__(self, value=0):
218 1
        if isinstance(value, str):
219 1
            self.name = value
220 1
            self.value = getattr(status_codes.StatusCodes, value)
221
        else:
222 1
            self.value = value
223 1
            self.name, self.doc = status_codes.get_name_and_doc(value)
224 1
        self._freeze = True
225
226 1
    def check(self):
227
        """
228
        Raises an exception if the status code is anything else than 0 (good).
229
230
        Use the is_good() method if you do not want an exception.
231
        """
232 1
        if not self.is_good():
233 1
            raise UaStatusCodeError(self.value)
234
235 1
    def is_good(self):
236
        """
237
        return True if status is Good.
238
        """
239 1
        mask = 3 << 30
240 1
        if mask & self.value:
241 1
            return False
242
        else:
243 1
            return True
244
245 1
    def __str__(self):
246 1
        return 'StatusCode({0})'.format(self.name)
247
248 1
    __repr__ = __str__
249
250 1
    def __eq__(self, other):
251 1
        return self.value == other.value
252
253 1
    def __ne__(self, other):
254
        return not self.__eq__(other)
255
256
257 1
class NodeIdType(IntEnum):
258 1
    TwoByte = 0
259 1
    FourByte = 1
260 1
    Numeric = 2
261 1
    String = 3
262 1
    Guid = 4
263 1
    ByteString = 5
264
265
266 1
class NodeId(object):
267
    """
268
    NodeId Object
269
270
    Args:
271
        identifier: The identifier might be an int, a string, bytes or a Guid
272
        namespaceidx(int): The index of the namespace
273
        nodeidtype(NodeIdType): The type of the nodeid if it cannor be guess or you want something special like twobyte nodeid or fourbytenodeid
274
275
276
    :ivar Identifier:
277
    :vartype Identifier: NodeId
278
    :ivar NamespaceIndex:
279
    :vartype NamespaceIndex: Int
280
    :ivar NamespaceUri:
281
    :vartype NamespaceUri: String
282
    :ivar ServerIndex:
283
    :vartype ServerIndex: Int
284
    """
285
286 1
    def __init__(self, identifier=None, namespaceidx=0, nodeidtype=None):
287
288 1
        self.Identifier = identifier
289 1
        self.NamespaceIndex = namespaceidx
290 1
        self.NodeIdType = nodeidtype
291 1
        self.NamespaceUri = ""
292 1
        self.ServerIndex = 0
293 1
        self._freeze = True
294 1
        if self.Identifier is None:
295 1
            self.Identifier = 0
296 1
            self.NodeIdType = NodeIdType.TwoByte
297 1
            return
298 1
        if self.NodeIdType is None:
299 1
            if isinstance(self.Identifier, int):
300 1
                self.NodeIdType = NodeIdType.Numeric
301 1
            elif isinstance(self.Identifier, str):
302 1
                self.NodeIdType = NodeIdType.String
303 1
            elif isinstance(self.Identifier, bytes):
304
                self.NodeIdType = NodeIdType.ByteString
305 1
            elif isinstance(self.Identifier, uuid.UUID):
306 1
                self.NodeIdType = NodeIdType.Guid
307
            else:
308
                raise UaError("NodeId: Could not guess type of NodeId, set NodeIdType")
309
310 1
    def __eq__(self, node):
311 1
        return isinstance(node, NodeId) and self.NamespaceIndex == node.NamespaceIndex and self.Identifier == node.Identifier
312
313 1
    def __ne__(self, other):
314 1
        return not self.__eq__(other)
315
316 1
    def __hash__(self):
317 1
        return hash((self.NamespaceIndex, self.Identifier))
318
319 1
    def __lt__(self, other):
320 1
        if not isinstance(other, NodeId):
321
            raise AttributeError("Can only compare to NodeId")
322 1
        return (self.NodeIdType, self.NamespaceIndex, self.Identifier) < (other.NodeIdType, other.NamespaceIndex, other.Identifier)
323
324 1
    def is_null(self):
325 1
        if self.NamespaceIndex != 0:
326 1
            return False
327 1
        return self.has_null_identifier()
328
329 1
    def has_null_identifier(self):
330 1
        if not self.Identifier:
331 1
            return True
332 1
        if self.NodeIdType == NodeIdType.Guid and re.match(b'0.', self.Identifier):
333 1
            return True
334 1
        return False
335
336 1
    @staticmethod
337
    def from_string(string):
338 1
        try:
339 1
            return NodeId._from_string(string)
340 1
        except ValueError as ex:
341 1
            raise UaStringParsingError("Error parsing string {0}".format(string), ex)
342
343 1
    @staticmethod
344
    def _from_string(string):
345 1
        l = string.split(";")
346 1
        identifier = None
347 1
        namespace = 0
348 1
        ntype = None
349 1
        srv = None
350 1
        nsu = None
351 1
        for el in l:
352 1
            if not el:
353 1
                continue
354 1
            k, v = el.split("=", 1)
355 1
            k = k.strip()
356 1
            v = v.strip()
357 1
            if k == "ns":
358 1
                namespace = int(v)
359 1
            elif k == "i":
360 1
                ntype = NodeIdType.Numeric
361 1
                identifier = int(v)
362 1
            elif k == "s":
363 1
                ntype = NodeIdType.String
364 1
                identifier = v
365 1
            elif k == "g":
366
                ntype = NodeIdType.Guid
367
                identifier = v
368 1
            elif k == "b":
369
                ntype = NodeIdType.ByteString
370
                identifier = v
371 1
            elif k == "srv":
372 1
                srv = v
373 1
            elif k == "nsu":
374 1
                nsu = v
375 1
        if identifier is None:
376 1
            raise UaStringParsingError("Could not find identifier in string: " + string)
377 1
        nodeid = NodeId(identifier, namespace, ntype)
378 1
        nodeid.NamespaceUri = nsu
379 1
        nodeid.ServerIndex = srv
380 1
        return nodeid
381
382 1
    def to_string(self):
383 1
        string = []
384 1
        if self.NamespaceIndex != 0:
385 1
            string.append("ns={0}".format(self.NamespaceIndex))
386 1
        ntype = None
387 1
        if self.NodeIdType == NodeIdType.Numeric:
388 1
            ntype = "i"
389 1
        elif self.NodeIdType == NodeIdType.String:
390 1
            ntype = "s"
391 1
        elif self.NodeIdType == NodeIdType.TwoByte:
392 1
            ntype = "i"
393 1
        elif self.NodeIdType == NodeIdType.FourByte:
394 1
            ntype = "i"
395
        elif self.NodeIdType == NodeIdType.Guid:
396
            ntype = "g"
397
        elif self.NodeIdType == NodeIdType.ByteString:
398
            ntype = "b"
399 1
        string.append("{0}={1}".format(ntype, self.Identifier))
400 1
        if self.ServerIndex:
401 1
            string.append("srv={}".format(self.ServerIndex))
402 1
        if self.NamespaceUri:
403 1
            string.append("nsu={0}".format(self.NamespaceUri))
404 1
        return ";".join(string)
405
406 1
    def __str__(self):
407 1
        return "{0}NodeId({1})".format(self.NodeIdType.name, self.to_string())
408
409 1
    __repr__ = __str__
410
411 1
    def to_binary(self):
412
        import opcua
413
        return opcua.ua.ua_binary.nodeid_to_binary(self)
414
415
416 1
class TwoByteNodeId(NodeId):
417 1
    def __init__(self, identifier):
418 1
        NodeId.__init__(self, identifier, 0, NodeIdType.TwoByte)
419
420
421 1
class FourByteNodeId(NodeId):
422 1
    def __init__(self, identifier, namespace=0):
423 1
        NodeId.__init__(self, identifier, namespace, NodeIdType.FourByte)
424
425
426 1
class NumericNodeId(NodeId):
427 1
    def __init__(self, identifier, namespace=0):
428 1
        NodeId.__init__(self, identifier, namespace, NodeIdType.Numeric)
429
430
431 1
class ByteStringNodeId(NodeId):
432 1
    def __init__(self, identifier, namespace=0):
433 1
        NodeId.__init__(self, identifier, namespace, NodeIdType.ByteString)
434
435
436 1
class GuidNodeId(NodeId):
437 1
    def __init__(self, identifier, namespace=0):
438 1
        NodeId.__init__(self, identifier, namespace, NodeIdType.Guid)
439
440
441 1
class StringNodeId(NodeId):
442 1
    def __init__(self, identifier, namespace=0):
443 1
        NodeId.__init__(self, identifier, namespace, NodeIdType.String)
444
445
446 1
ExpandedNodeId = NodeId
447
448
449 1
class QualifiedName(FrozenClass):
450
    """
451
    A string qualified with a namespace index.
452
    """
453
454 1
    ua_types = [
455
        ('NamespaceIndex', 'UInt16'),
456
        ('Name', 'String'),
457
    ]
458
459 1
    def __init__(self, name=None, namespaceidx=0):
460 1
        if not isinstance(namespaceidx, int):
461
            raise UaError("namespaceidx must be an int")
462 1
        self.NamespaceIndex = namespaceidx
463 1
        self.Name = name
464 1
        self._freeze = True
465
466 1
    def to_string(self):
467 1
        return "{0}:{1}".format(self.NamespaceIndex, self.Name)
468
469 1
    @staticmethod
470
    def from_string(string):
471 1
        if ":" in string:
472 1
            try:
473 1
                idx, name = string.split(":", 1)
474 1
                idx = int(idx)
475 1
            except (TypeError, ValueError) as ex:
476 1
                raise UaStringParsingError("Error parsing string {0}".format(string), ex)
477
        else:
478 1
            idx = 0
479 1
            name = string
480 1
        return QualifiedName(name, idx)
481
482 1
    def __eq__(self, bname):
483 1
        return isinstance(bname,
484
                          QualifiedName) and self.Name == bname.Name and self.NamespaceIndex == bname.NamespaceIndex
485
486 1
    def __ne__(self, other):
487 1
        return not self.__eq__(other)
488
489 1
    def __lt__(self, other):
490
        if not isinstance(other, QualifiedName):
491
            raise TypeError("Cannot compare QualifiedName and {0}".format(other))
492
        if self.NamespaceIndex == other.NamespaceIndex:
493
            return self.Name < other.Name
494
        else:
495
            return self.NamespaceIndex < other.NamespaceIndex
496
497 1
    def __str__(self):
498 1
        return 'QualifiedName({0}:{1})'.format(self.NamespaceIndex, self.Name)
499
500 1
    __repr__ = __str__
501
502
503 1
class LocalizedText(FrozenClass):
504
    """
505
    A string qualified with a namespace index.
506
    """
507
508 1
    ua_switches = {
509
        'Locale': ('Encoding', 0),
510
        'Text': ('Encoding', 1),
511
    }
512
513 1
    ua_types = (
514
            ('Encoding', 'Byte'), 
515
            ('Locale', 'String'), 
516
            ('Text', 'String'), )
517
518 1
    def __init__(self, text=None):
519 1
        self.Encoding = 0
520 1
        if text is not None and not isinstance(text, str):
521
            raise ValueError("A LocalizedText object takes a string as argument, not a {}, {}".format(text, type(text)))
522 1
        self.Text = text
523 1
        if self.Text:
524 1
            self.Encoding |= (1 << 1)
525 1
        self.Locale = None
526 1
        self._freeze = True
527
528 1
    def to_string(self):
529
        # FIXME: use local
530 1
        if self.Text is None:
531
            return ""
532 1
        return self.Text
533
534 1
    def __str__(self):
535 1
        return 'LocalizedText(' + 'Encoding:' + str(self.Encoding) + ', ' + \
536
            'Locale:' + str(self.Locale) + ', ' + \
537
            'Text:' + str(self.Text) +')'
538
539 1
    __repr__ = __str__
540
541 1
    def __eq__(self, other):
542 1
        if isinstance(other, LocalizedText) and self.Locale == other.Locale and self.Text == other.Text:
543 1
            return True
544 1
        return False
545
546 1
    def __ne__(self, other):
547 1
        return not self.__eq__(other)
548
549
550 1
class ExtensionObject(FrozenClass):
551
    """
552
    Any UA object packed as an ExtensionObject
553
554
    :ivar TypeId:
555
    :vartype TypeId: NodeId
556
    :ivar Body:
557
    :vartype Body: bytes
558
    """
559 1
    ua_switches = {
560
        'Body': ('Encoding', 0),
561
    }
562
563 1
    ua_types = (
564
            ("TypeId", "NodeId"), 
565
            ("Encoding", "Byte"), 
566
            ("Body", "ByteString"), 
567
            )
568
569 1
    def __init__(self):
570 1
        self.TypeId = NodeId()
571 1
        self.Encoding = 0
572 1
        self.Body = None
573 1
        self._freeze = True
574
575 1
    def __bool__(self):
576 1
        return self.Body is not None
577 1
    __nonzero__ = __bool__  # Python2 compatibilty
578
579 1
    def __str__(self):
580
        size = len(self.Body) if self.Body is not None else None
581
        return 'ExtensionObject(' + 'TypeId:' + str(self.TypeId) + ', ' + \
582
            'Encoding:' + str(self.Encoding) + ', ' + str(size) + ' bytes)'
583
584 1
    __repr__ = __str__
585
586
587 1
class VariantType(Enum):
588
    """
589
    The possible types of a variant.
590
591
    :ivar Null:
592
    :ivar Boolean:
593
    :ivar SByte:
594
    :ivar Byte:
595
    :ivar Int16:
596
    :ivar UInt16:
597
    :ivar Int32:
598
    :ivar UInt32:
599
    :ivar Int64:
600
    :ivar UInt64:
601
    :ivar Float:
602
    :ivar Double:
603
    :ivar String:
604
    :ivar DateTime:
605
    :ivar Guid:
606
    :ivar ByteString:
607
    :ivar XmlElement:
608
    :ivar NodeId:
609
    :ivar ExpandedNodeId:
610
    :ivar StatusCode:
611
    :ivar QualifiedName:
612
    :ivar LocalizedText:
613
    :ivar ExtensionObject:
614
    :ivar DataValue:
615
    :ivar Variant:
616
    :ivar DiagnosticInfo:
617
    """
618
619 1
    Null = 0
620 1
    Boolean = 1
621 1
    SByte = 2
622 1
    Byte = 3
623 1
    Int16 = 4
624 1
    UInt16 = 5
625 1
    Int32 = 6
626 1
    UInt32 = 7
627 1
    Int64 = 8
628 1
    UInt64 = 9
629 1
    Float = 10
630 1
    Double = 11
631 1
    String = 12
632 1
    DateTime = 13
633 1
    Guid = 14
634 1
    ByteString = 15
635 1
    XmlElement = 16
636 1
    NodeId = 17
637 1
    ExpandedNodeId = 18
638 1
    StatusCode = 19
639 1
    QualifiedName = 20
640 1
    LocalizedText = 21
641 1
    ExtensionObject = 22
642 1
    DataValue = 23
643 1
    Variant = 24
644 1
    DiagnosticInfo = 25
645
646
647 1
class VariantTypeCustom(object):
648
    """
649
    Looks like sometime we get variant with other values than those
650
    defined in VariantType.
651
    FIXME: We should not need this class, as far as I iunderstand the spec
652
    variants can only be of VariantType
653
    """
654
655 1
    def __init__(self, val):
656 1
        self.name = "Custom"
657 1
        self.value = val
658 1
        if self.value > 0b00111111:
659 1
            raise UaError(
660
                "Cannot create VariantType. VariantType must be {0} > x > {1}, received {2}".format(0b111111, 25, val))
661
662 1
    def __str__(self):
663
        return "VariantType.Custom:{0}".format(self.value)
664
665 1
    __repr__ = __str__
666
667 1
    def __eq__(self, other):
668 1
        return self.value == other.value
669
670
671 1
class Variant(FrozenClass):
672
    """
673
    Create an OPC-UA Variant object.
674
    if no argument a Null Variant is created.
675
    if not variant type is given, attemps to guess type from python type
676
    if a variant is given as value, the new objects becomes a copy of the argument
677
678
    :ivar Value:
679
    :vartype Value: Any supported type
680
    :ivar VariantType:
681
    :vartype VariantType: VariantType
682
    :ivar Dimension:
683
    :vartype Dimensions: The length of each dimensions. Usually guessed from value.
684
    :ivar is_array:
685
    :vartype is_array: If the variant is an array. Usually guessed from value.
686
    """
687
688 1
    def __init__(self, value=None, varianttype=None, dimensions=None, is_array=None):
689 1
        self.Value = value
690 1
        self.VariantType = varianttype
691 1
        self.Dimensions = dimensions
692 1
        self.is_array = is_array
693 1
        if self.is_array is None:
694 1
            if isinstance(value, (list, tuple)):
695 1
                self.is_array = True
696
            else:
697 1
                self.is_array = False
698 1
        self._freeze = True
699 1
        if isinstance(value, Variant):
700 1
            self.Value = value.Value
701 1
            self.VariantType = value.VariantType
702 1
        if self.VariantType is None:
703 1
            self.VariantType = self._guess_type(self.Value)
704 1
        if self.Value is None and not self.is_array and self.VariantType not in (VariantType.Null, VariantType.String,
705
                                                                                 VariantType.DateTime):
706
            raise UaError("Non array Variant of type {0} cannot have value None".format(self.VariantType))
707 1
        if self.Dimensions is None and isinstance(self.Value, (list, tuple)):
708 1
            dims = get_shape(self.Value)
709 1
            if len(dims) > 1:
710 1
                self.Dimensions = dims
711
712 1
    def __eq__(self, other):
713 1
        if isinstance(other, Variant) and self.VariantType == other.VariantType and self.Value == other.Value:
714 1
            return True
715 1
        return False
716
717 1
    def __ne__(self, other):
718 1
        return not self.__eq__(other)
719
720 1
    def _guess_type(self, val):
721 1
        if isinstance(val, (list, tuple)):
722 1
            error_val = val
723 1
        while isinstance(val, (list, tuple)):
724 1
            if len(val) == 0:
725
                raise UaError("could not guess UA type of variable {0}".format(error_val))
726 1
            val = val[0]
727 1
        if val is None:
728 1
            return VariantType.Null
729 1
        elif isinstance(val, bool):
730 1
            return VariantType.Boolean
731 1
        elif isinstance(val, float):
732 1
            return VariantType.Double
733 1
        elif isinstance(val, IntEnum):
734 1
            return VariantType.Int32
735 1
        elif isinstance(val, int):
736 1
            return VariantType.Int64
737 1
        elif isinstance(val, (str, unicode)):
738 1
            return VariantType.String
739 1
        elif isinstance(val, bytes):
740
            return VariantType.ByteString
741 1
        elif isinstance(val, datetime):
742 1
            return VariantType.DateTime
743 1
        elif isinstance(val, uuid.UUID):
744 1
            return VariantType.Guid
745
        else:
746 1
            if isinstance(val, object):
747 1
                try:
748 1
                    return getattr(VariantType, val.__class__.__name__)
749 1
                except AttributeError:
750 1
                    return VariantType.ExtensionObject
751
            else:
752
                raise UaError("Could not guess UA type of {0} with type {1}, specify UA type".format(val, type(val)))
753
754 1
    def __str__(self):
755 1
        return "Variant(val:{0!s},type:{1})".format(self.Value, self.VariantType)
756
757 1
    __repr__ = __str__
758
759 1
    def to_binary(self):
760
        from opcua.ua.ua_binary import variant_to_binary
761
        return variant_to_binary(self)
762
763
764 1
def _split_list(l, n):
765
    n = max(1, n)
766
    return [l[i:i + n] for i in range(0, len(l), n)]
767
768
769 1
def flatten_and_get_shape(mylist):
770
    dims = []
771
    dims.append(len(mylist))
772
    while isinstance(mylist[0], (list, tuple)):
773
        dims.append(len(mylist[0]))
774
        mylist = [item for sublist in mylist for item in sublist]
775
        if len(mylist) == 0:
776
            break
777
    return mylist, dims
778
779
780 1
def flatten(mylist):
781 1
    if mylist is None:
782 1
        return None
783 1
    elif len(mylist) == 0:
784 1
        return mylist
785 1
    while isinstance(mylist[0], (list, tuple)):
786 1
        mylist = [item for sublist in mylist for item in sublist]
787 1
        if len(mylist) == 0:
788 1
            break
789 1
    return mylist
790
791
792 1
def get_shape(mylist):
793 1
    dims = []
794 1
    while isinstance(mylist, (list, tuple)):
795 1
        dims.append(len(mylist))
796 1
        if len(mylist) == 0:
797 1
            break
798 1
        mylist = mylist[0]
799 1
    return dims
800
801
802 1
class DataValue(FrozenClass):
803
    """
804
    A value with an associated timestamp, and quality.
805 View Code Duplication
    Automatically generated from xml , copied and modified here to fix errors in xml spec
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
806
807
    :ivar Value:
808
    :vartype Value: Variant
809
    :ivar StatusCode:
810
    :vartype StatusCode: StatusCode
811
    :ivar SourceTimestamp:
812
    :vartype SourceTimestamp: datetime
813
    :ivar SourcePicoSeconds:
814
    :vartype SourcePicoSeconds: int
815
    :ivar ServerTimestamp:
816
    :vartype ServerTimestamp: datetime
817
    :ivar ServerPicoseconds:
818
    :vartype ServerPicoseconds: int
819
    """
820
821 1
    ua_switches = {
822
        'Value': ('Encoding', 0),
823
        'StatusCode': ('Encoding', 1),
824
        'SourceTimestamp': ('Encoding', 2),
825
        'ServerTimestamp': ('Encoding', 3),
826
        'SourcePicoseconds': ('Encoding', 4),
827
        'ServerPicoseconds': ('Encoding', 5),
828
    }
829
830 1
    ua_types = (
831
            ('Encoding', 'Byte'), 
832
            ('Value', 'Variant'), 
833
            ('StatusCode', 'StatusCode'), 
834
            ('SourceTimestamp', 'DateTime'),
835
            ('SourcePicoseconds', 'UInt16'), 
836
            ('ServerTimestamp', 'DateTime'), 
837
            ('ServerPicoseconds', 'UInt16'), 
838
            )
839
840 1
    def __init__(self, variant=None, status=None):
841 1
        self.Encoding = 0
842 1
        if not isinstance(variant, Variant):
843 1
            variant = Variant(variant)
844 1
        self.Value = variant
845 1
        if status is None:
846 1
            self.StatusCode = StatusCode()
847
        else:
848
            self.StatusCode = status
849 1
        self.SourceTimestamp = None  # DateTime()
850 1
        self.SourcePicoseconds = None
851 1
        self.ServerTimestamp = None  # DateTime()
852 1
        self.ServerPicoseconds = None
853 1
        self._freeze = True
854
855 1
    def __str__(self):
856 1
        s = 'DataValue(Value:{0}'.format(self.Value)
857 1
        if self.StatusCode is not None:
858 1
            s += ', StatusCode:{0}'.format(self.StatusCode)
859 1
        if self.SourceTimestamp is not None:
860 1
            s += ', SourceTimestamp:{0}'.format(self.SourceTimestamp)
861 1
        if self.ServerTimestamp is not None:
862
            s += ', ServerTimestamp:{0}'.format(self.ServerTimestamp)
863 1
        if self.SourcePicoseconds is not None:
864
            s += ', SourcePicoseconds:{0}'.format(self.SourcePicoseconds)
865 1
        if self.ServerPicoseconds is not None:
866
            s += ', ServerPicoseconds:{0}'.format(self.ServerPicoseconds)
867 1
        s += ')'
868 1
        return s
869
870 1
    __repr__ = __str__
871
872
873 1
def datatype_to_varianttype(int_type):
874
    """
875
    Takes a NodeId or int and return a VariantType
876
    This is only supported if int_type < 63 due to VariantType encoding
877
    At low level we do not have access to address space thus decoding is limited
878
    a better version of this method can be find in ua_utils.py
879
    """
880 1
    if isinstance(int_type, NodeId):
881
        int_type = int_type.Identifier
882
883 1
    if int_type <= 25:
884 1
        return VariantType(int_type)
885
    else:
886 1
        return VariantTypeCustom(int_type)
887
888
889 1
def get_default_value(vtype):
890
    """
891
    Given a variant type return default value for this type
892
    """
893 1
    if vtype == VariantType.Null:
894
        return None
895 1
    elif vtype == VariantType.Boolean:
896 1
        return False
897 1
    elif vtype in (VariantType.SByte, VariantType.Byte):
898
        return 0
899 1
    elif vtype == VariantType.ByteString:
900
        return b""
901 1
    elif 4 <= vtype.value <= 9:
902 1
        return 0
903 1
    elif vtype in (VariantType.Float, VariantType.Double):
904 1
        return 0.0
905 1
    elif vtype == VariantType.String:
906 1
        return None  # a string can be null
907
    elif vtype == VariantType.DateTime:
908
        return datetime.utcnow()
909
    elif vtype == VariantType.Guid:
910
        return uuid.uuid4()
911
    elif vtype == VariantType.XmlElement:
912
        return None  #Not sure this is correct
913
    elif vtype == VariantType.NodeId:
914
        return NodeId()
915
    elif vtype == VariantType.ExpandedNodeId:
916
        return NodeId()
917
    elif vtype == VariantType.StatusCode:
918
        return StatusCode()
919
    elif vtype == VariantType.QualifiedName:
920
        return QualifiedName()
921
    elif vtype == VariantType.LocalizedText:
922
        return LocalizedText()
923
    elif vtype == VariantType.ExtensionObject:
924
        return ExtensionObject()
925
    elif vtype == VariantType.DataValue:
926
        return DataValue()
927
    elif vtype == VariantType.Variant:
928
        return Variant()
929
    else:
930
        raise RuntimeError("function take a uatype as argument, got:", vtype)
931
932
933
# These dictionnaries are used to register extensions classes for automatic
934
# decoding and encoding
935 1
extension_object_classes = {}
936 1
extension_object_ids = {}
937
938
939 1
def register_extension_object(name, nodeid, class_type):
940
    """
941
    Register a new extension object for automatic decoding and make them available in ua module
942
    """
943 1
    logger.warning("registring new extension object: %s %s %s", name, nodeid, class_type)
944 1
    extension_object_classes[nodeid] = class_type
945 1
    extension_object_ids[name] = nodeid
946
    # FIXME: Next line is not exactly a Python best practices, so feel free to propose something else
947
    # add new extensions objects to ua modules to automate decoding
948 1
    import opcua.ua
949 1
    setattr(opcua.ua, name, class_type)
950
951
952 1
def get_extensionobject_class_type(typeid):
953
    """
954
    Returns the registered class type for typid of an extension object
955
    """
956
    if typeid in extension_object_classes:
957
        return extension_object_classes[typeid]
958
    else:
959
        return None
960