mirror of
https://github.com/python-kasa/python-kasa.git
synced 2024-12-22 19:23: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()
|
BinarySensor = auto()
|
||||||
Switch = auto()
|
Switch = auto()
|
||||||
Button = auto()
|
Button = auto()
|
||||||
|
Number = auto()
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -35,6 +36,12 @@ class Feature:
|
|||||||
#: Type of the feature
|
#: Type of the feature
|
||||||
type: FeatureType = FeatureType.Sensor
|
type: FeatureType = FeatureType.Sensor
|
||||||
|
|
||||||
|
# Number-specific attributes
|
||||||
|
#: Minimum value
|
||||||
|
minimum_value: int = 0
|
||||||
|
#: Maximum value
|
||||||
|
maximum_value: int = 2**16 # Arbitrary max
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self):
|
def value(self):
|
||||||
"""Return the current value."""
|
"""Return the current value."""
|
||||||
@ -47,5 +54,12 @@ class Feature:
|
|||||||
"""Set the value."""
|
"""Set the value."""
|
||||||
if self.attribute_setter is None:
|
if self.attribute_setter is None:
|
||||||
raise ValueError("Tried to set read-only feature.")
|
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
|
container = self.container if self.container is not None else self.device
|
||||||
return await getattr(container, self.attribute_setter)(value)
|
return await getattr(container, self.attribute_setter)(value)
|
||||||
|
@ -15,7 +15,7 @@ class DeviceModule(SmartModule):
|
|||||||
"get_device_info": None,
|
"get_device_info": None,
|
||||||
}
|
}
|
||||||
# Device usage is not available on older firmware versions
|
# 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
|
query["get_device_usage"] = None
|
||||||
|
|
||||||
return query
|
return query
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""Module for smooth light transitions."""
|
"""Module for smooth light transitions."""
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from ...exceptions import KasaException
|
||||||
from ...feature import Feature, FeatureType
|
from ...feature import Feature, FeatureType
|
||||||
from ..smartmodule import SmartModule
|
from ..smartmodule import SmartModule
|
||||||
|
|
||||||
@ -13,29 +14,152 @@ class LightTransitionModule(SmartModule):
|
|||||||
|
|
||||||
REQUIRED_COMPONENT = "on_off_gradually"
|
REQUIRED_COMPONENT = "on_off_gradually"
|
||||||
QUERY_GETTER_NAME = "get_on_off_gradually_info"
|
QUERY_GETTER_NAME = "get_on_off_gradually_info"
|
||||||
|
MAXIMUM_DURATION = 60
|
||||||
|
|
||||||
def __init__(self, device: "SmartDevice", module: str):
|
def __init__(self, device: "SmartDevice", module: str):
|
||||||
super().__init__(device, module)
|
super().__init__(device, module)
|
||||||
self._add_feature(
|
self._create_features()
|
||||||
Feature(
|
|
||||||
device=device,
|
|
||||||
container=self,
|
|
||||||
name="Smooth transitions",
|
|
||||||
icon="mdi:transition",
|
|
||||||
attribute_getter="enabled",
|
|
||||||
attribute_setter="set_enabled",
|
|
||||||
type=FeatureType.Switch,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
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."""
|
"""Enable gradual on/off."""
|
||||||
return self.call("set_on_off_gradually_info", {"enable": enable})
|
return self.call("set_on_off_gradually_info", {"enable": enable})
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def enabled(self) -> bool:
|
def enabled_v1(self) -> bool:
|
||||||
"""Return True if gradual on/off is enabled."""
|
"""Return True if gradual on/off is enabled."""
|
||||||
return bool(self.data["enable"])
|
return bool(self.data["enable"])
|
||||||
|
|
||||||
def __cli_output__(self):
|
@property
|
||||||
return f"Gradual on/off enabled: {self.enabled}"
|
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 next(iter(filtered_data.values()))
|
||||||
|
|
||||||
return filtered_data
|
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