mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-05-01 20:21:23 +00:00
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:
parent
e37244de0f
commit
012436c494
@ -8,6 +8,7 @@ from typing import Awaitable, Callable, Dict, Mapping, Type, Union, cast
|
|||||||
from kasa.protocol import TPLinkSmartHomeProtocol
|
from kasa.protocol import TPLinkSmartHomeProtocol
|
||||||
from kasa.smartbulb import SmartBulb
|
from kasa.smartbulb import SmartBulb
|
||||||
from kasa.smartdevice import SmartDevice, SmartDeviceException
|
from kasa.smartdevice import SmartDevice, SmartDeviceException
|
||||||
|
from kasa.smartdimmer import SmartDimmer
|
||||||
from kasa.smartplug import SmartPlug
|
from kasa.smartplug import SmartPlug
|
||||||
from kasa.smartstrip import SmartStrip
|
from kasa.smartstrip import SmartStrip
|
||||||
|
|
||||||
@ -195,7 +196,12 @@ class Discover:
|
|||||||
else:
|
else:
|
||||||
raise SmartDeviceException("No 'system' nor 'get_sysinfo' in response")
|
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
|
return SmartStrip
|
||||||
elif "smartplug" in type_.lower():
|
elif "smartplug" in type_.lower():
|
||||||
return SmartPlug
|
return SmartPlug
|
||||||
|
@ -667,6 +667,11 @@ class SmartDevice:
|
|||||||
"""Return True if the device supports color temperature."""
|
"""Return True if the device supports color temperature."""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_color(self) -> bool:
|
||||||
|
"""Return True if the device supports color changes."""
|
||||||
|
return False
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<{} model {} at {} ({}), is_on: {} - dev specific: {}>".format(
|
return "<{} model {} at {} ({}), is_on: {} - dev specific: {}>".format(
|
||||||
self.__class__.__name__,
|
self.__class__.__name__,
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
"""Module for dimmers (currently only HS220)."""
|
"""Module for dimmers (currently only HS220)."""
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
from kasa import DeviceType, SmartDeviceException
|
from kasa.smartdevice import DeviceType, SmartDeviceException, requires_update
|
||||||
from kasa.smartdevice import requires_update
|
|
||||||
from kasa.smartplug import SmartPlug
|
from kasa.smartplug import SmartPlug
|
||||||
|
|
||||||
|
|
||||||
@ -61,7 +60,6 @@ class SmartDimmer(SmartPlug):
|
|||||||
if not isinstance(value, int):
|
if not isinstance(value, int):
|
||||||
raise ValueError("Brightness must be integer, " "not of %s.", type(value))
|
raise ValueError("Brightness must be integer, " "not of %s.", type(value))
|
||||||
elif 0 <= value <= 100:
|
elif 0 <= value <= 100:
|
||||||
await self.turn_on()
|
|
||||||
await self._query_helper(
|
await self._query_helper(
|
||||||
"smartlife.iot.dimmer", "set_brightness", {"brightness": value}
|
"smartlife.iot.dimmer", "set_brightness", {"brightness": value}
|
||||||
)
|
)
|
||||||
|
@ -17,7 +17,7 @@ SUPPORTED_DEVICES = glob.glob(
|
|||||||
|
|
||||||
BULBS = {"KL60", "LB100", "LB120", "LB130", "KL120", "KL130"}
|
BULBS = {"KL60", "LB100", "LB120", "LB130", "KL120", "KL130"}
|
||||||
VARIABLE_TEMP = {"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"}
|
STRIPS = {"HS107", "HS300", "KP303", "KP400"}
|
||||||
DIMMERS = {"HS220"}
|
DIMMERS = {"HS220"}
|
||||||
COLOR_BULBS = {"LB130", "KL130"}
|
COLOR_BULBS = {"LB130", "KL130"}
|
||||||
@ -49,6 +49,18 @@ no_emeter = pytest.mark.parametrize(
|
|||||||
bulb = pytest.mark.parametrize("dev", filter_model("bulbs", BULBS), indirect=True)
|
bulb = pytest.mark.parametrize("dev", filter_model("bulbs", BULBS), indirect=True)
|
||||||
plug = pytest.mark.parametrize("dev", filter_model("plugs", PLUGS), indirect=True)
|
plug = pytest.mark.parametrize("dev", filter_model("plugs", PLUGS), indirect=True)
|
||||||
strip = pytest.mark.parametrize("dev", filter_model("strips", STRIPS), 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(
|
dimmable = pytest.mark.parametrize(
|
||||||
"dev", filter_model("dimmable", DIMMABLE), indirect=True
|
"dev", filter_model("dimmable", DIMMABLE), indirect=True
|
||||||
|
@ -249,8 +249,8 @@ def success(target, cmd, res):
|
|||||||
|
|
||||||
|
|
||||||
class FakeTransportProtocol(TPLinkSmartHomeProtocol):
|
class FakeTransportProtocol(TPLinkSmartHomeProtocol):
|
||||||
def __init__(self, info, invalid=False):
|
def __init__(self, info):
|
||||||
# TODO remove invalid when removing the old tests.
|
self.discovery_data = info
|
||||||
proto = FakeTransportProtocol.baseproto
|
proto = FakeTransportProtocol.baseproto
|
||||||
for target in info:
|
for target in info:
|
||||||
# print("target %s" % target)
|
# print("target %s" % target)
|
||||||
|
43
kasa/tests/test_discovery.py
Normal file
43
kasa/tests/test_discovery.py
Normal 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)
|
@ -1,4 +1,3 @@
|
|||||||
import asyncio
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user