Add fan module (#764)

Co-authored-by: Steven B <51370195+sdb9696@users.noreply.github.com>
This commit is contained in:
Teemu R 2024-04-17 12:07:16 +02:00 committed by GitHub
parent da441bc697
commit 700643d3cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 120 additions and 7 deletions

View File

@ -16,6 +16,7 @@ class DeviceType(Enum):
LightStrip = "lightstrip" LightStrip = "lightstrip"
Sensor = "sensor" Sensor = "sensor"
Hub = "hub" Hub = "hub"
Fan = "fan"
Unknown = "unknown" Unknown = "unknown"
@staticmethod @staticmethod

View File

@ -9,6 +9,7 @@ from .cloudmodule import CloudModule
from .colortemp import ColorTemperatureModule from .colortemp import ColorTemperatureModule
from .devicemodule import DeviceModule from .devicemodule import DeviceModule
from .energymodule import EnergyModule from .energymodule import EnergyModule
from .fanmodule import FanModule
from .firmware import Firmware from .firmware import Firmware
from .humidity import HumiditySensor from .humidity import HumiditySensor
from .ledmodule import LedModule from .ledmodule import LedModule
@ -30,6 +31,7 @@ __all__ = [
"AutoOffModule", "AutoOffModule",
"LedModule", "LedModule",
"Brightness", "Brightness",
"FanModule",
"Firmware", "Firmware",
"CloudModule", "CloudModule",
"LightTransitionModule", "LightTransitionModule",

View File

@ -0,0 +1,66 @@
"""Implementation of fan_control module."""
from typing import TYPE_CHECKING, Dict
from ...feature import Feature, FeatureType
from ..smartmodule import SmartModule
if TYPE_CHECKING:
from ..smartdevice import SmartDevice
class FanModule(SmartModule):
"""Implementation of fan_control module."""
REQUIRED_COMPONENT = "fan_control"
def __init__(self, device: "SmartDevice", module: str):
super().__init__(device, module)
self._add_feature(
Feature(
device,
"Fan speed level",
container=self,
attribute_getter="fan_speed_level",
attribute_setter="set_fan_speed_level",
icon="mdi:fan",
type=FeatureType.Number,
minimum_value=1,
maximum_value=4,
)
)
self._add_feature(
Feature(
device,
"Fan sleep mode",
container=self,
attribute_getter="sleep_mode",
attribute_setter="set_sleep_mode",
icon="mdi:sleep",
type=FeatureType.Switch
)
)
def query(self) -> Dict:
"""Query to execute during the update cycle."""
return {}
@property
def fan_speed_level(self) -> int:
"""Return fan speed level."""
return self.data["fan_speed_level"]
async def set_fan_speed_level(self, level: int):
"""Set fan speed level."""
if level < 1 or level > 4:
raise ValueError("Invalid level, should be in range 1-4.")
return await self.call("set_device_info", {"fan_speed_level": level})
@property
def sleep_mode(self) -> bool:
"""Return sleep mode status."""
return self.data["fan_sleep_mode_on"]
async def set_sleep_mode(self, on: bool):
"""Set sleep mode."""
return await self.call("set_device_info", {"fan_sleep_mode_on": on})

View File

@ -49,6 +49,8 @@ class SmartChildDevice(SmartDevice):
child_device_map = { child_device_map = {
"plug.powerstrip.sub-plug": DeviceType.Plug, "plug.powerstrip.sub-plug": DeviceType.Plug,
"subg.trigger.temp-hmdt-sensor": DeviceType.Sensor, "subg.trigger.temp-hmdt-sensor": DeviceType.Sensor,
"kasa.switch.outlet.sub-fan": DeviceType.Fan,
"kasa.switch.outlet.sub-dimmer": DeviceType.Dimmer,
} }
dev_type = child_device_map.get(self.sys_info["category"]) dev_type = child_device_map.get(self.sys_info["category"])
if dev_type is None: if dev_type is None:

View File

@ -314,12 +314,11 @@ class SmartDevice(Device):
return self._last_update return self._last_update
def _update_internal_state(self, info): def _update_internal_state(self, info):
"""Update internal state. """Update the internal info state.
This is used by the parent to push updates to its children This is used by the parent to push updates to its children.
""" """
# TODO: cleanup the _last_update, _info mess. self._info = info
self._last_update = self._info = info
async def _query_helper( async def _query_helper(
self, method: str, params: Optional[Dict] = None, child_ids=None self, method: str, params: Optional[Dict] = None, child_ids=None

View File

@ -62,7 +62,7 @@ class SmartModule(Module):
q = self.query() q = self.query()
if not q: if not q:
return dev.internal_state["get_device_info"] return dev.sys_info
q_keys = list(q.keys()) q_keys = list(q.keys())
query_key = q_keys[0] query_key = q_keys[0]

View File

@ -0,0 +1,43 @@
from pytest_mock import MockerFixture
from kasa import SmartDevice
from kasa.smart.modules import FanModule
from kasa.tests.device_fixtures import parametrize
fan = parametrize(
"has fan", component_filter="fan_control", protocol_filter={"SMART.CHILD"}
)
@fan
async def test_fan_speed(dev: SmartDevice, mocker: MockerFixture):
"""Test fan speed feature."""
fan: FanModule = dev.modules["FanModule"]
level_feature = fan._module_features["fan_speed_level"]
assert level_feature.minimum_value <= level_feature.value <= level_feature.maximum_value
call = mocker.spy(fan, "call")
await fan.set_fan_speed_level(3)
call.assert_called_with("set_device_info", {"fan_sleep_level": 3})
await dev.update()
assert fan.fan_speed_level == 3
assert level_feature.value == 3
@fan
async def test_sleep_mode(dev: SmartDevice, mocker: MockerFixture):
"""Test sleep mode feature."""
fan: FanModule = dev.modules["FanModule"]
sleep_feature = fan._module_features["fan_sleep_mode"]
assert isinstance(sleep_feature.value, bool)
call = mocker.spy(fan, "call")
await fan.set_sleep_mode(True)
call.assert_called_with("set_device_info", {"fan_sleep_mode_on": True})
await dev.update()
assert fan.sleep_mode is True
assert sleep_feature.value is True

View File

@ -32,8 +32,8 @@ async def test_childdevice_update(dev, dummy_protocol, mocker):
await dev.update() await dev.update()
assert dev._last_update != first._last_update assert dev._info != first._info
assert child_list[0] == first._last_update assert child_list[0] == first._info
@strip_smart @strip_smart