diff --git a/kasa/smart/modules/__init__.py b/kasa/smart/modules/__init__.py index ae9fb68f..1d397702 100644 --- a/kasa/smart/modules/__init__.py +++ b/kasa/smart/modules/__init__.py @@ -12,6 +12,7 @@ from .color import Color from .colortemperature import ColorTemperature from .contactsensor import ContactSensor from .devicemodule import DeviceModule +from .doubleclick import DoubleClick from .energy import Energy from .fan import Fan from .firmware import Firmware @@ -42,6 +43,7 @@ __all__ = [ "DeviceModule", "ChildDevice", "BatterySensor", + "DoubleClick", "HumiditySensor", "TemperatureSensor", "TemperatureControl", diff --git a/kasa/smart/modules/doubleclick.py b/kasa/smart/modules/doubleclick.py new file mode 100644 index 00000000..b2267954 --- /dev/null +++ b/kasa/smart/modules/doubleclick.py @@ -0,0 +1,42 @@ +"""Module for double click enable.""" + +from __future__ import annotations + +from ...feature import Feature +from ..smartmodule import SmartModule, allow_update_after + + +class DoubleClick(SmartModule): + """Implementation of double click module.""" + + REQUIRED_COMPONENT = "double_click" + QUERY_GETTER_NAME = "get_double_click_info" + + def _initialize_features(self) -> None: + """Initialize features after the initial update.""" + self._add_feature( + Feature( + self._device, + id="double_click", + name="Double click", + container=self, + attribute_getter="enabled", + attribute_setter="set_enabled", + type=Feature.Type.Switch, + category=Feature.Category.Config, + ) + ) + + def query(self) -> dict: + """Query to execute during the update cycle.""" + return {self.QUERY_GETTER_NAME: {}} + + @property + def enabled(self) -> bool: + """Return current double click enabled status.""" + return self.data["enable"] + + @allow_update_after + async def set_enabled(self, enable: bool) -> dict: + """Set double click enable.""" + return await self.call("set_double_click_info", {"enable": enable}) diff --git a/kasa/smart/smartdevice.py b/kasa/smart/smartdevice.py index 5fd22115..1c4925be 100644 --- a/kasa/smart/smartdevice.py +++ b/kasa/smart/smartdevice.py @@ -183,7 +183,7 @@ class SmartDevice(Device): """Update the internal device info.""" self._info = self._try_get_response(info_resp, "get_device_info") - async def update(self, update_children: bool = False) -> None: + async def update(self, update_children: bool = True) -> None: """Update the device.""" if self.credentials is None and self.credentials_hash is None: raise AuthenticationError("Tapo plug requires authentication.") @@ -207,7 +207,7 @@ class SmartDevice(Device): # devices will always update children to prevent errors on module access. # This needs to go after updating the internal state of the children so that # child modules have access to their sysinfo. - if update_children or self.device_type != DeviceType.Hub: + if first_update or update_children or self.device_type != DeviceType.Hub: for child in self._children.values(): if TYPE_CHECKING: assert isinstance(child, SmartChildDevice) diff --git a/kasa/smart/smartmodule.py b/kasa/smart/smartmodule.py index a5666f63..fd937ccb 100644 --- a/kasa/smart/smartmodule.py +++ b/kasa/smart/smartmodule.py @@ -4,12 +4,13 @@ from __future__ import annotations import logging from collections.abc import Awaitable, Callable, Coroutine -from typing import TYPE_CHECKING, Any, Concatenate, ParamSpec, TypeVar +from typing import TYPE_CHECKING, Any, Concatenate, Final, ParamSpec, TypeVar from ..exceptions import DeviceError, KasaException, SmartErrorCode -from ..module import Module +from ..module import Module, ModuleName if TYPE_CHECKING: + from . import modules from .smartdevice import SmartDevice _LOGGER = logging.getLogger(__name__) @@ -51,6 +52,8 @@ def raise_if_update_error(func: Callable[[_T], _R]) -> Callable[[_T], _R]: class SmartModule(Module): """Base class for SMART modules.""" + SmartDoubleClick: Final[ModuleName[modules.DoubleClick]] = ModuleName("DoubleClick") + NAME: str #: Module is initialized, if the given component is available REQUIRED_COMPONENT: str | None = None diff --git a/tests/fakeprotocol_smart.py b/tests/fakeprotocol_smart.py index c0222b99..aa17f1b2 100644 --- a/tests/fakeprotocol_smart.py +++ b/tests/fakeprotocol_smart.py @@ -167,6 +167,12 @@ class FakeSmartTransport(BaseTransport): "setup_payload": "00:0000000-0000.00.000", }, ), + "get_double_click_info": ( + "double_click", + { + "enable": False, + }, + ), } async def send(self, request: str): diff --git a/tests/smart/modules/test_doubleclick.py b/tests/smart/modules/test_doubleclick.py new file mode 100644 index 00000000..f0cd1c22 --- /dev/null +++ b/tests/smart/modules/test_doubleclick.py @@ -0,0 +1,43 @@ +"""Tests for smart double click module.""" + +from __future__ import annotations + +from kasa import Device +from kasa.smartcam.smartcammodule import SmartModule + +from ...device_fixtures import parametrize + +doubleclick = parametrize( + "has double click", component_filter="double_click", protocol_filter={"SMART.CHILD"} +) + + +@doubleclick +async def test_doubleclick(dev: Device): + """Test device double click.""" + doubleclick = dev.modules.get(SmartModule.SmartDoubleClick) + assert doubleclick + + dc_feat = dev.features.get("double_click") + assert dc_feat + + original_enabled = doubleclick.enabled + + try: + await doubleclick.set_enabled(not original_enabled) + await dev.update() + assert doubleclick.enabled is not original_enabled + assert dc_feat.value is not original_enabled + + await doubleclick.set_enabled(original_enabled) + await dev.update() + assert doubleclick.enabled is original_enabled + assert dc_feat.value is original_enabled + + await dc_feat.set_value(not original_enabled) + await dev.update() + assert doubleclick.enabled is not original_enabled + assert dc_feat.value is not original_enabled + + finally: + await doubleclick.set_enabled(original_enabled)