Compare commits

...

2 Commits

Author SHA1 Message Date
Ludovic BOUÉ 520d18fbac feat: add room/segment cleaning support for Roborock Q10 (B01/ss07)
Adds `CLEAN_AREA` support to `RoborockQ10Vacuum` using
`VacuumTrait.clean_segments` introduced in python-roborock 5.22.0.
Room ids are sourced from the push-driven `MapContentTrait` already
available on `Q10PropertiesApi`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 21:02:17 +02:00
Ludovic BOUÉ efed17dd44 Bump python-roborock to 5.22.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 21:01:18 +02:00
6 changed files with 133 additions and 4 deletions
@@ -20,7 +20,7 @@
"loggers": ["roborock"],
"quality_scale": "silver",
"requirements": [
"python-roborock==5.21.0",
"python-roborock==5.22.0",
"vacuum-map-parser-roborock==0.1.5"
]
}
@@ -562,6 +562,7 @@ class RoborockQ10Vacuum(RoborockCoordinatedEntityB01Q10, StateVacuumEntity):
| VacuumEntityFeature.LOCATE
| VacuumEntityFeature.STATE
| VacuumEntityFeature.START
| VacuumEntityFeature.CLEAN_AREA
)
_attr_translation_key = DOMAIN
_attr_name = None
@@ -699,6 +700,30 @@ class RoborockQ10Vacuum(RoborockCoordinatedEntityB01Q10, StateVacuumEntity):
},
) from err
@override
async def async_get_segments(self) -> list[Segment]:
"""Get the segments that can be cleaned."""
return [
Segment(id=str(room.id), name=room.name)
for room in self.coordinator.api.map.rooms
]
@override
async def async_clean_segments(self, segment_ids: list[str], **kwargs: Any) -> None:
"""Clean the specified segments."""
try:
await self.coordinator.api.vacuum.clean_segments(
[int(seg_id) for seg_id in segment_ids]
)
except RoborockException as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="command_failed",
translation_placeholders={
"command": "clean_segments",
},
) from err
@override
async def async_send_command(
self,
+1 -1
View File
@@ -2721,7 +2721,7 @@ python-rabbitair==0.0.8
python-ripple-api==0.0.3
# homeassistant.components.roborock
python-roborock==5.21.0
python-roborock==5.22.0
# homeassistant.components.smarttub
python-smarttub==0.0.47
+6
View File
@@ -57,6 +57,7 @@ from roborock.devices.traits.v1.valley_electricity_timer import (
)
from roborock.devices.traits.v1.volume import SoundVolumeTrait
from roborock.devices.traits.v1.wash_towel_mode import WashTowelModeTrait
from roborock.map.b01_q10_map_parser import Q10Room
from roborock.roborock_message import RoborockDyadDataProtocol, RoborockZeoProtocol
from homeassistant.components.roborock.const import (
@@ -212,6 +213,11 @@ def create_b01_q10_trait() -> Mock:
[cb() for cb in _dnd_listeners],
)
)
q10_trait.map = Mock()
q10_trait.map.rooms = [
Q10Room(id=9, raw_name="rr_bedroom", pixel_value=36, pixel_count=100),
Q10Room(id=10, raw_name="rr_living_room", pixel_value=40, pixel_count=200),
]
return q10_trait
@@ -65,7 +65,7 @@
'platform': 'roborock',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <VacuumEntityFeature: 13116>,
'supported_features': <VacuumEntityFeature: 29500>,
'translation_key': 'roborock',
'unique_id': 'q10_duid',
'unit_of_measurement': None,
@@ -84,7 +84,7 @@
'max_plus',
]),
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Roborock Q10 S5+',
<EntityStateAttribute.SUPPORTED_FEATURES: 'supported_features'>: <VacuumEntityFeature: 13116>,
<EntityStateAttribute.SUPPORTED_FEATURES: 'supported_features'>: <VacuumEntityFeature: 29500>,
}),
'context': <ANY>,
'entity_id': 'vacuum.roborock_q10_s5',
+98
View File
@@ -814,6 +814,7 @@ def fake_q10_vacuum_api_fixture(
api.vacuum.stop_clean.side_effect = send_message_exception
api.vacuum.return_to_dock.side_effect = send_message_exception
api.vacuum.set_fan_level.side_effect = send_message_exception
api.vacuum.clean_segments.side_effect = send_message_exception
api.command.send.side_effect = send_message_exception
return api
@@ -1069,3 +1070,100 @@ async def test_q10_ha_refresh(
# Verify that refresh was called
fake_q10_vacuum.b01_q10_properties.refresh.assert_called()
async def test_q10_get_segments(
hass: HomeAssistant,
setup_entry: MockConfigEntry,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test that async_get_segments returns rooms from the Q10 map."""
client = await hass_ws_client(hass)
await client.send_json_auto_id(
{"type": "vacuum/get_segments", "entity_id": Q10_ENTITY_ID}
)
msg = await client.receive_json()
assert msg["success"]
assert msg["result"] == {
"segments": [
{"id": "9", "name": "Bedroom", "group": None},
{"id": "10", "name": "Living Room", "group": None},
]
}
async def test_q10_get_segments_no_rooms(
hass: HomeAssistant,
setup_entry: MockConfigEntry,
fake_q10_vacuum: FakeDevice,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test that async_get_segments returns empty list when no map has been received."""
assert fake_q10_vacuum.b01_q10_properties is not None
fake_q10_vacuum.b01_q10_properties.map.rooms = []
client = await hass_ws_client(hass)
await client.send_json_auto_id(
{"type": "vacuum/get_segments", "entity_id": Q10_ENTITY_ID}
)
msg = await client.receive_json()
assert msg["success"]
assert msg["result"] == {"segments": []}
async def test_q10_clean_segments(
hass: HomeAssistant,
setup_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
q10_vacuum_api: Mock,
) -> None:
"""Test that clean_area service calls clean_segments with the correct room ids."""
entity_registry.async_update_entity_options(
Q10_ENTITY_ID,
VACUUM_DOMAIN,
{
"area_mapping": {"bedroom": ["9"]},
"last_seen_segments": [
{"id": "9", "name": "Bedroom", "group": None},
{"id": "10", "name": "Living Room", "group": None},
],
},
)
await hass.services.async_call(
VACUUM_DOMAIN,
SERVICE_CLEAN_AREA,
{ATTR_ENTITY_ID: Q10_ENTITY_ID, "cleaning_area_id": ["bedroom"]},
blocking=True,
)
assert q10_vacuum_api.vacuum.clean_segments.call_count == 1
assert q10_vacuum_api.vacuum.clean_segments.call_args == call([9])
@pytest.mark.parametrize("send_message_exception", [RoborockException()])
async def test_q10_clean_segments_failed(
hass: HomeAssistant,
setup_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
q10_vacuum_api: Mock,
) -> None:
"""Test that a clean_segments failure raises HomeAssistantError."""
entity_registry.async_update_entity_options(
Q10_ENTITY_ID,
VACUUM_DOMAIN,
{
"area_mapping": {"bedroom": ["9"]},
"last_seen_segments": [
{"id": "9", "name": "Bedroom", "group": None},
],
},
)
with pytest.raises(HomeAssistantError):
await hass.services.async_call(
VACUUM_DOMAIN,
SERVICE_CLEAN_AREA,
{ATTR_ENTITY_ID: Q10_ENTITY_ID, "cleaning_area_id": ["bedroom"]},
blocking=True,
)