Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions kasa/smart/modules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
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
Expand Down Expand Up @@ -42,6 +43,7 @@
"DeviceModule",
"ChildDevice",
"BatterySensor",
"DoubleClick",
"HumiditySensor",
"TemperatureSensor",
"TemperatureControl",
Expand Down
42 changes: 42 additions & 0 deletions kasa/smart/modules/doubleclick.py
Original file line number Diff line number Diff line change
@@ -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})
4 changes: 2 additions & 2 deletions kasa/smart/smartdevice.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ def _update_internal_info(self, info_resp: dict) -> None:
"""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.")
Expand All @@ -207,7 +207,7 @@ async def update(self, update_children: bool = False) -> None:
# 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)
Expand Down
7 changes: 5 additions & 2 deletions kasa/smart/smartmodule.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@

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__)
Expand Down Expand Up @@ -51,6 +52,8 @@ def _wrap(self: _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
Expand Down
6 changes: 6 additions & 0 deletions tests/fakeprotocol_smart.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,12 @@ def credentials_hash(self):
"setup_payload": "00:0000000-0000.00.000",
},
),
"get_double_click_info": (
"double_click",
{
"enable": False,
},
),
}

async def send(self, request: str):
Expand Down
43 changes: 43 additions & 0 deletions tests/smart/modules/test_doubleclick.py
Original file line number Diff line number Diff line change
@@ -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)
Loading