From 136fb4d51020e3d19a9424a4e037b5d14f43ac75 Mon Sep 17 00:00:00 2001 From: flubshi <4031504+flubshi@users.noreply.github.com> Date: Mon, 26 Aug 2024 22:17:20 +0200 Subject: [PATCH 01/12] Add XML command "SetFanSpeed" --- deebot_client/commands/xml/__init__.py | 3 ++- deebot_client/commands/xml/common.py | 29 ++++++++++++++++++++++--- deebot_client/commands/xml/fan_speed.py | 18 ++++++++++++++- tests/commands/xml/test_fan_speed.py | 12 ++++++++++ 4 files changed, 57 insertions(+), 5 deletions(-) diff --git a/deebot_client/commands/xml/__init__.py b/deebot_client/commands/xml/__init__.py index 7448f8097..de7952385 100644 --- a/deebot_client/commands/xml/__init__.py +++ b/deebot_client/commands/xml/__init__.py @@ -8,7 +8,7 @@ from .charge_state import GetChargeState from .error import GetError -from .fan_speed import GetFanSpeed +from .fan_speed import GetFanSpeed, SetFanSpeed from .pos import GetPos if TYPE_CHECKING: @@ -18,6 +18,7 @@ "GetChargeState", "GetError", "GetFanSpeed", + "SetFanSpeed", "GetPos", ] diff --git a/deebot_client/commands/xml/common.py b/deebot_client/commands/xml/common.py index d63e40449..e477509dc 100644 --- a/deebot_client/commands/xml/common.py +++ b/deebot_client/commands/xml/common.py @@ -8,10 +8,10 @@ from defusedxml import ElementTree # type: ignore[import-untyped] -from deebot_client.command import Command, CommandWithMessageHandling +from deebot_client.command import Command, CommandWithMessageHandling, SetCommand from deebot_client.const import DataType from deebot_client.logging_filter import get_logger -from deebot_client.message import HandlingResult, MessageStr +from deebot_client.message import HandlingResult, MessageStr, HandlingState if TYPE_CHECKING: from deebot_client.event_bus import EventBus @@ -25,7 +25,6 @@ class XmlCommand(Command): data_type: DataType = DataType.XML @property # type: ignore[misc] - @classmethod def has_sub_element(cls) -> bool: """Return True if command has inner element.""" return False @@ -65,3 +64,27 @@ def _handle_str(cls, event_bus: EventBus, message: str) -> HandlingResult: """ xml = ElementTree.fromstring(message) return cls._handle_xml(event_bus, xml) + + +class ExecuteCommand(XmlCommandWithMessageHandling, ABC): + """Command, which is executing something (ex. Charge).""" + + @classmethod + def _handle_xml(cls, event_bus: EventBus, xml: Element) -> HandlingResult: + """Handle message->xml and notify the correct event subscribers. + + :return: A message response + """ + # Success event looks like + if xml.attrib.get("ret") == "ok": + return HandlingResult.success() + + _LOGGER.warning('Command "%s" was not successful. XML response: %s', cls.name, xml) + return HandlingResult(HandlingState.FAILED) + + +class XmlSetCommand(ExecuteCommand, SetCommand, ABC): + """Xml base set command. + + Command needs to be linked to the "get" command, for handling (updating) the sensors. + """ \ No newline at end of file diff --git a/deebot_client/commands/xml/fan_speed.py b/deebot_client/commands/xml/fan_speed.py index d0c1f3792..649d20d01 100644 --- a/deebot_client/commands/xml/fan_speed.py +++ b/deebot_client/commands/xml/fan_speed.py @@ -2,12 +2,14 @@ from __future__ import annotations +from types import MappingProxyType from typing import TYPE_CHECKING +from deebot_client.command import InitParam from deebot_client.events import FanSpeedEvent, FanSpeedLevel from deebot_client.message import HandlingResult -from .common import XmlCommandWithMessageHandling +from .common import XmlCommandWithMessageHandling, XmlSetCommand if TYPE_CHECKING: from xml.etree.ElementTree import Element @@ -42,3 +44,17 @@ def _handle_xml(cls, event_bus: EventBus, xml: Element) -> HandlingResult: return HandlingResult.success() return HandlingResult.analyse() + + +class SetFanSpeed(XmlSetCommand): + """Set fan speed command.""" + + name = "SetCleanSpeed" + get_command = GetFanSpeed + _mqtt_params = MappingProxyType({"speed": InitParam(FanSpeedLevel)}) + + def __init__(self, speed: FanSpeedLevel | str) -> None: + if isinstance(speed, int): + speed = "strong" if speed in [1, 2] else "normal" + super().__init__({"speed": speed}) + diff --git a/tests/commands/xml/test_fan_speed.py b/tests/commands/xml/test_fan_speed.py index bf728ecfc..740091fce 100644 --- a/tests/commands/xml/test_fan_speed.py +++ b/tests/commands/xml/test_fan_speed.py @@ -6,11 +6,13 @@ from deebot_client.command import CommandResult from deebot_client.commands.xml import GetFanSpeed +from deebot_client.commands.xml.fan_speed import SetFanSpeed from deebot_client.events import FanSpeedEvent, FanSpeedLevel from deebot_client.message import HandlingState from tests.commands import assert_command from . import get_request_xml +from ..json import assert_set_command if TYPE_CHECKING: from deebot_client.events.base import Event @@ -42,3 +44,13 @@ async def test_get_fan_speed_error(xml: str) -> None: None, command_result=CommandResult(HandlingState.ANALYSE_LOGGED), ) + +async def test_set_fan_speed() -> None: + command = SetFanSpeed(FanSpeedLevel.MAX) + json = get_request_xml("") + await assert_command(command, json, None, command_result=CommandResult(HandlingState.SUCCESS)) + +async def test_set_fan_speed_error() -> None: + command = SetFanSpeed("invalid") + json = get_request_xml("") + await assert_command(command, json, None, command_result=CommandResult(HandlingState.FAILED)) From 315307a97700f89f9ee649a87afc42474e68f848 Mon Sep 17 00:00:00 2001 From: flubshi <4031504+flubshi@users.noreply.github.com> Date: Wed, 11 Sep 2024 19:27:02 +0200 Subject: [PATCH 02/12] Add feedback for Get/SetCleanSpeed commands --- deebot_client/commands/xml/__init__.py | 8 +++++--- deebot_client/commands/xml/fan_speed.py | 18 +++++++++--------- deebot_client/events/fan_speed.py | 2 ++ tests/commands/xml/test_fan_speed.py | 18 +++++++++--------- 4 files changed, 25 insertions(+), 21 deletions(-) diff --git a/deebot_client/commands/xml/__init__.py b/deebot_client/commands/xml/__init__.py index 4541cba55..ba5c85e47 100644 --- a/deebot_client/commands/xml/__init__.py +++ b/deebot_client/commands/xml/__init__.py @@ -8,7 +8,7 @@ from .charge_state import GetChargeState from .error import GetError -from .fan_speed import GetFanSpeed, SetFanSpeed +from .fan_speed import GetCleanSpeed, SetCleanSpeed from .pos import GetPos from .stats import GetCleanSum @@ -16,17 +16,19 @@ from .common import XmlCommand __all__ = [ + "GetCleanSpeed", + "SetCleanSpeed", "GetChargeState", "GetCleanSum", "GetError", - "GetFanSpeed", - "SetFanSpeed", "GetPos", ] # fmt: off # ordered by file asc _COMMANDS: list[type[XmlCommand]] = [ + GetCleanSpeed, + SetCleanSpeed, GetError, ] # fmt: on diff --git a/deebot_client/commands/xml/fan_speed.py b/deebot_client/commands/xml/fan_speed.py index 649d20d01..1a0751bc7 100644 --- a/deebot_client/commands/xml/fan_speed.py +++ b/deebot_client/commands/xml/fan_speed.py @@ -17,8 +17,8 @@ from deebot_client.event_bus import EventBus -class GetFanSpeed(XmlCommandWithMessageHandling): - """GetFanSpeed command.""" +class GetCleanSpeed(XmlCommandWithMessageHandling): + """GetCleanSpeed command.""" name = "GetCleanSpeed" @@ -35,9 +35,9 @@ def _handle_xml(cls, event_bus: EventBus, xml: Element) -> HandlingResult: match speed.lower(): case "standard": - event = FanSpeedEvent(FanSpeedLevel.NORMAL) + event = FanSpeedEvent(FanSpeedLevel.STANDARD) case "strong": - event = FanSpeedEvent(FanSpeedLevel.MAX) + event = FanSpeedEvent(FanSpeedLevel.STRONG) if event: event_bus.notify(event) @@ -46,15 +46,15 @@ def _handle_xml(cls, event_bus: EventBus, xml: Element) -> HandlingResult: return HandlingResult.analyse() -class SetFanSpeed(XmlSetCommand): - """Set fan speed command.""" +class SetCleanSpeed(XmlSetCommand): + """Set clean speed command.""" name = "SetCleanSpeed" - get_command = GetFanSpeed + get_command = GetCleanSpeed _mqtt_params = MappingProxyType({"speed": InitParam(FanSpeedLevel)}) def __init__(self, speed: FanSpeedLevel | str) -> None: - if isinstance(speed, int): - speed = "strong" if speed in [1, 2] else "normal" + if isinstance(speed, FanSpeedLevel): + speed = speed.name.lower() super().__init__({"speed": speed}) diff --git a/deebot_client/events/fan_speed.py b/deebot_client/events/fan_speed.py index 4a8f599e1..00fcc3bb3 100644 --- a/deebot_client/events/fan_speed.py +++ b/deebot_client/events/fan_speed.py @@ -14,8 +14,10 @@ class FanSpeedLevel(IntEnum): # Values should be sort from low to high on their meanings QUIET = 1000 + STANDARD = -1 NORMAL = 0 MAX = 1 + STRONG = -2 MAX_PLUS = 2 diff --git a/tests/commands/xml/test_fan_speed.py b/tests/commands/xml/test_fan_speed.py index 740091fce..140fbdb18 100644 --- a/tests/commands/xml/test_fan_speed.py +++ b/tests/commands/xml/test_fan_speed.py @@ -5,14 +5,12 @@ import pytest from deebot_client.command import CommandResult -from deebot_client.commands.xml import GetFanSpeed -from deebot_client.commands.xml.fan_speed import SetFanSpeed +from deebot_client.commands.xml import GetCleanSpeed, SetCleanSpeed from deebot_client.events import FanSpeedEvent, FanSpeedLevel from deebot_client.message import HandlingState from tests.commands import assert_command from . import get_request_xml -from ..json import assert_set_command if TYPE_CHECKING: from deebot_client.events.base import Event @@ -21,14 +19,14 @@ @pytest.mark.parametrize( ("speed", "expected_event"), [ - ("standard", FanSpeedEvent(FanSpeedLevel.NORMAL)), - ("strong", FanSpeedEvent(FanSpeedLevel.MAX)), + ("standard", FanSpeedEvent(FanSpeedLevel.STANDARD)), + ("strong", FanSpeedEvent(FanSpeedLevel.STRONG)), ], ids=["standard", "strong"], ) async def test_get_fan_speed(speed: str, expected_event: Event) -> None: json = get_request_xml(f"") - await assert_command(GetFanSpeed(), json, expected_event) + await assert_command(GetCleanSpeed(), json, expected_event) @pytest.mark.parametrize( @@ -39,18 +37,20 @@ async def test_get_fan_speed(speed: str, expected_event: Event) -> None: async def test_get_fan_speed_error(xml: str) -> None: json = get_request_xml(xml) await assert_command( - GetFanSpeed(), + GetCleanSpeed(), json, None, command_result=CommandResult(HandlingState.ANALYSE_LOGGED), ) + async def test_set_fan_speed() -> None: - command = SetFanSpeed(FanSpeedLevel.MAX) + command = SetCleanSpeed(FanSpeedLevel.STRONG) json = get_request_xml("") await assert_command(command, json, None, command_result=CommandResult(HandlingState.SUCCESS)) + async def test_set_fan_speed_error() -> None: - command = SetFanSpeed("invalid") + command = SetCleanSpeed("invalid") json = get_request_xml("") await assert_command(command, json, None, command_result=CommandResult(HandlingState.FAILED)) From 5ae93c39ffb1a3acac2460b168865f229d385d5b Mon Sep 17 00:00:00 2001 From: flubshi <4031504+flubshi@users.noreply.github.com> Date: Tue, 21 Jan 2025 19:39:33 +0100 Subject: [PATCH 03/12] XML SetCleanSpeed: Adjust NAME attribute --- deebot_client/commands/xml/fan_speed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deebot_client/commands/xml/fan_speed.py b/deebot_client/commands/xml/fan_speed.py index e2d7d17c0..ce4989543 100644 --- a/deebot_client/commands/xml/fan_speed.py +++ b/deebot_client/commands/xml/fan_speed.py @@ -49,7 +49,7 @@ def _handle_xml(cls, event_bus: EventBus, xml: Element) -> HandlingResult: class SetCleanSpeed(XmlSetCommand): """Set clean speed command.""" - name = "SetCleanSpeed" + NAME = "SetCleanSpeed" get_command = GetCleanSpeed _mqtt_params = MappingProxyType({"speed": InitParam(FanSpeedLevel)}) From f80cc31e93dca2fc4e6c64c4eb66a697b221a8a5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 19:00:14 +0000 Subject: [PATCH 04/12] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- deebot_client/commands/xml/__init__.py | 5 ++--- deebot_client/commands/xml/fan_speed.py | 1 - tests/commands/xml/test_fan_speed.py | 8 ++++++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/deebot_client/commands/xml/__init__.py b/deebot_client/commands/xml/__init__.py index d9d70295c..5c6070c3a 100644 --- a/deebot_client/commands/xml/__init__.py +++ b/deebot_client/commands/xml/__init__.py @@ -8,7 +8,6 @@ from .charge_state import GetChargeState from .error import GetError - from .fan_speed import GetCleanSpeed, SetCleanSpeed from .life_span import GetLifeSpan from .play_sound import PlaySound @@ -19,14 +18,14 @@ from .common import XmlCommand __all__ = [ - "GetCleanSpeed", - "SetCleanSpeed", "GetChargeState", + "GetCleanSpeed", "GetCleanSum", "GetError", "GetLifeSpan", "GetPos", "PlaySound", + "SetCleanSpeed", ] # fmt: off diff --git a/deebot_client/commands/xml/fan_speed.py b/deebot_client/commands/xml/fan_speed.py index ce4989543..fb46a01d9 100644 --- a/deebot_client/commands/xml/fan_speed.py +++ b/deebot_client/commands/xml/fan_speed.py @@ -57,4 +57,3 @@ def __init__(self, speed: FanSpeedLevel | str) -> None: if isinstance(speed, FanSpeedLevel): speed = speed.name.lower() super().__init__({"speed": speed}) - diff --git a/tests/commands/xml/test_fan_speed.py b/tests/commands/xml/test_fan_speed.py index 140fbdb18..21eba4a1a 100644 --- a/tests/commands/xml/test_fan_speed.py +++ b/tests/commands/xml/test_fan_speed.py @@ -47,10 +47,14 @@ async def test_get_fan_speed_error(xml: str) -> None: async def test_set_fan_speed() -> None: command = SetCleanSpeed(FanSpeedLevel.STRONG) json = get_request_xml("") - await assert_command(command, json, None, command_result=CommandResult(HandlingState.SUCCESS)) + await assert_command( + command, json, None, command_result=CommandResult(HandlingState.SUCCESS) + ) async def test_set_fan_speed_error() -> None: command = SetCleanSpeed("invalid") json = get_request_xml("") - await assert_command(command, json, None, command_result=CommandResult(HandlingState.FAILED)) + await assert_command( + command, json, None, command_result=CommandResult(HandlingState.FAILED) + ) From 9c69dc73281745f32e12da6a0ab4d80cb031bb04 Mon Sep 17 00:00:00 2001 From: flubshi <4031504+flubshi@users.noreply.github.com> Date: Tue, 21 Jan 2025 20:08:51 +0100 Subject: [PATCH 05/12] Improve tests for XML clean_speed commands --- tests/commands/xml/test_fan_speed.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/tests/commands/xml/test_fan_speed.py b/tests/commands/xml/test_fan_speed.py index 21eba4a1a..7924a2777 100644 --- a/tests/commands/xml/test_fan_speed.py +++ b/tests/commands/xml/test_fan_speed.py @@ -4,7 +4,7 @@ import pytest -from deebot_client.command import CommandResult +from deebot_client.command import CommandResult, CommandWithMessageHandling from deebot_client.commands.xml import GetCleanSpeed, SetCleanSpeed from deebot_client.events import FanSpeedEvent, FanSpeedLevel from deebot_client.message import HandlingState @@ -24,7 +24,7 @@ ], ids=["standard", "strong"], ) -async def test_get_fan_speed(speed: str, expected_event: Event) -> None: +async def test_get_clean_speed(speed: str, expected_event: Event) -> None: json = get_request_xml(f"") await assert_command(GetCleanSpeed(), json, expected_event) @@ -34,7 +34,7 @@ async def test_get_fan_speed(speed: str, expected_event: Event) -> None: ["", ""], ids=["error", "no_state"], ) -async def test_get_fan_speed_error(xml: str) -> None: +async def test_get_clean_speed_error(xml: str) -> None: json = get_request_xml(xml) await assert_command( GetCleanSpeed(), @@ -44,17 +44,15 @@ async def test_get_fan_speed_error(xml: str) -> None: ) -async def test_set_fan_speed() -> None: - command = SetCleanSpeed(FanSpeedLevel.STRONG) - json = get_request_xml("") - await assert_command( - command, json, None, command_result=CommandResult(HandlingState.SUCCESS) - ) - - -async def test_set_fan_speed_error() -> None: - command = SetCleanSpeed("invalid") - json = get_request_xml("") +@pytest.mark.parametrize( + ("command", "xml", "result"), + [ + (SetCleanSpeed(FanSpeedLevel.STRONG), "", HandlingState.SUCCESS), + (SetCleanSpeed("invalid"), "", HandlingState.FAILED) + ] +) +async def test_set_clean_speed(command: CommandWithMessageHandling, xml: str, result: HandlingState) -> None: + json = get_request_xml(xml) await assert_command( - command, json, None, command_result=CommandResult(HandlingState.FAILED) + command, json, None, command_result=CommandResult(result) ) From faa24eaefc47461cc58bc1729689c67faffa2e1f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 19:15:18 +0000 Subject: [PATCH 06/12] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/commands/xml/test_fan_speed.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/commands/xml/test_fan_speed.py b/tests/commands/xml/test_fan_speed.py index 7924a2777..673e5c3fa 100644 --- a/tests/commands/xml/test_fan_speed.py +++ b/tests/commands/xml/test_fan_speed.py @@ -47,12 +47,16 @@ async def test_get_clean_speed_error(xml: str) -> None: @pytest.mark.parametrize( ("command", "xml", "result"), [ - (SetCleanSpeed(FanSpeedLevel.STRONG), "", HandlingState.SUCCESS), - (SetCleanSpeed("invalid"), "", HandlingState.FAILED) - ] + ( + SetCleanSpeed(FanSpeedLevel.STRONG), + "", + HandlingState.SUCCESS, + ), + (SetCleanSpeed("invalid"), "", HandlingState.FAILED), + ], ) -async def test_set_clean_speed(command: CommandWithMessageHandling, xml: str, result: HandlingState) -> None: +async def test_set_clean_speed( + command: CommandWithMessageHandling, xml: str, result: HandlingState +) -> None: json = get_request_xml(xml) - await assert_command( - command, json, None, command_result=CommandResult(result) - ) + await assert_command(command, json, None, command_result=CommandResult(result)) From ca7ed53eaa113f40217e2b7a309f2219d010ce12 Mon Sep 17 00:00:00 2001 From: flubshi <4031504+flubshi@users.noreply.github.com> Date: Wed, 22 Jan 2025 21:02:40 +0100 Subject: [PATCH 07/12] xml clean_speed: add XmlGetCommand --- deebot_client/commands/xml/common.py | 29 ++++++++++++++++++++++--- deebot_client/commands/xml/fan_speed.py | 17 ++++++++++++--- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/deebot_client/commands/xml/common.py b/deebot_client/commands/xml/common.py index ac026df71..ba21729fd 100644 --- a/deebot_client/commands/xml/common.py +++ b/deebot_client/commands/xml/common.py @@ -3,15 +3,25 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, cast +from typing import TYPE_CHECKING, Any, cast from xml.etree.ElementTree import Element, SubElement from defusedxml import ElementTree # type: ignore[import-untyped] -from deebot_client.command import Command, CommandWithMessageHandling, SetCommand +from deebot_client.command import ( + Command, + CommandWithMessageHandling, + GetCommand, + SetCommand, +) from deebot_client.const import DataType from deebot_client.logging_filter import get_logger -from deebot_client.message import HandlingResult, HandlingState, MessageStr +from deebot_client.message import ( + HandlingResult, + HandlingState, + MessageBodyDataDict, + MessageStr, +) if TYPE_CHECKING: from deebot_client.event_bus import EventBus @@ -86,3 +96,16 @@ class XmlSetCommand(ExecuteCommand, SetCommand, ABC): Command needs to be linked to the "get" command, for handling (updating) the sensors. """ + + +class XmlGetCommand( + XmlCommandWithMessageHandling, MessageBodyDataDict, GetCommand, ABC +): + """Xml get command.""" + + @classmethod + def handle_set_args( + cls, event_bus: EventBus, args: dict[str, Any] + ) -> HandlingResult: + """Handle arguments of set command.""" + return cls._handle_body_data_dict(event_bus, args) diff --git a/deebot_client/commands/xml/fan_speed.py b/deebot_client/commands/xml/fan_speed.py index fb46a01d9..53ef9b1cb 100644 --- a/deebot_client/commands/xml/fan_speed.py +++ b/deebot_client/commands/xml/fan_speed.py @@ -3,13 +3,13 @@ from __future__ import annotations from types import MappingProxyType -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any from deebot_client.command import InitParam from deebot_client.events import FanSpeedEvent, FanSpeedLevel from deebot_client.message import HandlingResult -from .common import XmlCommandWithMessageHandling, XmlSetCommand +from .common import XmlGetCommand, XmlSetCommand if TYPE_CHECKING: from xml.etree.ElementTree import Element @@ -17,11 +17,22 @@ from deebot_client.event_bus import EventBus -class GetCleanSpeed(XmlCommandWithMessageHandling): +class GetCleanSpeed(XmlGetCommand): """GetCleanSpeed command.""" NAME = "GetCleanSpeed" + @classmethod + def _handle_body_data_dict( + cls, event_bus: EventBus, data: dict[str, Any] + ) -> HandlingResult: + """Handle message->body->data and notify the correct event subscribers. + + :return: A message response + """ + event_bus.notify(FanSpeedEvent(FanSpeedLevel(int(data["speed"])))) + return HandlingResult.success() + @classmethod def _handle_xml(cls, event_bus: EventBus, xml: Element) -> HandlingResult: """Handle xml message and notify the correct event subscribers. From dd7a3dd6587df62ef2f65761dbfdff5e86f997ca Mon Sep 17 00:00:00 2001 From: flubshi <4031504+flubshi@users.noreply.github.com> Date: Thu, 23 Jan 2025 18:54:38 +0100 Subject: [PATCH 08/12] Implement handle_set_args of XmlGetCommand in every command --- deebot_client/commands/xml/common.py | 7 ++----- deebot_client/commands/xml/fan_speed.py | 6 +++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/deebot_client/commands/xml/common.py b/deebot_client/commands/xml/common.py index ba21729fd..22b7d3935 100644 --- a/deebot_client/commands/xml/common.py +++ b/deebot_client/commands/xml/common.py @@ -19,7 +19,6 @@ from deebot_client.message import ( HandlingResult, HandlingState, - MessageBodyDataDict, MessageStr, ) @@ -98,14 +97,12 @@ class XmlSetCommand(ExecuteCommand, SetCommand, ABC): """ -class XmlGetCommand( - XmlCommandWithMessageHandling, MessageBodyDataDict, GetCommand, ABC -): +class XmlGetCommand(XmlCommandWithMessageHandling, GetCommand, ABC): """Xml get command.""" @classmethod + @abstractmethod def handle_set_args( cls, event_bus: EventBus, args: dict[str, Any] ) -> HandlingResult: """Handle arguments of set command.""" - return cls._handle_body_data_dict(event_bus, args) diff --git a/deebot_client/commands/xml/fan_speed.py b/deebot_client/commands/xml/fan_speed.py index 53ef9b1cb..da954a51c 100644 --- a/deebot_client/commands/xml/fan_speed.py +++ b/deebot_client/commands/xml/fan_speed.py @@ -23,14 +23,14 @@ class GetCleanSpeed(XmlGetCommand): NAME = "GetCleanSpeed" @classmethod - def _handle_body_data_dict( - cls, event_bus: EventBus, data: dict[str, Any] + def handle_set_args( + cls, event_bus: EventBus, args: dict[str, Any] ) -> HandlingResult: """Handle message->body->data and notify the correct event subscribers. :return: A message response """ - event_bus.notify(FanSpeedEvent(FanSpeedLevel(int(data["speed"])))) + event_bus.notify(FanSpeedEvent(FanSpeedLevel(int(args["speed"])))) return HandlingResult.success() @classmethod From 4c6e8628cad77067cd84e16895238ca5f867f887 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Wed, 29 Jan 2025 12:58:40 +0000 Subject: [PATCH 09/12] enable p2p for xml commands --- deebot_client/commands/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/deebot_client/commands/__init__.py b/deebot_client/commands/__init__.py index 5fb527c47..fc4574353 100644 --- a/deebot_client/commands/__init__.py +++ b/deebot_client/commands/__init__.py @@ -11,6 +11,9 @@ COMMANDS as JSON_COMMANDS, COMMANDS_WITH_MQTT_P2P_HANDLING as JSON_COMMANDS_WITH_MQTT_P2P_HANDLING, ) +from .xml import ( + COMMANDS_WITH_MQTT_P2P_HANDLING as XML_COMMANDS_WITH_MQTT_P2P_HANDLING, +) if TYPE_CHECKING: from deebot_client.command import Command, CommandMqttP2P @@ -18,7 +21,8 @@ COMMANDS: dict[DataType, dict[str, type[Command]]] = {DataType.JSON: JSON_COMMANDS} COMMANDS_WITH_MQTT_P2P_HANDLING: dict[DataType, dict[str, type[CommandMqttP2P]]] = { - DataType.JSON: JSON_COMMANDS_WITH_MQTT_P2P_HANDLING + DataType.JSON: JSON_COMMANDS_WITH_MQTT_P2P_HANDLING, + DataType.XML: XML_COMMANDS_WITH_MQTT_P2P_HANDLING, } From 9a239b24aec109113dafdc74c05957a2f028a417 Mon Sep 17 00:00:00 2001 From: flubshi <4031504+flubshi@users.noreply.github.com> Date: Sun, 9 Feb 2025 18:44:53 +0100 Subject: [PATCH 10/12] FanSpeed: extend for XML enum --- deebot_client/commands/xml/fan_speed.py | 23 +++++-------------- deebot_client/events/fan_speed.py | 30 ++++++++++++++++++++----- tests/commands/xml/test_fan_speed.py | 11 +++++---- 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/deebot_client/commands/xml/fan_speed.py b/deebot_client/commands/xml/fan_speed.py index da954a51c..c275a3180 100644 --- a/deebot_client/commands/xml/fan_speed.py +++ b/deebot_client/commands/xml/fan_speed.py @@ -30,7 +30,7 @@ def handle_set_args( :return: A message response """ - event_bus.notify(FanSpeedEvent(FanSpeedLevel(int(args["speed"])))) + event_bus.notify(FanSpeedEvent(FanSpeedLevel.from_xml(str(args["speed"])))) return HandlingResult.success() @classmethod @@ -42,19 +42,8 @@ def _handle_xml(cls, event_bus: EventBus, xml: Element) -> HandlingResult: if xml.attrib.get("ret") != "ok" or not (speed := xml.attrib.get("speed")): return HandlingResult.analyse() - event: FanSpeedEvent | None = None - - match speed.lower(): - case "standard": - event = FanSpeedEvent(FanSpeedLevel.STANDARD) - case "strong": - event = FanSpeedEvent(FanSpeedLevel.STRONG) - - if event: - event_bus.notify(event) - return HandlingResult.success() - - return HandlingResult.analyse() + event_bus.notify(FanSpeedEvent(FanSpeedLevel.from_xml(speed))) + return HandlingResult.success() class SetCleanSpeed(XmlSetCommand): @@ -64,7 +53,5 @@ class SetCleanSpeed(XmlSetCommand): get_command = GetCleanSpeed _mqtt_params = MappingProxyType({"speed": InitParam(FanSpeedLevel)}) - def __init__(self, speed: FanSpeedLevel | str) -> None: - if isinstance(speed, FanSpeedLevel): - speed = speed.name.lower() - super().__init__({"speed": speed}) + def __init__(self, speed: FanSpeedLevel) -> None: + super().__init__({"speed": speed.xml_value}) diff --git a/deebot_client/events/fan_speed.py b/deebot_client/events/fan_speed.py index 00fcc3bb3..a350f3ba9 100644 --- a/deebot_client/events/fan_speed.py +++ b/deebot_client/events/fan_speed.py @@ -4,6 +4,7 @@ from dataclasses import dataclass from enum import IntEnum, unique +from typing import Self from .base import Event @@ -12,13 +13,30 @@ class FanSpeedLevel(IntEnum): """Enum class for all possible fan speed levels.""" + xml_value: str + + def __new__(cls, value: int, xml_value: str = "") -> Self: + """Get new instance.""" + obj = int.__new__(cls) + obj._value_ = value + obj.xml_value = xml_value + return obj + + @classmethod + def from_xml(cls, value: str) -> FanSpeedLevel: + """Get FanSpeedLevel from xml value.""" + for fan_speed_level in FanSpeedLevel: + if fan_speed_level.xml_value == value: + return fan_speed_level + + msg = f"{value} is not a valid {cls.__name__}" + raise ValueError(msg) + # Values should be sort from low to high on their meanings - QUIET = 1000 - STANDARD = -1 - NORMAL = 0 - MAX = 1 - STRONG = -2 - MAX_PLUS = 2 + QUIET = 1000, "" + NORMAL = 0, "standard" + MAX = 1, "strong" + MAX_PLUS = 2, "" @dataclass(frozen=True) diff --git a/tests/commands/xml/test_fan_speed.py b/tests/commands/xml/test_fan_speed.py index 673e5c3fa..cafebe990 100644 --- a/tests/commands/xml/test_fan_speed.py +++ b/tests/commands/xml/test_fan_speed.py @@ -19,8 +19,8 @@ @pytest.mark.parametrize( ("speed", "expected_event"), [ - ("standard", FanSpeedEvent(FanSpeedLevel.STANDARD)), - ("strong", FanSpeedEvent(FanSpeedLevel.STRONG)), + ("standard", FanSpeedEvent(FanSpeedLevel.NORMAL)), + ("strong", FanSpeedEvent(FanSpeedLevel.MAX)), ], ids=["standard", "strong"], ) @@ -31,8 +31,8 @@ async def test_get_clean_speed(speed: str, expected_event: Event) -> None: @pytest.mark.parametrize( "xml", - ["", ""], - ids=["error", "no_state"], + [""], + ids=["error"], ) async def test_get_clean_speed_error(xml: str) -> None: json = get_request_xml(xml) @@ -48,11 +48,10 @@ async def test_get_clean_speed_error(xml: str) -> None: ("command", "xml", "result"), [ ( - SetCleanSpeed(FanSpeedLevel.STRONG), + SetCleanSpeed(FanSpeedLevel.MAX), "", HandlingState.SUCCESS, ), - (SetCleanSpeed("invalid"), "", HandlingState.FAILED), ], ) async def test_set_clean_speed( From 07cd5dbf20f6763288a8d2e5aa88960793efdb7a Mon Sep 17 00:00:00 2001 From: flubshi <4031504+flubshi@users.noreply.github.com> Date: Mon, 10 Feb 2025 23:11:27 +0100 Subject: [PATCH 11/12] XML command SetCleanSpeed: accept speed as string --- deebot_client/commands/xml/fan_speed.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/deebot_client/commands/xml/fan_speed.py b/deebot_client/commands/xml/fan_speed.py index c275a3180..7130b0d68 100644 --- a/deebot_client/commands/xml/fan_speed.py +++ b/deebot_client/commands/xml/fan_speed.py @@ -53,5 +53,7 @@ class SetCleanSpeed(XmlSetCommand): get_command = GetCleanSpeed _mqtt_params = MappingProxyType({"speed": InitParam(FanSpeedLevel)}) - def __init__(self, speed: FanSpeedLevel) -> None: - super().__init__({"speed": speed.xml_value}) + def __init__(self, speed: FanSpeedLevel | str) -> None: + if isinstance(speed, FanSpeedLevel): + speed = speed.xml_value + super().__init__({"speed": speed}) From 770d72d2bfea707151339cf098bbf5224f20561d Mon Sep 17 00:00:00 2001 From: flubshi <4031504+flubshi@users.noreply.github.com> Date: Thu, 13 Feb 2025 22:53:33 +0100 Subject: [PATCH 12/12] WIP: MqttP2P --- deebot_client/command.py | 8 +++++--- deebot_client/commands/xml/common.py | 23 ++++++++++++++++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/deebot_client/command.py b/deebot_client/command.py index dc0e0cf38..6afb94be7 100644 --- a/deebot_client/command.py +++ b/deebot_client/command.py @@ -323,9 +323,11 @@ def _pop_or_raise(name: str, type_: type, data: dict[str, Any]) -> Any: value = data.pop(name) try: return type_(value) - except ValueError as err: - msg = f'Could not convert "{value}" of {name} into {type_}' - raise DeebotError(msg) from err + except ValueError: + # TODO: Workaround to map out custom enums + return type_.from_xml(value) + # msg = f'Could not convert "{value}" of {name} into {type_}' + # raise DeebotError(msg) from err class GetCommand(CommandWithMessageHandling, ABC): diff --git a/deebot_client/commands/xml/common.py b/deebot_client/commands/xml/common.py index 22b7d3935..77a78b8e5 100644 --- a/deebot_client/commands/xml/common.py +++ b/deebot_client/commands/xml/common.py @@ -10,6 +10,7 @@ from deebot_client.command import ( Command, + CommandMqttP2P, CommandWithMessageHandling, GetCommand, SetCommand, @@ -90,7 +91,27 @@ def _handle_xml(cls, _: EventBus, xml: Element) -> HandlingResult: return HandlingResult(HandlingState.FAILED) -class XmlSetCommand(ExecuteCommand, SetCommand, ABC): +class XmlCommandMqttP2P(XmlCommand, CommandMqttP2P, ABC): + """Json base command for mqtt p2p channel.""" + + @classmethod + def create_from_mqtt(cls, payload: str | bytes | bytearray) -> CommandMqttP2P: + """Create a command from the mqtt data.""" + xml = ElementTree.fromstring(payload) + return cls._create_from_mqtt(xml.attrib) + + def handle_mqtt_p2p( + self, event_bus: EventBus, response_payload: str | bytes | bytearray + ) -> None: + """Handle response received over the mqtt channel "p2p".""" + self._handle_mqtt_p2p(event_bus, response_payload) + + @abstractmethod + def _handle_mqtt_p2p(self, event_bus: EventBus, response: dict[str, Any]) -> None: + """Handle response received over the mqtt channel "p2p".""" + + +class XmlSetCommand(ExecuteCommand, SetCommand, XmlCommandMqttP2P, ABC): """Xml base set command. Command needs to be linked to the "get" command, for handling (updating) the sensors.