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.

command = <RoborockCommand.APP_GET_INIT_STATUS: 'app_get_init_status'>
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.