Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
3691ee9
initial support for roborock washing machine
yangqian Dec 22, 2025
6f46863
fix commented out code
yangqian Dec 22, 2025
71e78d2
Format A01 code: remove commented lines, fix trailing spaces, use laz…
yangqian Dec 22, 2025
1d8c12d
ruff format
yangqian Dec 22, 2025
1965044
Refactor imports in Roborock components: remove unused imports and cl…
yangqian Dec 22, 2025
67b1510
Format button and switch code: improve readability by breaking long l…
yangqian Dec 22, 2025
628f6bc
Address PR review comments for roborock washing machine integration
yangqian Dec 30, 2025
b68d653
revert changes on battery sensor and error descriptions for A01 devices
yangqian Dec 30, 2025
85d6000
Remove unnecessary condition for A01 switch descriptions in setup entry
yangqian Dec 30, 2025
2c0d912
Rename A01 button descriptions to ZEO_BUTTON_DESCRIPTIONS and update …
yangqian Jan 10, 2026
9c2123b
Format import statements for improved readability
yangqian Feb 13, 2026
1f2baac
Update homeassistant/components/roborock/strings.json
yangqian Feb 13, 2026
f053d2d
Update softener_type snapshot to match mock data (medium, not low)
yangqian Feb 13, 2026
18b290d
Move detergent_type and softener_type from sensor to select
yangqian Feb 14, 2026
bd92367
Update homeassistant/components/roborock/strings.json
yangqian Feb 16, 2026
2f65e2b
Update homeassistant/components/roborock/strings.json
yangqian Feb 16, 2026
de3b331
Merge branch 'dev' into roborock-cloud-push-for-A01-devices
yangqian Feb 16, 2026
8c85379
Move Zeo detergent and softener empty to binary sensors
yangqian Feb 16, 2026
0c11354
move async requet out of try block
yangqian Feb 16, 2026
3d0309d
Add ServiceValidationError for invalid select options in RoborockSele…
yangqian Feb 16, 2026
389d690
Handle Roborock command failures with appropriate error messaging in …
yangqian Feb 17, 2026
c08ff68
Add tests for A01 Zeo select entities including success and failure s…
yangqian Feb 17, 2026
31028ad
Add detergent and softener states in translations
yangqian Feb 17, 2026
5def17b
Add snapshot tests for roborock button and switch entities
yangqian Feb 17, 2026
8abb99f
Update binary sensor snapshots for washing machine entities
yangqian Feb 17, 2026
02e5e9c
Add test coverage for A01 select entity None value handling
yangqian Feb 17, 2026
2ac66e2
Update homeassistant/components/roborock/strings.json
yangqian Feb 17, 2026
d0680b8
Merge branch 'dev' into roborock-cloud-push-for-A01-devices
yangqian Feb 17, 2026
f8ea6c0
Merge branch 'dev' into roborock-cloud-push-for-A01-devices
yangqian Feb 21, 2026
240224d
Merge remote-tracking branch 'origin/dev' into roborock-cloud-push-fo…
yangqian Feb 24, 2026
6671238
Remove unused RoborockDataUpdateCoordinatorB01 import from sensor.py
yangqian Feb 24, 2026
da05a60
Filter A01 select and switch entities by supported data protocols
yangqian Feb 24, 2026
52ef3ee
Remove unused Dyad Pro Sound setting snapshots from test_switch.ambr
yangqian Feb 24, 2026
6e38b7d
Merge branch 'dev' into roborock-cloud-push-for-A01-devices
yangqian Feb 24, 2026
0c11573
Remove unused error states and add aftercare status in Roborock strings
yangqian Feb 24, 2026
0fb54b2
Merge branch 'dev' into roborock-cloud-push-for-A01-devices
yangqian Feb 24, 2026
2bbe027
Merge branch 'dev' into roborock-cloud-push-for-A01-devices
joostlek Feb 24, 2026
2df26e9
Add edge test for A01 switch handling unknown state when API omits pr…
yangqian Feb 25, 2026
87a88c0
Update drying error messages for clarity in Roborock integration
yangqian Feb 25, 2026
2cf3708
Remove unused value_fn from RoborockSensorDescriptionA01
yangqian Feb 25, 2026
2c1a5f9
Merge branch 'dev' into roborock-cloud-push-for-A01-devices
yangqian Feb 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 70 additions & 3 deletions homeassistant/components/roborock/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from dataclasses import dataclass

from roborock.data import CleanFluidStatus, RoborockStateCode
from roborock.roborock_message import RoborockZeoProtocol

from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
Expand All @@ -15,9 +16,15 @@
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
Expand All @@ -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",
Expand Down Expand Up @@ -111,21 +126,52 @@ class RoborockBinarySensorDescription(BinarySensorEntityDescription):
]


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,
)
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):
Expand All @@ -150,3 +196,24 @@ def __init__(
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)
77 changes: 75 additions & 2 deletions homeassistant/components/roborock/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

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
Expand All @@ -18,8 +19,13 @@
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__)

Expand Down Expand Up @@ -65,6 +71,32 @@ class RoborockButtonDescription(ButtonEntityDescription):
]


@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,
Expand Down Expand Up @@ -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
),
)
)

Expand Down Expand Up @@ -160,3 +201,35 @@ def __init__(
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(
Comment thread
allenporter marked this conversation as resolved.
translation_domain=DOMAIN,
translation_key="button_press_failed",

Copilot AI Dec 22, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The translation key "button_press_failed" is used here but is not defined in strings.json. This will result in an untranslated error message being shown to users. Please add the translation key to the "exceptions" section of strings.json.

Suggested change
translation_key="button_press_failed",
translation_key="command_failed",
translation_placeholders={
"command": "BUTTON_PRESS",
},

Copilot uses AI. Check for mistakes.
) from err
finally:
await self.coordinator.async_request_refresh()
12 changes: 12 additions & 0 deletions homeassistant/components/roborock/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,18 @@ def __init__(
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(
Expand Down
Loading
Loading