Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Make Light and Fan a common module interface #911

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 13, 2024
Merged
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
4 changes: 2 additions & 2 deletions kasa/interfaces/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

from abc import ABC, abstractmethod

from ..device import Device
from ..module import Module


class Fan(Device, ABC):
class Fan(Module, ABC):
"""Interface for a Fan."""

@property
Expand Down
16 changes: 6 additions & 10 deletions kasa/interfaces/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from pydantic.v1 import BaseModel

from ..device import Device
from ..module import Module


class ColorTempRange(NamedTuple):
Expand Down Expand Up @@ -42,12 +42,13 @@ class LightPreset(BaseModel):
mode: Optional[int] # noqa: UP007


class Light(Device, ABC):
class Light(Module, ABC):
"""Base class for TP-Link Light."""

def _raise_for_invalid_brightness(self, value):
if not isinstance(value, int) or not (0 <= value <= 100):
raise ValueError(f"Invalid brightness value: {value} (valid range: 0-100%)")
@property
@abstractmethod
def is_dimmable(self) -> bool:
"""Whether the light supports brightness changes."""

@property
@abstractmethod
Expand Down Expand Up @@ -132,8 +133,3 @@ async def set_brightness(
:param int brightness: brightness in percent
:param int transition: transition in milliseconds.
"""

@property
@abstractmethod
def presets(self) -> list[LightPreset]:
"""Return a list of available bulb setting presets."""
58 changes: 21 additions & 37 deletions kasa/iot/iotbulb.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,20 @@

from ..device_type import DeviceType
from ..deviceconfig import DeviceConfig
from ..feature import Feature
from ..interfaces.light import HSV, ColorTempRange, Light, LightPreset
from ..interfaces.light import HSV, ColorTempRange, LightPreset
from ..module import Module
from ..protocol import BaseProtocol
from .iotdevice import IotDevice, KasaException, requires_update
from .modules import Antitheft, Cloud, Countdown, Emeter, Schedule, Time, Usage
from .modules import (
Antitheft,
Cloud,
Countdown,
Emeter,
Light,
Schedule,
Time,
Usage,
)


class BehaviorMode(str, Enum):
Expand Down Expand Up @@ -88,7 +96,7 @@ class TurnOnBehaviors(BaseModel):
_LOGGER = logging.getLogger(__name__)


class IotBulb(IotDevice, Light):
class IotBulb(IotDevice):
r"""Representation of a TP-Link Smart Bulb.

To initialize, you have to await :func:`update()` at least once.
Expand Down Expand Up @@ -199,6 +207,10 @@ def __init__(
) -> None:
super().__init__(host=host, config=config, protocol=protocol)
self._device_type = DeviceType.Bulb

async def _initialize_modules(self):
"""Initialize modules not added in init."""
await super()._initialize_modules()
self.add_module(
Module.IotSchedule, Schedule(self, "smartlife.iot.common.schedule")
)
Expand All @@ -210,39 +222,7 @@ def __init__(
self.add_module(Module.IotEmeter, Emeter(self, self.emeter_type))
self.add_module(Module.IotCountdown, Countdown(self, "countdown"))
self.add_module(Module.IotCloud, Cloud(self, "smartlife.iot.common.cloud"))

async def _initialize_features(self):
await super()._initialize_features()

if bool(self.sys_info["is_dimmable"]): # pragma: no branch
self._add_feature(
Feature(
device=self,
id="brightness",
name="Brightness",
attribute_getter="brightness",
attribute_setter="set_brightness",
minimum_value=1,
maximum_value=100,
type=Feature.Type.Number,
category=Feature.Category.Primary,
)
)

if self.is_variable_color_temp:
self._add_feature(
Feature(
device=self,
id="color_temperature",
name="Color temperature",
container=self,
attribute_getter="color_temp",
attribute_setter="set_color_temp",
range_getter="valid_temperature_range",
category=Feature.Category.Primary,
type=Feature.Type.Number,
)
)
self.add_module(Module.Light, Light(self, "light"))

@property # type: ignore
@requires_update
Expand Down Expand Up @@ -458,6 +438,10 @@ async def set_color_temp(

return await self.set_light_state(light_state, transition=transition)

def _raise_for_invalid_brightness(self, value):
if not isinstance(value, int) or not (0 <= value <= 100):
raise ValueError(f"Invalid brightness value: {value} (valid range: 0-100%)")

@property # type: ignore
@requires_update
def brightness(self) -> int:
Expand Down
6 changes: 6 additions & 0 deletions kasa/iot/iotdevice.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,13 +307,19 @@ async def update(self, update_children: bool = True):
self._last_update = response
self._set_sys_info(response["system"]["get_sysinfo"])

if not self._modules:
await self._initialize_modules()

await self._modular_update(req)

if not self._features:
await self._initialize_features()

self._set_sys_info(self._last_update["system"]["get_sysinfo"])

async def _initialize_modules(self):
"""Initialize modules not added in init."""

async def _initialize_features(self):
self._add_feature(
Feature(
Expand Down
27 changes: 6 additions & 21 deletions kasa/iot/iotdimmer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@

from ..device_type import DeviceType
from ..deviceconfig import DeviceConfig
from ..feature import Feature
from ..module import Module
from ..protocol import BaseProtocol
from .iotdevice import KasaException, requires_update
from .iotplug import IotPlug
from .modules import AmbientLight, Motion
from .modules import AmbientLight, Light, Motion


class ButtonAction(Enum):
Expand Down Expand Up @@ -80,29 +79,15 @@ def __init__(
) -> None:
super().__init__(host=host, config=config, protocol=protocol)
self._device_type = DeviceType.Dimmer

async def _initialize_modules(self):
"""Initialize modules."""
await super()._initialize_modules()
# TODO: need to be verified if it's okay to call these on HS220 w/o these
# TODO: need to be figured out what's the best approach to detect support
self.add_module(Module.IotMotion, Motion(self, "smartlife.iot.PIR"))
self.add_module(Module.IotAmbientLight, AmbientLight(self, "smartlife.iot.LAS"))

async def _initialize_features(self):
await super()._initialize_features()

if "brightness" in self.sys_info: # pragma: no branch
self._add_feature(
Feature(
device=self,
id="brightness",
name="Brightness",
attribute_getter="brightness",
attribute_setter="set_brightness",
minimum_value=1,
maximum_value=100,
unit="%",
type=Feature.Type.Number,
category=Feature.Category.Primary,
)
)
self.add_module(Module.Light, Light(self, "light"))

@property # type: ignore
@requires_update
Expand Down
4 changes: 4 additions & 0 deletions kasa/iot/iotlightstrip.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ def __init__(
) -> None:
super().__init__(host=host, config=config, protocol=protocol)
self._device_type = DeviceType.LightStrip

async def _initialize_modules(self):
"""Initialize modules not added in init."""
await super()._initialize_modules()
self.add_module(
Module.LightEffect,
LightEffect(self, "smartlife.iot.lighting_effect"),
Expand Down
4 changes: 4 additions & 0 deletions kasa/iot/iotplug.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ def __init__(
) -> None:
super().__init__(host=host, config=config, protocol=protocol)
self._device_type = DeviceType.Plug

async def _initialize_modules(self):
"""Initialize modules."""
await super()._initialize_modules()
self.add_module(Module.IotSchedule, Schedule(self, "schedule"))
self.add_module(Module.IotUsage, Usage(self, "schedule"))
self.add_module(Module.IotAntitheft, Antitheft(self, "anti_theft"))
Expand Down
4 changes: 4 additions & 0 deletions kasa/iot/iotstrip.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,10 @@ def __init__(self, host: str, parent: IotStrip, child_id: str) -> None:
self._set_sys_info(parent.sys_info)
self._device_type = DeviceType.StripSocket
self.protocol = parent.protocol # Must use the same connection as the parent

async def _initialize_modules(self):
"""Initialize modules not added in init."""
await super()._initialize_modules()
self.add_module("time", Time(self, "time"))

async def update(self, update_children: bool = True):
Expand Down
2 changes: 2 additions & 0 deletions kasa/iot/modules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .countdown import Countdown
from .emeter import Emeter
from .led import Led
from .light import Light
from .lighteffect import LightEffect
from .motion import Motion
from .rulemodule import Rule, RuleModule
Expand All @@ -20,6 +21,7 @@
"Countdown",
"Emeter",
"Led",
"Light",
"LightEffect",
"Motion",
"Rule",
Expand Down
Loading