Skip to content

Commit 4c97af9

Browse files
Andrew YeatsAndrew Yeats
authored andcommitted
refactor: type Q10Status.fault as YXFault, mirroring the Q7 fault field
Retype the fault field from a raw int to YXFault | None and make fault_name a str accessor, matching B01Props.fault / fault_name on the Q7. Update the enum provenance notes from live hardware confirmation (10 codes) and drop the unconfirmed 569 label.
1 parent bad5564 commit 4c97af9

4 files changed

Lines changed: 42 additions & 41 deletions

File tree

roborock/data/b01_q10/b01_q10_code_mappings.py

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -294,25 +294,24 @@ class YXFault(RoborockModeEnum):
294294
295295
``dpFault`` is *overloaded*: several values are lifecycle/status rather than
296296
errors (e.g. 400 = scheduled clean starting, 501 = returning to dock,
297-
502 = recharge). A non-zero fault is only a *blocking* error when the app
298-
routes the code into its ``D_ErrorCode`` bucket; alert/offline codes are softer.
297+
502 = recharge). A non-zero fault is not necessarily a blocking error.
299298
300299
These labels follow the ss07 app text, which differs from the Q7 ``B01Fault``
301-
for several shared numbers (500, 501, 503, 569, 570) -- so this is a
302-
Q10-specific map, not a reuse of ``B01Fault``. Codes marked "seen live" were
303-
observed as raw ``dpFault`` values on a physical ss07; the rest are
304-
app-sourced only (n=1, not hardware-confirmed).
300+
for several shared numbers (500, 501, 503, 570) -- so this is a Q10-specific
301+
map, not a reuse of ``B01Fault``. Codes marked "hw-confirmed" were observed
302+
firing live on a physical ss07 in a context matching the label; the rest are
303+
app-sourced only (single device, single app version).
305304
"""
306305

307306
UNKNOWN = "unknown", -1
308307
NONE = "none", 0
309308
LIDAR_BLOCKED = "lidar_blocked", 1
310309
BUMPER_STUCK = "bumper_stuck", 2
311-
ROBOT_SUSPENDED = "robot_suspended", 3
312-
CLIFF_SENSOR_ERROR = "cliff_sensor_error", 4
310+
ROBOT_SUSPENDED = "robot_suspended", 3 # hw-confirmed (wheels lifted off the floor mid-task)
311+
CLIFF_SENSOR_ERROR = "cliff_sensor_error", 4 # hw-confirmed (one side lifted mid-task)
313312
MAIN_BRUSH_STUCK = "main_brush_stuck", 5
314313
MAIN_WHEELS_STUCK = "main_wheels_stuck", 7
315-
ROBOT_TRAPPED = "robot_trapped", 8 # seen live
314+
ROBOT_TRAPPED = "robot_trapped", 8 # hw-confirmed
316315
CHECK_DUSTBIN_FILTER = "check_dustbin_filter", 9
317316
LOW_BATTERY = "low_battery", 12
318317
TEMPERATURE_THRESHOLD = "temperature_threshold", 14
@@ -328,17 +327,16 @@ class YXFault(RoborockModeEnum):
328327
FILTER_SERVICE_LIFE = "filter_service_life", 301
329328
MAIN_BRUSH_SERVICE_LIFE = "main_brush_service_life", 302
330329
SIDE_BRUSH_SERVICE_LIFE = "side_brush_service_life", 303
331-
SENSOR_NEEDS_CLEANING = "sensor_needs_cleaning", 304
332-
DUST_BAG_FULL = "dust_bag_full", 310
333-
STARTING_SCHEDULED_CLEAN = "starting_scheduled_clean", 400 # seen live; lifecycle, not an error
330+
SENSOR_NEEDS_CLEANING = "sensor_needs_cleaning", 304 # hw-confirmed (fired during auto-empty)
331+
DUST_BAG_FULL = "dust_bag_full", 310 # inferred from auto-empty context; not hw-confirmed
332+
STARTING_SCHEDULED_CLEAN = "starting_scheduled_clean", 400 # hw-confirmed x3; lifecycle, not an error
334333
CLEANING_IN_PROGRESS = "cleaning_in_progress", 407
335-
EMPTY_DUSTBIN = "empty_dustbin", 500 # ss07 != Q7 B01Fault (lidar_blocked)
336-
CLEANING_COMPLETED_RETURNING = "cleaning_completed_returning", 501 # ss07 != Q7 (robot_suspended)
337-
LOW_BATTERY_RESUME = "low_battery_resume", 502
338-
DOCKING_ERROR = "docking_error", 503 # seen live; ss07 != Q7 (dustbin_not_installed)
339-
POSITIONING_FAILED = "positioning_failed", 556 # seen live; relocalization
340-
TOO_FREQUENT_EMPTYING = "too_frequent_emptying", 569 # ss07 != Q7 (main_wheels_entangled)
341-
CANNOT_REACH_TARGET = "cannot_reach_target", 570 # seen live; ss07 != Q7 (main_brush_entangled)
334+
EMPTY_DUSTBIN = "empty_dustbin", 500 # ss07 != Q7 B01Fault (lidar_blocked); app-sourced
335+
CLEANING_COMPLETED_RETURNING = "cleaning_completed_returning", 501 # hw-confirmed; ss07 != Q7 (robot_suspended)
336+
LOW_BATTERY_RESUME = "low_battery_resume", 502 # hw-confirmed; lifecycle
337+
DOCKING_ERROR = "docking_error", 503 # hw-confirmed; ss07 != Q7 (dustbin_not_installed)
338+
POSITIONING_FAILED = "positioning_failed", 556 # hw-confirmed; relocalization
339+
CANNOT_REACH_TARGET = "cannot_reach_target", 570 # hw-confirmed; ss07 != Q7 (main_brush_entangled)
342340
OFFLINE_WARNING_ASLEEP = "offline_warning_asleep", 588
343341
OFFLINE_WARNING_LOW_BATTERY = "offline_warning_low_battery", 589
344342
DND_AUTO_TOPUP_DISABLED = "dnd_auto_topup_disabled", 591

roborock/data/b01_q10/b01_q10_containers.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ class Q10Status(RoborockBase):
166166
clean_task_type: YXDeviceCleanTask | None = field(default=None, metadata={"dps": B01_Q10_DP.CLEAN_TASK_TYPE})
167167
back_type: YXBackType | None = field(default=None, metadata={"dps": B01_Q10_DP.BACK_TYPE})
168168
cleaning_progress: int | None = field(default=None, metadata={"dps": B01_Q10_DP.CLEAN_PROGRESS})
169-
fault: int | None = field(default=None, metadata={"dps": B01_Q10_DP.FAULT})
169+
fault: YXFault | None = field(default=None, metadata={"dps": B01_Q10_DP.FAULT})
170170

171171
# Additional state reported in the device's full status dump.
172172
clean_line: YXCleanLine | None = field(default=None, metadata={"dps": B01_Q10_DP.CLEAN_LINE})
@@ -205,17 +205,9 @@ class Q10Status(RoborockBase):
205205
sensor_life: int | None = field(default=None, metadata={"dps": B01_Q10_DP.SENSOR_LIFE})
206206

207207
@property
208-
def fault_name(self) -> YXFault | None:
209-
"""Decoded ``dpFault`` label, or ``None`` for an unmapped code.
210-
211-
Additive: ``fault`` keeps the raw integer so unknown/new codes are never
212-
lost; this is the decoded companion. ``dpFault`` is overloaded (see
213-
:class:`~roborock.data.b01_q10.YXFault`), so a non-``NONE`` value is not
214-
necessarily an error.
215-
"""
216-
if self.fault is None:
217-
return None
218-
return YXFault.from_code_optional(self.fault)
208+
def fault_name(self) -> str | None:
209+
"""Returns the name of the current fault."""
210+
return self.fault.value if self.fault is not None else None
219211

220212

221213
@dataclass

tests/data/b01_q10/test_b01_q10_code_mappings.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,24 @@ def test_q10_fault_codes_map_to_ss07_labels(code: int, expected: YXFault) -> Non
4949
assert YXFault.from_code(code) is expected
5050

5151

52-
def test_q10_fault_name_is_additive_and_preserves_raw_int() -> None:
53-
"""``fault_name`` decodes the label without ever losing the raw ``fault`` int."""
54-
status = Q10Status(fault=570)
55-
assert status.fault_name is YXFault.CANNOT_REACH_TARGET
56-
assert status.fault == 570
57-
58-
unmapped = Q10Status(fault=99999)
59-
assert unmapped.fault_name is None # unknown code -> None, no crash
60-
assert unmapped.fault == 99999
52+
def test_q10_status_fault_field_and_name() -> None:
53+
"""``fault`` is a typed ``YXFault`` field; ``fault_name`` mirrors the Q7 accessor."""
54+
status = Q10Status(fault=YXFault.CANNOT_REACH_TARGET)
55+
assert status.fault is YXFault.CANNOT_REACH_TARGET
56+
assert status.fault_name == "cannot_reach_target"
6157

6258
assert Q10Status(fault=None).fault_name is None
59+
60+
61+
def test_q10_status_fault_decodes_from_dict() -> None:
62+
"""A raw dpFault int decodes to the enum through the standard conversion path."""
63+
status = Q10Status.from_dict({"fault": 503})
64+
assert status is not None
65+
assert status.fault is YXFault.DOCKING_ERROR
66+
67+
# An unmapped code follows the library-wide enum-field behavior:
68+
# the failed conversion is logged and the field is left None.
69+
unmapped = Q10Status.from_dict({"fault": 99999})
70+
assert unmapped is not None
71+
assert unmapped.fault is None
72+
assert unmapped.fault_name is None

tests/devices/traits/b01/q10/test_status.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
YXDeviceDustCollectionFrequency,
1919
YXDeviceState,
2020
YXFanLevel,
21+
YXFault,
2122
YXWaterLevel,
2223
)
2324
from roborock.data.b01_q10.b01_q10_containers import dpNetInfo, dpNotDisturbExpand, dpTimeZone
@@ -150,7 +151,7 @@ async def test_status_trait_refresh(
150151
assert q10_api.consumable.filter_life == 0
151152
assert q10_api.consumable.sensor_life == 0
152153
assert q10_api.status.cleaning_progress == 100
153-
assert q10_api.status.fault == 0
154+
assert q10_api.status.fault is YXFault.NONE
154155
assert q10_api.status.clean_mode == YXCleanType.VAC_AND_MOP
155156
assert q10_api.status.water_level == YXWaterLevel.LOW
156157

0 commit comments

Comments
 (0)