88from datetime import timezone
99from enum import Enum
1010from functools import cached_property
11- from typing import Any , NamedTuple , get_args , get_origin
12-
11+ from typing import Any , NamedTuple , TypeVar , get_args , get_origin
12+
13+ from .clean_modes import (
14+ CleanRoutes ,
15+ RoborockModeEnum ,
16+ VacuumModes ,
17+ WaterModes ,
18+ get_clean_modes ,
19+ get_clean_routes ,
20+ get_water_modes ,
21+ )
1322from .code_mappings import (
1423 SHORT_MODEL_TO_ENUM ,
1524 RoborockCategory ,
1928 RoborockDockTypeCode ,
2029 RoborockDockWashTowelModeCode ,
2130 RoborockErrorCode ,
22- RoborockFanPowerCode ,
2331 RoborockFanSpeedP10 ,
2432 RoborockFanSpeedQ7Max ,
2533 RoborockFanSpeedQRevoCurv ,
3341 RoborockFanSpeedSaros10R ,
3442 RoborockFinishReason ,
3543 RoborockInCleaning ,
36- RoborockMopIntensityCode ,
3744 RoborockMopIntensityP10 ,
3845 RoborockMopIntensityQ7Max ,
3946 RoborockMopIntensityQRevoCurv ,
4552 RoborockMopIntensityS8MaxVUltra ,
4653 RoborockMopIntensitySaros10 ,
4754 RoborockMopIntensitySaros10R ,
48- RoborockMopModeCode ,
4955 RoborockMopModeQRevoCurv ,
5056 RoborockMopModeQRevoMaster ,
5157 RoborockMopModeQRevoMaxV ,
9096 ROBOROCK_G20S_Ultra ,
9197)
9298from .device_features import DeviceFeatures
93- from .exceptions import RoborockException
9499
95100_LOGGER = logging .getLogger (__name__ )
101+ T = TypeVar ("T" , bound = "RoborockModeEnum" )
96102
97103
98104def _camelize (s : str ):
@@ -353,12 +359,12 @@ class Status(RoborockBase):
353359 back_type : int | None = None
354360 wash_phase : int | None = None
355361 wash_ready : int | None = None
356- fan_power : RoborockFanPowerCode | None = None
362+ fan_power : int | None = None
357363 dnd_enabled : int | None = None
358364 map_status : int | None = None
359365 is_locating : int | None = None
360366 lock_status : int | None = None
361- water_box_mode : RoborockMopIntensityCode | None = None
367+ water_box_mode : int | None = None
362368 water_box_carriage_status : int | None = None
363369 mop_forbidden_enable : int | None = None
364370 camera_status : int | None = None
@@ -371,7 +377,7 @@ class Status(RoborockBase):
371377 dust_collection_status : int | None = None
372378 auto_dust_collection : int | None = None
373379 avoid_count : int | None = None
374- mop_mode : RoborockMopModeCode | None = None
380+ mop_mode : int | None = None
375381 debug_mode : int | None = None
376382 collision_avoid_status : int | None = None
377383 switch_map_mode : int | None = None
@@ -392,38 +398,64 @@ class Status(RoborockBase):
392398 error_code_name : str | None = None
393399 state_name : str | None = None
394400 water_box_mode_name : str | None = None
395- fan_power_options : list [str ] = field (default_factory = list )
396401 fan_power_name : str | None = None
397402 mop_mode_name : str | None = None
403+ supported_fan_powers : list [VacuumModes ] = field (default_factory = list , init = False , repr = False )
404+ supported_water_modes : list [WaterModes ] = field (default_factory = list , init = False , repr = False )
405+ supported_mop_modes : list [CleanRoutes ] = field (default_factory = list , init = False , repr = False )
398406
399407 def __post_init__ (self ) -> None :
400408 self .square_meter_clean_area = round (self .clean_area / 1000000 , 1 ) if self .clean_area is not None else None
401409 if self .error_code is not None :
402410 self .error_code_name = self .error_code .name
403411 if self .state is not None :
404412 self .state_name = self .state .name
405- if self .water_box_mode is not None :
406- self .water_box_mode_name = self .water_box_mode .name
407- if self .fan_power is not None :
408- self .fan_power_options = self .fan_power .keys ()
409- self .fan_power_name = self .fan_power .name
410- if self .mop_mode is not None :
411- self .mop_mode_name = self .mop_mode .name
413+
414+ @staticmethod
415+ def _find_enum_by_code (code : int | None , enums : list [T ]) -> T | None :
416+ """Helper to find an enum member in a list by its code."""
417+ if code is None :
418+ return None
419+ for enum_member in enums :
420+ if enum_member .code == code :
421+ return enum_member
422+ return None
423+
424+ @staticmethod
425+ def _find_code_by_name (name : str , enums : list [T ]) -> int | None :
426+ """Helper to find an enum member in a list by its code."""
427+ for enum_member in enums :
428+ if enum_member .value == name :
429+ return enum_member .code
430+ return None
431+
432+ def configure (self , features : DeviceFeatures , region : str ) -> None :
433+ """
434+ Configures the status object with device-specific capabilities and processes the data.
435+ This method should be called immediately after creating the Status object.
436+ """
437+ self .supported_fan_powers = get_clean_modes (features )
438+ self .supported_water_modes = get_water_modes (features )
439+ self .supported_mop_modes = get_clean_routes (features , region )
440+ fan_power_enum = self ._find_enum_by_code (self .fan_power , self .supported_fan_powers )
441+ self .fan_power_name = fan_power_enum .value if fan_power_enum else None
442+
443+ # Resolve Water Mode
444+ water_box_mode_enum = self ._find_enum_by_code (self .water_box_mode , self .supported_water_modes )
445+ self .water_box_mode_name = water_box_mode_enum .value if water_box_mode_enum else None
446+
447+ # Resolve Mop Mode (Clean Route)
448+ mop_mode_enum = self ._find_enum_by_code (self .mop_mode , self .supported_mop_modes )
449+ self .mop_mode_name = mop_mode_enum .value if mop_mode_enum else None
412450
413451 def get_fan_speed_code (self , fan_speed : str ) -> int :
414- if self .fan_power is None :
415- raise RoborockException ("Attempted to get fan speed before status has been updated." )
416- return self .fan_power .as_dict ().get (fan_speed )
452+ return self ._find_code_by_name (fan_speed , self .supported_fan_powers )
417453
418454 def get_mop_intensity_code (self , mop_intensity : str ) -> int :
419- if self .water_box_mode is None :
420- raise RoborockException ("Attempted to get mop_intensity before status has been updated." )
421- return self .water_box_mode .as_dict ().get (mop_intensity )
455+ return self ._find_code_by_name (mop_intensity , self .supported_water_modes )
422456
423457 def get_mop_mode_code (self , mop_mode : str ) -> int :
424- if self .mop_mode is None :
425- raise RoborockException ("Attempted to get mop_mode before status has been updated." )
426- return self .mop_mode .as_dict ().get (mop_mode )
458+ return self ._find_code_by_name (mop_mode , self .supported_mop_modes )
427459
428460 @property
429461 def current_map (self ) -> int | None :
@@ -433,6 +465,33 @@ def current_map(self) -> int | None:
433465 return None
434466
435467
468+ class CustomStatus :
469+ """A factory for creating fully configured Status objects for a specific device."""
470+
471+ def __init__ (self , features : DeviceFeatures , region : str ):
472+ """
473+ Initializes the factory with the device-specific configuration.
474+
475+ Args:
476+ features: The DeviceFeatures object for the target device.
477+ region: The region code for the device (e.g., "US", "CN").
478+ """
479+ self ._features = features
480+ self ._region = region
481+
482+ def from_dict (self , data : dict ) -> Status :
483+ """
484+ Creates a Status instance from a dictionary and immediately configures it.
485+ """
486+ # Step 1: Create the base Status object from the raw API data.
487+ instance = Status .from_dict (data )
488+
489+ # Step 2: Configure it with the stored features and region.
490+ instance .configure (features = self ._features , region = self ._region )
491+
492+ return instance
493+
494+
436495@dataclass
437496class S4MaxStatus (Status ):
438497 fan_power : RoborockFanSpeedS6Pure | None = None
@@ -730,6 +789,7 @@ class DeviceData(RoborockBase):
730789 device : HomeDataDevice
731790 model : str
732791 host : str | None = None
792+ region : str = "us"
733793 product_nickname : RoborockProductNickname | None = None
734794 device_features : DeviceFeatures | None = None
735795
0 commit comments