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 ..device_type import DeviceType
from ..deviceconfig import DeviceConfig from ..deviceconfig import DeviceConfig
from ..feature import Feature from ..feature import Feature
from ..module import Module
from ..protocol import BaseProtocol from ..protocol import BaseProtocol
from .iotdevice import IotDevice, KasaException, requires_update from .iotdevice import IotDevice, KasaException, requires_update
from .modules import Antitheft, Cloud, Countdown, Emeter, Schedule, Time, Usage from .modules import Antitheft, Cloud, Countdown, Emeter, Schedule, Time, Usage
@ -198,13 +199,17 @@ class IotBulb(IotDevice, Bulb):
) -> None: ) -> None:
super().__init__(host=host, config=config, protocol=protocol) super().__init__(host=host, config=config, protocol=protocol)
self._device_type = DeviceType.Bulb self._device_type = DeviceType.Bulb
self.add_module("schedule", Schedule(self, "smartlife.iot.common.schedule")) self.add_module(
self.add_module("usage", Usage(self, "smartlife.iot.common.schedule")) Module.IotSchedule, Schedule(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(Module.IotUsage, Usage(self, "smartlife.iot.common.schedule"))
self.add_module("emeter", Emeter(self, self.emeter_type)) self.add_module(
self.add_module("countdown", Countdown(self, "countdown")) Module.IotAntitheft, Antitheft(self, "smartlife.iot.common.anti_theft")
self.add_module("cloud", Cloud(self, "smartlife.iot.common.cloud")) )
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): async def _initialize_features(self):
await super()._initialize_features() await super()._initialize_features()

View File

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

View File

@ -8,6 +8,7 @@ from typing import Any
from ..device_type import DeviceType from ..device_type import DeviceType
from ..deviceconfig import DeviceConfig from ..deviceconfig import DeviceConfig
from ..feature import Feature from ..feature import Feature
from ..module import Module
from ..protocol import BaseProtocol from ..protocol import BaseProtocol
from .iotdevice import KasaException, requires_update from .iotdevice import KasaException, requires_update
from .iotplug import IotPlug from .iotplug import IotPlug
@ -81,8 +82,8 @@ class IotDimmer(IotPlug):
self._device_type = DeviceType.Dimmer 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 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 # 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(Module.IotMotion, Motion(self, "smartlife.iot.PIR"))
self.add_module("ambient", AmbientLight(self, "smartlife.iot.LAS")) self.add_module(Module.IotAmbientLight, AmbientLight(self, "smartlife.iot.LAS"))
async def _initialize_features(self): async def _initialize_features(self):
await super()._initialize_features() await super()._initialize_features()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,12 +2,12 @@
from __future__ import annotations 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 ..effects import EFFECT_MAPPING_V1, EFFECT_NAMES_V1
from ..iotmodule import IotModule from ..iotmodule import IotModule
class LightEffectModule(IotModule, LightEffect): class LightEffect(IotModule, LightEffectInterface):
"""Implementation of dynamic light effects.""" """Implementation of dynamic light effects."""
@property @property

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,15 +20,10 @@ from ..module import Module
from ..modulemapping import ModuleMapping, ModuleName from ..modulemapping import ModuleMapping, ModuleName
from ..smartprotocol import SmartProtocol from ..smartprotocol import SmartProtocol
from .modules import ( from .modules import (
Brightness, Cloud,
CloudModule,
ColorModule,
ColorTemperatureModule,
DeviceModule, DeviceModule,
EnergyModule,
FanModule,
Firmware, Firmware,
TimeModule, Time,
) )
from .smartmodule import SmartModule 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. # 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 # This list should be updated when creating new modules that could have the
# same issue, homekit perhaps? # 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 # Device must go last as the other interfaces also inherit Device
@ -329,11 +324,11 @@ class SmartDevice(Bulb, Fan, Device):
self._add_feature(feat) self._add_feature(feat)
@property @property
def is_cloud_connected(self): def is_cloud_connected(self) -> bool:
"""Returns if the device is connected to the cloud.""" """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 False
return self.modules["CloudModule"].is_connected return self.modules[Module.Cloud].is_connected
@property @property
def sys_info(self) -> dict[str, Any]: def sys_info(self) -> dict[str, Any]:
@ -357,10 +352,10 @@ class SmartDevice(Bulb, Fan, Device):
def time(self) -> datetime: def time(self) -> datetime:
"""Return the time.""" """Return the time."""
# TODO: Default to parent's time module for child devices # TODO: Default to parent's time module for child devices
if self._parent and "TimeModule" in self.modules: if self._parent and Module.Time in self.modules:
_timemod = cast(TimeModule, self._parent.modules["TimeModule"]) # noqa: F405 _timemod = self._parent.modules[Module.Time]
else: else:
_timemod = cast(TimeModule, self.modules["TimeModule"]) # noqa: F405 _timemod = self.modules[Module.Time]
return _timemod.time return _timemod.time
@ -437,7 +432,7 @@ class SmartDevice(Bulb, Fan, Device):
@property @property
def has_emeter(self) -> bool: def has_emeter(self) -> bool:
"""Return if the device has emeter.""" """Return if the device has emeter."""
return "EnergyModule" in self.modules return Module.Energy in self.modules
@property @property
def is_dimmer(self) -> bool: def is_dimmer(self) -> bool:
@ -479,19 +474,19 @@ class SmartDevice(Bulb, Fan, Device):
@property @property
def emeter_realtime(self) -> EmeterStatus: def emeter_realtime(self) -> EmeterStatus:
"""Get the emeter status.""" """Get the emeter status."""
energy = cast(EnergyModule, self.modules["EnergyModule"]) energy = self.modules[Module.Energy]
return energy.emeter_realtime return energy.emeter_realtime
@property @property
def emeter_this_month(self) -> float | None: def emeter_this_month(self) -> float | None:
"""Get the emeter value for this month.""" """Get the emeter value for this month."""
energy = cast(EnergyModule, self.modules["EnergyModule"]) energy = self.modules[Module.Energy]
return energy.emeter_this_month return energy.emeter_this_month
@property @property
def emeter_today(self) -> float | None: def emeter_today(self) -> float | None:
"""Get the emeter value for today.""" """Get the emeter value for today."""
energy = cast(EnergyModule, self.modules["EnergyModule"]) energy = self.modules[Module.Energy]
return energy.emeter_today return energy.emeter_today
@property @property
@ -503,8 +498,7 @@ class SmartDevice(Bulb, Fan, Device):
): ):
return None return None
on_time = cast(float, on_time) on_time = cast(float, on_time)
if (timemod := self.modules.get("TimeModule")) is not None: if (timemod := self.modules.get(Module.Time)) is not None:
timemod = cast(TimeModule, timemod) # noqa: F405
return timemod.time - timedelta(seconds=on_time) return timemod.time - timedelta(seconds=on_time)
else: # We have no device time, use current local time. else: # We have no device time, use current local time.
return datetime.now().replace(microsecond=0) - timedelta(seconds=on_time) return datetime.now().replace(microsecond=0) - timedelta(seconds=on_time)
@ -650,37 +644,37 @@ class SmartDevice(Bulb, Fan, Device):
@property @property
def is_fan(self) -> bool: def is_fan(self) -> bool:
"""Return True if the device is a fan.""" """Return True if the device is a fan."""
return "FanModule" in self.modules return Module.Fan in self.modules
@property @property
def fan_speed_level(self) -> int: def fan_speed_level(self) -> int:
"""Return fan speed level.""" """Return fan speed level."""
if not self.is_fan: if not self.is_fan:
raise KasaException("Device is not a 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): async def set_fan_speed_level(self, level: int):
"""Set fan speed level.""" """Set fan speed level."""
if not self.is_fan: if not self.is_fan:
raise KasaException("Device is not a 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 # Bulb interface methods
@property @property
def is_color(self) -> bool: def is_color(self) -> bool:
"""Whether the bulb supports color changes.""" """Whether the bulb supports color changes."""
return "ColorModule" in self.modules return Module.Color in self.modules
@property @property
def is_dimmable(self) -> bool: def is_dimmable(self) -> bool:
"""Whether the bulb supports brightness changes.""" """Whether the bulb supports brightness changes."""
return "Brightness" in self.modules return Module.Brightness in self.modules
@property @property
def is_variable_color_temp(self) -> bool: def is_variable_color_temp(self) -> bool:
"""Whether the bulb supports color temperature changes.""" """Whether the bulb supports color temperature changes."""
return "ColorTemperatureModule" in self.modules return Module.ColorTemperature in self.modules
@property @property
def valid_temperature_range(self) -> ColorTempRange: def valid_temperature_range(self) -> ColorTempRange:
@ -691,9 +685,7 @@ class SmartDevice(Bulb, Fan, Device):
if not self.is_variable_color_temp: if not self.is_variable_color_temp:
raise KasaException("Color temperature not supported") raise KasaException("Color temperature not supported")
return cast( return self.modules[Module.ColorTemperature].valid_temperature_range
ColorTemperatureModule, self.modules["ColorTemperatureModule"]
).valid_temperature_range
@property @property
def hsv(self) -> HSV: def hsv(self) -> HSV:
@ -704,7 +696,7 @@ class SmartDevice(Bulb, Fan, Device):
if not self.is_color: if not self.is_color:
raise KasaException("Bulb does not support color.") raise KasaException("Bulb does not support color.")
return cast(ColorModule, self.modules["ColorModule"]).hsv return self.modules[Module.Color].hsv
@property @property
def color_temp(self) -> int: def color_temp(self) -> int:
@ -712,9 +704,7 @@ class SmartDevice(Bulb, Fan, Device):
if not self.is_variable_color_temp: if not self.is_variable_color_temp:
raise KasaException("Bulb does not support colortemp.") raise KasaException("Bulb does not support colortemp.")
return cast( return self.modules[Module.ColorTemperature].color_temp
ColorTemperatureModule, self.modules["ColorTemperatureModule"]
).color_temp
@property @property
def brightness(self) -> int: def brightness(self) -> int:
@ -722,7 +712,7 @@ class SmartDevice(Bulb, Fan, Device):
if not self.is_dimmable: # pragma: no cover if not self.is_dimmable: # pragma: no cover
raise KasaException("Bulb is not dimmable.") raise KasaException("Bulb is not dimmable.")
return cast(Brightness, self.modules["Brightness"]).brightness return self.modules[Module.Brightness].brightness
async def set_hsv( async def set_hsv(
self, self,
@ -744,9 +734,7 @@ class SmartDevice(Bulb, Fan, Device):
if not self.is_color: if not self.is_color:
raise KasaException("Bulb does not support color.") raise KasaException("Bulb does not support color.")
return await cast(ColorModule, self.modules["ColorModule"]).set_hsv( return await self.modules[Module.Color].set_hsv(hue, saturation, value)
hue, saturation, value
)
async def set_color_temp( async def set_color_temp(
self, temp: int, *, brightness=None, transition: int | None = None 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: if not self.is_variable_color_temp:
raise KasaException("Bulb does not support colortemp.") raise KasaException("Bulb does not support colortemp.")
return await cast( return await self.modules[Module.ColorTemperature].set_color_temp(temp)
ColorTemperatureModule, self.modules["ColorTemperatureModule"]
).set_color_temp(temp)
async def set_brightness( async def set_brightness(
self, brightness: int, *, transition: int | None = None self, brightness: int, *, transition: int | None = None
@ -777,9 +763,7 @@ class SmartDevice(Bulb, Fan, Device):
if not self.is_dimmable: # pragma: no cover if not self.is_dimmable: # pragma: no cover
raise KasaException("Bulb is not dimmable.") raise KasaException("Bulb is not dimmable.")
return await cast(Brightness, self.modules["Brightness"]).set_brightness( return await self.modules[Module.Brightness].set_brightness(brightness)
brightness
)
@property @property
def presets(self) -> list[BulbPreset]: def presets(self) -> list[BulbPreset]:
@ -789,4 +773,4 @@ class SmartDevice(Bulb, Fan, Device):
@property @property
def has_effects(self) -> bool: def has_effects(self) -> bool:
"""Return True if the device supports effects.""" """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 pytest_mock import MockerFixture
from kasa import Device, Feature, Module 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 from kasa.tests.device_fixtures import parametrize
light_effect = parametrize( light_effect = parametrize(
@ -18,7 +18,7 @@ light_effect = parametrize(
async def test_light_effect(dev: Device, mocker: MockerFixture): async def test_light_effect(dev: Device, mocker: MockerFixture):
"""Test light effect.""" """Test light effect."""
light_effect = dev.modules.get(Module.LightEffect) light_effect = dev.modules.get(Module.LightEffect)
assert isinstance(light_effect, LightEffectModule) assert isinstance(light_effect, LightEffect)
feature = light_effect._module_features["light_effect"] feature = light_effect._module_features["light_effect"]
assert feature.type == Feature.Type.Choice assert feature.type == Feature.Type.Choice
@ -28,7 +28,7 @@ async def test_light_effect(dev: Device, mocker: MockerFixture):
assert feature.choices assert feature.choices
for effect in chain(reversed(feature.choices), feature.choices): for effect in chain(reversed(feature.choices), feature.choices):
await light_effect.set_effect(effect) 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} params: dict[str, bool | str] = {"enable": enable}
if enable: if enable:
params["id"] = light_effect._scenes_names_to_id[effect] 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( dummy_device = await get_device_for_fixture_protocol(
"P300(EU)_1.0_1.0.13.json", "SMART" "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) mocker.patch("kasa.discover.Discover.discover_single", return_value=dummy_device)
res = await runner.invoke( res = await runner.invoke(

View File

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