Add Fan interface for SMART devices (#873)

Enables the Fan interface for devices supporting that component.
Currently the only device with a fan is the ks240 which implements it as
a child device. This PR adds a method `get_module` to search the child
device for modules if it is a WallSwitch device type.
This commit is contained in:
Steven B
2024-04-30 17:42:53 +01:00
committed by GitHub
parent 7db989e2ec
commit 16f17a7729
6 changed files with 124 additions and 18 deletions

View File

@@ -14,6 +14,7 @@ from ..device_type import DeviceType
from ..deviceconfig import DeviceConfig
from ..emeterstatus import EmeterStatus
from ..exceptions import AuthenticationError, DeviceError, KasaException, SmartErrorCode
from ..fan import Fan
from ..feature import Feature
from ..smartprotocol import SmartProtocol
from .modules import (
@@ -23,6 +24,7 @@ from .modules import (
ColorTemperatureModule,
DeviceModule,
EnergyModule,
FanModule,
Firmware,
TimeModule,
)
@@ -36,7 +38,7 @@ if TYPE_CHECKING:
# 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] # noqa: F405
WALL_SWITCH_PARENT_ONLY_MODULES = [DeviceModule, TimeModule, Firmware, CloudModule]
AVAILABLE_BULB_EFFECTS = {
"L1": "Party",
@@ -44,7 +46,7 @@ AVAILABLE_BULB_EFFECTS = {
}
class SmartDevice(Device, Bulb):
class SmartDevice(Device, Bulb, Fan):
"""Base class to represent a SMART protocol based device."""
def __init__(
@@ -221,9 +223,6 @@ class SmartDevice(Device, Bulb):
if await module._check_supported():
self._modules[module.name] = module
if self._exposes_child_modules:
self._modules.update(**child_modules_to_skip)
async def _initialize_features(self):
"""Initialize device features."""
self._add_feature(
@@ -309,6 +308,16 @@ class SmartDevice(Device, Bulb):
for feat in module._module_features.values():
self._add_feature(feat)
def get_module(self, module_name) -> SmartModule | None:
"""Return the module from the device modules or None if not present."""
if module_name in self.modules:
return self.modules[module_name]
elif self._exposes_child_modules:
for child in self._children.values():
if module_name in child.modules:
return child.modules[module_name]
return None
@property
def is_cloud_connected(self):
"""Returns if the device is connected to the cloud."""
@@ -460,19 +469,19 @@ class SmartDevice(Device, Bulb):
@property
def emeter_realtime(self) -> EmeterStatus:
"""Get the emeter status."""
energy = cast(EnergyModule, self.modules["EnergyModule"]) # noqa: F405
energy = cast(EnergyModule, self.modules["EnergyModule"])
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"]) # noqa: F405
energy = cast(EnergyModule, self.modules["EnergyModule"])
return energy.emeter_this_month
@property
def emeter_today(self) -> float | None:
"""Get the emeter value for today."""
energy = cast(EnergyModule, self.modules["EnergyModule"]) # noqa: F405
energy = cast(EnergyModule, self.modules["EnergyModule"])
return energy.emeter_today
@property
@@ -635,6 +644,26 @@ class SmartDevice(Device, Bulb):
_LOGGER.warning("Unknown device type, falling back to plug")
return DeviceType.Plug
# Fan interface methods
@property
def is_fan(self) -> bool:
"""Return True if the device is a fan."""
return "FanModule" 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
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)
# Bulb interface methods
@property