Improve dimmer support (#62)

* discover dimmers properly

* fix circular import, hopefully

* add is_color to SmartDevice API

* allow changing the dimming without implicitly turning the device on

* Add tests for device type handling for discovery data, make sure new fixtures are added to categories inside conftest
This commit is contained in:
Teemu R 2020-05-20 21:17:33 +02:00 committed by GitHub
parent e37244de0f
commit 012436c494
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 71 additions and 8 deletions

View File

@ -8,6 +8,7 @@ from typing import Awaitable, Callable, Dict, Mapping, Type, Union, cast
from kasa.protocol import TPLinkSmartHomeProtocol
from kasa.smartbulb import SmartBulb
from kasa.smartdevice import SmartDevice, SmartDeviceException
from kasa.smartdimmer import SmartDimmer
from kasa.smartplug import SmartPlug
from kasa.smartstrip import SmartStrip
@ -195,7 +196,12 @@ class Discover:
else:
raise SmartDeviceException("No 'system' nor 'get_sysinfo' in response")
if "smartplug" in type_.lower() and "children" in sysinfo:
if (
"smartlife.iot.dimmer" in info
and "get_dimmer_parameters" in info["smartlife.iot.dimmer"]
):
return SmartDimmer
elif "smartplug" in type_.lower() and "children" in sysinfo:
return SmartStrip
elif "smartplug" in type_.lower():
return SmartPlug

View File

@ -667,6 +667,11 @@ class SmartDevice:
"""Return True if the device supports color temperature."""
return False
@property
def is_color(self) -> bool:
"""Return True if the device supports color changes."""
return False
def __repr__(self):
return "<{} model {} at {} ({}), is_on: {} - dev specific: {}>".format(
self.__class__.__name__,

View File

@ -1,8 +1,7 @@
"""Module for dimmers (currently only HS220)."""
from typing import Any, Dict
from kasa import DeviceType, SmartDeviceException
from kasa.smartdevice import requires_update
from kasa.smartdevice import DeviceType, SmartDeviceException, requires_update
from kasa.smartplug import SmartPlug
@ -61,7 +60,6 @@ class SmartDimmer(SmartPlug):
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}
)

View File

@ -17,7 +17,7 @@ SUPPORTED_DEVICES = glob.glob(
BULBS = {"KL60", "LB100", "LB120", "LB130", "KL120", "KL130"}
VARIABLE_TEMP = {"LB120", "LB130", "KL120", "KL130"}
PLUGS = {"HS100", "HS103", "HS105", "HS110", "HS200", "HS210", "HS300"}
PLUGS = {"HS100", "HS103", "HS105", "HS110", "HS200", "HS210"}
STRIPS = {"HS107", "HS300", "KP303", "KP400"}
DIMMERS = {"HS220"}
COLOR_BULBS = {"LB130", "KL130"}
@ -49,6 +49,18 @@ no_emeter = pytest.mark.parametrize(
bulb = pytest.mark.parametrize("dev", filter_model("bulbs", BULBS), indirect=True)
plug = pytest.mark.parametrize("dev", filter_model("plugs", PLUGS), indirect=True)
strip = pytest.mark.parametrize("dev", filter_model("strips", STRIPS), indirect=True)
dimmer = pytest.mark.parametrize("dev", filter_model("dimmers", DIMMERS), indirect=True)
# This ensures that every single file inside fixtures/ is being placed in some category
categorized_fixtures = set(dimmer.args[1] + strip.args[1] + plug.args[1] + bulb.args[1])
diff = set(SUPPORTED_DEVICES) - set(categorized_fixtures)
if diff:
for file in diff:
print(
"No category for file %s, add to the corresponding set (BULBS, PLUGS, ..)"
% file
)
raise Exception("Missing category for %s" % diff)
dimmable = pytest.mark.parametrize(
"dev", filter_model("dimmable", DIMMABLE), indirect=True

View File

@ -249,8 +249,8 @@ def success(target, cmd, res):
class FakeTransportProtocol(TPLinkSmartHomeProtocol):
def __init__(self, info, invalid=False):
# TODO remove invalid when removing the old tests.
def __init__(self, info):
self.discovery_data = info
proto = FakeTransportProtocol.baseproto
for target in info:
# print("target %s" % target)

View File

@ -0,0 +1,43 @@
# type: ignore
import pytest # type: ignore # https://github.com/pytest-dev/pytest/issues/3342
from kasa import DeviceType, Discover, SmartDevice, SmartDeviceException
from .conftest import bulb, dimmer, plug, strip
# to avoid adding this for each async function separately
pytestmark = pytest.mark.asyncio
@plug
async def test_type_detection_plug(dev: SmartDevice):
d = Discover._get_device_class(dev.protocol.discovery_data)("localhost")
assert d.is_plug
assert d.device_type == DeviceType.Plug
@bulb
async def test_type_detection_bulb(dev: SmartDevice):
d = Discover._get_device_class(dev.protocol.discovery_data)("localhost")
assert d.is_bulb
assert d.device_type == DeviceType.Bulb
@strip
async def test_type_detection_strip(dev: SmartDevice):
d = Discover._get_device_class(dev.protocol.discovery_data)("localhost")
assert d.is_strip
assert d.device_type == DeviceType.Strip
@dimmer
async def test_type_detection_dimmer(dev: SmartDevice):
d = Discover._get_device_class(dev.protocol.discovery_data)("localhost")
assert d.is_dimmer
assert d.device_type == DeviceType.Dimmer
async def test_type_unknown():
invalid_info = {"system": {"get_sysinfo": {"type": "nosuchtype"}}}
with pytest.raises(SmartDeviceException):
Discover._get_device_class(invalid_info)

View File

@ -1,4 +1,3 @@
import asyncio
from datetime import datetime
from unittest.mock import patch