Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion roborock/data/b01_q7/b01_q7_code_mappings.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Self, cast

from ..code_mappings import RoborockModeEnum
from ..code_mappings import RoborockEnum, RoborockModeEnum


class WorkStatusMapping(RoborockModeEnum):
Expand Down Expand Up @@ -121,6 +121,22 @@ class WorkModeMapping(RoborockModeEnum):
EXPLORE_IDLE = ("explore_idle", 49)


class StationStateMapping(RoborockEnum):
"""Known dock activity states observed on the Q7 M5+."""

unknown = -1
idle = 0
collecting_dust = 3


class DustCollectionStateMapping(RoborockEnum):
"""Known dust collection states observed on the Q7 M5+."""

unknown = -1
idle = 0
collecting_dust = 1


class StationActionMapping(RoborockModeEnum):
"""Maps actions for the cleaning/drying station."""

Expand Down
6 changes: 4 additions & 2 deletions roborock/data/b01_q7/b01_q7_containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
CleanPathPreferenceMapping,
CleanRepeatMapping,
CleanTypeMapping,
DustCollectionStateMapping,
SCWindMapping,
StationStateMapping,
WaterLevelMapping,
WorkModeMapping,
WorkStatusMapping,
Expand Down Expand Up @@ -137,11 +139,11 @@ class B01Props(RoborockBase):
custom_type: int | None = None
sound: int | None = None
work_mode: WorkModeMapping | None = None
station_act: int | None = None # dock activity; 3 while collecting dust, 0 idle. hw-confirmed (Q7 M5+)
station_act: StationStateMapping | None = None
charge_state: int | None = None
current_map_id: int | None = None
map_num: int | None = None
dust_action: int | None = None # 1 while the dock is collecting dust; read-only (prop.set rejected, code 1)
dust_action: DustCollectionStateMapping | None = None # Read-only; prop.set is rejected with code 1 on the Q7 M5+.
quiet_is_open: int | None = None
quiet_begin_time: int | None = None
quiet_end_time: int | None = None
Expand Down
51 changes: 51 additions & 0 deletions tests/data/b01_q7/test_b01_q7_containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
CleanRecordDetail,
CleanRecordList,
CleanRepeatMapping,
DustCollectionStateMapping,
SCWindMapping,
StationStateMapping,
WorkStatusMapping,
)
from roborock.data.code_mappings import completed_warnings
Expand Down Expand Up @@ -179,6 +181,55 @@ def test_b01props_deserialization_unknown_work_status(caplog: pytest.LogCaptureF
assert "Failed to convert status" not in caplog.text


@pytest.mark.parametrize("station_key,dust_key", [("station_act", "dust_action"), ("stationAct", "dustAction")])
@pytest.mark.parametrize(
("station_code", "dust_code", "expected_station", "expected_dust"),
[
(0, 0, StationStateMapping.idle, DustCollectionStateMapping.idle),
(3, 1, StationStateMapping.collecting_dust, DustCollectionStateMapping.collecting_dust),
],
)
def test_b01props_dock_states(
station_key: str,
dust_key: str,
station_code: int,
dust_code: int,
expected_station: StationStateMapping,
expected_dust: DustCollectionStateMapping,
) -> None:
"""Decode dock states and preserve integer values when serializing them."""
props = B01Props.from_dict({station_key: station_code, dust_key: dust_code})

assert props.station_act is expected_station
assert props.dust_action is expected_dust
serialized = json.loads(json.dumps(props.as_dict()))
assert serialized == {"stationAct": station_code, "dustAction": dust_code}
restored = B01Props.from_dict(serialized)
assert restored.station_act is expected_station
assert restored.dust_action is expected_dust


@pytest.mark.parametrize("station_code,dust_code", [(-1, -1), (1, 2), (999, 999)])
def test_b01props_unknown_dock_states(station_code: int, dust_code: int, caplog: pytest.LogCaptureFixture) -> None:
"""Unknown dock states must not appear idle or discard other properties."""
props = B01Props.from_dict({"station_act": station_code, "dust_action": dust_code, "quantity": 87})

assert props.station_act is StationStateMapping.unknown
assert props.dust_action is DustCollectionStateMapping.unknown
assert props.quantity == 87
assert "Failed to convert" not in caplog.text


@pytest.mark.parametrize("payload", [{}, {"station_act": None, "dust_action": None}])
def test_b01props_missing_dock_states(payload: dict[str, None]) -> None:
"""Missing dock states stay absent rather than becoming idle or unknown."""
props = B01Props.from_dict(payload)

assert props.station_act is None
assert props.dust_action is None
assert props.as_dict() == {}


def test_b01_q7_clean_record_list_parses_detail_fields():
payload = {
"total_time": 34980,
Expand Down
17 changes: 16 additions & 1 deletion tests/devices/traits/b01/q7/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
from roborock.data.b01_q7 import (
CleanTaskTypeMapping,
CleanTypeMapping,
DustCollectionStateMapping,
SCDeviceCleanParam,
SCWindMapping,
StationStateMapping,
WaterLevelMapping,
WorkStatusMapping,
)
Expand All @@ -23,6 +25,8 @@ async def test_q7_api_query_values(q7_api: Q7PropertiesApi, fake_channel: FakeQ7
"status": 1,
"wind": 2,
"battery": 100,
"station_act": 3,
"dust_action": 1,
}

fake_channel.response_queue.append(response_data)
Expand All @@ -31,17 +35,28 @@ async def test_q7_api_query_values(q7_api: Q7PropertiesApi, fake_channel: FakeQ7
[
RoborockB01Props.STATUS,
RoborockB01Props.WIND,
RoborockB01Props.STATION_ACT,
RoborockB01Props.DUST_ACTION,
]
)

assert result is not None
assert result.status == WorkStatusMapping.WAITING_FOR_ORDERS
assert result.wind == SCWindMapping.STANDARD
assert result.station_act is StationStateMapping.collecting_dust
assert result.dust_action is DustCollectionStateMapping.collecting_dust

assert len(fake_channel.published_commands) == 1
command, params = fake_channel.published_commands[0]
assert command == RoborockB01Q7Methods.GET_PROP
assert params == {"property": [RoborockB01Props.STATUS, RoborockB01Props.WIND]}
assert params == {
"property": [
RoborockB01Props.STATUS,
RoborockB01Props.WIND,
RoborockB01Props.STATION_ACT,
RoborockB01Props.DUST_ACTION,
]
}


@pytest.mark.parametrize(
Expand Down
Loading