Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions kasa/device_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

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,
Expand Down Expand Up @@ -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 = (
Expand Down
11 changes: 7 additions & 4 deletions kasa/deviceconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
{'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()
Expand Down Expand Up @@ -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(
Expand Down
6 changes: 3 additions & 3 deletions kasa/discover.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,6 @@ def datagram_received(
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:
Expand Down Expand Up @@ -634,6 +633,8 @@ async def try_connect_all(
Device.Family.SmartTapoPlug,
Device.Family.IotSmartPlugSwitch,
Device.Family.SmartIpCamera,
Device.Family.SmartTapoRobovac,
Device.Family.IotIpCamera,
}
candidates: dict[
tuple[type[BaseProtocol], type[BaseTransport], type[Device]],
Expand Down Expand Up @@ -663,10 +664,9 @@ async def try_connect_all(
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,5 @@
"device_family": "SMART.IPCAMERA",
"encryption_type": "AES",
"https": true
},
"uses_http": false
}
}
3 changes: 1 addition & 2 deletions tests/fixtures/serialization/deviceconfig_plug-klap.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,5 @@
"encryption_type": "KLAP",
"https": false,
"login_version": 2
},
"uses_http": false
}
}
3 changes: 1 addition & 2 deletions tests/fixtures/serialization/deviceconfig_plug-xor.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,5 @@
"device_family": "IOT.SMARTPLUGSWITCH",
"encryption_type": "XOR",
"https": false
},
"uses_http": false
}
}
2 changes: 0 additions & 2 deletions tests/test_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading