-
Notifications
You must be signed in to change notification settings - Fork 75
feat: add better cleaning mode support #817
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,9 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import typing | ||
| from enum import StrEnum | ||
|
|
||
| from ...exceptions import RoborockUnsupportedFeature | ||
| from ..code_mappings import RoborockModeEnum | ||
|
|
||
| if typing.TYPE_CHECKING: | ||
|
|
@@ -68,6 +70,14 @@ class WashTowelModes(RoborockModeEnum): | |
| SUPER_DEEP = ("super_deep", 8) | ||
|
|
||
|
|
||
| class CleaningModes(StrEnum): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some pydoc here describing how this differs from the other lower level modes would be useful. Basically never use those and always use this? |
||
| VACUUM = "vacuum" | ||
| VAC_AND_MOP = "vac_and_mop" | ||
| MOP = "mop" | ||
| CUSTOM = "custom" | ||
| SMART_MODE = "smart_mode" | ||
|
|
||
|
|
||
| WATER_SLIDE_MODE_MAPPING: dict[int, WaterModes] = { | ||
| 200: WaterModes.OFF, | ||
| 221: WaterModes.PURE_WATER_FLOW_START, | ||
|
|
@@ -174,7 +184,105 @@ def get_water_mode_mapping(features: DeviceFeatures) -> dict[int, str]: | |
| return {mode.code: mode.value for mode in get_water_modes(features)} | ||
|
|
||
|
|
||
| def is_mode_customized(clean_mode: VacuumModes, water_mode: WaterModes, mop_mode: CleanRoutes) -> bool: | ||
| def get_cleaning_mode_options(features: DeviceFeatures) -> list[CleaningModes]: | ||
| """Get the supported high-level cleaning modes for the device.""" | ||
| if not features.is_support_water_mode: | ||
| return [] | ||
|
|
||
| options = [CleaningModes.VACUUM, CleaningModes.VAC_AND_MOP] | ||
| if features.is_pure_clean_mop_supported: | ||
| options.append(CleaningModes.MOP) | ||
| if features.is_customized_clean_supported: | ||
| options.append(CleaningModes.CUSTOM) | ||
| if features.is_smart_clean_mode_set_supported: | ||
| options.append(CleaningModes.SMART_MODE) | ||
| return options | ||
|
|
||
|
|
||
| def get_mop_only_vacuum_mode(features: DeviceFeatures) -> VacuumModes: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is getting the vaccuum mode when the vacuum is in mop only mode? |
||
| if not features.is_pure_clean_mop_supported: | ||
| raise RoborockUnsupportedFeature("Mop-only cleaning is not supported") | ||
| if features.is_support_main_brush_up_down_supported: | ||
| return VacuumModes.OFF_RAISE_MAIN_BRUSH | ||
| return VacuumModes.OFF | ||
|
|
||
|
|
||
| def _get_default_mopping_water_code(features: DeviceFeatures) -> int: | ||
| """Pick a sensible default water code when mopping for the device.""" | ||
| # Water-slide devices use a disjoint set of water codes; pick a mid-flow | ||
| # slide code instead of the standard 202, which they don't accept. | ||
| if features.is_water_slide_mode_supported: | ||
| return WaterModes.PURE_WATER_FLOW_MIDDLE.code | ||
| return WaterModes.STANDARD.code | ||
|
|
||
|
|
||
| def _get_clean_motor_mode_params(mode: CleaningModes, features: DeviceFeatures) -> tuple[int, int, int]: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this should be using the enum types instead of the code everywhere in this file, then only convert to the code when added to the params dict |
||
| """Return (fan_power, water_box_mode, mop_mode) codes for the high-level mode.""" | ||
| if mode == CleaningModes.VACUUM: | ||
| return (VacuumModes.BALANCED.code, WaterModes.OFF.code, CleanRoutes.STANDARD.code) | ||
| if mode == CleaningModes.VAC_AND_MOP: | ||
| return (VacuumModes.BALANCED.code, _get_default_mopping_water_code(features), CleanRoutes.STANDARD.code) | ||
| if mode == CleaningModes.MOP: | ||
| return ( | ||
| get_mop_only_vacuum_mode(features).code, | ||
| _get_default_mopping_water_code(features), | ||
| CleanRoutes.STANDARD.code, | ||
| ) | ||
| if mode == CleaningModes.CUSTOM: | ||
| return (VacuumModes.CUSTOMIZED.code, WaterModes.CUSTOMIZED.code, CleanRoutes.CUSTOMIZED.code) | ||
| if mode == CleaningModes.SMART_MODE: | ||
| return (VacuumModes.SMART_MODE.code, WaterModes.SMART_MODE.code, CleanRoutes.SMART_MODE.code) | ||
| raise RoborockUnsupportedFeature(f"Cleaning mode {mode.value!r} is not supported") | ||
|
|
||
|
|
||
| def get_cleaning_mode_parameters(cleaning_mode: str | CleaningModes, features: DeviceFeatures) -> list[dict[str, int]]: | ||
| """Get the RPC payload for switching the high-level cleaning mode.""" | ||
| try: | ||
| mode = CleaningModes(cleaning_mode) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i'd say do the string to enum conversation as high up as possible and just take the enum here |
||
| except ValueError as err: | ||
| raise RoborockUnsupportedFeature(f"Cleaning mode {cleaning_mode!r} is not supported") from err | ||
| if mode not in get_cleaning_mode_options(features): | ||
| raise RoborockUnsupportedFeature(f"Cleaning mode {mode.value!r} is not supported") | ||
|
|
||
| fan_power, water_box_mode, mop_mode = _get_clean_motor_mode_params(mode, features) | ||
| params: dict[str, int] = {"fan_power": fan_power, "water_box_mode": water_box_mode} | ||
| if features.is_clean_route_setting_supported: | ||
| params["mop_mode"] = mop_mode | ||
| return [params] | ||
|
|
||
|
|
||
| def get_current_cleaning_mode( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One thought is that this could be on I think the only advantage could be the names would be shorter, but otherwise its not to different than what is here and can just be a style choice (optional) |
||
| clean_mode: int | None, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this also take the enum values instead of the codes? |
||
| water_mode: int | None, | ||
| mop_mode: int | None, | ||
| features: DeviceFeatures, | ||
| ) -> CleaningModes | None: | ||
| """Classify the current high-level cleaning mode from individual mode codes.""" | ||
| if not features.is_support_water_mode: | ||
| return None | ||
| if clean_mode is None or water_mode is None: | ||
| return None | ||
|
|
||
| if is_smart_mode_set(water_mode, clean_mode, mop_mode): | ||
| return CleaningModes.SMART_MODE | ||
| if is_mode_customized(clean_mode, water_mode, mop_mode): | ||
| return CleaningModes.CUSTOM | ||
| if water_mode != WaterModes.OFF.code: | ||
| try: | ||
| if clean_mode == get_mop_only_vacuum_mode(features).code: | ||
| return CleaningModes.MOP | ||
| except RoborockUnsupportedFeature: | ||
| pass | ||
| if water_mode == WaterModes.OFF.code: | ||
| return CleaningModes.VACUUM | ||
| return CleaningModes.VAC_AND_MOP | ||
|
|
||
|
|
||
| def is_mode_customized( | ||
| clean_mode: int | VacuumModes | None, | ||
| water_mode: int | WaterModes | None, | ||
| mop_mode: int | CleanRoutes | None, | ||
| ) -> bool: | ||
| """Check if any of the cleaning modes are set to a custom value.""" | ||
| return ( | ||
| clean_mode == VacuumModes.CUSTOMIZED | ||
|
|
@@ -183,7 +291,11 @@ def is_mode_customized(clean_mode: VacuumModes, water_mode: WaterModes, mop_mode | |
| ) | ||
|
Lash-L marked this conversation as resolved.
|
||
|
|
||
|
|
||
| def is_smart_mode_set(water_mode: WaterModes, clean_mode: VacuumModes, mop_mode: CleanRoutes) -> bool: | ||
| def is_smart_mode_set( | ||
| water_mode: int | WaterModes | None, | ||
| clean_mode: int | VacuumModes | None, | ||
| mop_mode: int | CleanRoutes | None, | ||
| ) -> bool: | ||
| """Check if the smart mode is set for the given water mode and clean mode""" | ||
| return ( | ||
| water_mode == WaterModes.SMART_MODE | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,16 @@ | ||
| from functools import cached_property | ||
|
|
||
| from roborock import ( | ||
| CleaningModes, | ||
| CleanRoutes, | ||
| StatusV2, | ||
| VacuumModes, | ||
| WaterModes, | ||
| get_clean_modes, | ||
| get_clean_routes, | ||
| get_cleaning_mode_options, | ||
| get_cleaning_mode_parameters, | ||
| get_current_cleaning_mode, | ||
| get_water_mode_mapping, | ||
| get_water_modes, | ||
| ) | ||
|
|
@@ -34,10 +38,11 @@ class StatusTrait(StatusV2, common.V1TraitMixin): | |
| - Water Mode | ||
| - Mop Route | ||
|
|
||
| You should call the _options() version of the attribute to know which are supported for your device | ||
| (i.e. fan_speed_options()) | ||
| Then you can call the _mapping to convert an int value to the actual Enum. (i.e. fan_speed_mapping()) | ||
| You can call the _name property to get the str value of the enum. (i.e. fan_speed_name) | ||
| You should use the _options version of the attribute to know which are | ||
| supported for your device (i.e. fan_speed_options) | ||
| Then you can use the _mapping to convert an int value to the actual Enum. | ||
| (i.e. fan_speed_mapping) | ||
| You can use the _name property to get the str value of the enum. (i.e. fan_speed_name) | ||
|
|
||
| """ | ||
|
|
||
|
|
@@ -74,6 +79,10 @@ def mop_route_options(self) -> list[CleanRoutes]: | |
| def mop_route_mapping(self) -> dict[int, str]: | ||
| return {route.code: route.value for route in self.mop_route_options} | ||
|
|
||
| @cached_property | ||
| def cleaning_mode_options(self) -> list[CleaningModes]: | ||
| return get_cleaning_mode_options(self._device_features_trait) | ||
|
|
||
| @property | ||
| def fan_speed_name(self) -> str | None: | ||
| if self.fan_power is None: | ||
|
|
@@ -91,3 +100,29 @@ def mop_route_name(self) -> str | None: | |
| if self.mop_mode is None: | ||
| return None | ||
| return self.mop_route_mapping.get(self.mop_mode) | ||
|
|
||
| @property | ||
| def current_cleaning_mode(self) -> CleaningModes | None: | ||
| return get_current_cleaning_mode( | ||
| clean_mode=self.fan_power, | ||
| water_mode=self.water_box_mode, | ||
| mop_mode=self.mop_mode, | ||
| features=self._device_features_trait, | ||
| ) | ||
|
|
||
| @property | ||
| def cleaning_mode_name(self) -> str | None: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be |
||
| if (cleaning_mode := self.current_cleaning_mode) is None: | ||
| return None | ||
| return cleaning_mode.value | ||
|
|
||
| def get_cleaning_mode_parameters(self, cleaning_mode: str | CleaningModes) -> list[dict[str, int]]: | ||
| """Get the RPC payload for the selected high-level cleaning mode.""" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was going to say this can be private, but really I think it can just be inlined. in |
||
| return get_cleaning_mode_parameters(cleaning_mode, self._device_features_trait) | ||
|
|
||
| async def set_cleaning_mode(self, cleaning_mode: str | CleaningModes) -> None: | ||
| """Set the high-level cleaning mode.""" | ||
| await self.rpc_channel.send_command( | ||
| RoborockCommand.SET_CLEAN_MOTOR_MODE, | ||
| params=self.get_cleaning_mode_parameters(cleaning_mode), | ||
| ) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CleaningMode?