Add Switch platform to Roborock (#93833)

* init

* add switch platform

* remove stale comments

* remove stale list

* set entity category to config
This commit is contained in:
Luke 2023-05-30 21:10:28 -04:00 committed by GitHub
parent 3a7f9ab57d
commit 049582ec50
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 185 additions and 1 deletions

View file

@ -6,4 +6,4 @@ CONF_ENTRY_CODE = "code"
CONF_BASE_URL = "base_url"
CONF_USER_DATA = "user_data"
PLATFORMS = [Platform.VACUUM, Platform.SELECT, Platform.SENSOR]
PLATFORMS = [Platform.VACUUM, Platform.SELECT, Platform.SENSOR, Platform.SWITCH]

View file

@ -97,6 +97,11 @@
}
}
},
"switch": {
"child_lock": {
"name": "Child lock"
}
},
"vacuum": {
"roborock": {
"state_attributes": {

View file

@ -0,0 +1,110 @@
"""Support for Roborock switch."""
from collections.abc import Callable, Coroutine
from dataclasses import dataclass
import logging
from typing import Any
from roborock.roborock_typing import RoborockCommand
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import slugify
from .const import DOMAIN
from .coordinator import RoborockDataUpdateCoordinator
from .device import RoborockCoordinatedEntity
_LOGGER = logging.getLogger(__name__)
@dataclass
class RoborockSwitchDescriptionMixin:
"""Define an entity description mixin for switch entities."""
# Gets the status of the switch
get_value: Callable[[RoborockCoordinatedEntity], Coroutine[Any, Any, dict]]
# Evaluate the result of get_value to determine a bool
evaluate_value: Callable[[dict], bool]
# Sets the status of the switch
set_command: Callable[[RoborockCoordinatedEntity, bool], Coroutine[Any, Any, dict]]
@dataclass
class RoborockSwitchDescription(
SwitchEntityDescription, RoborockSwitchDescriptionMixin
):
"""Class to describe an Roborock switch entity."""
SWITCH_DESCRIPTIONS: list[RoborockSwitchDescription] = [
RoborockSwitchDescription(
set_command=lambda entity, value: entity.send(
RoborockCommand.SET_CHILD_LOCK_STATUS, {"lock_status": 1 if value else 0}
),
get_value=lambda data: data.send(RoborockCommand.GET_CHILD_LOCK_STATUS),
evaluate_value=lambda data: data["lock_status"] == 1,
key="child_lock",
translation_key="child_lock",
icon="mdi:account-lock",
entity_category=EntityCategory.CONFIG,
)
]
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Roborock switch platform."""
coordinators: dict[str, RoborockDataUpdateCoordinator] = hass.data[DOMAIN][
config_entry.entry_id
]
async_add_entities(
(
RoborockSwitchEntity(
f"{description.key}_{slugify(device_id)}",
coordinator,
description,
)
for device_id, coordinator in coordinators.items()
for description in SWITCH_DESCRIPTIONS
),
True,
)
class RoborockSwitchEntity(RoborockCoordinatedEntity, SwitchEntity):
"""A class to let you turn functionality on Roborock devices on and off."""
entity_description: RoborockSwitchDescription
def __init__(
self,
unique_id: str,
coordinator: RoborockDataUpdateCoordinator,
entity_description: RoborockSwitchDescription,
) -> None:
"""Create a switch entity."""
self.entity_description = entity_description
super().__init__(unique_id, coordinator)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off the switch."""
await self.entity_description.set_command(self, False)
return self.async_schedule_update_ha_state(True)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the switch."""
await self.entity_description.set_command(self, True)
return self.async_schedule_update_ha_state(True)
async def async_update(self) -> None:
"""Update switch."""
self._attr_is_on = self.entity_description.evaluate_value(
await self.entity_description.get_value(self)
)

View file

@ -58,6 +58,8 @@ async def setup_entry(
), patch(
"homeassistant.components.roborock.coordinator.RoborockLocalClient.get_prop",
return_value=PROP,
), patch(
"homeassistant.components.roborock.coordinator.RoborockLocalClient.send_message"
):
assert await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()

View file

@ -0,0 +1,67 @@
"""Test Roborock Switch platform."""
from unittest.mock import patch
import pytest
from roborock.exceptions import RoborockException
from homeassistant.components.switch import SERVICE_TURN_OFF, SERVICE_TURN_ON
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from tests.common import MockConfigEntry
@pytest.mark.parametrize(
("entity_id"),
[
("switch.roborock_s7_maxv_child_lock"),
],
)
async def test_update_success(
hass: HomeAssistant,
bypass_api_fixture,
setup_entry: MockConfigEntry,
entity_id: str,
) -> None:
"""Test turning switch entities on and off."""
with patch(
"homeassistant.components.roborock.coordinator.RoborockLocalClient.send_message"
) as mock_send_message:
await hass.services.async_call(
"switch",
SERVICE_TURN_ON,
service_data=None,
blocking=True,
target={"entity_id": entity_id},
)
assert mock_send_message.assert_called_once
with patch(
"homeassistant.components.roborock.coordinator.RoborockLocalClient.send_message"
) as mock_send_message:
await hass.services.async_call(
"switch",
SERVICE_TURN_OFF,
service_data=None,
blocking=True,
target={"entity_id": entity_id},
)
assert mock_send_message.assert_called_once
async def test_update_failure(
hass: HomeAssistant,
bypass_api_fixture,
setup_entry: MockConfigEntry,
) -> None:
"""Test that changing a value will raise a homeassistanterror when it fails."""
with patch(
"homeassistant.components.roborock.coordinator.RoborockLocalClient.send_message",
side_effect=RoborockException(),
), pytest.raises(HomeAssistantError):
await hass.services.async_call(
"switch",
SERVICE_TURN_ON,
service_data=None,
blocking=True,
target={"entity_id": "switch.roborock_s7_maxv_child_lock"},
)