diff --git a/homeassistant/components/roborock/device.py b/homeassistant/components/roborock/device.py index 41db2cc08ac..3801ccbecc9 100644 --- a/homeassistant/components/roborock/device.py +++ b/homeassistant/components/roborock/device.py @@ -72,3 +72,13 @@ class RoborockCoordinatedEntity( if status: return status return Status({}) + + async def send( + self, + command: RoborockCommand, + params: dict[str, Any] | list[Any] | None = None, + ) -> dict: + """Overloads normal send command but refreshes coordinator.""" + res = await super().send(command, params) + await self.coordinator.async_refresh() + return res diff --git a/homeassistant/components/roborock/strings.json b/homeassistant/components/roborock/strings.json index e36b8c89e34..f711ceaf74a 100644 --- a/homeassistant/components/roborock/strings.json +++ b/homeassistant/components/roborock/strings.json @@ -110,6 +110,9 @@ "child_lock": { "name": "Child lock" }, + "dnd_switch": { + "name": "Do not disturb" + }, "status_indicator": { "name": "Status indicator light" } diff --git a/homeassistant/components/roborock/switch.py b/homeassistant/components/roborock/switch.py index d8ff50430cb..d16d7437202 100644 --- a/homeassistant/components/roborock/switch.py +++ b/homeassistant/components/roborock/switch.py @@ -17,7 +17,7 @@ from homeassistant.util import slugify from .const import DOMAIN from .coordinator import RoborockDataUpdateCoordinator -from .device import RoborockEntity +from .device import RoborockCoordinatedEntity, RoborockEntity _LOGGER = logging.getLogger(__name__) @@ -36,6 +36,16 @@ class RoborockSwitchDescriptionMixin: check_support: Callable[[RoborockDataUpdateCoordinator], Coroutine[Any, Any, dict]] +@dataclass +class RoborockCoordinatedSwitchDescriptionMixIn: + """Define an entity description mixin for switch entities.""" + + get_value: Callable[[RoborockCoordinatedEntity], bool] + set_command: Callable[[RoborockCoordinatedEntity, bool], Coroutine[Any, Any, dict]] + # Check support of this feature + check_support: Callable[[RoborockDataUpdateCoordinator], dict] + + @dataclass class RoborockSwitchDescription( SwitchEntityDescription, RoborockSwitchDescriptionMixin @@ -43,6 +53,13 @@ class RoborockSwitchDescription( """Class to describe an Roborock switch entity.""" +@dataclass +class RoborockCoordinatedSwitchDescription( + SwitchEntityDescription, RoborockCoordinatedSwitchDescriptionMixIn +): + """Class to describe an Roborock switch entity that needs a coordinator.""" + + SWITCH_DESCRIPTIONS: list[RoborockSwitchDescription] = [ RoborockSwitchDescription( set_command=lambda entity, value: entity.send( @@ -74,6 +91,28 @@ SWITCH_DESCRIPTIONS: list[RoborockSwitchDescription] = [ ), ] +COORDINATED_SWITCH_DESCRIPTION = [ + RoborockCoordinatedSwitchDescription( + set_command=lambda entity, value: entity.send( + RoborockCommand.SET_DND_TIMER, + [ + entity.coordinator.roborock_device_info.props.dnd_timer.start_hour, + entity.coordinator.roborock_device_info.props.dnd_timer.start_minute, + entity.coordinator.roborock_device_info.props.dnd_timer.end_hour, + entity.coordinator.roborock_device_info.props.dnd_timer.end_minute, + ], + ) + if value + else entity.send(RoborockCommand.CLOSE_DND_TIMER), + check_support=lambda data: data.roborock_device_info.props.dnd_timer, + get_value=lambda data: data.coordinator.roborock_device_info.props.dnd_timer.enabled, + key="dnd_switch", + translation_key="dnd_switch", + icon="mdi:bell-cancel", + entity_category=EntityCategory.CONFIG, + ), +] + async def async_setup_entry( hass: HomeAssistant, @@ -100,7 +139,7 @@ async def async_setup_entry( ), return_exceptions=True, ) - valid_entities: list[RoborockSwitchEntity] = [] + valid_entities: list[RoborockNonCoordinatedSwitchEntity] = [] for posible_entity, result in zip(possible_entities, results): if isinstance(result, Exception): if not isinstance(result, RoborockException): @@ -108,7 +147,7 @@ async def async_setup_entry( _LOGGER.debug("Not adding entity because of %s", result) else: valid_entities.append( - RoborockSwitchEntity( + RoborockNonCoordinatedSwitchEntity( f"{posible_entity[2].key}_{slugify(posible_entity[0])}", posible_entity[1], posible_entity[2], @@ -119,10 +158,22 @@ async def async_setup_entry( valid_entities, True, ) + async_add_entities( + ( + RoborockCoordinatedSwitchEntity( + f"{description.key}_{slugify(device_id)}", + coordinator, + description, + ) + for device_id, coordinator in coordinators.items() + for description in COORDINATED_SWITCH_DESCRIPTION + if description.check_support(coordinator) is not None + ) + ) -class RoborockSwitchEntity(RoborockEntity, SwitchEntity): - """A class to let you turn functionality on Roborock devices on and off.""" +class RoborockNonCoordinatedSwitchEntity(RoborockEntity, SwitchEntity): + """A class to let you turn functionality on Roborock devices on and off that does not need a coordinator.""" entity_description: RoborockSwitchDescription @@ -151,3 +202,32 @@ class RoborockSwitchEntity(RoborockEntity, SwitchEntity): self._attr_is_on = self.entity_description.evaluate_value( await self.entity_description.get_value(self) ) + + +class RoborockCoordinatedSwitchEntity(RoborockCoordinatedEntity, SwitchEntity): + """A class to let you turn functionality on Roborock devices on and off that does need a coordinator.""" + + entity_description: RoborockCoordinatedSwitchDescription + + def __init__( + self, + unique_id: str, + coordinator: RoborockDataUpdateCoordinator, + entity_description: RoborockCoordinatedSwitchDescription, + ) -> None: + """Create a switch entity.""" + self.entity_description = entity_description + super().__init__(unique_id, coordinator) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn off the switch.""" + await self.entity_description.set_command(self, False) + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn on the switch.""" + await self.entity_description.set_command(self, True) + + @property + def is_on(self) -> bool | None: + """Use the coordinator to determine if the switch is on.""" + return self.entity_description.get_value(self) diff --git a/homeassistant/components/roborock/vacuum.py b/homeassistant/components/roborock/vacuum.py index 9e486fa54b1..932febd80f0 100644 --- a/homeassistant/components/roborock/vacuum.py +++ b/homeassistant/components/roborock/vacuum.py @@ -144,7 +144,6 @@ class RoborockVacuum(RoborockCoordinatedEntity, StateVacuumEntity): RoborockCommand.SET_CUSTOM_MODE, [self._device_status.fan_power.as_dict().get(fan_speed)], ) - await self.coordinator.async_request_refresh() async def async_start_pause(self) -> None: """Start, pause or resume the cleaning task.""" diff --git a/tests/components/roborock/test_switch.py b/tests/components/roborock/test_switch.py index 276c2758da4..9c079ef85b6 100644 --- a/tests/components/roborock/test_switch.py +++ b/tests/components/roborock/test_switch.py @@ -16,6 +16,7 @@ from tests.common import MockConfigEntry [ ("switch.roborock_s7_maxv_child_lock"), ("switch.roborock_s7_maxv_status_indicator_light"), + ("switch.roborock_s7_maxv_do_not_disturb"), ], ) async def test_update_success(