diff --git a/kasa/device_factory.py b/kasa/device_factory.py index 99654a0c..3eb6419a 100644 --- a/kasa/device_factory.py +++ b/kasa/device_factory.py @@ -8,7 +8,7 @@ from typing import Any from .device import Device from .device_type import DeviceType -from .deviceconfig import DeviceConfig, DeviceFamily +from .deviceconfig import DeviceConfig, DeviceEncryptionType, DeviceFamily from .exceptions import KasaException, UnsupportedDeviceError from .iot import ( IotBulb, @@ -176,25 +176,32 @@ def get_device_class_from_family( return cls -def get_protocol( - config: DeviceConfig, -) -> BaseProtocol | None: - """Return the protocol from the connection name. +def get_protocol(config: DeviceConfig, *, strict: bool = False) -> BaseProtocol | None: + """Return the protocol from the device config. For cameras and vacuums the device family is a simple mapping to the protocol/transport. For other device types the transport varies based on the discovery information. + + :param config: Device config to derive protocol + :param strict: Require exact match on encrypt type """ ctype = config.connection_type protocol_name = ctype.device_family.value.split(".")[0] if ctype.device_family is DeviceFamily.SmartIpCamera: + if strict and ctype.encryption_type is not DeviceEncryptionType.Aes: + return None return SmartCamProtocol(transport=SslAesTransport(config=config)) if ctype.device_family is DeviceFamily.IotIpCamera: + if strict and ctype.encryption_type is not DeviceEncryptionType.Xor: + return None return IotProtocol(transport=LinkieTransportV2(config=config)) if ctype.device_family is DeviceFamily.SmartTapoRobovac: + if strict and ctype.encryption_type is not DeviceEncryptionType.Aes: + return None return SmartProtocol(transport=SslTransport(config=config)) protocol_transport_key = ( diff --git a/kasa/deviceconfig.py b/kasa/deviceconfig.py index d2fb3e45..c5d5b1d5 100644 --- a/kasa/deviceconfig.py +++ b/kasa/deviceconfig.py @@ -20,7 +20,7 @@ None {'host': '127.0.0.3', 'timeout': 5, 'credentials': {'username': 'user@example.com', \ 'password': 'great_password'}, 'connection_type'\ : {'device_family': 'SMART.TAPOBULB', 'encryption_type': 'KLAP', 'login_version': 2, \ -'https': False}, 'uses_http': True} +'https': False}} >>> later_device = await Device.connect(config=Device.Config.from_dict(config_dict)) >>> print(later_device.alias) # Alias is available as connect() calls update() @@ -148,9 +148,12 @@ class DeviceConfig(_DeviceConfigBaseMixin): DeviceFamily.IotSmartPlugSwitch, DeviceEncryptionType.Xor ) ) - #: True if the device uses http. Consumers should retrieve rather than set this - #: in order to determine whether they should pass a custom http client if desired. - uses_http: bool = False + + @property + def uses_http(self) -> bool: + """True if the device uses http.""" + ctype = self.connection_type + return ctype.encryption_type is not DeviceEncryptionType.Xor or ctype.https #: Set a custom http_client for the device to use. http_client: ClientSession | None = field( diff --git a/kasa/discover.py b/kasa/discover.py index b696c370..9ed4d4cf 100755 --- a/kasa/discover.py +++ b/kasa/discover.py @@ -360,7 +360,6 @@ class _DiscoverProtocol(asyncio.DatagramProtocol): json_func = Discover._get_discovery_json_legacy device_func = Discover._get_device_instance_legacy elif port == Discover.DISCOVERY_PORT_2: - config.uses_http = True json_func = Discover._get_discovery_json device_func = Discover._get_device_instance else: @@ -634,6 +633,8 @@ class Discover: Device.Family.SmartTapoPlug, Device.Family.IotSmartPlugSwitch, Device.Family.SmartIpCamera, + Device.Family.SmartTapoRobovac, + Device.Family.IotIpCamera, } candidates: dict[ tuple[type[BaseProtocol], type[BaseTransport], type[Device]], @@ -663,10 +664,9 @@ class Discover: port_override=port, credentials=credentials, http_client=http_client, - uses_http=encrypt is not Device.EncryptionType.Xor, ) ) - and (protocol := get_protocol(config)) + and (protocol := get_protocol(config, strict=True)) and ( device_class := get_device_class_from_family( device_family.value, https=https, require_exact=True diff --git a/tests/fixtures/serialization/deviceconfig_camera-aes-https.json b/tests/fixtures/serialization/deviceconfig_camera-aes-https.json index 559e834b..361ec6ec 100644 --- a/tests/fixtures/serialization/deviceconfig_camera-aes-https.json +++ b/tests/fixtures/serialization/deviceconfig_camera-aes-https.json @@ -5,6 +5,5 @@ "device_family": "SMART.IPCAMERA", "encryption_type": "AES", "https": true - }, - "uses_http": false + } } diff --git a/tests/fixtures/serialization/deviceconfig_plug-klap.json b/tests/fixtures/serialization/deviceconfig_plug-klap.json index ef42bb2f..fa7a6ba8 100644 --- a/tests/fixtures/serialization/deviceconfig_plug-klap.json +++ b/tests/fixtures/serialization/deviceconfig_plug-klap.json @@ -6,6 +6,5 @@ "encryption_type": "KLAP", "https": false, "login_version": 2 - }, - "uses_http": false + } } diff --git a/tests/fixtures/serialization/deviceconfig_plug-xor.json b/tests/fixtures/serialization/deviceconfig_plug-xor.json index 78cc05a9..5cb0222a 100644 --- a/tests/fixtures/serialization/deviceconfig_plug-xor.json +++ b/tests/fixtures/serialization/deviceconfig_plug-xor.json @@ -5,6 +5,5 @@ "device_family": "IOT.SMARTPLUGSWITCH", "encryption_type": "XOR", "https": false - }, - "uses_http": false + } } diff --git a/tests/test_discovery.py b/tests/test_discovery.py index 59a337d2..07553e74 100644 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -154,12 +154,10 @@ async def test_discover_single(discovery_mock, custom_port, mocker): discovery_mock.encrypt_type, discovery_mock.login_version, ) - uses_http = discovery_mock.default_port == 80 config = DeviceConfig( host=host, port_override=custom_port, connection_type=ct, - uses_http=uses_http, credentials=Credentials(), ) assert x.config == config