Move dimmer support to its own class (#34)

* Move dimmer support to its own class

SmartDimmer extends SmartPlug with brightness settings.
This will make the API of SmartPlug less confusing and will
make it simpler to downstream users to act with dimmers.

Fixes #33

* Lint & make tests pass

* Fix rebase after cache and emeter cleanups, hopefully everything went smoothly..

* oopsie, has_emeter was mistakenly included in smartplug
This commit is contained in:
Teemu R 2020-04-18 23:35:39 +02:00 committed by GitHub
parent b6c3a7c54b
commit b73c0d222e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 116 additions and 66 deletions

View File

@ -15,6 +15,7 @@ from kasa.discover import Discover
from kasa.protocol import TPLinkSmartHomeProtocol
from kasa.smartbulb import SmartBulb
from kasa.smartdevice import DeviceType, EmeterStatus, SmartDevice, SmartDeviceException
from kasa.smartdimmer import SmartDimmer
from kasa.smartplug import SmartPlug
from kasa.smartstrip import SmartStrip
@ -28,4 +29,5 @@ __all__ = [
"SmartDeviceException",
"SmartPlug",
"SmartStrip",
"SmartDimmer",
]

View File

@ -29,6 +29,7 @@ class DeviceType(Enum):
Plug = 1
Bulb = 2
Strip = 3
Dimmer = 4
Unknown = -1
@ -583,6 +584,11 @@ class SmartDevice:
"""Return True if the device is a strip."""
return self._device_type == DeviceType.Strip
@property
def is_dimmer(self) -> bool:
"""Return True if the device is a dimmer."""
return self._device_type == DeviceType.Dimmer
@property
def is_dimmable(self) -> bool:
"""Return True if the device is dimmable."""

94
kasa/smartdimmer.py Normal file
View File

@ -0,0 +1,94 @@
"""Module for dimmers (currently only HS220)."""
from typing import Any, Dict
from kasa import DeviceType, SmartDeviceException
from kasa.smartdevice import requires_update
from kasa.smartplug import SmartPlug
class SmartDimmer(SmartPlug):
"""Representation of a TP-Link Smart Dimmer.
Dimmers work similarly to plugs, but provide also support for
adjusting the brightness. This class extends SmartPlug interface.
Example:
```
dimmer = SmartDimmer("192.168.1.105")
await dimmer.turn_on()
print("Current brightness: %s" % dimmer.brightness)
await dimmer.set_brightness(100)
```
Refer to SmartPlug for the full API.
"""
def __init__(self, host: str) -> None:
super().__init__(host)
self._device_type = DeviceType.Dimmer
@property # type: ignore
@requires_update
def brightness(self) -> int:
"""Return current brightness on dimmers.
Will return a range between 0 - 100.
:returns: integer
:rtype: int
"""
if not self.is_dimmable:
raise SmartDeviceException("Device is not dimmable.")
sys_info = self.sys_info
return int(sys_info["brightness"])
@requires_update
async def set_brightness(self, value: int):
"""Set the new dimmer brightness level.
Note:
When setting brightness, if the light is not
already on, it will be turned on automatically.
:param value: integer between 1 and 100
"""
if not self.is_dimmable:
raise SmartDeviceException("Device is not dimmable.")
if not isinstance(value, int):
raise ValueError("Brightness must be integer, " "not of %s.", type(value))
elif 0 < value <= 100:
await self.turn_on()
await self._query_helper(
"smartlife.iot.dimmer", "set_brightness", {"brightness": value}
)
await self.update()
else:
raise ValueError("Brightness value %s is not valid." % value)
@property # type: ignore
@requires_update
def is_dimmable(self):
"""Whether the switch supports brightness changes.
:return: True if switch supports brightness changes, False otherwise
:rtype: bool
"""
sys_info = self.sys_info
return "brightness" in sys_info
@property # type: ignore
@requires_update
def state_information(self) -> Dict[str, Any]:
"""Return switch-specific state information.
:return: Switch information dict, keys in user-presentable form.
:rtype: dict
"""
info = super().state_information
info["Brightness"] = self.brightness
return info

View File

@ -3,12 +3,7 @@ import datetime
import logging
from typing import Any, Dict
from kasa.smartdevice import (
DeviceType,
SmartDevice,
SmartDeviceException,
requires_update,
)
from kasa.smartdevice import DeviceType, SmartDevice, requires_update
_LOGGER = logging.getLogger(__name__)
@ -40,58 +35,6 @@ class SmartPlug(SmartDevice):
self.emeter_type = "emeter"
self._device_type = DeviceType.Plug
@property # type: ignore
@requires_update
def brightness(self) -> int:
"""Return current brightness on dimmers.
Will return a range between 0 - 100.
:returns: integer
:rtype: int
"""
if not self.is_dimmable:
raise SmartDeviceException("Device is not dimmable.")
sys_info = self.sys_info
return int(sys_info["brightness"])
@requires_update
async def set_brightness(self, value: int):
"""Set the new dimmer brightness level.
Note:
When setting brightness, if the light is not
already on, it will be turned on automatically.
:param value: integer between 1 and 100
"""
if not self.is_dimmable:
raise SmartDeviceException("Device is not dimmable.")
if not isinstance(value, int):
raise ValueError("Brightness must be integer, " "not of %s.", type(value))
elif 0 < value <= 100:
await self.turn_on()
await self._query_helper(
"smartlife.iot.dimmer", "set_brightness", {"brightness": value}
)
await self.update()
else:
raise ValueError("Brightness value %s is not valid." % value)
@property # type: ignore
@requires_update
def is_dimmable(self):
"""Whether the switch supports brightness changes.
:return: True if switch supports brightness changes, False otherwise
:rtype: bool
"""
sys_info = self.sys_info
return "brightness" in sys_info
@property # type: ignore
@requires_update
def is_on(self) -> bool:
@ -159,6 +102,4 @@ class SmartPlug(SmartDevice):
:rtype: dict
"""
info = {"LED state": self.led, "On since": self.on_since}
if self.is_dimmable:
info["Brightness"] = self.brightness
return info

View File

@ -45,7 +45,7 @@ class SmartStrip(SmartDevice):
and should be handled by the user of the library.
"""
def __init__(self, host: str, *, cache_ttl: int = 3) -> None:
def __init__(self, host: str) -> None:
super().__init__(host=host)
self.emeter_type = "emeter"
self._device_type = DeviceType.Strip

View File

@ -6,7 +6,7 @@ from os.path import basename
import pytest
from kasa import Discover, SmartBulb, SmartPlug, SmartStrip
from kasa import Discover, SmartBulb, SmartDimmer, SmartPlug, SmartStrip
from .newfakes import FakeTransportProtocol
@ -14,15 +14,17 @@ SUPPORTED_DEVICES = glob.glob(
os.path.dirname(os.path.abspath(__file__)) + "/fixtures/*.json"
)
BULBS = {"LB100", "LB120", "LB130", "KL120", "KL130"}
VARIABLE_TEMP = {"LB120", "LB130", "KL120", "KL130"}
PLUGS = {"HS100", "HS103", "HS105", "HS110", "HS200", "HS210", "HS220", "HS300"}
PLUGS = {"HS100", "HS103", "HS105", "HS110", "HS200", "HS210", "HS300"}
STRIPS = {"HS107", "HS300", "KP303", "KP400"}
DIMMERS = {"HS220"}
COLOR_BULBS = {"LB130", "KL130"}
DIMMABLE = {*BULBS, "HS220"}
EMETER = {"HS110", "HS300", *BULBS}
EMETER = {"HS110", "HS300", *BULBS, *STRIPS}
ALL_DEVICES = BULBS.union(PLUGS)
ALL_DEVICES = BULBS.union(PLUGS).union(STRIPS).union(DIMMERS)
def filter_model(desc, filter):
@ -52,7 +54,9 @@ dimmable = pytest.mark.parametrize(
"dev", filter_model("dimmable", DIMMABLE), indirect=True
)
non_dimmable = pytest.mark.parametrize(
"dev", filter_model("non-dimmable", ALL_DEVICES - DIMMABLE - STRIPS), indirect=True
"dev",
filter_model("non-dimmable", ALL_DEVICES - DIMMABLE - STRIPS - PLUGS),
indirect=True,
)
variable_temp = pytest.mark.parametrize(
@ -110,6 +114,9 @@ def dev(request):
for d in BULBS:
if d in model:
return SmartBulb
for d in DIMMERS:
if d in model:
return SmartDimmer
raise Exception("Unable to find type for %s", model)