8000 Update post first review · python-kasa/python-kasa@a21985e · GitHub
[go: up one dir, main page]

Skip to content

Commit a21985e

Browse files
committed
Update post first review
1 parent 2b04c39 commit a21985e

11 files changed

+85
-55
lines changed

kasa/connectionparams.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
"""Module for holding connection parameters."""
2+
import logging
23
from dataclasses import dataclass
34
from enum import Enum
45
from typing import Dict, Optional
56

7+
_LOGGER = logging.getLogger(__name__)
8+
69

710
class EncryptType(Enum):
811
"""Encrypt type enum."""
@@ -49,14 +52,19 @@ class ConnectionParameters:
4952

5053
@staticmethod
5154
def from_values(
52-
tplink_device_type: str = "Unknown",
53-
encryption_type: str = "XOR",
54-
) -> "ConnectionParameters":
55+
tplink_device_type: str,
56+
encryption_type: str,
57+
) -> Optional["ConnectionParameters"]:
5558
"""Return connection parameters from string values."""
56-
return ConnectionParameters(
57-
TPLinkDeviceType.from_value(tplink_device_type),
58-
EncryptType.from_value(encryption_type),
59-
)
59+
tdt = TPLinkDeviceType.from_value(tplink_device_type)
60+
et = EncryptType.from_value(encryption_type)
61+
if tdt != TPLinkDeviceType.Unknown and et != EncryptType.Unknown:
62+
return ConnectionParameters(
63+
TPLinkDeviceType.from_value(tplink_device_type),
64+
EncryptType.from_value(encryption_type),
65+
)
66+
_LOGGER.debug("Invalid values for ConnectionParameter %s %s", tdt, et)
67+
return None
6068

6169
@staticmethod
6270
def from_dict(
@@ -70,6 +78,9 @@ def from_dict(
7078
and (encryption_type := parameters.get("encryption_type"))
7179
):
7280
return ConnectionParameters.from_values(tplink_device_type, encryption_type)
81+
_LOGGER.debug(
82+
"Invalid dict for ConnectionParameter %s", connection_parameters_dict
83+
)
7384
return None
7485

7586
def to_dict(self) -> Dict[str, Dict[str, str]]:

kasa/device_factory.py

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,25 @@
22

33
import logging
44
import time
5-
from typing import Optional
5+
from typing import Optional, Type
66

77
from .connectionparams import ConnectionParameters
88
from .credentials import Credentials
99
from .device_helpers import (
1010
get_device_class_from_sys_info,
11+
get_device_class_from_type_name,
1112
get_protocol_from_connection_parameters,
1213
)
1314
from .device_type import DeviceType
1415
from .discover import Discover
16+
from .protocol import TPLinkSmartHomeProtocol
1517
from .smartbulb import SmartBulb
1618
from .smartdevice import SmartDevice, SmartDeviceException
1719
from .smartdimmer import SmartDimmer
1820
from .smartlightstrip import SmartLightStrip
1921
from .smartplug import SmartPlug
2022
from .smartstrip import SmartStrip
21-
from .tapo import TapoBulb, TapoPlug
23+
from .tapo import TapoBulb, TapoDevice, TapoPlug
2224

2325
DEVICE_TYPE_TO_CLASS = {
2426
DeviceType.Plug: SmartPlug,
@@ -71,23 +73,42 @@ async def connect(
7173
start_time = time.perf_counter()
7274

7375
protocol = None
76+
device_class: Optional[Type[SmartDevice]] = None
77+
device_class_iot: Optional[Type[SmartDevice]] = None
78+
79+
if device_type and device_type != DeviceType.Unknown:
80+
device_class_iot = DEVICE_TYPE_TO_CLASS.get(device_type)
81+
7482
if connection_params:
7583
protocol = get_protocol_from_connection_parameters(
7684
connection_params, host, credentials=credentials
7785
)
86+
if klass := get_device_class_from_type_name(
87+
connection_params.tplink_device_type.value
88+
):
89+
# Do not use device_type parameter for TAPO/SMART devices
90+
if issubclass(klass, TapoDevice):
91+
device_class = klass
92+
elif device_class_iot:
93+
device_class = device_class_iot
94+
elif device_class_iot:
95+
device_class = device_class_iot
96+
protocol = TPLinkSmartHomeProtocol(
97+
host, port=port, timeout=timeout, credentials=credentials
98+
)
7899

79-
if device_type and (klass := DEVICE_TYPE_TO_CLASS.get(device_type)):
80-
dev: SmartDevice = klass(
100+
if device_class and protocol:
101+
dev: SmartDevice = device_class(
81102
host=host, port=port, credentials=credentials, timeout=timeout
82103
)
83-
if protocol:
84-
dev.protocol = protocol
104+
dev.protocol = protocol
105+
dev.connection_parameters = connection_params
85106
try:
86107
await dev.update()
87108
except SmartDeviceException as ex:
88109
if not try_discovery_on_error:
89110
raise ex
90-
_LOGGER.warning(
111+
_LOGGER.debug(
91112
"Error connecting to device %s, trying discovery: %s", host, ex
92113
)
93114
dev = await Discover.discover_single(
@@ -105,19 +126,21 @@ async def connect(
105126
)
106127
return dev
107128

108-
# The following code will only work with the legacy IOT protocol
129+
# The following code error for devices not using the legacy IOT protocol
130+
# But will can revert to discovery on error
109131
unknown_dev = SmartDevice(
110132
host=host, port=port, credentials=credentials, timeout=timeout
111133
)
134+
# This could happen if ConnectionParameters is present for a legacy IOT
135+
# device but there is no device_type parameter supplied
112136
if protocol:
113137
unknown_dev.protocol = protocol
114-
115138
try:
116139
await unknown_dev.update()
117140
except SmartDeviceException as ex:
118141
if not try_discovery_on_error:
119142
raise ex
120-
_LOGGER.warning("Error connecting to device %s, trying discovery: %s", host, ex)
143+
_LOGGER.debug("Error connecting to device %s, trying discovery: %s", host, ex)
121144
dev = await Discover.discover_single(
122145
host, port=port, timeout=timeout, credentials=credentials
123146
)

kasa/device_helpers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,5 +84,6 @@ def get_device_class_from_type_name(device_type: str) -> Optional[Type[SmartDevi
8484
"SMART.TAPOBULB": TapoBulb,
8585
"SMART.KASAPLUG": TapoPlug,
8686
"IOT.SMARTPLUGSWITCH": SmartPlug,
87+
"IOT.SMARTBULB": SmartBulb,
8788
}
8889
return supported_device_types.get(device_type)

kasa/discover.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
except ImportError:
1616
from pydantic import BaseModel, ValidationError
1717

18-
from kasa.connectionparams import ConnectionParameters
18+
from kasa.connectionparams import ConnectionParameters, EncryptType
1919
from kasa.credentials import Credentials
2020
from kasa.exceptions import UnsupportedDeviceException
2121
from kasa.json import dumps as json_dumps
@@ -380,7 +380,7 @@ def _get_device_instance_legacy(data: bytes, ip: str, port: int) -> SmartDevice:
380380
device_type := sys_info.get("type")
381381
):
382382
device.connection_parameters = ConnectionParameters.from_values(
383-
tplink_device_type=device_type
383+
tplink_device_type=device_type, encryption_type=EncryptType.Xor.value
384384
)
385385
device.update_from_discover_info(info)
386386
return device
@@ -393,14 +393,14 @@ def _get_device_instance(
393393
try:
394394
info = json_loads(data[16:])
395395
except Exception as ex:
396-
_LOGGER.warning("Got invalid response from device %s: %s", ip, data)
396+
_LOGGER.debug("Got invalid response from device %s: %s", ip, data)
397397
raise SmartDeviceException(
398398
f"Unable to read response from device: {ip}: {ex}"
399399
) from ex
400400
try:
401401
discovery_result = DiscoveryResult(**info["result"])
402402
except ValidationError as ex:
403-
_LOGGER.warning("Unable to parse discovery from device %s: %s", ip, info)
403+
_LOGGER.debug("Unable to parse discovery from device %s: %s", ip, info)
404404
raise UnsupportedDeviceException(
405405
f"Unable to parse discovery from device: {ip}: {ex}"
406406
) from ex
@@ -410,8 +410,15 @@ def _get_device_instance(
410410
connection_params = ConnectionParameters.from_values(
411411
type_, discovery_result.mgt_encrypt_schm.encrypt_type
412412
)
413+
if connection_params is None:
414+
# Connection_params logs a message
415+
raise UnsupportedDeviceException(
416+
f"Unsupported device {ip} of type {type_} "
417+
+ f"with encrypt_type {discovery_result.mgt_encrypt_schm.encrypt_type}",
418+
discovery_result=discovery_result.get_dict(),
419+
)
413420
if (device_class := get_device_class_from_type_name(type_)) is None:
414-
_LOGGER.warning("Got unsupported device type: %s", type_)
421+
_LOGGER.debug("Got unsupported device type: %s", type_)
415422
raise UnsupportedDeviceException(
416423
f"Unsupported device {ip} of type {type_}: {info}",
417424
discovery_result=discovery_result.get_dict(),
@@ -421,7 +428,7 @@ def _get_device_instance(
421428
connection_params, ip, credentials=credentials
422429
)
423430
) is None:
424-
_LOGGER.warning(
431+
_LOGGER.debug(
425432
"Got unsupported device type: %s", connection_params.to_dict()
426433
)
427434
raise UnsupportedDeviceException(

kasa/smartdevice.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ def __init__(
233233
self.modules: Dict[str, Any] = {}
234234

235235
self.children: List["SmartDevice"] = []
236-
self.connection_parameters = ConnectionParameters.from_values()
236+
self.connection_parameters: Optional[ConnectionParameters] = None
237237

238238
def add_module(self, name: str, module: Module):
239239
"""Register a module."""
@@ -653,7 +653,7 @@ def state_information(self) -> Dict[str, Any]:
653653
raise NotImplementedError("Device subclass needs to implement this.")
654654

655655
@property # type: ignore
656-
@requires_discovery_or_update
656+
@requires_update
657657
def device_id(self) -> str:
658658
"""Return unique ID for the device.
659659
@@ -728,7 +728,7 @@ def device_type(self) -> DeviceType:
728728
@property
729729
def is_bulb(self) -> bool:
730730
"""Return True if the device is a bulb."""
731-
return self._device_type in [DeviceType.Bulb, DeviceType.TapoBulb]
731+
return self._device_type == DeviceType.Bulb
732732

733733
@property
734734
def is_light_strip(self) -> bool:
@@ -738,7 +738,7 @@ def is_light_strip(self) -> bool:
738738
@property
739739
def is_plug(self) -> bool:
740740
"""Return True if the device is a plug."""
741-
return self._device_type in [DeviceType.Plug, DeviceType.TapoPlug]
741+
return self._device_type == DeviceType.Plug
742742

743743
@property
744744
def is_strip(self) -> bool:
@@ -779,11 +779,6 @@ def internal_state(self) -> Any:
779779
"""
780780
return self._last_update or self._discovery_info
781781

782-
@property
783-
def has_led(self) -> bool:
784-
"""Return True if device has an led switch."""
785-
return False
786-
787782
def __repr__(self):
788783
if self._last_update is None:
789784
if self._discovery_info:

kasa/tapo/tapobulb.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
"""Module for tapo-branded smart bulbs (L5**)."""
22
from typing import Any, Dict, List, Optional
33

4-
from ..credentials import Credentials
5-
from ..device_type import DeviceType
64
from ..exceptions import SmartDeviceException
75
from ..smartbulb import HSV, ColorTempRange, SmartBulb, SmartBulbPreset
86
from .tapodevice import TapoDevice
@@ -19,16 +17,10 @@ class TapoBulb(TapoDevice, SmartBulb):
1917
Documentation TBD. See :class:`~kasa.smartbulb.SmartBulb` for now.
2018
"""
2119

22-
def __init__(
23-
self,
24-
host: str,
25-
*,
26-
port: Optional[int] = None,
27-
credentials: Optional[Credentials] = None,
28-
timeout: Optional[int] = None,
29-
) -> None:
30-
super().__init__(host, port=port, credentials=credentials, timeout=timeout)
31-
self._device_type = DeviceType.TapoBulb
20+
@property
21+
def is_bulb(self) -> bool:
22+
"""Return True if the device is a bulb."""
23+
return True
3224

3325
@property
3426
def has_emeter(self) -> bool:

kasa/tapo/tapodevice.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ def __init__(
2525
) -> None:
2626
super().__init__(host, port=port, credentials=credentials, timeout=timeout)
2727
self._state_information: Dict[str, Any] = {}
28-
2928
# TODO: typing Any is just as using Optional[Dict] would require separate
3029
# checks in accessors. the @updated_required decorator does not ensure
3130
# mypy that these are not accessed incorrectly.

kasa/tapo/tapoplug.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,22 @@ def __init__(
2424
timeout: Optional[int] = None,
2525
) -> None:
2626
super().__init__(host, port=port, credentials=credentials, timeout=timeout)
27-
self._device_type = DeviceType.TapoPlug
27+
self._device_type = DeviceType.Plug
2828
self.modules: Dict[str, Any] = {}
2929
self.emeter_type = "emeter"
3030
self.modules["emeter"] = Emeter(self, self.emeter_type)
3131

32+
@property
33+
def is_plug(self) -> bool:
34+
"""Return True if the device is a plug."""
35+
return True
36+
3237
@property # type: ignore
3338
@requires_update
3439
def has_emeter(self) -> bool:
3540
"""Return that the plug has an emeter."""
3641
return True
3742

38-
@property
39-
def has_led(self) -> bool:
40-
"""Return True if device has an led switch."""
41-
return False
42-
4343
async def update(self, update_children: bool = True):
4444
"""Call the device endpoint and update the device data."""
4545
await super().update(update_children)

kasa/tests/test_bulb.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ async def test_bulb_sysinfo(dev: SmartBulb):
2828

2929
# TODO: remove special handling for lightstrip
3030
if not dev.is_light_strip:
31-
assert dev.device_type in [DeviceType.Bulb, DeviceType.TapoBulb]
31+
assert dev.device_type == DeviceType.Bulb
3232
assert dev.is_bulb
3333

3434

kasa/tests/test_device_factory.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
SmartLightStrip,
1616
SmartPlug,
1717
)
18-
from kasa.connectionparams import ConnectionParameters
18+
from kasa.connectionparams import ConnectionParameters, EncryptType, TPLinkDeviceType
1919
from kasa.device_factory import (
2020
DEVICE_TYPE_TO_CLASS,
2121
connect,
@@ -127,7 +127,9 @@ async def test_connect_pass_connection_params(
127127
mocker.patch(
128128
"kasa.TPLinkSmartHomeProtocol.query", return_value=all_fixture_data
129129
)
130-
connection_params = ConnectionParameters.from_values("IOT.SMARTPLUGSWITCH")
130+
connection_params = ConnectionParameters.from_values(
131+
TPLinkDeviceType.IotSmartPlugSwitch.value, EncryptType.Xor.value
132+
)
131133
protocol_class = get_protocol_from_connection_parameters(
132134
connection_params, host
133135
).__class__

0 commit comments

Comments
 (0)
0