From a4b05de7d6924374bd0a7f2d613f05cb88beb6f8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 7 Oct 2023 08:36:54 -1000 Subject: [PATCH 1/3] Add a connect_single method to Discover to avoid the need for UDP --- kasa/discover.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/kasa/discover.py b/kasa/discover.py index f8e11a62b..7ef4b8c8b 100755 --- a/kasa/discover.py +++ b/kasa/discover.py @@ -290,6 +290,31 @@ async def discover_single( else: raise SmartDeviceException(f"Unable to get discovery response for {host}") + @staticmethod + async def connect_single( + host: str, + *, + port: Optional[int] = None, + timeout=5, + credentials: Optional[Credentials] = None, + ) -> SmartDevice: + """Connect to a single device by the given IP address. + + The device type is discovered by querying the device. + """ + unknown_dev = SmartDevice( + host=host, port=port, credentials=credentials, timeout=timeout + ) + await unknown_dev.update() + device_class = Discover._get_device_class(unknown_dev.internal_state) + dev = device_class( + host=host, port=port, credentials=credentials, timeout=timeout + ) + # Reuse the connection from the unknown device + # so we don't have to reconnect + dev.protocol = unknown_dev.protocol + return dev + @staticmethod def _get_device_class(info: dict) -> Type[SmartDevice]: """Find SmartDevice subclass for device described by passed data.""" From 03354dffb184fdce34db9412cced31636ad69848 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 7 Oct 2023 09:12:16 -1000 Subject: [PATCH 2/3] coverage --- kasa/tests/test_discovery.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/kasa/tests/test_discovery.py b/kasa/tests/test_discovery.py index 41578a2ce..2aa10f1cc 100644 --- a/kasa/tests/test_discovery.py +++ b/kasa/tests/test_discovery.py @@ -74,6 +74,26 @@ def mock_discover(self): assert x.port == custom_port or 9999 +@pytest.mark.parametrize("custom_port", [123, None]) +async def test_connect_single(discovery_data: dict, mocker, custom_port): + """Make sure that connect_single returns an initialized SmartDevice instance.""" + host = "127.0.0.1" + mocker.patch("kasa.TPLinkSmartHomeProtocol.query", return_value=discovery_data) + + dev = await Discover.connect_single(host, port=custom_port) + assert issubclass(dev.__class__, SmartDevice) + assert dev.port == custom_port or 9999 + + +async def test_connect_single_query_fails(discovery_data: dict, mocker): + """Make sure that connect_single fails when query fails.""" + host = "127.0.0.1" + mocker.patch("kasa.TPLinkSmartHomeProtocol.query", side_effect=SmartDeviceException) + + with pytest.raises(SmartDeviceException): + await Discover.connect_single(host) + + UNSUPPORTED = { "result": { "device_id": "xx", From 892f4c48320efc8580cab49e663ef26bef4502c1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 7 Oct 2023 13:36:02 -1000 Subject: [PATCH 3/3] docs --- kasa/discover.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/kasa/discover.py b/kasa/discover.py index 99870c6be..4cb7a5329 100755 --- a/kasa/discover.py +++ b/kasa/discover.py @@ -256,6 +256,11 @@ async def discover_single( ) -> SmartDevice: """Discover a single device by the given IP address. + It is generally preferred to avoid :func:`discover_single()` and + use :func:`connect_single()` instead as it should perform better when + the WiFi network is congested or the device is not responding + to discovery requests. + :param host: Hostname of device to query :rtype: SmartDevice :return: Object for querying/controlling found device. @@ -309,7 +314,19 @@ async def connect_single( ) -> SmartDevice: """Connect to a single device by the given IP address. + This method avoids the UDP based discovery process and + will connect directly to the device to query its type. + + It is generally preferred to avoid :func:`discover_single()` and + use this function instead as it should perform better when + the WiFi network is congested or the device is not responding + to discovery requests. + The device type is discovered by querying the device. + + :param host: Hostname of device to query + :rtype: SmartDevice + :return: Object for querying/controlling found device. """ unknown_dev = SmartDevice( host=host, port=port, credentials=credentials, timeout=timeout