mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-05-02 20:51:10 +00:00
bulb: allow set_hsv without v, add fallback ct range (#200)
* bulb: allow set_hsv without v, add fallback ct range * add ColorTempRange and HSV named tuples * add a fallback color temp range if unknown, log a warning * set_hsv: the value is now optional * Fix tests, change fallback range to 2700-5000
This commit is contained in:
parent
7565d03c8e
commit
2c83d8ee6d
@ -1,6 +1,7 @@
|
|||||||
"""Module for bulbs (LB*, KL*, KB*)."""
|
"""Module for bulbs (LB*, KL*, KB*)."""
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
from typing import Any, Dict, Tuple, cast
|
from typing import Any, Dict, NamedTuple, cast
|
||||||
|
|
||||||
from kasa.smartdevice import (
|
from kasa.smartdevice import (
|
||||||
DeviceType,
|
DeviceType,
|
||||||
@ -9,18 +10,36 @@ from kasa.smartdevice import (
|
|||||||
requires_update,
|
requires_update,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ColorTempRange(NamedTuple):
|
||||||
|
"""Color temperature range."""
|
||||||
|
|
||||||
|
min: int
|
||||||
|
max: int
|
||||||
|
|
||||||
|
|
||||||
|
class HSV(NamedTuple):
|
||||||
|
"""Hue-saturation-value."""
|
||||||
|
|
||||||
|
hue: int
|
||||||
|
saturation: int
|
||||||
|
value: int
|
||||||
|
|
||||||
|
|
||||||
TPLINK_KELVIN = {
|
TPLINK_KELVIN = {
|
||||||
"LB130": (2500, 9000),
|
"LB130": ColorTempRange(2500, 9000),
|
||||||
"LB120": (2700, 6500),
|
"LB120": ColorTempRange(2700, 6500),
|
||||||
"LB230": (2500, 9000),
|
"LB230": ColorTempRange(2500, 9000),
|
||||||
"KB130": (2500, 9000),
|
"KB130": ColorTempRange(2500, 9000),
|
||||||
"KL130": (2500, 9000),
|
"KL130": ColorTempRange(2500, 9000),
|
||||||
"KL125": (2500, 6500),
|
"KL125": ColorTempRange(2500, 6500),
|
||||||
r"KL120\(EU\)": (2700, 6500),
|
r"KL120\(EU\)": ColorTempRange(2700, 6500),
|
||||||
r"KL120\(US\)": (2700, 5000),
|
r"KL120\(US\)": ColorTempRange(2700, 5000),
|
||||||
r"KL430": (2500, 9000),
|
r"KL430": ColorTempRange(2500, 9000),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class SmartBulb(SmartDevice):
|
class SmartBulb(SmartDevice):
|
||||||
"""Representation of a TP-Link Smart Bulb.
|
"""Representation of a TP-Link Smart Bulb.
|
||||||
@ -69,7 +88,7 @@ class SmartBulb(SmartDevice):
|
|||||||
Bulbs supporting color temperature can be queried to know which range is accepted:
|
Bulbs supporting color temperature can be queried to know which range is accepted:
|
||||||
|
|
||||||
>>> bulb.valid_temperature_range
|
>>> bulb.valid_temperature_range
|
||||||
(2500, 9000)
|
ColorTempRange(min=2500, max=9000)
|
||||||
>>> asyncio.run(bulb.set_color_temp(3000))
|
>>> asyncio.run(bulb.set_color_temp(3000))
|
||||||
>>> asyncio.run(bulb.update())
|
>>> asyncio.run(bulb.update())
|
||||||
>>> bulb.color_temp
|
>>> bulb.color_temp
|
||||||
@ -80,7 +99,7 @@ class SmartBulb(SmartDevice):
|
|||||||
>>> asyncio.run(bulb.set_hsv(180, 100, 80))
|
>>> asyncio.run(bulb.set_hsv(180, 100, 80))
|
||||||
>>> asyncio.run(bulb.update())
|
>>> asyncio.run(bulb.update())
|
||||||
>>> bulb.hsv
|
>>> bulb.hsv
|
||||||
(180, 100, 80)
|
HSV(hue=180, saturation=100, value=80)
|
||||||
|
|
||||||
If you don't want to use the default transitions, you can pass `transition` in milliseconds.
|
If you don't want to use the default transitions, you can pass `transition` in milliseconds.
|
||||||
This applies to all transitions (turn_on, turn_off, set_hsv, set_color_temp, set_brightness).
|
This applies to all transitions (turn_on, turn_off, set_hsv, set_color_temp, set_brightness).
|
||||||
@ -122,21 +141,21 @@ class SmartBulb(SmartDevice):
|
|||||||
|
|
||||||
@property # type: ignore
|
@property # type: ignore
|
||||||
@requires_update
|
@requires_update
|
||||||
def valid_temperature_range(self) -> Tuple[int, int]:
|
def valid_temperature_range(self) -> ColorTempRange:
|
||||||
"""Return the device-specific white temperature range (in Kelvin).
|
"""Return the device-specific white temperature range (in Kelvin).
|
||||||
|
|
||||||
:return: White temperature range in Kelvin (minimum, maximum)
|
:return: White temperature range in Kelvin (minimum, maximum)
|
||||||
"""
|
"""
|
||||||
if not self.is_variable_color_temp:
|
if not self.is_variable_color_temp:
|
||||||
raise SmartDeviceException("Color temperature not supported")
|
raise SmartDeviceException("Color temperature not supported")
|
||||||
|
|
||||||
for model, temp_range in TPLINK_KELVIN.items():
|
for model, temp_range in TPLINK_KELVIN.items():
|
||||||
sys_info = self.sys_info
|
sys_info = self.sys_info
|
||||||
if re.match(model, sys_info["model"]):
|
if re.match(model, sys_info["model"]):
|
||||||
return temp_range
|
return temp_range
|
||||||
|
|
||||||
raise SmartDeviceException(
|
_LOGGER.warning("Unknown color temperature range, fallback to 2700-5000")
|
||||||
"Unknown color temperature range, please open an issue on github"
|
return ColorTempRange(2700, 5000)
|
||||||
)
|
|
||||||
|
|
||||||
@property # type: ignore
|
@property # type: ignore
|
||||||
@requires_update
|
@requires_update
|
||||||
@ -200,7 +219,7 @@ class SmartBulb(SmartDevice):
|
|||||||
|
|
||||||
@property # type: ignore
|
@property # type: ignore
|
||||||
@requires_update
|
@requires_update
|
||||||
def hsv(self) -> Tuple[int, int, int]:
|
def hsv(self) -> HSV:
|
||||||
"""Return the current HSV state of the bulb.
|
"""Return the current HSV state of the bulb.
|
||||||
|
|
||||||
:return: hue, saturation and value (degrees, %, %)
|
:return: hue, saturation and value (degrees, %, %)
|
||||||
@ -214,7 +233,7 @@ class SmartBulb(SmartDevice):
|
|||||||
saturation = light_state["saturation"]
|
saturation = light_state["saturation"]
|
||||||
value = light_state["brightness"]
|
value = light_state["brightness"]
|
||||||
|
|
||||||
return hue, saturation, value
|
return HSV(hue, saturation, value)
|
||||||
|
|
||||||
def _raise_for_invalid_brightness(self, value):
|
def _raise_for_invalid_brightness(self, value):
|
||||||
if not isinstance(value, int) or not (0 <= value <= 100):
|
if not isinstance(value, int) or not (0 <= value <= 100):
|
||||||
@ -224,7 +243,7 @@ class SmartBulb(SmartDevice):
|
|||||||
|
|
||||||
@requires_update
|
@requires_update
|
||||||
async def set_hsv(
|
async def set_hsv(
|
||||||
self, hue: int, saturation: int, value: int, *, transition: int = None
|
self, hue: int, saturation: int, value: int = None, *, transition: int = None
|
||||||
) -> Dict:
|
) -> Dict:
|
||||||
"""Set new HSV.
|
"""Set new HSV.
|
||||||
|
|
||||||
@ -247,15 +266,16 @@ class SmartBulb(SmartDevice):
|
|||||||
"(valid range: 0-100%)".format(saturation)
|
"(valid range: 0-100%)".format(saturation)
|
||||||
)
|
)
|
||||||
|
|
||||||
self._raise_for_invalid_brightness(value)
|
|
||||||
|
|
||||||
light_state = {
|
light_state = {
|
||||||
"hue": hue,
|
"hue": hue,
|
||||||
"saturation": saturation,
|
"saturation": saturation,
|
||||||
"brightness": value,
|
|
||||||
"color_temp": 0,
|
"color_temp": 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if value is not None:
|
||||||
|
self._raise_for_invalid_brightness(value)
|
||||||
|
light_state["brightness"] = value
|
||||||
|
|
||||||
return await self.set_light_state(light_state, transition=transition)
|
return await self.set_light_state(light_state, transition=transition)
|
||||||
|
|
||||||
@property # type: ignore
|
@property # type: ignore
|
||||||
@ -284,7 +304,7 @@ class SmartBulb(SmartDevice):
|
|||||||
if temp < valid_temperature_range[0] or temp > valid_temperature_range[1]:
|
if temp < valid_temperature_range[0] or temp > valid_temperature_range[1]:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Temperature should be between {} "
|
"Temperature should be between {} "
|
||||||
"and {}".format(*valid_temperature_range)
|
"and {}, was {}".format(*valid_temperature_range, temp)
|
||||||
)
|
)
|
||||||
|
|
||||||
light_state = {"color_temp": temp}
|
light_state = {"color_temp": temp}
|
||||||
|
@ -148,10 +148,11 @@ async def test_set_color_temp_transition(dev, mocker):
|
|||||||
|
|
||||||
|
|
||||||
@variable_temp
|
@variable_temp
|
||||||
async def test_unknown_temp_range(dev, monkeypatch):
|
async def test_unknown_temp_range(dev, monkeypatch, caplog):
|
||||||
with pytest.raises(SmartDeviceException):
|
monkeypatch.setitem(dev._sys_info, "model", "unknown bulb")
|
||||||
monkeypatch.setitem(dev._sys_info, "model", "unknown bulb")
|
|
||||||
dev.valid_temperature_range()
|
assert dev.valid_temperature_range == (2700, 5000)
|
||||||
|
assert "Unknown color temperature range, fallback to 2700-5000" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
@variable_temp
|
@variable_temp
|
||||||
|
Loading…
x
Reference in New Issue
Block a user