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