roborock.devices.traits.v1.device_features
1from dataclasses import Field, fields 2 3from roborock.data import AppInitStatus, HomeDataProduct, RoborockBase 4from roborock.data.v1.v1_containers import FieldNameBase 5from roborock.device_features import DeviceFeatures 6from roborock.devices.cache import DeviceCache 7from roborock.devices.traits.v1 import common 8from roborock.roborock_typing import RoborockCommand 9 10 11class DeviceFeaturesTrait(DeviceFeatures, common.V1TraitMixin): 12 """Trait for managing supported features on Roborock devices.""" 13 14 command = RoborockCommand.APP_GET_INIT_STATUS 15 16 def __init__(self, product: HomeDataProduct, device_cache: DeviceCache) -> None: # pylint: disable=super-init-not-called 17 """Initialize DeviceFeaturesTrait.""" 18 self._product = product 19 self._nickname = product.product_nickname 20 self._device_cache = device_cache 21 # All fields of DeviceFeatures are required. Initialize them to False 22 # so we have some known state. 23 for field in fields(self): 24 setattr(self, field.name, False) 25 26 def is_field_supported(self, cls: type[RoborockBase], field_name: FieldNameBase) -> bool: 27 """Determines if the specified field is supported by this device. 28 29 We use dataclass attributes on the field to specify the schema code that is required 30 for the field to be supported and it is compared against the list of 31 supported schema codes for the device returned in the product information. 32 """ 33 dataclass_field: Field | None = None 34 for field in fields(cls): 35 if field.name == field_name: 36 dataclass_field = field 37 break 38 if dataclass_field is None: 39 raise ValueError(f"Field {field_name} not found in {cls}") 40 41 requires_schema_code = dataclass_field.metadata.get("requires_schema_code", None) 42 if requires_schema_code is None: 43 # We assume the field is supported 44 return True 45 # If the field requires a protocol that is not supported, we return False 46 return requires_schema_code in self._product.supported_schema_codes 47 48 async def refresh(self) -> None: 49 """Refresh the contents of this trait. 50 51 This will use cached device features if available since they do not 52 change often and this avoids unnecessary RPC calls. This would only 53 ever change with a firmware update, so caching is appropriate. 54 """ 55 cache_data = await self._device_cache.get() 56 if cache_data.device_features is not None: 57 self._update_trait_values(cache_data.device_features) 58 return 59 # Save cached device features 60 await super().refresh() 61 cache_data.device_features = self 62 await self._device_cache.set(cache_data) 63 64 def _parse_response(self, response: common.V1ResponseData) -> DeviceFeatures: 65 """Parse the response from the device into a MapContentTrait instance.""" 66 if not isinstance(response, list): 67 raise ValueError(f"Unexpected AppInitStatus response format: {type(response)}") 68 app_status = AppInitStatus.from_dict(response[0]) 69 return DeviceFeatures.from_feature_flags( 70 new_feature_info=app_status.new_feature_info, 71 new_feature_info_str=app_status.new_feature_info_str, 72 feature_info=app_status.feature_info, 73 product_nickname=self._nickname, 74 )
class
DeviceFeaturesTrait(roborock.device_features.DeviceFeatures, roborock.devices.traits.v1.common.V1TraitMixin):
12class DeviceFeaturesTrait(DeviceFeatures, common.V1TraitMixin): 13 """Trait for managing supported features on Roborock devices.""" 14 15 command = RoborockCommand.APP_GET_INIT_STATUS 16 17 def __init__(self, product: HomeDataProduct, device_cache: DeviceCache) -> None: # pylint: disable=super-init-not-called 18 """Initialize DeviceFeaturesTrait.""" 19 self._product = product 20 self._nickname = product.product_nickname 21 self._device_cache = device_cache 22 # All fields of DeviceFeatures are required. Initialize them to False 23 # so we have some known state. 24 for field in fields(self): 25 setattr(self, field.name, False) 26 27 def is_field_supported(self, cls: type[RoborockBase], field_name: FieldNameBase) -> bool: 28 """Determines if the specified field is supported by this device. 29 30 We use dataclass attributes on the field to specify the schema code that is required 31 for the field to be supported and it is compared against the list of 32 supported schema codes for the device returned in the product information. 33 """ 34 dataclass_field: Field | None = None 35 for field in fields(cls): 36 if field.name == field_name: 37 dataclass_field = field 38 break 39 if dataclass_field is None: 40 raise ValueError(f"Field {field_name} not found in {cls}") 41 42 requires_schema_code = dataclass_field.metadata.get("requires_schema_code", None) 43 if requires_schema_code is None: 44 # We assume the field is supported 45 return True 46 # If the field requires a protocol that is not supported, we return False 47 return requires_schema_code in self._product.supported_schema_codes 48 49 async def refresh(self) -> None: 50 """Refresh the contents of this trait. 51 52 This will use cached device features if available since they do not 53 change often and this avoids unnecessary RPC calls. This would only 54 ever change with a firmware update, so caching is appropriate. 55 """ 56 cache_data = await self._device_cache.get() 57 if cache_data.device_features is not None: 58 self._update_trait_values(cache_data.device_features) 59 return 60 # Save cached device features 61 await super().refresh() 62 cache_data.device_features = self 63 await self._device_cache.set(cache_data) 64 65 def _parse_response(self, response: common.V1ResponseData) -> DeviceFeatures: 66 """Parse the response from the device into a MapContentTrait instance.""" 67 if not isinstance(response, list): 68 raise ValueError(f"Unexpected AppInitStatus response format: {type(response)}") 69 app_status = AppInitStatus.from_dict(response[0]) 70 return DeviceFeatures.from_feature_flags( 71 new_feature_info=app_status.new_feature_info, 72 new_feature_info_str=app_status.new_feature_info_str, 73 feature_info=app_status.feature_info, 74 product_nickname=self._nickname, 75 )
Trait for managing supported features on Roborock devices.
DeviceFeaturesTrait( product: roborock.data.containers.HomeDataProduct, device_cache: roborock.devices.cache.DeviceCache)
17 def __init__(self, product: HomeDataProduct, device_cache: DeviceCache) -> None: # pylint: disable=super-init-not-called 18 """Initialize DeviceFeaturesTrait.""" 19 self._product = product 20 self._nickname = product.product_nickname 21 self._device_cache = device_cache 22 # All fields of DeviceFeatures are required. Initialize them to False 23 # so we have some known state. 24 for field in fields(self): 25 setattr(self, field.name, False)
Initialize DeviceFeaturesTrait.
def
is_field_supported( self, cls: type[roborock.data.containers.RoborockBase], field_name: roborock.data.v1.v1_containers.FieldNameBase) -> bool:
27 def is_field_supported(self, cls: type[RoborockBase], field_name: FieldNameBase) -> bool: 28 """Determines if the specified field is supported by this device. 29 30 We use dataclass attributes on the field to specify the schema code that is required 31 for the field to be supported and it is compared against the list of 32 supported schema codes for the device returned in the product information. 33 """ 34 dataclass_field: Field | None = None 35 for field in fields(cls): 36 if field.name == field_name: 37 dataclass_field = field 38 break 39 if dataclass_field is None: 40 raise ValueError(f"Field {field_name} not found in {cls}") 41 42 requires_schema_code = dataclass_field.metadata.get("requires_schema_code", None) 43 if requires_schema_code is None: 44 # We assume the field is supported 45 return True 46 # If the field requires a protocol that is not supported, we return False 47 return requires_schema_code in self._product.supported_schema_codes
Determines if the specified field is supported by this device.
We use dataclass attributes on the field to specify the schema code that is required for the field to be supported and it is compared against the list of supported schema codes for the device returned in the product information.
async def
refresh(self) -> None:
49 async def refresh(self) -> None: 50 """Refresh the contents of this trait. 51 52 This will use cached device features if available since they do not 53 change often and this avoids unnecessary RPC calls. This would only 54 ever change with a firmware update, so caching is appropriate. 55 """ 56 cache_data = await self._device_cache.get() 57 if cache_data.device_features is not None: 58 self._update_trait_values(cache_data.device_features) 59 return 60 # Save cached device features 61 await super().refresh() 62 cache_data.device_features = self 63 await self._device_cache.set(cache_data)
Refresh the contents of this trait.
This will use cached device features if available since they do not change often and this avoids unnecessary RPC calls. This would only ever change with a firmware update, so caching is appropriate.