99
99
Annotated ,
100
100
Any ,
101
101
NamedTuple ,
102
+ TypedDict ,
102
103
cast ,
103
104
)
104
105
@@ -147,18 +148,35 @@ class ConnectAttempt(NamedTuple):
147
148
device : type
148
149
149
150
151
+ class DiscoveredMeta (TypedDict ):
152
+ """Meta info about discovery response."""
153
+
154
+ ip : str
155
+ port : int
156
+
157
+
158
+ class DiscoveredRaw (TypedDict ):
159
+ """Try to connect attempt."""
160
+
161
+ meta : DiscoveredMeta
162
+ discovery_response : dict
163
+
164
+
150
165
OnDiscoveredCallable = Callable [[Device ], Coroutine ]
166
+ OnDiscoveredRawCallable = Callable [[DiscoveredRaw ], None ]
151
167
OnUnsupportedCallable = Callable [[UnsupportedDeviceError ], Coroutine ]
152
168
OnConnectAttemptCallable = Callable [[ConnectAttempt , bool ], None ]
153
169
DeviceDict = dict [str , Device ]
154
170
155
171
NEW_DISCOVERY_REDACTORS : dict [str , Callable [[Any ], Any ] | None ] = {
156
172
"device_id" : lambda x : "REDACTED_" + x [9 ::],
173
+ "device_name" : lambda x : "#MASKED_NAME#" if x else "" ,
157
174
"owner" : lambda x : "REDACTED_" + x [9 ::],
158
175
"mac" : mask_mac ,
159
176
"master_device_id" : lambda x : "REDACTED_" + x [9 ::],
160
177
"group_id" : lambda x : "REDACTED_" + x [9 ::],
161
178
"group_name" : lambda x : "I01BU0tFRF9TU0lEIw==" ,
179
+ "encrypt_info" : lambda x : {** x , "key" : "" , "data" : "" },
162
180
}
163
181
164
182
@@ -216,6 +234,7 @@ def __init__(
216
234
self ,
217
235
* ,
218
236
on_discovered : OnDiscoveredCallable | None = None ,
237
+ on_discovered_raw : OnDiscoveredRawCallable | None = None ,
219
238
target : str = "255.255.255.255" ,
220
239
discovery_packets : int = 3 ,
221
240
discovery_timeout : int = 5 ,
@@ -240,6 +259,7 @@ def __init__(
240
259
self .unsupported_device_exceptions : dict = {}
241
260
self .invalid_device_exceptions : dict = {}
242
261
self .on_unsupported = on_unsupported
262
+ self .on_discovered_raw = on_discovered_raw
243
263
self .credentials = credentials
244
264
self .timeout = timeout
245
265
self .discovery_timeout = discovery_timeout
@@ -329,12 +349,23 @@ def datagram_received(
329
349
config .timeout = self .timeout
330
350
try :
331
351
if port == self .discovery_port :
332
- device = Discover ._get_device_instance_legacy (data , config )
352
+ json_func = Discover ._get_discovery_json_legacy
353
+ device_func = Discover ._get_device_instance_legacy
333
354
elif port == Discover .DISCOVERY_PORT_2 :
334
355
config .uses_http = True
335
- device = Discover ._get_device_instance (data , config )
356
+ json_func = Discover ._get_discovery_json
357
+ device_func = Discover ._get_device_instance
336
358
else :
337
359
return
360
+ info = json_func (data , ip )
361
+ if self .on_discovered_raw is not None :
362
+ self .on_discovered_raw (
363
+ {
364
+ "discovery_response" : info ,
365
+ "meta" : {"ip" : ip , "port" : port },
366
+ }
367
+ )
368
+ device = device_func (info , config )
338
369
except UnsupportedDeviceError as udex :
339
370
_LOGGER .debug ("Unsupported device found at %s << %s" , ip , udex )
340
371
self .unsupported_device_exceptions [ip ] = udex
@@ -391,6 +422,7 @@ async def discover(
391
422
* ,
392
423
target : str = "255.255.255.255" ,
393
424
on_discovered : OnDiscoveredCallable | None = None ,
425
+ on_discovered_raw : OnDiscoveredRawCallable | None = None ,
394
426
discovery_timeout : int = 5 ,
395
427
discovery_packets : int = 3 ,
396
428
interface : str | None = None ,
@@ -421,6 +453,8 @@ async def discover(
421
453
:param target: The target address where to send the broadcast discovery
422
454
queries if multi-homing (e.g. 192.168.xxx.255).
423
455
:param on_discovered: coroutine to execute on discovery
456
+ :param on_discovered_raw: Optional callback once discovered json is loaded
457
+ before any attempt to deserialize it and create devices
424
458
:param discovery_timeout: Seconds to wait for responses, defaults to 5
425
459
:param discovery_packets: Number of discovery packets to broadcast
426
460
:param interface: Bind to specific interface
@@ -443,6 +477,7 @@ async def discover(
443
477
discovery_packets = discovery_packets ,
444
478
interface = interface ,
445
479
on_unsupported = on_unsupported ,
480
+ on_discovered_raw = on_discovered_raw ,
446
481
credentials = credentials ,
447
482
timeout = timeout ,
448
483
discovery_timeout = discovery_timeout ,
@@ -476,6 +511,7 @@ async def discover_single(
476
511
credentials : Credentials | None = None ,
477
512
username : str | None = None ,
478
513
password : str | None = None ,
514
+ on_discovered_raw : OnDiscoveredRawCallable | None = None ,
479
515
on_unsupported : OnUnsupportedCallable | None = None ,
480
516
) -> Device | None :
481
517
"""Discover a single device by the given IP address.
@@ -493,6 +529,9 @@ async def discover_single(
493
529
username and password are ignored if provided.
494
530
:param username: Username for devices that require authentication
495
531
:param password: Password for devices that require authentication
532
+ :param on_discovered_raw: Optional callback once discovered json is loaded
533
+ before any attempt to deserialize it and create devices
534
+ :param on_unsupported: Optional callback when unsupported devices are discovered
496
535
:rtype: SmartDevice
497
536
:return: Object for querying/controlling found device.
498
537
"""
@@ -529,6 +568,7 @@ async def discover_single(
529
568
credentials = credentials ,
530
569
timeout = timeout ,
531
570
discovery_timeout = discovery_timeout ,
571
+ on_discovered_raw = on_discovered_raw ,
532
572
),
533
573
local_addr = ("0.0.0.0" , 0 ), # noqa: S104
534
574
)
@@ -666,15 +706,19 @@ def _get_device_class(info: dict) -> type[Device]:
666
706
return get_device_class_from_sys_info (info )
667
707
668
708
@staticmethod
669
- def _get_device_instance_legacy (data : bytes , config : DeviceConfig ) -> IotDevice :
670
- """Get SmartDevice from legacy 9999 response."""
709
+ def _get_discovery_json_legacy (data : bytes , ip : str ) -> dict :
710
+ """Get discovery json from legacy 9999 response."""
671
711
try :
672
712
info = json_loads (XorEncryption .decrypt (data ))
673
713
except Exception as ex :
674
714
raise KasaException (
675
- f"Unable to read response from device: { config . host } : { ex } "
715
+ f"Unable to read response from device: { ip } : { ex } "
676
716
) from ex
717
+ return info
677
718
719
+ @staticmethod
720
+ def _get_device_instance_legacy (info : dict , config : DeviceConfig ) -> Device :
721
+ """Get IotDevice from legacy 9999 response."""
678
722
if _LOGGER .isEnabledFor (logging .DEBUG ):
679
723
data = redact_data (info , IOT_REDACTORS ) if Discover ._redact_data else info
680
724
_LOGGER .debug ("[DISCOVERY] %s << %s" , config .host , pf (data ))
@@ -716,19 +760,24 @@ def _decrypt_discovery_data(discovery_result: DiscoveryResult) -> None:
716
760
discovery_result .decrypted_data = json_loads (decrypted_data )
717
761
718
762
@staticmethod
719
- def _get_device_instance (
720
- data : bytes ,
721
- config : DeviceConfig ,
722
- ) -> Device :
723
- """Get SmartDevice from the new 20002 response."""
724
- debug_enabled = _LOGGER .isEnabledFor (logging .DEBUG )
763
+ def _get_discovery_json (data : bytes , ip : str ) -> dict :
764
+ """Get discovery json from the new 20002 response."""
725
765
try :
726
766
info = json_loads (data [16 :])
727
767
except Exception as ex :
728
- _LOGGER .debug ("Got invalid response from device %s: %s" , config . host , data )
768
+ _LOGGER .debug ("Got invalid response from device %s: %s" , ip , data )
729
769
raise KasaException (
730
- f"Unable to read response from device: { config . host } : { ex } "
770
+ f"Unable to read response from device: { ip } : { ex } "
731
771
) from ex
772
+ return info
773
+
774
+ @staticmethod
775
+ def _get_device_instance (
776
+ info : dict ,
777
+ config : DeviceConfig ,
778
+ ) -> Device :
779
+ """Get SmartDevice from the new 20002 response."""
780
+ debug_enabled = _LOGGER .isEnabledFor (logging .DEBUG )
732
781
733
782
try :
734
783
discovery_result = DiscoveryResult .from_dict (info ["result" ])
@@ -757,7 +806,9 @@ def _get_device_instance(
757
806
Discover ._decrypt_discovery_data (discovery_result )
758
807
except Exception :
759
808
_LOGGER .exception (
760
- "Unable to decrypt discovery data %s: %s" , config .host , data
809
+ "Unable to decrypt discovery data %s: %s" ,
810
+ config .host ,
811
+ redact_data (info , NEW_DISCOVERY_REDACTORS ),
761
812
)
762
813
763
814
type_ = discovery_result .device_type
0 commit comments