tplinkrouterc6u 5.0.3__py3-none-any.whl → 5.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- test/{test_client.py → test_client_c6u.py} +2 -1
- test/test_client_ex.py +339 -0
- test/test_client_mr.py +365 -0
- test/test_client_xdr.py +536 -0
- tplinkrouterc6u/__init__.py +16 -12
- tplinkrouterc6u/client/__init__.py +1 -0
- tplinkrouterc6u/client/c1200.py +102 -0
- tplinkrouterc6u/client/c5400x.py +109 -0
- tplinkrouterc6u/client/c6u.py +436 -0
- tplinkrouterc6u/client/c6v4.py +38 -0
- tplinkrouterc6u/client/deco.py +177 -0
- tplinkrouterc6u/client/ex.py +295 -0
- tplinkrouterc6u/client/mr.py +712 -0
- tplinkrouterc6u/client/xdr.py +263 -0
- tplinkrouterc6u/client_abstract.py +48 -0
- tplinkrouterc6u/common/__init__.py +1 -0
- tplinkrouterc6u/{dataclass.py → common/dataclass.py} +40 -1
- tplinkrouterc6u/{package_enum.py → common/package_enum.py} +5 -0
- tplinkrouterc6u/provider.py +39 -0
- {tplinkrouterc6u-5.0.3.dist-info → tplinkrouterc6u-5.2.0.dist-info}/METADATA +52 -2
- tplinkrouterc6u-5.2.0.dist-info/RECORD +30 -0
- {tplinkrouterc6u-5.0.3.dist-info → tplinkrouterc6u-5.2.0.dist-info}/WHEEL +1 -1
- tplinkrouterc6u/client.py +0 -1451
- tplinkrouterc6u-5.0.3.dist-info/RECORD +0 -17
- /tplinkrouterc6u/{encryption.py → common/encryption.py} +0 -0
- /tplinkrouterc6u/{exception.py → common/exception.py} +0 -0
- /tplinkrouterc6u/{helper.py → common/helper.py} +0 -0
- {tplinkrouterc6u-5.0.3.dist-info → tplinkrouterc6u-5.2.0.dist-info}/LICENSE +0 -0
- {tplinkrouterc6u-5.0.3.dist-info → tplinkrouterc6u-5.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from re import search
|
|
2
|
+
from requests import post
|
|
3
|
+
from tplinkrouterc6u.common.package_enum import Connection
|
|
4
|
+
from tplinkrouterc6u.common.exception import ClientException
|
|
5
|
+
from tplinkrouterc6u.client.c6u import TplinkBaseRouter
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TplinkC5400XRouter(TplinkBaseRouter):
|
|
9
|
+
def supports(self) -> bool:
|
|
10
|
+
return len(self.password) >= 200
|
|
11
|
+
|
|
12
|
+
def authorize(self) -> None:
|
|
13
|
+
if len(self.password) < 200:
|
|
14
|
+
raise Exception('You need to use web encrypted password instead. Check the documentation!')
|
|
15
|
+
|
|
16
|
+
url = '{}/cgi-bin/luci/;stok=/login?form=login'.format(self.host)
|
|
17
|
+
|
|
18
|
+
response = post(
|
|
19
|
+
url,
|
|
20
|
+
params={'operation': 'login', 'username': self.username, 'password': self.password},
|
|
21
|
+
headers=self._headers_login,
|
|
22
|
+
timeout=self.timeout,
|
|
23
|
+
verify=self._verify_ssl,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
self._stok = response.json().get('data').get('stok')
|
|
28
|
+
regex_result = search('sysauth=(.*);', response.headers['set-cookie'])
|
|
29
|
+
self._sysauth = regex_result.group(1)
|
|
30
|
+
self._logged = True
|
|
31
|
+
self._smart_network = False
|
|
32
|
+
|
|
33
|
+
except Exception as e:
|
|
34
|
+
error = "TplinkRouter - C5400X - Cannot authorize! Error - {}; Response - {}".format(e, response.text)
|
|
35
|
+
if self._logger:
|
|
36
|
+
self._logger.debug(error)
|
|
37
|
+
raise ClientException(error)
|
|
38
|
+
|
|
39
|
+
def set_led(self, enable: bool) -> None:
|
|
40
|
+
current_state = (self.request('admin/ledgeneral?form=setting&operation=read', 'operation=read')
|
|
41
|
+
.get('enable', 'off') == 'on')
|
|
42
|
+
if current_state != enable:
|
|
43
|
+
self.request('admin/ledgeneral?form=setting&operation=write', 'operation=write')
|
|
44
|
+
|
|
45
|
+
def get_led(self) -> bool:
|
|
46
|
+
|
|
47
|
+
data = self.request('admin/ledgeneral?form=setting&operation=read', 'operation=read')
|
|
48
|
+
led_status = data.get('enable') if 'enable' in data else None
|
|
49
|
+
if led_status == 'on':
|
|
50
|
+
return True
|
|
51
|
+
elif led_status == 'off':
|
|
52
|
+
return False
|
|
53
|
+
else:
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
def set_wifi(self, wifi: Connection, enable: bool = None, ssid: str = None, hidden: str = None,
|
|
57
|
+
encryption: str = None, psk_version: str = None, psk_cipher: str = None, psk_key: str = None,
|
|
58
|
+
hwmode: str = None, htmode: str = None, channel: int = None, txpower: str = None,
|
|
59
|
+
disabled_all: str = None) -> None:
|
|
60
|
+
values = {
|
|
61
|
+
Connection.HOST_2G: 'wireless_2g',
|
|
62
|
+
Connection.HOST_5G: 'wireless_5g',
|
|
63
|
+
Connection.HOST_6G: 'wireless_6g',
|
|
64
|
+
Connection.GUEST_2G: 'guest_2g',
|
|
65
|
+
Connection.GUEST_5G: 'guest_5g',
|
|
66
|
+
Connection.GUEST_6G: 'guest_6g',
|
|
67
|
+
Connection.IOT_2G: 'iot_2g',
|
|
68
|
+
Connection.IOT_5G: 'iot_5g',
|
|
69
|
+
Connection.IOT_6G: 'iot_6g',
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
value = values.get(wifi)
|
|
73
|
+
if not value:
|
|
74
|
+
raise ValueError(f"Invalid Wi-Fi connection type: {wifi}")
|
|
75
|
+
|
|
76
|
+
if all(v is None for v in [enable, ssid, hidden, encryption, psk_version, psk_cipher, psk_key, hwmode,
|
|
77
|
+
htmode, channel, txpower, disabled_all]):
|
|
78
|
+
raise ValueError("At least one wireless setting must be provided")
|
|
79
|
+
|
|
80
|
+
data = "operation=write"
|
|
81
|
+
|
|
82
|
+
if enable is not None:
|
|
83
|
+
data += f"&enable={'on' if enable else 'off'}"
|
|
84
|
+
if ssid is not None:
|
|
85
|
+
data += f"&ssid={ssid}"
|
|
86
|
+
if hidden is not None:
|
|
87
|
+
data += f"&hidden={hidden}"
|
|
88
|
+
if encryption is not None:
|
|
89
|
+
data += f"&encryption={encryption}"
|
|
90
|
+
if psk_version is not None:
|
|
91
|
+
data += f"&psk_version={psk_version}"
|
|
92
|
+
if psk_cipher is not None:
|
|
93
|
+
data += f"&psk_cipher={psk_cipher}"
|
|
94
|
+
if psk_key is not None:
|
|
95
|
+
data += f"&psk_key={psk_key}"
|
|
96
|
+
if hwmode is not None:
|
|
97
|
+
data += f"&hwmode={hwmode}"
|
|
98
|
+
if htmode is not None:
|
|
99
|
+
data += f"&htmode={htmode}"
|
|
100
|
+
if channel is not None:
|
|
101
|
+
data += f"&channel={channel}"
|
|
102
|
+
if txpower is not None:
|
|
103
|
+
data += f"&txpower={txpower}"
|
|
104
|
+
if disabled_all is not None:
|
|
105
|
+
data += f"&disabled_all={disabled_all}"
|
|
106
|
+
|
|
107
|
+
path = f"admin/wireless?form={value}&{data}"
|
|
108
|
+
|
|
109
|
+
self.request(path, data)
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
from hashlib import md5
|
|
2
|
+
from re import search
|
|
3
|
+
from json import loads
|
|
4
|
+
from requests import post, Response
|
|
5
|
+
from macaddress import EUI48
|
|
6
|
+
from ipaddress import IPv4Address
|
|
7
|
+
from logging import Logger
|
|
8
|
+
from tplinkrouterc6u.common.helper import get_ip, get_mac
|
|
9
|
+
from tplinkrouterc6u.common.encryption import EncryptionWrapper
|
|
10
|
+
from tplinkrouterc6u.common.package_enum import Connection
|
|
11
|
+
from tplinkrouterc6u.common.dataclass import Firmware, Status, Device, IPv4Reservation, IPv4DHCPLease, IPv4Status
|
|
12
|
+
from tplinkrouterc6u.common.exception import ClientException, ClientError
|
|
13
|
+
from tplinkrouterc6u.client_abstract import AbstractRouter
|
|
14
|
+
from abc import abstractmethod
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TplinkRequest:
|
|
18
|
+
host = ''
|
|
19
|
+
_stok = ''
|
|
20
|
+
timeout = 10
|
|
21
|
+
_logged = False
|
|
22
|
+
_sysauth = None
|
|
23
|
+
_verify_ssl = False
|
|
24
|
+
_logger = None
|
|
25
|
+
_headers_request = {}
|
|
26
|
+
_headers_login = {}
|
|
27
|
+
_data_block = 'data'
|
|
28
|
+
|
|
29
|
+
def request(self, path: str, data: str, ignore_response: bool = False, ignore_errors: bool = False) -> dict | None:
|
|
30
|
+
if self._logged is False:
|
|
31
|
+
raise Exception('Not authorised')
|
|
32
|
+
url = '{}/cgi-bin/luci/;stok={}/{}'.format(self.host, self._stok, path)
|
|
33
|
+
|
|
34
|
+
response = post(
|
|
35
|
+
url,
|
|
36
|
+
data=self._prepare_data(data),
|
|
37
|
+
headers=self._headers_request,
|
|
38
|
+
cookies={'sysauth': self._sysauth},
|
|
39
|
+
timeout=self.timeout,
|
|
40
|
+
verify=self._verify_ssl,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if ignore_response:
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
data = response.text
|
|
47
|
+
error = ''
|
|
48
|
+
try:
|
|
49
|
+
data = response.json()
|
|
50
|
+
if 'data' not in data:
|
|
51
|
+
raise Exception("Router didn't respond with JSON")
|
|
52
|
+
data = self._decrypt_response(data)
|
|
53
|
+
|
|
54
|
+
if self._is_valid_response(data):
|
|
55
|
+
return data.get(self._data_block)
|
|
56
|
+
elif ignore_errors:
|
|
57
|
+
return data
|
|
58
|
+
except Exception as e:
|
|
59
|
+
error = ('TplinkRouter - {} - An unknown response - {}; Request {} - Response {}'
|
|
60
|
+
.format(self.__class__.__name__, e, path, data))
|
|
61
|
+
error = ('TplinkRouter - {} - Response with error; Request {} - Response {}'
|
|
62
|
+
.format(self.__class__.__name__, path, data)) if not error else error
|
|
63
|
+
if self._logger:
|
|
64
|
+
self._logger.debug(error)
|
|
65
|
+
raise ClientError(error)
|
|
66
|
+
|
|
67
|
+
def _is_valid_response(self, data: dict) -> bool:
|
|
68
|
+
return 'success' in data and data['success'] and self._data_block in data
|
|
69
|
+
|
|
70
|
+
def _prepare_data(self, data: str):
|
|
71
|
+
return data
|
|
72
|
+
|
|
73
|
+
def _decrypt_response(self, data: dict) -> dict:
|
|
74
|
+
return data
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TplinkEncryption(TplinkRequest):
|
|
78
|
+
username = ''
|
|
79
|
+
password = ''
|
|
80
|
+
nn = ''
|
|
81
|
+
ee = ''
|
|
82
|
+
_seq = ''
|
|
83
|
+
_pwdNN = ''
|
|
84
|
+
_pwdEE = ''
|
|
85
|
+
_encryption = EncryptionWrapper()
|
|
86
|
+
|
|
87
|
+
def supports(self) -> bool:
|
|
88
|
+
if len(self.password) > 125:
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
self._request_pwd()
|
|
93
|
+
return True
|
|
94
|
+
except ClientException:
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
def authorize(self) -> None:
|
|
98
|
+
if self._pwdNN == '':
|
|
99
|
+
self._request_pwd()
|
|
100
|
+
|
|
101
|
+
if self._seq == '':
|
|
102
|
+
self._request_seq()
|
|
103
|
+
|
|
104
|
+
response = self._try_login()
|
|
105
|
+
|
|
106
|
+
is_valid_json = False
|
|
107
|
+
try:
|
|
108
|
+
response.json()
|
|
109
|
+
is_valid_json = True
|
|
110
|
+
except BaseException:
|
|
111
|
+
"""Ignore"""
|
|
112
|
+
|
|
113
|
+
if is_valid_json is False or response.status_code == 403:
|
|
114
|
+
self._logged = False
|
|
115
|
+
self._request_pwd()
|
|
116
|
+
self._request_seq()
|
|
117
|
+
response = self._try_login()
|
|
118
|
+
|
|
119
|
+
data = response.text
|
|
120
|
+
try:
|
|
121
|
+
data = response.json()
|
|
122
|
+
data = self._decrypt_response(data)
|
|
123
|
+
|
|
124
|
+
self._stok = data[self._data_block]['stok']
|
|
125
|
+
regex_result = search(
|
|
126
|
+
'sysauth=(.*);', response.headers['set-cookie'])
|
|
127
|
+
self._sysauth = regex_result.group(1)
|
|
128
|
+
self._logged = True
|
|
129
|
+
|
|
130
|
+
except Exception as e:
|
|
131
|
+
error = ("TplinkRouter - {} - Cannot authorize! Error - {}; Response - {}"
|
|
132
|
+
.format(self.__class__.__name__, e, data))
|
|
133
|
+
if self._logger:
|
|
134
|
+
self._logger.debug(error)
|
|
135
|
+
raise ClientException(error)
|
|
136
|
+
|
|
137
|
+
def _request_pwd(self) -> None:
|
|
138
|
+
url = '{}/cgi-bin/luci/;stok=/login?form=keys'.format(self.host)
|
|
139
|
+
|
|
140
|
+
# If possible implement RSA encryption of password here.
|
|
141
|
+
response = post(
|
|
142
|
+
url, params={'operation': 'read'},
|
|
143
|
+
timeout=self.timeout,
|
|
144
|
+
verify=self._verify_ssl,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
data = response.json()
|
|
149
|
+
|
|
150
|
+
args = data[self._data_block]['password']
|
|
151
|
+
|
|
152
|
+
self._pwdNN = args[0]
|
|
153
|
+
self._pwdEE = args[1]
|
|
154
|
+
|
|
155
|
+
except Exception as e:
|
|
156
|
+
error = ('TplinkRouter - {} - Unknown error for pwd! Error - {}; Response - {}'
|
|
157
|
+
.format(self.__class__.__name__, e, response.text))
|
|
158
|
+
if self._logger:
|
|
159
|
+
self._logger.debug(error)
|
|
160
|
+
raise ClientException(error)
|
|
161
|
+
|
|
162
|
+
def _request_seq(self) -> None:
|
|
163
|
+
url = '{}/cgi-bin/luci/;stok=/login?form=auth'.format(self.host)
|
|
164
|
+
|
|
165
|
+
# If possible implement RSA encryption of password here.
|
|
166
|
+
response = post(
|
|
167
|
+
url,
|
|
168
|
+
params={'operation': 'read'},
|
|
169
|
+
timeout=self.timeout,
|
|
170
|
+
verify=self._verify_ssl,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
data = response.json()
|
|
175
|
+
|
|
176
|
+
self._seq = data[self._data_block]['seq']
|
|
177
|
+
args = data[self._data_block]['key']
|
|
178
|
+
|
|
179
|
+
self.nn = args[0]
|
|
180
|
+
self.ee = args[1]
|
|
181
|
+
|
|
182
|
+
except Exception as e:
|
|
183
|
+
error = ('TplinkRouter - {} - Unknown error for seq! Error - {}; Response - {}'
|
|
184
|
+
.format(self.__class__.__name__, e, response.text))
|
|
185
|
+
if self._logger:
|
|
186
|
+
self._logger.debug(error)
|
|
187
|
+
raise ClientException(error)
|
|
188
|
+
|
|
189
|
+
def _try_login(self) -> Response:
|
|
190
|
+
url = '{}/cgi-bin/luci/;stok=/login?form=login'.format(self.host)
|
|
191
|
+
|
|
192
|
+
crypted_pwd = self._encryption.rsa_encrypt(self.password, self._pwdNN, self._pwdEE)
|
|
193
|
+
|
|
194
|
+
body = self._prepare_data(self._get_login_data(crypted_pwd))
|
|
195
|
+
|
|
196
|
+
return post(
|
|
197
|
+
url,
|
|
198
|
+
data=body,
|
|
199
|
+
headers=self._headers_login,
|
|
200
|
+
timeout=self.timeout,
|
|
201
|
+
verify=self._verify_ssl,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
@staticmethod
|
|
205
|
+
def _get_login_data(crypted_pwd: str) -> str:
|
|
206
|
+
return 'operation=login&password={}&confirm=true'.format(crypted_pwd)
|
|
207
|
+
|
|
208
|
+
def _prepare_data(self, data: str) -> dict:
|
|
209
|
+
encrypted_data = self._encryption.aes_encrypt(data)
|
|
210
|
+
data_len = len(encrypted_data)
|
|
211
|
+
hash = md5((self.username + self.password).encode()).hexdigest()
|
|
212
|
+
|
|
213
|
+
sign = self._encryption.get_signature(int(self._seq) + data_len,
|
|
214
|
+
True if self._logged is False else False,
|
|
215
|
+
hash, self.nn, self.ee)
|
|
216
|
+
|
|
217
|
+
return {'sign': sign, 'data': encrypted_data}
|
|
218
|
+
|
|
219
|
+
def _decrypt_response(self, data: dict) -> dict:
|
|
220
|
+
return loads(self._encryption.aes_decrypt(data['data']))
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class TplinkBaseRouter(AbstractRouter, TplinkRequest):
|
|
224
|
+
_smart_network = True
|
|
225
|
+
_perf_status = True
|
|
226
|
+
|
|
227
|
+
def __init__(self, host: str, password: str, username: str = 'admin', logger: Logger = None,
|
|
228
|
+
verify_ssl: bool = True, timeout: int = 30) -> None:
|
|
229
|
+
super().__init__(host, password, username, logger, verify_ssl, timeout)
|
|
230
|
+
|
|
231
|
+
self._url_firmware = 'admin/firmware?form=upgrade&operation=read'
|
|
232
|
+
self._url_ipv4_reservations = 'admin/dhcps?form=reservation&operation=load'
|
|
233
|
+
self._url_ipv4_dhcp_leases = 'admin/dhcps?form=client&operation=load'
|
|
234
|
+
referer = '{}/webpages/index.html'.format(self.host)
|
|
235
|
+
self._headers_request = {'Referer': referer}
|
|
236
|
+
self._headers_login = {'Referer': referer, 'Content-Type': 'application/x-www-form-urlencoded'}
|
|
237
|
+
|
|
238
|
+
@abstractmethod
|
|
239
|
+
def authorize(self) -> bool:
|
|
240
|
+
pass
|
|
241
|
+
|
|
242
|
+
def set_wifi(self, wifi: Connection, enable: bool) -> None:
|
|
243
|
+
values = {
|
|
244
|
+
Connection.HOST_2G: 'wireless_2g',
|
|
245
|
+
Connection.HOST_5G: 'wireless_5g',
|
|
246
|
+
Connection.HOST_6G: 'wireless_6g',
|
|
247
|
+
Connection.GUEST_2G: 'guest_2g',
|
|
248
|
+
Connection.GUEST_5G: 'guest_5g',
|
|
249
|
+
Connection.GUEST_6G: 'guest_6g',
|
|
250
|
+
Connection.IOT_2G: 'iot_2g',
|
|
251
|
+
Connection.IOT_5G: 'iot_5g',
|
|
252
|
+
Connection.IOT_6G: 'iot_6g',
|
|
253
|
+
}
|
|
254
|
+
value = values.get(wifi)
|
|
255
|
+
path = f"admin/wireless?&form=guest&form={value}"
|
|
256
|
+
data = f"operation=write&{value}_enable={'on' if enable else 'off'}"
|
|
257
|
+
self.request(path, data)
|
|
258
|
+
|
|
259
|
+
def reboot(self) -> None:
|
|
260
|
+
self.request('admin/system?form=reboot', 'operation=write', True)
|
|
261
|
+
|
|
262
|
+
def logout(self) -> None:
|
|
263
|
+
self.request('admin/system?form=logout', 'operation=write', True)
|
|
264
|
+
self._stok = ''
|
|
265
|
+
self._sysauth = ''
|
|
266
|
+
self._logged = False
|
|
267
|
+
|
|
268
|
+
def get_firmware(self) -> Firmware:
|
|
269
|
+
data = self.request(self._url_firmware, 'operation=read')
|
|
270
|
+
firmware = Firmware(data.get('hardware_version', ''), data.get('model', ''), data.get('firmware_version', ''))
|
|
271
|
+
|
|
272
|
+
return firmware
|
|
273
|
+
|
|
274
|
+
def get_status(self) -> Status:
|
|
275
|
+
data = self.request('admin/status?form=all&operation=read', 'operation=read')
|
|
276
|
+
|
|
277
|
+
status = Status()
|
|
278
|
+
status._wan_macaddr = EUI48(data['wan_macaddr']) if 'wan_macaddr' in data else None
|
|
279
|
+
status._lan_macaddr = EUI48(data['lan_macaddr'])
|
|
280
|
+
status._wan_ipv4_addr = IPv4Address(data['wan_ipv4_ipaddr']) if 'wan_ipv4_ipaddr' in data else None
|
|
281
|
+
status._lan_ipv4_addr = IPv4Address(data['lan_ipv4_ipaddr']) if 'lan_ipv4_ipaddr' in data else None
|
|
282
|
+
status._wan_ipv4_gateway = IPv4Address(
|
|
283
|
+
data['wan_ipv4_gateway']) if 'wan_ipv4_gateway' in data else None
|
|
284
|
+
status.wan_ipv4_uptime = data.get('wan_ipv4_uptime')
|
|
285
|
+
status.mem_usage = data.get('mem_usage')
|
|
286
|
+
status.cpu_usage = data.get('cpu_usage')
|
|
287
|
+
status.wired_total = len(data.get('access_devices_wired', []))
|
|
288
|
+
status.wifi_clients_total = len(data.get('access_devices_wireless_host', []))
|
|
289
|
+
status.guest_clients_total = len(data.get('access_devices_wireless_guest', []))
|
|
290
|
+
status.guest_2g_enable = self._str2bool(data.get('guest_2g_enable'))
|
|
291
|
+
status.guest_5g_enable = self._str2bool(data.get('guest_5g_enable'))
|
|
292
|
+
status.guest_6g_enable = self._str2bool(data.get('guest_6g_enable'))
|
|
293
|
+
status.iot_2g_enable = self._str2bool(data.get('iot_2g_enable'))
|
|
294
|
+
status.iot_5g_enable = self._str2bool(data.get('iot_5g_enable'))
|
|
295
|
+
status.iot_6g_enable = self._str2bool(data.get('iot_6g_enable'))
|
|
296
|
+
status.wifi_2g_enable = self._str2bool(data.get('wireless_2g_enable'))
|
|
297
|
+
status.wifi_5g_enable = self._str2bool(data.get('wireless_5g_enable'))
|
|
298
|
+
status.wifi_6g_enable = self._str2bool(data.get('wireless_6g_enable'))
|
|
299
|
+
|
|
300
|
+
if (status.mem_usage is None or status.mem_usage is None) and self._perf_status:
|
|
301
|
+
try:
|
|
302
|
+
performance = self.request('admin/status?form=perf&operation=read', 'operation=read')
|
|
303
|
+
status.mem_usage = performance.get('mem_usage')
|
|
304
|
+
status.cpu_usage = performance.get('cpu_usage')
|
|
305
|
+
except BaseException:
|
|
306
|
+
self._perf_status = False
|
|
307
|
+
|
|
308
|
+
devices = {}
|
|
309
|
+
|
|
310
|
+
def _add_device(conn: Connection, item: dict) -> None:
|
|
311
|
+
devices[item['macaddr']] = Device(conn, get_mac(item.get('macaddr', '00:00:00:00:00:00')),
|
|
312
|
+
get_ip(item['ipaddr']),
|
|
313
|
+
item['hostname'])
|
|
314
|
+
|
|
315
|
+
for item in data.get('access_devices_wired', []):
|
|
316
|
+
type = self._map_wire_type(item.get('wire_type'))
|
|
317
|
+
_add_device(type, item)
|
|
318
|
+
|
|
319
|
+
for item in data.get('access_devices_wireless_host', []):
|
|
320
|
+
type = self._map_wire_type(item.get('wire_type'))
|
|
321
|
+
_add_device(type, item)
|
|
322
|
+
|
|
323
|
+
for item in data.get('access_devices_wireless_guest', []):
|
|
324
|
+
type = self._map_wire_type(item.get('wire_type'), False)
|
|
325
|
+
_add_device(type, item)
|
|
326
|
+
|
|
327
|
+
smart_network = None
|
|
328
|
+
if self._smart_network:
|
|
329
|
+
try:
|
|
330
|
+
smart_network = self.request('admin/smart_network?form=game_accelerator', 'operation=loadDevice')
|
|
331
|
+
except Exception:
|
|
332
|
+
self._smart_network = False
|
|
333
|
+
|
|
334
|
+
if smart_network:
|
|
335
|
+
for item in smart_network:
|
|
336
|
+
if item['mac'] not in devices:
|
|
337
|
+
conn = self._map_wire_type(item.get('deviceTag'), not item.get('isGuest'))
|
|
338
|
+
devices[item['mac']] = Device(conn, get_mac(item.get('mac', '00:00:00:00:00:00')),
|
|
339
|
+
get_ip(item['ip']), item['deviceName'])
|
|
340
|
+
if conn.is_iot():
|
|
341
|
+
if status.iot_clients_total is None:
|
|
342
|
+
status.iot_clients_total = 0
|
|
343
|
+
status.iot_clients_total += 1
|
|
344
|
+
|
|
345
|
+
devices[item['mac']].down_speed = item.get('downloadSpeed')
|
|
346
|
+
devices[item['mac']].up_speed = item.get('uploadSpeed')
|
|
347
|
+
devices[item['mac']].signal = int(item.get('signal')) if item.get('signal') else None
|
|
348
|
+
|
|
349
|
+
for item in self.request('admin/wireless?form=statistics', 'operation=load'):
|
|
350
|
+
if item['mac'] not in devices:
|
|
351
|
+
status.wifi_clients_total += 1
|
|
352
|
+
type = self._map_wire_type(item.get('type'))
|
|
353
|
+
devices[item['mac']] = Device(type, EUI48(item['mac']), IPv4Address('0.0.0.0'),
|
|
354
|
+
'')
|
|
355
|
+
devices[item['mac']].packets_sent = item.get('txpkts')
|
|
356
|
+
devices[item['mac']].packets_received = item.get('rxpkts')
|
|
357
|
+
|
|
358
|
+
status.devices = list(devices.values())
|
|
359
|
+
status.clients_total = status.wired_total + status.wifi_clients_total + status.guest_clients_total
|
|
360
|
+
|
|
361
|
+
return status
|
|
362
|
+
|
|
363
|
+
def get_ipv4_status(self) -> IPv4Status:
|
|
364
|
+
ipv4_status = IPv4Status()
|
|
365
|
+
data = self.request('admin/network?form=status_ipv4&operation=read', 'operation=read')
|
|
366
|
+
ipv4_status._wan_macaddr = EUI48(data['wan_macaddr'])
|
|
367
|
+
ipv4_status._wan_ipv4_ipaddr = IPv4Address(data['wan_ipv4_ipaddr'])
|
|
368
|
+
ipv4_status._wan_ipv4_gateway = IPv4Address(data['wan_ipv4_gateway'])
|
|
369
|
+
ipv4_status.wan_ipv4_conntype = data['wan_ipv4_conntype']
|
|
370
|
+
ipv4_status._wan_ipv4_netmask = IPv4Address(data['wan_ipv4_netmask'])
|
|
371
|
+
ipv4_status._wan_ipv4_pridns = IPv4Address(data['wan_ipv4_pridns'])
|
|
372
|
+
ipv4_status._wan_ipv4_snddns = IPv4Address(data['wan_ipv4_snddns'])
|
|
373
|
+
ipv4_status._lan_macaddr = EUI48(data['lan_macaddr'])
|
|
374
|
+
ipv4_status._lan_ipv4_ipaddr = IPv4Address(data['lan_ipv4_ipaddr'])
|
|
375
|
+
ipv4_status.lan_ipv4_dhcp_enable = self._str2bool(data['lan_ipv4_dhcp_enable'])
|
|
376
|
+
ipv4_status._lan_ipv4_netmask = IPv4Address(data['lan_ipv4_netmask'])
|
|
377
|
+
ipv4_status.remote = self._str2bool(data.get('remote'))
|
|
378
|
+
|
|
379
|
+
return ipv4_status
|
|
380
|
+
|
|
381
|
+
def get_ipv4_reservations(self) -> [IPv4Reservation]:
|
|
382
|
+
ipv4_reservations = []
|
|
383
|
+
data = self.request(self._url_ipv4_reservations, 'operation=load')
|
|
384
|
+
|
|
385
|
+
for item in data:
|
|
386
|
+
ipv4_reservations.append(
|
|
387
|
+
IPv4Reservation(EUI48(item['mac']), IPv4Address(item['ip']), item['comment'],
|
|
388
|
+
self._str2bool(item['enable'])))
|
|
389
|
+
|
|
390
|
+
return ipv4_reservations
|
|
391
|
+
|
|
392
|
+
def get_ipv4_dhcp_leases(self) -> [IPv4DHCPLease]:
|
|
393
|
+
dhcp_leases = []
|
|
394
|
+
data = self.request(self._url_ipv4_dhcp_leases, 'operation=load')
|
|
395
|
+
|
|
396
|
+
for item in data:
|
|
397
|
+
dhcp_leases.append(
|
|
398
|
+
IPv4DHCPLease(EUI48(item['macaddr']), IPv4Address(item['ipaddr']), item['name'],
|
|
399
|
+
item['leasetime']))
|
|
400
|
+
|
|
401
|
+
return dhcp_leases
|
|
402
|
+
|
|
403
|
+
@staticmethod
|
|
404
|
+
def _str2bool(v) -> bool | None:
|
|
405
|
+
return str(v).lower() in ("yes", "true", "on") if v is not None else None
|
|
406
|
+
|
|
407
|
+
@staticmethod
|
|
408
|
+
def _map_wire_type(data: str | None, host: bool = True) -> Connection:
|
|
409
|
+
result = Connection.UNKNOWN
|
|
410
|
+
if data is None:
|
|
411
|
+
return result
|
|
412
|
+
if data == 'wired':
|
|
413
|
+
result = Connection.WIRED
|
|
414
|
+
if data.startswith('2.4'):
|
|
415
|
+
result = Connection.HOST_2G if host else Connection.GUEST_2G
|
|
416
|
+
elif data.startswith('5'):
|
|
417
|
+
result = Connection.HOST_5G if host else Connection.GUEST_5G
|
|
418
|
+
elif data.startswith('6'):
|
|
419
|
+
result = Connection.HOST_6G if host else Connection.GUEST_6G
|
|
420
|
+
elif data.startswith('iot_2'):
|
|
421
|
+
result = Connection.IOT_2G
|
|
422
|
+
elif data.startswith('iot_5'):
|
|
423
|
+
result = Connection.IOT_5G
|
|
424
|
+
elif data.startswith('iot_6'):
|
|
425
|
+
result = Connection.IOT_6G
|
|
426
|
+
return result
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
class TplinkRouter(TplinkEncryption, TplinkBaseRouter):
|
|
430
|
+
def __init__(self, host: str, password: str, username: str = 'admin', logger: Logger = None,
|
|
431
|
+
verify_ssl: bool = True, timeout: int = 30) -> None:
|
|
432
|
+
super().__init__(host, password, username, logger, verify_ssl, timeout)
|
|
433
|
+
|
|
434
|
+
self._url_firmware = 'admin/firmware?form=upgrade'
|
|
435
|
+
self._url_ipv4_reservations = 'admin/dhcps?form=reservation'
|
|
436
|
+
self._url_ipv4_dhcp_leases = 'admin/dhcps?form=client'
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from requests import post
|
|
2
|
+
from tplinkrouterc6u.common.package_enum import Connection
|
|
3
|
+
from tplinkrouterc6u.common.dataclass import Firmware, Status
|
|
4
|
+
from tplinkrouterc6u.common.exception import ClientException
|
|
5
|
+
from tplinkrouterc6u.client_abstract import AbstractRouter
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TplinkC6V4Router(AbstractRouter):
|
|
9
|
+
def supports(self) -> bool:
|
|
10
|
+
url = '{}/?code=2&asyn=1'.format(self.host)
|
|
11
|
+
try:
|
|
12
|
+
response = post(url, timeout=self.timeout, verify=self._verify_ssl)
|
|
13
|
+
except BaseException:
|
|
14
|
+
return False
|
|
15
|
+
if response.status_code == 401 and response.text.startswith('00'):
|
|
16
|
+
raise ClientException(('Your router is not supported. Please add your router support to '
|
|
17
|
+
'https://github.com/AlexandrErohin/TP-Link-Archer-C6U '
|
|
18
|
+
'by implementing methods for TplinkC6V4Router class'
|
|
19
|
+
))
|
|
20
|
+
return False
|
|
21
|
+
|
|
22
|
+
def authorize(self) -> None:
|
|
23
|
+
raise ClientException('Not Implemented')
|
|
24
|
+
|
|
25
|
+
def logout(self) -> None:
|
|
26
|
+
raise ClientException('Not Implemented')
|
|
27
|
+
|
|
28
|
+
def get_firmware(self) -> Firmware:
|
|
29
|
+
raise ClientException('Not Implemented')
|
|
30
|
+
|
|
31
|
+
def get_status(self) -> Status:
|
|
32
|
+
raise ClientException('Not Implemented')
|
|
33
|
+
|
|
34
|
+
def reboot(self) -> None:
|
|
35
|
+
raise ClientException('Not Implemented')
|
|
36
|
+
|
|
37
|
+
def set_wifi(self, wifi: Connection, enable: bool) -> None:
|
|
38
|
+
raise ClientException('Not Implemented')
|