1
0
mirror of https://github.com/home-assistant/core.git synced 2026-07-08 15:24:58 +01:00
Files
core/homeassistant/components/roborock/button.py
T

342 lines
11 KiB
Python

"""Support for Roborock button."""
from collections.abc import Callable
from dataclasses import dataclass
import logging
from typing import Any, override
from roborock.devices.traits.v1.consumeable import ConsumableAttribute
from roborock.exceptions import RoborockException
from roborock.roborock_message import RoborockZeoProtocol
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .coordinator import (
RoborockB01Q10UpdateCoordinator,
RoborockConfigEntry,
RoborockCoordinatorType,
RoborockDataUpdateCoordinator,
RoborockDataUpdateCoordinatorA01,
RoborockWashingMachineUpdateCoordinator,
)
from .entity import (
RoborockCoordinatedEntityA01,
RoborockCoordinatedEntityB01Q10,
RoborockEntity,
RoborockEntityV1,
)
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 0
@dataclass(frozen=True, kw_only=True)
class RoborockButtonDescription(ButtonEntityDescription):
"""Describes a Roborock button entity."""
attribute: ConsumableAttribute
is_dock_entity: bool = False
is_supported: Callable[[RoborockDataUpdateCoordinator], bool] = lambda _: True
CONSUMABLE_BUTTON_DESCRIPTIONS = [
RoborockButtonDescription(
key="reset_sensor_consumable",
translation_key="reset_sensor_consumable",
attribute=ConsumableAttribute.SENSOR_DIRTY_TIME,
entity_category=EntityCategory.CONFIG,
entity_registry_enabled_default=False,
),
RoborockButtonDescription(
key="reset_air_filter_consumable",
translation_key="reset_air_filter_consumable",
attribute=ConsumableAttribute.FILTER_WORK_TIME,
entity_category=EntityCategory.CONFIG,
entity_registry_enabled_default=False,
),
RoborockButtonDescription(
key="reset_side_brush_consumable",
translation_key="reset_side_brush_consumable",
attribute=ConsumableAttribute.SIDE_BRUSH_WORK_TIME,
entity_category=EntityCategory.CONFIG,
entity_registry_enabled_default=False,
),
RoborockButtonDescription(
key="reset_main_brush_consumable",
translation_key="reset_main_brush_consumable",
attribute=ConsumableAttribute.MAIN_BRUSH_WORK_TIME,
entity_category=EntityCategory.CONFIG,
entity_registry_enabled_default=False,
),
RoborockButtonDescription(
key="reset_dock_strainer_consumable",
translation_key="reset_dock_strainer_consumable",
attribute=ConsumableAttribute.STRAINER_WORK_TIME,
entity_category=EntityCategory.CONFIG,
entity_registry_enabled_default=False,
is_dock_entity=True,
is_supported=lambda coordinator: (
coordinator.properties_api.wash_towel_mode is not None
),
),
RoborockButtonDescription(
key="reset_dock_cleaning_brush_consumable",
translation_key="reset_dock_cleaning_brush_consumable",
attribute=ConsumableAttribute.CLEANING_BRUSH_WORK_TIME,
entity_category=EntityCategory.CONFIG,
entity_registry_enabled_default=False,
is_dock_entity=True,
is_supported=lambda coordinator: (
coordinator.properties_api.wash_towel_mode is not None
),
),
]
@dataclass(frozen=True, kw_only=True)
class RoborockButtonDescriptionA01(ButtonEntityDescription):
"""Describes a Roborock A01 button entity."""
data_protocol: RoborockZeoProtocol
ZEO_BUTTON_DESCRIPTIONS = [
RoborockButtonDescriptionA01(
key="start",
data_protocol=RoborockZeoProtocol.START,
translation_key="start",
),
RoborockButtonDescriptionA01(
key="pause",
data_protocol=RoborockZeoProtocol.PAUSE,
translation_key="pause",
),
RoborockButtonDescriptionA01(
key="shutdown",
data_protocol=RoborockZeoProtocol.SHUTDOWN,
translation_key="shutdown",
),
]
Q10_BUTTON_DESCRIPTIONS = [
ButtonEntityDescription(
key="empty_dustbin",
translation_key="empty_dustbin",
),
]
async def async_setup_entry(
hass: HomeAssistant,
config_entry: RoborockConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Roborock button platform."""
coordinators = config_entry.runtime_data
@callback
def async_add_coordinator_entities(
coordinator: RoborockCoordinatorType,
) -> None:
"""Add entities for a specific coordinator."""
entities: list[ButtonEntity] = []
if isinstance(coordinator, RoborockDataUpdateCoordinator):
entities.extend(
RoborockButtonEntity(coordinator, description)
for description in CONSUMABLE_BUTTON_DESCRIPTIONS
if description.is_supported(coordinator)
)
async def async_add_routine_buttons() -> None:
try:
routines = await coordinator.get_routines()
except HomeAssistantError as err:
_LOGGER.error(
"Failed to get routines for %s: %s",
coordinator.device.name,
err,
)
return
routine_entities = [
RoborockRoutineButtonEntity(
coordinator,
ButtonEntityDescription(
key=str(routine.id),
name=routine.name,
),
)
for routine in routines
]
async_add_entities(routine_entities)
config_entry.async_create_background_task(
hass,
async_add_routine_buttons(),
f"roborock_setup_routines_{coordinator.duid}",
)
elif isinstance(coordinator, RoborockWashingMachineUpdateCoordinator):
entities.extend(
RoborockButtonEntityA01(coordinator, description)
for description in ZEO_BUTTON_DESCRIPTIONS
)
elif isinstance(coordinator, RoborockB01Q10UpdateCoordinator):
entities.extend(
RoborockQ10EmptyDustbinButtonEntity(coordinator, description)
for description in Q10_BUTTON_DESCRIPTIONS
)
async_add_entities(entities)
for coordinator in coordinators.values():
async_add_coordinator_entities(coordinator)
config_entry.async_on_unload(
async_dispatcher_connect(
hass,
f"roborock_coordinator_added_{config_entry.entry_id}",
async_add_coordinator_entities,
)
)
class RoborockButtonEntity(RoborockEntityV1, ButtonEntity):
"""A class to define Roborock button entities."""
entity_description: RoborockButtonDescription
def __init__(
self,
coordinator: RoborockDataUpdateCoordinator,
entity_description: RoborockButtonDescription,
) -> None:
"""Create a button entity."""
device_info = (
coordinator.dock_device_info
if entity_description.is_dock_entity
else coordinator.device_info
)
super().__init__(
f"{entity_description.key}_{coordinator.duid_slug}",
device_info,
api=coordinator.properties_api.command,
)
self.entity_description = entity_description
self._consumable = coordinator.properties_api.consumables
@override
async def async_press(self) -> None:
"""Press the button."""
try:
await self._consumable.reset_consumable(self.entity_description.attribute)
except RoborockException as err:
# This error message could be improved since it is fairly low level
# and technical. Can add a more user friendly message with the
# name of the attribute being reset.
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="command_failed",
translation_placeholders={
"command": "RESET_CONSUMABLE",
},
) from err
class RoborockRoutineButtonEntity(RoborockEntity, ButtonEntity):
"""A class to define Roborock routines button entities."""
entity_description: ButtonEntityDescription
def __init__(
self,
coordinator: RoborockDataUpdateCoordinator,
entity_description: ButtonEntityDescription,
) -> None:
"""Create a button entity."""
super().__init__(
f"{entity_description.key}_{coordinator.duid_slug}",
coordinator.device_info,
)
self._routine_id = int(entity_description.key)
self._coordinator = coordinator
self.entity_description = entity_description
@override
async def async_press(self, **kwargs: Any) -> None:
"""Press the button."""
await self._coordinator.execute_routines(self._routine_id)
class RoborockButtonEntityA01(RoborockCoordinatedEntityA01, ButtonEntity):
"""A class to define Roborock A01 button entities."""
entity_description: RoborockButtonDescriptionA01
def __init__(
self,
coordinator: RoborockDataUpdateCoordinatorA01,
entity_description: RoborockButtonDescriptionA01,
) -> None:
"""Create an A01 button entity."""
self.entity_description = entity_description
super().__init__(
f"{entity_description.key}_{coordinator.duid_slug}", coordinator
)
@override
async def async_press(self) -> None:
"""Press the button."""
try:
await self.coordinator.api.set_value( # type: ignore[attr-defined]
self.entity_description.data_protocol,
1,
)
except RoborockException as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="button_press_failed",
) from err
finally:
await self.coordinator.async_request_refresh()
class RoborockQ10EmptyDustbinButtonEntity(
RoborockCoordinatedEntityB01Q10, ButtonEntity
):
"""A class to define Q10 empty dustbin button entity."""
entity_description: ButtonEntityDescription
coordinator: RoborockB01Q10UpdateCoordinator
def __init__(
self,
coordinator: RoborockB01Q10UpdateCoordinator,
entity_description: ButtonEntityDescription,
) -> None:
"""Create a Q10 empty dustbin button entity."""
self.entity_description = entity_description
super().__init__(
f"{entity_description.key}_{coordinator.duid_slug}",
coordinator,
)
@override
async def async_press(self, **kwargs: Any) -> None:
"""Press the button to empty dustbin."""
try:
await self.coordinator.api.vacuum.empty_dustbin()
except RoborockException as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="command_failed",
translation_placeholders={
"command": "empty_dustbin",
},
) from err