mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-06-30 16:49:49 +00:00
Add mop module (#1456)
Adds the following new features: a setting to control water level and a sensor if the mop is attached: ``` Mop water level (mop_waterlevel): *Disable* Low Medium High Mop attached (mop_attached): True ```
This commit is contained in:
parent
bc97c0794a
commit
17356c10f1
@ -165,6 +165,7 @@ class Module(ABC):
|
|||||||
Clean: Final[ModuleName[smart.Clean]] = ModuleName("Clean")
|
Clean: Final[ModuleName[smart.Clean]] = ModuleName("Clean")
|
||||||
Dustbin: Final[ModuleName[smart.Dustbin]] = ModuleName("Dustbin")
|
Dustbin: Final[ModuleName[smart.Dustbin]] = ModuleName("Dustbin")
|
||||||
Speaker: Final[ModuleName[smart.Speaker]] = ModuleName("Speaker")
|
Speaker: Final[ModuleName[smart.Speaker]] = ModuleName("Speaker")
|
||||||
|
Mop: Final[ModuleName[smart.Mop]] = ModuleName("Mop")
|
||||||
|
|
||||||
def __init__(self, device: Device, module: str) -> None:
|
def __init__(self, device: Device, module: str) -> None:
|
||||||
self._device = device
|
self._device = device
|
||||||
|
@ -27,6 +27,7 @@ from .lightpreset import LightPreset
|
|||||||
from .lightstripeffect import LightStripEffect
|
from .lightstripeffect import LightStripEffect
|
||||||
from .lighttransition import LightTransition
|
from .lighttransition import LightTransition
|
||||||
from .matter import Matter
|
from .matter import Matter
|
||||||
|
from .mop import Mop
|
||||||
from .motionsensor import MotionSensor
|
from .motionsensor import MotionSensor
|
||||||
from .overheatprotection import OverheatProtection
|
from .overheatprotection import OverheatProtection
|
||||||
from .reportmode import ReportMode
|
from .reportmode import ReportMode
|
||||||
@ -76,4 +77,5 @@ __all__ = [
|
|||||||
"HomeKit",
|
"HomeKit",
|
||||||
"Matter",
|
"Matter",
|
||||||
"Dustbin",
|
"Dustbin",
|
||||||
|
"Mop",
|
||||||
]
|
]
|
||||||
|
90
kasa/smart/modules/mop.py
Normal file
90
kasa/smart/modules/mop.py
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
"""Implementation of vacuum mop."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from enum import IntEnum
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from ...feature import Feature
|
||||||
|
from ...module import FeatureAttribute
|
||||||
|
from ..smartmodule import SmartModule
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Waterlevel(IntEnum):
|
||||||
|
"""Water level for mopping."""
|
||||||
|
|
||||||
|
Disable = 0
|
||||||
|
Low = 1
|
||||||
|
Medium = 2
|
||||||
|
High = 3
|
||||||
|
|
||||||
|
|
||||||
|
class Mop(SmartModule):
|
||||||
|
"""Implementation of vacuum mop."""
|
||||||
|
|
||||||
|
REQUIRED_COMPONENT = "mop"
|
||||||
|
|
||||||
|
def _initialize_features(self) -> None:
|
||||||
|
"""Initialize features."""
|
||||||
|
self._add_feature(
|
||||||
|
Feature(
|
||||||
|
self._device,
|
||||||
|
id="mop_attached",
|
||||||
|
name="Mop attached",
|
||||||
|
container=self,
|
||||||
|
icon="mdi:square-rounded",
|
||||||
|
attribute_getter="mop_attached",
|
||||||
|
category=Feature.Category.Info,
|
||||||
|
type=Feature.BinarySensor,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self._add_feature(
|
||||||
|
Feature(
|
||||||
|
self._device,
|
||||||
|
id="mop_waterlevel",
|
||||||
|
name="Mop water level",
|
||||||
|
container=self,
|
||||||
|
attribute_getter="waterlevel",
|
||||||
|
attribute_setter="set_waterlevel",
|
||||||
|
icon="mdi:water",
|
||||||
|
choices_getter=lambda: list(Waterlevel.__members__),
|
||||||
|
category=Feature.Category.Config,
|
||||||
|
type=Feature.Type.Choice,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def query(self) -> dict:
|
||||||
|
"""Query to execute during the update cycle."""
|
||||||
|
return {
|
||||||
|
"getMopState": {},
|
||||||
|
"getCleanAttr": {"type": "global"},
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mop_attached(self) -> bool:
|
||||||
|
"""Return True if mop is attached."""
|
||||||
|
return self.data["getMopState"]["mop_state"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _settings(self) -> dict:
|
||||||
|
"""Return settings settings."""
|
||||||
|
return self.data["getCleanAttr"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def waterlevel(self) -> Annotated[str, FeatureAttribute()]:
|
||||||
|
"""Return water level."""
|
||||||
|
return Waterlevel(int(self._settings["cistern"])).name
|
||||||
|
|
||||||
|
async def set_waterlevel(self, mode: str) -> Annotated[dict, FeatureAttribute()]:
|
||||||
|
"""Set waterlevel mode."""
|
||||||
|
name_to_value = {x.name: x.value for x in Waterlevel}
|
||||||
|
if mode not in name_to_value:
|
||||||
|
raise ValueError("Invalid waterlevel %s, available %s", mode, name_to_value)
|
||||||
|
|
||||||
|
settings = self._settings.copy()
|
||||||
|
settings["cistern"] = name_to_value[mode]
|
||||||
|
return await self.call("setCleanAttr", settings)
|
58
tests/smart/modules/test_mop.py
Normal file
58
tests/smart/modules/test_mop.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from pytest_mock import MockerFixture
|
||||||
|
|
||||||
|
from kasa import Module
|
||||||
|
from kasa.smart import SmartDevice
|
||||||
|
from kasa.smart.modules.mop import Waterlevel
|
||||||
|
|
||||||
|
from ...device_fixtures import get_parent_and_child_modules, parametrize
|
||||||
|
|
||||||
|
mop = parametrize("has mop", component_filter="mop", protocol_filter={"SMART"})
|
||||||
|
|
||||||
|
|
||||||
|
@mop
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("feature", "prop_name", "type"),
|
||||||
|
[
|
||||||
|
("mop_attached", "mop_attached", bool),
|
||||||
|
("mop_waterlevel", "waterlevel", str),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_features(dev: SmartDevice, feature: str, prop_name: str, type: type):
|
||||||
|
"""Test that features are registered and work as expected."""
|
||||||
|
mod = next(get_parent_and_child_modules(dev, Module.Mop))
|
||||||
|
assert mod is not None
|
||||||
|
|
||||||
|
prop = getattr(mod, prop_name)
|
||||||
|
assert isinstance(prop, type)
|
||||||
|
|
||||||
|
feat = mod._device.features[feature]
|
||||||
|
assert feat.value == prop
|
||||||
|
assert isinstance(feat.value, type)
|
||||||
|
|
||||||
|
|
||||||
|
@mop
|
||||||
|
async def test_mop_waterlevel(dev: SmartDevice, mocker: MockerFixture):
|
||||||
|
"""Test dust mode."""
|
||||||
|
mop_module = next(get_parent_and_child_modules(dev, Module.Mop))
|
||||||
|
call = mocker.spy(mop_module, "call")
|
||||||
|
|
||||||
|
waterlevel = mop_module._device.features["mop_waterlevel"]
|
||||||
|
assert mop_module.waterlevel == waterlevel.value
|
||||||
|
|
||||||
|
new_level = Waterlevel.High
|
||||||
|
await mop_module.set_waterlevel(new_level.name)
|
||||||
|
|
||||||
|
params = mop_module._settings.copy()
|
||||||
|
params["cistern"] = new_level.value
|
||||||
|
|
||||||
|
call.assert_called_with("setCleanAttr", params)
|
||||||
|
|
||||||
|
await dev.update()
|
||||||
|
|
||||||
|
assert mop_module.waterlevel == new_level.name
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="Invalid waterlevel"):
|
||||||
|
await mop_module.set_waterlevel("invalid")
|
Loading…
x
Reference in New Issue
Block a user