mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-01-07 13:27:07 +00:00
Add post update hook to module and use in smart LightEffect (#921)
Adds a post update hook to modules so they can calculate values and collections once rather than on each property access
This commit is contained in:
parent
3490a1ef84
commit
9989d0f6ec
kasa
@ -107,6 +107,18 @@ class Module(ABC):
|
|||||||
"""Initialize features after the initial update.
|
"""Initialize features after the initial update.
|
||||||
|
|
||||||
This can be implemented if features depend on module query responses.
|
This can be implemented if features depend on module query responses.
|
||||||
|
It will only be called once per module and will always be called
|
||||||
|
after *_post_update_hook* has been called for every device module and its
|
||||||
|
children's modules.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _post_update_hook(self): # noqa: B027
|
||||||
|
"""Perform actions after a device update.
|
||||||
|
|
||||||
|
This can be implemented if a module needs to perform actions each time
|
||||||
|
the device has updated like generating collections for property access.
|
||||||
|
It will be called after every update and will be called prior to
|
||||||
|
*_initialize_features* on the first update.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _add_feature(self, feature: Feature):
|
def _add_feature(self, feature: Feature):
|
||||||
|
@ -4,14 +4,11 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
import copy
|
import copy
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import Any
|
||||||
|
|
||||||
from ...interfaces.lighteffect import LightEffect as LightEffectInterface
|
from ...interfaces.lighteffect import LightEffect as LightEffectInterface
|
||||||
from ..smartmodule import SmartModule
|
from ..smartmodule import SmartModule
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from ..smartdevice import SmartDevice
|
|
||||||
|
|
||||||
|
|
||||||
class LightEffect(SmartModule, LightEffectInterface):
|
class LightEffect(SmartModule, LightEffectInterface):
|
||||||
"""Implementation of dynamic light effects."""
|
"""Implementation of dynamic light effects."""
|
||||||
@ -23,12 +20,13 @@ class LightEffect(SmartModule, LightEffectInterface):
|
|||||||
"L2": "Relax",
|
"L2": "Relax",
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, device: SmartDevice, module: str):
|
_effect: str
|
||||||
super().__init__(device, module)
|
_effect_state_list: dict[str, dict[str, Any]]
|
||||||
self._scenes_names_to_id: dict[str, str] = {}
|
_effect_list: list[str]
|
||||||
|
_scenes_names_to_id: dict[str, str]
|
||||||
|
|
||||||
def _initialize_effects(self) -> dict[str, dict[str, Any]]:
|
def _post_update_hook(self) -> None:
|
||||||
"""Return built-in effects."""
|
"""Update internal effect state."""
|
||||||
# Copy the effects so scene name updates do not update the underlying dict.
|
# Copy the effects so scene name updates do not update the underlying dict.
|
||||||
effects = copy.deepcopy(
|
effects = copy.deepcopy(
|
||||||
{effect["id"]: effect for effect in self.data["rule_list"]}
|
{effect["id"]: effect for effect in self.data["rule_list"]}
|
||||||
@ -40,10 +38,21 @@ class LightEffect(SmartModule, LightEffectInterface):
|
|||||||
else:
|
else:
|
||||||
# Otherwise it will be b64 encoded
|
# Otherwise it will be b64 encoded
|
||||||
effect["scene_name"] = base64.b64decode(effect["scene_name"]).decode()
|
effect["scene_name"] = base64.b64decode(effect["scene_name"]).decode()
|
||||||
|
|
||||||
|
self._effect_state_list = effects
|
||||||
|
self._effect_list = [self.LIGHT_EFFECTS_OFF]
|
||||||
|
self._effect_list.extend([effect["scene_name"] for effect in effects.values()])
|
||||||
self._scenes_names_to_id = {
|
self._scenes_names_to_id = {
|
||||||
effect["scene_name"]: effect["id"] for effect in effects.values()
|
effect["scene_name"]: effect["id"] for effect in effects.values()
|
||||||
}
|
}
|
||||||
return effects
|
# get_dynamic_light_effect_rules also has an enable property and current_rule_id
|
||||||
|
# property that could be used here as an alternative
|
||||||
|
if self._device._info["dynamic_light_effect_enable"]:
|
||||||
|
self._effect = self._effect_state_list[
|
||||||
|
self._device._info["dynamic_light_effect_id"]
|
||||||
|
]["scene_name"]
|
||||||
|
else:
|
||||||
|
self._effect = self.LIGHT_EFFECTS_OFF
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def effect_list(self) -> list[str]:
|
def effect_list(self) -> list[str]:
|
||||||
@ -52,22 +61,12 @@ class LightEffect(SmartModule, LightEffectInterface):
|
|||||||
Example:
|
Example:
|
||||||
['Party', 'Relax', ...]
|
['Party', 'Relax', ...]
|
||||||
"""
|
"""
|
||||||
effects = [self.LIGHT_EFFECTS_OFF]
|
return self._effect_list
|
||||||
effects.extend(
|
|
||||||
[effect["scene_name"] for effect in self._initialize_effects().values()]
|
|
||||||
)
|
|
||||||
return effects
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def effect(self) -> str:
|
def effect(self) -> str:
|
||||||
"""Return effect name."""
|
"""Return effect name."""
|
||||||
# get_dynamic_light_effect_rules also has an enable property and current_rule_id
|
return self._effect
|
||||||
# property that could be used here as an alternative
|
|
||||||
if self._device._info["dynamic_light_effect_enable"]:
|
|
||||||
return self._initialize_effects()[
|
|
||||||
self._device._info["dynamic_light_effect_id"]
|
|
||||||
]["scene_name"]
|
|
||||||
return self.LIGHT_EFFECTS_OFF
|
|
||||||
|
|
||||||
async def set_effect(
|
async def set_effect(
|
||||||
self,
|
self,
|
||||||
|
@ -41,7 +41,6 @@ class SmartChildDevice(SmartDevice):
|
|||||||
"""Create a child device based on device info and component listing."""
|
"""Create a child device based on device info and component listing."""
|
||||||
child: SmartChildDevice = cls(parent, child_info, child_components)
|
child: SmartChildDevice = cls(parent, child_info, child_components)
|
||||||
await child._initialize_modules()
|
await child._initialize_modules()
|
||||||
await child._initialize_features()
|
|
||||||
return child
|
return child
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -184,6 +184,13 @@ class SmartDevice(Device):
|
|||||||
for info in child_info["child_device_list"]:
|
for info in child_info["child_device_list"]:
|
||||||
self._children[info["device_id"]]._update_internal_state(info)
|
self._children[info["device_id"]]._update_internal_state(info)
|
||||||
|
|
||||||
|
# Call handle update for modules that want to update internal data
|
||||||
|
for module in self._modules.values():
|
||||||
|
module._post_update_hook()
|
||||||
|
for child in self._children.values():
|
||||||
|
for child_module in child._modules.values():
|
||||||
|
child_module._post_update_hook()
|
||||||
|
|
||||||
# We can first initialize the features after the first update.
|
# We can first initialize the features after the first update.
|
||||||
# We make here an assumption that every device has at least a single feature.
|
# We make here an assumption that every device has at least a single feature.
|
||||||
if not self._features:
|
if not self._features:
|
||||||
@ -332,6 +339,9 @@ class SmartDevice(Device):
|
|||||||
for feat in module._module_features.values():
|
for feat in module._module_features.values():
|
||||||
self._add_feature(feat)
|
self._add_feature(feat)
|
||||||
|
|
||||||
|
for child in self._children.values():
|
||||||
|
await child._initialize_features()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_cloud_connected(self) -> bool:
|
def is_cloud_connected(self) -> bool:
|
||||||
"""Returns if the device is connected to the cloud."""
|
"""Returns if the device is connected to the cloud."""
|
||||||
|
@ -48,7 +48,10 @@ async def test_initial_update(dev: SmartDevice, mocker: MockerFixture):
|
|||||||
"""Test the initial update cycle."""
|
"""Test the initial update cycle."""
|
||||||
# As the fixture data is already initialized, we reset the state for testing
|
# As the fixture data is already initialized, we reset the state for testing
|
||||||
dev._components_raw = None
|
dev._components_raw = None
|
||||||
|
dev._components = {}
|
||||||
|
dev._modules = {}
|
||||||
dev._features = {}
|
dev._features = {}
|
||||||
|
dev._children = {}
|
||||||
|
|
||||||
negotiate = mocker.spy(dev, "_negotiate")
|
negotiate = mocker.spy(dev, "_negotiate")
|
||||||
initialize_modules = mocker.spy(dev, "_initialize_modules")
|
initialize_modules = mocker.spy(dev, "_initialize_modules")
|
||||||
|
Loading…
Reference in New Issue
Block a user