mirror of
https://github.com/python-kasa/python-kasa.git
synced 2024-12-22 11:13:34 +00:00
Support for on_off_gradually v2+ (#793)
Previously, only v1 of on_off_gradually is supported, and the newer versions are not backwards compatible. This PR adds support for the newer versions of the component, and implements `number` type for `Feature` to expose the transition time selection. This also adds a new `supported_version` property to the main module API.
This commit is contained in:
parent
a73e2a9ede
commit
cbf82c9498
@ -14,6 +14,7 @@ class FeatureType(Enum):
|
||||
BinarySensor = auto()
|
||||
Switch = auto()
|
||||
Button = auto()
|
||||
Number = auto()
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -35,6 +36,12 @@ class Feature:
|
||||
#: Type of the feature
|
||||
type: FeatureType = FeatureType.Sensor
|
||||
|
||||
# Number-specific attributes
|
||||
#: Minimum value
|
||||
minimum_value: int = 0
|
||||
#: Maximum value
|
||||
maximum_value: int = 2**16 # Arbitrary max
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""Return the current value."""
|
||||
@ -47,5 +54,12 @@ class Feature:
|
||||
"""Set the value."""
|
||||
if self.attribute_setter is None:
|
||||
raise ValueError("Tried to set read-only feature.")
|
||||
if self.type == FeatureType.Number: # noqa: SIM102
|
||||
if value < self.minimum_value or value > self.maximum_value:
|
||||
raise ValueError(
|
||||
f"Value {value} out of range "
|
||||
f"[{self.minimum_value}, {self.maximum_value}]"
|
||||
)
|
||||
|
||||
container = self.container if self.container is not None else self.device
|
||||
return await getattr(container, self.attribute_setter)(value)
|
||||
|
@ -15,7 +15,7 @@ class DeviceModule(SmartModule):
|
||||
"get_device_info": None,
|
||||
}
|
||||
# Device usage is not available on older firmware versions
|
||||
if self._device._components[self.REQUIRED_COMPONENT] >= 2:
|
||||
if self.supported_version >= 2:
|
||||
query["get_device_usage"] = None
|
||||
|
||||
return query
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""Module for smooth light transitions."""
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ...exceptions import KasaException
|
||||
from ...feature import Feature, FeatureType
|
||||
from ..smartmodule import SmartModule
|
||||
|
||||
@ -13,29 +14,152 @@ class LightTransitionModule(SmartModule):
|
||||
|
||||
REQUIRED_COMPONENT = "on_off_gradually"
|
||||
QUERY_GETTER_NAME = "get_on_off_gradually_info"
|
||||
MAXIMUM_DURATION = 60
|
||||
|
||||
def __init__(self, device: "SmartDevice", module: str):
|
||||
super().__init__(device, module)
|
||||
self._add_feature(
|
||||
Feature(
|
||||
device=device,
|
||||
container=self,
|
||||
name="Smooth transitions",
|
||||
icon="mdi:transition",
|
||||
attribute_getter="enabled",
|
||||
attribute_setter="set_enabled",
|
||||
type=FeatureType.Switch,
|
||||
)
|
||||
)
|
||||
self._create_features()
|
||||
|
||||
def set_enabled(self, enable: bool):
|
||||
def _create_features(self):
|
||||
"""Create features based on the available version."""
|
||||
icon = "mdi:transition"
|
||||
if self.supported_version == 1:
|
||||
self._add_feature(
|
||||
Feature(
|
||||
device=self._device,
|
||||
container=self,
|
||||
name="Smooth transitions",
|
||||
icon=icon,
|
||||
attribute_getter="enabled_v1",
|
||||
attribute_setter="set_enabled_v1",
|
||||
type=FeatureType.Switch,
|
||||
)
|
||||
)
|
||||
elif self.supported_version >= 2:
|
||||
# v2 adds separate on & off states
|
||||
# v3 adds max_duration
|
||||
# TODO: note, hardcoding the maximums for now as the features get
|
||||
# initialized before the first update.
|
||||
self._add_feature(
|
||||
Feature(
|
||||
self._device,
|
||||
"Smooth transition on",
|
||||
container=self,
|
||||
attribute_getter="turn_on_transition",
|
||||
attribute_setter="set_turn_on_transition",
|
||||
icon=icon,
|
||||
type=FeatureType.Number,
|
||||
maximum_value=self.MAXIMUM_DURATION,
|
||||
)
|
||||
) # self._turn_on_transition_max
|
||||
self._add_feature(
|
||||
Feature(
|
||||
self._device,
|
||||
"Smooth transition off",
|
||||
container=self,
|
||||
attribute_getter="turn_off_transition",
|
||||
attribute_setter="set_turn_off_transition",
|
||||
icon=icon,
|
||||
type=FeatureType.Number,
|
||||
maximum_value=self.MAXIMUM_DURATION,
|
||||
)
|
||||
) # self._turn_off_transition_max
|
||||
|
||||
@property
|
||||
def _turn_on(self):
|
||||
"""Internal getter for turn on settings."""
|
||||
if "on_state" not in self.data:
|
||||
raise KasaException(
|
||||
f"Unsupported for {self.REQUIRED_COMPONENT} v{self.supported_version}"
|
||||
)
|
||||
|
||||
return self.data["on_state"]
|
||||
|
||||
@property
|
||||
def _turn_off(self):
|
||||
"""Internal getter for turn off settings."""
|
||||
if "off_state" not in self.data:
|
||||
raise KasaException(
|
||||
f"Unsupported for {self.REQUIRED_COMPONENT} v{self.supported_version}"
|
||||
)
|
||||
|
||||
return self.data["off_state"]
|
||||
|
||||
def set_enabled_v1(self, enable: bool):
|
||||
"""Enable gradual on/off."""
|
||||
return self.call("set_on_off_gradually_info", {"enable": enable})
|
||||
|
||||
@property
|
||||
def enabled(self) -> bool:
|
||||
def enabled_v1(self) -> bool:
|
||||
"""Return True if gradual on/off is enabled."""
|
||||
return bool(self.data["enable"])
|
||||
|
||||
def __cli_output__(self):
|
||||
return f"Gradual on/off enabled: {self.enabled}"
|
||||
@property
|
||||
def turn_on_transition(self) -> int:
|
||||
"""Return transition time for turning the light on.
|
||||
|
||||
Available only from v2.
|
||||
"""
|
||||
return self._turn_on["duration"]
|
||||
|
||||
@property
|
||||
def _turn_on_transition_max(self) -> int:
|
||||
"""Maximum turn on duration."""
|
||||
# v3 added max_duration, we default to 60 when it's not available
|
||||
return self._turn_on.get("max_duration", 60)
|
||||
|
||||
async def set_turn_on_transition(self, seconds: int):
|
||||
"""Set turn on transition in seconds.
|
||||
|
||||
Setting to 0 turns the feature off.
|
||||
"""
|
||||
if seconds > self._turn_on_transition_max:
|
||||
raise ValueError(
|
||||
f"Value {seconds} out of range, max {self._turn_on_transition_max}"
|
||||
)
|
||||
|
||||
if seconds <= 0:
|
||||
return await self.call(
|
||||
"set_on_off_gradually_info",
|
||||
{"on_state": {**self._turn_on, "enable": False}},
|
||||
)
|
||||
|
||||
return await self.call(
|
||||
"set_on_off_gradually_info",
|
||||
{"on_state": {**self._turn_on, "duration": seconds}},
|
||||
)
|
||||
|
||||
@property
|
||||
def turn_off_transition(self) -> int:
|
||||
"""Return transition time for turning the light off.
|
||||
|
||||
Available only from v2.
|
||||
"""
|
||||
return self._turn_off["duration"]
|
||||
|
||||
@property
|
||||
def _turn_off_transition_max(self) -> int:
|
||||
"""Maximum turn on duration."""
|
||||
# v3 added max_duration, we default to 60 when it's not available
|
||||
return self._turn_off.get("max_duration", 60)
|
||||
|
||||
async def set_turn_off_transition(self, seconds: int):
|
||||
"""Set turn on transition in seconds.
|
||||
|
||||
Setting to 0 turns the feature off.
|
||||
"""
|
||||
if seconds > self._turn_off_transition_max:
|
||||
raise ValueError(
|
||||
f"Value {seconds} out of range, max {self._turn_off_transition_max}"
|
||||
)
|
||||
|
||||
if seconds <= 0:
|
||||
return await self.call(
|
||||
"set_on_off_gradually_info",
|
||||
{"off_state": {**self._turn_off, "enable": False}},
|
||||
)
|
||||
|
||||
return await self.call(
|
||||
"set_on_off_gradually_info",
|
||||
{"off_state": {**self._turn_on, "duration": seconds}},
|
||||
)
|
||||
|
@ -80,3 +80,8 @@ class SmartModule(Module):
|
||||
return next(iter(filtered_data.values()))
|
||||
|
||||
return filtered_data
|
||||
|
||||
@property
|
||||
def supported_version(self) -> int:
|
||||
"""Return version supported by the device."""
|
||||
return self._device._components[self.REQUIRED_COMPONENT]
|
||||
|
Loading…
Reference in New Issue
Block a user