Skip to content

Commit 0a23de7

Browse files
committed
refactor: expose raw status and app_init dataclasses on simulator for direct test manipulation
1 parent eb5578b commit 0a23de7

2 files changed

Lines changed: 193 additions & 86 deletions

File tree

roborock/testing/v1_simulator.py

Lines changed: 176 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import logging
99
import time
1010
from collections.abc import Callable
11-
from dataclasses import asdict
11+
from dataclasses import asdict, replace
1212
from enum import Enum
1313
from typing import Any
1414
from unittest.mock import Mock
@@ -50,6 +50,70 @@ def _serialize_dataclass(obj: Any) -> dict[str, Any]:
5050
return {k: (v.value if isinstance(v, Enum) else v) for k, v in asdict(obj).items() if v is not None}
5151

5252

53+
DEFAULT_STATUS = StatusV2(
54+
msg_ver=2,
55+
msg_seq=458,
56+
state=RoborockStateCode.charging,
57+
battery=100,
58+
clean_time=1176,
59+
clean_area=20965000,
60+
error_code=RoborockErrorCode(0),
61+
map_present=1,
62+
in_cleaning=RoborockInCleaning.complete,
63+
in_returning=0,
64+
in_fresh_state=1,
65+
lab_status=1,
66+
water_box_status=1,
67+
back_type=-1,
68+
wash_phase=0,
69+
wash_ready=0,
70+
fan_power=102,
71+
dnd_enabled=0,
72+
map_status=3,
73+
is_locating=0,
74+
lock_status=0,
75+
water_box_mode=200,
76+
water_box_carriage_status=1,
77+
mop_forbidden_enable=1,
78+
camera_status=3457,
79+
is_exploring=0,
80+
home_sec_status=0,
81+
home_sec_enable_password=0,
82+
adbumper_status=[0, 0, 0],
83+
water_shortage_status=0,
84+
dock_type=RoborockDockTypeCode.s8_dock,
85+
dust_collection_status=0,
86+
auto_dust_collection=1,
87+
avoid_count=19,
88+
mop_mode=300,
89+
debug_mode=0,
90+
collision_avoid_status=1,
91+
switch_map_mode=0,
92+
dock_error_status=RoborockDockErrorCode(0),
93+
charge_status=RoborockChargeStatus.charge_waiting,
94+
unsave_map_reason=0,
95+
unsave_map_flag=0,
96+
dss=169,
97+
)
98+
99+
DEFAULT_APP_INIT = AppInitStatus(
100+
local_info=AppInitStatusLocalInfo(
101+
location="us",
102+
bom="A.03.0069",
103+
featureset=1,
104+
language="en",
105+
logserver="awsusor0.fds.api.xiaomi.com",
106+
wifiplan="0x39",
107+
timezone="US/Pacific",
108+
name="custom_A.03.0069_FCC",
109+
),
110+
feature_info=[111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 122, 123, 124, 125],
111+
new_feature_info=633887780925447,
112+
new_feature_info_str="0000000000002000",
113+
new_feature_info_2=8192,
114+
)
115+
116+
53117
class V1VacuumSimulator(RoborockDeviceSimulator):
54118
"""Firmware simulator for a V1/L01 vacuum device.
55119
@@ -66,7 +130,7 @@ def __init__(
66130
self,
67131
duid: str = "fake_duid",
68132
battery: int = 100,
69-
state: int = RoborockStateCode.charging,
133+
state: RoborockStateCode | int = RoborockStateCode.charging,
70134
fan_power: int = 102, # balanced
71135
dnd_enabled: int = 0,
72136
mop_mode: int = 300,
@@ -75,18 +139,22 @@ def __init__(
75139
device_info: HomeDataDevice | None = None,
76140
product: HomeDataProduct | None = None,
77141
dss: int = 169,
78-
dock_type: int = 3,
142+
dock_type: RoborockDockTypeCode | int = 3,
79143
):
80144
super().__init__(duid=duid, device_info=device_info, product=product)
81-
self.battery = battery
82-
self.state = state
83-
self.fan_power = fan_power
84-
self.dnd_enabled = dnd_enabled
85-
self.mop_mode = mop_mode
86-
self.water_box_mode = water_box_mode
145+
self.status = replace(DEFAULT_STATUS)
146+
self.app_init = replace(DEFAULT_APP_INIT)
147+
self.app_init.local_info = replace(DEFAULT_APP_INIT.local_info)
148+
149+
self.status.battery = battery
150+
self.status.state = RoborockStateCode(state)
151+
self.status.fan_power = fan_power
152+
self.status.dnd_enabled = dnd_enabled
153+
self.status.mop_mode = mop_mode
154+
self.status.water_box_mode = water_box_mode
155+
self.status.dss = dss
156+
self.status.dock_type = RoborockDockTypeCode(dock_type)
87157
self.custom_handlers = custom_handlers or {}
88-
self.dss = dss
89-
self.dock_type = dock_type
90158

91159
self.network_info = NetworkInfo(
92160
ip="1.1.1.1",
@@ -174,105 +242,143 @@ def v1_channel(self) -> V1Channel:
174242
"""Returns the real V1Channel bound to the fake channels."""
175243
return self._v1_channel
176244

245+
@property
246+
def battery(self) -> int:
247+
"""Get battery level."""
248+
return self.status.battery or 0
249+
250+
@battery.setter
251+
def battery(self, value: int) -> None:
252+
"""Set battery level."""
253+
self.status.battery = value
254+
255+
@property
256+
def state(self) -> RoborockStateCode:
257+
"""Get device state code."""
258+
return self.status.state or RoborockStateCode.charging
259+
260+
@state.setter
261+
def state(self, value: RoborockStateCode | int) -> None:
262+
"""Set device state code."""
263+
self.status.state = RoborockStateCode(value)
264+
265+
@property
266+
def fan_power(self) -> int:
267+
"""Get fan power speed."""
268+
return self.status.fan_power or 0
269+
270+
@fan_power.setter
271+
def fan_power(self, value: int) -> None:
272+
"""Set fan power speed."""
273+
self.status.fan_power = value
274+
275+
@property
276+
def dnd_enabled(self) -> int:
277+
"""Get DND enabled state."""
278+
return self.status.dnd_enabled or 0
279+
280+
@dnd_enabled.setter
281+
def dnd_enabled(self, value: int) -> None:
282+
"""Set DND enabled state."""
283+
self.status.dnd_enabled = value
284+
285+
@property
286+
def mop_mode(self) -> int:
287+
"""Get mop route mode."""
288+
return self.status.mop_mode or 0
289+
290+
@mop_mode.setter
291+
def mop_mode(self, value: int) -> None:
292+
"""Set mop route mode."""
293+
self.status.mop_mode = value
294+
295+
@property
296+
def water_box_mode(self) -> int:
297+
"""Get water box mode."""
298+
return self.status.water_box_mode or 0
299+
300+
@water_box_mode.setter
301+
def water_box_mode(self, value: int) -> None:
302+
"""Set water box mode."""
303+
self.status.water_box_mode = value
304+
305+
@property
306+
def dss(self) -> int:
307+
"""Get dock sensor status."""
308+
return self.status.dss or 0
309+
310+
@dss.setter
311+
def dss(self, value: int) -> None:
312+
"""Set dock sensor status."""
313+
self.status.dss = value
314+
315+
@property
316+
def dock_type(self) -> RoborockDockTypeCode:
317+
"""Get dock type."""
318+
return self.status.dock_type or RoborockDockTypeCode.no_dock
319+
320+
@dock_type.setter
321+
def dock_type(self, value: RoborockDockTypeCode | int) -> None:
322+
"""Set dock type."""
323+
self.status.dock_type = RoborockDockTypeCode(value)
324+
177325
@property
178326
def in_cleaning(self) -> RoborockInCleaning:
179327
"""Return global_clean_not_complete if cleaning, else complete."""
180328
return (
181329
RoborockInCleaning.global_clean_not_complete
182-
if self.state == RoborockStateCode.cleaning
330+
if self.status.state == RoborockStateCode.cleaning
183331
else RoborockInCleaning.complete
184332
)
185333

186334
@property
187335
def in_returning(self) -> int:
188336
"""Return 1 if returning, else 0."""
189-
return 1 if self.state == RoborockStateCode.returning_home else 0
337+
return 1 if self.status.state == RoborockStateCode.returning_home else 0
190338

191339
@property
192340
def charge_status(self) -> RoborockChargeStatus:
193341
"""Return charging if charging, else charge_waiting."""
194342
return (
195343
RoborockChargeStatus.charging
196-
if self.state == RoborockStateCode.charging
344+
if self.status.state == RoborockStateCode.charging
197345
else RoborockChargeStatus.charge_waiting
198346
)
199347

200348
def get_status_dict(self) -> dict[str, Any]:
201349
"""Generate status dict using the current simulated state."""
202-
status = StatusV2(
203-
msg_ver=2,
204-
msg_seq=458,
205-
state=RoborockStateCode(self.state),
206-
battery=self.battery,
207-
clean_time=1176,
208-
clean_area=20965000,
209-
error_code=RoborockErrorCode(0),
210-
map_present=1,
211-
in_cleaning=self.in_cleaning,
212-
in_returning=self.in_returning,
213-
in_fresh_state=1,
214-
lab_status=1,
215-
water_box_status=1,
216-
back_type=-1,
217-
wash_phase=0,
218-
wash_ready=0,
219-
fan_power=self.fan_power,
220-
dnd_enabled=self.dnd_enabled,
221-
map_status=3,
222-
is_locating=0,
223-
lock_status=0,
224-
water_box_mode=self.water_box_mode,
225-
water_box_carriage_status=1,
226-
mop_forbidden_enable=1,
227-
camera_status=3457,
228-
is_exploring=0,
229-
home_sec_status=0,
230-
home_sec_enable_password=0,
231-
adbumper_status=[0, 0, 0],
232-
water_shortage_status=0,
233-
dock_type=RoborockDockTypeCode(self.dock_type),
234-
dust_collection_status=0,
235-
auto_dust_collection=1,
236-
avoid_count=19,
237-
mop_mode=self.mop_mode,
238-
debug_mode=0,
239-
collision_avoid_status=1,
240-
switch_map_mode=0,
241-
dock_error_status=RoborockDockErrorCode(0),
242-
charge_status=self.charge_status,
243-
unsave_map_reason=0,
244-
unsave_map_flag=0,
245-
dss=self.dss,
246-
)
247-
return _serialize_dataclass(status)
350+
self.status.in_cleaning = self.in_cleaning
351+
self.status.in_returning = self.in_returning
352+
self.status.charge_status = self.charge_status
353+
return _serialize_dataclass(self.status)
248354

249355
def _handle_app_start(self, params: Any) -> str:
250-
self.state = RoborockStateCode.cleaning
356+
self.status.state = RoborockStateCode.cleaning
251357
return "ok"
252358

253359
def _handle_app_stop(self, params: Any) -> str:
254-
self.state = RoborockStateCode.paused
360+
self.status.state = RoborockStateCode.paused
255361
return "ok"
256362

257363
def _handle_app_charge(self, params: Any) -> str:
258-
self.state = RoborockStateCode.returning_home
364+
self.status.state = RoborockStateCode.returning_home
259365
return "ok"
260366

261367
def _handle_set_custom_mode(self, params: Any) -> str:
262368
if isinstance(params, list) and len(params) > 0:
263-
self.fan_power = params[0]
369+
self.status.fan_power = params[0]
264370
elif isinstance(params, dict):
265-
self.fan_power = params.get("fan_power", self.fan_power)
371+
self.status.fan_power = params.get("fan_power", self.status.fan_power)
266372
return "ok"
267373

268374
def _handle_set_mop_mode(self, params: Any) -> str:
269375
if isinstance(params, list) and len(params) > 0:
270-
self.mop_mode = params[0]
376+
self.status.mop_mode = params[0]
271377
return "ok"
272378

273379
def _handle_set_water_box_custom_mode(self, params: Any) -> str:
274380
if isinstance(params, list) and len(params) > 0:
275-
self.water_box_mode = params[0]
381+
self.status.water_box_mode = params[0]
276382
return "ok"
277383

278384
def _handle_reset_consumable(self, params: Any) -> str:
@@ -305,8 +411,8 @@ def _handle_app_get_init_status(self, params: Any) -> list[dict[str, Any]]:
305411
payload["new_feature_info2"] = payload.pop("new_feature_info_2")
306412

307413
payload["status_info"] = {
308-
"state": self.state,
309-
"battery": self.battery,
414+
"state": self.status.state.value if self.status.state else 0,
415+
"battery": self.status.battery,
310416
"clean_time": 5610,
311417
"clean_area": 96490000,
312418
"error_code": 0,
@@ -318,7 +424,7 @@ def _handle_app_get_init_status(self, params: Any) -> list[dict[str, Any]]:
318424
"map_status": 3,
319425
"is_locating": 0,
320426
"lock_status": 0,
321-
"water_box_mode": self.water_box_mode,
427+
"water_box_mode": self.status.water_box_mode,
322428
"distance_off": 0,
323429
"water_box_carriage_status": 0,
324430
"mop_forbidden_enable": 0,

0 commit comments

Comments
 (0)