diff --git a/deebot_client/commands/xml/clean.py b/deebot_client/commands/xml/clean.py
new file mode 100644
index 00000000..d6138efb
--- /dev/null
+++ b/deebot_client/commands/xml/clean.py
@@ -0,0 +1,27 @@
+"""Clean commands."""
+
+from __future__ import annotations
+
+from deebot_client.models import CleanAction, CleanMode
+
+from .common import ExecuteCommand
+
+
+class CleanArea(ExecuteCommand):
+ """Clean area command."""
+
+ NAME = "Clean"
+ HAS_SUB_ELEMENT = True
+
+ def __init__(self, mode: CleanMode, area: str, cleanings: int = 1) -> None:
+ #
+
+ super().__init__(
+ {
+ "type": mode.xml_value,
+ "act": CleanAction.START.xml_value,
+ "speed": "standard", # TODO: FanSpeedLevel.NORMAL.xml_value, after #560 is merged
+ "deep": str(cleanings),
+ "mid": area,
+ }
+ )
diff --git a/deebot_client/models.py b/deebot_client/models.py
index c88ccabb..426dccf6 100644
--- a/deebot_client/models.py
+++ b/deebot_client/models.py
@@ -3,10 +3,12 @@
from __future__ import annotations
from dataclasses import dataclass
-from enum import IntEnum, StrEnum, unique
+from enum import IntEnum, unique
from pathlib import Path
from typing import TYPE_CHECKING, Required, TypedDict
+from deebot_client.util.enum import StrEnumWithXml
+
if TYPE_CHECKING:
from deebot_client.capabilities import Capabilities
from deebot_client.const import DataType
@@ -64,22 +66,22 @@ class State(IntEnum):
@unique
-class CleanAction(StrEnum):
+class CleanAction(StrEnumWithXml):
"""Enum class for all possible clean actions."""
- START = "start"
- PAUSE = "pause"
- RESUME = "resume"
- STOP = "stop"
+ START = "start", "s"
+ PAUSE = "pause", "p"
+ RESUME = "resume", "r"
+ STOP = "stop", "h"
@unique
-class CleanMode(StrEnum):
+class CleanMode(StrEnumWithXml):
"""Enum class for all possible clean modes."""
- AUTO = "auto"
- SPOT_AREA = "spotArea"
- CUSTOM_AREA = "customArea"
+ AUTO = "auto", "auto"
+ SPOT_AREA = "spotArea", "SpotArea"
+ CUSTOM_AREA = "customArea", "spot"
@dataclass(frozen=True)
diff --git a/deebot_client/util/enum.py b/deebot_client/util/enum.py
new file mode 100644
index 00000000..c6036fa0
--- /dev/null
+++ b/deebot_client/util/enum.py
@@ -0,0 +1,29 @@
+"""Enum util."""
+
+from __future__ import annotations
+
+from enum import StrEnum
+from typing import Self
+
+
+class StrEnumWithXml(StrEnum):
+ """String enum with xml value."""
+
+ xml_value: str
+
+ def __new__(cls, value: str, xml_value: str = "") -> Self:
+ """Create new StrEnumWithXml."""
+ obj = str.__new__(cls, value)
+ obj._value_ = value
+ obj.xml_value = xml_value
+ return obj
+
+ @classmethod
+ def from_xml(cls, value: str) -> Self:
+ """Get CleanAction from xml value."""
+ for member in cls:
+ if member.xml_value == value:
+ return member
+
+ msg = f"{value} is not a valid {cls.__name__}"
+ raise ValueError(msg)
diff --git a/tests/commands/xml/test_clean.py b/tests/commands/xml/test_clean.py
new file mode 100644
index 00000000..b67c8ee6
--- /dev/null
+++ b/tests/commands/xml/test_clean.py
@@ -0,0 +1,24 @@
+from __future__ import annotations
+
+import pytest
+
+from deebot_client.command import CommandResult
+from deebot_client.commands.xml.clean import CleanArea
+from deebot_client.message import HandlingState
+from deebot_client.models import CleanMode
+from tests.commands import assert_command
+
+from . import get_request_xml
+
+
+@pytest.mark.parametrize(
+ ("command", "command_result"),
+ [
+ (CleanArea(CleanMode.SPOT_AREA, "4", 1), HandlingState.SUCCESS),
+ ],
+)
+async def test_CleanArea(command: CleanArea, command_result: HandlingState) -> None:
+ json = get_request_xml("")
+ await assert_command(
+ command, json, None, command_result=CommandResult(command_result)
+ )
diff --git a/tests/util/test_enum.py b/tests/util/test_enum.py
new file mode 100644
index 00000000..8b34fe47
--- /dev/null
+++ b/tests/util/test_enum.py
@@ -0,0 +1,35 @@
+from __future__ import annotations
+
+import pytest
+
+from deebot_client.util.enum import StrEnumWithXml
+
+
+class TestStrEnumWithXml(StrEnumWithXml):
+ """Simple Enum for testing."""
+
+ ENUM1 = "value1", "xmlvalue1"
+ ENUM2 = "value2", "xmlvalue2"
+
+
+@pytest.mark.parametrize(
+ ("test_enum", "test_value", "test_xml_value"),
+ [
+ (TestStrEnumWithXml.ENUM1, "value1", "xmlvalue1"),
+ (TestStrEnumWithXml.ENUM2, "value2", "xmlvalue2"),
+ ],
+)
+def test_StrEnumWithXml_values(
+ test_enum: TestStrEnumWithXml, test_value: str, test_xml_value: str
+) -> None:
+ assert test_enum.value == test_value
+ assert test_enum.xml_value == test_xml_value
+ assert test_value == TestStrEnumWithXml.from_xml(test_xml_value).value
+ assert test_xml_value == TestStrEnumWithXml(test_value).xml_value
+
+ # test ENUM from invalid xml
+ try:
+ TestStrEnumWithXml.from_xml("this_is_invalid")
+ raise AssertionError
+ except ValueError:
+ assert True