Completed
Pull Request — master (#133)
by Denis
02:36
created

opcua.ua.BaseEvent.__init__()   A

Complexity

Conditions 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1
Metric Value
cc 1
dl 0
loc 10
ccs 10
cts 10
cp 1
crap 1
rs 9.4285
1
"""
2
implement ua datatypes
3
"""
4 1
import logging
5 1
from enum import Enum
6 1
from datetime import datetime, timedelta, tzinfo, MAXYEAR
7 1
from calendar import timegm
8 1
import sys
9 1
import os
10 1
import uuid
11 1
import struct
12 1
if sys.version_info.major > 2:
13 1
    unicode = str
14
15 1
from opcua.ua import status_codes
16 1
from opcua.common.uaerrors import UaError
17 1
from opcua.common.uaerrors import UaStatusCodeError
18 1
from opcua.common.uaerrors import UaStringParsingError
19 1
from opcua.ua.object_ids import ObjectIds
20
21 1
logger = logging.getLogger('opcua.uaprotocol')
22
23
24
# types that will packed and unpacked directly using struct (string, bytes and datetime are handles as special cases
25 1
UaTypes = ("Boolean", "SByte", "Byte", "Int8", "UInt8", "Int16", "UInt16", "Int32", "UInt32", "Int64", "UInt64", "Float", "Double")
26
27
28 1
EPOCH_AS_FILETIME = 116444736000000000  # January 1, 1970 as MS file time
29 1
HUNDREDS_OF_NANOSECONDS = 10000000
30 1
FILETIME_EPOCH_AS_DATETIME = datetime(1601, 1, 1)
31
32
33 1
class UTC(tzinfo):
34
35
    """UTC"""
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
# method copied from David Buxton <[email protected]> sample code
48 1
def datetime_to_win_epoch(dt):
49 1
    if (dt.tzinfo is None) or (dt.tzinfo.utcoffset(dt) is None):
50 1
        dt = dt.replace(tzinfo=UTC())
51 1
    ft = EPOCH_AS_FILETIME + (timegm(dt.timetuple()) * HUNDREDS_OF_NANOSECONDS)
52 1
    return ft + (dt.microsecond * 10)
53
54
55 1
def win_epoch_to_datetime(epch):
56 1
    try:
57 1
        return FILETIME_EPOCH_AS_DATETIME + timedelta(microseconds=epch // 10)
58
    except OverflowError:
59
        # FILETIMEs after 31 Dec 9999 can't be converted to datetime
60
        logger.warning("datetime overflow: {}".format(epch))
61
        return datetime(MAXYEAR, 12, 31, 23, 59, 59, 999999)
62
63
64 1
def build_array_format_py2(prefix, length, fmtchar):
65
    return prefix + str(length) + fmtchar
66
67
68 1
def build_array_format_py3(prefix, length, fmtchar):
69 1
    return prefix + str(length) + chr(fmtchar)
70
71
72 1
if sys.version_info.major < 3:
73
    build_array_format = build_array_format_py2
74
else:
75 1
    build_array_format = build_array_format_py3
76
77
78 1
def pack_uatype_array_primitive(st, value, length):
79 1
    if length == 1:
80 1
        return b'\x01\x00\x00\x00' + st.pack(value[0])
81
    else:
82 1
        return struct.pack(build_array_format("<i", length, st.format[1]), length, *value)
83
84
85 1
def pack_uatype_array(uatype, value):
86 1
    if value is None:
87
        return b'\xff\xff\xff\xff'
88 1
    length = len(value)
89 1
    if length == 0:
90 1
        return b'\x00\x00\x00\x00'
91 1
    if uatype in uatype2struct:
92 1
        return pack_uatype_array_primitive(uatype2struct[uatype], value, length)
93 1
    b = []
94 1
    b.append(uatype_Int32.pack(length))
95 1
    for val in value:
96 1
        b.append(pack_uatype(uatype, val))
97 1
    return b"".join(b)
98
99
100 1
def pack_uatype(uatype, value):
101 1
    if uatype in uatype2struct:
102 1
        if value is None:
103
            value = 0
104 1
        return uatype2struct[uatype].pack(value)
105 1
    elif uatype == "Null":
106 1
        return b''
107 1
    elif uatype == "String":
108 1
        return pack_string(value)
109 1
    elif uatype in ("CharArray", "ByteString", "Custom"):
110 1
        return pack_bytes(value)
111 1
    elif uatype == "DateTime":
112 1
        return pack_datetime(value)
113 1
    elif uatype == "ExtensionObject":
114
        # dependency loop: classes in uaprotocol_auto use Variant defined in this file,
115
        # but Variant can contain any object from uaprotocol_auto as ExtensionObject.
116
        # Using local import to avoid import loop
117 1
        from opcua.ua.uaprotocol_auto import extensionobject_to_binary
118 1
        return extensionobject_to_binary(value)
119
    else:
120 1
        return value.to_binary()
121
122 1
uatype_Int8 = struct.Struct("<b")
123 1
uatype_SByte = uatype_Int8
124 1
uatype_Int16 = struct.Struct("<h")
125 1
uatype_Int32 = struct.Struct("<i")
126 1
uatype_Int64 = struct.Struct("<q")
127 1
uatype_UInt8 = struct.Struct("<B")
128 1
uatype_Char = uatype_UInt8
129 1
uatype_Byte = uatype_UInt8
130 1
uatype_UInt16 = struct.Struct("<H")
131 1
uatype_UInt32 = struct.Struct("<I")
132 1
uatype_UInt64 = struct.Struct("<Q")
133 1
uatype_Boolean = struct.Struct("<?")
134 1
uatype_Double = struct.Struct("<d")
135 1
uatype_Float = struct.Struct("<f")
136
137 1
uatype2struct = {
138
    "Int8": uatype_Int8,
139
    "SByte": uatype_SByte,
140
    "Int16": uatype_Int16,
141
    "Int32": uatype_Int32,
142
    "Int64": uatype_Int64,
143
    "UInt8": uatype_UInt8,
144
    "Char": uatype_Char,
145
    "Byte": uatype_Byte,
146
    "UInt16": uatype_UInt16,
147
    "UInt32": uatype_UInt32,
148
    "UInt64": uatype_UInt64,
149
    "Boolean": uatype_Boolean,
150
    "Double": uatype_Double,
151
    "Float": uatype_Float,
152
}
153
154
155 1
def unpack_uatype(uatype, data):
156 1
    if uatype in uatype2struct:
157 1
        st = uatype2struct[uatype]
158 1
        return st.unpack(data.read(st.size))[0]
159 1
    elif uatype == "String":
160 1
        return unpack_string(data)
161 1
    elif uatype in ("CharArray", "ByteString", "Custom"):
162 1
        return unpack_bytes(data)
163 1
    elif uatype == "DateTime":
164 1
        return unpack_datetime(data)
165 1
    elif uatype == "ExtensionObject":
166
        # dependency loop: classes in uaprotocol_auto use Variant defined in this file,
167
        # but Variant can contain any object from uaprotocol_auto as ExtensionObject.
168
        # Using local import to avoid import loop
169 1
        from opcua.ua.uaprotocol_auto import extensionobject_from_binary
170 1
        return extensionobject_from_binary(data)
171
    else:
172 1
        glbs = globals()
173 1
        if uatype in glbs:
174 1
            klass = glbs[uatype]
175 1
            if hasattr(klass, 'from_binary'):
176 1
                return klass.from_binary(data)
177
        raise UaError("can not unpack unknown uatype %s" % uatype)
178
179
180 1
def unpack_uatype_array(uatype, data):
181 1
    length = uatype_Int32.unpack(data.read(4))[0]
182 1
    if length == -1:
183
        return None
184 1
    elif length == 0:
185 1
        return []
186 1
    elif uatype in uatype2struct:
187 1
        st = uatype2struct[uatype]
188 1
        if length == 1:
189 1
            return list(st.unpack(data.read(st.size)))
190
        else:
191 1
            arrst = struct.Struct(build_array_format("<", length, st.format[1]))
192 1
            return list(arrst.unpack(data.read(arrst.size)))
193
    else:
194 1
        result = []
195 1
        for _ in range(0, length):
196 1
            result.append(unpack_uatype(uatype, data))
197 1
        return result
198
199
200 1
def pack_datetime(value):
201 1
    epch = datetime_to_win_epoch(value)
202 1
    return uatype_Int64.pack(epch)
203
204
205 1
def unpack_datetime(data):
206 1
    epch = uatype_Int64.unpack(data.read(8))[0]
207 1
    return win_epoch_to_datetime(epch)
208
209
210 1
def pack_string(string):
211 1
    if isinstance(string, unicode):
212 1
        string = string.encode('utf-8')
213 1
    length = len(string)
214 1
    if length == 0:
215 1
        return b'\xff\xff\xff\xff'
216 1
    return uatype_Int32.pack(length) + string
217
218 1
pack_bytes = pack_string
219
220
221 1
def unpack_bytes(data):
222 1
    length = uatype_Int32.unpack(data.read(4))[0]
223 1
    if length == -1:
224 1
        return b''
225 1
    return data.read(length)
226
227
228 1
def py3_unpack_string(data):
229 1
    b = unpack_bytes(data)
230 1
    return b.decode("utf-8")
231
232
233 1
if sys.version_info.major < 3:
234
    unpack_string = unpack_bytes
235
else:
236 1
    unpack_string = py3_unpack_string
237
238
239 1
def test_bit(data, offset):
240 1
    mask = 1 << offset
241 1
    return data & mask
242
243
244 1
def set_bit(data, offset):
245 1
    mask = 1 << offset
246 1
    return data | mask
247
248
249 1
class _FrozenClass(object):
250
251
    """
252
    make it impossible to add members to a class.
253
    This is a hack since I found out that most bugs are due to misspelling a variable in protocol
254
    """
255 1
    _freeze = False
256
257 1
    def __setattr__(self, key, value):
258 1
        if self._freeze and not hasattr(self, key):
259
            raise TypeError("Error adding member '{}' to class '{}', class is frozen, members are {}".format(key, self.__class__.__name__, self.__dict__.keys()))
260 1
        object.__setattr__(self, key, value)
261
262 1
if "PYOPCUA_NO_TYPO_CHECK" in os.environ:
263
    # typo check is cpu consuming, but it will make debug easy.
264
    # if typo check is not need (in production), please set env PYOPCUA_NO_TYPO_CHECK.
265
    # this will make all uatype class inherit from object intead of _FrozenClass
266
    # and skip the typo check.
267
    FrozenClass = object
268
else:
269 1
    FrozenClass = _FrozenClass
270
271
272 1
class Guid(FrozenClass):
273
274 1
    def __init__(self):
275 1
        self.uuid = uuid.uuid4()
276 1
        self._freeze = True
277
278 1
    def to_binary(self):
279 1
        return self.uuid.bytes
280
281 1
    def __hash__(self):
282
        return hash(self.uuid.bytes)
283
284 1
    @staticmethod
285
    def from_binary(data):
286 1
        g = Guid()
287 1
        g.uuid = uuid.UUID(bytes=data.read(16))
288 1
        return g
289
290 1
    def __eq__(self, other):
291 1
        return isinstance(other, Guid) and self.uuid == other.uuid
292
293
294 1
class StatusCode(FrozenClass):
295
296
    """
297
    :ivar value:
298
    :vartype value: int
299
    :ivar name:
300
    :vartype name: string
301
    :ivar doc:
302
    :vartype doc: string
303
    """
304
305 1
    def __init__(self, value=0):
306 1
        self.value = value
307 1
        self.name, self.doc = status_codes.get_name_and_doc(value)
308 1
        self._freeze = True
309
310 1
    def to_binary(self):
311 1
        return uatype_UInt32.pack(self.value)
312
313 1
    @staticmethod
314
    def from_binary(data):
315 1
        val = uatype_UInt32.unpack(data.read(4))[0]
316 1
        sc = StatusCode(val)
317 1
        return sc
318
319 1
    def check(self):
320
        """
321
        raise en exception if status code is anything else than 0
322
        use is is_good() method if not exception is desired
323
        """
324 1
        if self.value != 0:
325 1
            raise UaStatusCodeError("{}({})".format(self.doc, self.name))
326
327 1
    def is_good(self):
328
        """
329
        return True if status is Good.
330
        """
331 1
        if self.value == 0:
332 1
            return True
333 1
        return False
334
335 1
    def __str__(self):
336
        return 'StatusCode({})'.format(self.name)
337 1
    __repr__ = __str__
338
339 1
    def __eq__(self, other):
340
        return self.value == other.value
341
342 1
    def __ne__(self, other):
343
        return not self.__eq__(other)
344
345
346 1
class NodeIdType(Enum):
347 1
    TwoByte = 0
348 1
    FourByte = 1
349 1
    Numeric = 2
350 1
    String = 3
351 1
    Guid = 4
352 1
    ByteString = 5
353
354
355 1
class NodeId(FrozenClass):
356
357
    """
358
    NodeId Object
359
360
    Args:
361
        identifier: The identifier might be an int, a string, bytes or a Guid
362
        namespaceidx(int): The index of the namespace
363
        nodeidtype(NodeIdType): The type of the nodeid if it cannor be guess or you want something special like twobyte nodeid or fourbytenodeid
364
365
366
    :ivar Identifier:
367
    :vartype Identifier: NodeId
368
    :ivar NamespaceIndex:
369
    :vartype NamespaceIndex: Int
370
    :ivar NamespaceUri:
371
    :vartype NamespaceUri: String
372
    :ivar ServerIndex:
373
    :vartype ServerIndex: Int
374
    """
375
376 1
    def __init__(self, identifier=None, namespaceidx=0, nodeidtype=None):
377 1
        self.Identifier = identifier
378 1
        self.NamespaceIndex = namespaceidx
379 1
        self.NodeIdType = nodeidtype
380 1
        self.NamespaceUri = ""
381 1
        self.ServerIndex = 0
382 1
        self._freeze = True
383 1
        if not isinstance(self.NamespaceIndex, int):
384 1
            raise UaError("NamespaceIndex must be an int")
385 1
        if self.Identifier is None:
386 1
            self.Identifier = 0
387 1
            self.NodeIdType = NodeIdType.TwoByte
388 1
            return
389 1
        if self.NodeIdType is None:
390 1
            if isinstance(self.Identifier, int):
391 1
                self.NodeIdType = NodeIdType.Numeric
392 1
            elif isinstance(self.Identifier, str):
393 1
                self.NodeIdType = NodeIdType.String
394
            elif isinstance(self.Identifier, bytes):
395
                self.NodeIdType = NodeIdType.ByteString
396
            else:
397
                raise UaError("NodeId: Could not guess type of NodeId, set NodeIdType")
398
399 1
    def __key(self):
400 1
        if self.NodeIdType in (NodeIdType.TwoByte, NodeIdType.FourByte, NodeIdType.Numeric):  # twobyte, fourbyte and numeric may represent the same node
401 1
            return self.NamespaceIndex, self.Identifier
402
        else:
403 1
            return self.NodeIdType, self.NamespaceIndex, self.Identifier
404
405 1
    def __eq__(self, node):
406 1
        return isinstance(node, NodeId) and self.__key() == node.__key()
407
408 1
    def __ne__(self, other):
409 1
        return not self.__eq__(other)
410
411 1
    def __hash__(self):
412 1
        return hash(self.__key())
413
414 1
    def is_null(self):
415 1
        ret = True
416 1
        if self.NamespaceIndex != 0:
417 1
            ret = False
418 1
        if self.NodeIdType  in (NodeIdType.TwoByte, NodeIdType.FourByte, NodeIdType.Numeric):
419 1
            if self.Identifier != 0:
420 1
                ret = False
421 1
        elif self.NodeIdType is NodeIdType.String:
422 1
            if self.Identifier or self.Identifier != '':
423 1
                ret = False
424
        elif self.NodeIdType is NodeIdType.ByteString:
425
            if not len(self.Identifier):
426
                ret = False
427 1
        return ret
428
429 1
    @staticmethod
430
    def from_string(string):
431 1
        try:
432 1
            return NodeId._from_string(string)
433 1
        except ValueError as ex:
434 1
            raise UaStringParsingError("Error parsing string {}".format(string), ex)
435
436 1
    @staticmethod
437
    def _from_string(string):
438 1
        l = string.split(";")
439 1
        identifier = None
440 1
        namespace = 0
441 1
        ntype = None
442 1
        srv = None
443 1
        nsu = None
444 1
        for el in l:
445 1
            if not el:
446 1
                continue
447 1
            k, v = el.split("=", 1)
448 1
            k = k.strip()
449 1
            v = v.strip()
450 1
            if k == "ns":
451 1
                namespace = int(v)
452 1
            elif k == "i":
453 1
                ntype = NodeIdType.Numeric
454 1
                identifier = int(v)
455 1
            elif k == "s":
456 1
                ntype = NodeIdType.String
457 1
                identifier = v
458 1
            elif k == "g":
459
                ntype = NodeIdType.Guid
460
                identifier = v
461 1
            elif k == "b":
462
                ntype = NodeIdType.ByteString
463
                identifier = v
464 1
            elif k == "srv":
465 1
                srv = v
466
            elif k == "nsu":
467
                nsu = v
468 1
        if identifier is None:
469 1
            raise UaStringParsingError("Could not find identifier in string: " + string)
470 1
        nodeid = NodeId(identifier, namespace, ntype)
471 1
        nodeid.NamespaceUri = nsu
472 1
        nodeid.ServerIndex = srv
473 1
        return nodeid
474
475 1
    def to_string(self):
476 1
        string = ""
477 1
        if self.NamespaceIndex != 0:
478 1
            string += "ns={};".format(self.NamespaceIndex)
479 1
        ntype = None
480 1
        if self.NodeIdType == NodeIdType.Numeric:
481
            ntype = "i"
482 1
        elif self.NodeIdType == NodeIdType.String:
483 1
            ntype = "s"
484 1
        elif self.NodeIdType == NodeIdType.TwoByte:
485 1
            ntype = "i"
486 1
        elif self.NodeIdType == NodeIdType.FourByte:
487 1
            ntype = "i"
488
        elif self.NodeIdType == NodeIdType.Guid:
489
            ntype = "g"
490
        elif self.NodeIdType == NodeIdType.ByteString:
491
            ntype = "b"
492 1
        string += "{}={}".format(ntype, self.Identifier)
493 1
        if self.ServerIndex:
494
            string = "srv=" + str(self.ServerIndex) + string
495 1
        if self.NamespaceUri:
496
            string += "nsu={}".format(self.NamespaceUri)
497 1
        return string
498
499 1
    def __str__(self):
500 1
        return "{}NodeId({})".format(self.NodeIdType.name, self.to_string())
501 1
    __repr__ = __str__
502
503 1
    def to_binary(self):
504 1
        if self.NodeIdType == NodeIdType.TwoByte:
505 1
            return struct.pack("<BB", self.NodeIdType.value, self.Identifier)
506 1
        elif self.NodeIdType == NodeIdType.FourByte:
507 1
            return struct.pack("<BBH", self.NodeIdType.value, self.NamespaceIndex, self.Identifier)
508 1
        elif self.NodeIdType == NodeIdType.Numeric:
509 1
            return struct.pack("<BHI", self.NodeIdType.value, self.NamespaceIndex, self.Identifier)
510 1
        elif self.NodeIdType == NodeIdType.String:
511 1
            return struct.pack("<BH", self.NodeIdType.value, self.NamespaceIndex) + \
512
                pack_string(self.Identifier)
513 1
        elif self.NodeIdType == NodeIdType.ByteString:
514 1
            return struct.pack("<BH", self.NodeIdType.value, self.NamespaceIndex) + \
515
                pack_bytes(self.Identifier)
516
        else:
517 1
            return struct.pack("<BH", self.NodeIdType.value, self.NamespaceIndex) + \
518
                self.Identifier.to_binary()
519
        #FIXME: Missing NNamespaceURI and ServerIndex
520
521 1
    @staticmethod
522
    def from_binary(data):
523 1
        nid = NodeId()
524 1
        encoding = ord(data.read(1))
525 1
        nid.NodeIdType = NodeIdType(encoding & 0b00111111)
526
527 1
        if nid.NodeIdType == NodeIdType.TwoByte:
528 1
            nid.Identifier = ord(data.read(1))
529 1
        elif nid.NodeIdType == NodeIdType.FourByte:
530 1
            nid.NamespaceIndex, nid.Identifier = struct.unpack("<BH", data.read(3))
531 1
        elif nid.NodeIdType == NodeIdType.Numeric:
532 1
            nid.NamespaceIndex, nid.Identifier = struct.unpack("<HI", data.read(6))
533 1
        elif nid.NodeIdType == NodeIdType.String:
534 1
            nid.NamespaceIndex = uatype_UInt16.unpack(data.read(2))[0]
535 1
            nid.Identifier = unpack_string(data)
536 1
        elif nid.NodeIdType == NodeIdType.ByteString:
537 1
            nid.NamespaceIndex = uatype_UInt16.unpack(data.read(2))[0]
538 1
            nid.Identifier = unpack_bytes(data)
539 1
        elif nid.NodeIdType == NodeIdType.Guid:
540 1
            nid.NamespaceIndex = uatype_UInt16.unpack(data.read(2))[0]
541 1
            nid.Identifier = Guid.from_binary(data)
542
        else:
543
            raise UaError("Unknown NodeId encoding: " + str(nid.NodeIdType))
544
545 1
        if test_bit(encoding, 7):
546
            nid.NamespaceUri = unpack_string(data)
547 1
        if test_bit(encoding, 6):
548
            nid.ServerIndex = uatype_UInt32.unpack(data.read(4))[0]
549
550 1
        return nid
551
552
553 1
class TwoByteNodeId(NodeId):
554
555 1
    def __init__(self, identifier):
556 1
        NodeId.__init__(self, identifier, 0, NodeIdType.TwoByte)
557
558
559 1
class FourByteNodeId(NodeId):
560
561 1
    def __init__(self, identifier, namespace=0):
562 1
        NodeId.__init__(self, identifier, namespace, NodeIdType.FourByte)
563
564
565 1
class NumericNodeId(NodeId):
566
567 1
    def __init__(self, identifier, namespace=0):
568 1
        NodeId.__init__(self, identifier, namespace, NodeIdType.Numeric)
569
570
571 1
class ByteStringNodeId(NodeId):
572
573 1
    def __init__(self, identifier, namespace=0):
574 1
        NodeId.__init__(self, identifier, namespace, NodeIdType.ByteString)
575
576
577 1
class GuidNodeId(NodeId):
578
579 1
    def __init__(self, identifier, namespace=0):
580 1
        NodeId.__init__(self, identifier, namespace, NodeIdType.Guid)
581
582
583 1
class StringNodeId(NodeId):
584
585 1
    def __init__(self, identifier, namespace=0):
586 1
        NodeId.__init__(self, identifier, namespace, NodeIdType.String)
587
588
589 1
ExpandedNodeId = NodeId
590
591
592 1
class QualifiedName(FrozenClass):
593
594
    '''
595
    A string qualified with a namespace index.
596
    '''
597
598 1
    def __init__(self, name="", namespaceidx=0):
599 1
        if not isinstance(namespaceidx, int):
600
            raise UaError("namespaceidx must be an int")
601 1
        self.NamespaceIndex = namespaceidx
602 1
        self.Name = name
603 1
        self._freeze = True
604
605 1
    def to_string(self):
606 1
        return "{}:{}".format(self.NamespaceIndex, self.Name)
607
608 1
    @staticmethod
609
    def from_string(string):
610 1
        if ":" in string:
611 1
            try:
612 1
                idx, name = string.split(":", 1)
613 1
                idx = int(idx)
614 1
            except (TypeError, ValueError) as ex:
615 1
                raise UaStringParsingError("Error parsing string {}".format(string), ex)
616
        else:
617 1
            idx = 0
618 1
            name = string
619 1
        return QualifiedName(name, idx)
620
621 1
    def to_binary(self):
622 1
        packet = []
623 1
        packet.append(uatype_UInt16.pack(self.NamespaceIndex))
624 1
        packet.append(pack_string(self.Name))
625 1
        return b''.join(packet)
626
627 1
    @staticmethod
628
    def from_binary(data):
629 1
        obj = QualifiedName()
630 1
        obj.NamespaceIndex = uatype_UInt16.unpack(data.read(2))[0]
631 1
        obj.Name = unpack_string(data)
632 1
        return obj
633
634 1
    def __eq__(self, bname):
635 1
        return isinstance(bname, QualifiedName) and self.Name == bname.Name and self.NamespaceIndex == bname.NamespaceIndex
636
637 1
    def __ne__(self, other):
638
        return not self.__eq__(other)
639
640 1
    def __str__(self):
641
        return 'QualifiedName({}:{})'.format(self.NamespaceIndex, self.Name)
642
643 1
    __repr__ = __str__
644
645
646 1
class LocalizedText(FrozenClass):
647
648
    '''
649
    A string qualified with a namespace index.
650
    '''
651
652 1
    def __init__(self, text=""):
653 1
        self.Encoding = 0
654 1
        self.Text = text
655 1
        if isinstance(self.Text, unicode):
656 1
            self.Text = self.Text.encode('utf-8')
657 1
        if self.Text:
658 1
            self.Encoding |= (1 << 1)
659 1
        self.Locale = b''
660 1
        self._freeze = True
661
662 1
    def to_binary(self):
663 1
        packet = []
664 1
        if self.Locale:
665
            self.Encoding |= (1 << 0)
666 1
        if self.Text:
667 1
            self.Encoding |= (1 << 1)
668 1
        packet.append(uatype_UInt8.pack(self.Encoding))
669 1
        if self.Locale:
670
            packet.append(pack_bytes(self.Locale))
671 1
        if self.Text:
672 1
            packet.append(pack_bytes(self.Text))
673 1
        return b''.join(packet)
674
675 1
    @staticmethod
676
    def from_binary(data):
677 1
        obj = LocalizedText()
678 1
        obj.Encoding = ord(data.read(1))
679 1
        if obj.Encoding & (1 << 0):
680
            obj.Locale = unpack_bytes(data)
681 1
        if obj.Encoding & (1 << 1):
682 1
            obj.Text = unpack_bytes(data)
683 1
        return obj
684
685 1
    def to_string(self):
686
        # FIXME: use local
687
        return self.Text.decode()
688
689 1
    def __str__(self):
690
        return 'LocalizedText(' + 'Encoding:' + str(self.Encoding) + ', ' + \
691
            'Locale:' + str(self.Locale) + ', ' + \
692
            'Text:' + str(self.Text) + ')'
693 1
    __repr__ = __str__
694
695 1
    def __eq__(self, other):
696 1
        if isinstance(other, LocalizedText) and self.Locale == other.Locale and self.Text == other.Text:
697 1
            return True
698 1
        return False
699
700 1
    def __ne__(self, other):
701 1
        return not self.__eq__(other)
702
703
704 1
class ExtensionObject(FrozenClass):
705
706
    '''
707
708
    Any UA object packed as an ExtensionObject
709
710
711
    :ivar TypeId:
712
    :vartype TypeId: NodeId
713
    :ivar Body:
714
    :vartype Body: bytes
715
716
    '''
717
718 1
    def __init__(self):
719 1
        self.TypeId = NodeId()
720 1
        self.Encoding = 0
721 1
        self.Body = b''
722 1
        self._freeze = True
723
724 1
    def to_binary(self):
725 1
        packet = []
726 1
        if self.Body:
727 1
            self.Encoding |= (1 << 0)
728 1
        packet.append(self.TypeId.to_binary())
729 1
        packet.append(pack_uatype('UInt8', self.Encoding))
730 1
        if self.Body:
731 1
            packet.append(pack_uatype('ByteString', self.Body))
732 1
        return b''.join(packet)
733
734 1
    @staticmethod
735
    def from_binary(data):
736
        obj = ExtensionObject()
737
        obj.TypeId = NodeId.from_binary(data)
738
        obj.Encoding = unpack_uatype('UInt8', data)
739
        if obj.Encoding & (1 << 0):
740
            obj.Body = unpack_uatype('ByteString', data)
741
        return obj
742
743 1
    @staticmethod
744
    def from_object(obj):
745
        ext = ExtensionObject()
746
        oid = getattr(ObjectIds, "{}_Encoding_DefaultBinary".format(obj.__class__.__name__))
747
        ext.TypeId = FourByteNodeId(oid)
748
        ext.Body = obj.to_binary()
749
        return ext
750
751 1
    def __str__(self):
752
        return 'ExtensionObject(' + 'TypeId:' + str(self.TypeId) + ', ' + \
753
            'Encoding:' + str(self.Encoding) + ', ' + str(len(self.Body)) + ' bytes)'
754
755 1
    __repr__ = __str__
756
757
758 1
class VariantType(Enum):
759
760
    '''
761
    The possible types of a variant.
762
763
    :ivar Null:
764
    :ivar Boolean:
765
    :ivar SByte:
766
    :ivar Byte:
767
    :ivar Int16:
768
    :ivar UInt16:
769
    :ivar Int32:
770
    :ivar UInt32:
771
    :ivar Int64:
772
    :ivar UInt64:
773
    :ivar Float:
774
    :ivar Double:
775
    :ivar String:
776
    :ivar DateTime:
777
    :ivar Guid:
778
    :ivar ByteString:
779
    :ivar XmlElement:
780
    :ivar NodeId:
781
    :ivar ExpandedNodeId:
782
    :ivar StatusCode:
783
    :ivar QualifiedName:
784
    :ivar LocalizedText:
785
    :ivar ExtensionObject:
786
    :ivar DataValue:
787
    :ivar Variant:
788
    :ivar DiagnosticInfo:
789
790
791
792
    '''
793 1
    Null = 0
794 1
    Boolean = 1
795 1
    SByte = 2
796 1
    Byte = 3
797 1
    Int16 = 4
798 1
    UInt16 = 5
799 1
    Int32 = 6
800 1
    UInt32 = 7
801 1
    Int64 = 8
802 1
    UInt64 = 9
803 1
    Float = 10
804 1
    Double = 11
805 1
    String = 12
806 1
    DateTime = 13
807 1
    Guid = 14
808 1
    ByteString = 15
809 1
    XmlElement = 16
810 1
    NodeId = 17
811 1
    ExpandedNodeId = 18
812 1
    StatusCode = 19
813 1
    QualifiedName = 20
814 1
    LocalizedText = 21
815 1
    ExtensionObject = 22
816 1
    DataValue = 23
817 1
    Variant = 24
818 1
    DiagnosticInfo = 25
819
820
821 1
class VariantTypeCustom(object):
822 1
    def __init__(self, val):
823 1
        self.name = "Custom"
824 1
        self.value = val
825 1
        if self.value > 0b00111111:
826 1
            raise UaError("Cannot create VariantType. VariantType must be %s > x > %s", 0b111111, 25)
827
828 1
    def __str__(self):
829
        return "VariantType.Custom:{}".format(self.value)
830 1
    __repr__ = __str__
831
832 1
    def __eq__(self, other):
833 1
        return self.value == other.value
834
835
836 1
class Variant(FrozenClass):
837
838
    """
839
    Create an OPC-UA Variant object.
840
    if no argument a Null Variant is created.
841
    if not variant type is given, attemps to guess type from python type
842
    if a variant is given as value, the new objects becomes a copy of the argument
843
844
    :ivar Value:
845
    :vartype Value: Any supported type
846
    :ivar VariantType:
847
    :vartype VariantType: VariantType
848
    """
849
850 1
    def __init__(self, value=None, varianttype=None, dimensions=None):
851 1
        self.Value = value
852 1
        self.VariantType = varianttype
853 1
        self.Dimensions = dimensions
854 1
        self._freeze = True
855 1
        if isinstance(value, Variant):
856 1
            self.Value = value.Value
857 1
            self.VariantType = value.VariantType
858 1
        if self.VariantType is None:
859 1
            self.VariantType = self._guess_type(self.Value)
860 1
        if self.Dimensions is None and type(self.Value) in (list, tuple):
861 1
            dims = get_shape(self.Value)
862 1
            if len(dims) > 1:
863 1
                self.Dimensions = dims
864
865 1
    def __eq__(self, other):
866 1
        if isinstance(other, Variant) and self.VariantType == other.VariantType and self.Value == other.Value:
867 1
            return True
868 1
        return False
869
870 1
    def __ne__(self, other):
871 1
        return not self.__eq__(other)
872
873 1
    def _guess_type(self, val):
874 1
        if isinstance(val, (list, tuple)):
875 1
            error_val = val
876 1
        while isinstance(val, (list, tuple)):
877 1
            if len(val) == 0:
878
                raise UaError("could not guess UA type of variable {}".format(error_val))
879 1
            val = val[0]
880 1
        if val is None:
881 1
            return VariantType.Null
882 1
        elif isinstance(val, bool):
883 1
            return VariantType.Boolean
884 1
        elif isinstance(val, float):
885 1
            return VariantType.Double
886 1
        elif isinstance(val, int):
887 1
            return VariantType.Int64
888 1
        elif type(val) in (str, unicode):
889 1
            return VariantType.String
890 1
        elif isinstance(val, bytes):
891 1
            return VariantType.ByteString
892 1
        elif isinstance(val, datetime):
893 1
            return VariantType.DateTime
894
        else:
895 1
            if isinstance(val, object):
896 1
                try:
897 1
                    return getattr(VariantType, val.__class__.__name__)
898 1
                except AttributeError:
899 1
                    return VariantType.ExtensionObject
900
            else:
901
                raise UaError("Could not guess UA type of {} with type {}, specify UA type".format(val, type(val)))
902
903 1
    def __str__(self):
904 1
        return "Variant(val:{!s},type:{})".format(self.Value, self.VariantType)
905 1
    __repr__ = __str__
906
907 1
    def to_binary(self):
908 1
        b = []
909 1
        encoding = self.VariantType.value & 0b111111
910 1
        if type(self.Value) in (list, tuple):
911 1
            if self.Dimensions is not None:
912 1
                encoding = set_bit(encoding, 6)
913 1
            encoding = set_bit(encoding, 7)
914 1
            b.append(uatype_UInt8.pack(encoding))
915 1
            b.append(pack_uatype_array(self.VariantType.name, flatten(self.Value)))
916 1
            if self.Dimensions is not None:
917 1
                b.append(pack_uatype_array("Int32", self.Dimensions))
918
        else:
919 1
            b.append(uatype_UInt8.pack(encoding))
920 1
            b.append(pack_uatype(self.VariantType.name, self.Value))
921
922 1
        return b"".join(b)
923
924 1
    @staticmethod
925
    def from_binary(data):
926 1
        dimensions = None
927 1
        encoding = ord(data.read(1))
928 1
        int_type = encoding & 0b00111111
929 1
        if int_type > 25:
930 1
            vtype = VariantTypeCustom(int_type)
931
        else:
932 1
            vtype = VariantType(int_type)
933 1
        if vtype == VariantType.Null:
934 1
            return Variant(None, vtype, encoding)
935 1
        if test_bit(encoding, 7):
936 1
            value = unpack_uatype_array(vtype.name, data)
937
        else:
938 1
            value = unpack_uatype(vtype.name, data)
939 1
        if test_bit(encoding, 6):
940 1
            dimensions = unpack_uatype_array("Int32", data)
941 1
            value = reshape(value, dimensions)
942
943 1
        return Variant(value, vtype, dimensions)
944
945
946 1
def reshape(flat, dims):
947 1
    subdims = dims[1:]
948 1
    subsize = 1
949 1
    for i in subdims:
950 1
        if i == 0:
951 1
            i = 1
952 1
        subsize *= i
953 1
    while dims[0] * subsize > len(flat):
954 1
        flat.append([])
955 1
    if not subdims or subdims == [0]:
956 1
        return flat
957 1
    return [reshape(flat[i: i + subsize], subdims) for i in range(0, len(flat), subsize)]
958
959
960 1
def _split_list(l, n):
961
    n = max(1, n)
962
    return [l[i:i + n] for i in range(0, len(l), n)]
963
964
965 1
def flatten_and_get_shape(mylist):
966
    dims = []
967
    dims.append(len(mylist))
968
    while isinstance(mylist[0], (list, tuple)):
969
        dims.append(len(mylist[0]))
970
        mylist = [item for sublist in mylist for item in sublist]
971
        if len(mylist) == 0:
972
            break
973
    return mylist, dims
974
975
976 1
def flatten(mylist):
977 1
    if len(mylist) == 0:
978 1
        return mylist
979 1
    while isinstance(mylist[0], (list, tuple)):
980 1
        mylist = [item for sublist in mylist for item in sublist]
981 1
        if len(mylist) == 0:
982 1
            break
983 1
    return mylist
984
985
986 1
def get_shape(mylist):
987 1
    dims = []
988 1
    while isinstance(mylist, (list, tuple)):
989 1
        dims.append(len(mylist))
990 1
        if len(mylist) == 0:
991 1
            break
992 1
        mylist = mylist[0]
993 1
    return dims
994
995
996 1
class XmlElement(FrozenClass):
997
    '''
998
    An XML element encoded as an UTF-8 string.
999
    '''
1000 1
    def __init__(self, binary=None):
1001
        if binary is not None:
1002
            self._binary_init(binary)
1003
            self._freeze = True
1004
            return
1005
        self.Value = []
1006
        self._freeze = True
1007
1008 1
    def to_binary(self):
1009
        return pack_string(self.Value)
1010
1011 1
    @staticmethod
1012
    def from_binary(data):
1013
        return XmlElement(data)
1014
1015 1
    def _binary_init(self, data):
1016
        self.Value = unpack_string(data)
1017
1018 1
    def __str__(self):
1019
        return 'XmlElement(Value:' + str(self.Value) + ')'
1020
1021 1
    __repr__ = __str__
1022
1023
1024
1025
1026 1
class DataValue(FrozenClass):
1027
1028
    '''
1029
    A value with an associated timestamp, and quality.
1030
    Automatically generated from xml , copied and modified here to fix errors in xml spec
1031
1032
    :ivar Value:
1033
    :vartype Value: Variant
1034
    :ivar StatusCode:
1035
    :vartype StatusCode: StatusCode
1036
    :ivar SourceTimestamp:
1037
    :vartype SourceTimestamp: datetime
1038
    :ivar SourcePicoSeconds:
1039
    :vartype SourcePicoSeconds: int
1040
    :ivar ServerTimestamp:
1041
    :vartype ServerTimestamp: datetime
1042
    :ivar ServerPicoseconds:
1043
    :vartype ServerPicoseconds: int
1044
1045
    '''
1046
1047 1
    def __init__(self, variant=None, status=None):
1048 1
        self.Encoding = 0
1049 1
        if not isinstance(variant, Variant):
1050 1
            variant = Variant(variant)
1051 1
        self.Value = variant
1052 1
        if status is None:
1053 1
            self.StatusCode = StatusCode()
1054
        else:
1055 1
            self.StatusCode = status
1056 1
        self.SourceTimestamp = None  # DateTime()
1057 1
        self.SourcePicoseconds = None
1058 1
        self.ServerTimestamp = None  # DateTime()
1059 1
        self.ServerPicoseconds = None
1060 1
        self._freeze = True
1061
1062 1
    def to_binary(self):
1063 1
        packet = []
1064 1
        if self.Value:
1065 1
            self.Encoding |= (1 << 0)
1066 1
        if self.StatusCode:
1067 1
            self.Encoding |= (1 << 1)
1068 1
        if self.SourceTimestamp:
1069 1
            self.Encoding |= (1 << 2)
1070 1
        if self.ServerTimestamp:
1071 1
            self.Encoding |= (1 << 3)
1072 1
        if self.SourcePicoseconds:
1073
            self.Encoding |= (1 << 4)
1074 1
        if self.ServerPicoseconds:
1075
            self.Encoding |= (1 << 5)
1076 1
        packet.append(uatype_UInt8.pack(self.Encoding))
1077 1
        if self.Value:
1078 1
            packet.append(self.Value.to_binary())
1079 1
        if self.StatusCode:
1080 1
            packet.append(self.StatusCode.to_binary())
1081 1
        if self.SourceTimestamp:
1082 1
            packet.append(pack_datetime(self.SourceTimestamp))  # self.SourceTimestamp.to_binary())
1083 1
        if self.ServerTimestamp:
1084 1
            packet.append(pack_datetime(self.ServerTimestamp))  # self.ServerTimestamp.to_binary())
1085 1
        if self.SourcePicoseconds:
1086
            packet.append(uatype_UInt16.pack(self.SourcePicoseconds))
1087 1
        if self.ServerPicoseconds:
1088
            packet.append(uatype_UInt16.pack(self.ServerPicoseconds))
1089 1
        return b''.join(packet)
1090
1091 1
    @staticmethod
1092
    def from_binary(data):
1093 1
        encoding = ord(data.read(1))
1094 1
        if encoding & (1 << 0):
1095 1
            value = Variant.from_binary(data)
1096
        else:
1097
            value = None
1098 1
        if encoding & (1 << 1):
1099 1
            status = StatusCode.from_binary(data)
1100
        else:
1101
            status = None
1102 1
        obj = DataValue(value, status)
1103 1
        obj.Encoding = encoding
1104 1
        if obj.Encoding & (1 << 2):
1105 1
            obj.SourceTimestamp = unpack_datetime(data)  # DateTime.from_binary(data)
1106 1
        if obj.Encoding & (1 << 3):
1107 1
            obj.ServerTimestamp = unpack_datetime(data)  # DateTime.from_binary(data)
1108 1
        if obj.Encoding & (1 << 4):
1109
            obj.SourcePicoseconds = uatype_UInt16.unpack(data.read(2))[0]
1110 1
        if obj.Encoding & (1 << 5):
1111
            obj.ServerPicoseconds = uatype_UInt16.unpack(data.read(2))[0]
1112 1
        return obj
1113
1114 1
    def __str__(self):
1115
        s = 'DataValue(Value:{}'.format(self.Value)
1116
        if self.StatusCode is not None:
1117
            s += ', StatusCode:{}'.format(self.StatusCode)
1118
        if self.SourceTimestamp is not None:
1119
            s += ', SourceTimestamp:{}'.format(self.SourceTimestamp)
1120
        if self.ServerTimestamp is not None:
1121
            s += ', ServerTimestamp:{}'.format(self.ServerTimestamp)
1122
        if self.SourcePicoseconds is not None:
1123
            s += ', SourcePicoseconds:{}'.format(self.SourcePicoseconds)
1124
        if self.ServerPicoseconds is not None:
1125
            s += ', ServerPicoseconds:{}'.format(self.ServerPicoseconds)
1126
        s += ')'
1127
        return s
1128
1129 1
    __repr__ = __str__
1130
1131
1132
# TODO: This should be autogeneratd form XML description of EventTypes
1133 1
class BaseEvent(FrozenClass):
1134
1135
    '''
1136
    BaseEvent implements BaseEventType from which inherit all other events and it is used per default.
1137
1138
    '''
1139
1140 1
    def __init__(self, sourcenode=NodeId(ObjectIds.Server), message=None, severity=1):
1141 1
        self.EventId = bytes()
1142 1
        self.EventType = NodeId(ObjectIds.BaseEventType)
1143 1
        self.SourceNode = sourcenode
1144 1
        self.SourceName = None
1145 1
        self.Time = None
1146 1
        self.RecieveTime = None
1147 1
        self.LocalTime = None
1148 1
        self.Message = LocalizedText(message)
1149 1
        self.Severity = Variant(severity, VariantType.UInt16)
1150
        # FIXME: Should be frozen but for now is not because of asigning parameters
1151
        #self._freeze = True
1152
1153 1
    def __str__(self):
1154
        s = 'BaseEventType(EventId:{}'.format(self.EventId)
1155
        s += ', EventType:{}'.format(self.EventType)
1156
        s += ', SourceNode:{}'.format(self.SourceNode)
1157
        s += ', SourceName:{}'.format(self.SourceName)
1158
        s += ', Time:{}'.format(self.Time)
1159
        s += ', RecieveTime:{}'.format(self.RecieveTime)
1160
        s += ', LocalTime:{}'.format(self.LocalTime)
1161
        s += ', Message:{}'.format(self.Message)
1162
        s += ', Severity:{}'.format(self.Severity)
1163
        s += ')'
1164
        return s
1165
    __repr__ = __str__
1166