Migrate Light module to feature based capability checks

This commit is contained in:
Steven B 2024-11-22 17:02:38 +00:00
parent c5830a4cdc
commit 8bfddbdd71
No known key found for this signature in database
GPG Key ID: 6D5B46B3679F2A43
8 changed files with 167 additions and 155 deletions

View File

@ -52,9 +52,9 @@ True
>>> await dev.update() >>> await dev.update()
>>> light.brightness >>> light.brightness
50 50
>>> light.is_color >>> light.has_feature(light.set_hsv)
True True
>>> if light.is_color: >>> if light.has_feature(light.set_hsv):
>>> print(light.hsv) >>> print(light.hsv)
HSV(hue=0, saturation=100, value=50) HSV(hue=0, saturation=100, value=50)

View File

@ -107,7 +107,7 @@ from __future__ import annotations
import logging import logging
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from collections.abc import Mapping, Sequence from collections.abc import Callable, Mapping, Sequence
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime, tzinfo from datetime import datetime, tzinfo
from typing import TYPE_CHECKING, Any, TypeAlias from typing import TYPE_CHECKING, Any, TypeAlias
@ -525,10 +525,47 @@ class Device(ABC):
return None return None
def _get_deprecated_callable_attribute(self, name: str) -> Any | None:
vals: dict[str, tuple[ModuleName, Callable[[Any], Any], str]] = {
"is_dimmable": (
Module.Light,
lambda c: c.has_feature("set_brightness"),
"light_module.has_feature('set_brightness')",
),
"is_color": (
Module.Light,
lambda c: c.has_feature("set_hsv"),
"light_module.has_feature('set_hsv')",
),
"is_variable_color_temp": (
Module.Light,
lambda c: c.has_feature("set_color_temp"),
"light_module.has_feature('set_color_temp')",
),
"valid_temperature_range": (
Module.Light,
lambda c: c._deprecated_valid_temperature_range(),
"minimum and maximum value of get_feature('set_color_temp')",
),
"has_effects": (
Module.Light,
lambda c: Module.LightEffect in c._device.modules,
"Module.LightEffect in c._device.modules",
),
}
if mod_call_msg := vals.get(name):
mod, call, msg = mod_call_msg
msg = f"{name} is deprecated, use: {msg} instead"
warn(msg, DeprecationWarning, stacklevel=2)
if (module := self.modules.get(mod)) is None:
raise AttributeError(f"Device has no attribute {name!r}")
return call(module)
return None
_deprecated_other_attributes = { _deprecated_other_attributes = {
# light attributes # light attributes
"is_color": (Module.Light, ["is_color"]), "is_color": (Module.Light, ["is_color"]),
"is_dimmable": (Module.Light, ["is_dimmable"]),
"is_variable_color_temp": (Module.Light, ["is_variable_color_temp"]), "is_variable_color_temp": (Module.Light, ["is_variable_color_temp"]),
"brightness": (Module.Light, ["brightness"]), "brightness": (Module.Light, ["brightness"]),
"set_brightness": (Module.Light, ["set_brightness"]), "set_brightness": (Module.Light, ["set_brightness"]),
@ -536,8 +573,6 @@ class Device(ABC):
"set_hsv": (Module.Light, ["set_hsv"]), "set_hsv": (Module.Light, ["set_hsv"]),
"color_temp": (Module.Light, ["color_temp"]), "color_temp": (Module.Light, ["color_temp"]),
"set_color_temp": (Module.Light, ["set_color_temp"]), "set_color_temp": (Module.Light, ["set_color_temp"]),
"valid_temperature_range": (Module.Light, ["valid_temperature_range"]),
"has_effects": (Module.Light, ["has_effects"]),
"_deprecated_set_light_state": (Module.Light, ["has_effects"]), "_deprecated_set_light_state": (Module.Light, ["has_effects"]),
# led attributes # led attributes
"led": (Module.Led, ["led"]), "led": (Module.Led, ["led"]),
@ -576,6 +611,9 @@ class Device(ABC):
msg = f"{name} is deprecated, use device_type property instead" msg = f"{name} is deprecated, use device_type property instead"
warn(msg, DeprecationWarning, stacklevel=2) warn(msg, DeprecationWarning, stacklevel=2)
return self.device_type == dep_device_type_attr[1] return self.device_type == dep_device_type_attr[1]
# callable
if result := self._get_deprecated_callable_attributes(name) is not None:
return result
# Other deprecated attributes # Other deprecated attributes
if (dep_attr := self._deprecated_other_attributes.get(name)) and ( if (dep_attr := self._deprecated_other_attributes.get(name)) and (
(replacing_attr := self._get_replacing_attr(dep_attr[0], *dep_attr[1])) (replacing_attr := self._get_replacing_attr(dep_attr[0], *dep_attr[1]))

View File

@ -25,11 +25,11 @@ Get the light module to interact:
You can use the ``is_``-prefixed properties to check for supported features: You can use the ``is_``-prefixed properties to check for supported features:
>>> light.is_dimmable >>> light.has_feature(light.set_brightness)
True True
>>> light.is_color >>> light.has_feature(light.set_hsv)
True True
>>> light.is_variable_color_temp >>> light.has_feature(light.set_color_temp)
True True
All known bulbs support changing the brightness: All known bulbs support changing the brightness:
@ -43,8 +43,9 @@ All known bulbs support changing the brightness:
Bulbs supporting color temperature can be queried for the supported range: Bulbs supporting color temperature can be queried for the supported range:
>>> light.valid_temperature_range >>> if color_temp_feature := light.get_feature(light.set_color_temp):
ColorTempRange(min=2500, max=6500) >>> print(f"{color_temp_feature.minimum_value}, {color_temp_feature.maximum_value}")
2500, 6500
>>> await light.set_color_temp(3000) >>> await light.set_color_temp(3000)
>>> await dev.update() >>> await dev.update()
>>> light.color_temp >>> light.color_temp
@ -63,10 +64,13 @@ HSV(hue=180, saturation=100, value=80)
from __future__ import annotations from __future__ import annotations
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from typing import NamedTuple from typing import Annotated, Any, NamedTuple
from warnings import warn
from ..module import Module from ..exceptions import KasaException
from ..module import FeatureAttribute, Module
@dataclass @dataclass
@ -101,35 +105,7 @@ class Light(Module, ABC):
@property @property
@abstractmethod @abstractmethod
def is_dimmable(self) -> bool: def hsv(self) -> Annotated[HSV, FeatureAttribute()]:
"""Whether the light supports brightness changes."""
@property
@abstractmethod
def is_color(self) -> bool:
"""Whether the bulb supports color changes."""
@property
@abstractmethod
def is_variable_color_temp(self) -> bool:
"""Whether the bulb supports color temperature changes."""
@property
@abstractmethod
def valid_temperature_range(self) -> ColorTempRange:
"""Return the device-specific white temperature range (in Kelvin).
:return: White temperature range in Kelvin (minimum, maximum)
"""
@property
@abstractmethod
def has_effects(self) -> bool:
"""Return True if the device supports effects."""
@property
@abstractmethod
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, %, %)
@ -137,12 +113,12 @@ class Light(Module, ABC):
@property @property
@abstractmethod @abstractmethod
def color_temp(self) -> int: def color_temp(self) -> Annotated[int, FeatureAttribute()]:
"""Whether the bulb supports color temperature changes.""" """Whether the bulb supports color temperature changes."""
@property @property
@abstractmethod @abstractmethod
def brightness(self) -> int: def brightness(self) -> Annotated[int, FeatureAttribute()]:
"""Return the current brightness in percentage.""" """Return the current brightness in percentage."""
@abstractmethod @abstractmethod
@ -153,7 +129,7 @@ class Light(Module, ABC):
value: int | None = None, value: int | None = None,
*, *,
transition: int | None = None, transition: int | None = None,
) -> dict: ) -> Annotated[dict, FeatureAttribute()]:
"""Set new HSV. """Set new HSV.
Note, transition is not supported and will be ignored. Note, transition is not supported and will be ignored.
@ -167,7 +143,7 @@ class Light(Module, ABC):
@abstractmethod @abstractmethod
async def set_color_temp( async def set_color_temp(
self, temp: int, *, brightness: int | None = None, transition: int | None = None self, temp: int, *, brightness: int | None = None, transition: int | None = None
) -> dict: ) -> Annotated[dict, FeatureAttribute()]:
"""Set the color temperature of the device in kelvin. """Set the color temperature of the device in kelvin.
Note, transition is not supported and will be ignored. Note, transition is not supported and will be ignored.
@ -179,7 +155,7 @@ class Light(Module, ABC):
@abstractmethod @abstractmethod
async def set_brightness( async def set_brightness(
self, brightness: int, *, transition: int | None = None self, brightness: int, *, transition: int | None = None
) -> dict: ) -> Annotated[dict, FeatureAttribute()]:
"""Set the brightness in percentage. """Set the brightness in percentage.
Note, transition is not supported and will be ignored. Note, transition is not supported and will be ignored.
@ -196,3 +172,42 @@ class Light(Module, ABC):
@abstractmethod @abstractmethod
async def set_state(self, state: LightState) -> dict: async def set_state(self, state: LightState) -> dict:
"""Set the light state.""" """Set the light state."""
def _deprecated_valid_temperature_range(self) -> ColorTempRange:
if not (temp := self.get_feature(self.set_color_temp)):
raise KasaException("Color temperature not supported")
return ColorTempRange(temp.minimum_value, temp.maximum_value)
def _deprecated_attributes(self, dep_name: str) -> Callable | None:
map: dict[str, Callable] = {
"is_color": self.set_hsv,
"is_dimmable": self.set_brightness,
"is_variable_color_temp": self.set_color_temp,
}
return map.get(dep_name)
def __getattr__(self, name: str) -> Any:
if name == "valid_temperature_range":
res = self._deprecated_valid_temperature_range()
msg = (
"valid_temperature_range is deprecated, use "
"get_feature(self.set_color_temp) minimum_value "
" and maximum_value instead"
)
warn(msg, DeprecationWarning, stacklevel=2)
return res
if name == "has_effects":
msg = (
"has_effects is deprecated, use Module.LightEffect "
"in device.modules instead"
)
warn(msg, DeprecationWarning, stacklevel=2)
return Module.LightEffect in self._device.modules
if attr := self._deprecated_attributes(name):
msg = f"{name} is deprecated, use has_feature({attr}) instead"
warn(msg, DeprecationWarning, stacklevel=2)
return self.has_feature(attr)
raise AttributeError(f"Energy module has no attribute {name!r}")

View File

@ -13,8 +13,7 @@ Living Room Bulb
Light effects are accessed via the LightPreset module. To list available presets Light effects are accessed via the LightPreset module. To list available presets
>>> if dev.modules[Module.Light].has_effects: >>> light_effect = dev.modules[Module.LightEffect]
>>> light_effect = dev.modules[Module.LightEffect]
>>> light_effect.effect_list >>> light_effect.effect_list
['Off', 'Party', 'Relax'] ['Off', 'Party', 'Relax']

View File

@ -3,13 +3,14 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import asdict from dataclasses import asdict
from typing import TYPE_CHECKING, cast from typing import TYPE_CHECKING, Annotated, cast
from ...device_type import DeviceType from ...device_type import DeviceType
from ...exceptions import KasaException from ...exceptions import KasaException
from ...feature import Feature from ...feature import Feature
from ...interfaces.light import HSV, ColorTempRange, LightState from ...interfaces.light import HSV, LightState
from ...interfaces.light import Light as LightInterface from ...interfaces.light import Light as LightInterface
from ...module import FeatureAttribute
from ..iotmodule import IotModule from ..iotmodule import IotModule
if TYPE_CHECKING: if TYPE_CHECKING:
@ -32,7 +33,7 @@ class Light(IotModule, LightInterface):
super()._initialize_features() super()._initialize_features()
device = self._device device = self._device
if self._device._is_dimmable: if device._is_dimmable:
self._add_feature( self._add_feature(
Feature( Feature(
device, device,
@ -46,7 +47,9 @@ class Light(IotModule, LightInterface):
category=Feature.Category.Primary, category=Feature.Category.Primary,
) )
) )
if self._device._is_variable_color_temp: if device._is_variable_color_temp:
if TYPE_CHECKING:
assert isinstance(device, IotBulb)
self._add_feature( self._add_feature(
Feature( Feature(
device=device, device=device,
@ -55,12 +58,12 @@ class Light(IotModule, LightInterface):
container=self, container=self,
attribute_getter="color_temp", attribute_getter="color_temp",
attribute_setter="set_color_temp", attribute_setter="set_color_temp",
range_getter="valid_temperature_range", range_getter=lambda: device._valid_temperature_range,
category=Feature.Category.Primary, category=Feature.Category.Primary,
type=Feature.Type.Number, type=Feature.Type.Number,
) )
) )
if self._device._is_color: if device._is_color:
self._add_feature( self._add_feature(
Feature( Feature(
device=device, device=device,
@ -90,18 +93,13 @@ class Light(IotModule, LightInterface):
return None return None
@property # type: ignore @property # type: ignore
def is_dimmable(self) -> int: def brightness(self) -> Annotated[int, FeatureAttribute()]:
"""Whether the bulb supports brightness changes."""
return self._device._is_dimmable
@property # type: ignore
def brightness(self) -> int:
"""Return the current brightness in percentage.""" """Return the current brightness in percentage."""
return self._device._brightness return self._device._brightness
async def set_brightness( async def set_brightness(
self, brightness: int, *, transition: int | None = None self, brightness: int, *, transition: int | None = None
) -> dict: ) -> Annotated[dict, FeatureAttribute()]:
"""Set the brightness in percentage. A value of 0 will turn off the light. """Set the brightness in percentage. A value of 0 will turn off the light.
:param int brightness: brightness in percent :param int brightness: brightness in percent
@ -112,28 +110,7 @@ class Light(IotModule, LightInterface):
) )
@property @property
def is_color(self) -> bool: def hsv(self) -> Annotated[HSV, FeatureAttribute()]:
"""Whether the light supports color changes."""
if (bulb := self._get_bulb_device()) is None:
return False
return bulb._is_color
@property
def is_variable_color_temp(self) -> bool:
"""Whether the bulb supports color temperature changes."""
if (bulb := self._get_bulb_device()) is None:
return False
return bulb._is_variable_color_temp
@property
def has_effects(self) -> bool:
"""Return True if the device supports effects."""
if (bulb := self._get_bulb_device()) is None:
return False
return bulb._has_effects
@property
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, %, %)
@ -149,7 +126,7 @@ class Light(IotModule, LightInterface):
value: int | None = None, value: int | None = None,
*, *,
transition: int | None = None, transition: int | None = None,
) -> dict: ) -> Annotated[dict, FeatureAttribute()]:
"""Set new HSV. """Set new HSV.
Note, transition is not supported and will be ignored. Note, transition is not supported and will be ignored.
@ -164,19 +141,7 @@ class Light(IotModule, LightInterface):
return await bulb._set_hsv(hue, saturation, value, transition=transition) return await bulb._set_hsv(hue, saturation, value, transition=transition)
@property @property
def valid_temperature_range(self) -> ColorTempRange: def color_temp(self) -> Annotated[int, FeatureAttribute()]:
"""Return the device-specific white temperature range (in Kelvin).
:return: White temperature range in Kelvin (minimum, maximum)
"""
if (
bulb := self._get_bulb_device()
) is None or not bulb._is_variable_color_temp:
raise KasaException("Light does not support colortemp.")
return bulb._valid_temperature_range
@property
def color_temp(self) -> int:
"""Whether the bulb supports color temperature changes.""" """Whether the bulb supports color temperature changes."""
if ( if (
bulb := self._get_bulb_device() bulb := self._get_bulb_device()
@ -186,7 +151,7 @@ class Light(IotModule, LightInterface):
async def set_color_temp( async def set_color_temp(
self, temp: int, *, brightness: int | None = None, transition: int | None = None self, temp: int, *, brightness: int | None = None, transition: int | None = None
) -> dict: ) -> Annotated[dict, FeatureAttribute()]:
"""Set the color temperature of the device in kelvin. """Set the color temperature of the device in kelvin.
Note, transition is not supported and will be ignored. Note, transition is not supported and will be ignored.
@ -246,13 +211,13 @@ class Light(IotModule, LightInterface):
state = LightState(light_on=False) state = LightState(light_on=False)
else: else:
state = LightState(light_on=True) state = LightState(light_on=True)
if self.is_dimmable: if self._device._is_dimmable:
state.brightness = self.brightness state.brightness = self.brightness
if self.is_color: if self._device._is_color:
hsv = self.hsv hsv = self.hsv
state.hue = hsv.hue state.hue = hsv.hue
state.saturation = hsv.saturation state.saturation = hsv.saturation
if self.is_variable_color_temp: if self._device._is_variable_color_temp:
state.color_temp = self.color_temp state.color_temp = self.color_temp
self._light_state = state self._light_state = state

View File

@ -147,6 +147,11 @@ class Module(ABC):
self._module = module self._module = module
self._module_features: dict[str, Feature] = {} self._module_features: dict[str, Feature] = {}
@property
def _all_features(self) -> dict[str, Feature]:
"""Get the features for this module and any sub modules."""
return self._module_features
def has_feature(self, attribute: str | property | Callable) -> bool: def has_feature(self, attribute: str | property | Callable) -> bool:
"""Return True if the module attribute feature is supported.""" """Return True if the module attribute feature is supported."""
return bool(self.get_feature(attribute)) return bool(self.get_feature(attribute))
@ -247,7 +252,7 @@ def _get_bound_feature(
) )
check = {attribute_name, attribute_callable} check = {attribute_name, attribute_callable}
for feature in module._module_features.values(): for feature in module._all_features.values():
if (getter := feature.attribute_getter) and getter in check: if (getter := feature.attribute_getter) and getter in check:
return feature return feature

View File

@ -3,11 +3,13 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import asdict from dataclasses import asdict
from typing import Annotated
from ...exceptions import KasaException from ...exceptions import KasaException
from ...interfaces.light import HSV, ColorTempRange, LightState from ...feature import Feature
from ...interfaces.light import HSV, LightState
from ...interfaces.light import Light as LightInterface from ...interfaces.light import Light as LightInterface
from ...module import Module from ...module import FeatureAttribute, Module
from ..smartmodule import SmartModule from ..smartmodule import SmartModule
@ -16,59 +18,45 @@ class Light(SmartModule, LightInterface):
_light_state: LightState _light_state: LightState
@property
def _all_features(self) -> dict[str, Feature]:
"""Get the features for this module and any sub modules."""
ret: dict[str, Feature] = {}
if brightness := self._device.modules.get(Module.Brightness):
ret.update(**brightness._module_features)
if color := self._device.modules.get(Module.Color):
ret.update(**color._module_features)
if temp := self._device.modules.get(Module.ColorTemperature):
ret.update(**temp._module_features)
return ret
def query(self) -> dict: def query(self) -> dict:
"""Query to execute during the update cycle.""" """Query to execute during the update cycle."""
return {} return {}
@property @property
def is_color(self) -> bool: def hsv(self) -> Annotated[HSV, FeatureAttribute()]:
"""Whether the bulb supports color changes."""
return Module.Color in self._device.modules
@property
def is_dimmable(self) -> bool:
"""Whether the bulb supports brightness changes."""
return Module.Brightness in self._device.modules
@property
def is_variable_color_temp(self) -> bool:
"""Whether the bulb supports color temperature changes."""
return Module.ColorTemperature in self._device.modules
@property
def valid_temperature_range(self) -> ColorTempRange:
"""Return the device-specific white temperature range (in Kelvin).
:return: White temperature range in Kelvin (minimum, maximum)
"""
if not self.is_variable_color_temp:
raise KasaException("Color temperature not supported")
return self._device.modules[Module.ColorTemperature].valid_temperature_range
@property
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, %, %)
""" """
if not self.is_color: if Module.Color not in self._device.modules:
raise KasaException("Bulb does not support color.") raise KasaException("Bulb does not support color.")
return self._device.modules[Module.Color].hsv return self._device.modules[Module.Color].hsv
@property @property
def color_temp(self) -> int: def color_temp(self) -> Annotated[int, FeatureAttribute()]:
"""Whether the bulb supports color temperature changes.""" """Whether the bulb supports color temperature changes."""
if not self.is_variable_color_temp: if Module.ColorTemperature not in self._device.modules:
raise KasaException("Bulb does not support colortemp.") raise KasaException("Bulb does not support colortemp.")
return self._device.modules[Module.ColorTemperature].color_temp return self._device.modules[Module.ColorTemperature].color_temp
@property @property
def brightness(self) -> int: def brightness(self) -> Annotated[int, FeatureAttribute()]:
"""Return the current brightness in percentage.""" """Return the current brightness in percentage."""
if not self.is_dimmable: # pragma: no cover if Module.Brightness not in self._device.modules: # pragma: no cover
raise KasaException("Bulb is not dimmable.") raise KasaException("Bulb is not dimmable.")
return self._device.modules[Module.Brightness].brightness return self._device.modules[Module.Brightness].brightness
@ -80,7 +68,7 @@ class Light(SmartModule, LightInterface):
value: int | None = None, value: int | None = None,
*, *,
transition: int | None = None, transition: int | None = None,
) -> dict: ) -> Annotated[dict, FeatureAttribute()]:
"""Set new HSV. """Set new HSV.
Note, transition is not supported and will be ignored. Note, transition is not supported and will be ignored.
@ -90,14 +78,14 @@ class Light(SmartModule, LightInterface):
:param int value: value between 1 and 100 :param int value: value between 1 and 100
:param int transition: transition in milliseconds. :param int transition: transition in milliseconds.
""" """
if not self.is_color: if Module.Color not in self._device.modules:
raise KasaException("Bulb does not support color.") raise KasaException("Bulb does not support color.")
return await self._device.modules[Module.Color].set_hsv(hue, saturation, value) return await self._device.modules[Module.Color].set_hsv(hue, saturation, value)
async def set_color_temp( async def set_color_temp(
self, temp: int, *, brightness: int | None = None, transition: int | None = None self, temp: int, *, brightness: int | None = None, transition: int | None = None
) -> dict: ) -> Annotated[dict, FeatureAttribute()]:
"""Set the color temperature of the device in kelvin. """Set the color temperature of the device in kelvin.
Note, transition is not supported and will be ignored. Note, transition is not supported and will be ignored.
@ -105,7 +93,7 @@ class Light(SmartModule, LightInterface):
:param int temp: The new color temperature, in Kelvin :param int temp: The new color temperature, in Kelvin
:param int transition: transition in milliseconds. :param int transition: transition in milliseconds.
""" """
if not self.is_variable_color_temp: if Module.ColorTemperature not in self._device.modules:
raise KasaException("Bulb does not support colortemp.") raise KasaException("Bulb does not support colortemp.")
return await self._device.modules[Module.ColorTemperature].set_color_temp( return await self._device.modules[Module.ColorTemperature].set_color_temp(
temp, brightness=brightness temp, brightness=brightness
@ -113,7 +101,7 @@ class Light(SmartModule, LightInterface):
async def set_brightness( async def set_brightness(
self, brightness: int, *, transition: int | None = None self, brightness: int, *, transition: int | None = None
) -> dict: ) -> Annotated[dict, FeatureAttribute()]:
"""Set the brightness in percentage. """Set the brightness in percentage.
Note, transition is not supported and will be ignored. Note, transition is not supported and will be ignored.
@ -121,16 +109,11 @@ class Light(SmartModule, LightInterface):
:param int brightness: brightness in percent :param int brightness: brightness in percent
:param int transition: transition in milliseconds. :param int transition: transition in milliseconds.
""" """
if not self.is_dimmable: # pragma: no cover if Module.Brightness not in self._device.modules: # pragma: no cover
raise KasaException("Bulb is not dimmable.") raise KasaException("Bulb is not dimmable.")
return await self._device.modules[Module.Brightness].set_brightness(brightness) return await self._device.modules[Module.Brightness].set_brightness(brightness)
@property
def has_effects(self) -> bool:
"""Return True if the device supports effects."""
return Module.LightEffect in self._device.modules
async def set_state(self, state: LightState) -> dict: async def set_state(self, state: LightState) -> dict:
"""Set the light state.""" """Set the light state."""
state_dict = asdict(state) state_dict = asdict(state)
@ -157,12 +140,12 @@ class Light(SmartModule, LightInterface):
state = LightState(light_on=False) state = LightState(light_on=False)
else: else:
state = LightState(light_on=True) state = LightState(light_on=True)
if self.is_dimmable: if Module.Brightness in self._device.modules:
state.brightness = self.brightness state.brightness = self.brightness
if self.is_color: if Module.Color in self._device.modules:
hsv = self.hsv hsv = self.hsv
state.hue = hsv.hue state.hue = hsv.hue
state.saturation = hsv.saturation state.saturation = hsv.saturation
if self.is_variable_color_temp: if Module.ColorTemperature in self._device.modules:
state.color_temp = self.color_temp state.color_temp = self.color_temp
self._light_state = state self._light_state = state

View File

@ -96,13 +96,20 @@ class LightPreset(SmartModule, LightPresetInterface):
"""Return current preset name.""" """Return current preset name."""
light = self._device.modules[SmartModule.Light] light = self._device.modules[SmartModule.Light]
brightness = light.brightness brightness = light.brightness
color_temp = light.color_temp if light.is_variable_color_temp else None color_temp = (
h, s = (light.hsv.hue, light.hsv.saturation) if light.is_color else (None, None) light.color_temp if light.has_feature(light.set_color_temp) else None
)
h, s = (
(light.hsv.hue, light.hsv.saturation)
if light.has_feature(light.set_hsv)
else (None, None)
)
for preset_name, preset in self._presets.items(): for preset_name, preset in self._presets.items():
if ( if (
preset.brightness == brightness preset.brightness == brightness
and ( and (
preset.color_temp == color_temp or not light.is_variable_color_temp preset.color_temp == color_temp
or not light.has_feature(light.set_color_temp)
) )
and preset.hue == h and preset.hue == h
and preset.saturation == s and preset.saturation == s
@ -117,7 +124,7 @@ class LightPreset(SmartModule, LightPresetInterface):
"""Set a light preset for the device.""" """Set a light preset for the device."""
light = self._device.modules[SmartModule.Light] light = self._device.modules[SmartModule.Light]
if preset_name == self.PRESET_NOT_SET: if preset_name == self.PRESET_NOT_SET:
if light.is_color: if light.has_feature(light.set_hsv):
preset = LightState(hue=0, saturation=0, brightness=100) preset = LightState(hue=0, saturation=0, brightness=100)
else: else:
preset = LightState(brightness=100) preset = LightState(brightness=100)