Separate Roborock entities to a new dock device (#140612)

* Seperate entities to a new dock device

* update entity names

* Update homeassistant/components/roborock/coordinator.py

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
Luke Lashley 2025-03-14 22:06:09 -04:00 committed by GitHub
parent ed2ef04b98
commit baafcf48dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 51 additions and 7 deletions

View File

@ -26,6 +26,8 @@ class RoborockBinarySensorDescription(BinarySensorEntityDescription):
"""A class that describes Roborock binary sensors."""
value_fn: Callable[[DeviceProp], bool | int | None]
# If it is a dock entity
is_dock_entity: bool = False
BINARY_SENSOR_DESCRIPTIONS = [
@ -35,6 +37,7 @@ BINARY_SENSOR_DESCRIPTIONS = [
device_class=BinarySensorDeviceClass.RUNNING,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda data: data.status.dry_status,
is_dock_entity=True,
),
RoborockBinarySensorDescription(
key="water_box_carriage_status",
@ -105,6 +108,7 @@ class RoborockBinarySensorEntity(RoborockCoordinatedEntityV1, BinarySensorEntity
super().__init__(
f"{description.key}_{coordinator.duid_slug}",
coordinator,
is_dock_entity=description.is_dock_entity,
)
self.entity_description = description

View File

@ -128,6 +128,23 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
self._api_client = api_client
self._is_cloud_api = False
@cached_property
def dock_device_info(self) -> DeviceInfo:
"""Gets the device info for the dock.
This must happen after the coordinator does the first update.
Which will be the case when this is called.
"""
dock_type = self.roborock_device_info.props.status.dock_type
return DeviceInfo(
name=f"{self.roborock_device_info.device.name} Dock",
identifiers={(DOMAIN, f"{self.duid}_dock")},
manufacturer="Roborock",
model=f"{self.roborock_device_info.product.model} Dock",
model_id=str(dock_type.value) if dock_type is not None else "Unknown",
sw_version=self.roborock_device_info.device.fv,
)
async def _async_setup(self) -> None:
"""Set up the coordinator."""
# Verify we can communicate locally - if we can't, switch to cloud api

View File

@ -121,12 +121,15 @@ class RoborockCoordinatedEntityV1(
listener_request: list[RoborockDataProtocol]
| RoborockDataProtocol
| None = None,
is_dock_entity: bool = False,
) -> None:
"""Initialize the coordinated Roborock Device."""
RoborockEntityV1.__init__(
self,
unique_id=unique_id,
device_info=coordinator.device_info,
device_info=coordinator.device_info
if not is_dock_entity
else coordinator.dock_device_info,
api=coordinator.api,
)
CoordinatorEntity.__init__(self, coordinator=coordinator)

View File

@ -32,6 +32,8 @@ class RoborockSelectDescription(SelectEntityDescription):
parameter_lambda: Callable[[str, DeviceProp], list[int]]
protocol_listener: RoborockDataProtocol | None = None
# If it is a dock entity
is_dock_entity: bool = False
SELECT_DESCRIPTIONS: list[RoborockSelectDescription] = [
@ -70,6 +72,7 @@ SELECT_DESCRIPTIONS: list[RoborockSelectDescription] = [
parameter_lambda=lambda key, _: [
RoborockDockDustCollectionModeCode.as_dict().get(key)
],
is_dock_entity=True,
),
]
@ -117,6 +120,7 @@ class RoborockSelectEntity(RoborockCoordinatedEntityV1, SelectEntity):
f"{entity_description.key}_{coordinator.duid_slug}",
coordinator,
entity_description.protocol_listener,
is_dock_entity=entity_description.is_dock_entity,
)
self._attr_options = options

View File

@ -47,6 +47,9 @@ class RoborockSensorDescription(SensorEntityDescription):
protocol_listener: RoborockDataProtocol | None = None
# If it is a dock entity
is_dock_entity: bool = False
@dataclass(frozen=True, kw_only=True)
class RoborockSensorDescriptionA01(SensorEntityDescription):
@ -197,6 +200,7 @@ SENSOR_DESCRIPTIONS = [
entity_category=EntityCategory.DIAGNOSTIC,
device_class=SensorDeviceClass.ENUM,
options=RoborockDockErrorCode.keys(),
is_dock_entity=True,
),
RoborockSensorDescription(
key="mop_clean_remaining",
@ -205,6 +209,7 @@ SENSOR_DESCRIPTIONS = [
value_fn=lambda data: data.status.rdt,
translation_key="mop_drying_remaining_time",
entity_category=EntityCategory.DIAGNOSTIC,
is_dock_entity=True,
),
]
@ -335,6 +340,7 @@ class RoborockSensorEntity(RoborockCoordinatedEntityV1, SensorEntity):
f"{description.key}_{coordinator.duid_slug}",
coordinator,
description.protocol_listener,
is_dock_entity=description.is_dock_entity,
)
@property

View File

@ -35,6 +35,8 @@ class RoborockSwitchDescription(SwitchEntityDescription):
update_value: Callable[[AttributeCache, bool], Coroutine[Any, Any, None]]
# Attribute from cache
attribute: str
# If it is a dock entity
is_dock_entity: bool = False
SWITCH_DESCRIPTIONS: list[RoborockSwitchDescription] = [
@ -47,6 +49,7 @@ SWITCH_DESCRIPTIONS: list[RoborockSwitchDescription] = [
key="child_lock",
translation_key="child_lock",
entity_category=EntityCategory.CONFIG,
is_dock_entity=True,
),
RoborockSwitchDescription(
cache_key=CacheableAttribute.flow_led_status,
@ -57,6 +60,7 @@ SWITCH_DESCRIPTIONS: list[RoborockSwitchDescription] = [
key="status_indicator",
translation_key="status_indicator",
entity_category=EntityCategory.CONFIG,
is_dock_entity=True,
),
RoborockSwitchDescription(
cache_key=CacheableAttribute.dnd_timer,
@ -147,7 +151,13 @@ class RoborockSwitch(RoborockEntityV1, SwitchEntity):
) -> None:
"""Initialize the entity."""
self.entity_description = entity_description
super().__init__(unique_id, coordinator.device_info, coordinator.api)
super().__init__(
unique_id,
coordinator.device_info
if not entity_description.is_dock_entity
else coordinator.dock_device_info,
coordinator.api,
)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off the switch."""

View File

@ -53,7 +53,7 @@ async def test_sensors(hass: HomeAssistant, setup_entry: MockConfigEntry) -> Non
assert hass.states.get("sensor.roborock_s7_maxv_cleaning_area").state == "21.0"
assert hass.states.get("sensor.roborock_s7_maxv_vacuum_error").state == "none"
assert hass.states.get("sensor.roborock_s7_maxv_battery").state == "100"
assert hass.states.get("sensor.roborock_s7_maxv_dock_error").state == "ok"
assert hass.states.get("sensor.roborock_s7_maxv_dock_dock_error").state == "ok"
assert hass.states.get("sensor.roborock_s7_maxv_total_cleaning_count").state == "31"
assert (
hass.states.get("sensor.roborock_s7_maxv_last_clean_begin").state

View File

@ -22,8 +22,8 @@ def platforms() -> list[Platform]:
@pytest.mark.parametrize(
("entity_id"),
[
("switch.roborock_s7_maxv_child_lock"),
("switch.roborock_s7_maxv_status_indicator_light"),
("switch.roborock_s7_maxv_dock_child_lock"),
("switch.roborock_s7_maxv_dock_status_indicator_light"),
("switch.roborock_s7_maxv_do_not_disturb"),
],
)
@ -59,8 +59,8 @@ async def test_update_success(
@pytest.mark.parametrize(
("entity_id", "service"),
[
("switch.roborock_s7_maxv_status_indicator_light", SERVICE_TURN_ON),
("switch.roborock_s7_maxv_status_indicator_light", SERVICE_TURN_OFF),
("switch.roborock_s7_maxv_dock_status_indicator_light", SERVICE_TURN_ON),
("switch.roborock_s7_maxv_dock_status_indicator_light", SERVICE_TURN_OFF),
],
)
@pytest.mark.parametrize(