Skip to content

Commit cff8edd

Browse files
committed
Add XML Clean Commands
1 parent 93f3406 commit cff8edd

3 files changed

Lines changed: 185 additions & 0 deletions

File tree

deebot_client/commands/xml/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from .charge import Charge
1010
from .charge_state import GetChargeState
11+
from .clean import Clean, CleanArea, GetCleanState
1112
from .error import GetError
1213
from .fan_speed import GetFanSpeed
1314
from .life_span import GetLifeSpan
@@ -20,7 +21,10 @@
2021

2122
__all__ = [
2223
"Charge",
24+
"Clean",
25+
"CleanArea",
2326
"GetChargeState",
27+
"GetCleanState",
2428
"GetCleanSum",
2529
"GetError",
2630
"GetFanSpeed",
@@ -32,7 +36,10 @@
3236
# fmt: off
3337
# ordered by file asc
3438
_COMMANDS: list[type[XmlCommand]] = [
39+
Clean,
40+
CleanArea,
3541
GetError,
42+
GetCleanState,
3643
GetLifeSpan,
3744
PlaySound,
3845
]
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"""Clean commands."""
2+
3+
from __future__ import annotations
4+
5+
from typing import TYPE_CHECKING
6+
7+
from deebot_client.events import FanSpeedEvent, FanSpeedLevel, StateEvent
8+
from deebot_client.logging_filter import get_logger
9+
from deebot_client.message import HandlingResult
10+
from deebot_client.models import CleanAction, CleanMode, State
11+
12+
from .common import ExecuteCommand, XmlCommandWithMessageHandling
13+
14+
if TYPE_CHECKING:
15+
from xml.etree.ElementTree import Element
16+
17+
from deebot_client.event_bus import EventBus
18+
19+
_LOGGER = get_logger(__name__)
20+
21+
22+
class Clean(ExecuteCommand):
23+
"""Generic start/pause/stop cleaning command."""
24+
25+
NAME = "Clean"
26+
HAS_SUB_ELEMENT = True
27+
28+
def __init__(
29+
self, action: CleanAction, speed: FanSpeedLevel = FanSpeedLevel.NORMAL
30+
) -> None:
31+
# <ctl><clean type='SpotArea' act='s' speed='standard' deep='1' mid='4,5'/></ctl>
32+
33+
super().__init__(
34+
{
35+
"type": CleanMode.AUTO.xml_value,
36+
"act": action.xml_value,
37+
"speed": speed.xml_value,
38+
}
39+
)
40+
41+
42+
class CleanArea(ExecuteCommand):
43+
"""Clean area command."""
44+
45+
NAME = "Clean"
46+
HAS_SUB_ELEMENT = True
47+
48+
def __init__(
49+
self,
50+
mode: CleanMode,
51+
area: str,
52+
cleanings: int = 1,
53+
speed: FanSpeedLevel = FanSpeedLevel.NORMAL,
54+
) -> None:
55+
# <ctl><clean type='SpotArea' act='s' speed='standard' deep='1' mid='4,5'/></ctl>
56+
57+
super().__init__(
58+
{
59+
"type": mode.xml_value,
60+
"act": CleanAction.START.xml_value,
61+
"speed": speed.xml_value,
62+
"deep": str(cleanings),
63+
"mid": area,
64+
}
65+
)
66+
67+
68+
class GetCleanState(XmlCommandWithMessageHandling):
69+
"""GetCleanState command."""
70+
71+
NAME = "GetCleanState"
72+
73+
@classmethod
74+
def _handle_xml(cls, event_bus: EventBus, xml: Element) -> HandlingResult:
75+
"""Handle xml message and notify the correct event subscribers.
76+
77+
:return: A message response
78+
"""
79+
if xml.attrib.get("ret") != "ok" or (clean := xml.find("clean")) is None:
80+
return HandlingResult.analyse()
81+
82+
speed_attrib = clean.attrib.get("speed")
83+
if speed_attrib is not None:
84+
fan_speed_level = FanSpeedLevel.from_xml(speed_attrib)
85+
event_bus.notify(FanSpeedEvent(fan_speed_level))
86+
87+
clean_attrib = clean.attrib.get("st")
88+
if clean_attrib is not None:
89+
clean_action = CleanAction.from_xml(clean_attrib)
90+
if clean_action == CleanAction.START:
91+
event_bus.notify(StateEvent(State.CLEANING))
92+
elif clean_action == CleanAction.PAUSE:
93+
event_bus.notify(StateEvent(State.PAUSED))
94+
else:
95+
_LOGGER.debug("Ignored CleanState %s", clean_action)
96+
97+
return HandlingResult.success()

tests/commands/xml/test_clean.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from __future__ import annotations
2+
3+
import pytest
4+
5+
from deebot_client.command import CommandResult
6+
from deebot_client.commands.xml import GetCleanState
7+
from deebot_client.commands.xml.clean import CleanArea
8+
from deebot_client.events import FanSpeedEvent, FanSpeedLevel, StateEvent
9+
from deebot_client.message import HandlingState
10+
from deebot_client.models import CleanMode, State
11+
from tests.commands import assert_command
12+
13+
from . import get_request_xml
14+
15+
16+
@pytest.mark.parametrize(
17+
("command", "command_result"),
18+
[
19+
(CleanArea(CleanMode.SPOT_AREA, "4", 1), HandlingState.SUCCESS),
20+
],
21+
)
22+
async def test_CleanArea(command: CleanArea, command_result: HandlingState) -> None:
23+
json = get_request_xml("<ctl ret='ok'/>")
24+
await assert_command(
25+
command, json, None, command_result=CommandResult(command_result)
26+
)
27+
28+
29+
@pytest.mark.parametrize(
30+
("speed", "state", "expected_fan_speed_event", "expected_state_event"),
31+
[
32+
(
33+
"standard",
34+
"s",
35+
FanSpeedEvent(FanSpeedLevel.NORMAL),
36+
StateEvent(State.CLEANING),
37+
),
38+
(
39+
"strong",
40+
"s",
41+
FanSpeedEvent(FanSpeedLevel.MAX),
42+
StateEvent(State.CLEANING),
43+
),
44+
(
45+
"standard",
46+
"p",
47+
FanSpeedEvent(FanSpeedLevel.NORMAL),
48+
StateEvent(State.PAUSED),
49+
),
50+
],
51+
ids=["standard_cleaning", "strong_cleaning", "paused"],
52+
)
53+
async def test_get_clean_state(
54+
speed: str,
55+
state: str,
56+
expected_fan_speed_event: FanSpeedEvent,
57+
expected_state_event: StateEvent,
58+
) -> None:
59+
json = get_request_xml(
60+
f"<ctl ret='ok'><clean type='auto' speed='{speed}' st='{state}' t='0' a='0' s='0' tr=''/></ctl>"
61+
)
62+
await assert_command(
63+
GetCleanState(),
64+
json,
65+
[x for x in [expected_fan_speed_event, expected_state_event] if x is not None],
66+
)
67+
68+
69+
@pytest.mark.parametrize(
70+
"xml",
71+
["<ctl ret='error'/>", "<ctl ret='ok'></ctl>"],
72+
ids=["error", "no_state"],
73+
)
74+
async def test_get_clean_state_error(xml: str) -> None:
75+
json = get_request_xml(xml)
76+
await assert_command(
77+
GetCleanState(),
78+
json,
79+
None,
80+
command_result=CommandResult(HandlingState.ANALYSE_LOGGED),
81+
)

0 commit comments

Comments
 (0)