Make module names consistent and remove redundant module casting (#909)

Address the inconsistent naming of smart modules by removing all "Module" suffixes and aligning filenames with class names. Removes the casting of modules to the correct module type now that is is redundant. Update the adding of iot modules to use the ModuleName class rather than a free string.
This commit is contained in:
Steven B 2024-05-11 19:28:18 +01:00 committed by GitHub
parent 9473d97ad2
commit f259a8f162
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 162 additions and 171 deletions

View File

@ -13,6 +13,7 @@ from ..bulb import HSV, Bulb, BulbPreset, ColorTempRange
from ..device_type import DeviceType
from ..deviceconfig import DeviceConfig
from ..feature import Feature
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
@ -198,13 +199,17 @@ class IotBulb(IotDevice, Bulb):
) -> None:
super().__init__(host=host, config=config, protocol=protocol)
self._device_type = DeviceType.Bulb
self.add_module("schedule", Schedule(self, "smartlife.iot.common.schedule"))
self.add_module("usage", Usage(self, "smartlife.iot.common.schedule"))
self.add_module("antitheft", Antitheft(self, "smartlife.iot.common.anti_theft"))
self.add_module("time", Time(self, "smartlife.iot.common.timesetting"))
self.add_module("emeter", Emeter(self, self.emeter_type))
self.add_module("countdown", Countdown(self, "countdown"))
self.add_module("cloud", Cloud(self, "smartlife.iot.common.cloud"))
self.add_module(
Module.IotSchedule, Schedule(self, "smartlife.iot.common.schedule")
)
self.add_module(Module.IotUsage, Usage(self, "smartlife.iot.common.schedule"))
self.add_module(
Module.IotAntitheft, Antitheft(self, "smartlife.iot.common.anti_theft")
)
self.add_module(Module.IotTime, Time(self, "smartlife.iot.common.timesetting"))
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()

View File

@ -30,7 +30,7 @@ from ..module import Module
from ..modulemapping import ModuleMapping, ModuleName
from ..protocol import BaseProtocol
from .iotmodule import IotModule
from .modules import Emeter, Time
from .modules import Emeter
_LOGGER = logging.getLogger(__name__)
@ -347,7 +347,7 @@ class IotDevice(Device):
_LOGGER.debug(
"The device has emeter, querying its information along sysinfo"
)
self.add_module("emeter", Emeter(self, self.emeter_type))
self.add_module(Module.IotEmeter, Emeter(self, self.emeter_type))
# TODO: perhaps modules should not have unsupported modules,
# making separate handling for this unnecessary
@ -440,27 +440,27 @@ class IotDevice(Device):
@requires_update
def time(self) -> datetime:
"""Return current time from the device."""
return cast(Time, self.modules["time"]).time
return self.modules[Module.IotTime].time
@property
@requires_update
def timezone(self) -> dict:
"""Return the current timezone."""
return cast(Time, self.modules["time"]).timezone
return self.modules[Module.IotTime].timezone
async def get_time(self) -> datetime | None:
"""Return current time from the device, if available."""
_LOGGER.warning(
"Use `time` property instead, this call will be removed in the future."
)
return await cast(Time, self.modules["time"]).get_time()
return await self.modules[Module.IotTime].get_time()
async def get_timezone(self) -> dict:
"""Return timezone information."""
_LOGGER.warning(
"Use `timezone` property instead, this call will be removed in the future."
)
return await cast(Time, self.modules["time"]).get_timezone()
return await self.modules[Module.IotTime].get_timezone()
@property # type: ignore
@requires_update
@ -541,26 +541,26 @@ class IotDevice(Device):
def emeter_realtime(self) -> EmeterStatus:
"""Return current energy readings."""
self._verify_emeter()
return EmeterStatus(cast(Emeter, self.modules["emeter"]).realtime)
return EmeterStatus(self.modules[Module.IotEmeter].realtime)
async def get_emeter_realtime(self) -> EmeterStatus:
"""Retrieve current energy readings."""
self._verify_emeter()
return EmeterStatus(await cast(Emeter, self.modules["emeter"]).get_realtime())
return EmeterStatus(await self.modules[Module.IotEmeter].get_realtime())
@property
@requires_update
def emeter_today(self) -> float | None:
"""Return today's energy consumption in kWh."""
self._verify_emeter()
return cast(Emeter, self.modules["emeter"]).emeter_today
return self.modules[Module.IotEmeter].emeter_today
@property
@requires_update
def emeter_this_month(self) -> float | None:
"""Return this month's energy consumption in kWh."""
self._verify_emeter()
return cast(Emeter, self.modules["emeter"]).emeter_this_month
return self.modules[Module.IotEmeter].emeter_this_month
async def get_emeter_daily(
self, year: int | None = None, month: int | None = None, kwh: bool = True
@ -574,7 +574,7 @@ class IotDevice(Device):
:return: mapping of day of month to value
"""
self._verify_emeter()
return await cast(Emeter, self.modules["emeter"]).get_daystat(
return await self.modules[Module.IotEmeter].get_daystat(
year=year, month=month, kwh=kwh
)
@ -589,15 +589,13 @@ class IotDevice(Device):
:return: dict: mapping of month to value
"""
self._verify_emeter()
return await cast(Emeter, self.modules["emeter"]).get_monthstat(
year=year, kwh=kwh
)
return await self.modules[Module.IotEmeter].get_monthstat(year=year, kwh=kwh)
@requires_update
async def erase_emeter_stats(self) -> dict:
"""Erase energy meter statistics."""
self._verify_emeter()
return await cast(Emeter, self.modules["emeter"]).erase_stats()
return await self.modules[Module.IotEmeter].erase_stats()
@requires_update
async def current_consumption(self) -> float:

View File

@ -8,6 +8,7 @@ from typing import Any
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
@ -81,8 +82,8 @@ class IotDimmer(IotPlug):
self._device_type = DeviceType.Dimmer
# 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("motion", Motion(self, "smartlife.iot.PIR"))
self.add_module("ambient", AmbientLight(self, "smartlife.iot.LAS"))
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()

View File

@ -9,7 +9,7 @@ from ..protocol import BaseProtocol
from .effects import EFFECT_NAMES_V1
from .iotbulb import IotBulb
from .iotdevice import KasaException, requires_update
from .modules.lighteffectmodule import LightEffectModule
from .modules.lighteffect import LightEffect
class IotLightStrip(IotBulb):
@ -58,7 +58,7 @@ class IotLightStrip(IotBulb):
self._device_type = DeviceType.LightStrip
self.add_module(
Module.LightEffect,
LightEffectModule(self, "smartlife.iot.lighting_effect"),
LightEffect(self, "smartlife.iot.lighting_effect"),
)
@property # type: ignore

View File

@ -9,7 +9,7 @@ from ..deviceconfig import DeviceConfig
from ..module import Module
from ..protocol import BaseProtocol
from .iotdevice import IotDevice, requires_update
from .modules import Antitheft, Cloud, LedModule, Schedule, Time, Usage
from .modules import Antitheft, Cloud, Led, Schedule, Time, Usage
_LOGGER = logging.getLogger(__name__)
@ -53,12 +53,12 @@ class IotPlug(IotDevice):
) -> None:
super().__init__(host=host, config=config, protocol=protocol)
self._device_type = DeviceType.Plug
self.add_module("schedule", Schedule(self, "schedule"))
self.add_module("usage", Usage(self, "schedule"))
self.add_module("antitheft", Antitheft(self, "anti_theft"))
self.add_module("time", Time(self, "time"))
self.add_module("cloud", Cloud(self, "cnCloud"))
self.add_module(Module.Led, LedModule(self, "system"))
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"))
self.add_module(Module.IotTime, Time(self, "time"))
self.add_module(Module.IotCloud, Cloud(self, "cnCloud"))
self.add_module(Module.Led, Led(self, "system"))
@property # type: ignore
@requires_update

View File

@ -10,6 +10,7 @@ from typing import Any
from ..device_type import DeviceType
from ..deviceconfig import DeviceConfig
from ..exceptions import KasaException
from ..module import Module
from ..protocol import BaseProtocol
from .iotdevice import (
EmeterStatus,
@ -95,11 +96,11 @@ class IotStrip(IotDevice):
super().__init__(host=host, config=config, protocol=protocol)
self.emeter_type = "emeter"
self._device_type = DeviceType.Strip
self.add_module("antitheft", Antitheft(self, "anti_theft"))
self.add_module("schedule", Schedule(self, "schedule"))
self.add_module("usage", Usage(self, "schedule"))
self.add_module("time", Time(self, "time"))
self.add_module("countdown", Countdown(self, "countdown"))
self.add_module(Module.IotAntitheft, Antitheft(self, "anti_theft"))
self.add_module(Module.IotSchedule, Schedule(self, "schedule"))
self.add_module(Module.IotUsage, Usage(self, "schedule"))
self.add_module(Module.IotTime, Time(self, "time"))
self.add_module(Module.IotCountdown, Countdown(self, "countdown"))
@property # type: ignore
@requires_update

View File

@ -5,7 +5,8 @@ from .antitheft import Antitheft
from .cloud import Cloud
from .countdown import Countdown
from .emeter import Emeter
from .ledmodule import LedModule
from .led import Led
from .lighteffect import LightEffect
from .motion import Motion
from .rulemodule import Rule, RuleModule
from .schedule import Schedule
@ -18,7 +19,8 @@ __all__ = [
"Cloud",
"Countdown",
"Emeter",
"LedModule",
"Led",
"LightEffect",
"Motion",
"Rule",
"RuleModule",

View File

@ -2,11 +2,11 @@
from __future__ import annotations
from ...interfaces.led import Led
from ...interfaces.led import Led as LedInterface
from ..iotmodule import IotModule
class LedModule(IotModule, Led):
class Led(IotModule, LedInterface):
"""Implementation of led controls."""
def query(self) -> dict:

View File

@ -2,12 +2,12 @@
from __future__ import annotations
from ...interfaces.lighteffect import LightEffect
from ...interfaces.lighteffect import LightEffect as LightEffectInterface
from ..effects import EFFECT_MAPPING_V1, EFFECT_NAMES_V1
from ..iotmodule import IotModule
class LightEffectModule(IotModule, LightEffect):
class LightEffect(IotModule, LightEffectInterface):
"""Implementation of dynamic light effects."""
@property

View File

@ -15,7 +15,7 @@ from .feature import Feature
from .modulemapping import ModuleName
if TYPE_CHECKING:
from .device import Device as DeviceType # avoid name clash with Device module
from .device import Device
from .interfaces.led import Led
from .interfaces.lighteffect import LightEffect
from .iot import modules as iot
@ -34,8 +34,8 @@ class Module(ABC):
"""
# Common Modules
LightEffect: Final[ModuleName[LightEffect]] = ModuleName("LightEffectModule")
Led: Final[ModuleName[Led]] = ModuleName("LedModule")
LightEffect: Final[ModuleName[LightEffect]] = ModuleName("LightEffect")
Led: Final[ModuleName[Led]] = ModuleName("Led")
# IOT only Modules
IotAmbientLight: Final[ModuleName[iot.AmbientLight]] = ModuleName("ambient")
@ -49,43 +49,43 @@ class Module(ABC):
IotTime: Final[ModuleName[iot.Time]] = ModuleName("time")
# SMART only Modules
Alarm: Final[ModuleName[smart.AlarmModule]] = ModuleName("AlarmModule")
AutoOff: Final[ModuleName[smart.AutoOffModule]] = ModuleName("AutoOffModule")
Alarm: Final[ModuleName[smart.Alarm]] = ModuleName("Alarm")
AutoOff: Final[ModuleName[smart.AutoOff]] = ModuleName("AutoOff")
BatterySensor: Final[ModuleName[smart.BatterySensor]] = ModuleName("BatterySensor")
Brightness: Final[ModuleName[smart.Brightness]] = ModuleName("Brightness")
ChildDevice: Final[ModuleName[smart.ChildDeviceModule]] = ModuleName(
"ChildDeviceModule"
)
Cloud: Final[ModuleName[smart.CloudModule]] = ModuleName("CloudModule")
Color: Final[ModuleName[smart.ColorModule]] = ModuleName("ColorModule")
ColorTemp: Final[ModuleName[smart.ColorTemperatureModule]] = ModuleName(
"ColorTemperatureModule"
ChildDevice: Final[ModuleName[smart.ChildDevice]] = ModuleName("ChildDevice")
Cloud: Final[ModuleName[smart.Cloud]] = ModuleName("Cloud")
Color: Final[ModuleName[smart.Color]] = ModuleName("Color")
ColorTemperature: Final[ModuleName[smart.ColorTemperature]] = ModuleName(
"ColorTemperature"
)
ContactSensor: Final[ModuleName[smart.ContactSensor]] = ModuleName("ContactSensor")
Device: Final[ModuleName[smart.DeviceModule]] = ModuleName("DeviceModule")
Energy: Final[ModuleName[smart.EnergyModule]] = ModuleName("EnergyModule")
Fan: Final[ModuleName[smart.FanModule]] = ModuleName("FanModule")
DeviceModule: Final[ModuleName[smart.DeviceModule]] = ModuleName("DeviceModule")
Energy: Final[ModuleName[smart.Energy]] = ModuleName("Energy")
Fan: Final[ModuleName[smart.Fan]] = ModuleName("Fan")
Firmware: Final[ModuleName[smart.Firmware]] = ModuleName("Firmware")
FrostProtection: Final[ModuleName[smart.FrostProtectionModule]] = ModuleName(
"FrostProtectionModule"
FrostProtection: Final[ModuleName[smart.FrostProtection]] = ModuleName(
"FrostProtection"
)
Humidity: Final[ModuleName[smart.HumiditySensor]] = ModuleName("HumiditySensor")
LightTransition: Final[ModuleName[smart.LightTransitionModule]] = ModuleName(
"LightTransitionModule"
HumiditySensor: Final[ModuleName[smart.HumiditySensor]] = ModuleName(
"HumiditySensor"
)
Report: Final[ModuleName[smart.ReportModule]] = ModuleName("ReportModule")
Temperature: Final[ModuleName[smart.TemperatureSensor]] = ModuleName(
LightTransition: Final[ModuleName[smart.LightTransition]] = ModuleName(
"LightTransition"
)
ReportMode: Final[ModuleName[smart.ReportMode]] = ModuleName("ReportMode")
TemperatureSensor: Final[ModuleName[smart.TemperatureSensor]] = ModuleName(
"TemperatureSensor"
)
TemperatureSensor: Final[ModuleName[smart.TemperatureControl]] = ModuleName(
TemperatureControl: Final[ModuleName[smart.TemperatureControl]] = ModuleName(
"TemperatureControl"
)
Time: Final[ModuleName[smart.TimeModule]] = ModuleName("TimeModule")
Time: Final[ModuleName[smart.Time]] = ModuleName("Time")
WaterleakSensor: Final[ModuleName[smart.WaterleakSensor]] = ModuleName(
"WaterleakSensor"
)
def __init__(self, device: DeviceType, module: str):
def __init__(self, device: Device, module: str):
self._device = device
self._module = module
self._module_features: dict[str, Feature] = {}

View File

@ -1,51 +1,51 @@
"""Modules for SMART devices."""
from .alarmmodule import AlarmModule
from .autooffmodule import AutoOffModule
from .battery import BatterySensor
from .alarm import Alarm
from .autooff import AutoOff
from .batterysensor import BatterySensor
from .brightness import Brightness
from .childdevicemodule import ChildDeviceModule
from .cloudmodule import CloudModule
from .colormodule import ColorModule
from .colortemp import ColorTemperatureModule
from .contact import ContactSensor
from .childdevice import ChildDevice
from .cloud import Cloud
from .color import Color
from .colortemperature import ColorTemperature
from .contactsensor import ContactSensor
from .devicemodule import DeviceModule
from .energymodule import EnergyModule
from .fanmodule import FanModule
from .energy import Energy
from .fan import Fan
from .firmware import Firmware
from .frostprotection import FrostProtectionModule
from .humidity import HumiditySensor
from .ledmodule import LedModule
from .lighteffectmodule import LightEffectModule
from .lighttransitionmodule import LightTransitionModule
from .reportmodule import ReportModule
from .temperature import TemperatureSensor
from .frostprotection import FrostProtection
from .humiditysensor import HumiditySensor
from .led import Led
from .lighteffect import LightEffect
from .lighttransition import LightTransition
from .reportmode import ReportMode
from .temperaturecontrol import TemperatureControl
from .timemodule import TimeModule
from .waterleak import WaterleakSensor
from .temperaturesensor import TemperatureSensor
from .time import Time
from .waterleaksensor import WaterleakSensor
__all__ = [
"AlarmModule",
"TimeModule",
"EnergyModule",
"Alarm",
"Time",
"Energy",
"DeviceModule",
"ChildDeviceModule",
"ChildDevice",
"BatterySensor",
"HumiditySensor",
"TemperatureSensor",
"TemperatureControl",
"ReportModule",
"AutoOffModule",
"LedModule",
"ReportMode",
"AutoOff",
"Led",
"Brightness",
"FanModule",
"Fan",
"Firmware",
"CloudModule",
"LightEffectModule",
"LightTransitionModule",
"ColorTemperatureModule",
"ColorModule",
"Cloud",
"LightEffect",
"LightTransition",
"ColorTemperature",
"Color",
"WaterleakSensor",
"ContactSensor",
"FrostProtectionModule",
"FrostProtection",
]

View File

@ -6,7 +6,7 @@ from ...feature import Feature
from ..smartmodule import SmartModule
class AlarmModule(SmartModule):
class Alarm(SmartModule):
"""Implementation of alarm module."""
REQUIRED_COMPONENT = "alarm"

View File

@ -12,7 +12,7 @@ if TYPE_CHECKING:
from ..smartdevice import SmartDevice
class AutoOffModule(SmartModule):
class AutoOff(SmartModule):
"""Implementation of auto off module."""
REQUIRED_COMPONENT = "auto_off"

View File

@ -3,7 +3,7 @@
from ..smartmodule import SmartModule
class ChildDeviceModule(SmartModule):
class ChildDevice(SmartModule):
"""Implementation for child devices."""
REQUIRED_COMPONENT = "child_device"

View File

@ -12,7 +12,7 @@ if TYPE_CHECKING:
from ..smartdevice import SmartDevice
class CloudModule(SmartModule):
class Cloud(SmartModule):
"""Implementation of cloud module."""
QUERY_GETTER_NAME = "get_connect_cloud_state"

View File

@ -12,7 +12,7 @@ if TYPE_CHECKING:
from ..smartdevice import SmartDevice
class ColorModule(SmartModule):
class Color(SmartModule):
"""Implementation of color module."""
REQUIRED_COMPONENT = "color"

View File

@ -18,7 +18,7 @@ _LOGGER = logging.getLogger(__name__)
DEFAULT_TEMP_RANGE = [2500, 6500]
class ColorTemperatureModule(SmartModule):
class ColorTemperature(SmartModule):
"""Implementation of color temp module."""
REQUIRED_COMPONENT = "color_temperature"

View File

@ -12,7 +12,7 @@ if TYPE_CHECKING:
from ..smartdevice import SmartDevice
class EnergyModule(SmartModule):
class Energy(SmartModule):
"""Implementation of energy monitoring module."""
REQUIRED_COMPONENT = "energy_monitoring"

View File

@ -11,7 +11,7 @@ if TYPE_CHECKING:
from ..smartdevice import SmartDevice
class FanModule(SmartModule):
class Fan(SmartModule):
"""Implementation of fan_control module."""
REQUIRED_COMPONENT = "fan_control"

View File

@ -12,7 +12,7 @@ if TYPE_CHECKING:
from ..smartdevice import SmartDevice
class FrostProtectionModule(SmartModule):
class FrostProtection(SmartModule):
"""Implementation for frost protection module.
This basically turns the thermostat on and off.

View File

@ -2,11 +2,11 @@
from __future__ import annotations
from ...interfaces.led import Led
from ...interfaces.led import Led as LedInterface
from ..smartmodule import SmartModule
class LedModule(SmartModule, Led):
class Led(SmartModule, LedInterface):
"""Implementation of led controls."""
REQUIRED_COMPONENT = "led"

View File

@ -6,14 +6,14 @@ import base64
import copy
from typing import TYPE_CHECKING, Any
from ...interfaces.lighteffect import LightEffect
from ...interfaces.lighteffect import LightEffect as LightEffectInterface
from ..smartmodule import SmartModule
if TYPE_CHECKING:
from ..smartdevice import SmartDevice
class LightEffectModule(SmartModule, LightEffect):
class LightEffect(SmartModule, LightEffectInterface):
"""Implementation of dynamic light effects."""
REQUIRED_COMPONENT = "light_effect"

View File

@ -12,7 +12,7 @@ if TYPE_CHECKING:
from ..smartdevice import SmartDevice
class LightTransitionModule(SmartModule):
class LightTransition(SmartModule):
"""Implementation of gradual on/off."""
REQUIRED_COMPONENT = "on_off_gradually"

View File

@ -11,7 +11,7 @@ if TYPE_CHECKING:
from ..smartdevice import SmartDevice
class ReportModule(SmartModule):
class ReportMode(SmartModule):
"""Implementation of report module."""
REQUIRED_COMPONENT = "report_mode"

View File

@ -13,7 +13,7 @@ if TYPE_CHECKING:
from ..smartdevice import SmartDevice
class TimeModule(SmartModule):
class Time(SmartModule):
"""Implementation of device_local_time."""
REQUIRED_COMPONENT = "time"

View File

@ -20,15 +20,10 @@ from ..module import Module
from ..modulemapping import ModuleMapping, ModuleName
from ..smartprotocol import SmartProtocol
from .modules import (
Brightness,
CloudModule,
ColorModule,
ColorTemperatureModule,
Cloud,
DeviceModule,
EnergyModule,
FanModule,
Firmware,
TimeModule,
Time,
)
from .smartmodule import SmartModule
@ -39,7 +34,7 @@ _LOGGER = logging.getLogger(__name__)
# the child but only work on the parent. See longer note below in _initialize_modules.
# This list should be updated when creating new modules that could have the
# same issue, homekit perhaps?
WALL_SWITCH_PARENT_ONLY_MODULES = [DeviceModule, TimeModule, Firmware, CloudModule]
WALL_SWITCH_PARENT_ONLY_MODULES = [DeviceModule, Time, Firmware, Cloud]
# Device must go last as the other interfaces also inherit Device
@ -329,11 +324,11 @@ class SmartDevice(Bulb, Fan, Device):
self._add_feature(feat)
@property
def is_cloud_connected(self):
def is_cloud_connected(self) -> bool:
"""Returns if the device is connected to the cloud."""
if "CloudModule" not in self.modules:
if Module.Cloud not in self.modules:
return False
return self.modules["CloudModule"].is_connected
return self.modules[Module.Cloud].is_connected
@property
def sys_info(self) -> dict[str, Any]:
@ -357,10 +352,10 @@ class SmartDevice(Bulb, Fan, Device):
def time(self) -> datetime:
"""Return the time."""
# TODO: Default to parent's time module for child devices
if self._parent and "TimeModule" in self.modules:
_timemod = cast(TimeModule, self._parent.modules["TimeModule"]) # noqa: F405
if self._parent and Module.Time in self.modules:
_timemod = self._parent.modules[Module.Time]
else:
_timemod = cast(TimeModule, self.modules["TimeModule"]) # noqa: F405
_timemod = self.modules[Module.Time]
return _timemod.time
@ -437,7 +432,7 @@ class SmartDevice(Bulb, Fan, Device):
@property
def has_emeter(self) -> bool:
"""Return if the device has emeter."""
return "EnergyModule" in self.modules
return Module.Energy in self.modules
@property
def is_dimmer(self) -> bool:
@ -479,19 +474,19 @@ class SmartDevice(Bulb, Fan, Device):
@property
def emeter_realtime(self) -> EmeterStatus:
"""Get the emeter status."""
energy = cast(EnergyModule, self.modules["EnergyModule"])
energy = self.modules[Module.Energy]
return energy.emeter_realtime
@property
def emeter_this_month(self) -> float | None:
"""Get the emeter value for this month."""
energy = cast(EnergyModule, self.modules["EnergyModule"])
energy = self.modules[Module.Energy]
return energy.emeter_this_month
@property
def emeter_today(self) -> float | None:
"""Get the emeter value for today."""
energy = cast(EnergyModule, self.modules["EnergyModule"])
energy = self.modules[Module.Energy]
return energy.emeter_today
@property
@ -503,8 +498,7 @@ class SmartDevice(Bulb, Fan, Device):
):
return None
on_time = cast(float, on_time)
if (timemod := self.modules.get("TimeModule")) is not None:
timemod = cast(TimeModule, timemod) # noqa: F405
if (timemod := self.modules.get(Module.Time)) is not None:
return timemod.time - timedelta(seconds=on_time)
else: # We have no device time, use current local time.
return datetime.now().replace(microsecond=0) - timedelta(seconds=on_time)
@ -650,37 +644,37 @@ class SmartDevice(Bulb, Fan, Device):
@property
def is_fan(self) -> bool:
"""Return True if the device is a fan."""
return "FanModule" in self.modules
return Module.Fan in self.modules
@property
def fan_speed_level(self) -> int:
"""Return fan speed level."""
if not self.is_fan:
raise KasaException("Device is not a Fan")
return cast(FanModule, self.modules["FanModule"]).fan_speed_level
return self.modules[Module.Fan].fan_speed_level
async def set_fan_speed_level(self, level: int):
"""Set fan speed level."""
if not self.is_fan:
raise KasaException("Device is not a Fan")
await cast(FanModule, self.modules["FanModule"]).set_fan_speed_level(level)
await self.modules[Module.Fan].set_fan_speed_level(level)
# Bulb interface methods
@property
def is_color(self) -> bool:
"""Whether the bulb supports color changes."""
return "ColorModule" in self.modules
return Module.Color in self.modules
@property
def is_dimmable(self) -> bool:
"""Whether the bulb supports brightness changes."""
return "Brightness" in self.modules
return Module.Brightness in self.modules
@property
def is_variable_color_temp(self) -> bool:
"""Whether the bulb supports color temperature changes."""
return "ColorTemperatureModule" in self.modules
return Module.ColorTemperature in self.modules
@property
def valid_temperature_range(self) -> ColorTempRange:
@ -691,9 +685,7 @@ class SmartDevice(Bulb, Fan, Device):
if not self.is_variable_color_temp:
raise KasaException("Color temperature not supported")
return cast(
ColorTemperatureModule, self.modules["ColorTemperatureModule"]
).valid_temperature_range
return self.modules[Module.ColorTemperature].valid_temperature_range
@property
def hsv(self) -> HSV:
@ -704,7 +696,7 @@ class SmartDevice(Bulb, Fan, Device):
if not self.is_color:
raise KasaException("Bulb does not support color.")
return cast(ColorModule, self.modules["ColorModule"]).hsv
return self.modules[Module.Color].hsv
@property
def color_temp(self) -> int:
@ -712,9 +704,7 @@ class SmartDevice(Bulb, Fan, Device):
if not self.is_variable_color_temp:
raise KasaException("Bulb does not support colortemp.")
return cast(
ColorTemperatureModule, self.modules["ColorTemperatureModule"]
).color_temp
return self.modules[Module.ColorTemperature].color_temp
@property
def brightness(self) -> int:
@ -722,7 +712,7 @@ class SmartDevice(Bulb, Fan, Device):
if not self.is_dimmable: # pragma: no cover
raise KasaException("Bulb is not dimmable.")
return cast(Brightness, self.modules["Brightness"]).brightness
return self.modules[Module.Brightness].brightness
async def set_hsv(
self,
@ -744,9 +734,7 @@ class SmartDevice(Bulb, Fan, Device):
if not self.is_color:
raise KasaException("Bulb does not support color.")
return await cast(ColorModule, self.modules["ColorModule"]).set_hsv(
hue, saturation, value
)
return await self.modules[Module.Color].set_hsv(hue, saturation, value)
async def set_color_temp(
self, temp: int, *, brightness=None, transition: int | None = None
@ -760,9 +748,7 @@ class SmartDevice(Bulb, Fan, Device):
"""
if not self.is_variable_color_temp:
raise KasaException("Bulb does not support colortemp.")
return await cast(
ColorTemperatureModule, self.modules["ColorTemperatureModule"]
).set_color_temp(temp)
return await self.modules[Module.ColorTemperature].set_color_temp(temp)
async def set_brightness(
self, brightness: int, *, transition: int | None = None
@ -777,9 +763,7 @@ class SmartDevice(Bulb, Fan, Device):
if not self.is_dimmable: # pragma: no cover
raise KasaException("Bulb is not dimmable.")
return await cast(Brightness, self.modules["Brightness"]).set_brightness(
brightness
)
return await self.modules[Module.Brightness].set_brightness(brightness)
@property
def presets(self) -> list[BulbPreset]:
@ -789,4 +773,4 @@ class SmartDevice(Bulb, Fan, Device):
@property
def has_effects(self) -> bool:
"""Return True if the device supports effects."""
return "LightEffectModule" in self.modules
return Module.LightEffect in self.modules

View File

@ -6,7 +6,7 @@ import pytest
from pytest_mock import MockerFixture
from kasa import Device, Feature, Module
from kasa.smart.modules import LightEffectModule
from kasa.smart.modules import LightEffect
from kasa.tests.device_fixtures import parametrize
light_effect = parametrize(
@ -18,7 +18,7 @@ light_effect = parametrize(
async def test_light_effect(dev: Device, mocker: MockerFixture):
"""Test light effect."""
light_effect = dev.modules.get(Module.LightEffect)
assert isinstance(light_effect, LightEffectModule)
assert isinstance(light_effect, LightEffect)
feature = light_effect._module_features["light_effect"]
assert feature.type == Feature.Type.Choice
@ -28,7 +28,7 @@ async def test_light_effect(dev: Device, mocker: MockerFixture):
assert feature.choices
for effect in chain(reversed(feature.choices), feature.choices):
await light_effect.set_effect(effect)
enable = effect != LightEffectModule.LIGHT_EFFECTS_OFF
enable = effect != LightEffect.LIGHT_EFFECTS_OFF
params: dict[str, bool | str] = {"enable": enable}
if enable:
params["id"] = light_effect._scenes_names_to_id[effect]

View File

@ -737,7 +737,7 @@ async def test_feature_set(mocker, runner):
dummy_device = await get_device_for_fixture_protocol(
"P300(EU)_1.0_1.0.13.json", "SMART"
)
led_setter = mocker.patch("kasa.smart.modules.ledmodule.LedModule.set_led")
led_setter = mocker.patch("kasa.smart.modules.led.Led.set_led")
mocker.patch("kasa.discover.Discover.discover_single", return_value=dummy_device)
res = await runner.invoke(

View File

@ -127,21 +127,21 @@ async def test_get_modules():
dummy_device = await get_device_for_fixture_protocol(
"KS240(US)_1.0_1.0.5.json", "SMART"
)
from kasa.smart.modules import CloudModule
from kasa.smart.modules import Cloud
# Modules on device
module = dummy_device.modules.get("CloudModule")
module = dummy_device.modules.get("Cloud")
assert module
assert module._device == dummy_device
assert isinstance(module, CloudModule)
assert isinstance(module, Cloud)
module = dummy_device.modules.get(Module.Cloud)
assert module
assert module._device == dummy_device
assert isinstance(module, CloudModule)
assert isinstance(module, Cloud)
# Modules on child
module = dummy_device.modules.get("FanModule")
module = dummy_device.modules.get("Fan")
assert module
assert module._device != dummy_device
assert module._device._parent == dummy_device