FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Add mop module by rytilahti · Pull Request #1456 · python-kasa/python-kasa · GitHub

Repository navigation

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .py  (4) All 1 file type selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
1 change: 1 addition & 0 deletions kasa/module.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ class Module(ABC):
Clean: Final[ModuleName[smart.Clean]] = ModuleName("Clean")
Dustbin: Final[ModuleName[smart.Dustbin]] = ModuleName("Dustbin")
Speaker: Final[ModuleName[smart.Speaker]] = ModuleName("Speaker")
Mop: Final[ModuleName[smart.Mop]] = ModuleName("Mop")

def __init__(self, device: Device, module: str) -> None:
self._device = device
Expand Down
2 changes: 2 additions & 0 deletions kasa/smart/modules/__init__.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from .lightstripeffect import LightStripEffect
from .lighttransition import LightTransition
from .matter import Matter
from .mop import Mop
from .motionsensor import MotionSensor
from .overheatprotection import OverheatProtection
from .reportmode import ReportMode
Expand Down Expand Up @@ -76,4 +77,5 @@
"HomeKit",
"Matter",
"Dustbin",
"Mop",
]
90 changes: 90 additions & 0 deletions kasa/smart/modules/mop.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -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"},

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

This is the same query as used by clean module, but it probably shouldn't matter. If we ever add support for spot cleaning, we may need to consider how to handle that (as its query only differs by type).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Will there always be a vaccum module? Could this data come from that module to avoid querying it twice?

rytilahti Jan 15, 2025 •
edited
Loading

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Oh, good point. I think there is no mop-only devices, but if there is, this will still keep working.

And as we construct the query using update(), there are no duplicates here for the time being, so this is fine.

We will become problems if we want to add zone/spot cleaning though, as they will use type: pose for their settings.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

That would indeed be a challenge as we'd overwrite the other module query as things currently stand.

rytilahti Jan 15, 2025 •
edited
Loading

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Yes, we can tackle that whenever it comes an issue. But it is not necessary for this PR, so I think we can merge it as it is? Assuming the selected categories, identifier names and user visible strings are fine, that is.

}

@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 changes: 58 additions & 0 deletions tests/smart/modules/test_mop.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -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")

Back | FazBrowse Home | New Git URL