Skip to content

Commit ce3f328

Browse files
committed
Add StatusField support for DeviceFeatures
1 parent d0e544c commit ce3f328

File tree

6 files changed

+107
-64
lines changed

6 files changed

+107
-64
lines changed

device_info.yaml

Lines changed: 55 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,68 @@
11
roborock.vacuum.a15:
2-
Protocol Version: '1.0'
2+
Protocol Version: "1.0"
33
Product Nickname: TANOSS
44
New Feature Info: 636084721975295
5-
New Feature Info Str: '0000000000002000'
5+
New Feature Info Str: "0000000000002000"
66
Feature Info:
7-
- 111
8-
- 112
9-
- 113
10-
- 114
11-
- 115
12-
- 116
13-
- 117
14-
- 118
15-
- 119
16-
- 120
17-
- 122
18-
- 123
19-
- 124
20-
- 125
7+
- 111
8+
- 112
9+
- 113
10+
- 114
11+
- 115
12+
- 116
13+
- 117
14+
- 118
15+
- 119
16+
- 120
17+
- 122
18+
- 123
19+
- 124
20+
- 125
2121
roborock.vacuum.a87:
22-
Protocol Version: '1.0'
22+
Protocol Version: "1.0"
2323
Product Nickname: PEARLPLUS
2424
New Feature Info: 4499197267967999
2525
New Feature Info Str: 508A977F7EFEFFFF
2626
Feature Info:
27-
- 111
28-
- 112
29-
- 113
30-
- 114
31-
- 115
32-
- 116
33-
- 117
34-
- 118
35-
- 119
36-
- 120
37-
- 121
38-
- 122
39-
- 123
40-
- 124
41-
- 125
27+
- 111
28+
- 112
29+
- 113
30+
- 114
31+
- 115
32+
- 116
33+
- 117
34+
- 118
35+
- 119
36+
- 120
37+
- 121
38+
- 122
39+
- 123
40+
- 124
41+
- 125
4242
roborock.vacuum.s5e:
43-
Protocol Version: '1.0'
43+
Protocol Version: "1.0"
4444
Product Nickname: RUBYSLITE
4545
New Feature Info: 633887780925447
46-
New Feature Info Str: '0000000000002000'
46+
New Feature Info Str: "0000000000002000"
4747
Feature Info:
48-
- 111
49-
- 112
50-
- 113
51-
- 114
52-
- 115
53-
- 116
54-
- 117
55-
- 118
56-
- 119
57-
- 120
58-
- 122
59-
- 123
60-
- 124
61-
- 125
48+
- 111
49+
- 112
50+
- 113
51+
- 114
52+
- 115
53+
- 116
54+
- 117
55+
- 118
56+
- 119
57+
- 120
58+
- 122
59+
- 123
60+
- 124
61+
- 125
62+
roborock.vacuum.a144:
63+
Protocol Version: "1.0"
64+
Product Nickname: CORALPRO
65+
New Feature Info: 4499197267967999
66+
New Feature Info Str: "0000000000ED3EDDCFFF8F7F7EFEFFFF"
67+
# We don't have this value populated yet
68+
Feature Info: []

roborock/data/v1/v1_containers.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,10 @@ class StatusField(FieldNameBase):
103103
to understand if a feature is supported by the device using `is_field_supported`.
104104
105105
The enum values are names of fields in the `Status` class. Each field is
106-
annotated with `requires_schema_code` metadata to map the field to a schema
107-
code in the product schema, which may have a different name than the field/attribute name.
106+
annotated with one of the following:
107+
- `requires_schema_code` metadata to map the field to a schema code in the
108+
product schema, which may have a different name than the field/attribute name.
109+
- `requires_supported_feature` metadata to map the field to a field in `DeviceFeatures`.
108110
"""
109111

110112
STATE = "state"
@@ -113,18 +115,23 @@ class StatusField(FieldNameBase):
113115
WATER_BOX_MODE = "water_box_mode"
114116
CHARGE_STATUS = "charge_status"
115117
DRY_STATUS = "dry_status"
118+
CLEAN_PERCENT = "clean_percent"
116119

117120

118-
def _requires_schema_code(requires_schema_code: str, default=None) -> Any:
121+
def _requires_schema_code(requires_schema_code: str, default: Any = None) -> Any:
119122
return field(metadata={"requires_schema_code": requires_schema_code}, default=default)
120123

121124

125+
def _requires_supported_feature(requires_supported_feature: str, default: Any = None) -> Any:
126+
return field(metadata={"requires_supported_feature": requires_supported_feature}, default=default)
127+
128+
122129
@dataclass
123130
class Status(RoborockBase):
124131
msg_ver: int | None = None
125132
msg_seq: int | None = None
126-
state: RoborockStateCode | None = _requires_schema_code("state", default=None)
127-
battery: int | None = _requires_schema_code("battery", default=None)
133+
state: RoborockStateCode | None = _requires_schema_code("state")
134+
battery: int | None = _requires_schema_code("battery")
128135
clean_time: int | None = None
129136
clean_area: int | None = None
130137
error_code: RoborockErrorCode | None = None
@@ -137,12 +144,12 @@ class Status(RoborockBase):
137144
back_type: int | None = None
138145
wash_phase: int | None = None
139146
wash_ready: int | None = None
140-
fan_power: RoborockFanPowerCode | None = _requires_schema_code("fan_power", default=None)
147+
fan_power: RoborockFanPowerCode | None = _requires_schema_code("fan_power")
141148
dnd_enabled: int | None = None
142149
map_status: int | None = None
143150
is_locating: int | None = None
144151
lock_status: int | None = None
145-
water_box_mode: RoborockMopIntensityCode | None = _requires_schema_code("water_box_mode", default=None)
152+
water_box_mode: RoborockMopIntensityCode | None = _requires_schema_code("water_box_mode")
146153
water_box_carriage_status: int | None = None
147154
mop_forbidden_enable: int | None = None
148155
camera_status: int | None = None
@@ -160,15 +167,15 @@ class Status(RoborockBase):
160167
collision_avoid_status: int | None = None
161168
switch_map_mode: int | None = None
162169
dock_error_status: RoborockDockErrorCode | None = None
163-
charge_status: int | None = _requires_schema_code("charge_status", default=None)
170+
charge_status: int | None = _requires_schema_code("charge_status")
164171
unsave_map_reason: int | None = None
165172
unsave_map_flag: int | None = None
166173
wash_status: int | None = None
167174
distance_off: int | None = None
168175
in_warmup: int | None = None
169-
dry_status: int | None = _requires_schema_code("drying_status", default=None)
176+
dry_status: int | None = _requires_schema_code("drying_status")
170177
rdt: int | None = None
171-
clean_percent: int | None = None
178+
clean_percent: int | None = _requires_supported_feature("is_support_clean_estimate")
172179
rss: int | None = None
173180
dss: int | None = None
174181
common_status: int | None = None

roborock/devices/traits/v1/device_features.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,12 @@ def is_field_supported(self, cls: type[RoborockBase], field_name: FieldNameBase)
3939
raise ValueError(f"Field {field_name} not found in {cls}")
4040

4141
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
42+
if requires_schema_code is not None:
43+
return requires_schema_code in self._product.supported_schema_codes
44+
requires_supported_feature = dataclass_field.metadata.get("requires_supported_feature", None)
45+
if requires_supported_feature is not None:
46+
return getattr(self, requires_supported_feature)
47+
return True
4748

4849
async def refresh(self) -> None:
4950
"""Refresh the contents of this trait.

tests/devices/traits/v1/__snapshots__/test_device_features.ambr

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
dict({
44
'battery': True,
55
'charge_status': True,
6+
'clean_percent': False,
67
'dry_status': True,
78
'fan_power': True,
89
'state': True,
@@ -13,6 +14,7 @@
1314
dict({
1415
'battery': True,
1516
'charge_status': True,
17+
'clean_percent': False,
1618
'dry_status': True,
1719
'fan_power': True,
1820
'state': True,
@@ -23,6 +25,7 @@
2325
dict({
2426
'battery': True,
2527
'charge_status': True,
28+
'clean_percent': False,
2629
'dry_status': True,
2730
'fan_power': True,
2831
'state': True,

tests/devices/traits/v1/fixtures.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Fixtures for V1 trait tests."""
22

3+
from typing import Any
34
from unittest.mock import AsyncMock
45

56
import pytest
@@ -105,16 +106,26 @@ def dock_type_code_fixture(request: pytest.FixtureRequest) -> RoborockDockTypeCo
105106
return RoborockDockTypeCode.s7_max_ultra_dock
106107

107108

109+
@pytest.fixture(name="mock_app_get_init_status")
110+
def mock_app_get_init_status_fixture(device_info: HomeDataDevice, products: list[HomeDataProduct]) -> dict[str, Any]:
111+
"""Fixture to provide a DeviceFeaturesInfo instance for tests."""
112+
product = next(filter(lambda product: product.id == device_info.product_id, products))
113+
if not product:
114+
raise ValueError(f"Product {device_info.product_id} not found")
115+
return mock_data.APP_GET_INIT_STATUS
116+
117+
108118
@pytest.fixture(autouse=True)
109119
async def discover_features_fixture(
110120
device: RoborockDevice,
111121
mock_rpc_channel: AsyncMock,
122+
mock_app_get_init_status: dict[str, Any],
112123
dock_type_code: RoborockDockTypeCode | None,
113124
) -> None:
114125
"""Fixture to handle device feature discovery."""
115126
assert device.v1_properties
116127
mock_rpc_channel.send_command.side_effect = [
117-
[mock_data.APP_GET_INIT_STATUS],
128+
[mock_app_get_init_status],
118129
{
119130
**mock_data.STATUS,
120131
"dock_type": dock_type_code,

tests/mock_data.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import pathlib
66
from typing import Any
77

8+
import yaml
9+
810
# All data is based on a U.S. customer with a Roborock S7 MaxV Ultra
911
USER_EMAIL = "user@domain.com"
1012

@@ -141,6 +143,18 @@
141143
ZEO_ONE_DEVICE_DATA = DEVICES["home_data_device_zeo_one.json"]
142144
SAROS_10R_DEVICE_DATA = DEVICES["home_data_device_saros_10r.json"]
143145

146+
# Additional Device Features info from YAML. In the future we can merge these
147+
# all into a similar format or get from diagnostrics.
148+
_DEVICE_INFO_DATA = yaml.safe_load(pathlib.Path("device_info.yaml").read_text())
149+
DEVICE_INFO = {
150+
product_model: {
151+
"new_feature_info": data.get("New Feature Info"),
152+
"new_feature_info_str": data.get("New Feature Info Str"),
153+
"feature_info": data.get("Feature Info"),
154+
}
155+
for product_model, data in _DEVICE_INFO_DATA.items()
156+
}
157+
144158

145159
HOME_DATA_RAW: dict[str, Any] = {
146160
"id": 123456,

0 commit comments

Comments
 (0)