-
Notifications
You must be signed in to change notification settings - Fork 65
feat: make status dynamic #611
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
Changes from all commits
ae7450e
647073f
0ed4902
394de87
168123e
aab737c
080b4ae
910ff18
69671bb
9ae1e66
e2d829d
2785052
8b87f60
8652ed7
270755b
e0dfd72
c2587fd
131b9c8
5879989
05443d5
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 | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,7 +3,7 @@ | |||||||||||||||||||
| import logging | ||||||||||||||||||||
| from collections import namedtuple | ||||||||||||||||||||
| from enum import Enum, IntEnum, StrEnum | ||||||||||||||||||||
| from typing import Self | ||||||||||||||||||||
| from typing import Any, Self | ||||||||||||||||||||
|
|
||||||||||||||||||||
| _LOGGER = logging.getLogger(__name__) | ||||||||||||||||||||
| completed_warnings = set() | ||||||||||||||||||||
|
|
@@ -105,6 +105,21 @@ def keys(cls) -> list[str]: | |||||||||||||||||||
| """Returns a list of all member values.""" | ||||||||||||||||||||
| return [member.value for member in cls] | ||||||||||||||||||||
|
|
||||||||||||||||||||
| def __eq__(self, other: Any) -> bool: | ||||||||||||||||||||
| if isinstance(other, str): | ||||||||||||||||||||
| return self.value == other or self.name == other | ||||||||||||||||||||
| if isinstance(other, int): | ||||||||||||||||||||
| return self.code == other | ||||||||||||||||||||
| return super().__eq__(other) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| def __hash__(self) -> int: | ||||||||||||||||||||
| """Hash a RoborockModeEnum. | ||||||||||||||||||||
| It is critical that you do not mix RoborockModeEnums with raw strings or ints in hashed situations | ||||||||||||||||||||
| (i.e. sets or keys in dictionaries) | ||||||||||||||||||||
| """ | ||||||||||||||||||||
| return hash((self.code, self._value_)) | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
|
Comment on lines
108
to
123
|
||||||||||||||||||||
| def __eq__(self, other: Any) -> bool: | |
| if isinstance(other, str): | |
| return self.value == other or self.name == other | |
| if isinstance(other, int): | |
| return self.code == other | |
| return super().__eq__(other) | |
| def __hash__(self) -> int: | |
| return hash((self.code, self._value_)) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,24 +1,92 @@ | ||
| from functools import cached_property | ||
| from typing import Self | ||
|
|
||
| from roborock.data import HomeDataProduct, ModelStatus, S7MaxVStatus, Status | ||
| from roborock.devices.traits.v1 import common | ||
| from roborock import CleanRoutes, StatusV2, VacuumModes, WaterModes, get_clean_modes, get_clean_routes, get_water_modes | ||
| from roborock.roborock_typing import RoborockCommand | ||
|
|
||
| from . import common | ||
| from .device_features import DeviceFeaturesTrait | ||
|
|
||
| class StatusTrait(Status, common.V1TraitMixin): | ||
| """Trait for managing the status of Roborock devices.""" | ||
|
|
||
| class StatusTrait(StatusV2, common.V1TraitMixin): | ||
| """Trait for managing the status of Roborock devices. | ||
| The StatusTrait gives you the access to the state of a Roborock vacuum. | ||
| The various attribute options on state change per each device. | ||
| Values like fan speed, mop mode, etc. have different options for every device | ||
| and are dynamically determined. | ||
| Usage: | ||
|
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. is there anything callers need to do about knowing what features are supported or not? Maybe i'm mixing up what this will do.
Collaborator
Author
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. How are those extra docs? |
||
| Before accessing status properties, you should call `refresh()` to fetch | ||
| the latest data from the device. You must pass in the device feature trait | ||
| to this trait so that the dynamic attributes can be pre-determined. | ||
| The current dynamic attributes are: | ||
| - Fan Speed | ||
| - 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) | ||
| """ | ||
|
|
||
| command = RoborockCommand.GET_STATUS | ||
|
|
||
| def __init__(self, product_info: HomeDataProduct) -> None: | ||
| def __init__(self, device_feature_trait: DeviceFeaturesTrait, region: str | None = None) -> None: | ||
| """Initialize the StatusTrait.""" | ||
| self._product_info = product_info | ||
| super().__init__() | ||
| self._device_features_trait = device_feature_trait | ||
| self._region = region | ||
|
|
||
| @cached_property | ||
| def fan_speed_options(self) -> list[VacuumModes]: | ||
| return get_clean_modes(self._device_features_trait) | ||
|
|
||
| @cached_property | ||
| def fan_speed_mapping(self) -> dict[int, str]: | ||
| return {fan.code: fan.value for fan in self.fan_speed_options} | ||
|
|
||
| @cached_property | ||
| def water_mode_options(self) -> list[WaterModes]: | ||
| return get_water_modes(self._device_features_trait) | ||
|
|
||
| @cached_property | ||
| def water_mode_mapping(self) -> dict[int, str]: | ||
| return {mop.code: mop.value for mop in self.water_mode_options} | ||
|
|
||
| @cached_property | ||
| def mop_route_options(self) -> list[CleanRoutes]: | ||
| return get_clean_routes(self._device_features_trait, self._region or "us") | ||
|
|
||
| @cached_property | ||
| def mop_route_mapping(self) -> dict[int, str]: | ||
| return {route.code: route.value for route in self.mop_route_options} | ||
|
|
||
| @property | ||
| def fan_speed_name(self) -> str | None: | ||
| if self.fan_power is None: | ||
| return None | ||
| return self.fan_speed_mapping.get(self.fan_power) | ||
|
|
||
| @property | ||
| def water_mode_name(self) -> str | None: | ||
| if self.water_box_mode is None: | ||
| return None | ||
| return self.water_mode_mapping.get(self.water_box_mode) | ||
|
|
||
| @property | ||
| def mop_route_name(self) -> str | None: | ||
| if self.mop_mode is None: | ||
| return None | ||
| return self.mop_route_mapping.get(self.mop_mode) | ||
Lash-L marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| def _parse_response(self, response: common.V1ResponseData) -> Self: | ||
| """Parse the response from the device into a CleanSummary.""" | ||
| status_type: type[Status] = ModelStatus.get(self._product_info.model, S7MaxVStatus) | ||
| """Parse the response from the device into a StatusV2-based status object.""" | ||
| if isinstance(response, list): | ||
| response = response[0] | ||
| if isinstance(response, dict): | ||
| return status_type.from_dict(response) | ||
| return StatusV2.from_dict(response) | ||
| raise ValueError(f"Unexpected status format: {response!r}") | ||
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.
this is a very vague eq, but I think it makes sense, let me know if you think otherwise though @allenporter
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.
What did you think about the copilot comment on hash?
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.
The comment makes sense - I don't necessarily see us often mixing the types in the same section where they would be hashed together. My opinion is that the eq logic is helpful enough that it makes the warning on hash worthwhile. Open to other opinions though.
I added a docstring to hash for now.