From 85a3a28ba5ce1ed16436cb77a4657d8bb8675695 Mon Sep 17 00:00:00 2001 From: MarkGodwin <10632972+MarkGodwin@users.noreply.github.com> Date: Sun, 8 Dec 2024 00:14:27 +0000 Subject: [PATCH 1/9] Add Deebot N20 Plus with base station functions --- deebot_client/capabilities.py | 15 ++ deebot_client/commands/json/__init__.py | 6 +- deebot_client/commands/json/station_action.py | 25 +++ deebot_client/events/__init__.py | 22 ++ deebot_client/hardware/deebot/buom7k.py | 198 ++++++++++++++++++ deebot_client/messages/json/__init__.py | 5 +- deebot_client/messages/json/base_station.py | 34 +++ tests/messages/test_get_messages.py | 2 + 8 files changed, 305 insertions(+), 2 deletions(-) create mode 100644 deebot_client/commands/json/station_action.py create mode 100644 deebot_client/hardware/deebot/buom7k.py create mode 100644 deebot_client/messages/json/base_station.py diff --git a/deebot_client/capabilities.py b/deebot_client/capabilities.py index 40bfa2ecc..2214b634e 100644 --- a/deebot_client/capabilities.py +++ b/deebot_client/capabilities.py @@ -11,6 +11,8 @@ from deebot_client.events import ( AdvancedModeEvent, AvailabilityEvent, + BaseStationAction, + BaseStationEvent, BatteryEvent, BorderSwitchEvent, CachedMapInfoEvent, @@ -141,6 +143,18 @@ class CapabilityClean: preference: CapabilitySetEnable[CleanPreferenceEvent] | None = None work_mode: CapabilitySetTypes[WorkModeEvent, WorkMode] | None = None +@dataclass(frozen=True, kw_only=True) +class CapabilityBaseStationAction: + """Capabilities for base station action.""" + + command: Callable[[BaseStationAction], Command] + +@dataclass(frozen=True, kw_only=True) +class CapabilityBaseStation: + """Capabilities for base station.""" + + action: CapabilityBaseStationAction + event: CapabilityEvent[BaseStationEvent] | None = None @dataclass(frozen=True) class CapabilityCustomCommand(CapabilityEvent[_EVENT]): @@ -211,6 +225,7 @@ class Capabilities(ABC): device_type: DeviceType = field(kw_only=False) availability: CapabilityEvent[AvailabilityEvent] + base_station: CapabilityBaseStation | None = None battery: CapabilityEvent[BatteryEvent] charge: CapabilityExecute clean: CapabilityClean diff --git a/deebot_client/commands/json/__init__.py b/deebot_client/commands/json/__init__.py index 384e26978..f39524b3c 100644 --- a/deebot_client/commands/json/__init__.py +++ b/deebot_client/commands/json/__init__.py @@ -42,6 +42,7 @@ from .pos import GetPos from .relocation import SetRelocationState from .safe_protect import GetSafeProtect, SetSafeProtect +from .station_action import StationAction from .stats import GetStats, GetTotalStats from .sweep_mode import GetSweepMode, SetSweepMode from .true_detect import GetTrueDetect, SetTrueDetect @@ -122,6 +123,7 @@ "SetVolume", "SetWaterInfo", "SetWorkMode", + "StationAction", ] # fmt: off @@ -226,7 +228,9 @@ SetWaterInfo, GetWorkMode, - SetWorkMode + SetWorkMode, + + StationAction, ] # fmt: on diff --git a/deebot_client/commands/json/station_action.py b/deebot_client/commands/json/station_action.py new file mode 100644 index 000000000..e0509089d --- /dev/null +++ b/deebot_client/commands/json/station_action.py @@ -0,0 +1,25 @@ +"""Charge commands.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from deebot_client.events import BaseStationAction +from deebot_client.logging_filter import get_logger + +from .common import ExecuteCommand +from .const import CODE + +if TYPE_CHECKING: + from deebot_client.event_bus import EventBus + +_LOGGER = get_logger(__name__) + + +class StationAction(ExecuteCommand): + """Station Action command.""" + + name = "stationAction" + + def __init__(self, action: BaseStationAction) -> None: + super().__init__({"act": 1, "type": action.value}) diff --git a/deebot_client/events/__init__.py b/deebot_client/events/__init__.py index 12a03b95c..ab5d3e077 100644 --- a/deebot_client/events/__init__.py +++ b/deebot_client/events/__init__.py @@ -51,6 +51,7 @@ "Position", "PositionType", "PositionsEvent", + "BaseStationEvent", "SweepModeEvent", "SweepType", "WaterAmount", @@ -140,6 +141,27 @@ class LifeSpan(str, Enum): CLEANING_FLUID = "autoWater_cleaningFluid" STRAINER = "strainer" HAND_FILTER = "handFilter" + BASE_STATION_FILTER = "spHeap" + +@unique +class BaseStationAction(IntEnum): + """ Enum class for all possible base station actions. """ + + EMPTY_DUSTBIN = 1 + +@unique +class BaseStationStatus(IntEnum): + """ Enum class for all possible base station statuses. """ + + UNKNOWN = -1 + IDLE = 0 + EMPTYING = 1 + +@dataclass(frozen=True) +class BaseStationEvent(Event): + """Base Station Event representation.""" + + state: BaseStationStatus @dataclass(frozen=True) diff --git a/deebot_client/hardware/deebot/buom7k.py b/deebot_client/hardware/deebot/buom7k.py new file mode 100644 index 000000000..8a8091485 --- /dev/null +++ b/deebot_client/hardware/deebot/buom7k.py @@ -0,0 +1,198 @@ +"""Deebot N20 Plus Capabilities.""" + +from __future__ import annotations + +from deebot_client.capabilities import ( + Capabilities, + CapabilityBaseStation, + CapabilityBaseStationAction, + CapabilityClean, + CapabilityCleanAction, + CapabilityCustomCommand, + CapabilityEvent, + CapabilityExecute, + CapabilityLifeSpan, + CapabilityMap, + CapabilitySet, + CapabilitySetEnable, + CapabilitySettings, + CapabilitySetTypes, + CapabilityStats, + DeviceType, +) +from deebot_client.commands.json.battery import GetBattery +from deebot_client.commands.json.carpet import ( + GetCarpetAutoFanBoost, + SetCarpetAutoFanBoost, +) +from deebot_client.commands.json.charge import Charge +from deebot_client.commands.json.charge_state import GetChargeState +from deebot_client.commands.json.child_lock import GetChildLock, SetChildLock +from deebot_client.commands.json.clean import CleanAreaV2, CleanV2, GetCleanInfoV2 +from deebot_client.commands.json.clean_count import GetCleanCount, SetCleanCount +from deebot_client.commands.json.clean_logs import GetCleanLogs +from deebot_client.commands.json.continuous_cleaning import ( + GetContinuousCleaning, + SetContinuousCleaning, +) +from deebot_client.commands.json.custom import CustomCommand +from deebot_client.commands.json.error import GetError +from deebot_client.commands.json.fan_speed import GetFanSpeed, SetFanSpeed +from deebot_client.commands.json.life_span import GetLifeSpan, ResetLifeSpan +from deebot_client.commands.json.map import GetCachedMapInfo, GetMajorMap, GetMapTrace +from deebot_client.commands.json.multimap_state import ( + GetMultimapState, + SetMultimapState, +) +from deebot_client.commands.json.network import GetNetInfo +from deebot_client.commands.json.play_sound import PlaySound +from deebot_client.commands.json.pos import GetPos +from deebot_client.commands.json.relocation import SetRelocationState +from deebot_client.commands.json.station_action import StationAction +from deebot_client.commands.json.stats import GetStats, GetTotalStats +from deebot_client.commands.json.volume import GetVolume, SetVolume +from deebot_client.commands.json.water_info import GetWaterInfo, SetWaterInfo +from deebot_client.const import DataType +from deebot_client.events import ( + AvailabilityEvent, + BaseStationEvent, + BatteryEvent, + CachedMapInfoEvent, + CarpetAutoFanBoostEvent, + ChildLockEvent, + CleanCountEvent, + CleanLogEvent, + ContinuousCleaningEvent, + CustomCommandEvent, + ErrorEvent, + FanSpeedEvent, + FanSpeedLevel, + LifeSpan, + LifeSpanEvent, + MajorMapEvent, + MapChangedEvent, + MapTraceEvent, + MultimapStateEvent, + NetworkInfoEvent, + PositionsEvent, + ReportStatsEvent, + RoomsEvent, + StateEvent, + StatsEvent, + TotalStatsEvent, + VolumeEvent, + WaterAmount, + WaterInfoEvent, +) +from deebot_client.models import StaticDeviceInfo +from deebot_client.util import short_name + +from . import DEVICES + +DEVICES[short_name(__name__)] = StaticDeviceInfo( + DataType.JSON, + Capabilities( + device_type=DeviceType.VACUUM, + availability=CapabilityEvent( + AvailabilityEvent, [GetBattery(is_available_check=True)] + ), + base_station=CapabilityBaseStation( + action=CapabilityBaseStationAction(command=StationAction), + event=CapabilityEvent(BaseStationEvent, []), + ), + battery=CapabilityEvent(BatteryEvent, [GetBattery()]), + charge=CapabilityExecute(Charge), + clean=CapabilityClean( + action=CapabilityCleanAction(command=CleanV2, area=CleanAreaV2), + continuous=CapabilitySetEnable( + ContinuousCleaningEvent, + [GetContinuousCleaning()], + SetContinuousCleaning, + ), + count=CapabilitySet(CleanCountEvent, [GetCleanCount()], SetCleanCount), + log=CapabilityEvent(CleanLogEvent, [GetCleanLogs()]), + ), + custom=CapabilityCustomCommand( + event=CustomCommandEvent, get=[], set=CustomCommand + ), + error=CapabilityEvent(ErrorEvent, [GetError()]), + fan_speed=CapabilitySetTypes( + event=FanSpeedEvent, + get=[GetFanSpeed()], + set=SetFanSpeed, + types=( + FanSpeedLevel.QUIET, + FanSpeedLevel.NORMAL, + FanSpeedLevel.MAX, + FanSpeedLevel.MAX_PLUS, + ), + ), + life_span=CapabilityLifeSpan( + types=( + LifeSpan.BRUSH, + LifeSpan.FILTER, + LifeSpan.SIDE_BRUSH, + LifeSpan.UNIT_CARE, + LifeSpan.ROUND_MOP, + LifeSpan.BASE_STATION_FILTER + ), + event=LifeSpanEvent, + get=[ + GetLifeSpan( + [ + LifeSpan.BRUSH, + LifeSpan.FILTER, + LifeSpan.SIDE_BRUSH, + LifeSpan.UNIT_CARE, + LifeSpan.ROUND_MOP, + LifeSpan.BASE_STATION_FILTER + ] + ) + ], + reset=ResetLifeSpan, + ), + map=CapabilityMap( + cached_info=CapabilityEvent( + CachedMapInfoEvent, [GetCachedMapInfo(version=2)] + ), + changed=CapabilityEvent(MapChangedEvent, []), + major=CapabilityEvent(MajorMapEvent, [GetMajorMap()]), + multi_state=CapabilitySetEnable( + MultimapStateEvent, [GetMultimapState()], SetMultimapState + ), + position=CapabilityEvent(PositionsEvent, [GetPos()]), + relocation=CapabilityExecute(SetRelocationState), + rooms=CapabilityEvent(RoomsEvent, [GetCachedMapInfo(version=2)]), + trace=CapabilityEvent(MapTraceEvent, [GetMapTrace()]), + ), + network=CapabilityEvent(NetworkInfoEvent, [GetNetInfo()]), + play_sound=CapabilityExecute(PlaySound), + settings=CapabilitySettings( + carpet_auto_fan_boost=CapabilitySetEnable( + CarpetAutoFanBoostEvent, + [GetCarpetAutoFanBoost()], + SetCarpetAutoFanBoost, + ), + child_lock=CapabilitySetEnable( + ChildLockEvent, [GetChildLock()], SetChildLock + ), + volume=CapabilitySet(VolumeEvent, [GetVolume()], SetVolume), + ), + state=CapabilityEvent(StateEvent, [GetChargeState(), GetCleanInfoV2()]), + stats=CapabilityStats( + clean=CapabilityEvent(StatsEvent, [GetStats()]), + report=CapabilityEvent(ReportStatsEvent, []), + total=CapabilityEvent(TotalStatsEvent, [GetTotalStats()]), + ), + water=CapabilitySetTypes( + event=WaterInfoEvent, + get=[GetWaterInfo()], + set=SetWaterInfo, + types=( + WaterAmount.LOW, + WaterAmount.MEDIUM, + WaterAmount.HIGH, + ), + ), + ), +) diff --git a/deebot_client/messages/json/__init__.py b/deebot_client/messages/json/__init__.py index 8e8402de6..9e3f23b85 100644 --- a/deebot_client/messages/json/__init__.py +++ b/deebot_client/messages/json/__init__.py @@ -7,6 +7,7 @@ from .battery import OnBattery from .map import OnMapSetV2 from .stats import ReportStats +from .base_station import OnStationState if TYPE_CHECKING: from deebot_client.message import Message @@ -15,6 +16,7 @@ "OnBattery", "OnMapSetV2", "ReportStats", + "OnStationState", ] # fmt: off @@ -23,7 +25,8 @@ OnBattery, OnMapSetV2, - ReportStats + ReportStats, + OnStationState, ] # fmt: on diff --git a/deebot_client/messages/json/base_station.py b/deebot_client/messages/json/base_station.py new file mode 100644 index 000000000..abb12d99f --- /dev/null +++ b/deebot_client/messages/json/base_station.py @@ -0,0 +1,34 @@ +"""Base station messages.""" + +from __future__ import annotations +from typing import TYPE_CHECKING, Any + +from deebot_client.events import BaseStationEvent, BaseStationStatus +from deebot_client.message import HandlingResult, MessageBodyDataDict + +if TYPE_CHECKING: + from deebot_client.event_bus import EventBus + + +class OnStationState(MessageBodyDataDict): + """On battery message.""" + + name = "onStationState" + + @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 + """ + # "body":{"data":{"content":{"error":[],"type":0},"state":0},"code":0,"msg":"ok"} - Idle + # "body":{"data":{"content":{"error":[],"type":1,"motionState":1},"state":1},"code":0,"msg":"ok"} - Emptying + # Assume anything else is possible + + reported_state = BaseStationStatus.IDLE if data["state"] == 0 else ( + BaseStationStatus.EMPTYING if data["content"]["type"] == 1 and data["content"]["motionState"] == 1 else BaseStationStatus.UNKNOWN) + + event_bus.notify(BaseStationEvent(reported_state)) + return HandlingResult.success() diff --git a/tests/messages/test_get_messages.py b/tests/messages/test_get_messages.py index 5db55670a..382c3e372 100644 --- a/tests/messages/test_get_messages.py +++ b/tests/messages/test_get_messages.py @@ -7,6 +7,7 @@ from deebot_client.commands.json.error import GetError from deebot_client.const import DataType from deebot_client.messages import get_message +from deebot_client.messages.json.base_station import OnStationState from deebot_client.messages.json.battery import OnBattery if TYPE_CHECKING: @@ -18,6 +19,7 @@ [ ("onBattery", DataType.JSON, OnBattery), ("onBattery_V2", DataType.JSON, OnBattery), + ("onStationState", DataType.JSON, OnStationState), ("onError", DataType.JSON, GetError), ("GetCleanLogs", DataType.JSON, None), ("unknown", DataType.JSON, None), From dfa3c231b13b8211b5331b4765cf29064948c105 Mon Sep 17 00:00:00 2001 From: MarkGodwin <10632972+MarkGodwin@users.noreply.github.com> Date: Sun, 8 Dec 2024 00:30:41 +0000 Subject: [PATCH 2/9] Ruff Ruff --- deebot_client/commands/json/station_action.py | 6 ++---- deebot_client/events/__init__.py | 9 ++++++--- deebot_client/messages/json/__init__.py | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/deebot_client/commands/json/station_action.py b/deebot_client/commands/json/station_action.py index e0509089d..9167a1fbe 100644 --- a/deebot_client/commands/json/station_action.py +++ b/deebot_client/commands/json/station_action.py @@ -2,16 +2,14 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING -from deebot_client.events import BaseStationAction from deebot_client.logging_filter import get_logger from .common import ExecuteCommand -from .const import CODE if TYPE_CHECKING: - from deebot_client.event_bus import EventBus + from deebot_client.events import BaseStationAction _LOGGER = get_logger(__name__) diff --git a/deebot_client/events/__init__.py b/deebot_client/events/__init__.py index ab5d3e077..cd5c8b3be 100644 --- a/deebot_client/events/__init__.py +++ b/deebot_client/events/__init__.py @@ -31,6 +31,7 @@ from deebot_client.models import Room, State __all__ = [ + "BaseStationEvent", "BatteryEvent", "CachedMapInfoEvent", "CleanJobStatus", @@ -51,7 +52,6 @@ "Position", "PositionType", "PositionsEvent", - "BaseStationEvent", "SweepModeEvent", "SweepType", "WaterAmount", @@ -143,20 +143,23 @@ class LifeSpan(str, Enum): HAND_FILTER = "handFilter" BASE_STATION_FILTER = "spHeap" + @unique class BaseStationAction(IntEnum): - """ Enum class for all possible base station actions. """ + """Enum class for all possible base station actions.""" EMPTY_DUSTBIN = 1 + @unique class BaseStationStatus(IntEnum): - """ Enum class for all possible base station statuses. """ + """Enum class for all possible base station statuses.""" UNKNOWN = -1 IDLE = 0 EMPTYING = 1 + @dataclass(frozen=True) class BaseStationEvent(Event): """Base Station Event representation.""" diff --git a/deebot_client/messages/json/__init__.py b/deebot_client/messages/json/__init__.py index 9e3f23b85..8ac85354b 100644 --- a/deebot_client/messages/json/__init__.py +++ b/deebot_client/messages/json/__init__.py @@ -4,10 +4,10 @@ from typing import TYPE_CHECKING +from .base_station import OnStationState from .battery import OnBattery from .map import OnMapSetV2 from .stats import ReportStats -from .base_station import OnStationState if TYPE_CHECKING: from deebot_client.message import Message @@ -15,8 +15,8 @@ __all__ = [ "OnBattery", "OnMapSetV2", - "ReportStats", "OnStationState", + "ReportStats", ] # fmt: off From a319d018324b37a32a9b87a449a7440c4a3f550f Mon Sep 17 00:00:00 2001 From: MarkGodwin <10632972+MarkGodwin@users.noreply.github.com> Date: Sun, 8 Dec 2024 00:37:47 +0000 Subject: [PATCH 3/9] Expand messy code and avoid runtime errors --- deebot_client/messages/json/base_station.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/deebot_client/messages/json/base_station.py b/deebot_client/messages/json/base_station.py index abb12d99f..35500e3cb 100644 --- a/deebot_client/messages/json/base_station.py +++ b/deebot_client/messages/json/base_station.py @@ -1,6 +1,7 @@ """Base station messages.""" from __future__ import annotations + from typing import TYPE_CHECKING, Any from deebot_client.events import BaseStationEvent, BaseStationStatus @@ -27,8 +28,15 @@ def _handle_body_data_dict( # "body":{"data":{"content":{"error":[],"type":1,"motionState":1},"state":1},"code":0,"msg":"ok"} - Emptying # Assume anything else is possible - reported_state = BaseStationStatus.IDLE if data["state"] == 0 else ( - BaseStationStatus.EMPTYING if data["content"]["type"] == 1 and data["content"]["motionState"] == 1 else BaseStationStatus.UNKNOWN) + if data.get("state") == 0: + reported_state = BaseStationStatus.IDLE + elif ( + data.get("content", {}).get("type") == 1 + and data["content"].get("motionState") == 1 + ): + reported_state = BaseStationStatus.EMPTYING + else: + reported_state = BaseStationStatus.UNKNOWN event_bus.notify(BaseStationEvent(reported_state)) return HandlingResult.success() From c96f4c110a263a91b17618e529f8d345fce53732 Mon Sep 17 00:00:00 2001 From: MarkGodwin <10632972+MarkGodwin@users.noreply.github.com> Date: Sun, 8 Dec 2024 00:43:14 +0000 Subject: [PATCH 4/9] ruff-format --- deebot_client/capabilities.py | 3 +++ deebot_client/hardware/deebot/buom7k.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/deebot_client/capabilities.py b/deebot_client/capabilities.py index 2214b634e..0ec4a5746 100644 --- a/deebot_client/capabilities.py +++ b/deebot_client/capabilities.py @@ -143,12 +143,14 @@ class CapabilityClean: preference: CapabilitySetEnable[CleanPreferenceEvent] | None = None work_mode: CapabilitySetTypes[WorkModeEvent, WorkMode] | None = None + @dataclass(frozen=True, kw_only=True) class CapabilityBaseStationAction: """Capabilities for base station action.""" command: Callable[[BaseStationAction], Command] + @dataclass(frozen=True, kw_only=True) class CapabilityBaseStation: """Capabilities for base station.""" @@ -156,6 +158,7 @@ class CapabilityBaseStation: action: CapabilityBaseStationAction event: CapabilityEvent[BaseStationEvent] | None = None + @dataclass(frozen=True) class CapabilityCustomCommand(CapabilityEvent[_EVENT]): """Capability custom command.""" diff --git a/deebot_client/hardware/deebot/buom7k.py b/deebot_client/hardware/deebot/buom7k.py index 8a8091485..d82405b71 100644 --- a/deebot_client/hardware/deebot/buom7k.py +++ b/deebot_client/hardware/deebot/buom7k.py @@ -134,7 +134,7 @@ LifeSpan.SIDE_BRUSH, LifeSpan.UNIT_CARE, LifeSpan.ROUND_MOP, - LifeSpan.BASE_STATION_FILTER + LifeSpan.BASE_STATION_FILTER, ), event=LifeSpanEvent, get=[ @@ -145,7 +145,7 @@ LifeSpan.SIDE_BRUSH, LifeSpan.UNIT_CARE, LifeSpan.ROUND_MOP, - LifeSpan.BASE_STATION_FILTER + LifeSpan.BASE_STATION_FILTER, ] ) ], From 5bf0afc31838a690c860f5e6e88233e7501bd75f Mon Sep 17 00:00:00 2001 From: MarkGodwin <10632972+MarkGodwin@users.noreply.github.com> Date: Sun, 8 Dec 2024 01:12:58 +0000 Subject: [PATCH 5/9] Add hardware id to tests --- tests/hardware/test_init.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/hardware/test_init.py b/tests/hardware/test_init.py index cace005b7..a9c5bfd9e 100644 --- a/tests/hardware/test_init.py +++ b/tests/hardware/test_init.py @@ -266,6 +266,7 @@ def test_all_models_loaded() -> None: "9ku8nu", "9s1s80", "b742vd", + "buom7k", "clojes", "e6ofmn", "guzput", From 8509453d79b0b546e8fea545815f02633d010c35 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Fri, 20 Dec 2024 20:08:55 +0000 Subject: [PATCH 6/9] refactor --- deebot_client/commands/__init__.py | 8 ++++++++ deebot_client/commands/json/__init__.py | 10 ++++------ deebot_client/commands/json/station_action.py | 2 +- deebot_client/events/__init__.py | 9 --------- deebot_client/messages/json/__init__.py | 4 ++-- 5 files changed, 15 insertions(+), 18 deletions(-) diff --git a/deebot_client/commands/__init__.py b/deebot_client/commands/__init__.py index 8c8d7df4c..90fd37db9 100644 --- a/deebot_client/commands/__init__.py +++ b/deebot_client/commands/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations +from enum import IntEnum, unique from typing import TYPE_CHECKING from deebot_client.const import DataType @@ -19,3 +20,10 @@ COMMANDS_WITH_MQTT_P2P_HANDLING: dict[DataType, dict[str, type[CommandMqttP2P]]] = { DataType.JSON: JSON_COMMANDS_WITH_MQTT_P2P_HANDLING } + + +@unique +class BaseStationAction(IntEnum): + """Enum class for all possible base station actions.""" + + EMPTY_DUSTBIN = 1 diff --git a/deebot_client/commands/json/__init__.py b/deebot_client/commands/json/__init__.py index 93f696195..1a3587b26 100644 --- a/deebot_client/commands/json/__init__.py +++ b/deebot_client/commands/json/__init__.py @@ -6,7 +6,7 @@ from deebot_client.command import Command, CommandMqttP2P -from . import auto_empty +from . import auto_empty, station_action from .advanced_mode import GetAdvancedMode, SetAdvancedMode from .battery import GetBattery from .border_switch import GetBorderSwitch, SetBorderSwitch @@ -43,7 +43,6 @@ from .pos import GetPos from .relocation import SetRelocationState from .safe_protect import GetSafeProtect, SetSafeProtect -from .station_action import StationAction from .stats import GetStats, GetTotalStats from .sweep_mode import GetSweepMode, SetSweepMode from .true_detect import GetTrueDetect, SetTrueDetect @@ -124,7 +123,6 @@ "SetVolume", "SetWaterInfo", "SetWorkMode", - "StationAction", ] # fmt: off @@ -213,6 +211,8 @@ GetSafeProtect, SetSafeProtect, + station_action.StationAction, + GetSweepMode, SetSweepMode, @@ -232,9 +232,7 @@ SetWaterInfo, GetWorkMode, - SetWorkMode, - - StationAction, + SetWorkMode ] # fmt: on diff --git a/deebot_client/commands/json/station_action.py b/deebot_client/commands/json/station_action.py index 9167a1fbe..44772f33b 100644 --- a/deebot_client/commands/json/station_action.py +++ b/deebot_client/commands/json/station_action.py @@ -9,7 +9,7 @@ from .common import ExecuteCommand if TYPE_CHECKING: - from deebot_client.events import BaseStationAction + from deebot_client.commands import BaseStationAction _LOGGER = get_logger(__name__) diff --git a/deebot_client/events/__init__.py b/deebot_client/events/__init__.py index 026e00056..dd80f69f4 100644 --- a/deebot_client/events/__init__.py +++ b/deebot_client/events/__init__.py @@ -34,7 +34,6 @@ __all__ = [ "AutoEmptyEvent", - "BaseStationEvent", "BatteryEvent", "CachedMapInfoEvent", "CleanJobStatus", @@ -167,18 +166,10 @@ def from_xml(cls, value: str) -> LifeSpan: BASE_STATION_FILTER = "spHeap", "SpHeap" -@unique -class BaseStationAction(IntEnum): - """Enum class for all possible base station actions.""" - - EMPTY_DUSTBIN = 1 - - @unique class BaseStationStatus(IntEnum): """Enum class for all possible base station statuses.""" - UNKNOWN = -1 IDLE = 0 EMPTYING = 1 diff --git a/deebot_client/messages/json/__init__.py b/deebot_client/messages/json/__init__.py index dccde284f..75d51d338 100644 --- a/deebot_client/messages/json/__init__.py +++ b/deebot_client/messages/json/__init__.py @@ -16,7 +16,6 @@ __all__ = [ "OnBattery", "OnMapSetV2", - "OnStationState", "ReportStats", ] @@ -29,8 +28,9 @@ OnMapSetV2, - ReportStats, OnStationState, + + ReportStats, ] # fmt: on From b5597682dba7aebda5b91365728049e672f34591 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Fri, 20 Dec 2024 20:28:53 +0000 Subject: [PATCH 7/9] refactor + add getStationState command --- deebot_client/capabilities.py | 39 ++++++------------- deebot_client/commands/json/__init__.py | 4 +- deebot_client/commands/json/station_action.py | 2 +- deebot_client/commands/json/station_state.py | 13 +++++++ deebot_client/hardware/deebot/buom7k.py | 20 ++++++++++ deebot_client/hardware/deebot/p95mgv.py | 33 +++++++++------- deebot_client/hardware/deebot/qhe2o2.py | 29 ++++++++------ deebot_client/messages/json/__init__.py | 2 +- .../{base_station.py => station_state.py} | 13 ++++--- tests/hardware/test_init.py | 3 ++ tests/messages/test_get_messages.py | 2 - 11 files changed, 95 insertions(+), 65 deletions(-) create mode 100644 deebot_client/commands/json/station_state.py rename deebot_client/messages/json/{base_station.py => station_state.py} (79%) diff --git a/deebot_client/capabilities.py b/deebot_client/capabilities.py index 8057c43fd..84bd36527 100644 --- a/deebot_client/capabilities.py +++ b/deebot_client/capabilities.py @@ -11,7 +11,6 @@ from deebot_client.events import ( AdvancedModeEvent, AvailabilityEvent, - BaseStationAction, BaseStationEvent, BatteryEvent, BorderSwitchEvent, @@ -62,6 +61,7 @@ from _typeshed import DataclassInstance from deebot_client.command import Command + from deebot_client.commands import BaseStationAction from deebot_client.commands.json.common import ExecuteCommand from deebot_client.events.efficiency_mode import EfficiencyMode, EfficiencyModeEvent from deebot_client.models import CleanAction, CleanMode @@ -149,21 +149,6 @@ class CapabilityClean: ) -@dataclass(frozen=True, kw_only=True) -class CapabilityBaseStationAction: - """Capabilities for base station action.""" - - command: Callable[[BaseStationAction], Command] - - -@dataclass(frozen=True, kw_only=True) -class CapabilityBaseStation: - """Capabilities for base station.""" - - action: CapabilityBaseStationAction - event: CapabilityEvent[BaseStationEvent] | None = None - - @dataclass(frozen=True) class CapabilityCustomCommand(CapabilityEvent[_EVENT]): """Capability custom command.""" @@ -228,17 +213,16 @@ class CapabilitySettings: @dataclass(frozen=True, kw_only=True) -class CapabilityStation: - """Capabilities for station.""" - - auto_empty: ( - CapabilitySetTypes[ - auto_empty.AutoEmptyEvent, - [bool | None, auto_empty.Frequency | str | None], - auto_empty.Frequency, - ] - | None - ) = None +class CapabilityBaseStation: + """Capabilities for the base station.""" + + action: Callable[[BaseStationAction], Command] + auto_empty: CapabilitySetTypes[ + auto_empty.AutoEmptyEvent, + [bool | None, auto_empty.Frequency | str | None], + auto_empty.Frequency, + ] + status: CapabilityEvent[BaseStationEvent] @dataclass(frozen=True, kw_only=True) @@ -263,7 +247,6 @@ class Capabilities(ABC): play_sound: CapabilityExecute settings: CapabilitySettings state: CapabilityEvent[StateEvent] - station: CapabilityStation = field(default_factory=CapabilityStation) stats: CapabilityStats water: ( CapabilitySetTypes[WaterInfoEvent, [WaterAmount | str], WaterAmount] | None diff --git a/deebot_client/commands/json/__init__.py b/deebot_client/commands/json/__init__.py index 1a3587b26..e9ae08dbc 100644 --- a/deebot_client/commands/json/__init__.py +++ b/deebot_client/commands/json/__init__.py @@ -6,7 +6,7 @@ from deebot_client.command import Command, CommandMqttP2P -from . import auto_empty, station_action +from . import auto_empty, station_action, station_state from .advanced_mode import GetAdvancedMode, SetAdvancedMode from .battery import GetBattery from .border_switch import GetBorderSwitch, SetBorderSwitch @@ -213,6 +213,8 @@ station_action.StationAction, + station_state.GetStationState, + GetSweepMode, SetSweepMode, diff --git a/deebot_client/commands/json/station_action.py b/deebot_client/commands/json/station_action.py index 44772f33b..16330e242 100644 --- a/deebot_client/commands/json/station_action.py +++ b/deebot_client/commands/json/station_action.py @@ -17,7 +17,7 @@ class StationAction(ExecuteCommand): """Station Action command.""" - name = "stationAction" + NAME = "stationAction" def __init__(self, action: BaseStationAction) -> None: super().__init__({"act": 1, "type": action.value}) diff --git a/deebot_client/commands/json/station_state.py b/deebot_client/commands/json/station_state.py new file mode 100644 index 000000000..88c5047fe --- /dev/null +++ b/deebot_client/commands/json/station_state.py @@ -0,0 +1,13 @@ +"""Battery commands.""" + +from __future__ import annotations + +from deebot_client.messages.json.station_state import OnStationState + +from .common import JsonCommandWithMessageHandling + + +class GetStationState(OnStationState, JsonCommandWithMessageHandling): + """Get station state command.""" + + NAME = "getStationState" diff --git a/deebot_client/hardware/deebot/buom7k.py b/deebot_client/hardware/deebot/buom7k.py index 4e91d2c5e..4ad446252 100644 --- a/deebot_client/hardware/deebot/buom7k.py +++ b/deebot_client/hardware/deebot/buom7k.py @@ -4,6 +4,7 @@ from deebot_client.capabilities import ( Capabilities, + CapabilityBaseStation, CapabilityClean, CapabilityCleanAction, CapabilityCustomCommand, @@ -18,6 +19,7 @@ CapabilityStats, DeviceType, ) +from deebot_client.commands.json.auto_empty import GetAutoEmpty, SetAutoEmpty from deebot_client.commands.json.battery import GetBattery from deebot_client.commands.json.carpet import ( GetCarpetAutoFanBoost, @@ -46,12 +48,15 @@ from deebot_client.commands.json.play_sound import PlaySound from deebot_client.commands.json.pos import GetPos from deebot_client.commands.json.relocation import SetRelocationState +from deebot_client.commands.json.station_action import StationAction +from deebot_client.commands.json.station_state import GetStationState from deebot_client.commands.json.stats import GetStats, GetTotalStats from deebot_client.commands.json.volume import GetVolume, SetVolume from deebot_client.commands.json.water_info import GetWaterInfo, SetWaterInfo from deebot_client.const import DataType from deebot_client.events import ( AvailabilityEvent, + BaseStationEvent, BatteryEvent, CachedMapInfoEvent, CarpetAutoFanBoostEvent, @@ -79,7 +84,9 @@ VolumeEvent, WaterAmount, WaterInfoEvent, + auto_empty, ) +from deebot_client.events.auto_empty import AutoEmptyEvent from deebot_client.models import StaticDeviceInfo from deebot_client.util import short_name @@ -92,6 +99,19 @@ availability=CapabilityEvent( AvailabilityEvent, [GetBattery(is_available_check=True)] ), + base_station=CapabilityBaseStation( + action=StationAction, + auto_empty=CapabilitySetTypes( + event=AutoEmptyEvent, + get=[GetAutoEmpty()], + set=SetAutoEmpty, + types=( + auto_empty.Frequency.AUTO, + auto_empty.Frequency.SMART, + ), + ), + status=CapabilityEvent(BaseStationEvent, [GetStationState()]), + ), battery=CapabilityEvent(BatteryEvent, [GetBattery()]), charge=CapabilityExecute(Charge), clean=CapabilityClean( diff --git a/deebot_client/hardware/deebot/p95mgv.py b/deebot_client/hardware/deebot/p95mgv.py index 97155f003..d587dcfe7 100644 --- a/deebot_client/hardware/deebot/p95mgv.py +++ b/deebot_client/hardware/deebot/p95mgv.py @@ -4,6 +4,7 @@ from deebot_client.capabilities import ( Capabilities, + CapabilityBaseStation, CapabilityClean, CapabilityCleanAction, CapabilityCustomCommand, @@ -15,7 +16,6 @@ CapabilitySetEnable, CapabilitySettings, CapabilitySetTypes, - CapabilityStation, CapabilityStats, DeviceType, ) @@ -57,6 +57,8 @@ from deebot_client.commands.json.play_sound import PlaySound from deebot_client.commands.json.pos import GetPos from deebot_client.commands.json.relocation import SetRelocationState +from deebot_client.commands.json.station_action import StationAction +from deebot_client.commands.json.station_state import GetStationState from deebot_client.commands.json.stats import GetStats, GetTotalStats from deebot_client.commands.json.true_detect import GetTrueDetect, SetTrueDetect from deebot_client.commands.json.voice_assistant_state import ( @@ -69,6 +71,7 @@ from deebot_client.events import ( AdvancedModeEvent, AvailabilityEvent, + BaseStationEvent, BatteryEvent, CachedMapInfoEvent, CarpetAutoFanBoostEvent, @@ -114,6 +117,21 @@ availability=CapabilityEvent( AvailabilityEvent, [GetBattery(is_available_check=True)] ), + base_station=CapabilityBaseStation( + action=StationAction, + auto_empty=CapabilitySetTypes( + event=auto_empty.AutoEmptyEvent, + get=[GetAutoEmpty()], + set=SetAutoEmpty, + types=( + auto_empty.Frequency.MIN_10, + auto_empty.Frequency.MIN_15, + auto_empty.Frequency.MIN_25, + auto_empty.Frequency.AUTO, + ), + ), + status=CapabilityEvent(BaseStationEvent, [GetStationState()]), + ), battery=CapabilityEvent(BatteryEvent, [GetBattery()]), charge=CapabilityExecute(Charge), clean=CapabilityClean( @@ -209,19 +227,6 @@ volume=CapabilitySet(VolumeEvent, [GetVolume()], SetVolume), ), state=CapabilityEvent(StateEvent, [GetChargeState(), GetCleanInfo()]), - station=CapabilityStation( - auto_empty=CapabilitySetTypes( - event=auto_empty.AutoEmptyEvent, - get=[GetAutoEmpty()], - set=SetAutoEmpty, - types=( - auto_empty.Frequency.MIN_10, - auto_empty.Frequency.MIN_15, - auto_empty.Frequency.MIN_25, - auto_empty.Frequency.AUTO, - ), - ), - ), stats=CapabilityStats( clean=CapabilityEvent(StatsEvent, [GetStats()]), report=CapabilityEvent(ReportStatsEvent, []), diff --git a/deebot_client/hardware/deebot/qhe2o2.py b/deebot_client/hardware/deebot/qhe2o2.py index 42cc73b64..a2ed86745 100644 --- a/deebot_client/hardware/deebot/qhe2o2.py +++ b/deebot_client/hardware/deebot/qhe2o2.py @@ -4,6 +4,7 @@ from deebot_client.capabilities import ( Capabilities, + CapabilityBaseStation, CapabilityClean, CapabilityCleanAction, CapabilityCustomCommand, @@ -15,7 +16,6 @@ CapabilitySetEnable, CapabilitySettings, CapabilitySetTypes, - CapabilityStation, CapabilityStats, DeviceType, ) @@ -48,6 +48,8 @@ from deebot_client.commands.json.play_sound import PlaySound from deebot_client.commands.json.pos import GetPos from deebot_client.commands.json.relocation import SetRelocationState +from deebot_client.commands.json.station_action import StationAction +from deebot_client.commands.json.station_state import GetStationState from deebot_client.commands.json.stats import GetStats, GetTotalStats from deebot_client.commands.json.volume import GetVolume, SetVolume from deebot_client.commands.json.water_info import GetWaterInfo, SetWaterInfo @@ -55,6 +57,7 @@ from deebot_client.events import ( AutoEmptyEvent, AvailabilityEvent, + BaseStationEvent, BatteryEvent, CachedMapInfoEvent, CarpetAutoFanBoostEvent, @@ -97,6 +100,19 @@ AvailabilityEvent, [GetBattery(is_available_check=True)] ), battery=CapabilityEvent(BatteryEvent, [GetBattery()]), + base_station=CapabilityBaseStation( + action=StationAction, + auto_empty=CapabilitySetTypes( + event=AutoEmptyEvent, + get=[GetAutoEmpty()], + set=SetAutoEmpty, + types=( + auto_empty.Frequency.AUTO, + auto_empty.Frequency.SMART, + ), + ), + status=CapabilityEvent(BaseStationEvent, [GetStationState()]), + ), charge=CapabilityExecute(Charge), clean=CapabilityClean( action=CapabilityCleanAction(command=CleanV2, area=CleanAreaV2), @@ -173,17 +189,6 @@ volume=CapabilitySet(VolumeEvent, [GetVolume()], SetVolume), ), state=CapabilityEvent(StateEvent, [GetChargeState(), GetCleanInfoV2()]), - station=CapabilityStation( - auto_empty=CapabilitySetTypes( - event=AutoEmptyEvent, - get=[GetAutoEmpty()], - set=SetAutoEmpty, - types=( - auto_empty.Frequency.AUTO, - auto_empty.Frequency.SMART, - ), - ), - ), stats=CapabilityStats( clean=CapabilityEvent(StatsEvent, [GetStats()]), report=CapabilityEvent(ReportStatsEvent, []), diff --git a/deebot_client/messages/json/__init__.py b/deebot_client/messages/json/__init__.py index 75d51d338..6a916460f 100644 --- a/deebot_client/messages/json/__init__.py +++ b/deebot_client/messages/json/__init__.py @@ -5,9 +5,9 @@ from typing import TYPE_CHECKING from .auto_empty import OnAutoEmpty -from .base_station import OnStationState from .battery import OnBattery from .map import OnMapSetV2 +from .station_state import OnStationState from .stats import ReportStats if TYPE_CHECKING: diff --git a/deebot_client/messages/json/base_station.py b/deebot_client/messages/json/station_state.py similarity index 79% rename from deebot_client/messages/json/base_station.py rename to deebot_client/messages/json/station_state.py index 35500e3cb..6a2f749e9 100644 --- a/deebot_client/messages/json/base_station.py +++ b/deebot_client/messages/json/station_state.py @@ -14,7 +14,7 @@ class OnStationState(MessageBodyDataDict): """On battery message.""" - name = "onStationState" + NAME = "onStationState" @classmethod def _handle_body_data_dict( @@ -26,17 +26,18 @@ def _handle_body_data_dict( """ # "body":{"data":{"content":{"error":[],"type":0},"state":0},"code":0,"msg":"ok"} - Idle # "body":{"data":{"content":{"error":[],"type":1,"motionState":1},"state":1},"code":0,"msg":"ok"} - Emptying - # Assume anything else is possible - if data.get("state") == 0: + if (state := data.get("state")) == 0: reported_state = BaseStationStatus.IDLE elif ( - data.get("content", {}).get("type") == 1 - and data["content"].get("motionState") == 1 + state == 1 + and (content := data.get("content")) + and content.get("type") == 1 + and content.get("motionState") == 1 ): reported_state = BaseStationStatus.EMPTYING else: - reported_state = BaseStationStatus.UNKNOWN + return HandlingResult.analyse() event_bus.notify(BaseStationEvent(reported_state)) return HandlingResult.success() diff --git a/tests/hardware/test_init.py b/tests/hardware/test_init.py index 94282e9d6..23fa42c4a 100644 --- a/tests/hardware/test_init.py +++ b/tests/hardware/test_init.py @@ -35,6 +35,7 @@ from deebot_client.commands.json.ota import GetOta from deebot_client.commands.json.pos import GetPos from deebot_client.commands.json.safe_protect import GetSafeProtect +from deebot_client.commands.json.station_state import GetStationState from deebot_client.commands.json.stats import GetStats, GetTotalStats from deebot_client.commands.json.true_detect import GetTrueDetect from deebot_client.commands.json.voice_assistant_state import GetVoiceAssistantState @@ -44,6 +45,7 @@ AdvancedModeEvent, AutoEmptyEvent, AvailabilityEvent, + BaseStationEvent, BatteryEvent, BorderSwitchEvent, CarpetAutoFanBoostEvent, @@ -196,6 +198,7 @@ async def test_get_static_device_info( AutoEmptyEvent: [GetAutoEmpty()], AdvancedModeEvent: [GetAdvancedMode()], AvailabilityEvent: [GetBattery(is_available_check=True)], + BaseStationEvent: [GetStationState()], BatteryEvent: [GetBattery()], CachedMapInfoEvent: [GetCachedMapInfo()], CarpetAutoFanBoostEvent: [GetCarpetAutoFanBoost()], diff --git a/tests/messages/test_get_messages.py b/tests/messages/test_get_messages.py index 382c3e372..5db55670a 100644 --- a/tests/messages/test_get_messages.py +++ b/tests/messages/test_get_messages.py @@ -7,7 +7,6 @@ from deebot_client.commands.json.error import GetError from deebot_client.const import DataType from deebot_client.messages import get_message -from deebot_client.messages.json.base_station import OnStationState from deebot_client.messages.json.battery import OnBattery if TYPE_CHECKING: @@ -19,7 +18,6 @@ [ ("onBattery", DataType.JSON, OnBattery), ("onBattery_V2", DataType.JSON, OnBattery), - ("onStationState", DataType.JSON, OnStationState), ("onError", DataType.JSON, GetError), ("GetCleanLogs", DataType.JSON, None), ("unknown", DataType.JSON, None), From 067ec3fb141e76dfff646d76d32db1360f60702b Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Fri, 20 Dec 2024 21:02:23 +0000 Subject: [PATCH 8/9] Add station state tests --- deebot_client/events/__init__.py | 20 ++-------- deebot_client/events/base_station.py | 25 ++++++++++++ deebot_client/messages/json/station_state.py | 6 +-- tests/commands/json/test_station_state.py | 34 ++++++++++++++++ tests/messages/json/test_station_state.py | 41 ++++++++++++++++++++ 5 files changed, 107 insertions(+), 19 deletions(-) create mode 100644 deebot_client/events/base_station.py create mode 100644 tests/commands/json/test_station_state.py create mode 100644 tests/messages/json/test_station_state.py diff --git a/deebot_client/events/__init__.py b/deebot_client/events/__init__.py index dd80f69f4..32d478b78 100644 --- a/deebot_client/events/__init__.py +++ b/deebot_client/events/__init__.py @@ -8,8 +8,9 @@ from deebot_client.events.base import Event -from . import auto_empty +from . import auto_empty, base_station from .auto_empty import AutoEmptyEvent +from .base_station import BaseStationEvent from .efficiency_mode import EfficiencyMode, EfficiencyModeEvent from .fan_speed import FanSpeedEvent, FanSpeedLevel from .map import ( @@ -34,6 +35,7 @@ __all__ = [ "AutoEmptyEvent", + "BaseStationEvent", "BatteryEvent", "CachedMapInfoEvent", "CleanJobStatus", @@ -61,6 +63,7 @@ "WorkMode", "WorkModeEvent", "auto_empty", + "base_station", ] @@ -166,21 +169,6 @@ def from_xml(cls, value: str) -> LifeSpan: BASE_STATION_FILTER = "spHeap", "SpHeap" -@unique -class BaseStationStatus(IntEnum): - """Enum class for all possible base station statuses.""" - - IDLE = 0 - EMPTYING = 1 - - -@dataclass(frozen=True) -class BaseStationEvent(Event): - """Base Station Event representation.""" - - state: BaseStationStatus - - @dataclass(frozen=True) class LifeSpanEvent(Event): """Life span event representation.""" diff --git a/deebot_client/events/base_station.py b/deebot_client/events/base_station.py new file mode 100644 index 000000000..39e609e19 --- /dev/null +++ b/deebot_client/events/base_station.py @@ -0,0 +1,25 @@ +"""Base station event module.""" + +from __future__ import annotations + +from dataclasses import dataclass +from enum import IntEnum, unique + +from .base import Event as _Event + +__all__ = ["BaseStationEvent", "Status"] + + +@unique +class Status(IntEnum): + """Enum class for all possible base station statuses.""" + + IDLE = 0 + EMPTYING = 1 + + +@dataclass(frozen=True) +class BaseStationEvent(_Event): + """Base Station Event representation.""" + + state: Status diff --git a/deebot_client/messages/json/station_state.py b/deebot_client/messages/json/station_state.py index 6a2f749e9..36ca1409a 100644 --- a/deebot_client/messages/json/station_state.py +++ b/deebot_client/messages/json/station_state.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any -from deebot_client.events import BaseStationEvent, BaseStationStatus +from deebot_client.events.base_station import BaseStationEvent, Status from deebot_client.message import HandlingResult, MessageBodyDataDict if TYPE_CHECKING: @@ -28,14 +28,14 @@ def _handle_body_data_dict( # "body":{"data":{"content":{"error":[],"type":1,"motionState":1},"state":1},"code":0,"msg":"ok"} - Emptying if (state := data.get("state")) == 0: - reported_state = BaseStationStatus.IDLE + reported_state = Status.IDLE elif ( state == 1 and (content := data.get("content")) and content.get("type") == 1 and content.get("motionState") == 1 ): - reported_state = BaseStationStatus.EMPTYING + reported_state = Status.EMPTYING else: return HandlingResult.analyse() diff --git a/tests/commands/json/test_station_state.py b/tests/commands/json/test_station_state.py new file mode 100644 index 000000000..aa7104687 --- /dev/null +++ b/tests/commands/json/test_station_state.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +from typing import Any + +import pytest + +from deebot_client.commands.json.station_state import GetStationState +from deebot_client.events.base_station import BaseStationEvent, Status +from tests.helpers import get_request_json, get_success_body + +from . import assert_command + + +@pytest.mark.parametrize( + ("state", "additional_content", "expected"), + [ + (0, {"type": 0}, Status.IDLE), + (1, {"type": 1, "motionState": 1}, Status.EMPTYING), + ], +) +async def test_GetStationState( + state: int, + additional_content: dict[str, Any], + expected: Status, +) -> None: + json = get_request_json( + get_success_body( + { + "content": {"error": [], **additional_content}, + "state": state, + } + ) + ) + await assert_command(GetStationState(), json, BaseStationEvent(expected)) diff --git a/tests/messages/json/test_station_state.py b/tests/messages/json/test_station_state.py new file mode 100644 index 000000000..e862aa076 --- /dev/null +++ b/tests/messages/json/test_station_state.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +from typing import Any + +import pytest + +from deebot_client.events.base_station import BaseStationEvent, Status +from deebot_client.messages.json.station_state import OnStationState +from tests.messages import assert_message + + +@pytest.mark.parametrize( + ("state", "additional_content", "expected"), + [ + (0, {"type": 0}, Status.IDLE), + (1, {"type": 1, "motionState": 1}, Status.EMPTYING), + ], +) +def test_onStationState( + state: int, + additional_content: dict[str, Any], + expected: Status, +) -> None: + data: dict[str, Any] = { + "header": { + "pri": 1, + "tzm": 60, + "ts": "1734719921057", + "ver": "0.0.1", + "fwVer": "1.30.0", + "hwVer": "0.1.1", + "wkVer": "0.1.54", + }, + "body": { + "data": {"content": {"error": [], **additional_content}, "state": state}, + "code": 0, + "msg": "ok", + }, + } + + assert_message(OnStationState, data, BaseStationEvent(expected)) From 494a1caa674fb701c0fab2b61912b69c78253a0e Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Fri, 20 Dec 2024 21:17:41 +0000 Subject: [PATCH 9/9] Add StationAction tests --- tests/commands/json/test_station_action.py | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 tests/commands/json/test_station_action.py diff --git a/tests/commands/json/test_station_action.py b/tests/commands/json/test_station_action.py new file mode 100644 index 000000000..614706408 --- /dev/null +++ b/tests/commands/json/test_station_action.py @@ -0,0 +1,30 @@ +"""Auto empty tests.""" + +from __future__ import annotations + +from typing import Any + +import pytest + +from deebot_client.commands import BaseStationAction +from deebot_client.commands.json.station_action import StationAction + +from . import assert_execute_command + + +@pytest.mark.parametrize( + ("action", "args"), + [ + ( + BaseStationAction.EMPTY_DUSTBIN, + {"act": 1, "type": 1}, + ), + ], +) +async def test_StationAction( + action: BaseStationAction, + args: dict[str, Any], +) -> None: + """Test StationAction.""" + command = StationAction(action) + await assert_execute_command(command, args)