From 54264cf89ba3ed5dff2b873802ed860d4aa266c5 Mon Sep 17 00:00:00 2001 From: Christoph Fink Date: Mon, 16 Dec 2024 20:17:46 +0100 Subject: [PATCH 01/16] Add X5 OMNI support (4jd37g) (#679) --- deebot_client/hardware/deebot/4jd37g.py | 1 + 1 file changed, 1 insertion(+) create mode 120000 deebot_client/hardware/deebot/4jd37g.py diff --git a/deebot_client/hardware/deebot/4jd37g.py b/deebot_client/hardware/deebot/4jd37g.py new file mode 120000 index 00000000..0f26ba6a --- /dev/null +++ b/deebot_client/hardware/deebot/4jd37g.py @@ -0,0 +1 @@ +p1jij8.py \ No newline at end of file From f5c981f8558c3ae3a211bf60b296af7e3ae0eb64 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Mon, 16 Dec 2024 21:16:03 +0100 Subject: [PATCH 02/16] Pin to 3.13.0 until Pylint solve the issue (#691) --- .python-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.python-version b/.python-version index 3a4f41ef..77fdc6bb 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.13 \ No newline at end of file +3.13.0 \ No newline at end of file From 201b59df166e46e84b0ee0203431a11872c51068 Mon Sep 17 00:00:00 2001 From: marmer1 <64642738+marmer1@users.noreply.github.com> Date: Mon, 16 Dec 2024 21:18:29 +0100 Subject: [PATCH 03/16] Add Deebot N8 Black (7zya6u) (#686) Co-authored-by: Robert Resch --- deebot_client/hardware/deebot/7zya6u.py | 1 + 1 file changed, 1 insertion(+) create mode 120000 deebot_client/hardware/deebot/7zya6u.py diff --git a/deebot_client/hardware/deebot/7zya6u.py b/deebot_client/hardware/deebot/7zya6u.py new file mode 120000 index 00000000..ade0eb9b --- /dev/null +++ b/deebot_client/hardware/deebot/7zya6u.py @@ -0,0 +1 @@ +x5d34r.py \ No newline at end of file From 0cde1ddfd46e74042001d88308c6ec3c5a2f6a0e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 21:29:10 +0100 Subject: [PATCH 04/16] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Lock=20file=20mainte?= =?UTF-8?q?nance=20(#688)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- uv.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/uv.lock b/uv.lock index dee4ee5e..30379fc1 100644 --- a/uv.lock +++ b/uv.lock @@ -71,14 +71,14 @@ wheels = [ [[package]] name = "aiosignal" -version = "1.3.1" +version = "1.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "frozenlist" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/67/0952ed97a9793b4958e5736f6d2b346b414a2cd63e82d05940032f45b32f/aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc", size = 19422 } +sdist = { url = "https://files.pythonhosted.org/packages/ba/b5/6d55e80f6d8a08ce22b982eafa278d823b541c925f11ee774b0b9c43473d/aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54", size = 19424 } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/ac/a7305707cb852b7e16ff80eaf5692309bde30e2b1100a1fcacdc8f731d97/aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17", size = 7617 }, + { url = "https://files.pythonhosted.org/packages/ec/6a/bc7e17a3e87a2985d3e8f4da4cd0f481060eb78fb08596c42be62c90a4d9/aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", size = 7597 }, ] [[package]] @@ -92,11 +92,11 @@ wheels = [ [[package]] name = "attrs" -version = "24.2.0" +version = "24.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fc/0f/aafca9af9315aee06a89ffde799a10a582fe8de76c563ee80bbcdc08b3fb/attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", size = 792678 } +sdist = { url = "https://files.pythonhosted.org/packages/48/c8/6260f8ccc11f0917360fc0da435c5c9c7504e3db174d5a12a1494887b045/attrs-24.3.0.tar.gz", hash = "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff", size = 805984 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/21/5b6702a7f963e95456c0de2d495f67bf5fd62840ac655dc451586d23d39a/attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2", size = 63001 }, + { url = "https://files.pythonhosted.org/packages/89/aa/ab0f7891a01eeb2d2e338ae8fecbe57fcebea1a24dbb64d45801bfab481d/attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308", size = 63397 }, ] [[package]] @@ -110,11 +110,11 @@ wheels = [ [[package]] name = "certifi" -version = "2024.8.30" +version = "2024.12.14" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 } +sdist = { url = "https://files.pythonhosted.org/packages/0f/bd/1d41ee578ce09523c81a15426705dd20969f5abf006d1afe8aeff0dd776a/certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db", size = 166010 } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 }, + { url = "https://files.pythonhosted.org/packages/a5/32/8f6669fc4798494966bf446c8c4a162e0b5d893dff088afddf76414f70e1/certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", size = 164927 }, ] [[package]] From a8da71891a284f90708c46b38843b3dcad7631da Mon Sep 17 00:00:00 2001 From: seanyan1994 Date: Tue, 17 Dec 2024 04:30:51 +0800 Subject: [PATCH 05/16] Add DEEBOT T8 MAX (7n95dm) (#680) Co-authored-by: Robert Resch --- deebot_client/hardware/deebot/7n95dm.py | 1 + 1 file changed, 1 insertion(+) create mode 120000 deebot_client/hardware/deebot/7n95dm.py diff --git a/deebot_client/hardware/deebot/7n95dm.py b/deebot_client/hardware/deebot/7n95dm.py new file mode 120000 index 00000000..ade0eb9b --- /dev/null +++ b/deebot_client/hardware/deebot/7n95dm.py @@ -0,0 +1 @@ +x5d34r.py \ No newline at end of file From 5937a2df8f34133fa6bbad7dfdbc5fa069ddf83e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 17 Dec 2024 00:44:20 +0100 Subject: [PATCH 06/16] [pre-commit.ci] pre-commit autoupdate (#693) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 38a675f1..16abd505 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ default_language_version: repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.2 + rev: v0.8.3 hooks: - id: ruff args: From 509e3c871329be2e14faa9103cedda5369f322ed Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Tue, 17 Dec 2024 00:57:24 +0100 Subject: [PATCH 07/16] Replace deprecated class properties (#694) --- deebot_client/command.py | 47 +++++++++---------- deebot_client/commands/json/__init__.py | 5 +- deebot_client/commands/json/advanced_mode.py | 6 +-- deebot_client/commands/json/battery.py | 2 +- deebot_client/commands/json/border_switch.py | 6 +-- deebot_client/commands/json/carpet.py | 6 +-- deebot_client/commands/json/charge.py | 2 +- deebot_client/commands/json/charge_state.py | 2 +- deebot_client/commands/json/child_lock.py | 6 +-- deebot_client/commands/json/clean.py | 8 ++-- deebot_client/commands/json/clean_count.py | 4 +- deebot_client/commands/json/clean_logs.py | 4 +- .../commands/json/clean_preference.py | 6 +-- deebot_client/commands/json/clear_map.py | 2 +- deebot_client/commands/json/common.py | 20 ++++---- .../commands/json/continuous_cleaning.py | 6 +-- .../commands/json/cross_map_border_warning.py | 6 +-- deebot_client/commands/json/custom.py | 12 ++--- deebot_client/commands/json/cut_direction.py | 4 +- deebot_client/commands/json/efficiency.py | 4 +- deebot_client/commands/json/error.py | 2 +- deebot_client/commands/json/fan_speed.py | 4 +- deebot_client/commands/json/life_span.py | 4 +- deebot_client/commands/json/map.py | 14 +++--- deebot_client/commands/json/moveup_warning.py | 6 +-- deebot_client/commands/json/multimap_state.py | 6 +-- deebot_client/commands/json/network.py | 2 +- deebot_client/commands/json/ota.py | 4 +- deebot_client/commands/json/play_sound.py | 2 +- deebot_client/commands/json/pos.py | 2 +- deebot_client/commands/json/relocation.py | 2 +- deebot_client/commands/json/safe_protect.py | 6 +-- deebot_client/commands/json/stats.py | 4 +- deebot_client/commands/json/sweep_mode.py | 6 +-- deebot_client/commands/json/true_detect.py | 6 +-- .../commands/json/voice_assistant_state.py | 6 +-- deebot_client/commands/json/volume.py | 4 +- deebot_client/commands/json/water_info.py | 4 +- deebot_client/commands/json/work_mode.py | 4 +- deebot_client/commands/xml/__init__.py | 5 +- deebot_client/commands/xml/charge_state.py | 2 +- deebot_client/commands/xml/common.py | 15 ++---- deebot_client/commands/xml/error.py | 2 +- deebot_client/commands/xml/fan_speed.py | 2 +- deebot_client/commands/xml/pos.py | 2 +- deebot_client/commands/xml/stats.py | 2 +- deebot_client/message.py | 32 +++++++------ deebot_client/messages/json/__init__.py | 2 +- deebot_client/messages/json/battery.py | 2 +- deebot_client/messages/json/map.py | 2 +- deebot_client/messages/json/stats.py | 2 +- deebot_client/util/__init__.py | 12 +++++ tests/commands/json/__init__.py | 4 +- tests/commands/json/test_clean_log.py | 2 +- tests/commands/json/test_common.py | 4 +- tests/test_command.py | 27 +++++++++-- tests/test_mqtt_client.py | 8 ++-- 57 files changed, 195 insertions(+), 178 deletions(-) diff --git a/deebot_client/command.py b/deebot_client/command.py index 416467be..4490b9ff 100644 --- a/deebot_client/command.py +++ b/deebot_client/command.py @@ -12,6 +12,7 @@ ApiTimeoutError, DeebotError, ) +from deebot_client.util import verify_required_class_variables_exists from .const import PATH_API_IOT_DEVMANAGER, REQUEST_HEADERS, DataType from .logging_filter import get_logger @@ -64,24 +65,18 @@ class Command(ABC): """Abstract command object.""" _targets_bot: bool = True + NAME: str + DATA_TYPE: DataType + + def __init_subclass__(cls) -> None: + verify_required_class_variables_exists(cls, ("NAME", "DATA_TYPE")) + return super().__init_subclass__() def __init__(self, args: dict[str, Any] | list[Any] | None = None) -> None: if args is None: args = {} self._args = args - @property # type: ignore[misc] - @classmethod - @abstractmethod - def name(cls) -> str: - """Command name.""" - - @property # type: ignore[misc] - @classmethod - @abstractmethod - def data_type(cls) -> DataType: - """Data type.""" - @abstractmethod def _get_payload(self) -> dict[str, Any] | list[Any] | str: """Get the payload for the rest call.""" @@ -115,7 +110,7 @@ async def execute( except Exception: # pylint: disable=broad-except _LOGGER.warning( "Could not execute command %s", - self.name, + self.NAME, exc_info=True, ) return DeviceCommandResult(device_reached=False) @@ -132,14 +127,14 @@ async def _execute( except ApiTimeoutError: _LOGGER.warning( "Could not execute command %s: Timeout reached", - self.name, + self.NAME, ) return CommandResult(HandlingState.ERROR), {} result = self.__handle_response(event_bus, response) if result.state == HandlingState.ANALYSE: _LOGGER.debug( - "ANALYSE: Could not handle command: %s with %s", self.name, response + "ANALYSE: Could not handle command: %s with %s", self.NAME, response ) return ( CommandResult( @@ -150,16 +145,16 @@ async def _execute( response, ) if result.state == HandlingState.ERROR: - _LOGGER.warning("Could not parse %s: %s", self.name, response) + _LOGGER.warning("Could not parse %s: %s", self.NAME, response) return result, response async def _execute_api_request( self, authenticator: Authenticator, device_info: ApiDeviceInfo ) -> dict[str, Any]: payload = { - "cmdName": self.name, + "cmdName": self.NAME, "payload": self._get_payload(), - "payloadType": self.data_type.value, + "payloadType": self.DATA_TYPE.value, "td": "q", "toId": device_info["did"], "toRes": device_info["resource"], @@ -195,7 +190,7 @@ def __handle_response( result = self._handle_response(event_bus, response) if result.state == HandlingState.ANALYSE: _LOGGER.debug( - "ANALYSE: Could not handle command: %s with %s", self.name, response + "ANALYSE: Could not handle command: %s with %s", self.NAME, response ) return CommandResult( HandlingState.ANALYSE_LOGGED, @@ -206,7 +201,7 @@ def __handle_response( except Exception: # pylint: disable=broad-except _LOGGER.warning( "Could not parse response for %s: %s", - self.name, + self.NAME, response, exc_info=True, ) @@ -223,12 +218,12 @@ def _handle_response( def __eq__(self, obj: object) -> bool: if isinstance(obj, Command): - return self.name == obj.name and self._args == obj._args + return self.NAME == obj.NAME and self._args == obj._args return False def __hash__(self) -> int: - return hash(self.name) + hash(self._args) + return hash(self.NAME) + hash(self._args) class CommandWithMessageHandling(Command, Message, ABC): @@ -253,7 +248,7 @@ def _handle_response( case 4200: # bot offline _LOGGER.info( - 'Device is offline. Could not execute command "%s"', self.name + 'Device is offline. Could not execute command "%s"', self.NAME ) event_bus.notify(AvailabilityEvent(available=False)) return CommandResult(HandlingState.FAILED) @@ -261,16 +256,16 @@ def _handle_response( if self._is_available_check: _LOGGER.info( 'No response received for command "%s" during availability-check.', - self.name, + self.NAME, ) else: _LOGGER.warning( 'No response received for command "%s". This can happen if the device has network issues or does not support the command', - self.name, + self.NAME, ) return CommandResult(HandlingState.FAILED) - _LOGGER.warning('Command "%s" was not successfully.', self.name) + _LOGGER.warning('Command "%s" was not successfully.', self.NAME) return CommandResult(HandlingState.ANALYSE) diff --git a/deebot_client/commands/json/__init__.py b/deebot_client/commands/json/__init__.py index 384e2697..21584825 100644 --- a/deebot_client/commands/json/__init__.py +++ b/deebot_client/commands/json/__init__.py @@ -230,10 +230,7 @@ ] # fmt: on -COMMANDS: dict[str, type[Command]] = { - cmd.name: cmd # type: ignore[misc] - for cmd in _COMMANDS -} +COMMANDS: dict[str, type[Command]] = {cmd.NAME: cmd for cmd in _COMMANDS} COMMANDS_WITH_MQTT_P2P_HANDLING: dict[str, type[CommandMqttP2P]] = { cmd_name: cmd diff --git a/deebot_client/commands/json/advanced_mode.py b/deebot_client/commands/json/advanced_mode.py index 9c000c87..18496edd 100644 --- a/deebot_client/commands/json/advanced_mode.py +++ b/deebot_client/commands/json/advanced_mode.py @@ -10,12 +10,12 @@ class GetAdvancedMode(GetEnableCommand): """Get advanced mode command.""" - name = "getAdvancedMode" - event_type = AdvancedModeEvent + NAME = "getAdvancedMode" + EVENT_TYPE = AdvancedModeEvent class SetAdvancedMode(SetEnableCommand): """Set advanced mode command.""" - name = "setAdvancedMode" + NAME = "setAdvancedMode" get_command = GetAdvancedMode diff --git a/deebot_client/commands/json/battery.py b/deebot_client/commands/json/battery.py index 64c2c33c..a55bb402 100644 --- a/deebot_client/commands/json/battery.py +++ b/deebot_client/commands/json/battery.py @@ -10,7 +10,7 @@ class GetBattery(OnBattery, JsonCommandWithMessageHandling): """Get battery command.""" - name = "getBattery" + NAME = "getBattery" def __init__(self, *, is_available_check: bool = False) -> None: super().__init__() diff --git a/deebot_client/commands/json/border_switch.py b/deebot_client/commands/json/border_switch.py index 9099fd6b..06406f55 100644 --- a/deebot_client/commands/json/border_switch.py +++ b/deebot_client/commands/json/border_switch.py @@ -10,12 +10,12 @@ class GetBorderSwitch(GetEnableCommand): """Get border switch command.""" - name = "getBorderSwitch" - event_type = BorderSwitchEvent + NAME = "getBorderSwitch" + EVENT_TYPE = BorderSwitchEvent class SetBorderSwitch(SetEnableCommand): """Set border switch command.""" - name = "setBorderSwitch" + NAME = "setBorderSwitch" get_command = GetBorderSwitch diff --git a/deebot_client/commands/json/carpet.py b/deebot_client/commands/json/carpet.py index dd30fade..677b4fcf 100644 --- a/deebot_client/commands/json/carpet.py +++ b/deebot_client/commands/json/carpet.py @@ -10,12 +10,12 @@ class GetCarpetAutoFanBoost(GetEnableCommand): """Get carpet auto fan boost command.""" - name = "getCarpertPressure" - event_type = CarpetAutoFanBoostEvent + NAME = "getCarpertPressure" + EVENT_TYPE = CarpetAutoFanBoostEvent class SetCarpetAutoFanBoost(SetEnableCommand): """Set carpet auto fan boost command.""" - name = "setCarpertPressure" + NAME = "setCarpertPressure" get_command = GetCarpetAutoFanBoost diff --git a/deebot_client/commands/json/charge.py b/deebot_client/commands/json/charge.py index b6288b8d..9e1992b5 100644 --- a/deebot_client/commands/json/charge.py +++ b/deebot_client/commands/json/charge.py @@ -21,7 +21,7 @@ class Charge(ExecuteCommand): """Charge command.""" - name = "charge" + NAME = "charge" def __init__(self) -> None: super().__init__({"act": "go"}) diff --git a/deebot_client/commands/json/charge_state.py b/deebot_client/commands/json/charge_state.py index a9ceedad..77adb4ca 100644 --- a/deebot_client/commands/json/charge_state.py +++ b/deebot_client/commands/json/charge_state.py @@ -18,7 +18,7 @@ class GetChargeState(JsonCommandWithMessageHandling, MessageBodyDataDict): """Get charge state command.""" - name = "getChargeState" + NAME = "getChargeState" @classmethod def _handle_body_data_dict( diff --git a/deebot_client/commands/json/child_lock.py b/deebot_client/commands/json/child_lock.py index 5f43e662..f5981b08 100644 --- a/deebot_client/commands/json/child_lock.py +++ b/deebot_client/commands/json/child_lock.py @@ -10,14 +10,14 @@ class GetChildLock(GetEnableCommand): """Get child lock command.""" - name = "getChildLock" - event_type = ChildLockEvent + NAME = "getChildLock" + EVENT_TYPE = ChildLockEvent _field_name = "on" class SetChildLock(SetEnableCommand): """Set child lock command.""" - name = "setChildLock" + NAME = "setChildLock" get_command = GetChildLock _field_name = "on" diff --git a/deebot_client/commands/json/clean.py b/deebot_client/commands/json/clean.py index 99ef10e5..ae0e51c1 100644 --- a/deebot_client/commands/json/clean.py +++ b/deebot_client/commands/json/clean.py @@ -22,7 +22,7 @@ class Clean(ExecuteCommand): """Clean command.""" - name = "clean" + NAME = "clean" def __init__(self, action: CleanAction) -> None: super().__init__(self._get_args(action)) @@ -77,7 +77,7 @@ def _get_args(self, action: CleanAction) -> dict[str, Any]: class CleanV2(Clean): """Clean V2 command.""" - name = "clean_V2" + NAME = "clean_V2" def _get_args(self, action: CleanAction) -> dict[str, Any]: content: dict[str, str] = {} @@ -107,7 +107,7 @@ def _get_args(self, action: CleanAction) -> dict[str, Any]: class GetCleanInfo(JsonCommandWithMessageHandling, MessageBodyDataDict): """Get clean info command.""" - name = "getCleanInfo" + NAME = "getCleanInfo" @classmethod def _handle_body_data_dict( @@ -158,4 +158,4 @@ def _handle_body_data_dict( class GetCleanInfoV2(GetCleanInfo): """Get clean info v2 command.""" - name = "getCleanInfo_V2" + NAME = "getCleanInfo_V2" diff --git a/deebot_client/commands/json/clean_count.py b/deebot_client/commands/json/clean_count.py index d59e009e..dbfae0d9 100644 --- a/deebot_client/commands/json/clean_count.py +++ b/deebot_client/commands/json/clean_count.py @@ -18,7 +18,7 @@ class GetCleanCount(JsonGetCommand): """Get clean count command.""" - name = "getCleanCount" + NAME = "getCleanCount" @classmethod def _handle_body_data_dict( @@ -35,7 +35,7 @@ def _handle_body_data_dict( class SetCleanCount(JsonSetCommand): """Set clean count command.""" - name = "setCleanCount" + NAME = "setCleanCount" get_command = GetCleanCount _mqtt_params = MappingProxyType({"count": InitParam(int)}) diff --git a/deebot_client/commands/json/clean_logs.py b/deebot_client/commands/json/clean_logs.py index 75024805..c80feadf 100644 --- a/deebot_client/commands/json/clean_logs.py +++ b/deebot_client/commands/json/clean_logs.py @@ -23,7 +23,7 @@ class GetCleanLogs(JsonCommand): """Get clean logs command.""" _targets_bot: bool = False - name = "GetCleanLogs" + NAME = "GetCleanLogs" def __init__(self, count: int = 0) -> None: super().__init__({"count": count}) @@ -32,7 +32,7 @@ async def _execute_api_request( self, authenticator: Authenticator, device_info: ApiDeviceInfo ) -> dict[str, Any]: json = { - "td": self.name, + "td": self.NAME, "did": device_info["did"], "resource": device_info["resource"], } diff --git a/deebot_client/commands/json/clean_preference.py b/deebot_client/commands/json/clean_preference.py index 3b46bca3..6d1ab688 100644 --- a/deebot_client/commands/json/clean_preference.py +++ b/deebot_client/commands/json/clean_preference.py @@ -10,12 +10,12 @@ class GetCleanPreference(GetEnableCommand): """Get clean preference command.""" - name = "getCleanPreference" - event_type = CleanPreferenceEvent + NAME = "getCleanPreference" + EVENT_TYPE = CleanPreferenceEvent class SetCleanPreference(SetEnableCommand): """Set clean preference command.""" - name = "setCleanPreference" + NAME = "setCleanPreference" get_command = GetCleanPreference diff --git a/deebot_client/commands/json/clear_map.py b/deebot_client/commands/json/clear_map.py index 4d1c7eaa..11941849 100644 --- a/deebot_client/commands/json/clear_map.py +++ b/deebot_client/commands/json/clear_map.py @@ -8,7 +8,7 @@ class ClearMap(ExecuteCommand): """ClearMap state command.""" - name = "clearMap" + NAME = "clearMap" def __init__(self) -> None: super().__init__({"type": "all"}) diff --git a/deebot_client/commands/json/common.py b/deebot_client/commands/json/common.py index bbb25aff..d7d80811 100644 --- a/deebot_client/commands/json/common.py +++ b/deebot_client/commands/json/common.py @@ -2,7 +2,7 @@ from __future__ import annotations -from abc import ABC, abstractmethod +from abc import ABC from datetime import datetime from types import MappingProxyType from typing import TYPE_CHECKING, Any @@ -22,6 +22,7 @@ MessageBody, MessageBodyDataDict, ) +from deebot_client.util import verify_required_class_variables_exists from .const import CODE @@ -32,10 +33,10 @@ _LOGGER = get_logger(__name__) -class JsonCommand(Command): +class JsonCommand(Command, ABC): """Json base command.""" - data_type: DataType = DataType.JSON + DATA_TYPE = DataType.JSON def _get_payload(self) -> dict[str, Any] | list[Any]: payload = { @@ -72,7 +73,7 @@ def _handle_body(cls, _: EventBus, body: dict[str, Any]) -> HandlingResult: if body.get(CODE, -1) == 0: return HandlingResult.success() - _LOGGER.warning('Command "%s" was not successfully. body=%s', cls.name, body) + _LOGGER.warning('Command "%s" was not successfully. body=%s', cls.NAME, body) return HandlingResult(HandlingState.FAILED) @@ -100,12 +101,11 @@ class GetEnableCommand(JsonGetCommand, ABC): """Abstract get enable command.""" _field_name: str = "enable" + EVENT_TYPE: type[EnableEvent] - @property # type: ignore[misc] - @classmethod - @abstractmethod - def event_type(cls) -> type[EnableEvent]: - """Event type.""" + def __init_subclass__(cls) -> None: + verify_required_class_variables_exists(cls, ("EVENT_TYPE",)) + return super().__init_subclass__() @classmethod def _handle_body_data_dict( @@ -115,7 +115,7 @@ def _handle_body_data_dict( :return: A message response """ - event: EnableEvent = cls.event_type(bool(data[cls._field_name])) # type: ignore[call-arg, assignment] + event: EnableEvent = cls.EVENT_TYPE(bool(data[cls._field_name])) event_bus.notify(event) return HandlingResult.success() diff --git a/deebot_client/commands/json/continuous_cleaning.py b/deebot_client/commands/json/continuous_cleaning.py index 34273c5e..d735daea 100644 --- a/deebot_client/commands/json/continuous_cleaning.py +++ b/deebot_client/commands/json/continuous_cleaning.py @@ -10,12 +10,12 @@ class GetContinuousCleaning(GetEnableCommand): """Get continuous cleaning command.""" - name = "getBreakPoint" - event_type = ContinuousCleaningEvent + NAME = "getBreakPoint" + EVENT_TYPE = ContinuousCleaningEvent class SetContinuousCleaning(SetEnableCommand): """Set continuous cleaning command.""" - name = "setBreakPoint" + NAME = "setBreakPoint" get_command = GetContinuousCleaning diff --git a/deebot_client/commands/json/cross_map_border_warning.py b/deebot_client/commands/json/cross_map_border_warning.py index f2e1d5dd..0117312b 100644 --- a/deebot_client/commands/json/cross_map_border_warning.py +++ b/deebot_client/commands/json/cross_map_border_warning.py @@ -10,12 +10,12 @@ class GetCrossMapBorderWarning(GetEnableCommand): """Get cross map border warning command.""" - name = "getCrossMapBorderWarning" - event_type = CrossMapBorderWarningEvent + NAME = "getCrossMapBorderWarning" + EVENT_TYPE = CrossMapBorderWarningEvent class SetCrossMapBorderWarning(SetEnableCommand): """Set cross map border warning command.""" - name = "setCrossMapBorderWarning" + NAME = "setCrossMapBorderWarning" get_command = GetCrossMapBorderWarning diff --git a/deebot_client/commands/json/custom.py b/deebot_client/commands/json/custom.py index a71c3091..6f8da581 100644 --- a/deebot_client/commands/json/custom.py +++ b/deebot_client/commands/json/custom.py @@ -19,12 +19,12 @@ class CustomCommand(JsonCommand): """Custom command, used when user wants to execute a command, which is not part of this library.""" - name: str = "CustomCommand" + NAME: str = "CustomCommand" def __init__( self, name: str, args: dict[str, Any] | list[Any] | None = None ) -> None: - self.name = name + self.NAME = name super().__init__(args) def _handle_response( @@ -36,20 +36,20 @@ def _handle_response( """ if response.get("ret") == "ok": data = response.get("resp", response) - event_bus.notify(CustomCommandEvent(self.name, data)) + event_bus.notify(CustomCommandEvent(self.NAME, data)) return CommandResult.success() - _LOGGER.warning('Command "%s" was not successfully: %s', self.name, response) + _LOGGER.warning('Command "%s" was not successfully: %s', self.NAME, response) return CommandResult(HandlingState.FAILED) def __eq__(self, obj: object) -> bool: if super().__eq__(obj) and isinstance(obj, CustomCommand): - return self.name == obj.name + return self.NAME == obj.NAME return False def __hash__(self) -> int: - return super().__hash__() + hash(self.name) + return super().__hash__() + hash(self.NAME) class CustomPayloadCommand(CustomCommand): diff --git a/deebot_client/commands/json/cut_direction.py b/deebot_client/commands/json/cut_direction.py index e05bb58f..7e4b3ba5 100644 --- a/deebot_client/commands/json/cut_direction.py +++ b/deebot_client/commands/json/cut_direction.py @@ -18,7 +18,7 @@ class GetCutDirection(JsonGetCommand): """Get cut direction command.""" - name = "getCutDirection" + NAME = "getCutDirection" @classmethod def _handle_body_data_dict( @@ -35,7 +35,7 @@ def _handle_body_data_dict( class SetCutDirection(JsonSetCommand): """Set cut direction command.""" - name = "setCutDirection" + NAME = "setCutDirection" get_command = GetCutDirection _mqtt_params = MappingProxyType({"angle": InitParam(int)}) diff --git a/deebot_client/commands/json/efficiency.py b/deebot_client/commands/json/efficiency.py index 511f1835..33764c87 100644 --- a/deebot_client/commands/json/efficiency.py +++ b/deebot_client/commands/json/efficiency.py @@ -19,7 +19,7 @@ class GetEfficiencyMode(JsonGetCommand): """Get efficiency mode command.""" - name = "getEfficiency" + NAME = "getEfficiency" @classmethod def _handle_body_data_dict( @@ -36,7 +36,7 @@ def _handle_body_data_dict( class SetEfficiencyMode(JsonSetCommand): """Set efficiency mode command.""" - name = "setEfficiency" + NAME = "setEfficiency" get_command = GetEfficiencyMode _mqtt_params = MappingProxyType({"efficiency": InitParam(EfficiencyMode)}) diff --git a/deebot_client/commands/json/error.py b/deebot_client/commands/json/error.py index 605f27d3..8f9bfc05 100644 --- a/deebot_client/commands/json/error.py +++ b/deebot_client/commands/json/error.py @@ -18,7 +18,7 @@ class GetError(JsonCommandWithMessageHandling, MessageBodyDataDict): """Get error command.""" - name = "getError" + NAME = "getError" @classmethod def _handle_body_data_dict( diff --git a/deebot_client/commands/json/fan_speed.py b/deebot_client/commands/json/fan_speed.py index 33902ad8..f4303564 100644 --- a/deebot_client/commands/json/fan_speed.py +++ b/deebot_client/commands/json/fan_speed.py @@ -19,7 +19,7 @@ class GetFanSpeed(JsonGetCommand): """Get fan speed command.""" - name = "getSpeed" + NAME = "getSpeed" @classmethod def _handle_body_data_dict( @@ -36,7 +36,7 @@ def _handle_body_data_dict( class SetFanSpeed(JsonSetCommand): """Set fan speed command.""" - name = "setSpeed" + NAME = "setSpeed" get_command = GetFanSpeed _mqtt_params = MappingProxyType({"speed": InitParam(FanSpeedLevel)}) diff --git a/deebot_client/commands/json/life_span.py b/deebot_client/commands/json/life_span.py index 4091125b..3a7b6e3c 100644 --- a/deebot_client/commands/json/life_span.py +++ b/deebot_client/commands/json/life_span.py @@ -19,7 +19,7 @@ class GetLifeSpan(JsonCommandWithMessageHandling, MessageBodyDataList): """Get life span command.""" - name = "getLifeSpan" + NAME = "getLifeSpan" def __init__(self, life_spans: LST[LifeSpan]) -> None: args = [life_span.value for life_span in life_spans] @@ -50,7 +50,7 @@ def _handle_body_data_list( class ResetLifeSpan(ExecuteCommand, CommandMqttP2P): """Reset life span command.""" - name = "resetLifeSpan" + NAME = "resetLifeSpan" _mqtt_params = MappingProxyType({"type": InitParam(LifeSpan, "life_span")}) def __init__(self, life_span: LifeSpan) -> None: diff --git a/deebot_client/commands/json/map.py b/deebot_client/commands/json/map.py index 73aa0cff..514d4415 100644 --- a/deebot_client/commands/json/map.py +++ b/deebot_client/commands/json/map.py @@ -32,7 +32,7 @@ class GetCachedMapInfo(JsonCommandWithMessageHandling, MessageBodyDataDict): """Get cached map info command.""" - name = "getCachedMapInfo" + NAME = "getCachedMapInfo" # version definition for using type of getMapSet v1 or v2 _map_set_command: type[GetMapSet | GetMapSetV2] @@ -94,7 +94,7 @@ def _handle_response( class GetMajorMap(JsonCommandWithMessageHandling, MessageBodyDataDict): """Get major map command.""" - name = "getMajorMap" + NAME = "getMajorMap" @classmethod def _handle_body_data_dict( @@ -135,7 +135,7 @@ class GetMapSet(JsonCommandWithMessageHandling, MessageBodyDataDict): _ARGS_TYPE = "type" _ARGS_SUBSETS = "subsets" - name = "getMapSet" + NAME = "getMapSet" def __init__( self, @@ -222,7 +222,7 @@ class GetMapSubSet(JsonCommandWithMessageHandling, MessageBodyDataDict): } ) - name = "getMapSubSet" + NAME = "getMapSubSet" def __init__( self, @@ -297,7 +297,7 @@ def _handle_body_data_dict( class GetMapSetV2(GetMapSet): """Get map set v2 command.""" - name = "getMapSet_V2" + NAME = "getMapSet_V2" @classmethod def _get_subset_ids( @@ -341,7 +341,7 @@ class GetMapTrace(JsonCommandWithMessageHandling, MessageBodyDataDict): _TRACE_POINT_COUNT = 200 - name = "getMapTrace" + NAME = "getMapTrace" def __init__(self, trace_start: int = 0) -> None: super().__init__( @@ -387,7 +387,7 @@ def _handle_response( class GetMinorMap(JsonCommandWithMessageHandling, MessageBodyDataDict): """Get minor map command.""" - name = "getMinorMap" + NAME = "getMinorMap" def __init__(self, *, map_id: str, piece_index: int) -> None: super().__init__({"mid": map_id, "type": "ol", "pieceIndex": piece_index}) diff --git a/deebot_client/commands/json/moveup_warning.py b/deebot_client/commands/json/moveup_warning.py index 4c17ccc9..b6063e28 100644 --- a/deebot_client/commands/json/moveup_warning.py +++ b/deebot_client/commands/json/moveup_warning.py @@ -10,12 +10,12 @@ class GetMoveUpWarning(GetEnableCommand): """Get move up lock command.""" - name = "getMoveupWarning" - event_type = MoveUpWarningEvent + NAME = "getMoveupWarning" + EVENT_TYPE = MoveUpWarningEvent class SetMoveUpWarning(SetEnableCommand): """Set move up lock command.""" - name = "setMoveupWarning" + NAME = "setMoveupWarning" get_command = GetMoveUpWarning diff --git a/deebot_client/commands/json/multimap_state.py b/deebot_client/commands/json/multimap_state.py index a9dd5cd0..5d4eeacf 100644 --- a/deebot_client/commands/json/multimap_state.py +++ b/deebot_client/commands/json/multimap_state.py @@ -10,12 +10,12 @@ class GetMultimapState(GetEnableCommand): """Get multimap state command.""" - name = "getMultiMapState" - event_type = MultimapStateEvent + NAME = "getMultiMapState" + EVENT_TYPE = MultimapStateEvent class SetMultimapState(SetEnableCommand): """Set multimap state command.""" - name = "setMultiMapState" + NAME = "setMultiMapState" get_command = GetMultimapState diff --git a/deebot_client/commands/json/network.py b/deebot_client/commands/json/network.py index b5c6c816..50696a57 100644 --- a/deebot_client/commands/json/network.py +++ b/deebot_client/commands/json/network.py @@ -16,7 +16,7 @@ class GetNetInfo(JsonCommandWithMessageHandling, MessageBodyDataDict): """Get network info command.""" - name = "getNetInfo" + NAME = "getNetInfo" @classmethod def _handle_body_data_dict( diff --git a/deebot_client/commands/json/ota.py b/deebot_client/commands/json/ota.py index 8ee5bdcd..7db059c1 100644 --- a/deebot_client/commands/json/ota.py +++ b/deebot_client/commands/json/ota.py @@ -18,7 +18,7 @@ class GetOta(JsonGetCommand): """Get ota command.""" - name = "getOta" + NAME = "getOta" @classmethod def _handle_body_data_dict( @@ -50,7 +50,7 @@ def handle_set_args( class SetOta(JsonSetCommand): """Set ota command.""" - name = "setOta" + NAME = "setOta" get_command = GetOta _mqtt_params = MappingProxyType({"autoSwitch": InitParam(bool, "auto_enabled")}) diff --git a/deebot_client/commands/json/play_sound.py b/deebot_client/commands/json/play_sound.py index ab3bd0b3..47023435 100644 --- a/deebot_client/commands/json/play_sound.py +++ b/deebot_client/commands/json/play_sound.py @@ -8,7 +8,7 @@ class PlaySound(ExecuteCommand): """Play sound command.""" - name = "playSound" + NAME = "playSound" def __init__(self) -> None: super().__init__({"count": 1, "sid": 30}) diff --git a/deebot_client/commands/json/pos.py b/deebot_client/commands/json/pos.py index 27564b9f..27f81773 100644 --- a/deebot_client/commands/json/pos.py +++ b/deebot_client/commands/json/pos.py @@ -16,7 +16,7 @@ class GetPos(JsonCommandWithMessageHandling, MessageBodyDataDict): """Get volume command.""" - name = "getPos" + NAME = "getPos" def __init__(self) -> None: super().__init__(["chargePos", "deebotPos"]) diff --git a/deebot_client/commands/json/relocation.py b/deebot_client/commands/json/relocation.py index 7d394b33..5849ad1f 100644 --- a/deebot_client/commands/json/relocation.py +++ b/deebot_client/commands/json/relocation.py @@ -8,7 +8,7 @@ class SetRelocationState(ExecuteCommand): """Set relocation state command.""" - name = "setRelocationState" + NAME = "setRelocationState" def __init__(self) -> None: super().__init__({"mode": "manu"}) diff --git a/deebot_client/commands/json/safe_protect.py b/deebot_client/commands/json/safe_protect.py index 25444218..1e3cb43e 100644 --- a/deebot_client/commands/json/safe_protect.py +++ b/deebot_client/commands/json/safe_protect.py @@ -10,12 +10,12 @@ class GetSafeProtect(GetEnableCommand): """Get safe protect command.""" - name = "getSafeProtect" - event_type = SafeProtectEvent + NAME = "getSafeProtect" + EVENT_TYPE = SafeProtectEvent class SetSafeProtect(SetEnableCommand): """Set safe protect command.""" - name = "setSafeProtect" + NAME = "setSafeProtect" get_command = GetSafeProtect diff --git a/deebot_client/commands/json/stats.py b/deebot_client/commands/json/stats.py index 133f90f4..70a9cb3e 100644 --- a/deebot_client/commands/json/stats.py +++ b/deebot_client/commands/json/stats.py @@ -16,7 +16,7 @@ class GetStats(JsonCommandWithMessageHandling, MessageBodyDataDict): """Get stats command.""" - name = "getStats" + NAME = "getStats" @classmethod def _handle_body_data_dict( @@ -38,7 +38,7 @@ def _handle_body_data_dict( class GetTotalStats(JsonCommandWithMessageHandling, MessageBodyDataDict): """Get stats command.""" - name = "getTotalStats" + NAME = "getTotalStats" @classmethod def _handle_body_data_dict( diff --git a/deebot_client/commands/json/sweep_mode.py b/deebot_client/commands/json/sweep_mode.py index de12c244..5e8bf5d9 100644 --- a/deebot_client/commands/json/sweep_mode.py +++ b/deebot_client/commands/json/sweep_mode.py @@ -10,14 +10,14 @@ class GetSweepMode(GetEnableCommand): """GetSweepMode command.""" - name = "getSweepMode" - event_type = SweepModeEvent + NAME = "getSweepMode" + EVENT_TYPE = SweepModeEvent _field_name = "type" class SetSweepMode(SetEnableCommand): """SetSweepMode command.""" - name = "setSweepMode" + NAME = "setSweepMode" get_command = GetSweepMode _field_name = "type" diff --git a/deebot_client/commands/json/true_detect.py b/deebot_client/commands/json/true_detect.py index 32f05f2a..982336a4 100644 --- a/deebot_client/commands/json/true_detect.py +++ b/deebot_client/commands/json/true_detect.py @@ -10,12 +10,12 @@ class GetTrueDetect(GetEnableCommand): """Get multimap state command.""" - name = "getTrueDetect" - event_type = TrueDetectEvent + NAME = "getTrueDetect" + EVENT_TYPE = TrueDetectEvent class SetTrueDetect(SetEnableCommand): """Set multimap state command.""" - name = "setTrueDetect" + NAME = "setTrueDetect" get_command = GetTrueDetect diff --git a/deebot_client/commands/json/voice_assistant_state.py b/deebot_client/commands/json/voice_assistant_state.py index 15838ef1..20a445cf 100644 --- a/deebot_client/commands/json/voice_assistant_state.py +++ b/deebot_client/commands/json/voice_assistant_state.py @@ -10,12 +10,12 @@ class GetVoiceAssistantState(GetEnableCommand): """Get voice assistant state command.""" - name = "getVoiceAssistantState" - event_type = VoiceAssistantStateEvent + NAME = "getVoiceAssistantState" + EVENT_TYPE = VoiceAssistantStateEvent class SetVoiceAssistantState(SetEnableCommand): """Set voice assistant state command.""" - name = "setVoiceAssistantState" + NAME = "setVoiceAssistantState" get_command = GetVoiceAssistantState diff --git a/deebot_client/commands/json/volume.py b/deebot_client/commands/json/volume.py index d11f9f17..24fb5927 100644 --- a/deebot_client/commands/json/volume.py +++ b/deebot_client/commands/json/volume.py @@ -18,7 +18,7 @@ class GetVolume(JsonGetCommand): """Get volume command.""" - name = "getVolume" + NAME = "getVolume" @classmethod def _handle_body_data_dict( @@ -35,7 +35,7 @@ def _handle_body_data_dict( class SetVolume(JsonSetCommand): """Set volume command.""" - name = "setVolume" + NAME = "setVolume" get_command = GetVolume _mqtt_params = MappingProxyType( { diff --git a/deebot_client/commands/json/water_info.py b/deebot_client/commands/json/water_info.py index c84bc660..284c82de 100644 --- a/deebot_client/commands/json/water_info.py +++ b/deebot_client/commands/json/water_info.py @@ -19,7 +19,7 @@ class GetWaterInfo(JsonGetCommand): """Get water info command.""" - name = "getWaterInfo" + NAME = "getWaterInfo" @classmethod def _handle_body_data_dict( @@ -49,7 +49,7 @@ def _handle_body_data_dict( class SetWaterInfo(JsonSetCommand): """Set water info command.""" - name = "setWaterInfo" + NAME = "setWaterInfo" get_command = GetWaterInfo _mqtt_params = MappingProxyType( { diff --git a/deebot_client/commands/json/work_mode.py b/deebot_client/commands/json/work_mode.py index aff6a613..f4bf6fc0 100644 --- a/deebot_client/commands/json/work_mode.py +++ b/deebot_client/commands/json/work_mode.py @@ -19,7 +19,7 @@ class GetWorkMode(JsonGetCommand): """Get work mode command.""" - name = "getWorkMode" + NAME = "getWorkMode" @classmethod def _handle_body_data_dict( @@ -36,7 +36,7 @@ def _handle_body_data_dict( class SetWorkMode(JsonSetCommand): """Set work mode command.""" - name = "setWorkMode" + NAME = "setWorkMode" get_command = GetWorkMode _mqtt_params = MappingProxyType({"mode": InitParam(WorkMode)}) diff --git a/deebot_client/commands/xml/__init__.py b/deebot_client/commands/xml/__init__.py index d16c8c57..4f42ad79 100644 --- a/deebot_client/commands/xml/__init__.py +++ b/deebot_client/commands/xml/__init__.py @@ -30,10 +30,7 @@ ] # fmt: on -COMMANDS: dict[str, type[Command]] = { - cmd.name: cmd # type: ignore[misc] - for cmd in _COMMANDS -} +COMMANDS: dict[str, type[Command]] = {cmd.NAME: cmd for cmd in _COMMANDS} COMMANDS_WITH_MQTT_P2P_HANDLING: dict[str, type[CommandMqttP2P]] = { cmd_name: cmd diff --git a/deebot_client/commands/xml/charge_state.py b/deebot_client/commands/xml/charge_state.py index 1d88f876..78b7110a 100644 --- a/deebot_client/commands/xml/charge_state.py +++ b/deebot_client/commands/xml/charge_state.py @@ -19,7 +19,7 @@ class GetChargeState(XmlCommandWithMessageHandling): """GetChargeState command.""" - name = "GetChargeState" + NAME = "GetChargeState" @classmethod def _handle_xml(cls, event_bus: EventBus, xml: Element) -> HandlingResult: diff --git a/deebot_client/commands/xml/common.py b/deebot_client/commands/xml/common.py index d63e4044..bc04d3d9 100644 --- a/deebot_client/commands/xml/common.py +++ b/deebot_client/commands/xml/common.py @@ -19,23 +19,18 @@ _LOGGER = get_logger(__name__) -class XmlCommand(Command): +class XmlCommand(Command, ABC): """Xml 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 + DATA_TYPE = DataType.XML + HAS_SUB_ELEMENT = False def _get_payload(self) -> str: element = ctl_element = Element("ctl") if len(self._args) > 0: - if self.has_sub_element: - element = SubElement(element, self.name.lower()) + if self.HAS_SUB_ELEMENT: + element = SubElement(element, self.NAME.lower()) if isinstance(self._args, dict): for key, value in self._args.items(): diff --git a/deebot_client/commands/xml/error.py b/deebot_client/commands/xml/error.py index b72007a2..06b59850 100644 --- a/deebot_client/commands/xml/error.py +++ b/deebot_client/commands/xml/error.py @@ -20,7 +20,7 @@ class GetError(XmlCommandWithMessageHandling): """Get error command.""" - name = "GetError" + NAME = "GetError" @classmethod def _handle_xml(cls, event_bus: EventBus, xml: Element) -> HandlingResult: diff --git a/deebot_client/commands/xml/fan_speed.py b/deebot_client/commands/xml/fan_speed.py index d0c1f379..2a834554 100644 --- a/deebot_client/commands/xml/fan_speed.py +++ b/deebot_client/commands/xml/fan_speed.py @@ -18,7 +18,7 @@ class GetFanSpeed(XmlCommandWithMessageHandling): """GetFanSpeed command.""" - name = "GetCleanSpeed" + NAME = "GetCleanSpeed" @classmethod def _handle_xml(cls, event_bus: EventBus, xml: Element) -> HandlingResult: diff --git a/deebot_client/commands/xml/pos.py b/deebot_client/commands/xml/pos.py index 926cf827..4b4eaea1 100644 --- a/deebot_client/commands/xml/pos.py +++ b/deebot_client/commands/xml/pos.py @@ -18,7 +18,7 @@ class GetPos(XmlCommandWithMessageHandling): """GetPos command.""" - name = "GetPos" + NAME = "GetPos" @classmethod def _handle_xml(cls, event_bus: EventBus, xml: Element) -> HandlingResult: diff --git a/deebot_client/commands/xml/stats.py b/deebot_client/commands/xml/stats.py index 55bc4243..aeb615cd 100644 --- a/deebot_client/commands/xml/stats.py +++ b/deebot_client/commands/xml/stats.py @@ -18,7 +18,7 @@ class GetCleanSum(XmlCommandWithMessageHandling): """GetCleanSum command.""" - name = "GetCleanSum" + NAME = "GetCleanSum" @classmethod def _handle_xml(cls, event_bus: EventBus, xml: Element) -> HandlingResult: diff --git a/deebot_client/message.py b/deebot_client/message.py index 6aa35af6..e2a0bed7 100644 --- a/deebot_client/message.py +++ b/deebot_client/message.py @@ -8,6 +8,8 @@ import functools from typing import TYPE_CHECKING, Any, TypeVar, final +from deebot_client.util import verify_required_class_variables_exists + from .logging_filter import get_logger if TYPE_CHECKING: @@ -61,13 +63,13 @@ def wrapper( try: response = func(cls, event_bus, data) if response.state == HandlingState.ANALYSE: - _LOGGER.debug("Could not handle %s message: %s", cls.name, data) + _LOGGER.debug("Could not handle %s message: %s", cls.NAME, data) return HandlingResult(HandlingState.ANALYSE_LOGGED, response.args) if response.state == HandlingState.ERROR: - _LOGGER.warning("Could not parse %s: %s", cls.name, data) + _LOGGER.warning("Could not parse %s: %s", cls.NAME, data) return response except Exception: # pylint: disable=broad-except - _LOGGER.warning("Could not parse %s: %s", cls.name, data, exc_info=True) + _LOGGER.warning("Could not parse %s: %s", cls.NAME, data, exc_info=True) return HandlingResult(HandlingState.ERROR) return wrapper @@ -76,11 +78,11 @@ def wrapper( class Message(ABC): """Message.""" - @property # type: ignore[misc] - @classmethod - @abstractmethod - def name(cls) -> str: - """Command name.""" + NAME: str + + def __init_subclass__(cls) -> None: + verify_required_class_variables_exists(cls, ("NAME",)) + return super().__init_subclass__() @classmethod @abstractmethod @@ -105,7 +107,7 @@ def handle( return cls._handle(event_bus, message) -class MessageStr(Message): +class MessageStr(Message, ABC): """String message.""" @classmethod @@ -137,7 +139,7 @@ def _handle( return super()._handle(event_bus, message) -class MessageBody(Message): +class MessageBody(Message, ABC): """Dict message with body attribute.""" @classmethod @@ -168,7 +170,7 @@ def _handle( return super()._handle(event_bus, message) -class MessageBodyData(MessageBody): +class MessageBodyData(MessageBody, ABC): """Dict message with body->data attribute.""" @classmethod @@ -189,11 +191,11 @@ def __handle_body_data( try: response = cls._handle_body_data(event_bus, data) if response.state == HandlingState.ANALYSE: - _LOGGER.debug("Could not handle %s message: %s", cls.name, data) + _LOGGER.debug("Could not handle %s message: %s", cls.NAME, data) return HandlingResult(HandlingState.ANALYSE_LOGGED, response.args) return response except Exception: # pylint: disable=broad-except - _LOGGER.warning("Could not parse %s: %s", cls.name, data, exc_info=True) + _LOGGER.warning("Could not parse %s: %s", cls.NAME, data, exc_info=True) return HandlingResult(HandlingState.ERROR) @classmethod @@ -208,7 +210,7 @@ def _handle_body(cls, event_bus: EventBus, body: dict[str, Any]) -> HandlingResu return super()._handle_body(event_bus, body) -class MessageBodyDataDict(MessageBodyData): +class MessageBodyDataDict(MessageBodyData, ABC): """Dict message with body->data attribute as dict.""" @classmethod @@ -235,7 +237,7 @@ def _handle_body_data( return super()._handle_body_data(event_bus, data) -class MessageBodyDataList(MessageBodyData): +class MessageBodyDataList(MessageBodyData, ABC): """Dict message with body->data attribute as list.""" @classmethod diff --git a/deebot_client/messages/json/__init__.py b/deebot_client/messages/json/__init__.py index 8e8402de..c346e9ba 100644 --- a/deebot_client/messages/json/__init__.py +++ b/deebot_client/messages/json/__init__.py @@ -27,4 +27,4 @@ ] # fmt: on -MESSAGES: dict[str, type[Message]] = {message.name: message for message in _MESSAGES} # type: ignore[misc] +MESSAGES: dict[str, type[Message]] = {message.NAME: message for message in _MESSAGES} diff --git a/deebot_client/messages/json/battery.py b/deebot_client/messages/json/battery.py index 67c1e364..2cf84d43 100644 --- a/deebot_client/messages/json/battery.py +++ b/deebot_client/messages/json/battery.py @@ -14,7 +14,7 @@ class OnBattery(MessageBodyDataDict): """On battery message.""" - name = "onBattery" + NAME = "onBattery" @classmethod def _handle_body_data_dict( diff --git a/deebot_client/messages/json/map.py b/deebot_client/messages/json/map.py index edff8c6a..0d2c0a59 100644 --- a/deebot_client/messages/json/map.py +++ b/deebot_client/messages/json/map.py @@ -14,7 +14,7 @@ class OnMapSetV2(MessageBodyDataDict): """On map set v2 message.""" - name = "onMapSet_V2" + NAME = "onMapSet_V2" @classmethod def _handle_body_data_dict( diff --git a/deebot_client/messages/json/stats.py b/deebot_client/messages/json/stats.py index 0170a8e9..a0c5e982 100644 --- a/deebot_client/messages/json/stats.py +++ b/deebot_client/messages/json/stats.py @@ -14,7 +14,7 @@ class ReportStats(MessageBodyDataDict): """Report stats message.""" - name = "reportStats" + NAME = "reportStats" @classmethod def _handle_body_data_dict( diff --git a/deebot_client/util/__init__.py b/deebot_client/util/__init__.py index 3d8f2a5c..b2693604 100644 --- a/deebot_client/util/__init__.py +++ b/deebot_client/util/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations +from abc import ABC import asyncio import base64 from contextlib import suppress @@ -25,6 +26,17 @@ def md5(text: str) -> str: return hashlib.md5(bytes(str(text), "utf8")).hexdigest() # noqa: S324 +def verify_required_class_variables_exists( + cls: type[Any], required_variables: tuple[str, ...] +) -> None: + """Verify that the class has the given class variables.""" + if ABC not in cls.__bases__: + for required in required_variables: + if not hasattr(cls, required): + msg = f"Class {cls.__name__} must have a {required} attribute" + raise ValueError(msg) + + def decompress_7z_base64_data(data: str) -> bytes: """Decompress base64 decoded 7z compressed string.""" final_array = bytearray() diff --git a/tests/commands/json/__init__.py b/tests/commands/json/__init__.py index 6c5deeca..dace16e9 100644 --- a/tests/commands/json/__init__.py +++ b/tests/commands/json/__init__.py @@ -30,7 +30,7 @@ async def assert_execute_command( command: ExecuteCommand, args: dict[str, Any] | list[Any] | None ) -> None: - assert command.name != "invalid" + assert command.NAME != "invalid" assert command._args == args # success @@ -49,7 +49,7 @@ async def assert_execute_command( ( "deebot_client.commands.json.common", "WARNING", - f'Command "{command.name}" was not successfully. body={body}', + f'Command "{command.NAME}" was not successfully. body={body}', ) ) diff --git a/tests/commands/json/test_clean_log.py b/tests/commands/json/test_clean_log.py index 37759ca7..470cb026 100644 --- a/tests/commands/json/test_clean_log.py +++ b/tests/commands/json/test_clean_log.py @@ -150,5 +150,5 @@ async def test_GetCleanLogs_handle_error(caplog: pytest.LogCaptureFixture) -> No assert ( "deebot_client.command", logging.WARNING, - f"Could not parse response for {GetCleanLogs.name}: {{}}", + f"Could not parse response for {GetCleanLogs.NAME}: {{}}", ) in caplog.record_tuples diff --git a/tests/commands/json/test_common.py b/tests/commands/json/test_common.py index cd04e6a5..24b299e7 100644 --- a/tests/commands/json/test_common.py +++ b/tests/commands/json/test_common.py @@ -84,14 +84,14 @@ async def test_common_functionality( assert ( "deebot_client.command", logging.INFO, - f'No response received for command "{command.name}" during availability-check.', + f'No response received for command "{command.NAME}" during availability-check.', ) in caplog.record_tuples elif expected_log: assert ( "deebot_client.command", expected_log[0], - expected_log[1].format(command.name), + expected_log[1].format(command.NAME), ) in caplog.record_tuples assert available.device_reached is False diff --git a/tests/test_command.py b/tests/test_command.py index 7eb6bef1..daa75cfa 100644 --- a/tests/test_command.py +++ b/tests/test_command.py @@ -7,7 +7,7 @@ from aiohttp import ClientTimeout import pytest -from deebot_client.command import CommandMqttP2P, CommandResult, InitParam +from deebot_client.command import Command, CommandMqttP2P, CommandResult, InitParam from deebot_client.const import DataType from deebot_client.exceptions import ApiTimeoutError, DeebotError @@ -19,8 +19,8 @@ class _TestCommand(CommandMqttP2P): - name = "TestCommand" - data_type = DataType.JSON + NAME = "TestCommand" + DATA_TYPE = DataType.JSON _mqtt_params = MappingProxyType({"field": InitParam(int), "remove": None}) def __init__(self, field: int) -> None: @@ -42,12 +42,31 @@ def _handle_response( def test_CommandMqttP2P_no_mqtt_params() -> None: class TestCommandNoParams(CommandMqttP2P): - pass + NAME = "TestCommand" + DATA_TYPE = DataType.JSON with pytest.raises(DeebotError, match=r"_mqtt_params not set"): TestCommandNoParams.create_from_mqtt({}) +def test_Command_no_NAME() -> None: + with pytest.raises( + ValueError, match="Class TestCommand must have a NAME attribute" + ): + + class TestCommand(Command): + pass + + +def test_Command_no_DATA_TYPE() -> None: + with pytest.raises( + ValueError, match="Class TestCommand must have a DATA_TYPE attribute" + ): + + class TestCommand(Command): + NAME = "TestCommand" + + @pytest.mark.parametrize( ("data", "expected"), [ diff --git a/tests/test_mqtt_client.py b/tests/test_mqtt_client.py index f0307715..e4391315 100644 --- a/tests/test_mqtt_client.py +++ b/tests/test_mqtt_client.py @@ -114,7 +114,7 @@ async def test_p2p_success( assert len(mqtt_client._received_p2p_commands) == 0 command_object = Mock(spec=SetVolume) - command_name = SetVolume.name + command_name = SetVolume.NAME command_type = Mock(spec=SetVolume) create_from_mqtt = command_type.create_from_mqtt create_from_mqtt.return_value = command_object @@ -160,7 +160,7 @@ async def test_p2p_not_supported( ) -> None: """Test that unsupported command will be logged.""" await subscribe(mqtt_client, api_device_info) - command_name: str = GetBattery.name + command_name: str = GetBattery.NAME await _publish_p2p( command_name, api_device_info, {}, "req", test_mqtt_client, is_request=True @@ -214,7 +214,7 @@ async def test_p2p_to_late( assert len(mqtt_client._received_p2p_commands) == 0 command_object = Mock(spec=SetVolume) - command_name = SetVolume.name + command_name = SetVolume.NAME command_type = Mock(spec=SetVolume) create_from_mqtt = command_type.create_from_mqtt create_from_mqtt.return_value = command_object @@ -267,7 +267,7 @@ async def test_p2p_parse_error( await subscribe(mqtt_client, api_device_info) command_object = Mock(spec=SetVolume) - command_name = SetVolume.name + command_name = SetVolume.NAME command_type = Mock(spec=SetVolume, return_value=command_object) with patch.dict( "deebot_client.mqtt_client.COMMANDS_WITH_MQTT_P2P_HANDLING", From e55705383c07d8d6c492ed9bb4e670a2d28224a0 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Tue, 17 Dec 2024 01:02:21 +0100 Subject: [PATCH 08/16] Add codecov test analytics (#695) --- .github/workflows/ci.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e518d8d..a8ab88b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,10 +62,17 @@ jobs: run: uv sync --locked --dev - name: Run pytest - run: uv run --frozen pytest tests --cov=./ --cov-report=xml + run: uv run --frozen pytest tests --cov=./ --cov-report=xml --junitxml=junit.xml -o junit_family=legacy - name: Upload coverage to Codecov uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true + + - name: Upload test results to Codecov + if: ${{ !cancelled() }} + uses: codecov/test-results-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: true From 09cacfc5803b4c9b13b15f00aff1d571047121f2 Mon Sep 17 00:00:00 2001 From: flubshi Date: Tue, 17 Dec 2024 01:11:26 +0100 Subject: [PATCH 09/16] Add XML command "GetLifeSpan" (#575) * Add XML command "GetLifeSpan" * fix XML command "TypeError: 'classmethod' object is not callable" * Fix some type errors * Fix some type errors * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Review * fix --------- Co-authored-by: flubshi <4031504+flubshi@users.noreply.github.com> Co-authored-by: Robert Resch --- deebot_client/commands/xml/__init__.py | 3 ++ deebot_client/commands/xml/life_span.py | 49 ++++++++++++++++++++ deebot_client/events/__init__.py | 55 +++++++++++++++-------- tests/commands/xml/test_life_span.py | 59 +++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 18 deletions(-) create mode 100644 deebot_client/commands/xml/life_span.py create mode 100644 tests/commands/xml/test_life_span.py diff --git a/deebot_client/commands/xml/__init__.py b/deebot_client/commands/xml/__init__.py index 4f42ad79..03be9996 100644 --- a/deebot_client/commands/xml/__init__.py +++ b/deebot_client/commands/xml/__init__.py @@ -9,6 +9,7 @@ from .charge_state import GetChargeState from .error import GetError from .fan_speed import GetFanSpeed +from .life_span import GetLifeSpan from .pos import GetPos from .stats import GetCleanSum @@ -20,6 +21,7 @@ "GetCleanSum", "GetError", "GetFanSpeed", + "GetLifeSpan", "GetPos", ] @@ -27,6 +29,7 @@ # ordered by file asc _COMMANDS: list[type[XmlCommand]] = [ GetError, + GetLifeSpan, ] # fmt: on diff --git a/deebot_client/commands/xml/life_span.py b/deebot_client/commands/xml/life_span.py new file mode 100644 index 00000000..52cf0bf7 --- /dev/null +++ b/deebot_client/commands/xml/life_span.py @@ -0,0 +1,49 @@ +"""Life span commands.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from deebot_client.events import LifeSpan, LifeSpanEvent +from deebot_client.message import HandlingResult + +from .common import XmlCommandWithMessageHandling + +if TYPE_CHECKING: + from xml.etree.ElementTree import Element + + from deebot_client.event_bus import EventBus + + +class GetLifeSpan(XmlCommandWithMessageHandling): + """GetLifeSpan command.""" + + NAME = "GetLifeSpan" + + def __init__(self, life_span: LifeSpan) -> None: + super().__init__({"type": life_span.xml_value}) + + @classmethod + def _handle_xml(cls, event_bus: EventBus, xml: Element) -> HandlingResult: + """Handle xml message and notify the correct event subscribers. + + :return: A message response + """ + if ( + xml.attrib.get("ret") != "ok" + or (component_type := xml.attrib.get("type")) is None + or (left_str := xml.attrib.get("left")) is None + or (total_str := xml.attrib.get("total")) is None + ): + return HandlingResult.analyse() + + percent = 0.0 + left = int(left_str) + total = int(total_str) + if total > 0: + percent = round((left / total) * 100, 2) + + event_bus.notify( + LifeSpanEvent(LifeSpan.from_xml(component_type), percent, left) + ) + return HandlingResult.success() diff --git a/deebot_client/events/__init__.py b/deebot_client/events/__init__.py index 12a03b95..b223b734 100644 --- a/deebot_client/events/__init__.py +++ b/deebot_client/events/__init__.py @@ -3,8 +3,8 @@ from __future__ import annotations from dataclasses import dataclass -from enum import Enum, IntEnum, unique -from typing import TYPE_CHECKING, Any +from enum import IntEnum, StrEnum, unique +from typing import TYPE_CHECKING, Any, Self from deebot_client.events.base import Event @@ -122,24 +122,43 @@ class ErrorEvent(Event): @unique -class LifeSpan(str, Enum): +class LifeSpan(StrEnum): """Enum class for all possible life span components.""" - BRUSH = "brush" - FILTER = "heap" - SIDE_BRUSH = "sideBrush" - UNIT_CARE = "unitCare" - ROUND_MOP = "roundMop" - AIR_FRESHENER = "dModule" - UV_SANITIZER = "uv" - HUMIDIFY = "humidify" - HUMIDIFY_MAINTENANCE = "wbCare" - BLADE = "blade" - LENS_BRUSH = "lensBrush" - DUST_BAG = "dustBag" - CLEANING_FLUID = "autoWater_cleaningFluid" - STRAINER = "strainer" - HAND_FILTER = "handFilter" + xml_value: str + + def __new__(cls, value: str, xml_value: str = "") -> Self: + obj = str.__new__(cls) + obj._value_ = value + obj.xml_value = xml_value + return obj + + @classmethod + def from_xml(cls, value: str) -> LifeSpan: + """Get LifeSpan from xml value.""" + for life_span in LifeSpan: + if life_span.xml_value == value: + return life_span + + msg = f"{value} is not a valid {cls.__name__}" + raise ValueError(msg) + + BRUSH = "brush", "Brush" + FILTER = "heap", "Heap" + SIDE_BRUSH = "sideBrush", "SideBrush" + UNIT_CARE = "unitCare", "UnitCare" + ROUND_MOP = "roundMop", "RoundMop" + AIR_FRESHENER = "dModule", "DModule" + UV_SANITIZER = "uv", "Uv" + HUMIDIFY = "humidify", "Humidify" + HUMIDIFY_MAINTENANCE = "wbCare", "WbCare" + BLADE = "blade", "Blade" + LENS_BRUSH = "lensBrush", "LensBrush" + DUST_BAG = "dustBag", "DustBag" + CLEANING_FLUID = "autoWater_cleaningFluid", "AutoWater_cleaningFluid" + STRAINER = "strainer", "Strainer" + HAND_FILTER = "handFilter", "HandFilter" + DUST_CASE_HEAP = "dustCaseHeap", "DustCaseHeap" @dataclass(frozen=True) diff --git a/tests/commands/xml/test_life_span.py b/tests/commands/xml/test_life_span.py new file mode 100644 index 00000000..fb039729 --- /dev/null +++ b/tests/commands/xml/test_life_span.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +import pytest + +from deebot_client.command import CommandResult +from deebot_client.commands.xml import GetLifeSpan +from deebot_client.events import LifeSpan, LifeSpanEvent +from deebot_client.message import HandlingState +from tests.commands import assert_command + +from . import get_request_xml + + +@pytest.mark.parametrize( + ("component_type", "lifespan_type", "left", "total", "expected_event"), + [ + ("Brush", LifeSpan.BRUSH, 50, 100, LifeSpanEvent(LifeSpan.BRUSH, 50, 50)), + ( + "DustCaseHeap", + LifeSpan.DUST_CASE_HEAP, + 50, + 200, + LifeSpanEvent(LifeSpan.DUST_CASE_HEAP, 25, 50), + ), + ( + "SideBrush", + LifeSpan.SIDE_BRUSH, + 25, + 200, + LifeSpanEvent(LifeSpan.SIDE_BRUSH, 12.5, 25), + ), + ], +) +async def test_get_life_span( + component_type: str, + lifespan_type: LifeSpan, + left: int, + total: int, + expected_event: LifeSpanEvent, +) -> None: + json = get_request_xml( + f"" + ) + await assert_command(GetLifeSpan(lifespan_type), json, expected_event) + + +@pytest.mark.parametrize( + "xml", + ["", ""], + ids=["error", "no_state"], +) +async def test_get_life_span_error(xml: str) -> None: + json = get_request_xml(xml) + await assert_command( + GetLifeSpan(LifeSpan.BRUSH), + json, + None, + command_result=CommandResult(HandlingState.ANALYSE_LOGGED), + ) From 6ce175c072eb61a010f1a5c04d7f618b031337b1 Mon Sep 17 00:00:00 2001 From: flammable-gel <73902579+flammable-gel@users.noreply.github.com> Date: Fri, 20 Dec 2024 10:15:28 +0100 Subject: [PATCH 10/16] Add support for DEEBOT X1e OMNI (bro5wu) (#703) --- deebot_client/hardware/deebot/bro5wu.py | 1 + 1 file changed, 1 insertion(+) create mode 120000 deebot_client/hardware/deebot/bro5wu.py diff --git a/deebot_client/hardware/deebot/bro5wu.py b/deebot_client/hardware/deebot/bro5wu.py new file mode 120000 index 00000000..391aa568 --- /dev/null +++ b/deebot_client/hardware/deebot/bro5wu.py @@ -0,0 +1 @@ +2o4lnm.py \ No newline at end of file From 7ebc150108911b2b1cfc04c8df6698b4a89af8c0 Mon Sep 17 00:00:00 2001 From: wenzet Date: Fri, 20 Dec 2024 10:15:56 +0100 Subject: [PATCH 11/16] Add DEEBOT N8 Pro Care (s1f8g7) (#689) * Add DEEBOT N8 Pro Care (s1f8g7) * Fix symlink --------- Co-authored-by: Robert Resch --- deebot_client/hardware/deebot/s1f8g7.py | 1 + 1 file changed, 1 insertion(+) create mode 120000 deebot_client/hardware/deebot/s1f8g7.py diff --git a/deebot_client/hardware/deebot/s1f8g7.py b/deebot_client/hardware/deebot/s1f8g7.py new file mode 120000 index 00000000..ade0eb9b --- /dev/null +++ b/deebot_client/hardware/deebot/s1f8g7.py @@ -0,0 +1 @@ +x5d34r.py \ No newline at end of file From 479385e7ecef05c0ac5e4dc6c49eccabbf512f37 Mon Sep 17 00:00:00 2001 From: Khrusaor33 Date: Fri, 20 Dec 2024 10:16:17 +0100 Subject: [PATCH 12/16] DEEBOT X2 COMBO (#699) * DEEBOT X2 COMBO * Fix symbolic link --------- Co-authored-by: Robert Resch --- deebot_client/hardware/deebot/e6rcnf.py | 1 + 1 file changed, 1 insertion(+) create mode 120000 deebot_client/hardware/deebot/e6rcnf.py diff --git a/deebot_client/hardware/deebot/e6rcnf.py b/deebot_client/hardware/deebot/e6rcnf.py new file mode 120000 index 00000000..e2baadee --- /dev/null +++ b/deebot_client/hardware/deebot/e6rcnf.py @@ -0,0 +1 @@ +e6ofmn.py \ No newline at end of file From 02385491b2822ef56b87f575904a97f7b259edf6 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Fri, 20 Dec 2024 09:19:08 +0000 Subject: [PATCH 13/16] Add e6yxdm by similarity to T20 --- deebot_client/hardware/deebot/e6yxdm.py | 1 + 1 file changed, 1 insertion(+) create mode 120000 deebot_client/hardware/deebot/e6yxdm.py diff --git a/deebot_client/hardware/deebot/e6yxdm.py b/deebot_client/hardware/deebot/e6yxdm.py new file mode 120000 index 00000000..576a700d --- /dev/null +++ b/deebot_client/hardware/deebot/e6yxdm.py @@ -0,0 +1 @@ +4jd37g.py \ No newline at end of file From 95a1590d5241ee380334ee3b08340a3377ec53d1 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Fri, 20 Dec 2024 13:48:19 +0100 Subject: [PATCH 14/16] Fix LifeSpan enum (#705) --- deebot_client/events/__init__.py | 2 +- tests/events/test_events.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 tests/events/test_events.py diff --git a/deebot_client/events/__init__.py b/deebot_client/events/__init__.py index b223b734..7f710910 100644 --- a/deebot_client/events/__init__.py +++ b/deebot_client/events/__init__.py @@ -128,7 +128,7 @@ class LifeSpan(StrEnum): xml_value: str def __new__(cls, value: str, xml_value: str = "") -> Self: - obj = str.__new__(cls) + obj = str.__new__(cls, value) obj._value_ = value obj.xml_value = xml_value return obj diff --git a/tests/events/test_events.py b/tests/events/test_events.py new file mode 100644 index 00000000..6e7a43a3 --- /dev/null +++ b/tests/events/test_events.py @@ -0,0 +1,11 @@ +"""Test events.""" + +from __future__ import annotations + +from deebot_client.events import LifeSpan + + +def test_life_span() -> None: + """Test life span events.""" + assert LifeSpan.BRUSH != LifeSpan.FILTER + assert LifeSpan.FILTER not in {LifeSpan.BLADE, LifeSpan.BRUSH, LifeSpan.SIDE_BRUSH} From 3b03d2ce1e4dc9e0c0fe45445a3d17d28544db2b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 19:49:27 +0100 Subject: [PATCH 15/16] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Update=20dependency?= =?UTF-8?q?=20mypy=20to=20v1.14.0=20(#706)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- uv.lock | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8dbb99b4..1c69d1e0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ version = "0.0.0" [tool.uv] dev-dependencies = [ - "mypy==1.13.0", + "mypy==1.14.0", "pre-commit==4.0.1", "pycountry==24.6.1", "pylint==3.3.2", diff --git a/uv.lock b/uv.lock index 30379fc1..35c12196 100644 --- a/uv.lock +++ b/uv.lock @@ -256,7 +256,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ - { name = "mypy", specifier = "==1.13.0" }, + { name = "mypy", specifier = "==1.14.0" }, { name = "pre-commit", specifier = "==4.0.1" }, { name = "pycountry", specifier = "==24.6.1" }, { name = "pylint", specifier = "==3.3.2" }, @@ -446,25 +446,25 @@ wheels = [ [[package]] name = "mypy" -version = "1.13.0" +version = "1.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mypy-extensions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e8/21/7e9e523537991d145ab8a0a2fd98548d67646dc2aaaf6091c31ad883e7c1/mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e", size = 3152532 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/31/c526a7bd2e5c710ae47717c7a5f53f616db6d9097caf48ad650581e81748/mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5", size = 11077900 }, - { url = "https://files.pythonhosted.org/packages/83/67/b7419c6b503679d10bd26fc67529bc6a1f7a5f220bbb9f292dc10d33352f/mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e", size = 10074818 }, - { url = "https://files.pythonhosted.org/packages/ba/07/37d67048786ae84e6612575e173d713c9a05d0ae495dde1e68d972207d98/mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2", size = 12589275 }, - { url = "https://files.pythonhosted.org/packages/1f/17/b1018c6bb3e9f1ce3956722b3bf91bff86c1cefccca71cec05eae49d6d41/mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0", size = 13037783 }, - { url = "https://files.pythonhosted.org/packages/cb/32/cd540755579e54a88099aee0287086d996f5a24281a673f78a0e14dba150/mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2", size = 9726197 }, - { url = "https://files.pythonhosted.org/packages/11/bb/ab4cfdc562cad80418f077d8be9b4491ee4fb257440da951b85cbb0a639e/mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7", size = 11069721 }, - { url = "https://files.pythonhosted.org/packages/59/3b/a393b1607cb749ea2c621def5ba8c58308ff05e30d9dbdc7c15028bca111/mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62", size = 10063996 }, - { url = "https://files.pythonhosted.org/packages/d1/1f/6b76be289a5a521bb1caedc1f08e76ff17ab59061007f201a8a18cc514d1/mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8", size = 12584043 }, - { url = "https://files.pythonhosted.org/packages/a6/83/5a85c9a5976c6f96e3a5a7591aa28b4a6ca3a07e9e5ba0cec090c8b596d6/mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7", size = 13036996 }, - { url = "https://files.pythonhosted.org/packages/b4/59/c39a6f752f1f893fccbcf1bdd2aca67c79c842402b5283563d006a67cf76/mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc", size = 9737709 }, - { url = "https://files.pythonhosted.org/packages/3b/86/72ce7f57431d87a7ff17d442f521146a6585019eb8f4f31b7c02801f78ad/mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a", size = 2647043 }, +sdist = { url = "https://files.pythonhosted.org/packages/8c/7b/08046ef9330735f536a09a2e31b00f42bccdb2795dcd979636ba43bb2d63/mypy-1.14.0.tar.gz", hash = "sha256:822dbd184d4a9804df5a7d5335a68cf7662930e70b8c1bc976645d1509f9a9d6", size = 3215684 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/d8/0e72175ee0253217f5c44524f5e95251c02e95ba9749fb87b0e2074d203a/mypy-1.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d5326ab70a6db8e856d59ad4cb72741124950cbbf32e7b70e30166ba7bbf61dd", size = 11269011 }, + { url = "https://files.pythonhosted.org/packages/e9/6d/4ea13839dabe5db588dc6a1b766da16f420d33cf118a7b7172cdf6c7fcb2/mypy-1.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bf4ec4980bec1e0e24e5075f449d014011527ae0055884c7e3abc6a99cd2c7f1", size = 10253076 }, + { url = "https://files.pythonhosted.org/packages/3e/38/7db2c5d0f4d290e998f7a52b2e2616c7bbad96b8e04278ab09d11978a29e/mypy-1.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:390dfb898239c25289495500f12fa73aa7f24a4c6d90ccdc165762462b998d63", size = 12862786 }, + { url = "https://files.pythonhosted.org/packages/bf/4b/62d59c801b34141040989949c2b5c157d0408b45357335d3ec5b2845b0f6/mypy-1.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7e026d55ddcd76e29e87865c08cbe2d0104e2b3153a523c529de584759379d3d", size = 12971568 }, + { url = "https://files.pythonhosted.org/packages/f1/9c/e0f281b32d70c87b9e4d2939e302b1ff77ada4d7b0f2fb32890c144bc1d6/mypy-1.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:585ed36031d0b3ee362e5107ef449a8b5dfd4e9c90ccbe36414ee405ee6b32ba", size = 9879477 }, + { url = "https://files.pythonhosted.org/packages/13/33/8380efd0ebdfdfac7fc0bf065f03a049800ca1e6c296ec1afc634340d992/mypy-1.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9f6f4c0b27401d14c483c622bc5105eff3911634d576bbdf6695b9a7c1ba741", size = 11251509 }, + { url = "https://files.pythonhosted.org/packages/15/6d/4e1c21c60fee11af7d8e4f2902a29886d1387d6a836be16229eb3982a963/mypy-1.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b2280cedcb312c7a79f5001ae5325582d0d339bce684e4a529069d0e7ca1e7", size = 10244282 }, + { url = "https://files.pythonhosted.org/packages/8b/cf/7a8ae5c0161edae15d25c2c67c68ce8b150cbdc45aefc13a8be271ee80b2/mypy-1.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:342de51c48bab326bfc77ce056ba08c076d82ce4f5a86621f972ed39970f94d8", size = 12867676 }, + { url = "https://files.pythonhosted.org/packages/9c/d0/71f7bbdcc7cfd0f2892db5b13b1e8857673f2cc9e0c30e3e4340523dc186/mypy-1.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:00df23b42e533e02a6f0055e54de9a6ed491cd8b7ea738647364fd3a39ea7efc", size = 12964189 }, + { url = "https://files.pythonhosted.org/packages/a7/40/fb4ad65d6d5f8c51396ecf6305ec0269b66013a5bf02d0e9528053640b4a/mypy-1.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:e8c8387e5d9dff80e7daf961df357c80e694e942d9755f3ad77d69b0957b8e3f", size = 9888247 }, + { url = "https://files.pythonhosted.org/packages/39/32/0214608af400cdf8f5102144bb8af10d880675c65ed0b58f7e0e77175d50/mypy-1.14.0-py3-none-any.whl", hash = "sha256:2238d7f93fc4027ed1efc944507683df3ba406445a2b6c96e79666a045aadfab", size = 2752803 }, ] [[package]] From 3ed2a376eb89cc55a209a257e5c1a69da209e863 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 19:49:45 +0100 Subject: [PATCH 16/16] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Update=20astral-sh/s?= =?UTF-8?q?etup-uv=20action=20to=20v5=20(#704)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 4 ++-- .github/workflows/python-publish.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8ab88b2..0af8e8ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: uses: actions/checkout@v4 - name: ๐Ÿ— Install uv and Python - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v5 with: enable-cache: true cache-dependency-glob: "uv.lock" @@ -51,7 +51,7 @@ jobs: uses: actions/checkout@v4 - name: ๐Ÿ— Install uv and Python ${{ matrix.python-version }} - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v5 with: enable-cache: true cache-dependency-glob: "uv.lock" diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index ec494757..612f2748 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -26,7 +26,7 @@ jobs: - name: โคต๏ธ Check out code from GitHub uses: actions/checkout@v4.2.2 - name: ๐Ÿ— Set up uv - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v5 with: enable-cache: true - name: ๐Ÿ— Set package version