summaryrefslogtreecommitdiff
path: root/smart_home/tplink_utils.py
blob: d8a75a2c3dd6d1b67808e88f62c4b6c037b885c9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#!/usr/bin/env python3

"""Wrapper functions for dealing with TPLink devices.  Based on code
written by Lubomir Stroetmann and Copyright 2016 softScheck GmbH which
was covered by the Apache 2.0 license:

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Modifications by Scott Gasch Copyright 2020-2022 also released under
the Apache 2.0 license as required by the license (see above).

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

"""

import json
import logging
import os
import re
import subprocess
import sys
from struct import pack
from typing import Dict, Optional

import logging_utils
from decorator_utils import timeout

logger = logging.getLogger(__name__)


@timeout(10.0, use_signals=False, error_message="Timed out waiting for tplink.py")
def tplink_command(command: str) -> bool:
    result = os.system(command)
    signal = result & 0xFF
    if signal != 0:
        msg = f'{command} died with signal {signal}'
        logger.warning(msg)
        logging_utils.hlog(msg)
        return False
    else:
        exit_value = result >> 8
        if exit_value != 0:
            msg = f'{command} failed, exited {exit_value}'
            logger.warning(msg)
            logging_utils.hlog(msg)
            return False
    logger.debug('%s succeeded.', command)
    return True


@timeout(10.0, use_signals=False, error_message="Timed out waiting for tplink.py")
def tplink_get_info(cmd: str) -> Optional[Dict]:
    logger.debug('Getting tplink device status via "%s"', cmd)
    try:
        out = subprocess.getoutput(cmd)
        logger.debug('RAW OUT> %s', out)
        out = re.sub("Sent:.*\n", "", out)
        out = re.sub("Received: *", "", out)
        info = json.loads(out)["system"]["get_sysinfo"]
        logger.debug("%s", json.dumps(info, indent=4, sort_keys=True))
        return info
    except Exception as e:
        logger.exception(e)
        print(out, file=sys.stderr)
        return None


def encrypt(string: str) -> bytes:
    """Encryption and Decryption of TP-Link Smart Home Protocol.

    XOR Autokey Cipher with starting key = 171

    >>> " ".join(hex(b).replace('0x', '') for b in encrypt('{"system":{"get_sysinfo":{}}}'))
    '0 0 0 1d d0 f2 81 f8 8b ff 9a f7 d5 ef 94 b6 d1 b4 c0 9f ec 95 e6 8f e1 87 e8 ca f0 8b f6 8b f6'

    """
    key = 171
    result = pack(">I", len(string))
    for i in string:
        a = key ^ ord(i)
        key = a
        result += bytes([a])
    return result


def decrypt(string: bytes) -> str:
    """Opposite of encrypt (above).

    >>> b = encrypt('hi, mom')
    >>> s = decrypt(b[4:])
    >>> s
    'hi, mom'

    """
    key = 171
    result = ""
    for i in string:
        a = key ^ i
        key = i
        result += chr(a)
    return result


if __name__ == '__main__':
    import doctest

    doctest.testmod()