roborock.devices.traits.v1.status

  1import logging
  2from functools import cached_property
  3from typing import Any
  4
  5from roborock import (
  6    CleaningMode,
  7    CleanRoutes,
  8    RoborockDockState,
  9    StatusV2,
 10    VacuumModes,
 11    WaterModes,
 12    get_clean_modes,
 13    get_clean_routes,
 14    get_cleaning_mode_options,
 15    get_cleaning_mode_parameters,
 16    get_current_cleaning_mode,
 17    get_dock_state,
 18    get_water_mode_mapping,
 19    get_water_modes,
 20    get_water_slide_mode_name,
 21    resolve_cleaning_mode,
 22)
 23from roborock.devices.traits.common import DpsDataConverter, TraitUpdateListener
 24from roborock.roborock_message import RoborockDataProtocol
 25from roborock.roborock_typing import RoborockCommand
 26
 27from . import common
 28from .device_features import DeviceFeaturesTrait
 29
 30_LOGGER = logging.getLogger(__name__)
 31
 32_DPS_CONVERTER = DpsDataConverter.from_dataclass(StatusV2)
 33
 34
 35class StatusTrait(StatusV2, common.V1TraitMixin, TraitUpdateListener):
 36    """Trait for managing the status of Roborock devices.
 37
 38    The StatusTrait gives you the access to the state of a Roborock vacuum.
 39    The various attribute options on state change per each device.
 40    Values like fan speed, mop mode, etc. have different options for every device
 41    and are dynamically determined.
 42
 43    Usage:
 44        Before accessing status properties, you should call `refresh()` to fetch
 45        the latest data from the device. You must pass in the device feature trait
 46        to this trait so that the dynamic attributes can be pre-determined.
 47
 48    The current dynamic attributes are:
 49    - Fan Speed
 50    - Water Mode
 51    - Mop Route
 52
 53    You should use the _options version of the attribute to know which are
 54    supported for your device (i.e. fan_speed_options)
 55    Then you can use the _mapping to convert an int value to the actual Enum.
 56    (i.e. fan_speed_mapping)
 57    You can use the _name property to get the str value of the enum. (i.e. fan_speed_name)
 58
 59    """
 60
 61    command = RoborockCommand.GET_STATUS
 62    converter = common.DefaultConverter(StatusV2)
 63
 64    def __init__(self, device_feature_trait: DeviceFeaturesTrait, region: str | None = None) -> None:
 65        """Initialize the StatusTrait."""
 66        super().__init__()
 67        TraitUpdateListener.__init__(self, logger=_LOGGER)
 68        self._device_features_trait = device_feature_trait
 69        self._region = region
 70
 71    @property
 72    def dock_state(self) -> RoborockDockState:
 73        """Return the feature-aware dock state used by the Roborock app."""
 74        return get_dock_state(
 75            state=self.state,
 76            battery=self.battery,
 77            charge_status=self.charge_status,
 78            is_supported_valley_electricity=self._device_features_trait.is_supported_valley_electricity,
 79        )
 80
 81    @property
 82    def is_battery_charging(self) -> bool:
 83        """Return whether the battery is actively charging."""
 84        return self.dock_state == RoborockDockState.charging
 85
 86    @cached_property
 87    def fan_speed_options(self) -> list[VacuumModes]:
 88        return get_clean_modes(self._device_features_trait)
 89
 90    @cached_property
 91    def fan_speed_mapping(self) -> dict[int, str]:
 92        return {fan.code: fan.value for fan in self.fan_speed_options}
 93
 94    @cached_property
 95    def water_mode_options(self) -> list[WaterModes]:
 96        return get_water_modes(self._device_features_trait)
 97
 98    @cached_property
 99    def water_mode_mapping(self) -> dict[int, str]:
100        return get_water_mode_mapping(self._device_features_trait)
101
102    @cached_property
103    def mop_route_options(self) -> list[CleanRoutes]:
104        return get_clean_routes(self._device_features_trait, self._region or "us")
105
106    @cached_property
107    def mop_route_mapping(self) -> dict[int, str]:
108        return {route.code: route.display_name for route in self.mop_route_options}
109
110    @cached_property
111    def cleaning_mode_options(self) -> list[CleaningMode]:
112        return get_cleaning_mode_options(self._device_features_trait)
113
114    @property
115    def fan_speed_name(self) -> str | None:
116        if self.fan_power is None:
117            return None
118        return self.fan_speed_mapping.get(self.fan_power)
119
120    @property
121    def water_mode_name(self) -> str | None:
122        if self.water_box_mode is None:
123            return None
124        if self._device_features_trait.is_water_slide_mode_supported:
125            return get_water_slide_mode_name(self.water_box_mode)
126        return self.water_mode_mapping.get(self.water_box_mode)
127
128    @property
129    def mop_route_name(self) -> str | None:
130        if self.mop_mode is None:
131            return None
132        return self.mop_route_mapping.get(self.mop_mode)
133
134    @property
135    def current_cleaning_mode(self) -> CleaningMode | None:
136        return get_current_cleaning_mode(
137            clean_mode=self.fan_power,
138            water_mode=self.water_box_mode,
139            mop_mode=self.mop_mode,
140            features=self._device_features_trait,
141        )
142
143    @property
144    def current_cleaning_mode_name(self) -> str | None:
145        if (cleaning_mode := self.current_cleaning_mode) is None:
146            return None
147        return cleaning_mode.value
148
149    async def set_cleaning_mode(self, cleaning_mode: str | CleaningMode) -> None:
150        """Set the preferred high-level cleaning mode for the device."""
151        await self.rpc_channel.send_command(
152            RoborockCommand.SET_CLEAN_MOTOR_MODE,
153            params=get_cleaning_mode_parameters(resolve_cleaning_mode(cleaning_mode), self._device_features_trait),
154        )
155
156    async def resolve_error(self, error_code: int | None = None) -> None:
157        """Resolve an error, like tapping "Resolved" in the Roborock app.
158
159        Without an explicit code this resolves the current dock error if there is one,
160        otherwise the current robot error. A dock error such as water_empty stays latched
161        on the device until it is resolved, even after the cause has been fixed.
162        """
163        if error_code is None:
164            if self.dock_error_status:
165                error_code = int(self.dock_error_status)
166            elif self.error_code:
167                error_code = int(self.error_code)
168            else:
169                return
170        await self.rpc_channel.send_command(RoborockCommand.RESOLVE_ERROR, params={"error_code": error_code})
171        await self.refresh()
172
173    def update_from_dps(self, decoded_dps: dict[RoborockDataProtocol, Any]) -> None:
174        """Update the trait from data protocol push message data.
175
176        This handles unsolicited status updates pushed by the device
177        via RoborockDataProtocol codes (e.g. STATE=121, BATTERY=122).
178        """
179        if _DPS_CONVERTER.update_from_dps(self, decoded_dps):
180            self._notify_update()
class StatusTrait(roborock.data.v1.v1_containers.StatusV2, roborock.devices.traits.v1.common.V1TraitMixin, roborock.devices.traits.common.TraitUpdateListener):
 36class StatusTrait(StatusV2, common.V1TraitMixin, TraitUpdateListener):
 37    """Trait for managing the status of Roborock devices.
 38
 39    The StatusTrait gives you the access to the state of a Roborock vacuum.
 40    The various attribute options on state change per each device.
 41    Values like fan speed, mop mode, etc. have different options for every device
 42    and are dynamically determined.
 43
 44    Usage:
 45        Before accessing status properties, you should call `refresh()` to fetch
 46        the latest data from the device. You must pass in the device feature trait
 47        to this trait so that the dynamic attributes can be pre-determined.
 48
 49    The current dynamic attributes are:
 50    - Fan Speed
 51    - Water Mode
 52    - Mop Route
 53
 54    You should use the _options version of the attribute to know which are
 55    supported for your device (i.e. fan_speed_options)
 56    Then you can use the _mapping to convert an int value to the actual Enum.
 57    (i.e. fan_speed_mapping)
 58    You can use the _name property to get the str value of the enum. (i.e. fan_speed_name)
 59
 60    """
 61
 62    command = RoborockCommand.GET_STATUS
 63    converter = common.DefaultConverter(StatusV2)
 64
 65    def __init__(self, device_feature_trait: DeviceFeaturesTrait, region: str | None = None) -> None:
 66        """Initialize the StatusTrait."""
 67        super().__init__()
 68        TraitUpdateListener.__init__(self, logger=_LOGGER)
 69        self._device_features_trait = device_feature_trait
 70        self._region = region
 71
 72    @property
 73    def dock_state(self) -> RoborockDockState:
 74        """Return the feature-aware dock state used by the Roborock app."""
 75        return get_dock_state(
 76            state=self.state,
 77            battery=self.battery,
 78            charge_status=self.charge_status,
 79            is_supported_valley_electricity=self._device_features_trait.is_supported_valley_electricity,
 80        )
 81
 82    @property
 83    def is_battery_charging(self) -> bool:
 84        """Return whether the battery is actively charging."""
 85        return self.dock_state == RoborockDockState.charging
 86
 87    @cached_property
 88    def fan_speed_options(self) -> list[VacuumModes]:
 89        return get_clean_modes(self._device_features_trait)
 90
 91    @cached_property
 92    def fan_speed_mapping(self) -> dict[int, str]:
 93        return {fan.code: fan.value for fan in self.fan_speed_options}
 94
 95    @cached_property
 96    def water_mode_options(self) -> list[WaterModes]:
 97        return get_water_modes(self._device_features_trait)
 98
 99    @cached_property
100    def water_mode_mapping(self) -> dict[int, str]:
101        return get_water_mode_mapping(self._device_features_trait)
102
103    @cached_property
104    def mop_route_options(self) -> list[CleanRoutes]:
105        return get_clean_routes(self._device_features_trait, self._region or "us")
106
107    @cached_property
108    def mop_route_mapping(self) -> dict[int, str]:
109        return {route.code: route.display_name for route in self.mop_route_options}
110
111    @cached_property
112    def cleaning_mode_options(self) -> list[CleaningMode]:
113        return get_cleaning_mode_options(self._device_features_trait)
114
115    @property
116    def fan_speed_name(self) -> str | None:
117        if self.fan_power is None:
118            return None
119        return self.fan_speed_mapping.get(self.fan_power)
120
121    @property
122    def water_mode_name(self) -> str | None:
123        if self.water_box_mode is None:
124            return None
125        if self._device_features_trait.is_water_slide_mode_supported:
126            return get_water_slide_mode_name(self.water_box_mode)
127        return self.water_mode_mapping.get(self.water_box_mode)
128
129    @property
130    def mop_route_name(self) -> str | None:
131        if self.mop_mode is None:
132            return None
133        return self.mop_route_mapping.get(self.mop_mode)
134
135    @property
136    def current_cleaning_mode(self) -> CleaningMode | None:
137        return get_current_cleaning_mode(
138            clean_mode=self.fan_power,
139            water_mode=self.water_box_mode,
140            mop_mode=self.mop_mode,
141            features=self._device_features_trait,
142        )
143
144    @property
145    def current_cleaning_mode_name(self) -> str | None:
146        if (cleaning_mode := self.current_cleaning_mode) is None:
147            return None
148        return cleaning_mode.value
149
150    async def set_cleaning_mode(self, cleaning_mode: str | CleaningMode) -> None:
151        """Set the preferred high-level cleaning mode for the device."""
152        await self.rpc_channel.send_command(
153            RoborockCommand.SET_CLEAN_MOTOR_MODE,
154            params=get_cleaning_mode_parameters(resolve_cleaning_mode(cleaning_mode), self._device_features_trait),
155        )
156
157    async def resolve_error(self, error_code: int | None = None) -> None:
158        """Resolve an error, like tapping "Resolved" in the Roborock app.
159
160        Without an explicit code this resolves the current dock error if there is one,
161        otherwise the current robot error. A dock error such as water_empty stays latched
162        on the device until it is resolved, even after the cause has been fixed.
163        """
164        if error_code is None:
165            if self.dock_error_status:
166                error_code = int(self.dock_error_status)
167            elif self.error_code:
168                error_code = int(self.error_code)
169            else:
170                return
171        await self.rpc_channel.send_command(RoborockCommand.RESOLVE_ERROR, params={"error_code": error_code})
172        await self.refresh()
173
174    def update_from_dps(self, decoded_dps: dict[RoborockDataProtocol, Any]) -> None:
175        """Update the trait from data protocol push message data.
176
177        This handles unsolicited status updates pushed by the device
178        via RoborockDataProtocol codes (e.g. STATE=121, BATTERY=122).
179        """
180        if _DPS_CONVERTER.update_from_dps(self, decoded_dps):
181            self._notify_update()

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: 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 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)

StatusTrait( device_feature_trait: roborock.devices.traits.v1.device_features.DeviceFeaturesTrait, region: str | None = None)
65    def __init__(self, device_feature_trait: DeviceFeaturesTrait, region: str | None = None) -> None:
66        """Initialize the StatusTrait."""
67        super().__init__()
68        TraitUpdateListener.__init__(self, logger=_LOGGER)
69        self._device_features_trait = device_feature_trait
70        self._region = region

Initialize the StatusTrait.

command = <RoborockCommand.GET_STATUS: 'get_status'>

The RoborockCommand used to fetch the trait data from the device (internal only).

converter = DefaultConverter

The converter used to parse the response from the device (internal only).

72    @property
73    def dock_state(self) -> RoborockDockState:
74        """Return the feature-aware dock state used by the Roborock app."""
75        return get_dock_state(
76            state=self.state,
77            battery=self.battery,
78            charge_status=self.charge_status,
79            is_supported_valley_electricity=self._device_features_trait.is_supported_valley_electricity,
80        )

Return the feature-aware dock state used by the Roborock app.

is_battery_charging: bool
82    @property
83    def is_battery_charging(self) -> bool:
84        """Return whether the battery is actively charging."""
85        return self.dock_state == RoborockDockState.charging

Return whether the battery is actively charging.

fan_speed_options: list[roborock.data.v1.v1_clean_modes.VacuumModes]
87    @cached_property
88    def fan_speed_options(self) -> list[VacuumModes]:
89        return get_clean_modes(self._device_features_trait)
fan_speed_mapping: dict[int, str]
91    @cached_property
92    def fan_speed_mapping(self) -> dict[int, str]:
93        return {fan.code: fan.value for fan in self.fan_speed_options}
water_mode_options: list[roborock.data.v1.v1_clean_modes.WaterModes]
95    @cached_property
96    def water_mode_options(self) -> list[WaterModes]:
97        return get_water_modes(self._device_features_trait)
water_mode_mapping: dict[int, str]
 99    @cached_property
100    def water_mode_mapping(self) -> dict[int, str]:
101        return get_water_mode_mapping(self._device_features_trait)
mop_route_options: list[roborock.data.v1.v1_clean_modes.CleanRoutes]
103    @cached_property
104    def mop_route_options(self) -> list[CleanRoutes]:
105        return get_clean_routes(self._device_features_trait, self._region or "us")
mop_route_mapping: dict[int, str]
107    @cached_property
108    def mop_route_mapping(self) -> dict[int, str]:
109        return {route.code: route.display_name for route in self.mop_route_options}
cleaning_mode_options: list[roborock.data.v1.v1_clean_modes.CleaningMode]
111    @cached_property
112    def cleaning_mode_options(self) -> list[CleaningMode]:
113        return get_cleaning_mode_options(self._device_features_trait)
fan_speed_name: str | None
115    @property
116    def fan_speed_name(self) -> str | None:
117        if self.fan_power is None:
118            return None
119        return self.fan_speed_mapping.get(self.fan_power)
water_mode_name: str | None
121    @property
122    def water_mode_name(self) -> str | None:
123        if self.water_box_mode is None:
124            return None
125        if self._device_features_trait.is_water_slide_mode_supported:
126            return get_water_slide_mode_name(self.water_box_mode)
127        return self.water_mode_mapping.get(self.water_box_mode)
mop_route_name: str | None
129    @property
130    def mop_route_name(self) -> str | None:
131        if self.mop_mode is None:
132            return None
133        return self.mop_route_mapping.get(self.mop_mode)
current_cleaning_mode: roborock.data.v1.v1_clean_modes.CleaningMode | None
135    @property
136    def current_cleaning_mode(self) -> CleaningMode | None:
137        return get_current_cleaning_mode(
138            clean_mode=self.fan_power,
139            water_mode=self.water_box_mode,
140            mop_mode=self.mop_mode,
141            features=self._device_features_trait,
142        )
current_cleaning_mode_name: str | None
144    @property
145    def current_cleaning_mode_name(self) -> str | None:
146        if (cleaning_mode := self.current_cleaning_mode) is None:
147            return None
148        return cleaning_mode.value
async def set_cleaning_mode( self, cleaning_mode: str | roborock.data.v1.v1_clean_modes.CleaningMode) -> None:
150    async def set_cleaning_mode(self, cleaning_mode: str | CleaningMode) -> None:
151        """Set the preferred high-level cleaning mode for the device."""
152        await self.rpc_channel.send_command(
153            RoborockCommand.SET_CLEAN_MOTOR_MODE,
154            params=get_cleaning_mode_parameters(resolve_cleaning_mode(cleaning_mode), self._device_features_trait),
155        )

Set the preferred high-level cleaning mode for the device.

async def resolve_error(self, error_code: int | None = None) -> None:
157    async def resolve_error(self, error_code: int | None = None) -> None:
158        """Resolve an error, like tapping "Resolved" in the Roborock app.
159
160        Without an explicit code this resolves the current dock error if there is one,
161        otherwise the current robot error. A dock error such as water_empty stays latched
162        on the device until it is resolved, even after the cause has been fixed.
163        """
164        if error_code is None:
165            if self.dock_error_status:
166                error_code = int(self.dock_error_status)
167            elif self.error_code:
168                error_code = int(self.error_code)
169            else:
170                return
171        await self.rpc_channel.send_command(RoborockCommand.RESOLVE_ERROR, params={"error_code": error_code})
172        await self.refresh()

Resolve an error, like tapping "Resolved" in the Roborock app.

Without an explicit code this resolves the current dock error if there is one, otherwise the current robot error. A dock error such as water_empty stays latched on the device until it is resolved, even after the cause has been fixed.

def update_from_dps( self, decoded_dps: dict[roborock.roborock_message.RoborockDataProtocol, typing.Any]) -> None:
174    def update_from_dps(self, decoded_dps: dict[RoborockDataProtocol, Any]) -> None:
175        """Update the trait from data protocol push message data.
176
177        This handles unsolicited status updates pushed by the device
178        via RoborockDataProtocol codes (e.g. STATE=121, BATTERY=122).
179        """
180        if _DPS_CONVERTER.update_from_dps(self, decoded_dps):
181            self._notify_update()

Update the trait from data protocol push message data.

This handles unsolicited status updates pushed by the device via RoborockDataProtocol codes (e.g. STATE=121, BATTERY=122).