From f97038f6b8a60f6b4aa107fb309262cacf3b9607 Mon Sep 17 00:00:00 2001 From: Luca De Petrillo <972242+lukakama@users.noreply.github.com> Date: Fri, 5 Jan 2024 17:17:14 +0000 Subject: [PATCH 1/7] Added support for get/setWashInfo commands and onWashInfo message --- deebot_client/capabilities.py | 3 ++ deebot_client/commands/json/__init__.py | 8 +++ deebot_client/commands/json/wash_info.py | 66 ++++++++++++++++++++++++ deebot_client/events/__init__.py | 3 ++ deebot_client/events/wash_info.py | 23 +++++++++ deebot_client/hardware/deebot/p1jij8.py | 20 +++++++ deebot_client/messages/json/__init__.py | 2 + deebot_client/messages/json/wash_info.py | 30 +++++++++++ tests/commands/json/test_wash_info.py | 37 +++++++++++++ 9 files changed, 192 insertions(+) create mode 100644 deebot_client/commands/json/wash_info.py create mode 100644 deebot_client/events/wash_info.py create mode 100644 deebot_client/messages/json/wash_info.py create mode 100644 tests/commands/json/test_wash_info.py diff --git a/deebot_client/capabilities.py b/deebot_client/capabilities.py index 088ce1c0b..c47cb09bc 100644 --- a/deebot_client/capabilities.py +++ b/deebot_client/capabilities.py @@ -51,6 +51,7 @@ from deebot_client.command import Command, SetCommand from deebot_client.events.efficiency_mode import EfficiencyMode, EfficiencyModeEvent + from deebot_client.events.wash_info import WashInfoEvent, WashMode from deebot_client.models import CleanAction, CleanMode @@ -131,6 +132,8 @@ class CapabilityClean: log: CapabilityEvent[CleanLogEvent] | None = None preference: CapabilitySetEnable[CleanPreferenceEvent] | None = None work_mode: CapabilitySetTypes[WorkModeEvent, WorkMode] | None = None + wash_info_mode: CapabilitySetTypes[WashInfoEvent, WashMode] | None = None + wash_info_hot_wash_amount: CapabilitySet[WashInfoEvent, int] | None = None @dataclass(frozen=True) diff --git a/deebot_client/commands/json/__init__.py b/deebot_client/commands/json/__init__.py index f731c80df..698844ace 100644 --- a/deebot_client/commands/json/__init__.py +++ b/deebot_client/commands/json/__init__.py @@ -40,6 +40,7 @@ from .true_detect import GetTrueDetect, SetTrueDetect from .voice_assistant_state import GetVoiceAssistantState, SetVoiceAssistantState from .volume import GetVolume, SetVolume +from .wash_info import GetWashInfo, SetWashInfoHotWashAmount, SetWashInfoMode from .water_info import GetWaterInfo, SetWaterInfo from .work_mode import GetWorkMode, SetWorkMode @@ -97,6 +98,9 @@ "SetVoiceAssistantState", "GetVolume", "SetVolume", + "GetWashInfo", + "SetWashInfoHotWashAmount", + "SetWashInfoMode", "GetWaterInfo", "SetWaterInfo", "GetWorkMode", @@ -183,6 +187,10 @@ GetVolume, SetVolume, + GetWashInfo, + SetWashInfoHotWashAmount, + SetWashInfoMode, + GetWaterInfo, SetWaterInfo, diff --git a/deebot_client/commands/json/wash_info.py b/deebot_client/commands/json/wash_info.py new file mode 100644 index 000000000..eeb999567 --- /dev/null +++ b/deebot_client/commands/json/wash_info.py @@ -0,0 +1,66 @@ +"""WashInfo command module.""" + +from types import MappingProxyType +from typing import Any + +from deebot_client.command import InitParam +from deebot_client.event_bus import EventBus +from deebot_client.events import WashInfoEvent, WashMode +from deebot_client.message import HandlingResult + +from .common import JsonGetCommand, JsonSetCommand + + +class GetWashInfo(JsonGetCommand): + """Get wash info command.""" + + name = "getWashInfo" + + @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( + WashInfoEvent( + mode=WashMode(int(data["mode"])), + hot_wash_amount=data["hot_wash_amount"], + interval=data["interval"], + ) + ) + return HandlingResult.success() + + +class SetWashInfoMode(JsonSetCommand): + """Set wash info command for mode.""" + + name = "setWashInfo" + get_command = GetWashInfo + _mqtt_params = MappingProxyType( + { + "mode": InitParam(int), + } + ) + + def __init__(self, mode: WashMode | str) -> None: + if isinstance(mode, str): + mode = WashMode.get(mode) + super().__init__({"mode": mode.value}) + + +class SetWashInfoHotWashAmount(JsonSetCommand): + """Set wash info command for hot_wash_amount.""" + + name = "setWashInfo" + get_command = GetWashInfo + _mqtt_params = MappingProxyType( + { + "hot_wash_amount": InitParam(int), + } + ) + + def __init__(self, hot_wash_amount: int) -> None: + super().__init__({"hot_wash_amount": hot_wash_amount}) diff --git a/deebot_client/events/__init__.py b/deebot_client/events/__init__.py index de47656da..1da36c50e 100644 --- a/deebot_client/events/__init__.py +++ b/deebot_client/events/__init__.py @@ -24,6 +24,7 @@ PositionType, ) from .network import NetworkInfoEvent +from .wash_info import WashInfoEvent, WashMode from .water_info import WaterAmount, WaterInfoEvent from .work_mode import WorkMode, WorkModeEvent @@ -52,6 +53,8 @@ "PositionType", "PositionsEvent", "SweepModeEvent", + "WashMode", + "WashInfoEvent", "WaterAmount", "WaterInfoEvent", "WorkMode", diff --git a/deebot_client/events/wash_info.py b/deebot_client/events/wash_info.py new file mode 100644 index 000000000..b1f862470 --- /dev/null +++ b/deebot_client/events/wash_info.py @@ -0,0 +1,23 @@ +"""Wash info event module.""" +from dataclasses import dataclass + +from deebot_client.util import DisplayNameIntEnum + +from .base import Event + + +class WashMode(DisplayNameIntEnum): + """Enum class for all possible wash modes.""" + + STANDARD = 0 + HOT = 1 + + +@dataclass(frozen=True) +class WashInfoEvent(Event): + """Wash info event representation.""" + + mode: WashMode + # None means no data available + interval: int + hot_wash_amount: int diff --git a/deebot_client/hardware/deebot/p1jij8.py b/deebot_client/hardware/deebot/p1jij8.py index 8f1594679..dd7a49bda 100644 --- a/deebot_client/hardware/deebot/p1jij8.py +++ b/deebot_client/hardware/deebot/p1jij8.py @@ -51,6 +51,11 @@ from deebot_client.commands.json.stats import GetStats, GetTotalStats from deebot_client.commands.json.true_detect import GetTrueDetect, SetTrueDetect from deebot_client.commands.json.volume import GetVolume, SetVolume +from deebot_client.commands.json.wash_info import ( + GetWashInfo, + SetWashInfoHotWashAmount, + SetWashInfoMode, +) from deebot_client.commands.json.water_info import GetWaterInfo, SetWaterInfo from deebot_client.commands.json.work_mode import GetWorkMode, SetWorkMode from deebot_client.const import DataType @@ -88,6 +93,7 @@ WorkMode, WorkModeEvent, ) +from deebot_client.events.wash_info import WashInfoEvent, WashMode from deebot_client.models import StaticDeviceInfo from deebot_client.util import short_name @@ -124,6 +130,20 @@ WorkMode.VACUUM_AND_MOP, ), ), + wash_info_mode=CapabilitySetTypes( + event=WashInfoEvent, + get=[GetWashInfo()], + set=SetWashInfoMode, + types=( + WashMode.STANDARD, + WashMode.HOT, + ), + ), + wash_info_hot_wash_amount=CapabilitySet( + event=WashInfoEvent, + get=[GetWashInfo()], + set=SetWashInfoHotWashAmount, + ), ), custom=CapabilityCustomCommand( event=CustomCommandEvent, get=[], set=CustomCommand diff --git a/deebot_client/messages/json/__init__.py b/deebot_client/messages/json/__init__.py index c42050c70..394fd3133 100644 --- a/deebot_client/messages/json/__init__.py +++ b/deebot_client/messages/json/__init__.py @@ -6,6 +6,7 @@ from .battery import OnBattery from .map import OnMapSetV2 from .stats import ReportStats +from .wash_info import OnWashInfo if TYPE_CHECKING: from deebot_client.message import Message @@ -13,6 +14,7 @@ __all__ = [ "OnBattery", "OnMapSetV2", + "OnWashInfo", "ReportStats", ] diff --git a/deebot_client/messages/json/wash_info.py b/deebot_client/messages/json/wash_info.py new file mode 100644 index 000000000..6fd020d5f --- /dev/null +++ b/deebot_client/messages/json/wash_info.py @@ -0,0 +1,30 @@ +"""WashInfo messages.""" +from typing import Any + +from deebot_client.event_bus import EventBus +from deebot_client.events import WashInfoEvent +from deebot_client.events.wash_info import WashMode +from deebot_client.message import HandlingResult, MessageBodyDataDict + + +class OnWashInfo(MessageBodyDataDict): + """On battery message.""" + + name = "onWashInfo" + + @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( + WashInfoEvent( + mode=WashMode(int(data["mode"])), + hot_wash_amount=data["hot_wash_amount"], + interval=data["interval"], + ) + ) + return HandlingResult.success() diff --git a/tests/commands/json/test_wash_info.py b/tests/commands/json/test_wash_info.py new file mode 100644 index 000000000..76c9cebc2 --- /dev/null +++ b/tests/commands/json/test_wash_info.py @@ -0,0 +1,37 @@ +from typing import Any + +import pytest + +from deebot_client.commands.json import ( + GetWashInfo, +) +from deebot_client.events import WashInfoEvent, WashMode +from tests.helpers import ( + get_request_json, + get_success_body, + verify_DisplayNameEnum_unique, +) + +from . import assert_command + + +def test_WashInfo_unique() -> None: + verify_DisplayNameEnum_unique(WashMode) + + +@pytest.mark.parametrize( + ("json", "expected"), + [ + ( + {"mode": 0, "interval": 12, "hot_wash_amount": 1}, + WashInfoEvent(WashMode.STANDARD, 12, 1), + ), + ( + {"mode": 1, "interval": 6, "hot_wash_amount": 3}, + WashInfoEvent(WashMode.HOT, 6, 3), + ), + ], +) +async def test_GetWashInfo(json: dict[str, Any], expected: WashInfoEvent) -> None: + json = get_request_json(get_success_body(json)) + await assert_command(GetWashInfo(), json, expected) From 7ab2967554f24c6820cfcbd917e430c7e05adfdc Mon Sep 17 00:00:00 2001 From: Luca De Petrillo <972242+lukakama@users.noreply.github.com> Date: Sun, 18 Feb 2024 18:50:22 +0100 Subject: [PATCH 2/7] Apply suggestions from code review Co-authored-by: Robert Resch --- deebot_client/commands/json/wash_info.py | 47 ++++++------------------ deebot_client/events/wash_info.py | 5 +-- 2 files changed, 14 insertions(+), 38 deletions(-) diff --git a/deebot_client/commands/json/wash_info.py b/deebot_client/commands/json/wash_info.py index eeb999567..4cf45c83a 100644 --- a/deebot_client/commands/json/wash_info.py +++ b/deebot_client/commands/json/wash_info.py @@ -16,51 +16,28 @@ class GetWashInfo(JsonGetCommand): name = "getWashInfo" - @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( - WashInfoEvent( - mode=WashMode(int(data["mode"])), - hot_wash_amount=data["hot_wash_amount"], - interval=data["interval"], - ) - ) - return HandlingResult.success() - -class SetWashInfoMode(JsonSetCommand): - """Set wash info command for mode.""" +class SetWashInfo(JsonSetCommand): + """Set wash info command.""" name = "setWashInfo" get_command = GetWashInfo _mqtt_params = MappingProxyType( { "mode": InitParam(int), + "hot_wash_amount": InitParam(int), } ) - def __init__(self, mode: WashMode | str) -> None: + def __init__(self, mode: WashMode | str| None = None, hot_wash_amount: int | None) -> None: + args = {} if isinstance(mode, str): mode = WashMode.get(mode) - super().__init__({"mode": mode.value}) - - -class SetWashInfoHotWashAmount(JsonSetCommand): - """Set wash info command for hot_wash_amount.""" - - name = "setWashInfo" - get_command = GetWashInfo - _mqtt_params = MappingProxyType( - { - "hot_wash_amount": InitParam(int), - } - ) - - def __init__(self, hot_wash_amount: int) -> None: - super().__init__({"hot_wash_amount": hot_wash_amount}) + + if mode is not None: + args["mode"] = mode + + if hot_wash_amount is not None: + agrs["hot_wash_amount"] = hot_wash_amount + super().__init__(args) diff --git a/deebot_client/events/wash_info.py b/deebot_client/events/wash_info.py index b1f862470..e303986fa 100644 --- a/deebot_client/events/wash_info.py +++ b/deebot_client/events/wash_info.py @@ -18,6 +18,5 @@ class WashInfoEvent(Event): """Wash info event representation.""" mode: WashMode - # None means no data available - interval: int - hot_wash_amount: int + interval: int | None + hot_wash_amount: int | None From 7d08f568d547861cfb1d0e3f1ca362b74ba76302 Mon Sep 17 00:00:00 2001 From: Luca De Petrillo <972242+lukakama@users.noreply.github.com> Date: Sun, 18 Feb 2024 18:50:38 +0100 Subject: [PATCH 3/7] Apply suggestions from code review Co-authored-by: Robert Resch --- deebot_client/commands/json/wash_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deebot_client/commands/json/wash_info.py b/deebot_client/commands/json/wash_info.py index 4cf45c83a..d7fbfc460 100644 --- a/deebot_client/commands/json/wash_info.py +++ b/deebot_client/commands/json/wash_info.py @@ -11,7 +11,7 @@ from .common import JsonGetCommand, JsonSetCommand -class GetWashInfo(JsonGetCommand): +class GetWashInfo(OnWaterInfo, JsonGetCommand): """Get wash info command.""" name = "getWashInfo" From 1e521b0bed2ea487a57a689728b5c7239dd147e4 Mon Sep 17 00:00:00 2001 From: Luca De Petrillo <972242+lukakama@users.noreply.github.com> Date: Sun, 18 Feb 2024 19:37:25 +0000 Subject: [PATCH 4/7] Refactored code to use a single WashInfo command class --- deebot_client/capabilities.py | 15 ++++++++++++--- deebot_client/commands/json/__init__.py | 8 +++----- deebot_client/commands/json/wash_info.py | 22 ++++++++++++---------- deebot_client/events/wash_info.py | 4 +++- deebot_client/hardware/deebot/p1jij8.py | 15 +++++---------- deebot_client/messages/json/wash_info.py | 18 +++++++++++++----- tests/commands/json/test_wash_info.py | 24 +++++++++++++++++++++++- 7 files changed, 71 insertions(+), 35 deletions(-) diff --git a/deebot_client/capabilities.py b/deebot_client/capabilities.py index c47cb09bc..663fe0514 100644 --- a/deebot_client/capabilities.py +++ b/deebot_client/capabilities.py @@ -43,6 +43,7 @@ WorkMode, WorkModeEvent, ) +from deebot_client.events.wash_info import WashInfoEvent if TYPE_CHECKING: from collections.abc import Callable @@ -50,8 +51,9 @@ from _typeshed import DataclassInstance from deebot_client.command import Command, SetCommand + from deebot_client.commands.json.wash_info import SetWashInfo from deebot_client.events.efficiency_mode import EfficiencyMode, EfficiencyModeEvent - from deebot_client.events.wash_info import WashInfoEvent, WashMode + from deebot_client.events.wash_info import WashMode from deebot_client.models import CleanAction, CleanMode @@ -122,6 +124,14 @@ class CapabilityCleanAction: area: Callable[[CleanMode, str, int], Command] +@dataclass(frozen=True, kw_only=True) +class CapabilityWashInfo(CapabilityEvent[WashInfoEvent]): + """Capabilities for wash handling.""" + + set: Callable[[WashMode | None, int | None], SetWashInfo] + wash_modes: tuple[WashMode, ...] + + @dataclass(frozen=True, kw_only=True) class CapabilityClean: """Capabilities for clean.""" @@ -132,8 +142,7 @@ class CapabilityClean: log: CapabilityEvent[CleanLogEvent] | None = None preference: CapabilitySetEnable[CleanPreferenceEvent] | None = None work_mode: CapabilitySetTypes[WorkModeEvent, WorkMode] | None = None - wash_info_mode: CapabilitySetTypes[WashInfoEvent, WashMode] | None = None - wash_info_hot_wash_amount: CapabilitySet[WashInfoEvent, int] | None = None + wash_info: CapabilityWashInfo | None = None @dataclass(frozen=True) diff --git a/deebot_client/commands/json/__init__.py b/deebot_client/commands/json/__init__.py index 698844ace..7c681f9bb 100644 --- a/deebot_client/commands/json/__init__.py +++ b/deebot_client/commands/json/__init__.py @@ -40,7 +40,7 @@ from .true_detect import GetTrueDetect, SetTrueDetect from .voice_assistant_state import GetVoiceAssistantState, SetVoiceAssistantState from .volume import GetVolume, SetVolume -from .wash_info import GetWashInfo, SetWashInfoHotWashAmount, SetWashInfoMode +from .wash_info import GetWashInfo, SetWashInfo from .water_info import GetWaterInfo, SetWaterInfo from .work_mode import GetWorkMode, SetWorkMode @@ -99,8 +99,7 @@ "GetVolume", "SetVolume", "GetWashInfo", - "SetWashInfoHotWashAmount", - "SetWashInfoMode", + "SetWashInfo", "GetWaterInfo", "SetWaterInfo", "GetWorkMode", @@ -188,8 +187,7 @@ SetVolume, GetWashInfo, - SetWashInfoHotWashAmount, - SetWashInfoMode, + SetWashInfo, GetWaterInfo, SetWaterInfo, diff --git a/deebot_client/commands/json/wash_info.py b/deebot_client/commands/json/wash_info.py index d7fbfc460..7c3f3a616 100644 --- a/deebot_client/commands/json/wash_info.py +++ b/deebot_client/commands/json/wash_info.py @@ -1,23 +1,22 @@ """WashInfo command module.""" +from __future__ import annotations from types import MappingProxyType from typing import Any from deebot_client.command import InitParam -from deebot_client.event_bus import EventBus -from deebot_client.events import WashInfoEvent, WashMode -from deebot_client.message import HandlingResult +from deebot_client.events import WashMode +from deebot_client.messages.json.wash_info import OnWashInfo from .common import JsonGetCommand, JsonSetCommand -class GetWashInfo(OnWaterInfo, JsonGetCommand): +class GetWashInfo(OnWashInfo, JsonGetCommand): """Get wash info command.""" name = "getWashInfo" - class SetWashInfo(JsonSetCommand): """Set wash info command.""" @@ -30,14 +29,17 @@ class SetWashInfo(JsonSetCommand): } ) - def __init__(self, mode: WashMode | str| None = None, hot_wash_amount: int | None) -> None: - args = {} + def __init__( + self, mode: WashMode | str | None = None, hot_wash_amount: int | None = None + ) -> None: + args: dict[str, Any] = {} + if isinstance(mode, str): mode = WashMode.get(mode) - + if mode is not None: args["mode"] = mode - + if hot_wash_amount is not None: - agrs["hot_wash_amount"] = hot_wash_amount + args["hot_wash_amount"] = hot_wash_amount super().__init__(args) diff --git a/deebot_client/events/wash_info.py b/deebot_client/events/wash_info.py index e303986fa..87ee3212f 100644 --- a/deebot_client/events/wash_info.py +++ b/deebot_client/events/wash_info.py @@ -1,4 +1,6 @@ """Wash info event module.""" +from __future__ import annotations + from dataclasses import dataclass from deebot_client.util import DisplayNameIntEnum @@ -17,6 +19,6 @@ class WashMode(DisplayNameIntEnum): class WashInfoEvent(Event): """Wash info event representation.""" - mode: WashMode + mode: WashMode | None interval: int | None hot_wash_amount: int | None diff --git a/deebot_client/hardware/deebot/p1jij8.py b/deebot_client/hardware/deebot/p1jij8.py index dd7a49bda..55cdb5e80 100644 --- a/deebot_client/hardware/deebot/p1jij8.py +++ b/deebot_client/hardware/deebot/p1jij8.py @@ -15,6 +15,7 @@ CapabilitySettings, CapabilitySetTypes, CapabilityStats, + CapabilityWashInfo, ) from deebot_client.commands.json.advanced_mode import GetAdvancedMode, SetAdvancedMode from deebot_client.commands.json.battery import GetBattery @@ -53,8 +54,7 @@ from deebot_client.commands.json.volume import GetVolume, SetVolume from deebot_client.commands.json.wash_info import ( GetWashInfo, - SetWashInfoHotWashAmount, - SetWashInfoMode, + SetWashInfo, ) from deebot_client.commands.json.water_info import GetWaterInfo, SetWaterInfo from deebot_client.commands.json.work_mode import GetWorkMode, SetWorkMode @@ -130,20 +130,15 @@ WorkMode.VACUUM_AND_MOP, ), ), - wash_info_mode=CapabilitySetTypes( + wash_info=CapabilityWashInfo( event=WashInfoEvent, get=[GetWashInfo()], - set=SetWashInfoMode, - types=( + set=SetWashInfo, + wash_modes=( WashMode.STANDARD, WashMode.HOT, ), ), - wash_info_hot_wash_amount=CapabilitySet( - event=WashInfoEvent, - get=[GetWashInfo()], - set=SetWashInfoHotWashAmount, - ), ), custom=CapabilityCustomCommand( event=CustomCommandEvent, get=[], set=CustomCommand diff --git a/deebot_client/messages/json/wash_info.py b/deebot_client/messages/json/wash_info.py index 6fd020d5f..f88e84805 100644 --- a/deebot_client/messages/json/wash_info.py +++ b/deebot_client/messages/json/wash_info.py @@ -1,11 +1,15 @@ """WashInfo messages.""" -from typing import Any +from __future__ import annotations + +from typing import TYPE_CHECKING, Any -from deebot_client.event_bus import EventBus from deebot_client.events import WashInfoEvent from deebot_client.events.wash_info import WashMode from deebot_client.message import HandlingResult, MessageBodyDataDict +if TYPE_CHECKING: + from deebot_client.event_bus import EventBus + class OnWashInfo(MessageBodyDataDict): """On battery message.""" @@ -20,11 +24,15 @@ def _handle_body_data_dict( :return: A message response """ + mode = data.get("mode") + if isinstance(mode, int): + mode = WashMode(mode) + event_bus.notify( WashInfoEvent( - mode=WashMode(int(data["mode"])), - hot_wash_amount=data["hot_wash_amount"], - interval=data["interval"], + mode=mode, + hot_wash_amount=data.get("hot_wash_amount"), + interval=data.get("interval"), ) ) return HandlingResult.success() diff --git a/tests/commands/json/test_wash_info.py b/tests/commands/json/test_wash_info.py index 76c9cebc2..748ea468b 100644 --- a/tests/commands/json/test_wash_info.py +++ b/tests/commands/json/test_wash_info.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Any import pytest @@ -5,6 +7,7 @@ from deebot_client.commands.json import ( GetWashInfo, ) +from deebot_client.commands.json.wash_info import SetWashInfo from deebot_client.events import WashInfoEvent, WashMode from tests.helpers import ( get_request_json, @@ -12,7 +15,7 @@ verify_DisplayNameEnum_unique, ) -from . import assert_command +from . import assert_command, assert_set_command def test_WashInfo_unique() -> None: @@ -35,3 +38,22 @@ def test_WashInfo_unique() -> None: async def test_GetWashInfo(json: dict[str, Any], expected: WashInfoEvent) -> None: json = get_request_json(get_success_body(json)) await assert_command(GetWashInfo(), json, expected) + + +@pytest.mark.parametrize(("value"), [WashMode.HOT, "hot"]) +async def test_SetWashInfo_mode(value: WashMode | str) -> None: + command = SetWashInfo(mode=value) + args = {"mode": WashMode.HOT} + await assert_set_command(command, args, WashInfoEvent(WashMode.HOT, None, None)) + + +def test_SetWashInfo_mode_inexisting_value() -> None: + with pytest.raises(ValueError, match="'INEXSTING' is not a valid WashMode member"): + SetWashInfo(mode="inexsting") + + +@pytest.mark.parametrize(("value"), [1]) +async def test_SetWashInfo_hot_wash_amount(value: int) -> None: + command = SetWashInfo(hot_wash_amount=value) + args = {"hot_wash_amount": value} + await assert_set_command(command, args, WashInfoEvent(None, None, value)) From ae8050e3acbfe8bb0a8ceb8adfd947d8942a64f4 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Fri, 23 Feb 2024 09:53:26 +0000 Subject: [PATCH 5/7] Add default value to InitParam --- deebot_client/command.py | 28 +++++++++++++++--------- deebot_client/commands/json/common.py | 4 +++- deebot_client/commands/json/life_span.py | 2 +- deebot_client/commands/json/ota.py | 4 +++- deebot_client/commands/json/wash_info.py | 4 ++-- 5 files changed, 27 insertions(+), 15 deletions(-) diff --git a/deebot_client/command.py b/deebot_client/command.py index b5f73320c..22c9ee80a 100644 --- a/deebot_client/command.py +++ b/deebot_client/command.py @@ -9,7 +9,13 @@ from deebot_client.events import AvailabilityEvent from deebot_client.exceptions import ApiTimeoutError, DeebotError -from .const import PATH_API_IOT_DEVMANAGER, REQUEST_HEADERS, DataType +from .const import ( + PATH_API_IOT_DEVMANAGER, + REQUEST_HEADERS, + UNDEFINED, + DataType, + UndefinedType, +) from .logging_filter import get_logger from .message import HandlingResult, HandlingState, Message @@ -252,7 +258,8 @@ class InitParam: """Init param.""" type_: type - name: str | None = None + name: str | None = field(kw_only=True, default=None) + default: UndefinedType | Any = field(kw_only=True, default=UNDEFINED) class CommandMqttP2P(Command, ABC): @@ -276,7 +283,7 @@ def create_from_mqtt(cls, data: dict[str, Any]) -> CommandMqttP2P: # Remove field data.pop(name, None) else: - values[param.name or name] = _pop_or_raise(name, param.type_, data) + values[param.name or name] = _pop_or_raise(name, param, data) if data: _LOGGER.debug("Following data will be ignored: %s", data) @@ -284,16 +291,17 @@ def create_from_mqtt(cls, data: dict[str, Any]) -> CommandMqttP2P: return cls(**values) -def _pop_or_raise(name: str, type_: type, data: dict[str, Any]) -> Any: - try: - value = data.pop(name) - except KeyError as err: +def _pop_or_raise(name: str, param: InitParam, data: dict[str, Any]) -> Any: + if name not in data: + if param.default is not UNDEFINED: + return param.default msg = f'"{name}" is missing in {data}' - raise DeebotError(msg) from err + raise DeebotError(msg) + value = data[name] try: - return type_(value) + return param.type_(value) except ValueError as err: - msg = f'Could not convert "{value}" of {name} into {type_}' + msg = f'Could not convert "{value}" of {name} into {param.type_}' raise DeebotError(msg) from err diff --git a/deebot_client/commands/json/common.py b/deebot_client/commands/json/common.py index e0d9f176f..1e42770dc 100644 --- a/deebot_client/commands/json/common.py +++ b/deebot_client/commands/json/common.py @@ -128,7 +128,9 @@ class SetEnableCommand(JsonSetCommand, ABC): _field_name = _ENABLE def __init_subclass__(cls, **kwargs: Any) -> None: - cls._mqtt_params = MappingProxyType({cls._field_name: InitParam(bool, _ENABLE)}) + cls._mqtt_params = MappingProxyType( + {cls._field_name: InitParam(bool, name=_ENABLE)} + ) super().__init_subclass__(**kwargs) def __init__(self, enable: bool) -> None: # noqa: FBT001 diff --git a/deebot_client/commands/json/life_span.py b/deebot_client/commands/json/life_span.py index 1abfcfa81..8c3828f84 100644 --- a/deebot_client/commands/json/life_span.py +++ b/deebot_client/commands/json/life_span.py @@ -50,7 +50,7 @@ class ResetLifeSpan(ExecuteCommand, CommandMqttP2P): """Reset life span command.""" name = "resetLifeSpan" - _mqtt_params = MappingProxyType({"type": InitParam(LifeSpan, "life_span")}) + _mqtt_params = MappingProxyType({"type": InitParam(LifeSpan, name="life_span")}) def __init__(self, life_span: LifeSpan) -> None: super().__init__({"type": life_span.value}) diff --git a/deebot_client/commands/json/ota.py b/deebot_client/commands/json/ota.py index 833ff430b..8822b4dec 100644 --- a/deebot_client/commands/json/ota.py +++ b/deebot_client/commands/json/ota.py @@ -52,7 +52,9 @@ class SetOta(JsonSetCommand): name = "setOta" get_command = GetOta - _mqtt_params = MappingProxyType({"autoSwitch": InitParam(bool, "auto_enabled")}) + _mqtt_params = MappingProxyType( + {"autoSwitch": InitParam(bool, name="auto_enabled")} + ) def __init__(self, auto_enabled: bool) -> None: # noqa: FBT001 super().__init__({"autoSwitch": 1 if auto_enabled else 0}) diff --git a/deebot_client/commands/json/wash_info.py b/deebot_client/commands/json/wash_info.py index 7c3f3a616..69fd7ee18 100644 --- a/deebot_client/commands/json/wash_info.py +++ b/deebot_client/commands/json/wash_info.py @@ -24,8 +24,8 @@ class SetWashInfo(JsonSetCommand): get_command = GetWashInfo _mqtt_params = MappingProxyType( { - "mode": InitParam(int), - "hot_wash_amount": InitParam(int), + "mode": InitParam(int, default=None), + "hot_wash_amount": InitParam(int, default=None), } ) From 3f56bf5bec87af9608b0e5abaab7536a9fbbd1fb Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Fri, 23 Feb 2024 09:53:49 +0000 Subject: [PATCH 6/7] Fix SetWashInfo --- deebot_client/commands/json/wash_info.py | 6 +++++- tests/commands/json/test_wash_info.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/deebot_client/commands/json/wash_info.py b/deebot_client/commands/json/wash_info.py index 69fd7ee18..df29e9e03 100644 --- a/deebot_client/commands/json/wash_info.py +++ b/deebot_client/commands/json/wash_info.py @@ -30,12 +30,16 @@ class SetWashInfo(JsonSetCommand): ) def __init__( - self, mode: WashMode | str | None = None, hot_wash_amount: int | None = None + self, + mode: WashMode | str | int | None = None, + hot_wash_amount: int | None = None, ) -> None: args: dict[str, Any] = {} if isinstance(mode, str): mode = WashMode.get(mode) + if isinstance(mode, WashMode): + mode = mode.value if mode is not None: args["mode"] = mode diff --git a/tests/commands/json/test_wash_info.py b/tests/commands/json/test_wash_info.py index 748ea468b..e1e778503 100644 --- a/tests/commands/json/test_wash_info.py +++ b/tests/commands/json/test_wash_info.py @@ -43,7 +43,7 @@ async def test_GetWashInfo(json: dict[str, Any], expected: WashInfoEvent) -> Non @pytest.mark.parametrize(("value"), [WashMode.HOT, "hot"]) async def test_SetWashInfo_mode(value: WashMode | str) -> None: command = SetWashInfo(mode=value) - args = {"mode": WashMode.HOT} + args = {"mode": 1} await assert_set_command(command, args, WashInfoEvent(WashMode.HOT, None, None)) From 52f19e4857f2078f5e880ed4f0f4671430b38e88 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Fri, 23 Feb 2024 09:58:09 +0000 Subject: [PATCH 7/7] Use pop again --- deebot_client/command.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deebot_client/command.py b/deebot_client/command.py index 22c9ee80a..e32be0e55 100644 --- a/deebot_client/command.py +++ b/deebot_client/command.py @@ -297,7 +297,7 @@ def _pop_or_raise(name: str, param: InitParam, data: dict[str, Any]) -> Any: return param.default msg = f'"{name}" is missing in {data}' raise DeebotError(msg) - value = data[name] + value = data.pop(name) try: return param.type_(value) except ValueError as err: