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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions devtools/helpers/smartrequests.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ def get_component_requests(component_id, ver_code):
"overheat_protection": [],
# Vacuum components
"clean": [
SmartRequest.get_raw_request("getCarpetClean"),
SmartRequest.get_raw_request("getCleanRecords"),
SmartRequest.get_raw_request("getVacStatus"),
SmartRequest.get_raw_request("getAreaUnit"),
Expand Down
43 changes: 41 additions & 2 deletions kasa/smart/modules/clean.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import logging
from datetime import timedelta
from enum import IntEnum
from enum import IntEnum, StrEnum
from typing import Annotated, Literal

from ...feature import Feature
Expand Down Expand Up @@ -55,6 +55,13 @@ class FanSpeed(IntEnum):
Max = 4


class CarpetCleanMode(StrEnum):
"""Carpet clean mode."""

Normal = "normal"
Boost = "boost"


class AreaUnit(IntEnum):
"""Area unit."""

Expand Down Expand Up @@ -142,7 +149,6 @@ def _initialize_features(self) -> None:
type=Feature.Type.Sensor,
)
)

self._add_feature(
Feature(
self._device,
Expand Down Expand Up @@ -170,6 +176,20 @@ def _initialize_features(self) -> None:
type=Feature.Type.Number,
)
)
self._add_feature(
Feature(
self._device,
id="carpet_clean_mode",
name="Carpet clean mode",
container=self,
attribute_getter="carpet_clean_mode",
attribute_setter="set_carpet_clean_mode",
icon="mdi:rug",
choices_getter=lambda: list(CarpetCleanMode.__members__),
category=Feature.Category.Config,
type=Feature.Type.Choice,
)
)
self._add_feature(
Feature(
self._device,
Expand Down Expand Up @@ -233,6 +253,7 @@ def query(self) -> dict:
return {
"getVacStatus": {},
"getCleanInfo": {},
"getCarpetClean": {},
"getAreaUnit": {},
"getBatteryInfo": {},
"getCleanStatus": {},
Expand Down Expand Up @@ -341,6 +362,24 @@ def status(self) -> Status:
_LOGGER.warning("Got unknown status code: %s (%s)", status_code, self.data)
return Status.UnknownInternal

@property
def carpet_clean_mode(self) -> Annotated[str, FeatureAttribute()]:
"""Return carpet clean mode."""
return CarpetCleanMode(self.data["getCarpetClean"]["carpet_clean_prefer"]).name

async def set_carpet_clean_mode(
self, mode: str
) -> Annotated[dict, FeatureAttribute()]:
"""Set carpet clean mode."""
name_to_value = {x.name: x.value for x in CarpetCleanMode}
if mode not in name_to_value:
raise ValueError(
"Invalid carpet clean mode %s, available %s", mode, name_to_value
)
return await self.call(
"setCarpetClean", {"carpet_clean_prefer": name_to_value[mode]}
)

@property
def area_unit(self) -> AreaUnit:
"""Return area unit."""
Expand Down
3 changes: 3 additions & 0 deletions tests/fixtures/smart/RV20 Max Plus(EU)_1.0_1.0.7.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@
"getBatteryInfo": {
"battery_percentage": 75
},
"getCarpetClean": {
"carpet_clean_prefer": "boost"
},
"getCleanAttr": {
"cistern": 2,
"clean_number": 1,
Expand Down
49 changes: 49 additions & 0 deletions tests/smart/modules/test_clean.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
("vacuum_status", "status", Status),
("vacuum_error", "error", ErrorCode),
("vacuum_fan_speed", "fan_speed_preset", str),
("carpet_clean_mode", "carpet_clean_mode", str),
("battery_level", "battery", int),
],
)
Expand Down Expand Up @@ -69,6 +70,13 @@ async def test_features(dev: SmartDevice, feature: str, prop_name: str, type: ty
{"suction": 1, "type": "global"},
id="vacuum_fan_speed",
),
pytest.param(
"carpet_clean_mode",
"Boost",
"setCarpetClean",
{"carpet_clean_prefer": "boost"},
id="carpet_clean_mode",
),
pytest.param(
"clean_count",
2,
Expand Down Expand Up @@ -151,3 +159,44 @@ async def test_unknown_status(

assert clean.status is Status.UnknownInternal
assert "Got unknown status code: 123" in caplog.text


@clean
@pytest.mark.parametrize(
("setting", "value", "exc", "exc_message"),
[
pytest.param(
"vacuum_fan_speed",
"invalid speed",
ValueError,
"Invalid fan speed",
id="vacuum_fan_speed",
),
pytest.param(
"carpet_clean_mode",
"invalid mode",
ValueError,
"Invalid carpet clean mode",
id="carpet_clean_mode",
),
],
)
async def test_invalid_settings(
dev: SmartDevice,
mocker: MockerFixture,
setting: str,
value: str,
exc: type[Exception],
exc_message: str,
):
"""Test invalid settings."""
clean = next(get_parent_and_child_modules(dev, Module.Clean))

# Not using feature.set_value() as it checks for valid values
setter_name = dev.features[setting].attribute_setter
assert isinstance(setter_name, str)

setter = getattr(clean, setter_name)

with pytest.raises(exc, match=exc_message):
await setter(value)
Loading