mirror of
https://github.com/home-assistant/core.git
synced 2026-09-12 11:38:47 +01:00
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>
This commit is contained in:
co-authored by
Norbert Rittel
Joost Lekkerkerker
parent
644c74f311
commit
2f80720730
@@ -6,6 +6,7 @@ from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
from roborock.data import CleanFluidStatus, RoborockStateCode
|
||||
from roborock.roborock_message import RoborockZeoProtocol
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDeviceClass,
|
||||
@@ -15,9 +16,15 @@ from homeassistant.components.binary_sensor import (
|
||||
from homeassistant.const import ATTR_BATTERY_CHARGING, EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
|
||||
from .coordinator import RoborockConfigEntry, RoborockDataUpdateCoordinator
|
||||
from .entity import RoborockCoordinatedEntityV1
|
||||
from .coordinator import (
|
||||
RoborockConfigEntry,
|
||||
RoborockDataUpdateCoordinator,
|
||||
RoborockDataUpdateCoordinatorA01,
|
||||
RoborockWashingMachineUpdateCoordinator,
|
||||
)
|
||||
from .entity import RoborockCoordinatedEntityA01, RoborockCoordinatedEntityV1
|
||||
from .models import DeviceState
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
@@ -34,6 +41,14 @@ class RoborockBinarySensorDescription(BinarySensorEntityDescription):
|
||||
"""Whether this sensor is for the dock."""
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class RoborockBinarySensorDescriptionA01(BinarySensorEntityDescription):
|
||||
"""A class that describes Roborock A01 binary sensors."""
|
||||
|
||||
data_protocol: RoborockZeoProtocol
|
||||
value_fn: Callable[[StateType], bool]
|
||||
|
||||
|
||||
BINARY_SENSOR_DESCRIPTIONS = [
|
||||
RoborockBinarySensorDescription(
|
||||
key="dry_status",
|
||||
@@ -111,13 +126,33 @@ BINARY_SENSOR_DESCRIPTIONS = [
|
||||
]
|
||||
|
||||
|
||||
ZEO_BINARY_SENSOR_DESCRIPTIONS: list[RoborockBinarySensorDescriptionA01] = [
|
||||
RoborockBinarySensorDescriptionA01(
|
||||
key="detergent_empty",
|
||||
data_protocol=RoborockZeoProtocol.DETERGENT_EMPTY,
|
||||
device_class=BinarySensorDeviceClass.PROBLEM,
|
||||
translation_key="detergent_empty",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
value_fn=bool,
|
||||
),
|
||||
RoborockBinarySensorDescriptionA01(
|
||||
key="softener_empty",
|
||||
data_protocol=RoborockZeoProtocol.SOFTENER_EMPTY,
|
||||
device_class=BinarySensorDeviceClass.PROBLEM,
|
||||
translation_key="softener_empty",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
value_fn=bool,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: RoborockConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Roborock vacuum binary sensors."""
|
||||
async_add_entities(
|
||||
entities: list[BinarySensorEntity] = [
|
||||
RoborockBinarySensorEntity(
|
||||
coordinator,
|
||||
description,
|
||||
@@ -125,7 +160,18 @@ async def async_setup_entry(
|
||||
for coordinator in config_entry.runtime_data.v1
|
||||
for description in BINARY_SENSOR_DESCRIPTIONS
|
||||
if description.value_fn(coordinator.data) is not None
|
||||
]
|
||||
entities.extend(
|
||||
RoborockBinarySensorEntityA01(
|
||||
coordinator,
|
||||
description,
|
||||
)
|
||||
for coordinator in config_entry.runtime_data.a01
|
||||
if isinstance(coordinator, RoborockWashingMachineUpdateCoordinator)
|
||||
for description in ZEO_BINARY_SENSOR_DESCRIPTIONS
|
||||
if description.data_protocol in coordinator.request_protocols
|
||||
)
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class RoborockBinarySensorEntity(RoborockCoordinatedEntityV1, BinarySensorEntity):
|
||||
@@ -150,3 +196,24 @@ class RoborockBinarySensorEntity(RoborockCoordinatedEntityV1, BinarySensorEntity
|
||||
def is_on(self) -> bool:
|
||||
"""Return the value reported by the sensor."""
|
||||
return bool(self.entity_description.value_fn(self.coordinator.data))
|
||||
|
||||
|
||||
class RoborockBinarySensorEntityA01(RoborockCoordinatedEntityA01, BinarySensorEntity):
|
||||
"""Representation of a A01 Roborock binary sensor."""
|
||||
|
||||
entity_description: RoborockBinarySensorDescriptionA01
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: RoborockDataUpdateCoordinatorA01,
|
||||
description: RoborockBinarySensorDescriptionA01,
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
self.entity_description = description
|
||||
super().__init__(f"{description.key}_{coordinator.duid_slug}", coordinator)
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return the value reported by the sensor."""
|
||||
value = self.coordinator.data[self.entity_description.data_protocol]
|
||||
return self.entity_description.value_fn(value)
|
||||
|
||||
@@ -10,6 +10,7 @@ from typing import Any
|
||||
|
||||
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
|
||||
@@ -18,8 +19,13 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import RoborockConfigEntry, RoborockDataUpdateCoordinator
|
||||
from .entity import RoborockEntity, RoborockEntityV1
|
||||
from .coordinator import (
|
||||
RoborockConfigEntry,
|
||||
RoborockDataUpdateCoordinator,
|
||||
RoborockDataUpdateCoordinatorA01,
|
||||
RoborockWashingMachineUpdateCoordinator,
|
||||
)
|
||||
from .entity import RoborockCoordinatedEntityA01, RoborockEntity, RoborockEntityV1
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -65,6 +71,32 @@ CONSUMABLE_BUTTON_DESCRIPTIONS = [
|
||||
]
|
||||
|
||||
|
||||
@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",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: RoborockConfigEntry,
|
||||
@@ -98,6 +130,15 @@ async def async_setup_entry(
|
||||
)
|
||||
for routine in routines
|
||||
),
|
||||
(
|
||||
RoborockButtonEntityA01(
|
||||
coordinator,
|
||||
description,
|
||||
)
|
||||
for coordinator in config_entry.runtime_data.a01
|
||||
if isinstance(coordinator, RoborockWashingMachineUpdateCoordinator)
|
||||
for description in ZEO_BUTTON_DESCRIPTIONS
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -160,3 +201,35 @@ class RoborockRoutineButtonEntity(RoborockEntity, ButtonEntity):
|
||||
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
|
||||
)
|
||||
|
||||
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()
|
||||
|
||||
@@ -432,6 +432,18 @@ class RoborockWashingMachineUpdateCoordinator(
|
||||
RoborockZeoProtocol.COUNTDOWN,
|
||||
RoborockZeoProtocol.WASHING_LEFT,
|
||||
RoborockZeoProtocol.ERROR,
|
||||
RoborockZeoProtocol.TIMES_AFTER_CLEAN,
|
||||
RoborockZeoProtocol.DETERGENT_EMPTY,
|
||||
RoborockZeoProtocol.SOFTENER_EMPTY,
|
||||
RoborockZeoProtocol.DETERGENT_TYPE,
|
||||
RoborockZeoProtocol.SOFTENER_TYPE,
|
||||
RoborockZeoProtocol.MODE,
|
||||
RoborockZeoProtocol.PROGRAM,
|
||||
RoborockZeoProtocol.TEMP,
|
||||
RoborockZeoProtocol.RINSE_TIMES,
|
||||
RoborockZeoProtocol.SPIN_LEVEL,
|
||||
RoborockZeoProtocol.DRYING_MODE,
|
||||
RoborockZeoProtocol.SOUND_SET,
|
||||
]
|
||||
|
||||
async def _async_update_data(
|
||||
|
||||
@@ -3,21 +3,35 @@
|
||||
import asyncio
|
||||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from roborock import B01Props, CleanTypeMapping
|
||||
from roborock.data import RoborockDockDustCollectionModeCode, WaterLevelMapping
|
||||
from roborock.data import (
|
||||
RoborockDockDustCollectionModeCode,
|
||||
RoborockEnum,
|
||||
WaterLevelMapping,
|
||||
ZeoDetergentType,
|
||||
ZeoDryingMode,
|
||||
ZeoMode,
|
||||
ZeoProgram,
|
||||
ZeoRinse,
|
||||
ZeoSoftenerType,
|
||||
ZeoSpin,
|
||||
ZeoTemperature,
|
||||
)
|
||||
from roborock.devices.traits.b01 import Q7PropertiesApi
|
||||
from roborock.devices.traits.v1 import PropertiesApi
|
||||
from roborock.devices.traits.v1.home import HomeTrait
|
||||
from roborock.devices.traits.v1.maps import MapsTrait
|
||||
from roborock.exceptions import RoborockException
|
||||
from roborock.roborock_message import RoborockZeoProtocol
|
||||
from roborock.roborock_typing import RoborockCommand
|
||||
|
||||
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN, MAP_SLEEP
|
||||
@@ -25,11 +39,18 @@ from .coordinator import (
|
||||
RoborockB01Q7UpdateCoordinator,
|
||||
RoborockConfigEntry,
|
||||
RoborockDataUpdateCoordinator,
|
||||
RoborockDataUpdateCoordinatorA01,
|
||||
)
|
||||
from .entity import (
|
||||
RoborockCoordinatedEntityA01,
|
||||
RoborockCoordinatedEntityB01Q7,
|
||||
RoborockCoordinatedEntityV1,
|
||||
)
|
||||
from .entity import RoborockCoordinatedEntityB01Q7, RoborockCoordinatedEntityV1
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class RoborockSelectDescription(SelectEntityDescription):
|
||||
@@ -65,6 +86,16 @@ class RoborockB01SelectDescription(SelectEntityDescription):
|
||||
"""Function to get all options of the select entity or returns None if not supported."""
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class RoborockSelectDescriptionA01(SelectEntityDescription):
|
||||
"""Class to describe a Roborock A01 select entity."""
|
||||
|
||||
# The protocol that the select entity will send to the api.
|
||||
data_protocol: RoborockZeoProtocol
|
||||
# Enum class for the select entity
|
||||
enum_class: type[RoborockEnum]
|
||||
|
||||
|
||||
B01_SELECT_DESCRIPTIONS: list[RoborockB01SelectDescription] = [
|
||||
RoborockB01SelectDescription(
|
||||
key="water_flow",
|
||||
@@ -139,6 +170,66 @@ SELECT_DESCRIPTIONS: list[RoborockSelectDescription] = [
|
||||
]
|
||||
|
||||
|
||||
A01_SELECT_DESCRIPTIONS: list[RoborockSelectDescriptionA01] = [
|
||||
RoborockSelectDescriptionA01(
|
||||
key="program",
|
||||
data_protocol=RoborockZeoProtocol.PROGRAM,
|
||||
translation_key="program",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
enum_class=ZeoProgram,
|
||||
),
|
||||
RoborockSelectDescriptionA01(
|
||||
key="mode",
|
||||
data_protocol=RoborockZeoProtocol.MODE,
|
||||
translation_key="mode",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
enum_class=ZeoMode,
|
||||
),
|
||||
RoborockSelectDescriptionA01(
|
||||
key="temperature",
|
||||
data_protocol=RoborockZeoProtocol.TEMP,
|
||||
translation_key="temperature",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
enum_class=ZeoTemperature,
|
||||
),
|
||||
RoborockSelectDescriptionA01(
|
||||
key="drying_mode",
|
||||
data_protocol=RoborockZeoProtocol.DRYING_MODE,
|
||||
translation_key="drying_mode",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
enum_class=ZeoDryingMode,
|
||||
),
|
||||
RoborockSelectDescriptionA01(
|
||||
key="spin_level",
|
||||
data_protocol=RoborockZeoProtocol.SPIN_LEVEL,
|
||||
translation_key="spin_level",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
enum_class=ZeoSpin,
|
||||
),
|
||||
RoborockSelectDescriptionA01(
|
||||
key="rinse_times",
|
||||
data_protocol=RoborockZeoProtocol.RINSE_TIMES,
|
||||
translation_key="rinse_times",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
enum_class=ZeoRinse,
|
||||
),
|
||||
RoborockSelectDescriptionA01(
|
||||
key="detergent_type",
|
||||
data_protocol=RoborockZeoProtocol.DETERGENT_TYPE,
|
||||
translation_key="detergent_type",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
enum_class=ZeoDetergentType,
|
||||
),
|
||||
RoborockSelectDescriptionA01(
|
||||
key="softener_type",
|
||||
data_protocol=RoborockZeoProtocol.SOFTENER_TYPE,
|
||||
translation_key="softener_type",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
enum_class=ZeoSoftenerType,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: RoborockConfigEntry,
|
||||
@@ -169,6 +260,12 @@ async def async_setup_entry(
|
||||
for description in B01_SELECT_DESCRIPTIONS
|
||||
if (options := description.options_lambda(coordinator.api)) is not None
|
||||
)
|
||||
async_add_entities(
|
||||
RoborockSelectEntityA01(coordinator, description)
|
||||
for coordinator in config_entry.runtime_data.a01
|
||||
for description in A01_SELECT_DESCRIPTIONS
|
||||
if description.data_protocol in coordinator.request_protocols
|
||||
)
|
||||
|
||||
|
||||
class RoborockB01SelectEntity(RoborockCoordinatedEntityB01Q7, SelectEntity):
|
||||
@@ -308,3 +405,64 @@ class RoborockCurrentMapSelectEntity(RoborockCoordinatedEntityV1, SelectEntity):
|
||||
if current_map_info := self._home_trait.current_map_data:
|
||||
return current_map_info.name or f"Map {current_map_info.map_flag}"
|
||||
return None
|
||||
|
||||
|
||||
class RoborockSelectEntityA01(RoborockCoordinatedEntityA01, SelectEntity):
|
||||
"""A class to let you set options on a Roborock A01 device."""
|
||||
|
||||
entity_description: RoborockSelectDescriptionA01
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: RoborockDataUpdateCoordinatorA01,
|
||||
entity_description: RoborockSelectDescriptionA01,
|
||||
) -> None:
|
||||
"""Create an A01 select entity."""
|
||||
self.entity_description = entity_description
|
||||
super().__init__(
|
||||
f"{entity_description.key}_{coordinator.duid_slug}",
|
||||
coordinator,
|
||||
)
|
||||
self._attr_options = list(entity_description.enum_class.keys())
|
||||
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Set the option."""
|
||||
# Get the protocol value for the selected option
|
||||
option_values = self.entity_description.enum_class.as_dict()
|
||||
if option not in option_values:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="select_option_failed",
|
||||
)
|
||||
value = option_values[option]
|
||||
try:
|
||||
await self.coordinator.api.set_value( # type: ignore[attr-defined]
|
||||
self.entity_description.data_protocol,
|
||||
value,
|
||||
)
|
||||
except RoborockException as err:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="command_failed",
|
||||
translation_placeholders={
|
||||
"command": self.entity_description.key,
|
||||
},
|
||||
) from err
|
||||
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
@property
|
||||
def current_option(self) -> str | None:
|
||||
"""Get the current status of the select entity from coordinator data."""
|
||||
if self.entity_description.data_protocol not in self.coordinator.data:
|
||||
return None
|
||||
|
||||
current_value = self.coordinator.data[self.entity_description.data_protocol]
|
||||
if current_value is None:
|
||||
return None
|
||||
_LOGGER.debug(
|
||||
"current_value: %s for %s",
|
||||
current_value,
|
||||
self.entity_description.key,
|
||||
)
|
||||
return str(current_value)
|
||||
|
||||
@@ -37,6 +37,8 @@ from .coordinator import (
|
||||
RoborockConfigEntry,
|
||||
RoborockDataUpdateCoordinator,
|
||||
RoborockDataUpdateCoordinatorA01,
|
||||
RoborockWashingMachineUpdateCoordinator,
|
||||
RoborockWetDryVacUpdateCoordinator,
|
||||
)
|
||||
from .entity import (
|
||||
RoborockCoordinatedEntityA01,
|
||||
@@ -252,7 +254,7 @@ SENSOR_DESCRIPTIONS = [
|
||||
),
|
||||
]
|
||||
|
||||
A01_SENSOR_DESCRIPTIONS: list[RoborockSensorDescriptionA01] = [
|
||||
DYAD_SENSOR_DESCRIPTIONS: list[RoborockSensorDescriptionA01] = [
|
||||
RoborockSensorDescriptionA01(
|
||||
key="status",
|
||||
data_protocol=RoborockDyadDataProtocol.STATUS,
|
||||
@@ -303,6 +305,9 @@ A01_SENSOR_DESCRIPTIONS: list[RoborockSensorDescriptionA01] = [
|
||||
translation_key="total_cleaning_time",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
]
|
||||
|
||||
ZEO_SENSOR_DESCRIPTIONS: list[RoborockSensorDescriptionA01] = [
|
||||
RoborockSensorDescriptionA01(
|
||||
key="state",
|
||||
data_protocol=RoborockZeoProtocol.STATE,
|
||||
@@ -335,6 +340,12 @@ A01_SENSOR_DESCRIPTIONS: list[RoborockSensorDescriptionA01] = [
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
options=ZeoError.keys(),
|
||||
),
|
||||
RoborockSensorDescriptionA01(
|
||||
key="times_after_clean",
|
||||
data_protocol=RoborockZeoProtocol.TIMES_AFTER_CLEAN,
|
||||
translation_key="times_after_clean",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
]
|
||||
|
||||
Q7_B01_SENSOR_DESCRIPTIONS = [
|
||||
@@ -418,7 +429,18 @@ async def async_setup_entry(
|
||||
description,
|
||||
)
|
||||
for coordinator in coordinators.a01
|
||||
for description in A01_SENSOR_DESCRIPTIONS
|
||||
if isinstance(coordinator, RoborockWetDryVacUpdateCoordinator)
|
||||
for description in DYAD_SENSOR_DESCRIPTIONS
|
||||
if description.data_protocol in coordinator.request_protocols
|
||||
)
|
||||
entities.extend(
|
||||
RoborockSensorEntityA01(
|
||||
coordinator,
|
||||
description,
|
||||
)
|
||||
for coordinator in coordinators.a01
|
||||
if isinstance(coordinator, RoborockWashingMachineUpdateCoordinator)
|
||||
for description in ZEO_SENSOR_DESCRIPTIONS
|
||||
if description.data_protocol in coordinator.request_protocols
|
||||
)
|
||||
entities.extend(
|
||||
|
||||
@@ -50,6 +50,13 @@
|
||||
"clean_fluid_empty": {
|
||||
"name": "Cleaning fluid"
|
||||
},
|
||||
"detergent_empty": {
|
||||
"name": "Detergent",
|
||||
"state": {
|
||||
"off": "Available",
|
||||
"on": "[%key:common::state::empty%]"
|
||||
}
|
||||
},
|
||||
"dirty_box_full": {
|
||||
"name": "Dirty water box"
|
||||
},
|
||||
@@ -62,6 +69,13 @@
|
||||
"mop_drying_status": {
|
||||
"name": "Mop drying"
|
||||
},
|
||||
"softener_empty": {
|
||||
"name": "Softener",
|
||||
"state": {
|
||||
"off": "Available",
|
||||
"on": "[%key:common::state::empty%]"
|
||||
}
|
||||
},
|
||||
"water_box_attached": {
|
||||
"name": "Water box attached"
|
||||
},
|
||||
@@ -70,6 +84,9 @@
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"pause": {
|
||||
"name": "Pause"
|
||||
},
|
||||
"reset_air_filter_consumable": {
|
||||
"name": "Reset air filter consumable"
|
||||
},
|
||||
@@ -81,6 +98,12 @@
|
||||
},
|
||||
"reset_side_brush_consumable": {
|
||||
"name": "Reset side brush consumable"
|
||||
},
|
||||
"shutdown": {
|
||||
"name": "Shutdown"
|
||||
},
|
||||
"start": {
|
||||
"name": "Start"
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
@@ -97,6 +120,25 @@
|
||||
"vacuum": "Vacuum only"
|
||||
}
|
||||
},
|
||||
"detergent_type": {
|
||||
"name": "Detergent type",
|
||||
"state": {
|
||||
"empty": "[%key:common::state::empty%]",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"medium": "[%key:common::state::medium%]"
|
||||
}
|
||||
},
|
||||
"drying_mode": {
|
||||
"name": "Drying mode",
|
||||
"state": {
|
||||
"iron": "Iron",
|
||||
"none": "No drying",
|
||||
"quick": "Quick",
|
||||
"store": "Store",
|
||||
"time_dry": "Time dry"
|
||||
}
|
||||
},
|
||||
"dust_collection_mode": {
|
||||
"name": "Empty mode",
|
||||
"state": {
|
||||
@@ -106,6 +148,19 @@
|
||||
"smart": "Smart"
|
||||
}
|
||||
},
|
||||
"mode": {
|
||||
"name": "Operating mode",
|
||||
"state": {
|
||||
"drain": "Drain",
|
||||
"dry": "Dry",
|
||||
"heavy": "Heavy",
|
||||
"pre_wash": "Pre-wash",
|
||||
"rinse_spin": "Rinse & spin",
|
||||
"spin": "Spin",
|
||||
"wash": "Wash",
|
||||
"wash_and_dry": "Wash and dry"
|
||||
}
|
||||
},
|
||||
"mop_intensity": {
|
||||
"name": "Mop intensity",
|
||||
"state": {
|
||||
@@ -138,9 +193,90 @@
|
||||
"standard": "Standard"
|
||||
}
|
||||
},
|
||||
"program": {
|
||||
"name": "Wash program",
|
||||
"state": {
|
||||
"air_refresh": "Air refresh",
|
||||
"anti_allergen": "Anti-allergen",
|
||||
"anti_mites": "Anti-mites",
|
||||
"baby_care": "Baby care",
|
||||
"bedding": "Bedding",
|
||||
"boiling_wash": "Boiling wash",
|
||||
"bra": "Bra",
|
||||
"cotton_linen": "Cotton/Linen",
|
||||
"custom": "Custom",
|
||||
"down": "Down",
|
||||
"down_clean": "Down clean",
|
||||
"exo_40_60": "Exo 40/60",
|
||||
"gentle": "Gentle",
|
||||
"intensive": "Intensive",
|
||||
"new_clothes": "New clothes",
|
||||
"night": "Night",
|
||||
"panties": "Panties",
|
||||
"quick": "Quick",
|
||||
"rinse_and_spin": "Rinse and spin",
|
||||
"sanitize": "Sanitize",
|
||||
"season": "Season",
|
||||
"shirts": "Shirts",
|
||||
"silk": "Silk",
|
||||
"socks": "Socks",
|
||||
"sportswear": "Sportswear",
|
||||
"stain_removal": "Stain removal",
|
||||
"standard": "Standard",
|
||||
"synthetics": "Synthetics",
|
||||
"t_shirts": "T-shirts",
|
||||
"towels": "Towels",
|
||||
"twenty_c": "20°C",
|
||||
"underwear": "Underwear",
|
||||
"warming": "Warming",
|
||||
"wool": "Wool"
|
||||
}
|
||||
},
|
||||
"rinse_times": {
|
||||
"name": "Rinse times",
|
||||
"state": {
|
||||
"high": "4",
|
||||
"low": "2",
|
||||
"max": "5",
|
||||
"mid": "3",
|
||||
"min": "1",
|
||||
"none": "Default"
|
||||
}
|
||||
},
|
||||
"selected_map": {
|
||||
"name": "Selected map"
|
||||
},
|
||||
"softener_type": {
|
||||
"name": "Softener type",
|
||||
"state": {
|
||||
"empty": "[%key:common::state::empty%]",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"medium": "[%key:common::state::medium%]"
|
||||
}
|
||||
},
|
||||
"spin_level": {
|
||||
"name": "Spin level",
|
||||
"state": {
|
||||
"high": "1000 RPM",
|
||||
"max": "1400 RPM",
|
||||
"mid": "800 RPM",
|
||||
"none": "Default",
|
||||
"very_high": "1200 RPM",
|
||||
"very_low": "600 RPM"
|
||||
}
|
||||
},
|
||||
"temperature": {
|
||||
"name": "Water temperature",
|
||||
"state": {
|
||||
"30": "30°C",
|
||||
"40": "40°C",
|
||||
"60": "60°C",
|
||||
"90": "90°C",
|
||||
"auto": "[%key:common::state::auto%]",
|
||||
"cold": "Cold"
|
||||
}
|
||||
},
|
||||
"water_flow": {
|
||||
"name": "Water flow",
|
||||
"state": {
|
||||
@@ -307,6 +443,9 @@
|
||||
"strainer_time_left": {
|
||||
"name": "Strainer time left"
|
||||
},
|
||||
"times_after_clean": {
|
||||
"name": "Times after clean"
|
||||
},
|
||||
"total_cleaning_area": {
|
||||
"name": "Total cleaning area"
|
||||
},
|
||||
@@ -375,14 +514,14 @@
|
||||
"communication_error": "Communication error",
|
||||
"door_lock_error": "Door lock error",
|
||||
"drain_error": "Drain error",
|
||||
"drying_error": "Drying error",
|
||||
"drying_error_e_12": "Drying error E12",
|
||||
"drying_error": "Drying error: check air inlet temperature sensor",
|
||||
"drying_error_e_12": "Drying error: check air outlet temperature sensor",
|
||||
"drying_error_e_13": "Drying error E13",
|
||||
"drying_error_e_14": "Drying error E14",
|
||||
"drying_error_e_15": "Drying error E15",
|
||||
"drying_error_e_16": "Drying error E16",
|
||||
"drying_error_restart": "Restart the washer",
|
||||
"drying_error_water_flow": "Check water flow",
|
||||
"drying_error_e_14": "Drying error: check inlet condenser temperature sensor",
|
||||
"drying_error_e_15": "Drying error: check heating element or turntable",
|
||||
"drying_error_e_16": "Drying error: check drying fan",
|
||||
"drying_error_restart": "Drying error: restart the washer",
|
||||
"drying_error_water_flow": "Drying error: check water flow",
|
||||
"heating_error": "Heating error",
|
||||
"inverter_error": "Inverter error",
|
||||
"none": "[%key:component::roborock::entity::sensor::vacuum_error::state::none%]",
|
||||
@@ -420,6 +559,9 @@
|
||||
"off_peak_switch": {
|
||||
"name": "Off-peak charging"
|
||||
},
|
||||
"sound_setting": {
|
||||
"name": "Sound setting"
|
||||
},
|
||||
"status_indicator": {
|
||||
"name": "Status indicator light"
|
||||
}
|
||||
@@ -464,6 +606,9 @@
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"button_press_failed": {
|
||||
"message": "Failed to press button"
|
||||
},
|
||||
"command_failed": {
|
||||
"message": "Error while calling {command}"
|
||||
},
|
||||
@@ -491,6 +636,12 @@
|
||||
"position_not_found": {
|
||||
"message": "Robot position not found"
|
||||
},
|
||||
"segment_id_parse_error": {
|
||||
"message": "Invalid segment ID format: {segment_id}"
|
||||
},
|
||||
"select_option_failed": {
|
||||
"message": "Failed to set selected option"
|
||||
},
|
||||
"update_data_fail": {
|
||||
"message": "Failed to update data"
|
||||
},
|
||||
@@ -504,7 +655,6 @@
|
||||
"title": "Cloud API used"
|
||||
}
|
||||
},
|
||||
|
||||
"options": {
|
||||
"step": {
|
||||
"drawables": {
|
||||
|
||||
@@ -10,6 +10,7 @@ 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
|
||||
@@ -18,8 +19,12 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import RoborockConfigEntry, RoborockDataUpdateCoordinator
|
||||
from .entity import RoborockEntityV1
|
||||
from .coordinator import (
|
||||
RoborockConfigEntry,
|
||||
RoborockDataUpdateCoordinator,
|
||||
RoborockDataUpdateCoordinatorA01,
|
||||
)
|
||||
from .entity import RoborockCoordinatedEntityA01, RoborockEntityV1
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -67,12 +72,30 @@ SWITCH_DESCRIPTIONS: list[RoborockSwitchDescription] = [
|
||||
]
|
||||
|
||||
|
||||
@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(
|
||||
@@ -87,6 +110,17 @@ async def async_setup_entry(
|
||||
]
|
||||
)
|
||||
|
||||
# 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."""
|
||||
@@ -137,3 +171,52 @@ class RoborockSwitch(RoborockEntityV1, SwitchEntity):
|
||||
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)
|
||||
|
||||
@@ -111,6 +111,18 @@ def create_zeo_trait() -> Mock:
|
||||
RoborockZeoProtocol.COUNTDOWN: 0,
|
||||
RoborockZeoProtocol.WASHING_LEFT: 253,
|
||||
RoborockZeoProtocol.ERROR: ZeoError.none.name,
|
||||
RoborockZeoProtocol.TIMES_AFTER_CLEAN: 5,
|
||||
RoborockZeoProtocol.DETERGENT_EMPTY: 0,
|
||||
RoborockZeoProtocol.SOFTENER_EMPTY: 0,
|
||||
RoborockZeoProtocol.DETERGENT_TYPE: 2,
|
||||
RoborockZeoProtocol.SOFTENER_TYPE: 2,
|
||||
RoborockZeoProtocol.MODE: 0,
|
||||
RoborockZeoProtocol.PROGRAM: 1,
|
||||
RoborockZeoProtocol.TEMP: 1,
|
||||
RoborockZeoProtocol.RINSE_TIMES: 1,
|
||||
RoborockZeoProtocol.SPIN_LEVEL: 5,
|
||||
RoborockZeoProtocol.DRYING_MODE: 3,
|
||||
RoborockZeoProtocol.SOUND_SET: False,
|
||||
}
|
||||
return zeo_trait
|
||||
|
||||
|
||||
@@ -699,3 +699,103 @@
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[binary_sensor.zeo_one_detergent-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'binary_sensor',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'binary_sensor.zeo_one_detergent',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Detergent',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Detergent',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'detergent_empty',
|
||||
'unique_id': 'detergent_empty_zeo_duid',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[binary_sensor.zeo_one_detergent-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'problem',
|
||||
'friendly_name': 'Zeo One Detergent',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.zeo_one_detergent',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[binary_sensor.zeo_one_softener-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'binary_sensor',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'binary_sensor.zeo_one_softener',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Softener',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Softener',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'softener_empty',
|
||||
'unique_id': 'softener_empty_zeo_duid',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[binary_sensor.zeo_one_softener-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'problem',
|
||||
'friendly_name': 'Zeo One Softener',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.zeo_one_softener',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
|
||||
@@ -0,0 +1,736 @@
|
||||
# serializer version: 1
|
||||
# name: test_buttons[button.roborock_s7_2_reset_air_filter_consumable-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'button.roborock_s7_2_reset_air_filter_consumable',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Reset air filter consumable',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Reset air filter consumable',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'reset_air_filter_consumable',
|
||||
'unique_id': 'reset_air_filter_consumable_device_2',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.roborock_s7_2_reset_air_filter_consumable-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Roborock S7 2 Reset air filter consumable',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.roborock_s7_2_reset_air_filter_consumable',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.roborock_s7_2_reset_main_brush_consumable-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'button.roborock_s7_2_reset_main_brush_consumable',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Reset main brush consumable',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Reset main brush consumable',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'reset_main_brush_consumable',
|
||||
'unique_id': 'reset_main_brush_consumable_device_2',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.roborock_s7_2_reset_main_brush_consumable-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Roborock S7 2 Reset main brush consumable',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.roborock_s7_2_reset_main_brush_consumable',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.roborock_s7_2_reset_sensor_consumable-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'button.roborock_s7_2_reset_sensor_consumable',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Reset sensor consumable',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Reset sensor consumable',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'reset_sensor_consumable',
|
||||
'unique_id': 'reset_sensor_consumable_device_2',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.roborock_s7_2_reset_sensor_consumable-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Roborock S7 2 Reset sensor consumable',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.roborock_s7_2_reset_sensor_consumable',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.roborock_s7_2_reset_side_brush_consumable-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'button.roborock_s7_2_reset_side_brush_consumable',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Reset side brush consumable',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Reset side brush consumable',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'reset_side_brush_consumable',
|
||||
'unique_id': 'reset_side_brush_consumable_device_2',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.roborock_s7_2_reset_side_brush_consumable-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Roborock S7 2 Reset side brush consumable',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.roborock_s7_2_reset_side_brush_consumable',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.roborock_s7_2_sc1-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': None,
|
||||
'entity_id': 'button.roborock_s7_2_sc1',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'sc1',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'sc1',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '12_device_2',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.roborock_s7_2_sc1-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Roborock S7 2 sc1',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.roborock_s7_2_sc1',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.roborock_s7_2_sc2-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': None,
|
||||
'entity_id': 'button.roborock_s7_2_sc2',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'sc2',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'sc2',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '24_device_2',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.roborock_s7_2_sc2-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Roborock S7 2 sc2',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.roborock_s7_2_sc2',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.roborock_s7_maxv_reset_air_filter_consumable-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'button.roborock_s7_maxv_reset_air_filter_consumable',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Reset air filter consumable',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Reset air filter consumable',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'reset_air_filter_consumable',
|
||||
'unique_id': 'reset_air_filter_consumable_abc123',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.roborock_s7_maxv_reset_air_filter_consumable-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Roborock S7 MaxV Reset air filter consumable',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.roborock_s7_maxv_reset_air_filter_consumable',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.roborock_s7_maxv_reset_main_brush_consumable-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'button.roborock_s7_maxv_reset_main_brush_consumable',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Reset main brush consumable',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Reset main brush consumable',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'reset_main_brush_consumable',
|
||||
'unique_id': 'reset_main_brush_consumable_abc123',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.roborock_s7_maxv_reset_main_brush_consumable-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Roborock S7 MaxV Reset main brush consumable',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.roborock_s7_maxv_reset_main_brush_consumable',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.roborock_s7_maxv_reset_sensor_consumable-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'button.roborock_s7_maxv_reset_sensor_consumable',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Reset sensor consumable',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Reset sensor consumable',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'reset_sensor_consumable',
|
||||
'unique_id': 'reset_sensor_consumable_abc123',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.roborock_s7_maxv_reset_sensor_consumable-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Roborock S7 MaxV Reset sensor consumable',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.roborock_s7_maxv_reset_sensor_consumable',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.roborock_s7_maxv_reset_side_brush_consumable-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'button.roborock_s7_maxv_reset_side_brush_consumable',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Reset side brush consumable',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Reset side brush consumable',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'reset_side_brush_consumable',
|
||||
'unique_id': 'reset_side_brush_consumable_abc123',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.roborock_s7_maxv_reset_side_brush_consumable-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Roborock S7 MaxV Reset side brush consumable',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.roborock_s7_maxv_reset_side_brush_consumable',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.roborock_s7_maxv_sc1-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': None,
|
||||
'entity_id': 'button.roborock_s7_maxv_sc1',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'sc1',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'sc1',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '12_abc123',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.roborock_s7_maxv_sc1-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Roborock S7 MaxV sc1',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.roborock_s7_maxv_sc1',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.roborock_s7_maxv_sc2-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': None,
|
||||
'entity_id': 'button.roborock_s7_maxv_sc2',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'sc2',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'sc2',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '24_abc123',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.roborock_s7_maxv_sc2-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Roborock S7 MaxV sc2',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.roborock_s7_maxv_sc2',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.zeo_one_pause-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': None,
|
||||
'entity_id': 'button.zeo_one_pause',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Pause',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Pause',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'pause',
|
||||
'unique_id': 'pause_zeo_duid',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.zeo_one_pause-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Zeo One Pause',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.zeo_one_pause',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.zeo_one_shutdown-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': None,
|
||||
'entity_id': 'button.zeo_one_shutdown',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Shutdown',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Shutdown',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'shutdown',
|
||||
'unique_id': 'shutdown_zeo_duid',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.zeo_one_shutdown-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Zeo One Shutdown',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.zeo_one_shutdown',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.zeo_one_start-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': None,
|
||||
'entity_id': 'button.zeo_one_start',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Start',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Start',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'start',
|
||||
'unique_id': 'start_zeo_duid',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[button.zeo_one_start-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Zeo One Start',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.zeo_one_start',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
@@ -3361,6 +3361,55 @@
|
||||
'state': 'drying',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.zeo_one_times_after_clean-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'sensor.zeo_one_times_after_clean',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Times after clean',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Times after clean',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'times_after_clean',
|
||||
'unique_id': 'times_after_clean_zeo_duid',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.zeo_one_times_after_clean-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Zeo One Times after clean',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.zeo_one_times_after_clean',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '5',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.zeo_one_washing_left-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -0,0 +1,442 @@
|
||||
# serializer version: 1
|
||||
# name: test_switches[switch.roborock_s7_2_do_not_disturb-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.roborock_s7_2_do_not_disturb',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Do not disturb',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Do not disturb',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'dnd_switch',
|
||||
'unique_id': 'dnd_switch_device_2',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[switch.roborock_s7_2_do_not_disturb-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Roborock S7 2 Do not disturb',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.roborock_s7_2_do_not_disturb',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[switch.roborock_s7_2_dock_child_lock-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.roborock_s7_2_dock_child_lock',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Child lock',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Child lock',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'child_lock',
|
||||
'unique_id': 'child_lock_device_2',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[switch.roborock_s7_2_dock_child_lock-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Roborock S7 2 Dock Child lock',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.roborock_s7_2_dock_child_lock',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[switch.roborock_s7_2_dock_status_indicator_light-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.roborock_s7_2_dock_status_indicator_light',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Status indicator light',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Status indicator light',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'status_indicator',
|
||||
'unique_id': 'status_indicator_device_2',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[switch.roborock_s7_2_dock_status_indicator_light-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Roborock S7 2 Dock Status indicator light',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.roborock_s7_2_dock_status_indicator_light',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[switch.roborock_s7_2_off_peak_charging-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.roborock_s7_2_off_peak_charging',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Off-peak charging',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Off-peak charging',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'off_peak_switch',
|
||||
'unique_id': 'off_peak_switch_device_2',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[switch.roborock_s7_2_off_peak_charging-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Roborock S7 2 Off-peak charging',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.roborock_s7_2_off_peak_charging',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[switch.roborock_s7_maxv_do_not_disturb-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.roborock_s7_maxv_do_not_disturb',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Do not disturb',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Do not disturb',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'dnd_switch',
|
||||
'unique_id': 'dnd_switch_abc123',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[switch.roborock_s7_maxv_do_not_disturb-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Roborock S7 MaxV Do not disturb',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.roborock_s7_maxv_do_not_disturb',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[switch.roborock_s7_maxv_dock_child_lock-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.roborock_s7_maxv_dock_child_lock',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Child lock',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Child lock',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'child_lock',
|
||||
'unique_id': 'child_lock_abc123',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[switch.roborock_s7_maxv_dock_child_lock-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Roborock S7 MaxV Dock Child lock',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.roborock_s7_maxv_dock_child_lock',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[switch.roborock_s7_maxv_dock_status_indicator_light-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.roborock_s7_maxv_dock_status_indicator_light',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Status indicator light',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Status indicator light',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'status_indicator',
|
||||
'unique_id': 'status_indicator_abc123',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[switch.roborock_s7_maxv_dock_status_indicator_light-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Roborock S7 MaxV Dock Status indicator light',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.roborock_s7_maxv_dock_status_indicator_light',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[switch.roborock_s7_maxv_off_peak_charging-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.roborock_s7_maxv_off_peak_charging',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Off-peak charging',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Off-peak charging',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'off_peak_switch',
|
||||
'unique_id': 'off_peak_switch_abc123',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[switch.roborock_s7_maxv_off_peak_charging-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Roborock S7 MaxV Off-peak charging',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.roborock_s7_maxv_off_peak_charging',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[switch.zeo_one_sound_setting-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.zeo_one_sound_setting',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Sound setting',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Sound setting',
|
||||
'platform': 'roborock',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'sound_setting',
|
||||
'unique_id': 'sound_setting_zeo_duid',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switches[switch.zeo_one_sound_setting-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Zeo One Sound setting',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.zeo_one_sound_setting',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
@@ -5,15 +5,17 @@ from unittest.mock import Mock
|
||||
import pytest
|
||||
from roborock import RoborockException
|
||||
from roborock.exceptions import RoborockTimeout
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.button import SERVICE_PRESS
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from .conftest import FakeDevice
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -28,6 +30,17 @@ def platforms() -> list[Platform]:
|
||||
return [Platform.BUTTON]
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_buttons(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
setup_entry: MockConfigEntry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test buttons and check test values are correctly set."""
|
||||
await snapshot_platform(hass, entity_registry, snapshot, setup_entry.entry_id)
|
||||
|
||||
|
||||
@pytest.fixture(name="consumeables_trait", autouse=True)
|
||||
def consumeables_trait_fixture(fake_vacuum: FakeDevice) -> Mock:
|
||||
"""Get the fake vacuum device command trait for asserting that commands happened."""
|
||||
@@ -179,3 +192,83 @@ async def test_press_routine_button_failure(
|
||||
routine_id
|
||||
)
|
||||
assert hass.states.get(entity_id).state == "2023-10-30T08:50:00+00:00"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("entity_id", "data_protocol"),
|
||||
[
|
||||
("button.zeo_one_start", "START"),
|
||||
("button.zeo_one_pause", "PAUSE"),
|
||||
("button.zeo_one_shutdown", "SHUTDOWN"),
|
||||
],
|
||||
)
|
||||
@pytest.mark.freeze_time("2023-10-30 08:50:00")
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_press_a01_button_success(
|
||||
hass: HomeAssistant,
|
||||
bypass_api_client_fixture: None,
|
||||
setup_entry: MockConfigEntry,
|
||||
entity_id: str,
|
||||
data_protocol: str,
|
||||
fake_devices: list[FakeDevice],
|
||||
) -> None:
|
||||
"""Test pressing A01 button entities."""
|
||||
# Get the washing machine (A01) device
|
||||
washing_machine = next(
|
||||
device
|
||||
for device in fake_devices
|
||||
if hasattr(device, "zeo") and device.zeo is not None
|
||||
)
|
||||
|
||||
# Ensure entity exists
|
||||
assert hass.states.get(entity_id) is not None
|
||||
|
||||
await hass.services.async_call(
|
||||
"button",
|
||||
SERVICE_PRESS,
|
||||
blocking=True,
|
||||
target={"entity_id": entity_id},
|
||||
)
|
||||
|
||||
# Verify the set_value was called with correct protocol and value
|
||||
washing_machine.zeo.set_value.assert_called_once()
|
||||
assert hass.states.get(entity_id).state == "2023-10-30T08:50:00+00:00"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("entity_id"),
|
||||
[
|
||||
("button.zeo_one_start"),
|
||||
],
|
||||
)
|
||||
@pytest.mark.freeze_time("2023-10-30 08:50:00")
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_press_a01_button_failure(
|
||||
hass: HomeAssistant,
|
||||
bypass_api_client_fixture: None,
|
||||
setup_entry: MockConfigEntry,
|
||||
entity_id: str,
|
||||
fake_devices: list[FakeDevice],
|
||||
) -> None:
|
||||
"""Test failure while pressing A01 button entity."""
|
||||
# Get the washing machine (A01) device
|
||||
washing_machine = next(
|
||||
device
|
||||
for device in fake_devices
|
||||
if hasattr(device, "zeo") and device.zeo is not None
|
||||
)
|
||||
washing_machine.zeo.set_value.side_effect = RoborockException
|
||||
|
||||
# Ensure entity exists
|
||||
assert hass.states.get(entity_id) is not None
|
||||
|
||||
with pytest.raises(HomeAssistantError, match="Failed to press button"):
|
||||
await hass.services.async_call(
|
||||
"button",
|
||||
SERVICE_PRESS,
|
||||
blocking=True,
|
||||
target={"entity_id": entity_id},
|
||||
)
|
||||
|
||||
washing_machine.zeo.set_value.assert_called_once()
|
||||
assert hass.states.get(entity_id).state == "2023-10-30T08:50:00+00:00"
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
"""Test Roborock Select platform."""
|
||||
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, call
|
||||
from unittest.mock import AsyncMock, Mock, call
|
||||
|
||||
import pytest
|
||||
from roborock import CleanTypeMapping, RoborockCommand
|
||||
from roborock.data import RoborockDockDustCollectionModeCode, WaterLevelMapping
|
||||
from roborock.data import (
|
||||
RoborockDockDustCollectionModeCode,
|
||||
WaterLevelMapping,
|
||||
ZeoProgram,
|
||||
)
|
||||
from roborock.exceptions import RoborockException
|
||||
from roborock.roborock_message import RoborockZeoProtocol
|
||||
|
||||
from homeassistant.components.roborock import DOMAIN
|
||||
from homeassistant.components.roborock.select import (
|
||||
A01_SELECT_DESCRIPTIONS,
|
||||
RoborockSelectEntityA01,
|
||||
)
|
||||
from homeassistant.const import SERVICE_SELECT_OPTION, STATE_UNKNOWN, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .conftest import FakeDevice
|
||||
@@ -278,3 +288,98 @@ async def test_update_success_q7_cleaning_mode(
|
||||
assert q7_device.b01_q7_properties.set_mode.call_count == 1
|
||||
|
||||
q7_device.b01_q7_properties.set_mode.assert_called_with(CleanTypeMapping.VACUUM)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def zeo_device(fake_devices: list[FakeDevice]) -> FakeDevice:
|
||||
"""Get the fake Zeo washing machine device."""
|
||||
return next(device for device in fake_devices if getattr(device, "zeo", None))
|
||||
|
||||
|
||||
async def test_update_success_zeo_program(
|
||||
hass: HomeAssistant,
|
||||
setup_entry: MockConfigEntry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
zeo_device: FakeDevice,
|
||||
) -> None:
|
||||
"""Test changing values for A01 Zeo select entities."""
|
||||
option = ZeoProgram.keys()[0]
|
||||
entity_id = entity_registry.async_get_entity_id(
|
||||
"select", DOMAIN, "program_zeo_duid"
|
||||
)
|
||||
assert entity_id is not None
|
||||
assert hass.states.get(entity_id) is not None
|
||||
|
||||
await hass.services.async_call(
|
||||
"select",
|
||||
SERVICE_SELECT_OPTION,
|
||||
service_data={"option": option},
|
||||
blocking=True,
|
||||
target={"entity_id": entity_id},
|
||||
)
|
||||
|
||||
assert zeo_device.zeo
|
||||
zeo_device.zeo.set_value.assert_awaited_once_with(
|
||||
RoborockZeoProtocol.PROGRAM,
|
||||
ZeoProgram.as_dict()[option],
|
||||
)
|
||||
|
||||
|
||||
async def test_current_option_zeo_program() -> None:
|
||||
"""Test current option retrieval for A01 Zeo select entities."""
|
||||
coordinator = Mock(
|
||||
duid_slug="zeo_duid",
|
||||
device_info=Mock(),
|
||||
data={RoborockZeoProtocol.PROGRAM: 1},
|
||||
api=AsyncMock(),
|
||||
async_request_refresh=AsyncMock(),
|
||||
)
|
||||
entity = RoborockSelectEntityA01(coordinator, A01_SELECT_DESCRIPTIONS[0])
|
||||
|
||||
assert entity.current_option == "1"
|
||||
coordinator.data = {}
|
||||
assert entity.current_option is None
|
||||
coordinator.data = {RoborockZeoProtocol.PROGRAM: None}
|
||||
assert entity.current_option is None
|
||||
|
||||
|
||||
async def test_update_failure_zeo_program(
|
||||
hass: HomeAssistant,
|
||||
setup_entry: MockConfigEntry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
zeo_device: FakeDevice,
|
||||
) -> None:
|
||||
"""Test failure while setting an A01 Zeo select option."""
|
||||
assert zeo_device.zeo
|
||||
zeo_device.zeo.set_value.side_effect = RoborockException
|
||||
option = ZeoProgram.keys()[0]
|
||||
entity_id = entity_registry.async_get_entity_id(
|
||||
"select", DOMAIN, "program_zeo_duid"
|
||||
)
|
||||
assert entity_id is not None
|
||||
|
||||
with pytest.raises(HomeAssistantError, match="Error while calling program"):
|
||||
await hass.services.async_call(
|
||||
"select",
|
||||
SERVICE_SELECT_OPTION,
|
||||
service_data={"option": option},
|
||||
blocking=True,
|
||||
target={"entity_id": entity_id},
|
||||
)
|
||||
|
||||
|
||||
async def test_update_failure_zeo_invalid_option() -> None:
|
||||
"""Test invalid option handling in A01 select entity."""
|
||||
coordinator = Mock(
|
||||
duid_slug="zeo_duid",
|
||||
device_info=Mock(),
|
||||
data={},
|
||||
api=AsyncMock(),
|
||||
async_request_refresh=AsyncMock(),
|
||||
)
|
||||
entity = RoborockSelectEntityA01(coordinator, A01_SELECT_DESCRIPTIONS[0])
|
||||
|
||||
with pytest.raises(ServiceValidationError):
|
||||
await entity.async_select_option("invalid_option")
|
||||
|
||||
coordinator.api.set_value.assert_not_called()
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
"""Test Roborock Switch platform."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from datetime import timedelta
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
import roborock
|
||||
from roborock.roborock_message import RoborockZeoProtocol
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.switch import SERVICE_TURN_OFF, SERVICE_TURN_ON
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .conftest import FakeDevice
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -22,6 +27,17 @@ def platforms() -> list[Platform]:
|
||||
return [Platform.SWITCH]
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_switches(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
setup_entry: MockConfigEntry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test switches and check test values are correctly set."""
|
||||
await snapshot_platform(hass, entity_registry, snapshot, setup_entry.entry_id)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("entity_id"),
|
||||
[
|
||||
@@ -115,3 +131,127 @@ async def test_update_failed(
|
||||
)
|
||||
|
||||
assert len(expected_call.mock_calls) == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("entity_id"),
|
||||
[
|
||||
("switch.zeo_one_sound_setting"),
|
||||
],
|
||||
)
|
||||
async def test_a01_switch_success(
|
||||
hass: HomeAssistant,
|
||||
setup_entry: MockConfigEntry,
|
||||
entity_id: str,
|
||||
fake_devices: list[FakeDevice],
|
||||
) -> None:
|
||||
"""Test turning A01 switch entities on and off."""
|
||||
# Get the washing machine (A01) device
|
||||
washing_machine = next(
|
||||
device
|
||||
for device in fake_devices
|
||||
if hasattr(device, "zeo") and device.zeo is not None
|
||||
)
|
||||
|
||||
# Verify entity exists
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.state == "off"
|
||||
|
||||
# Turn on the switch
|
||||
await hass.services.async_call(
|
||||
"switch",
|
||||
SERVICE_TURN_ON,
|
||||
service_data=None,
|
||||
blocking=True,
|
||||
target={"entity_id": entity_id},
|
||||
)
|
||||
# Verify set_value was called with the correct value (1 for on)
|
||||
washing_machine.zeo.set_value.assert_called_with(RoborockZeoProtocol.SOUND_SET, 1)
|
||||
|
||||
# Turn off the switch
|
||||
await hass.services.async_call(
|
||||
"switch",
|
||||
SERVICE_TURN_OFF,
|
||||
service_data=None,
|
||||
blocking=True,
|
||||
target={"entity_id": entity_id},
|
||||
)
|
||||
# Verify set_value was called with the correct value (0 for off)
|
||||
washing_machine.zeo.set_value.assert_called_with(RoborockZeoProtocol.SOUND_SET, 0)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("entity_id", "service"),
|
||||
[
|
||||
("switch.zeo_one_sound_setting", SERVICE_TURN_ON),
|
||||
("switch.zeo_one_sound_setting", SERVICE_TURN_OFF),
|
||||
],
|
||||
)
|
||||
async def test_a01_switch_failure(
|
||||
hass: HomeAssistant,
|
||||
setup_entry: MockConfigEntry,
|
||||
entity_id: str,
|
||||
service: str,
|
||||
fake_devices: list[FakeDevice],
|
||||
) -> None:
|
||||
"""Test a failure while updating an A01 switch."""
|
||||
# Get the washing machine (A01) device
|
||||
washing_machine = next(
|
||||
device
|
||||
for device in fake_devices
|
||||
if hasattr(device, "zeo") and device.zeo is not None
|
||||
)
|
||||
washing_machine.zeo.set_value.side_effect = roborock.exceptions.RoborockTimeout
|
||||
|
||||
# Ensure that the entity exists
|
||||
assert hass.states.get(entity_id) is not None
|
||||
|
||||
with pytest.raises(HomeAssistantError, match="Failed to update Roborock options"):
|
||||
await hass.services.async_call(
|
||||
"switch",
|
||||
service,
|
||||
service_data=None,
|
||||
blocking=True,
|
||||
target={"entity_id": entity_id},
|
||||
)
|
||||
|
||||
assert len(washing_machine.zeo.set_value.mock_calls) >= 1
|
||||
|
||||
|
||||
async def test_a01_switch_unknown_state(
|
||||
hass: HomeAssistant,
|
||||
setup_entry: MockConfigEntry,
|
||||
fake_devices: list[FakeDevice],
|
||||
) -> None:
|
||||
"""Test A01 switch returns unknown when API omits the protocol key."""
|
||||
entity_id = "switch.zeo_one_sound_setting"
|
||||
|
||||
# Verify entity exists with a known state initially
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.state == "off"
|
||||
|
||||
# Simulate the API returning data without the SOUND_SET key
|
||||
washing_machine = next(
|
||||
device
|
||||
for device in fake_devices
|
||||
if hasattr(device, "zeo") and device.zeo is not None
|
||||
)
|
||||
incomplete_data = {
|
||||
k: v
|
||||
for k, v in washing_machine.zeo.query_values.return_value.items()
|
||||
if k != RoborockZeoProtocol.SOUND_SET
|
||||
}
|
||||
washing_machine.zeo.query_values.return_value = incomplete_data
|
||||
|
||||
# Trigger a coordinator refresh
|
||||
async_fire_time_changed(
|
||||
hass,
|
||||
dt_util.utcnow() + timedelta(seconds=61),
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.state == "unknown"
|
||||
|
||||
Reference in New Issue
Block a user