1
0
mirror of https://github.com/home-assistant/core.git synced 2026-05-15 04:51:20 +01:00
Files
core/homeassistant/components/roborock/switch.py
T
Yangqian Yan 2f80720730 Add Full support for roborock Zeo washing/drying machines (#159575)
Co-authored-by: Norbert Rittel <norbert@rittel.de>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-02-24 23:17:56 -08:00

223 lines
7.1 KiB
Python

"""Support for Roborock switch."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
import logging
from typing import Any
from roborock.devices.traits.v1 import PropertiesApi
from roborock.devices.traits.v1.common import RoborockSwitchBase
from roborock.exceptions import RoborockException
from roborock.roborock_message import RoborockDyadDataProtocol, RoborockZeoProtocol
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .coordinator import (
RoborockConfigEntry,
RoborockDataUpdateCoordinator,
RoborockDataUpdateCoordinatorA01,
)
from .entity import RoborockCoordinatedEntityA01, RoborockEntityV1
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 0
@dataclass(frozen=True, kw_only=True)
class RoborockSwitchDescription(SwitchEntityDescription):
"""Class to describe a Roborock switch entity."""
trait: Callable[[PropertiesApi], RoborockSwitchBase | None]
# If it is a dock entity
is_dock_entity: bool = False
SWITCH_DESCRIPTIONS: list[RoborockSwitchDescription] = [
RoborockSwitchDescription(
trait=lambda traits: traits.child_lock,
key="child_lock",
translation_key="child_lock",
entity_category=EntityCategory.CONFIG,
is_dock_entity=True,
),
RoborockSwitchDescription(
trait=lambda traits: traits.flow_led_status,
key="status_indicator",
translation_key="status_indicator",
entity_category=EntityCategory.CONFIG,
is_dock_entity=True,
),
RoborockSwitchDescription(
trait=lambda traits: traits.dnd,
key="dnd_switch",
translation_key="dnd_switch",
entity_category=EntityCategory.CONFIG,
),
RoborockSwitchDescription(
trait=lambda traits: traits.valley_electricity_timer,
key="off_peak_switch",
translation_key="off_peak_switch",
entity_category=EntityCategory.CONFIG,
entity_registry_enabled_default=False,
),
]
@dataclass(frozen=True, kw_only=True)
class RoborockSwitchDescriptionA01(SwitchEntityDescription):
"""Class to describe a Roborock A01 switch entity."""
data_protocol: RoborockDyadDataProtocol | RoborockZeoProtocol
A01_SWITCH_DESCRIPTIONS: list[RoborockSwitchDescriptionA01] = [
RoborockSwitchDescriptionA01(
key="sound_setting",
data_protocol=RoborockZeoProtocol.SOUND_SET,
translation_key="sound_setting",
entity_category=EntityCategory.CONFIG,
),
]
async def async_setup_entry(
hass: HomeAssistant,
config_entry: RoborockConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Roborock switch platform."""
# V1 switches - using trait pattern from HEAD
async_add_entities(
[
RoborockSwitch(
f"{description.key}_{coordinator.duid_slug}",
coordinator,
description,
trait,
)
for coordinator in config_entry.runtime_data.v1
for description in SWITCH_DESCRIPTIONS
if (trait := description.trait(coordinator.properties_api)) is not None
]
)
# A01 switches
async_add_entities(
RoborockSwitchA01(
coordinator,
description,
)
for coordinator in config_entry.runtime_data.a01
for description in A01_SWITCH_DESCRIPTIONS
if description.data_protocol in coordinator.request_protocols
)
class RoborockSwitch(RoborockEntityV1, SwitchEntity):
"""A class to let you turn functionality on Roborock devices on and off that does need a coordinator."""
entity_description: RoborockSwitchDescription
def __init__(
self,
unique_id: str,
coordinator: RoborockDataUpdateCoordinator,
entity_description: RoborockSwitchDescription,
trait: RoborockSwitchBase,
) -> None:
"""Initialize the entity."""
self.entity_description = entity_description
super().__init__(
unique_id,
(
coordinator.device_info
if not entity_description.is_dock_entity
else coordinator.dock_device_info
),
coordinator.properties_api.command,
)
self._trait = trait
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off the switch."""
try:
await self._trait.disable()
except RoborockException as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="update_options_failed",
) from err
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the switch."""
try:
await self._trait.enable()
except RoborockException as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="update_options_failed",
) from err
@property
def is_on(self) -> bool | None:
"""Return True if entity is on."""
return self._trait.is_on
class RoborockSwitchA01(RoborockCoordinatedEntityA01, SwitchEntity):
"""A class to let you turn functionality on Roborock A01 devices on and off."""
entity_description: RoborockSwitchDescriptionA01
def __init__(
self,
coordinator: RoborockDataUpdateCoordinatorA01,
description: RoborockSwitchDescriptionA01,
) -> None:
"""Initialize the entity."""
self.entity_description = description
super().__init__(f"{description.key}_{coordinator.duid_slug}", coordinator)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off the switch."""
try:
await self.coordinator.api.set_value( # type: ignore[attr-defined]
self.entity_description.data_protocol, 0
)
await self.coordinator.async_request_refresh()
except RoborockException as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="update_options_failed",
) from err
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the switch."""
try:
await self.coordinator.api.set_value( # type: ignore[attr-defined]
self.entity_description.data_protocol, 1
)
await self.coordinator.async_request_refresh()
except RoborockException as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="update_options_failed",
) from err
@property
def is_on(self) -> bool | None:
"""Return True if entity is on."""
status = self.coordinator.data.get(self.entity_description.data_protocol)
if status is None:
return None
return bool(status)