Skip to content

Commit 17e3186

Browse files
authored
Implement XML Position messages (#916)
1 parent 813daea commit 17e3186

File tree

6 files changed

+132
-16
lines changed

6 files changed

+132
-16
lines changed

deebot_client/commands/xml/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from .fan_speed import GetFanSpeed
1515
from .life_span import GetLifeSpan
1616
from .play_sound import PlaySound
17-
from .pos import GetPos
17+
from .pos import GetChargerPos, GetPos
1818
from .stats import GetCleanSum
1919

2020
if TYPE_CHECKING:
@@ -24,6 +24,7 @@
2424
"Charge",
2525
"GetBatteryInfo",
2626
"GetChargeState",
27+
"GetChargerPos",
2728
"GetCleanLogs",
2829
"GetCleanSum",
2930
"GetError",
@@ -37,9 +38,11 @@
3738
# ordered by file asc
3839
_COMMANDS: list[type[XmlCommand]] = [
3940
GetBatteryInfo,
41+
GetChargerPos,
4042
GetCleanLogs,
4143
GetError,
4244
GetLifeSpan,
45+
GetPos,
4346
PlaySound,
4447
]
4548
# fmt: on

deebot_client/commands/xml/pos.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
from typing import TYPE_CHECKING
66

7-
from deebot_client.events import Position, PositionsEvent
87
from deebot_client.message import HandlingResult
8+
from deebot_client.messages.xml import Pos
99
from deebot_client.rs.map import PositionType
1010

1111
from .common import XmlCommandWithMessageHandling
@@ -16,7 +16,7 @@
1616
from deebot_client.event_bus import EventBus
1717

1818

19-
class GetPos(XmlCommandWithMessageHandling):
19+
class GetPos(XmlCommandWithMessageHandling, Pos):
2020
"""GetPos command."""
2121

2222
NAME = "GetPos"
@@ -30,13 +30,21 @@ def _handle_xml(cls, event_bus: EventBus, xml: Element) -> HandlingResult:
3030
if xml.attrib.get("ret") != "ok" or xml.attrib.get("t") != "p":
3131
return HandlingResult.analyse()
3232

33-
if p := xml.attrib.get("p"):
34-
p_x, p_y = p.split(",", 2)
35-
p_a = xml.attrib.get("a", 0)
36-
position = Position(
37-
type=PositionType.DEEBOT, x=int(p_x), y=int(p_y), a=int(p_a)
38-
)
39-
event_bus.notify(PositionsEvent(positions=[position]))
40-
return HandlingResult.success()
33+
return cls._parse_xml(PositionType.DEEBOT, event_bus, xml)
4134

42-
return HandlingResult.analyse()
35+
36+
class GetChargerPos(XmlCommandWithMessageHandling, Pos):
37+
"""GetChargerPos command."""
38+
39+
NAME = "GetChargerPos"
40+
41+
@classmethod
42+
def _handle_xml(cls, event_bus: EventBus, xml: Element) -> HandlingResult:
43+
"""Handle xml message and notify the correct event subscribers.
44+
45+
:return: A message response
46+
"""
47+
if xml.attrib.get("ret") != "ok":
48+
return HandlingResult.analyse()
49+
50+
return cls._parse_xml(PositionType.CHARGER, event_bus, xml)

deebot_client/messages/xml/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,19 @@
55
from typing import TYPE_CHECKING
66

77
from deebot_client.messages.xml.battery import BatteryInfo
8+
from deebot_client.messages.xml.pos import Pos
89

910
if TYPE_CHECKING:
1011
from collections.abc import Sequence
1112

1213
from deebot_client.message import Message
1314

14-
__all__: Sequence[str] = ["BatteryInfo"]
15+
__all__: Sequence[str] = ["BatteryInfo", "Pos"]
1516
# fmt: off
1617
# ordered by file asc
1718
_MESSAGES: list[type[Message]] = [
18-
BatteryInfo
19+
BatteryInfo,
20+
Pos
1921
]
2022
# fmt: on
2123

deebot_client/messages/xml/pos.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""Pos messages."""
2+
3+
from __future__ import annotations
4+
5+
from typing import TYPE_CHECKING
6+
7+
from deebot_client.events import Position, PositionsEvent
8+
from deebot_client.message import HandlingResult
9+
from deebot_client.messages.xml.common import XmlMessage
10+
from deebot_client.rs.map import PositionType
11+
12+
if TYPE_CHECKING:
13+
from xml.etree.ElementTree import Element
14+
15+
from deebot_client.event_bus import EventBus
16+
17+
18+
class Pos(XmlMessage):
19+
"""Pos message."""
20+
21+
NAME = "Pos"
22+
23+
@classmethod
24+
def _handle_xml(cls, event_bus: EventBus, xml: Element) -> HandlingResult:
25+
if xml.attrib.get("t") != "p":
26+
return HandlingResult.analyse()
27+
28+
return cls._parse_xml(PositionType.DEEBOT, event_bus, xml)
29+
30+
@classmethod
31+
def _parse_xml(
32+
cls, position_type: PositionType, event_bus: EventBus, xml: Element
33+
) -> HandlingResult:
34+
"""Handle xml message and notify the correct event subscribers."""
35+
if (p := xml.attrib.get("p")) and (xml.attrib.get("valid", "1")) == "1":
36+
p_x, p_y = p.split(",", 2)
37+
p_a = xml.attrib.get("a", 0)
38+
position = Position(type=position_type, x=int(p_x), y=int(p_y), a=int(p_a))
39+
event_bus.notify(PositionsEvent(positions=[position]))
40+
return HandlingResult.success()
41+
42+
return HandlingResult.analyse()

tests/commands/xml/test_pos.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from deebot_client.command import CommandResult
66
from deebot_client.commands.xml import GetPos
7+
from deebot_client.commands.xml.pos import GetChargerPos
78
from deebot_client.events import Position, PositionsEvent
89
from deebot_client.message import HandlingState
910
from deebot_client.rs.map import PositionType
@@ -22,8 +23,13 @@ async def test_get_pos() -> None:
2223

2324
@pytest.mark.parametrize(
2425
"xml",
25-
["<ctl ret='error'/>", "<ctl ret='ok' t='p'></ctl>"],
26-
ids=["error", "no_state"],
26+
[
27+
"<ctl ret='error'/>",
28+
"<ctl ret='ok' t='p'></ctl>",
29+
"<ctl ret='ok' t='??' p='77,-5' a='-3' valid='1'/>",
30+
"<ctl ret='ok' t='p' p='77,-5' a='-3' valid='0'/>",
31+
],
32+
ids=["error", "no_state", "wrong_type", "not_valid"],
2733
)
2834
async def test_get_pos_error(xml: str) -> None:
2935
json = get_request_xml(xml)
@@ -33,3 +39,26 @@ async def test_get_pos_error(xml: str) -> None:
3339
None,
3440
command_result=CommandResult(HandlingState.ANALYSE_LOGGED),
3541
)
42+
43+
44+
async def test_get_charger_pos() -> None:
45+
json = get_request_xml("<ctl ret='ok' p='77,-5' a='-3'/>")
46+
expected_event = PositionsEvent(
47+
positions=[Position(type=PositionType.CHARGER, x=77, y=-5, a=-3)]
48+
)
49+
await assert_command(GetChargerPos(), json, expected_event)
50+
51+
52+
@pytest.mark.parametrize(
53+
"xml",
54+
["<ctl ret='error'/>", "<ctl ret='ok'></ctl>"],
55+
ids=["error", "no_state"],
56+
)
57+
async def test_get_charger_pos_error(xml: str) -> None:
58+
json = get_request_xml(xml)
59+
await assert_command(
60+
GetChargerPos(),
61+
json,
62+
None,
63+
command_result=CommandResult(HandlingState.ANALYSE_LOGGED),
64+
)

tests/messages/xml/test_pos.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from __future__ import annotations
2+
3+
import pytest
4+
5+
from deebot_client.events import Position, PositionsEvent
6+
from deebot_client.message import HandlingState
7+
from deebot_client.messages.xml import Pos
8+
from deebot_client.rs.map import PositionType
9+
from tests.messages import assert_message, assert_message_failure
10+
11+
12+
@pytest.mark.parametrize("position", [(-9, 15, 89)])
13+
def test_Pos(position: tuple[int, int, int]) -> None:
14+
x, y, a = position
15+
xml_message = f'<ctl td="Pos" t="p" p="{x},{y}" a="{a}" valid="1" />'
16+
assert_message(
17+
Pos,
18+
xml_message,
19+
PositionsEvent([Position(type=PositionType.DEEBOT, x=x, y=y, a=a)]),
20+
)
21+
22+
23+
@pytest.mark.parametrize(
24+
"xml_message",
25+
{
26+
'<ctl td="Pos" t="p" a="89" valid="1" />',
27+
'<ctl td="Pos" t="??" p="0,0" a="89" valid="1" />',
28+
'<ctl td="Pos" t="p" p="0,0" a="89" valid="0" />',
29+
},
30+
)
31+
def test_Pos_error(xml_message: str) -> None:
32+
assert_message_failure(Pos, xml_message, HandlingState.ANALYSE_LOGGED)

0 commit comments

Comments
 (0)