tplinkrouterc6u 5.6.2__py3-none-any.whl → 5.8.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_c6u.py CHANGED
@@ -257,6 +257,7 @@ class TestTPLinkClient(TestCase):
257
257
  self.assertEqual(status.devices[0].hostname, 'SERVER')
258
258
  self.assertEqual(status.devices[0].packets_sent, None)
259
259
  self.assertEqual(status.devices[0].packets_received, None)
260
+ self.assertEqual(status.devices[0].active, True)
260
261
  self.assertIsInstance(status.devices[0], Device)
261
262
  self.assertEqual(status.devices[1].type, Connection.WIRED)
262
263
  self.assertEqual(status.devices[1].macaddr, 'AC-04-D6-25-2A-96')
@@ -266,6 +267,7 @@ class TestTPLinkClient(TestCase):
266
267
  self.assertEqual(status.devices[1].hostname, 'UNKNOWN')
267
268
  self.assertEqual(status.devices[1].packets_sent, None)
268
269
  self.assertEqual(status.devices[1].packets_received, None)
270
+ self.assertEqual(status.devices[1].active, True)
269
271
  self.assertIsInstance(status.devices[2], Device)
270
272
  self.assertEqual(status.devices[2].type, Connection.HOST_2G)
271
273
  self.assertEqual(status.devices[2].macaddr, '06-82-9D-2B-8F-C6')
@@ -273,6 +275,7 @@ class TestTPLinkClient(TestCase):
273
275
  self.assertEqual(status.devices[2].hostname, 'UNKNOWN')
274
276
  self.assertEqual(status.devices[2].packets_sent, 450333)
275
277
  self.assertEqual(status.devices[2].packets_received, 4867482)
278
+ self.assertEqual(status.devices[2].active, True)
276
279
  self.assertIsInstance(status.devices[3], Device)
277
280
  self.assertEqual(status.devices[3].type, Connection.HOST_2G)
278
281
  self.assertEqual(status.devices[3].macaddr, '06-55-9D-2B-8F-A7')
@@ -280,6 +283,7 @@ class TestTPLinkClient(TestCase):
280
283
  self.assertEqual(status.devices[3].hostname, 'Unknown')
281
284
  self.assertEqual(status.devices[3].packets_sent, None)
282
285
  self.assertEqual(status.devices[3].packets_received, None)
286
+ self.assertEqual(status.devices[3].active, True)
283
287
  self.assertIsInstance(status.devices[4], Device)
284
288
  self.assertEqual(status.devices[4].type, Connection.HOST_5G)
285
289
  self.assertEqual(status.devices[4].macaddr, '1F-7A-BD-F7-20-0D')
@@ -287,6 +291,7 @@ class TestTPLinkClient(TestCase):
287
291
  self.assertEqual(status.devices[4].hostname, '')
288
292
  self.assertEqual(status.devices[4].packets_sent, 134815)
289
293
  self.assertEqual(status.devices[4].packets_received, 2953078)
294
+ self.assertEqual(status.devices[4].active, True)
290
295
 
291
296
  def test_get_status_ax_55(self) -> None:
292
297
  response_status = '''
@@ -724,6 +729,45 @@ class TestTPLinkClient(TestCase):
724
729
  self.assertEqual(result.lan_ipv4_dhcp_enable, False)
725
730
  self.assertEqual(result.remote, None)
726
731
 
732
+ def test_get_status_wan_macaddr_empty(self) -> None:
733
+ response_status = '''
734
+ {
735
+ "success": true,
736
+ "data": {
737
+ "lan_macaddr": "06:e6:97:9e:23:f5",
738
+ "wan_macaddr": "",
739
+ "wan_ipv4_ipaddr": "0.0.0.0",
740
+ "wan_ipv4_gateway": "0.0.0.0"
741
+ }
742
+ }
743
+ '''
744
+ response_stats = '''
745
+ {
746
+ "data": [],
747
+ "timeout": false,
748
+ "success": true,
749
+ "operator": "load"
750
+ }
751
+ '''
752
+
753
+ class TPLinkRouterTest(TplinkRouter):
754
+ def request(self, path: str, data: str,
755
+ ignore_response: bool = False, ignore_errors: bool = False) -> dict | None:
756
+ if path == 'admin/status?form=all&operation=read':
757
+ return loads(response_status)['data']
758
+ elif path == 'admin/wireless?form=statistics':
759
+ return loads(response_stats)['data']
760
+ raise ClientException()
761
+
762
+ client = TPLinkRouterTest('', '')
763
+ result = client.get_status()
764
+
765
+ self.assertIsInstance(result, Status)
766
+ self.assertEqual(result.wan_macaddr, None)
767
+ self.assertEqual(result.wan_ipv4_addr, '0.0.0.0')
768
+ self.assertEqual(result.wan_ipv4_gateway, '0.0.0.0')
769
+ self.assertEqual(result.lan_macaddr, '06-E6-97-9E-23-F5')
770
+
727
771
 
728
772
  if __name__ == '__main__':
729
773
  main()
test/test_client_c80.py CHANGED
@@ -474,6 +474,7 @@ class TestTPLinkClient(TestCase):
474
474
  self.assertEqual(device.hostname, 'Laptop')
475
475
  self.assertEqual(device.up_speed, 0)
476
476
  self.assertEqual(device.down_speed, 0)
477
+ self.assertEqual(device.active, False)
477
478
 
478
479
  device = status.devices[1]
479
480
  self.assertIsInstance(device, Device)
@@ -485,6 +486,7 @@ class TestTPLinkClient(TestCase):
485
486
  self.assertEqual(device.hostname, 'iPhone')
486
487
  self.assertEqual(device.up_speed, 0)
487
488
  self.assertEqual(device.down_speed, 0)
489
+ self.assertEqual(device.active, False)
488
490
 
489
491
  device = status.devices[2]
490
492
  self.assertIsInstance(device, Device)
@@ -496,6 +498,7 @@ class TestTPLinkClient(TestCase):
496
498
  self.assertEqual(device.hostname, 'Laptop2')
497
499
  self.assertEqual(device.up_speed, 30)
498
500
  self.assertEqual(device.down_speed, 200)
501
+ self.assertEqual(device.active, True)
499
502
 
500
503
  device = status.devices[3]
501
504
  self.assertIsInstance(device, Device)
@@ -507,6 +510,7 @@ class TestTPLinkClient(TestCase):
507
510
  self.assertEqual(device.hostname, 'iPhone2')
508
511
  self.assertEqual(device.up_speed, 800)
509
512
  self.assertEqual(device.down_speed, 400)
513
+ self.assertEqual(device.active, True)
510
514
 
511
515
  device = status.devices[4]
512
516
  self.assertIsInstance(device, Device)
@@ -518,6 +522,7 @@ class TestTPLinkClient(TestCase):
518
522
  self.assertEqual(device.hostname, 'IoT_thing')
519
523
  self.assertEqual(device.up_speed, 1824)
520
524
  self.assertEqual(device.down_speed, 800)
525
+ self.assertEqual(device.active, True)
521
526
 
522
527
  device = status.devices[5]
523
528
  self.assertIsInstance(device, Device)
@@ -529,6 +534,7 @@ class TestTPLinkClient(TestCase):
529
534
  self.assertEqual(device.hostname, 'PC')
530
535
  self.assertEqual(device.up_speed, 600)
531
536
  self.assertEqual(device.down_speed, 50)
537
+ self.assertEqual(device.active, True)
532
538
 
533
539
  def test_get_status_without_iot(self) -> None:
534
540
  client = TplinkC80RouterTest('', '')
test/test_client_mr.py CHANGED
@@ -734,7 +734,6 @@ ussdStatus=1
734
734
 
735
735
  class TPLinkMRClientTest(TPLinkMRClient):
736
736
  def _request(self, url, method='POST', data_str=None, encrypt=False):
737
- nonlocal check_url, check_data
738
737
  check_url.append(url)
739
738
  check_data.append(data_str)
740
739
  return 200, responses.pop(0)
@@ -776,7 +775,6 @@ ussdStatus=2
776
775
 
777
776
  class TPLinkMRClientTest(TPLinkMRClient):
778
777
  def _request(self, url, method='POST', data_str=None, encrypt=False):
779
- nonlocal check_url, check_data
780
778
  check_url.append(url)
781
779
  check_data.append(data_str)
782
780
  return 200, responses.pop(0)
@@ -0,0 +1,292 @@
1
+ from unittest import main, TestCase
2
+ from ipaddress import IPv4Address
3
+ from macaddress import EUI48
4
+ from tplinkrouterc6u.common.dataclass import Firmware, Status
5
+ from tplinkrouterc6u.common.dataclass import IPv4Status, IPv4Reservation, IPv4DHCPLease
6
+ from tplinkrouterc6u import ClientError
7
+ from tplinkrouterc6u.client.wdr import TplinkWDRRouter
8
+
9
+ _NETWAN = (
10
+ '<SCRIPT language="javascript" type="text/javascript">\nvar wanTypeDetectInfoArray = '
11
+ + "new Array(\n1, 0, 4500, \n0,0 );\n</SCRIPT>\n"
12
+ + '<SCRIPT language="javascript" type="text/javascript">\nvar dhcpInf = new Array(\n1,\n'
13
+ + '0,\n1,\n0,\n0,\n0,\n0,\n0,\n"",\n"",\n0,\n0,\n"",\n"192.168.0.129",\n"255.255.255.0",\n'
14
+ + '"192.168.0.1",\n1,\n0,\n1500,\n0,\n"8.8.8.8",\n1,\n"8.8.4.4",\n0,\n0,\n0,\n"TL-WDR3600",\n0,0 );\n</SCRIPT>\n'
15
+ + '<SCRIPT language="javascript" type="text/javascript">\nvar wantypeinfo = new Array(\n6,\n0,\n'
16
+ + '"WanDynamicIpCfgRpm.htm",\n1,\n"WanStaticIpCfgRpm.htm",\n'
17
+ + '2,\n"PPPoECfgRpm.htm",\n5,\n"BPACfgRpm.htm",\n6,\n"L2TPCfgRpm.htm",\n7,\n"PPTPCfgRpm.htm",\n0,0 );\n</SCRIPT>'
18
+ )
19
+ _NETLAN = (
20
+ '<SCRIPT language="javascript" type="text/javascript">\nvar lanPara = new Array(\n"C4-6E-1F-41-67-C0",\n'
21
+ + '"192.168.1.254",\n2,\n"255.255.255.0",\n1,\n0,0 );\n</SCRIPT>'
22
+ )
23
+
24
+ _W24STA = (
25
+ '<SCRIPT language="javascript" type="text/javascript">\nvar wlanHostPara = new Array(\n1, 1, 8, 5000, 4,\n'
26
+ + '0,0);\n</SCRIPT>\n<SCRIPT language="javascript" type="text/javascript">\nvar hostList = new Array(\n'
27
+ + '"D0-BD-53-57-3E-4A", 1, 170893827, 0,\n"08-16-AC-03-E2-FA", 1, 873409583, 0,\n"F8-F1-E8-CD-0A-CF", 1, '
28
+ + '240958643, 0,\n"ED-49-92-1A-1D-D7", 1, 358743698, 0,\n"0E-50-99-5D-9A-D5", 1, 572346959, 0,\n0,0 );\n</SCRIPT>'
29
+ )
30
+ _W50STA = (
31
+ '<SCRIPT language="javascript" type="text/javascript">\nvar wlanHostPara = new Array(\n1, 1, 8, 5000, 4,\n'
32
+ + '0,0 );\n</SCRIPT>\n<SCRIPT language="javascript" type="text/javascript">\nvar hostList = new Array(\n'
33
+ + '"50-A6-FC-6D-EB-D3", 1, 708938274, 0,\n"6A-D0-5F-2A-FA-2D", 1, 287340958, 0,\n"DA-69-2D-59-B3-FA", 1'
34
+ + ", 540958641, 0,\n0,0 );\n</SCRIPT>"
35
+ )
36
+
37
+ _WLANGUEST = (
38
+ '<SCRIPT language="javascript" type="text/javascript">\nvar guestNetworkBandwidthInf = '
39
+ + "new Array(\n"
40
+ + '0,\n1000000,\n1000000,\n1024,\n1024,\n1,\n0,0);\n</SCRIPT>\n<SCRIPT language="javascript" '
41
+ + 'type="text/javascript">'
42
+ + '\nvar guestNetAccTime2gInf = new Array(\n1,\n1,\n0,\n0,\n0,\n1,\n0,\n0,\n0,\n0,\n0,\n0,\n0,\n1,\n"",\n"",\n'
43
+ + "0,0 );"
44
+ + '\n</SCRIPT>\n<SCRIPT language="javascript" type="text/javascript">\nvar guestNetAccTime5gInf = new Array(\n'
45
+ + '1,\n0,\n0,\n0,\n0,\n1,\n0,\n0,\n0,\n0,\n0,\n0,\n0,\n1,\n"",\n"",\n0,0 );\n</SCRIPT>\n'
46
+ + '<SCRIPT language="javascript" type="text/javascript">\nvar guestNetworkInf = '
47
+ + 'new Array(\n1, 1, 1, 0, "Pegasus", '
48
+ + '"Pegasus", 1, 1, 3, 3, "333", "333", 0, 0, "p4ssw0rd", "p4ssw0rd", '
49
+ + "0, 0, 0, 0, 3, 3, 1, 1, 5, 8, 0, \n0,0 );\n"
50
+ + "</SCRIPT>"
51
+ )
52
+
53
+ _DHCPCFG = (
54
+ '<SCRIPT language="javascript" type="text/javascript">\nvar DHCPPara = new Array(\n1,\n"192.168.1.129",\n'
55
+ + '"192.168.1.192",\n120,\n"192.168.1.254",\n"internal.lan",\n"8.8.8.8",\n"8.8.4.4",\n0,\n0,0 );\n</SCRIPT>'
56
+ )
57
+
58
+ _DHCPLEASES = (
59
+ '<SCRIPT language="javascript" type="text/javascript">var DHCPDynList = new Array(\n"aliquam",'
60
+ + '"A9-A8-2B-F7-9F-5D","192.168.1.123","Permanent",\n"pharetra","B3-A5-1E-C3-92-A9","192.168.1.163","Permanent",\n'
61
+ + '"ligula","71-34-47-FD-DE-84","192.168.1.165","Permanent",\n"vulputate","46-5F-5F-27-23-9F","192.168.1.103",'
62
+ + '"Permanent",\n"amet","86-9F-53-91-04-2B","192.168.1.72","Permanent",\n"volutpat","FA-5C-6F-87-A3-5A",'
63
+ + '"192.168.1.43",'
64
+ + '"Permanent",\n"eget","C4-ED-6C-B6-F6-B9","192.168.1.112","Permanent",\n"ante","FF-C1-A3-93-C8-E6",'
65
+ + '"192.168.1.38",'
66
+ + '"Permanent",\n"pellentesque","E3-5E-59-2E-CF-AD","192.168.1.148","Permanent",\n"metus","AE-15-51-37-0E-9E",'
67
+ + '"192.168.1.178","Permanent",\n0,0 );\n</SCRIPT>\n<SCRIPT language="javascript" type="text/javascript">\n'
68
+ + "var DHCPDynPara = new Array(\n10,\n4,\n0,0 );\n</SCRIPT>"
69
+ )
70
+
71
+ _DHCPRESERVES = (
72
+ '<SCRIPT language="javascript" type="text/javascript">\nvar dhcpList = new Array(\n"9B-FA-04-D8-AB-8D",'
73
+ + '"192.168.1.56",1,\n"EE-7C-6B-B6-05-2F","192.168.1.51",1,\n"09-51-B3-0B-92-01","192.168.1.21",1,\n'
74
+ + '"49-6F-72-CD-68-5D","192.168.1.25",1,\n"DF-24-38-C1-FE-BB","192.168.1.51",1,\n"1A-08-6C-52-31-3D",'
75
+ + '"192.168.1.22",1,\n"DE-E4-DF-9A-AD-0D","192.168.1.17",1,\n"DF-F2-CB-FE-46-15","192.168.1.26",1,\n'
76
+ + '0,0 )\n</SCRIPT>\n<SCRIPT language="javascript" type="text/javascript">var DHCPStaticPara = '
77
+ + "new Array(\n1,\n1,\n8,\n1,\n8,\n0,0 );\n</SCRIPT>"
78
+ )
79
+
80
+ ABSTRACT_STATUS = (
81
+ '<SCRIPT language="javascript" type="text/javascript">\nvar statusPara = new Array(\n1,\n1,\n1,\n'
82
+ + '22,\n20000,\n1468171,\n"3.13.34 Build 130909 Rel.53148n ",\n"WDR3600 v1 00000000",\n6732336,\n0, 0);\n'
83
+ + '</SCRIPT>\n'
84
+ + '<SCRIPT language="javascript" type="text/javascript">\nvar lanPara = new Array(\n"C4-6E-1F-41-67-C0", '
85
+ + '"192.168.1.254", "255.255.255.0",\n0, 0);\n</SCRIPT>\n<SCRIPT language="javascript" type="text/javascript">\n'
86
+ + 'var wlanPara = new Array(\n1,\n"testSSID24",\n15,\n5,\n"C4-6E-1F-41-67-BF",\n"192.168.1.254",\n2,\n8,\n71,\n6,\n'
87
+ + '6,\n0, 0);\n</SCRIPT>\n<SCRIPT language="javascript" type="text/javascript">\nvar wlan5GPara = new Array(\n1,\n'
88
+ + '"testSSID",\n15,\n8,\n"C4-6E-1F-41-67-C0",\n"192.168.1.254",\n2,\n8,\n83,\n36,\n6,\n0, 0);\n</SCRIPT>\n'
89
+ + '<SCRIPT language="javascript" type="text/javascript">\nvar statistList = '
90
+ + "new Array(\n1129349328, 3900411475, 200068023, 165562287,\n0, 0);\n</SCRIPT>\n"
91
+ + '<SCRIPT language="javascript" type="text/javascript">\nvar wanPara = new Array(\n4, '
92
+ + '"C4-6E-1F-41-67-C1", "192.168.0.129", 1, "255.255.255.0", 0, 0, "192.168.0.1", 1, 1, 0, '
93
+ + '"8.8.8.8 , 8.8.4.4", "", 0, 0, "0.0.0.0", "0.0.0.0", "0.0.0.0", "0.0.0.0 , 0.0.0.0", '
94
+ + "0, 0, 0, 0, 0,\n0, 0);\n</SCRIPT>"
95
+ )
96
+
97
+ ABSTRACT_NETWORK = {
98
+ "netWan": _NETWAN,
99
+ "netLan": _NETLAN,
100
+ "w24stations": _W24STA,
101
+ "w50stations": _W50STA,
102
+ "wgsettings": _WLANGUEST,
103
+ "dhcpconfig": _DHCPCFG,
104
+ "dhcplease": _DHCPLEASES,
105
+ "dhcpreserve": _DHCPRESERVES,
106
+ }
107
+
108
+
109
+ class ResponseMock:
110
+ def __init__(self, text: str, status_code=0):
111
+ self.content = text.encode("utf8")
112
+ self.status_code = status_code
113
+ self.headers: dict = {}
114
+
115
+
116
+ class TplinkWDRRouterTest(TplinkWDRRouter):
117
+ response = ""
118
+
119
+ def request(
120
+ self,
121
+ section: str,
122
+ data: str,
123
+ ignore_response: bool = False,
124
+ ignore_errors: bool = False,
125
+ ) -> str | dict | None:
126
+ # only a test, so no extra headers
127
+ # Responses
128
+ sections = "summary,status,"
129
+ sections += "netWan,netLan,dualBand,"
130
+ sections += "w24settings,w24wps,w24sec,w24adv,w24stations,"
131
+ sections += "w50settings,w50wps,w50sec,w50adv,w50stations,"
132
+ sections += "wgsettings,wgshare,dhcpconfig,dhcplease,dhcpreserve,"
133
+ sections += "portFwd,sysroute,upnpFwd"
134
+
135
+ section_list = sections.split(",")
136
+
137
+ if ignore_response:
138
+ return None
139
+ elif section == "check":
140
+ resp = ResponseMock("", 200)
141
+ resp.headers["www-authenticate"] = (
142
+ 'Basic realm="TP-LINK Wireless Dual Band Gigabit Router WDR3600"'
143
+ )
144
+ return resp
145
+ elif section in section_list:
146
+ if section in ["summary", "status"]:
147
+ return ResponseMock(ABSTRACT_STATUS).content
148
+ elif section in ["w24stations", "w50stations", "dhcpreserve"]:
149
+ return ResponseMock(ABSTRACT_NETWORK[section]).content
150
+ elif section in [
151
+ "netLan",
152
+ "netWan",
153
+ # 'w24stations',
154
+ # 'w50stations',
155
+ "wgsettings",
156
+ "dhcpconfig",
157
+ "dhcplease",
158
+ # 'dhcpreserve',
159
+ ]:
160
+ return ResponseMock(ABSTRACT_NETWORK[section]).content
161
+ else:
162
+ return ""
163
+ # raise ClientError (f'Section {section} not allowed')
164
+
165
+ else:
166
+ error = ""
167
+ error = (
168
+ (
169
+ "WDRRouter - {} - Response with error; Request {} - Response {}".format(
170
+ self.__class__.__name__, section, data
171
+ )
172
+ )
173
+ if not error
174
+ else error
175
+ )
176
+ if self._logger:
177
+ self._logger.debug(error)
178
+
179
+ raise ClientError(error)
180
+
181
+
182
+ class TestTPLinkWDRClient(TestCase):
183
+
184
+ def test_supports(self) -> None:
185
+ client = TplinkWDRRouterTest("", "")
186
+ # client.response = ResponseMock(ABSTRACT_STATUS)
187
+ supports = client.supports()
188
+ self.assertTrue(supports)
189
+
190
+ def test_get_firmware(self) -> None:
191
+
192
+ client = TplinkWDRRouterTest("", "")
193
+ client.response = ResponseMock(ABSTRACT_STATUS)
194
+ firmware = client.get_firmware()
195
+
196
+ self.assertIsInstance(firmware, Firmware)
197
+ self.assertEqual(firmware.hardware_version, "WDR3600 v1 00000000")
198
+ self.assertEqual(firmware.model, "WDR3600")
199
+ self.assertEqual(
200
+ firmware.firmware_version.strip(), "3.13.34 Build 130909 Rel.53148n"
201
+ )
202
+
203
+ def test_get_ipv4(self) -> None:
204
+
205
+ client = TplinkWDRRouterTest("", "")
206
+ ipv4status: IPv4Status = IPv4Status()
207
+ ipv4status = client.get_ipv4_status()
208
+
209
+ self.assertIsInstance(ipv4status, IPv4Status)
210
+ self.assertEqual(ipv4status._wan_macaddr, EUI48("C4-6E-1F-41-67-C1"))
211
+ self.assertEqual(
212
+ IPv4Address(ipv4status.wan_ipv4_ipaddr), IPv4Address("192.168.0.129")
213
+ )
214
+ self.assertEqual(
215
+ IPv4Address(ipv4status.wan_ipv4_gateway), IPv4Address("192.168.0.1")
216
+ )
217
+ self.assertEqual(ipv4status.wan_ipv4_conntype, "Dynamic IP")
218
+ self.assertEqual(
219
+ IPv4Address(ipv4status.wan_ipv4_netmask), IPv4Address("255.255.255.0")
220
+ )
221
+ self.assertEqual(
222
+ IPv4Address(ipv4status.wan_ipv4_pridns), IPv4Address("0.0.0.0")
223
+ )
224
+ self.assertEqual(
225
+ IPv4Address(ipv4status.wan_ipv4_snddns), IPv4Address("0.0.0.0")
226
+ )
227
+ self.assertEqual(ipv4status._lan_macaddr, EUI48("C4-6E-1F-41-67-C0"))
228
+ self.assertEqual(
229
+ IPv4Address(ipv4status.lan_ipv4_ipaddr), IPv4Address("192.168.1.254")
230
+ )
231
+ self.assertEqual(ipv4status.lan_ipv4_dhcp_enable, True)
232
+ self.assertEqual(
233
+ IPv4Address(ipv4status.lan_ipv4_netmask), IPv4Address("255.255.255.0")
234
+ )
235
+
236
+ def test_get_ipv4_reservations(self) -> None:
237
+ client = TplinkWDRRouterTest("", "")
238
+ ipv4_reservations: list[IPv4Reservation] = client.get_ipv4_reservations()
239
+ fRes: IPv4Reservation = ipv4_reservations[0]
240
+
241
+ self.assertIsInstance(fRes, IPv4Reservation)
242
+ self.assertEqual(EUI48(fRes.macaddress), EUI48("9B-FA-04-D8-AB-8D"))
243
+ self.assertEqual(IPv4Address(fRes.ipaddress), IPv4Address("192.168.1.56"))
244
+ self.assertEqual(fRes.enabled, True)
245
+
246
+ def test_get_ipv4_dhcp_leases(self) -> None:
247
+ client = TplinkWDRRouterTest("", "")
248
+ dhcp_leases: list[IPv4DHCPLease] = client.get_ipv4_dhcp_leases()
249
+
250
+ self.assertIsInstance(dhcp_leases[0], IPv4DHCPLease)
251
+ self.assertEqual(dhcp_leases[0].macaddress, EUI48("A9-A8-2B-F7-9F-5D"))
252
+ self.assertEqual(dhcp_leases[0].ipaddress, IPv4Address("192.168.1.123"))
253
+ self.assertEqual(dhcp_leases[0].hostname, "aliquam")
254
+ self.assertEqual(dhcp_leases[0].lease_time, "Permanent")
255
+
256
+ self.assertIsInstance(dhcp_leases[1], IPv4DHCPLease)
257
+ self.assertEqual(dhcp_leases[1].macaddress, EUI48("B3-A5-1E-C3-92-A9"))
258
+ self.assertEqual(dhcp_leases[1].ipaddress, IPv4Address("192.168.1.163"))
259
+ self.assertEqual(dhcp_leases[1].hostname, "pharetra")
260
+ self.assertEqual(dhcp_leases[1].lease_time, "Permanent")
261
+
262
+ def test_get_status(self) -> None:
263
+ client = TplinkWDRRouterTest("", "")
264
+ client.response = ResponseMock(ABSTRACT_STATUS)
265
+
266
+ status = client.get_status()
267
+
268
+ self.assertIsInstance(status, Status)
269
+ self.assertEqual(status.wan_macaddr, "C4-6E-1F-41-67-C1")
270
+ self.assertIsInstance(status.wan_macaddress, EUI48)
271
+ self.assertEqual(status.lan_macaddr, "C4-6E-1F-41-67-C0")
272
+ self.assertIsInstance(status.lan_macaddress, EUI48)
273
+ self.assertEqual(status.wan_ipv4_addr, "192.168.0.129")
274
+ self.assertIsInstance(status.lan_ipv4_address, IPv4Address)
275
+ self.assertEqual(status.lan_ipv4_addr, "192.168.1.254")
276
+ self.assertEqual(status.wan_ipv4_gateway, "192.168.0.1")
277
+ self.assertIsInstance(status.wan_ipv4_address, IPv4Address)
278
+
279
+ self.assertEqual(status.wired_total, 10)
280
+ self.assertEqual(status.wifi_clients_total, 8)
281
+ self.assertEqual(status.guest_clients_total, 0)
282
+ self.assertEqual(status.clients_total, 18)
283
+
284
+ self.assertTrue(status.guest_2g_enable)
285
+ self.assertFalse(status.guest_5g_enable)
286
+ self.assertTrue(status.wifi_2g_enable)
287
+ self.assertTrue(status.wifi_5g_enable)
288
+ self.assertEqual(status.wan_ipv4_uptime, 6732336)
289
+
290
+
291
+ if __name__ == "__main__":
292
+ main()
@@ -8,6 +8,7 @@ from tplinkrouterc6u.client.c80 import TplinkC80Router
8
8
  from tplinkrouterc6u.client.c5400x import TplinkC5400XRouter
9
9
  from tplinkrouterc6u.client.c1200 import TplinkC1200Router
10
10
  from tplinkrouterc6u.client.xdr import TPLinkXDRClient
11
+ from tplinkrouterc6u.client.wdr import TplinkWDRRouter
11
12
  from tplinkrouterc6u.provider import TplinkRouterProvider
12
13
  from tplinkrouterc6u.common.package_enum import Connection, VPN
13
14
  from tplinkrouterc6u.common.dataclass import (
@@ -275,7 +275,7 @@ class TplinkBaseRouter(AbstractRouter, TplinkRequest):
275
275
  data = self.request('admin/status?form=all&operation=read', 'operation=read')
276
276
 
277
277
  status = Status()
278
- status._wan_macaddr = EUI48(data['wan_macaddr']) if 'wan_macaddr' in data else None
278
+ status._wan_macaddr = EUI48(data['wan_macaddr']) if 'wan_macaddr' in data and data['wan_macaddr'] else None
279
279
  status._lan_macaddr = EUI48(data['lan_macaddr'])
280
280
  status._wan_ipv4_addr = IPv4Address(data['wan_ipv4_ipaddr']) if 'wan_ipv4_ipaddr' in data else None
281
281
  status._lan_ipv4_addr = IPv4Address(data['lan_ipv4_ipaddr']) if 'lan_ipv4_ipaddr' in data else None
@@ -82,8 +82,11 @@ class TplinkC80Router(AbstractRouter):
82
82
  self._encryption = EncryptionState()
83
83
 
84
84
  def supports(self) -> bool:
85
- response = self.request(2, 1, data='0|1,0,0')
86
- return response.status_code == 200 and response.text.startswith('00000')
85
+ try:
86
+ response = self.request(2, 1, data='0|1,0,0')
87
+ return response.status_code == 200 and response.text.startswith('00000')
88
+ except Exception:
89
+ return False
87
90
 
88
91
  def authorize(self) -> None:
89
92
  encoded_password = TplinkC80Router._encrypt_password(self.password)
@@ -328,6 +331,7 @@ class TplinkC80Router(AbstractRouter):
328
331
  device_to_add = Device(connection_type, EUI48(device['mac']), IPv4Address(device['ip']), device['name'])
329
332
  device_to_add.up_speed = int(device['up'])
330
333
  device_to_add.down_speed = int(device['down'])
334
+ device_to_add.active = device['online'] == '1'
331
335
  mapped_devices.append(device_to_add)
332
336
  return mapped_devices
333
337
 
@@ -0,0 +1,672 @@
1
+ import base64
2
+ from ipaddress import IPv4Address
3
+ from requests import get, Response
4
+ from logging import Logger
5
+ from macaddress import EUI48
6
+ from tplinkrouterc6u.common.helper import get_ip
7
+ from tplinkrouterc6u.common.package_enum import Connection
8
+ from tplinkrouterc6u.common.exception import ClientError
9
+ from tplinkrouterc6u.common.dataclass import (
10
+ Firmware,
11
+ Status,
12
+ Device,
13
+ IPv4Reservation,
14
+ IPv4DHCPLease,
15
+ IPv4Status,
16
+ )
17
+ from tplinkrouterc6u.client_abstract import AbstractRouter
18
+
19
+ from dataclasses import dataclass
20
+ from html.parser import HTMLParser
21
+
22
+ dataUrls = {
23
+ "check": "/StatusRpm.htm",
24
+ "summary": "/StatusRpm.htm",
25
+ "netWan": "/WanDynamicIpCfgRpm.htm?wan=0",
26
+ "netLan": "/NetworkCfgRpm.htm",
27
+ # 'macClone': "",
28
+ # WIFI
29
+ "dualBand": "/WlanBandRpm.htm",
30
+ # 2.4 Ghz"
31
+ "w24settings": "/WlanNetworkRpm.htm",
32
+ "w24wps": "/WpsCfgRpm.htm",
33
+ "w24sec": "/WlanSecurityRpm.htm",
34
+ "w24macflt": "/WlanMacFilterRpm.htm",
35
+ "w24adv": "/WlanAdvRpm.htm",
36
+ "w24stations": "/WlanStationRpm.htm?Page=1",
37
+ # 5.0 Ghz
38
+ "w50settings": "/WlanNetworkRpm_5g.htm",
39
+ "w50wps": "/WpsCfgRpm_5g.htm",
40
+ "w50sec": "/WlanSecurityRpm_5g.htm",
41
+ "w50macflt": "/WlanMacFilterRpm_5g.htm",
42
+ "w50adv": "/WlanAdvRpm_5g.htm",
43
+ "w50stations": "/WlanStationRpm_5g.htm?Page=1",
44
+ # Guest Network
45
+ "wgsettings": "/GuestNetWirelessCfgRpm.htm",
46
+ "wgshare": "/GuestNetUsbCfgRpm.htm",
47
+ # DHCP
48
+ "dhcpconfig": "/LanDhcpServerRpm.htm",
49
+ "dhcplease": "/AssignedIpAddrListRpm.htm",
50
+ "dhcpreserve": "/FixMapCfgRpm.htm",
51
+ # Referer
52
+ "defReferer": "/MenuRpm.htm",
53
+ # routing
54
+ "sysroute": "/SysRouteTableRpm.htm",
55
+ "portFwd": "/VirtualServerRpm.htm",
56
+ "upnpFwd": "/UpnpCfgRpm.htm",
57
+ # Reboot
58
+ "reboot": "/SysRebootHelpRpm.htm",
59
+ }
60
+
61
+
62
+ def defaultHeaders():
63
+ # default headers for all requests
64
+ return {
65
+ "Accept": "application/json, text/javascript, */*; q=0.01",
66
+ "User-Agent": "TP-Link Scrapper",
67
+ "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
68
+ "X-Requested-With": "XMLHttpRequest",
69
+ }
70
+
71
+
72
+ @dataclass
73
+ class HostId:
74
+ def __init__(self, ipaddr: str, host: str) -> None:
75
+ self.ipaddr = ipaddr
76
+ self.host = host
77
+
78
+
79
+ @dataclass
80
+ class NetInfo:
81
+ def __init__(self) -> None:
82
+ self.wlan24Gcfg = {}
83
+ self.wlan24Gsec = {}
84
+ self.wlan24Gadv = {}
85
+ self.wlan24Gcli: list[Device] = []
86
+
87
+ self.wlan50Gcfg = {}
88
+ self.wlan50Gsec = {}
89
+ self.wlan50Gadv = {}
90
+ self.wlan50Gcli: list[Device] = []
91
+
92
+ self.guest24Gcfg = {}
93
+ self.guest50Gcfg = {}
94
+
95
+ self.ipv4 = {}
96
+ self.routing = {}
97
+ self.fwd_static = {}
98
+ self.fwd_pnp = {}
99
+ self.dhcp_cfg = {}
100
+ self.security = {}
101
+
102
+
103
+ class muParser(HTMLParser):
104
+ def __init__(self, tag, convert_charrefs: bool = True):
105
+ super().__init__(convert_charrefs=convert_charrefs)
106
+
107
+ self.tag = tag
108
+ self.data: list = []
109
+ self.cTag = ""
110
+ self.cIdx = 0
111
+ self.cBlock = ""
112
+
113
+ def handle_starttag(self, tag, attrs):
114
+ if tag == self.tag:
115
+ self.cBlock = ""
116
+ self.cTag = tag
117
+
118
+ def handle_endtag(self, tag):
119
+ if tag == self.tag:
120
+ self.data.append(self.cBlock.strip("\r\n"))
121
+ self.cIdx += 1
122
+ self.cBlock = ""
123
+ self.cTag = ""
124
+
125
+ def handle_data(self, data):
126
+ if self.cTag == self.tag:
127
+ self.cBlock += data
128
+
129
+
130
+ class WDRRequest:
131
+ host = ""
132
+ credentials = ""
133
+ timeout = 10
134
+ _logged = False
135
+ _verify_ssl = False
136
+ _logger = None
137
+ _headers_request = {}
138
+
139
+ def buildUrl(self, section: str):
140
+ return "{}/userRpm{}".format(self.host, dataUrls[section])
141
+
142
+ def request(
143
+ self,
144
+ section: str,
145
+ data: str,
146
+ ignore_response: bool = False,
147
+ ignore_errors: bool = False,
148
+ ) -> str | dict | None:
149
+ if not self._headers_request:
150
+ self._headers_request = defaultHeaders()
151
+
152
+ # add xtra headers: User-Agent, Authorization and Referer
153
+ self._headers_request["Referer"] = self.buildUrl("defReferer")
154
+ self._headers_request["User-Agent"] = "TP-Link Scrapper"
155
+ self._headers_request["Authorization"] = "Basic {}".format(self.credentials)
156
+
157
+ path = dataUrls[section]
158
+ url = self.buildUrl(section)
159
+ if section == "reboot":
160
+ url = url + "?Reboot=Reboot"
161
+
162
+ # Always GET, so data always is a query
163
+ if data:
164
+ url = url + f"?{data}"
165
+
166
+ response = get(
167
+ url,
168
+ headers=self._headers_request,
169
+ timeout=self.timeout,
170
+ verify=self._verify_ssl,
171
+ )
172
+
173
+ data = response.content # better than .text for later parsing
174
+ if response.ok:
175
+ if ignore_response:
176
+ return None
177
+ if section == "check":
178
+ return response
179
+
180
+ return data
181
+ else:
182
+ if ignore_errors:
183
+ return data
184
+ error = ""
185
+ error = (
186
+ (
187
+ "WDRRouter - {} - Response with error; Request {} - Response {}".format(
188
+ self.__class__.__name__, path, data
189
+ )
190
+ )
191
+ if not error
192
+ else error
193
+ )
194
+ if self._logger:
195
+ self._logger.debug(error)
196
+ raise ClientError(error)
197
+
198
+
199
+ class TplinkWDRRouter(AbstractRouter, WDRRequest):
200
+ # _smart_network = True
201
+ _perf_status = False
202
+
203
+ def __init__(
204
+ self,
205
+ host: str,
206
+ password: str,
207
+ username: str = "admin",
208
+ logger: Logger = None,
209
+ verify_ssl: bool = True,
210
+ timeout: int = 30,
211
+ ) -> None:
212
+ super().__init__(host, password, username, logger, verify_ssl, timeout)
213
+
214
+ self.credentials = base64.b64encode(
215
+ bytes(f"{self.username}:{self.password}", "utf8")
216
+ ).decode("utf8")
217
+ # device data
218
+ self.status: Status = Status() # {}
219
+ self.brand = "TP-Link"
220
+ self.firmware: Firmware = {}
221
+ self.hostname = ""
222
+ self.ipv4status: IPv4Status = IPv4Status()
223
+ self.network: NetInfo = NetInfo()
224
+ self.ipv4Reserves: list[IPv4Reservation] = []
225
+ self.dhcpLeases: list[IPv4DHCPLease] = []
226
+ self.connDevices: list[Device] = []
227
+
228
+ # N/A. WDR family has no session support , so no "logged" state
229
+ def authorize(self) -> None:
230
+ pass
231
+
232
+ def logout(self) -> None:
233
+ pass
234
+
235
+ def supports(self) -> bool:
236
+ try:
237
+ response: Response = self.request("check", "")
238
+ return response.status_code == 200
239
+ except Exception:
240
+ return False
241
+
242
+ def get_firmware(self) -> Firmware:
243
+ self._updateStatus()
244
+ return self.firmware
245
+
246
+ def get_status(self) -> Status:
247
+ self._updateStatus()
248
+ return self.status
249
+
250
+ def get_ipv4_status(self) -> IPv4Status:
251
+ self._updateStatus()
252
+ self._updateNet()
253
+ return self.ipv4status
254
+
255
+ def get_ipv4_reservations(self):
256
+ self._updateNet()
257
+ return self.ipv4Reserves
258
+
259
+ def get_ipv4_dhcp_leases(self):
260
+ self._updateNet()
261
+ return self.dhcpLeases
262
+
263
+ def get_clients(self):
264
+ self._updateNet()
265
+ return self.connDevices
266
+
267
+ def reboot(self) -> None:
268
+ self.request("reboot", "Reboot=Reboot", True)
269
+
270
+ def set_wifi(self, wifi: Connection, enable: bool) -> None:
271
+ # main wifi cannot be activated / deactivated via software. Only by the phisical button
272
+ # Guest wifi can, but saved changes won't activate until next reboot
273
+ if wifi == Connection.GUEST_2G:
274
+ section = "wgsettings"
275
+ query = "setNetworkMode=1"
276
+ if wifi == Connection.GUEST_2G:
277
+ section = "wgsettings"
278
+ query = "setNetworkMode_5G=1"
279
+
280
+ self.request(section, query, True)
281
+
282
+ def update(self, what: str = "") -> None:
283
+ if what == "":
284
+ return None
285
+ if what.lower() == "status":
286
+ return self._updateStatus()
287
+ if what.lower() == "firmware":
288
+ return self._updateStatus()
289
+ if what.lower() == "net":
290
+ return self._updateNet()
291
+ if what.lower() == "all":
292
+ self._updateStatus()
293
+ self._updateNet()
294
+ return None
295
+
296
+ def _updateStatus(self) -> None:
297
+ raw = self.request("summary", "")
298
+ self._parseSummary(raw)
299
+ self._updateNet()
300
+
301
+ def _updateNet(self) -> None:
302
+ sections = "netWan,netLan,dualBand,"
303
+ sections += "w24settings,w24wps,w24sec,w24adv,"
304
+ sections += "w50settings,w50wps,w50sec,w50adv,"
305
+ sections += "wgsettings,wgshare,dhcpconfig,dhcplease,"
306
+ sections += "sysroute,upnpFwd"
307
+
308
+ section_list = sections.split(",")
309
+ for section in section_list:
310
+ self._updateSection(section)
311
+
312
+ multiPage_list = "w24stations,w50stations,dhcpreserve,portFwd".split(",")
313
+ for section in multiPage_list:
314
+ self._updateMultiSection(section)
315
+
316
+ self._updateDevices()
317
+
318
+ def _updateDevices(self):
319
+ isWireless: list = []
320
+ w24s: list = self.network.wlan24Gcli
321
+
322
+ self.connDevices = []
323
+ for wl24 in w24s:
324
+ if wl24[0] not in isWireless:
325
+ _dev: HostId = self._findHostInLeases(wl24[0])
326
+ thisone = Device(Connection.HOST_2G, wl24[0], _dev.ipaddr, _dev.host)
327
+ thisone.packets_received = wl24[3]
328
+ thisone.packets_sent = wl24[2]
329
+ self.connDevices.append(thisone)
330
+ isWireless.append(wl24[0])
331
+
332
+ w50s = self.network.wlan50Gcli
333
+ for wl50 in w50s:
334
+ if wl50[0] not in isWireless:
335
+ _dev: HostId = self._findHostInLeases(wl50[0])
336
+ thisone = Device(Connection.HOST_5G, wl50[0], _dev.ipaddr, _dev.host)
337
+ thisone.packets_received = wl50[3]
338
+ thisone.packets_sent = wl50[2]
339
+ self.connDevices.append(thisone)
340
+ isWireless.append(wl50[0])
341
+
342
+ self.status.wifi_clients_total = len(isWireless)
343
+
344
+ connected: list[IPv4DHCPLease] = self.dhcpLeases
345
+ client: IPv4DHCPLease = {}
346
+
347
+ wired_speed = 1 * 1024 * 1024 * 1024
348
+
349
+ for client in connected:
350
+ if client.macaddr not in isWireless:
351
+ thisone = Device(
352
+ Connection.WIRED, client.macaddr, client.ipaddr, client.hostname
353
+ )
354
+ thisone.up_speed = wired_speed
355
+ thisone.down_speed = wired_speed
356
+ self.connDevices.append(thisone)
357
+
358
+ self.status.devices = self.connDevices
359
+
360
+ wifiCli = len(isWireless)
361
+ totalCli = len(self.connDevices)
362
+ wiredCli = totalCli - wifiCli
363
+
364
+ self.status.wifi_clients_total = wifiCli
365
+ self.status.guest_clients_total = 0
366
+ self.status.clients_total = totalCli
367
+ self.status.wired_total = wiredCli
368
+
369
+ def _updateSection(self, section: str) -> None:
370
+ raw = self.request(section, "")
371
+ data = self._parseRawHTML(raw)
372
+ self._parseSection(section, data)
373
+
374
+ def _updateMultiSection(self, section: str) -> None:
375
+ # For sections with potentially more than one page
376
+ if section == "w24stations" or section == "w50stations":
377
+ raw = self.request(section, "")
378
+ data = self._parseRawHTML(raw)
379
+ mainData = data["script0"]
380
+ listData: list = data["script1"]
381
+ numTotal = mainData[0] - (mainData[1] - 1) * mainData[2]
382
+
383
+ nextPage = False
384
+ if numTotal > mainData[2]:
385
+ numTotal = mainData[2]
386
+ nextPage = True
387
+
388
+ while nextPage:
389
+ query = "Page=+int(mainData[1]+1"
390
+ raw = self.request(section, query)
391
+ nextPage = False
392
+ data = self._parseRawHTML(raw)
393
+ mainData = data["script0"]
394
+ listData.extend(data["script1"])
395
+ numTotal = mainData[0] - (mainData[1] - 1) * mainData[2]
396
+ if numTotal > mainData[2]:
397
+ numTotal = mainData[2]
398
+ nextPage = True
399
+
400
+ self._parseSection(section, {"script0": mainData, "script1": listData})
401
+
402
+ elif section == "dhcpreserve":
403
+ raw = self.request(section, "")
404
+ data = self._parseRawHTML(raw)
405
+ currpage = int(data["script1"][0])
406
+ lastpage = int(data["script1"][3])
407
+ tmpData = {}
408
+ while currpage < lastpage:
409
+ query = f"Page={str(currpage + 1)}"
410
+ raw = self.request(section, query)
411
+ tmpData = self._parseRawHTML(raw)
412
+
413
+ tArr = tmpData["script0"]
414
+ for item in tArr:
415
+ data["script0"].append(item)
416
+
417
+ currpage = int(tmpData["script1"][0])
418
+ lastpage = int(tmpData["script1"][3])
419
+ data["script1"] = tmpData["script1"]
420
+
421
+ self._parseSection(section, data)
422
+
423
+ elif section == "portFwd":
424
+ # TODO
425
+ # self._parseSection(section, data)
426
+ pass
427
+
428
+ def _parseSection(self, section: str, data: dict) -> None:
429
+
430
+ if section == "netLan":
431
+ lanData = data["script0"]
432
+ self.ipv4status._lan_ipv4_netmask = IPv4Address(lanData[3])
433
+ self.network.ipv4["igmpProxy"] = lanData[4]
434
+ self.ipv4status.lan_ipv4_dhcp_enable = False
435
+
436
+ elif section == "netWan":
437
+ wanData = data["script1"]
438
+ connType = self._get_conn_type(int(wanData[0]) - 1)
439
+ if not connType:
440
+ connType = "unkown"
441
+ self.ipv4status._wan_ipv4_conntype = connType
442
+ self.status.conn_type = connType
443
+
444
+ self.ipv4status._wan_ipv4_netmask = IPv4Address(wanData[14] or "0.0.0.0")
445
+ dns = ["0.0.0.0", "0.0.0.0"]
446
+ # self.ipv4status._wan_ipv4_pridns = '0.0.0.0'
447
+ # self.ipv4status._wan_ipv4_snddns = '0.0.0.0'
448
+ if wanData[19] == "1":
449
+ dns[0] = wanData[20]
450
+ dns[1] = wanData[22]
451
+
452
+ self.ipv4status._wan_ipv4_pridns = IPv4Address(dns[0])
453
+ self.ipv4status._wan_ipv4_snddns = IPv4Address(dns[1])
454
+
455
+ self.hostname = wanData[26]
456
+
457
+ elif section == "dualBand":
458
+ # TODO
459
+ pass
460
+ elif section == "w24settings" or section == "w50settings":
461
+ # TODO
462
+ pass
463
+ elif section == "w24sec" or section == "w50sec":
464
+ # TODO
465
+ pass
466
+ elif section == "w24adv" or section == "w50adv":
467
+ # TODO
468
+ pass
469
+ elif section == "w24stations" or section == "w50stations":
470
+ listData = data["script1"]
471
+ if len(listData) > 3:
472
+ for i in range(0, len(listData), 4):
473
+ tmpcli = [
474
+ listData[i],
475
+ int(listData[i + 1]),
476
+ int(listData[i + 2]),
477
+ int(listData[i + 3]),
478
+ ]
479
+ if section == "w24stations":
480
+ self.network.wlan24Gcli.append(tmpcli)
481
+ elif section == "w50stations":
482
+ self.network.wlan50Gcli.append(tmpcli)
483
+ elif section == "wgsettings":
484
+ guestData = data["script3"]
485
+ self.status.guest_2g_enable = bool(int(guestData[2]))
486
+ self.status.guest_5g_enable = bool(int(guestData[3]))
487
+
488
+ elif section == "wgshare":
489
+ # TODO
490
+ pass
491
+ elif section == "sysroute":
492
+ # TODO
493
+ pass
494
+ elif section == "portFwd":
495
+ # TODO
496
+ pass
497
+ elif section == "upnpFwd":
498
+ # TODO
499
+ pass
500
+ elif section == "dhcpconfig":
501
+ cfg = data["script0"]
502
+ if cfg[0] == 1:
503
+ self.ipv4status.lan_ipv4_dhcp_enable = True
504
+ oCfg = {}
505
+ oCfg["enabled"] = bool(int(cfg[0]))
506
+ oCfg["range_start"] = cfg[1]
507
+ oCfg["range_end"] = cfg[2]
508
+ oCfg["lease_time"] = int(cfg[3])
509
+ oCfg["gateway"] = cfg[4]
510
+ oCfg["domain"] = cfg[5] or None
511
+ oCfg["dns_pri"] = cfg[6] or None
512
+ oCfg["dns_sec"] = cfg[7] or None
513
+ self.network.dhcp_cfg = oCfg
514
+ elif section == "dhcpreserve":
515
+ item: IPv4Reservation = {}
516
+ self.ipv4Reserves = []
517
+ for i in range(0, len(data["script0"]), 3):
518
+ _dev: HostId = self._findHostInLeases(data["script0"][i])
519
+ item = IPv4Reservation(
520
+ data["script0"][i],
521
+ data["script0"][i + 1],
522
+ _dev.host,
523
+ bool(int(data["script0"][i + 2])),
524
+ )
525
+ self.ipv4Reserves.append(item)
526
+ elif section == "dhcplease":
527
+ self.dhcpLeases = []
528
+ for i in range(0, len(data["script0"]), 4):
529
+ item = IPv4DHCPLease(
530
+ EUI48(data["script0"][i + 1]),
531
+ IPv4Address(data["script0"][i + 2]),
532
+ data["script0"][i],
533
+ data["script0"][i + 3],
534
+ )
535
+ self.dhcpLeases.append(item)
536
+ elif section == "portFwd":
537
+ # TODO
538
+ pass
539
+
540
+ def _parseSummary(self, raw: str) -> None:
541
+ data = self._parseRawHTML(raw)
542
+ tFirm = data["script0"][6]
543
+ tHard = data["script0"][7]
544
+ # WDR3600 v1 00000000
545
+ tModel = tHard.split(" ")
546
+ self.firmware = Firmware(tHard, tModel[0], tFirm)
547
+ self.status = Status()
548
+ self.status.wan_ipv4_uptime = int(data["script0"][8])
549
+ self.status._lan_ipv4_addr = get_ip(data["script1"][1])
550
+ self.status._lan_macaddr = EUI48(data["script1"][0])
551
+
552
+ self.ipv4status._lan_ipv4_ipaddr = get_ip(data["script1"][1])
553
+ self.ipv4status._lan_macaddr = EUI48(data["script1"][0])
554
+
555
+ self.status._wan_macaddr = EUI48(data["script5"][1])
556
+ self.status._wan_ipv4_addr = get_ip(data["script5"][2])
557
+ self.status._wan_ipv4_gateway = get_ip(data["script5"][7])
558
+
559
+ self.ipv4status._wan_macaddr = EUI48(data["script5"][1])
560
+ self.ipv4status._wan_ipv4_ipaddr = get_ip(data["script5"][2])
561
+ self.ipv4status._wan_ipv4_netmask = ""
562
+ self.ipv4status._wan_ipv4_gateway = get_ip(data["script5"][7])
563
+
564
+ self.status.guest_2g_enable = None
565
+ self.status.guest_5g_enable = None
566
+ self.status.wifi_2g_enable = bool(int(data["script2"][0]))
567
+ self.status.wifi_5g_enable = bool(int(data["script3"][0]))
568
+
569
+ self.status.conn_type = "unknown"
570
+ self.status.devices = []
571
+
572
+ def _get_conn_type(self, n: int) -> str:
573
+ wantypeinfo = [
574
+ 6,
575
+ 0,
576
+ "WanDynamicIpCfgRpm.htm",
577
+ 1,
578
+ "WanStaticIpCfgRpm.htm",
579
+ 2,
580
+ "PPPoECfgRpm.htm",
581
+ 5,
582
+ "BPACfgRpm.htm",
583
+ 6,
584
+ "L2TPCfgRpm.htm",
585
+ 7,
586
+ "PPTPCfgRpm.htm",
587
+ 0,
588
+ 0,
589
+ ]
590
+ wantype_filtered = wantypeinfo[2 * n + 1]
591
+
592
+ wan_type: list = [
593
+ "Dynamic IP",
594
+ "Static IP",
595
+ "PPPoE/Russia PPPoE",
596
+ "802.1x DHCP",
597
+ "802.1x Static IP",
598
+ "BigPond Cable",
599
+ "L2TP/Russia L2TP",
600
+ "PPTP/Russia PPTP",
601
+ ]
602
+ return wan_type[wantype_filtered]
603
+
604
+ def _findHostInLeases(self, macaddr: str) -> HostId:
605
+ arr = self.dhcpLeases
606
+ for lease in arr:
607
+ if lease.macaddr == macaddr:
608
+ return HostId(lease.ipaddr, lease.hostname)
609
+
610
+ return HostId("0.0.0.0", "-")
611
+
612
+ def _parseRawHTML(self, rawHTML: str) -> dict:
613
+
614
+ parser = muParser("script")
615
+ if not rawHTML:
616
+ return {}
617
+ parser.feed(rawHTML.decode("utf8", "ignore"))
618
+
619
+ all_scripts = parser.data
620
+ data = {}
621
+ count = 0
622
+ for script in all_scripts:
623
+
624
+ if script == "":
625
+ continue
626
+
627
+ if not str(script).startswith(("var")):
628
+ continue
629
+
630
+ oneLiner = self._parseDataBlock(script)
631
+
632
+ newArr = []
633
+ for item in oneLiner.split(","):
634
+ newVal = None
635
+ try:
636
+ newVal = int(item)
637
+ except Exception:
638
+ try:
639
+ newVal = float(item)
640
+ except Exception:
641
+ newVal = item
642
+ newArr.append(newVal)
643
+
644
+ data["script" + str(count)] = newArr
645
+ count += 1
646
+
647
+ return data
648
+
649
+ def _parseDataBlock(self, text) -> str:
650
+ lines = text.splitlines()
651
+ if len(lines) < 1:
652
+ return []
653
+ if lines[0] == "":
654
+ lines.pop(0) # delete first line if empty
655
+ if lines[-1] == "":
656
+ lines.pop() # delete last line if empty
657
+ lines.pop(0)
658
+ lines.pop()
659
+ result: str = ""
660
+ if len(lines) == 1:
661
+ result = lines[0].replace(", ", ",").replace('"', "")
662
+ else:
663
+ linesNew = []
664
+ for oneLine in lines:
665
+ oneLine = oneLine.replace(", ", ",").replace('"', "")
666
+ linesNew.append(oneLine)
667
+ lines = linesNew
668
+ result = "".join(lines)
669
+
670
+ if result.endswith(","):
671
+ result = result[:-1]
672
+ return result
@@ -25,6 +25,7 @@ class Device:
25
25
  self.down_speed: int | None = None
26
26
  self.up_speed: int | None = None
27
27
  self.signal: int | None = None
28
+ self.active: bool = True
28
29
 
29
30
  @property
30
31
  def macaddr(self):
@@ -11,6 +11,7 @@ from tplinkrouterc6u.client.c5400x import TplinkC5400XRouter
11
11
  from tplinkrouterc6u.client.c1200 import TplinkC1200Router
12
12
  from tplinkrouterc6u.client.c80 import TplinkC80Router
13
13
  from tplinkrouterc6u.client.vr import TPLinkVRClient
14
+ from tplinkrouterc6u.client.wdr import TplinkWDRRouter
14
15
 
15
16
 
16
17
  class TplinkRouterProvider:
@@ -18,7 +19,7 @@ class TplinkRouterProvider:
18
19
  def get_client(host: str, password: str, username: str = 'admin', logger: Logger = None,
19
20
  verify_ssl: bool = True, timeout: int = 30) -> AbstractRouter:
20
21
  for client in [TplinkC5400XRouter, TPLinkVRClient, TPLinkEXClient, TPLinkMRClient, TPLinkDecoClient,
21
- TPLinkXDRClient, TplinkRouter, TplinkC80Router]:
22
+ TPLinkXDRClient, TplinkRouter, TplinkC80Router, TplinkWDRRouter]:
22
23
  router = client(host, password, username, logger, verify_ssl, timeout)
23
24
  if router.supports():
24
25
  return router
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tplinkrouterc6u
3
- Version: 5.6.2
3
+ Version: 5.8.0
4
4
  Summary: TP-Link Router API (supports also Mercusys Router)
5
5
  Home-page: https://github.com/AlexandrErohin/TP-Link-Archer-C6U
6
6
  Author: Alex Erohin
@@ -187,6 +187,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
187
187
  | down_speed | download speed | int, None |
188
188
  | up_speed | upload speed | int, None |
189
189
  | signal | Signal strength | int, None |
190
+ | active | Is active device | bool |
190
191
 
191
192
  ### <a id="IPv4Reservation">IPv4Reservation</a>
192
193
  | Field | Description | Type |
@@ -288,9 +289,11 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
288
289
  - [TP-LINK routers](#tplink)
289
290
  - [MERCUSYS routers](#mercusys)
290
291
  ### <a id="tplink">TP-LINK routers</a>
292
+ - Archer A6 V2.0
291
293
  - Archer A7 V5
292
- - Archer A8 1.0
294
+ - Archer A8 (1.0, 2.20)
293
295
  - Archer A9 V6
296
+ - Archer A20 v1.0
294
297
  - Archer AX10 v1.0
295
298
  - Archer AX12 v1.0
296
299
  - Archer AX20 (v1.0, v3.0)
@@ -309,6 +312,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
309
312
  - Archer AX3000 V1
310
313
  - Archer AX6000 V1
311
314
  - Archer AX11000 V1
315
+ - Archer BE550 v1.0
312
316
  - Archer BE800 v1.0
313
317
  - Archer BE805 v1.0
314
318
  - Archer BE3600 1.6
@@ -338,6 +342,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
338
342
  - Deco P7
339
343
  - Deco X20
340
344
  - Deco X50 v1.3
345
+ - Deco X55 1.0
341
346
  - Deco X60 V3
342
347
  - Deco X90
343
348
  - Deco XE75 (v1.0, v2.0)
@@ -353,6 +358,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
353
358
  - TL-WA1201 3.0
354
359
  - TL-WA3001 v1.0
355
360
  - TL-XDR3010 V2
361
+ - TL-WDR3600 V1
356
362
  - XC220-G3v v2.30
357
363
  ### <a id="mercusys">MERCUSYS routers</a>
358
364
  - MR47BE v1.0
@@ -1,32 +1,34 @@
1
1
  test/__init__.py,sha256=McQmUjeN3AwmwdS6QNfwGXXE77OKoPK852I2BM9XsrU,210
2
2
  test/test_client_c1200.py,sha256=4RdwMEwTGWLOxOVTuPikifXDcUJFQ13DuyWGm2Aevrk,8811
3
- test/test_client_c6u.py,sha256=3nvhybxYxiCVjjLXB3nBYrAkXS8zoLtJibgvsqsTQ3U,32140
4
- test/test_client_c80.py,sha256=ZW4Ad-Y-o_IVNz1YPdrtq6At-_hujT1sMtOEOLkgu24,42081
3
+ test/test_client_c6u.py,sha256=ulKoOJiwQVaBslY7kxCa7HmLaqTPzKJaNjm-xIBjB6A,33755
4
+ test/test_client_c80.py,sha256=RY_1SgRVcQQdN9h0_IXA0YW4_0flEB_uel05QvDDfws,42359
5
5
  test/test_client_deco.py,sha256=YPLKRD8GoyDYHfRgdXvCk8iVNw8zdMJW-AHVnNbpdTM,31719
6
6
  test/test_client_ex.py,sha256=0dVvOJqxpR2xNca99vadfdpxmzW-4sk9X_4tAK8x9-c,21710
7
- test/test_client_mr.py,sha256=NxTgXv_wN50K8KsJhykoU-TX0ddDGlX_7jp50FXOLjg,33517
7
+ test/test_client_mr.py,sha256=lePxkmjcPzcrSFcaT8bT67L154cVJIOWrFlXMDOa8oY,33423
8
+ test/test_client_wdr.py,sha256=0ZnRNP57MbuMv2cxFS8iIoVyv8Q6gtY0Q03gtHp9AWY,13492
8
9
  test/test_client_xdr.py,sha256=mgn-xL5mD5sHD8DjTz9vpY7jeh4Ob6Um6Y8v5Qgx2jA,23374
9
- tplinkrouterc6u/__init__.py,sha256=-YGlTq78D4jE7pTTs4xTW3NamoiSIrNtpswne5ohb0s,951
10
+ tplinkrouterc6u/__init__.py,sha256=DDy6XVFH8Ne5C9JToRWNzRqevUfNutLUTlDhvIMsebo,1006
10
11
  tplinkrouterc6u/client_abstract.py,sha256=3UYzmll774S_Gb5E0FTVO_rI3-XFM7PSklg1-V-2jls,1419
11
- tplinkrouterc6u/provider.py,sha256=FGwdxiebku-Bgr6OM9YZQqdus68t1twHw7eRPco2NjE,2080
12
+ tplinkrouterc6u/provider.py,sha256=bzH0WW2peC66f8NqfiI3t_niX6MFO8SAdKP9WZL-IV4,2152
12
13
  tplinkrouterc6u/client/__init__.py,sha256=KBy3fmtA9wgyFrb0Urh2x4CkKtWVnESdp-vxmuOvq0k,27
13
14
  tplinkrouterc6u/client/c1200.py,sha256=_nY_pJ-wPWODAaes9kHPdVcM6YM54f1E54CfdoFHqbE,4771
14
15
  tplinkrouterc6u/client/c5400x.py,sha256=9E0omBSbWY_ljrs5MTCMu5brmrLtzsDB5O62Db8lP8Q,4329
15
- tplinkrouterc6u/client/c6u.py,sha256=TbS8qbwfPzIVDEMNNlKMhWuGeo-RUvaCkG_bF7Y4L0Q,17637
16
- tplinkrouterc6u/client/c80.py,sha256=3Cy9FCYHZaGzTGAjsB2BdjxpzJmBoW6TMDTDaCyUDjI,18437
16
+ tplinkrouterc6u/client/c6u.py,sha256=zcnMFsP-rn219PLfav-7KtHhOFkfrdtYnCyHWdcdKOw,17661
17
+ tplinkrouterc6u/client/c80.py,sha256=ArVhza_fnXcEO-_fsQOd1l2QvmSfsswtohKxrZxEnoU,18568
17
18
  tplinkrouterc6u/client/deco.py,sha256=cpKRggKD2RvSmMZuD6tzsZmehAUCU9oLiTTHcZBW81Y,8898
18
19
  tplinkrouterc6u/client/ex.py,sha256=ZK08mlOts3fbBJh9NUrzBADP0N9LHgzOXCKY2tF9gG0,13111
19
20
  tplinkrouterc6u/client/mr.py,sha256=kJ-meAgMPub6aKtzBEVI3CNNzsVab7pLkOlXU9dj030,26030
20
21
  tplinkrouterc6u/client/vr.py,sha256=7Tbu0IrWtr4HHtyrnLFXEJi1QctzhilciL7agtwQ0R8,5025
22
+ tplinkrouterc6u/client/wdr.py,sha256=i54PEifjhfOScDpgNBXygw9U4bfsVtle846_YjnDoBs,21679
21
23
  tplinkrouterc6u/client/xdr.py,sha256=QaZ_5vCaf8BV_JEs3S2Nz-QDREBYHGh3OUWIVS-fefY,10406
22
24
  tplinkrouterc6u/common/__init__.py,sha256=pCTvVZ9CAwgb7MxRnLx0y1rI0sTKSwT24FfxWfQXeTM,33
23
- tplinkrouterc6u/common/dataclass.py,sha256=QQTGb9w4Yt3GNkISgrQhvWPMEEJOsgFFZ2MI1MIAPjw,7807
25
+ tplinkrouterc6u/common/dataclass.py,sha256=UNQdr-tkAQcF3KiefKOIKOtPTJczgcey4zMvbqbv_4M,7840
24
26
  tplinkrouterc6u/common/encryption.py,sha256=4HelTxzN6esMfDZRBt3m8bwB9Nj_biKijnCnrGWPWKg,6228
25
27
  tplinkrouterc6u/common/exception.py,sha256=_0G8ZvW5__CsGifHrsZeULdl8c6EUD071sDCQsQgrHY,140
26
28
  tplinkrouterc6u/common/helper.py,sha256=23b04fk9HuVinrZXMCS5R1rmF8uZ7eM-Cdnp7Br9NR0,572
27
29
  tplinkrouterc6u/common/package_enum.py,sha256=4ykL_2Pw0nDEIH_qR9UJlFF6stTgSfhPz32r8KT-sh8,1624
28
- tplinkrouterc6u-5.6.2.dist-info/licenses/LICENSE,sha256=YF6QR6Vjxcg5b_sYIyqkME7FZYau5TfEUGTG-0JeRK0,35129
29
- tplinkrouterc6u-5.6.2.dist-info/METADATA,sha256=OJZ_573ij0N15aCx8DIhTyQ-gM8x794j5gYaygq5310,15365
30
- tplinkrouterc6u-5.6.2.dist-info/WHEEL,sha256=tTnHoFhvKQHCh4jz3yCn0WPTYIy7wXx3CJtJ7SJGV7c,91
31
- tplinkrouterc6u-5.6.2.dist-info/top_level.txt,sha256=1iSCCIueqgEkrTxtQ-jiHe99jAB10zqrVdBcwvNfe_M,21
32
- tplinkrouterc6u-5.6.2.dist-info/RECORD,,
30
+ tplinkrouterc6u-5.8.0.dist-info/licenses/LICENSE,sha256=YF6QR6Vjxcg5b_sYIyqkME7FZYau5TfEUGTG-0JeRK0,35129
31
+ tplinkrouterc6u-5.8.0.dist-info/METADATA,sha256=XceOtjPvarwp7C7OvSVjvmFgLTU-nYDwu5K_DSMjbnc,15496
32
+ tplinkrouterc6u-5.8.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
33
+ tplinkrouterc6u-5.8.0.dist-info/top_level.txt,sha256=1iSCCIueqgEkrTxtQ-jiHe99jAB10zqrVdBcwvNfe_M,21
34
+ tplinkrouterc6u-5.8.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (77.0.1)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5