Skip to content

Commit 8092b67

Browse files
feat: extending device status by device model (#51)
* feat: extending device status by device model * chore: linting
1 parent 9e3630b commit 8092b67

File tree

7 files changed

+95
-148
lines changed

7 files changed

+95
-148
lines changed

roborock/api.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import struct
1414
import time
1515
from random import randint
16-
from typing import Any, Callable, Coroutine, Optional
16+
from typing import Any, Callable, Coroutine, Optional, Type
1717

1818
import aiohttp
1919

@@ -25,6 +25,7 @@
2525
DNDTimer,
2626
DustCollectionMode,
2727
HomeData,
28+
ModelStatus,
2829
MultiMapsList,
2930
NetworkInfo,
3031
RoborockDeviceInfo,
@@ -221,9 +222,8 @@ async def send_command(self, method: RoborockCommand, params: Optional[list | di
221222
async def get_status(self) -> Status | None:
222223
status = await self.send_command(RoborockCommand.GET_STATUS)
223224
if isinstance(status, dict):
224-
status = Status.from_dict(status)
225-
status.update_status(self.device_info.model_specification)
226-
return status
225+
_cls: Type[Status] = ModelStatus[self.device_info.model]
226+
return _cls.from_dict(status)
227227

228228
return None
229229

roborock/cli.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -125,17 +125,13 @@ async def command(ctx, cmd, device_id, params):
125125
device = next((device for device in devices if device.duid == device_id), None)
126126
if device is None:
127127
raise RoborockException("No device found")
128-
model_specification = next(
129-
(
130-
product.model_specification
131-
for product in home_data.products
132-
if device is not None and product.did == device.duid
133-
),
128+
model = next(
129+
(product.model for product in home_data.products if device is not None and product.did == device.duid),
134130
None,
135131
)
136-
if model_specification is None:
137-
raise RoborockException(f"Could not find model specifications for device {device.name}")
138-
device_info = RoborockDeviceInfo(device=device, model_specification=model_specification)
132+
if model is None:
133+
raise RoborockException(f"Could not find model for device {device.name}")
134+
device_info = RoborockDeviceInfo(device=device, model=model)
139135
mqtt_client = RoborockMqttClient(login_data.user_data, device_info)
140136
await mqtt_client.send_command(cmd, params)
141137
mqtt_client.__del__()

roborock/code_mappings.py

Lines changed: 0 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,9 @@
11
from __future__ import annotations
22

33
import logging
4-
from dataclasses import dataclass
54
from enum import IntEnum
65
from typing import Type
76

8-
from roborock.const import (
9-
ROBOROCK_Q7_MAX,
10-
ROBOROCK_S5_MAX,
11-
ROBOROCK_S6_MAXV,
12-
ROBOROCK_S6_PURE,
13-
ROBOROCK_S7,
14-
ROBOROCK_S7_MAXV,
15-
ROBOROCK_S8_PRO_ULTRA,
16-
)
17-
187
_LOGGER = logging.getLogger(__name__)
198

209

@@ -256,65 +245,3 @@ class RoborockDockWashTowelModeCode(RoborockEnum):
256245
light = 0
257246
balanced = 1
258247
deep = 2
259-
260-
261-
@dataclass
262-
class ModelSpecification:
263-
model_name: str
264-
model_code: str
265-
fan_power_code: Type[RoborockFanPowerCode]
266-
mop_mode_code: Type[RoborockMopModeCode] | None
267-
mop_intensity_code: Type[RoborockMopIntensityCode] | None
268-
269-
270-
model_specifications = {
271-
ROBOROCK_S5_MAX: ModelSpecification(
272-
model_name="Roborock S5 Max",
273-
model_code=ROBOROCK_S5_MAX,
274-
fan_power_code=RoborockFanSpeedS6Pure,
275-
mop_mode_code=None,
276-
mop_intensity_code=RoborockMopIntensityV2,
277-
),
278-
ROBOROCK_Q7_MAX: ModelSpecification(
279-
model_name="Roborock Q7 Max",
280-
model_code=ROBOROCK_Q7_MAX,
281-
fan_power_code=RoborockFanSpeedQ7Max,
282-
mop_mode_code=None,
283-
mop_intensity_code=RoborockMopIntensityV2,
284-
),
285-
ROBOROCK_S6_MAXV: ModelSpecification(
286-
model_name="Roborock S6 MaxV",
287-
model_code=ROBOROCK_S6_MAXV,
288-
fan_power_code=RoborockFanSpeedE2,
289-
mop_mode_code=None,
290-
mop_intensity_code=RoborockMopIntensityV2,
291-
),
292-
ROBOROCK_S6_PURE: ModelSpecification(
293-
model_name="Roborock S6 Pure",
294-
model_code=ROBOROCK_S6_PURE,
295-
fan_power_code=RoborockFanSpeedS6Pure,
296-
mop_mode_code=None,
297-
mop_intensity_code=None,
298-
),
299-
ROBOROCK_S7_MAXV: ModelSpecification(
300-
model_name="Roborock S7 MaxV",
301-
model_code=ROBOROCK_S7_MAXV,
302-
fan_power_code=RoborockFanSpeedS7MaxV,
303-
mop_mode_code=RoborockMopModeS7,
304-
mop_intensity_code=RoborockMopIntensityS7,
305-
),
306-
ROBOROCK_S7: ModelSpecification(
307-
model_name="Roborock S7",
308-
model_code=ROBOROCK_S7,
309-
fan_power_code=RoborockFanSpeedS7,
310-
mop_mode_code=RoborockMopModeS7,
311-
mop_intensity_code=RoborockMopIntensityS7,
312-
),
313-
ROBOROCK_S8_PRO_ULTRA: ModelSpecification(
314-
model_name="Roborock S8 Pro Ultra",
315-
model_code=ROBOROCK_S8_PRO_ULTRA,
316-
fan_power_code=RoborockFanSpeedS7MaxV,
317-
mop_mode_code=RoborockMopModeS8ProUltra,
318-
mop_intensity_code=RoborockMopIntensityS7,
319-
),
320-
}

roborock/containers.py

Lines changed: 74 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,35 @@
99
from dacite import Config, from_dict
1010

1111
from .code_mappings import (
12-
ModelSpecification,
1312
RoborockDockDustCollectionModeCode,
1413
RoborockDockErrorCode,
1514
RoborockDockTypeCode,
1615
RoborockDockWashTowelModeCode,
1716
RoborockErrorCode,
1817
RoborockFanPowerCode,
18+
RoborockFanSpeedE2,
19+
RoborockFanSpeedQ7Max,
20+
RoborockFanSpeedS6Pure,
21+
RoborockFanSpeedS7,
22+
RoborockFanSpeedS7MaxV,
1923
RoborockMopIntensityCode,
24+
RoborockMopIntensityS7,
25+
RoborockMopIntensityV2,
2026
RoborockMopModeCode,
27+
RoborockMopModeS7,
28+
RoborockMopModeS8ProUltra,
2129
RoborockStateCode,
22-
model_specifications,
2330
)
2431
from .const import (
2532
FILTER_REPLACE_TIME,
2633
MAIN_BRUSH_REPLACE_TIME,
34+
ROBOROCK_Q7_MAX,
35+
ROBOROCK_S5_MAX,
36+
ROBOROCK_S6_MAXV,
37+
ROBOROCK_S6_PURE,
38+
ROBOROCK_S7,
2739
ROBOROCK_S7_MAXV,
40+
ROBOROCK_S8_PRO_ULTRA,
2841
SENSOR_DIRTY_REPLACE_TIME,
2942
SIDE_BRUSH_REPLACE_TIME,
3043
)
@@ -126,28 +139,6 @@ class HomeDataProduct(RoborockBase):
126139
capability: Optional[int] = None
127140
category: Optional[str] = None
128141
schema: Optional[list[HomeDataProductSchema]] = None
129-
model_specification: Optional[ModelSpecification] = None
130-
131-
def __post_init__(self):
132-
if self.model not in model_specifications:
133-
_LOGGER.warning("We don't have specific device information for your model, please open an issue.")
134-
self.model_specification = model_specifications.get(ROBOROCK_S7_MAXV)
135-
else:
136-
self.model_specification = model_specifications.get(self.model)
137-
138-
139-
@dataclass
140-
class HomeDataDeviceStatus(RoborockBase):
141-
id: Optional[Any] = None
142-
name: Optional[Any] = None
143-
code: Optional[Any] = None
144-
model: Optional[str] = None
145-
icon_url: Optional[Any] = None
146-
attribute: Optional[Any] = None
147-
capability: Optional[Any] = None
148-
category: Optional[Any] = None
149-
schema: Optional[Any] = None
150-
model_specification: Optional[ModelSpecification] = None
151142

152143

153144
@dataclass
@@ -175,7 +166,7 @@ class HomeDataDevice(RoborockBase):
175166
sn: Optional[str] = None
176167
feature_set: Optional[str] = None
177168
new_feature_set: Optional[str] = None
178-
device_status: Optional[HomeDataDeviceStatus] = None
169+
device_status: Optional[dict] = None
179170
silent_ota_switch: Optional[bool] = None
180171

181172

@@ -231,14 +222,12 @@ class Status(RoborockBase):
231222
back_type: Optional[int] = None
232223
wash_phase: Optional[int] = None
233224
wash_ready: Optional[int] = None
234-
fan_power: Optional[int] = None
235-
fan_power_enum: Optional[Type[RoborockFanPowerCode]] = None
225+
fan_power: Optional[RoborockFanPowerCode] = None
236226
dnd_enabled: Optional[int] = None
237227
map_status: Optional[int] = None
238228
is_locating: Optional[int] = None
239229
lock_status: Optional[int] = None
240-
water_box_mode: Optional[int] = None
241-
water_box_mode_enum: Optional[Type[RoborockMopIntensityCode]] = None
230+
water_box_mode: Optional[RoborockMopIntensityCode] = None
242231
water_box_carriage_status: Optional[int] = None
243232
mop_forbidden_enable: Optional[int] = None
244233
camera_status: Optional[int] = None
@@ -251,8 +240,7 @@ class Status(RoborockBase):
251240
dust_collection_status: Optional[int] = None
252241
auto_dust_collection: Optional[int] = None
253242
avoid_count: Optional[int] = None
254-
mop_mode: Optional[int] = None
255-
mop_mode_enum: Optional[Type[RoborockMopModeCode]] = None
243+
mop_mode: Optional[RoborockMopModeCode] = None
256244
debug_mode: Optional[int] = None
257245
collision_avoid_status: Optional[int] = None
258246
switch_map_mode: Optional[int] = None
@@ -261,12 +249,60 @@ class Status(RoborockBase):
261249
unsave_map_reason: Optional[int] = None
262250
unsave_map_flag: Optional[int] = None
263251

264-
def update_status(self, model_specification: ModelSpecification) -> None:
265-
self.fan_power_enum = model_specification.fan_power_code.as_enum_dict()[self.fan_power]
266-
if model_specification.mop_mode_code is not None:
267-
self.mop_mode_enum = model_specification.mop_mode_code.as_enum_dict()[self.mop_mode]
268-
if model_specification.mop_intensity_code is not None:
269-
self.water_box_mode_enum = model_specification.mop_intensity_code.as_enum_dict()[self.water_box_mode]
252+
253+
@dataclass
254+
class S5MaxStatus(Status):
255+
fan_power: Optional[RoborockFanSpeedS6Pure] = None
256+
water_box_mode: Optional[RoborockMopIntensityV2] = None
257+
258+
259+
@dataclass
260+
class Q7MaxStatus(Status):
261+
fan_power: Optional[RoborockFanSpeedQ7Max] = None
262+
water_box_mode: Optional[RoborockMopIntensityV2] = None
263+
264+
265+
@dataclass
266+
class S6MaxVStatus(Status):
267+
fan_power: Optional[RoborockFanSpeedE2] = None
268+
water_box_mode: Optional[RoborockMopIntensityV2] = None
269+
270+
271+
@dataclass
272+
class S6PureStatus(Status):
273+
fan_power: Optional[RoborockFanSpeedS6Pure] = None
274+
275+
276+
@dataclass
277+
class S7MaxVStatus(Status):
278+
fan_power: Optional[RoborockFanSpeedS7MaxV] = None
279+
water_box_mode: Optional[RoborockMopIntensityS7] = None
280+
mop_mode: Optional[RoborockMopModeS7] = None
281+
282+
283+
@dataclass
284+
class S7Status(Status):
285+
fan_power: Optional[RoborockFanSpeedS7] = None
286+
water_box_mode: Optional[RoborockMopIntensityS7] = None
287+
mop_mode: Optional[RoborockMopModeS7] = None
288+
289+
290+
@dataclass
291+
class S8ProUltraStatus(Status):
292+
fan_power: Optional[RoborockFanSpeedS7MaxV] = None
293+
water_box_mode: Optional[RoborockMopIntensityS7] = None
294+
mop_mode: Optional[RoborockMopModeS8ProUltra] = None
295+
296+
297+
ModelStatus: dict[str, Type[Status]] = {
298+
ROBOROCK_S5_MAX: S5MaxStatus,
299+
ROBOROCK_Q7_MAX: Q7MaxStatus,
300+
ROBOROCK_S6_MAXV: S6MaxVStatus,
301+
ROBOROCK_S6_PURE: S6PureStatus,
302+
ROBOROCK_S7_MAXV: S7MaxVStatus,
303+
ROBOROCK_S7: S7Status,
304+
ROBOROCK_S8_PRO_ULTRA: S8ProUltraStatus,
305+
}
270306

271307

272308
@dataclass
@@ -385,7 +421,7 @@ class NetworkInfo(RoborockBase):
385421
@dataclass
386422
class RoborockDeviceInfo(RoborockBase):
387423
device: HomeDataDevice
388-
model_specification: ModelSpecification
424+
model: str
389425

390426

391427
@dataclass

tests/conftest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ def mqtt_client():
1111
user_data = UserData.from_dict(USER_DATA)
1212
home_data = HomeData.from_dict(HOME_DATA_RAW)
1313
device_info = RoborockDeviceInfo(
14-
device=home_data.devices[0], model_specification=home_data.products[0].model_specification
14+
device=home_data.devices[0],
15+
model=home_data.products[0].model,
1516
)
1617
client = RoborockMqttClient(user_data, device_info)
1718
yield client

tests/test_api.py

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
)
1313
from roborock.api import PreparedRequest, RoborockApiClient
1414
from roborock.cloud_api import RoborockMqttClient
15-
from roborock.containers import RoborockDeviceInfo, Status
15+
from roborock.containers import RoborockDeviceInfo, S7MaxVStatus
1616
from tests.mock_data import BASE_URL_REQUEST, GET_CODE_RESPONSE, HOME_DATA_RAW, STATUS, USER_DATA
1717

1818

@@ -26,9 +26,7 @@ def test_can_create_prepared_request():
2626

2727
def test_can_create_mqtt_roborock():
2828
home_data = HomeData.from_dict(HOME_DATA_RAW)
29-
device_info = RoborockDeviceInfo(
30-
device=home_data.devices[0], model_specification=home_data.products[0].model_specification
31-
)
29+
device_info = RoborockDeviceInfo(device=home_data.devices[0], model=home_data.products[0].model)
3230
RoborockMqttClient(UserData.from_dict(USER_DATA), device_info)
3331

3432

@@ -77,9 +75,7 @@ async def test_get_home_data():
7775
@pytest.mark.asyncio
7876
async def test_get_dust_collection_mode():
7977
home_data = HomeData.from_dict(HOME_DATA_RAW)
80-
device_info = RoborockDeviceInfo(
81-
device=home_data.devices[0], model_specification=home_data.products[0].model_specification
82-
)
78+
device_info = RoborockDeviceInfo(device=home_data.devices[0], model=home_data.products[0].model)
8379
rmc = RoborockMqttClient(UserData.from_dict(USER_DATA), device_info)
8480
with patch("roborock.cloud_api.RoborockMqttClient.send_command") as command:
8581
command.return_value = {"mode": 1}
@@ -91,9 +87,7 @@ async def test_get_dust_collection_mode():
9187
@pytest.mark.asyncio
9288
async def test_get_mop_wash_mode():
9389
home_data = HomeData.from_dict(HOME_DATA_RAW)
94-
device_info = RoborockDeviceInfo(
95-
device=home_data.devices[0], model_specification=home_data.products[0].model_specification
96-
)
90+
device_info = RoborockDeviceInfo(device=home_data.devices[0], model=home_data.products[0].model)
9791
rmc = RoborockMqttClient(UserData.from_dict(USER_DATA), device_info)
9892
with patch("roborock.cloud_api.RoborockMqttClient.send_command") as command:
9993
command.return_value = {"smart_wash": 0, "wash_interval": 1500}
@@ -106,9 +100,7 @@ async def test_get_mop_wash_mode():
106100
@pytest.mark.asyncio
107101
async def test_get_washing_mode():
108102
home_data = HomeData.from_dict(HOME_DATA_RAW)
109-
device_info = RoborockDeviceInfo(
110-
device=home_data.devices[0], model_specification=home_data.products[0].model_specification
111-
)
103+
device_info = RoborockDeviceInfo(device=home_data.devices[0], model=home_data.products[0].model)
112104
rmc = RoborockMqttClient(UserData.from_dict(USER_DATA), device_info)
113105
with patch("roborock.cloud_api.RoborockMqttClient.send_command") as command:
114106
command.return_value = {"wash_mode": 2}
@@ -121,15 +113,12 @@ async def test_get_washing_mode():
121113
@pytest.mark.asyncio
122114
async def test_get_prop():
123115
home_data = HomeData.from_dict(HOME_DATA_RAW)
124-
device_info = RoborockDeviceInfo(
125-
device=home_data.devices[0], model_specification=home_data.products[0].model_specification
126-
)
116+
device_info = RoborockDeviceInfo(device=home_data.devices[0], model=home_data.products[0].model)
127117
rmc = RoborockMqttClient(UserData.from_dict(USER_DATA), device_info)
128118
with patch("roborock.cloud_api.RoborockMqttClient.get_status") as get_status, patch(
129119
"roborock.cloud_api.RoborockMqttClient.send_command"
130120
), patch("roborock.cloud_api.RoborockMqttClient.get_dust_collection_mode"):
131-
status = Status.from_dict(STATUS)
132-
status.update_status(home_data.products[0].model_specification)
121+
status = S7MaxVStatus.from_dict(STATUS)
133122
status.dock_type = RoborockDockTypeCode.auto_empty_dock_pure
134123
get_status.return_value = status
135124

0 commit comments

Comments
 (0)