Add ColorModule for smart devices (#840)

Adds support L530 hw_version 1.0
This commit is contained in:
Steven B
2024-04-20 16:18:35 +01:00
committed by GitHub
parent 4573260ac8
commit aeb2c923c6
9 changed files with 160 additions and 57 deletions

View File

@@ -6,6 +6,7 @@ from .battery import BatterySensor
from .brightness import Brightness
from .childdevicemodule import ChildDeviceModule
from .cloudmodule import CloudModule
from .colormodule import ColorModule
from .colortemp import ColorTemperatureModule
from .devicemodule import DeviceModule
from .energymodule import EnergyModule
@@ -36,4 +37,5 @@ __all__ = [
"CloudModule",
"LightTransitionModule",
"ColorTemperatureModule",
"ColorModule",
]

View File

@@ -0,0 +1,94 @@
"""Implementation of color module."""
from __future__ import annotations
from typing import TYPE_CHECKING
from ...bulb import HSV
from ...feature import Feature
from ..smartmodule import SmartModule
if TYPE_CHECKING:
from ..smartdevice import SmartDevice
class ColorModule(SmartModule):
"""Implementation of color module."""
REQUIRED_COMPONENT = "color"
def __init__(self, device: SmartDevice, module: str):
super().__init__(device, module)
self._add_feature(
Feature(
device,
"HSV",
container=self,
attribute_getter="hsv",
# TODO proper type for setting hsv
attribute_setter="set_hsv",
)
)
def query(self) -> dict:
"""Query to execute during the update cycle."""
# HSV is contained in the main device info response.
return {}
@property
def hsv(self) -> HSV:
"""Return the current HSV state of the bulb.
:return: hue, saturation and value (degrees, %, 1-100)
"""
h, s, v = (
self.data.get("hue", 0),
self.data.get("saturation", 0),
self.data.get("brightness", 0),
)
return HSV(hue=h, saturation=s, value=v)
def _raise_for_invalid_brightness(self, value: int):
"""Raise error on invalid brightness value."""
if not isinstance(value, int) or not (1 <= value <= 100):
raise ValueError(f"Invalid brightness value: {value} (valid range: 1-100%)")
async def set_hsv(
self,
hue: int,
saturation: int,
value: int | None = None,
*,
transition: int | None = None,
) -> dict:
"""Set new HSV.
Note, transition is not supported and will be ignored.
:param int hue: hue in degrees
:param int saturation: saturation in percentage [0,100]
:param int value: value in percentage [0, 100]
:param int transition: transition in milliseconds.
"""
if not isinstance(hue, int) or not (0 <= hue <= 360):
raise ValueError(f"Invalid hue value: {hue} (valid range: 0-360)")
if not isinstance(saturation, int) or not (0 <= saturation <= 100):
raise ValueError(
f"Invalid saturation value: {saturation} (valid range: 0-100%)"
)
if value is not None:
self._raise_for_invalid_brightness(value)
request_payload = {
"color_temp": 0, # If set, color_temp takes precedence over hue&sat
"hue": hue,
"saturation": saturation,
}
# The device errors on invalid brightness values.
if value is not None:
request_payload["brightness"] = value
return await self.call("set_device_info", {**request_payload})

View File

@@ -2,6 +2,7 @@
from __future__ import annotations
import logging
from typing import TYPE_CHECKING
from ...bulb import ColorTempRange
@@ -12,6 +13,11 @@ if TYPE_CHECKING:
from ..smartdevice import SmartDevice
_LOGGER = logging.getLogger(__name__)
DEFAULT_TEMP_RANGE = [2500, 6500]
class ColorTemperatureModule(SmartModule):
"""Implementation of color temp module."""
@@ -38,7 +44,14 @@ class ColorTemperatureModule(SmartModule):
@property
def valid_temperature_range(self) -> ColorTempRange:
"""Return valid color-temp range."""
return ColorTempRange(*self.data.get("color_temp_range"))
if (ct_range := self.data.get("color_temp_range")) is None:
_LOGGER.debug(
"Device doesn't report color temperature range, "
"falling back to default %s",
DEFAULT_TEMP_RANGE,
)
ct_range = DEFAULT_TEMP_RANGE
return ColorTempRange(*ct_range)
@property
def color_temp(self):
@@ -56,3 +69,7 @@ class ColorTemperatureModule(SmartModule):
)
return await self.call("set_device_info", {"color_temp": temp})
async def _check_supported(self) -> bool:
"""Check the color_temp_range has more than one value."""
return self.valid_temperature_range.min != self.valid_temperature_range.max