Add button to litterrobot (#59734)

This commit is contained in:
Nathan Spencer 2021-11-15 12:09:22 -07:00 committed by GitHub
parent ce9385d442
commit eaaa53d8d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 148 additions and 31 deletions

View File

@ -2,6 +2,11 @@
from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN
from homeassistant.components.select import DOMAIN as SELECT_DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.components.vacuum import DOMAIN as VACUUM_DOMAIN
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
@ -9,7 +14,7 @@ from homeassistant.exceptions import ConfigEntryNotReady
from .const import DOMAIN
from .hub import LitterRobotHub
PLATFORMS = ["select", "sensor", "switch", "vacuum"]
PLATFORMS = [BUTTON_DOMAIN, SELECT_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN, VACUUM_DOMAIN]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View File

@ -0,0 +1,43 @@
"""Support for Litter-Robot button."""
from __future__ import annotations
from homeassistant.components.button import ButtonEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ENTITY_CATEGORY_CONFIG
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from .entity import LitterRobotEntity
from .hub import LitterRobotHub
TYPE_RESET_WASTE_DRAWER = "Reset Waste Drawer"
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Litter-Robot cleaner using config entry."""
hub: LitterRobotHub = hass.data[DOMAIN][entry.entry_id]
async_add_entities(
[
LitterRobotResetWasteDrawerButton(
robot=robot, entity_type=TYPE_RESET_WASTE_DRAWER, hub=hub
)
for robot in hub.account.robots
]
)
class LitterRobotResetWasteDrawerButton(LitterRobotEntity, ButtonEntity):
"""Litter-Robot reset waste drawer button."""
_attr_icon = "mdi:delete-variant"
_attr_entity_category = ENTITY_CATEGORY_CONFIG
async def async_press(self) -> None:
"""Press the button."""
await self.robot.reset_waste_drawer()
self.coordinator.async_set_updated_data(True)

View File

@ -1,6 +1,7 @@
"""Support for Litter-Robot "Vacuum"."""
from __future__ import annotations
import logging
from typing import Any
from pylitterbot.enums import LitterBoxStatus
@ -29,6 +30,8 @@ from .const import DOMAIN
from .entity import LitterRobotControlEntity
from .hub import LitterRobotHub
_LOGGER = logging.getLogger(__name__)
SUPPORT_LITTERROBOT = (
SUPPORT_START | SUPPORT_STATE | SUPPORT_STATUS | SUPPORT_TURN_OFF | SUPPORT_TURN_ON
)
@ -121,6 +124,13 @@ class LitterRobotCleaner(LitterRobotControlEntity, StateVacuumEntity):
async def async_reset_waste_drawer(self) -> None:
"""Reset the waste drawer level."""
# The Litter-Robot reset waste drawer service has been replaced by a
# dedicated button entity and marked as deprecated
_LOGGER.warning(
"The 'litterrobot.reset_waste_drawer' service is deprecated and "
"replaced by a dedicated reset waste drawer button entity; Please "
"use that entity to reset the waste drawer instead"
)
await self.robot.reset_waste_drawer()
self.coordinator.async_set_updated_data(True)
@ -136,6 +146,13 @@ class LitterRobotCleaner(LitterRobotControlEntity, StateVacuumEntity):
async def async_set_wait_time(self, minutes: int) -> None:
"""Set the wait time."""
# The Litter-Robot set wait time service has been replaced by a
# dedicated select entity and marked as deprecated
_LOGGER.warning(
"The 'litterrobot.set_wait_time' service is deprecated and "
"replaced by a dedicated set wait time select entity; Please "
"use that entity to set the wait time instead"
)
await self.perform_action_and_refresh(self.robot.set_wait_time, minutes)
@property

View File

@ -0,0 +1,48 @@
"""Test the Litter-Robot button entity."""
from unittest.mock import MagicMock
from freezegun import freeze_time
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_ICON,
ENTITY_CATEGORY_CONFIG,
STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from .conftest import setup_integration
BUTTON_ENTITY = "button.test_reset_waste_drawer"
@freeze_time("2021-11-15 17:37:00", tz_offset=-7)
async def test_button(hass: HomeAssistant, mock_account: MagicMock) -> None:
"""Test the creation and values of the Litter-Robot button."""
await setup_integration(hass, mock_account, BUTTON_DOMAIN)
entity_registry = er.async_get(hass)
state = hass.states.get(BUTTON_ENTITY)
assert state
assert state.attributes.get(ATTR_ICON) == "mdi:delete-variant"
assert state.state == STATE_UNKNOWN
entry = entity_registry.async_get(BUTTON_ENTITY)
assert entry
assert entry.entity_category == ENTITY_CATEGORY_CONFIG
await hass.services.async_call(
BUTTON_DOMAIN,
SERVICE_PRESS,
{ATTR_ENTITY_ID: BUTTON_ENTITY},
blocking=True,
)
await hass.async_block_till_done()
assert mock_account.robots[0].reset_waste_drawer.call_count == 1
mock_account.robots[0].reset_waste_drawer.assert_called_with()
state = hass.states.get(BUTTON_ENTITY)
assert state
assert state.state == "2021-11-15T10:37:00+00:00"

View File

@ -1,5 +1,9 @@
"""Test the Litter-Robot vacuum entity."""
from __future__ import annotations
from datetime import timedelta
from typing import Any
from unittest.mock import MagicMock
import pytest
from voluptuous.error import MultipleInvalid
@ -36,7 +40,7 @@ COMPONENT_SERVICE_DOMAIN = {
}
async def test_vacuum(hass: HomeAssistant, mock_account):
async def test_vacuum(hass: HomeAssistant, mock_account: MagicMock) -> None:
"""Tests the vacuum entity was set up."""
await setup_integration(hass, mock_account, PLATFORM_DOMAIN)
assert hass.services.has_service(DOMAIN, SERVICE_RESET_WASTE_DRAWER)
@ -48,8 +52,8 @@ async def test_vacuum(hass: HomeAssistant, mock_account):
async def test_vacuum_status_when_sleeping(
hass: HomeAssistant, mock_account_with_sleeping_robot
):
hass: HomeAssistant, mock_account_with_sleeping_robot: MagicMock
) -> None:
"""Tests the vacuum status when sleeping."""
await setup_integration(hass, mock_account_with_sleeping_robot, PLATFORM_DOMAIN)
@ -58,14 +62,18 @@ async def test_vacuum_status_when_sleeping(
assert vacuum.attributes.get(ATTR_STATUS) == "Ready (Sleeping)"
async def test_no_robots(hass: HomeAssistant, mock_account_with_no_robots):
async def test_no_robots(
hass: HomeAssistant, mock_account_with_no_robots: MagicMock
) -> None:
"""Tests the vacuum entity was set up."""
await setup_integration(hass, mock_account_with_no_robots, PLATFORM_DOMAIN)
assert not hass.services.has_service(DOMAIN, SERVICE_RESET_WASTE_DRAWER)
async def test_vacuum_with_error(hass: HomeAssistant, mock_account_with_error):
async def test_vacuum_with_error(
hass: HomeAssistant, mock_account_with_error: MagicMock
) -> None:
"""Tests a vacuum entity with an error."""
await setup_integration(hass, mock_account_with_error, PLATFORM_DOMAIN)
@ -80,39 +88,34 @@ async def test_vacuum_with_error(hass: HomeAssistant, mock_account_with_error):
(SERVICE_START, "start_cleaning", None),
(SERVICE_TURN_OFF, "set_power_status", None),
(SERVICE_TURN_ON, "set_power_status", None),
(
SERVICE_RESET_WASTE_DRAWER,
"reset_waste_drawer",
None,
),
(SERVICE_RESET_WASTE_DRAWER, "reset_waste_drawer", {"deprecated": True}),
(
SERVICE_SET_SLEEP_MODE,
"set_sleep_mode",
{"enabled": True, "start_time": "22:30"},
{"data": {"enabled": True, "start_time": "22:30"}},
),
(SERVICE_SET_SLEEP_MODE, "set_sleep_mode", {"data": {"enabled": True}}),
(SERVICE_SET_SLEEP_MODE, "set_sleep_mode", {"data": {"enabled": False}}),
(
SERVICE_SET_SLEEP_MODE,
"set_sleep_mode",
{"enabled": True},
),
(
SERVICE_SET_SLEEP_MODE,
"set_sleep_mode",
{"enabled": False},
SERVICE_SET_WAIT_TIME,
"set_wait_time",
{"data": {"minutes": 3}, "deprecated": True},
),
(
SERVICE_SET_WAIT_TIME,
"set_wait_time",
{"minutes": 3},
),
(
SERVICE_SET_WAIT_TIME,
"set_wait_time",
{"minutes": "15"},
{"data": {"minutes": "15"}, "deprecated": True},
),
],
)
async def test_commands(hass: HomeAssistant, mock_account, service, command, extra):
async def test_commands(
hass: HomeAssistant,
mock_account: MagicMock,
caplog: pytest.LogCaptureFixture,
service: str,
command: str,
extra: dict[str, Any],
) -> None:
"""Test sending commands to the vacuum."""
await setup_integration(hass, mock_account, PLATFORM_DOMAIN)
@ -120,9 +123,9 @@ async def test_commands(hass: HomeAssistant, mock_account, service, command, ext
assert vacuum
assert vacuum.state == STATE_DOCKED
data = {ATTR_ENTITY_ID: VACUUM_ENTITY_ID}
if extra:
data.update(extra)
extra = extra or {}
data = {ATTR_ENTITY_ID: VACUUM_ENTITY_ID, **extra.get("data", {})}
deprecated = extra.get("deprecated", False)
await hass.services.async_call(
COMPONENT_SERVICE_DOMAIN.get(service, PLATFORM_DOMAIN),
@ -133,9 +136,10 @@ async def test_commands(hass: HomeAssistant, mock_account, service, command, ext
future = utcnow() + timedelta(seconds=REFRESH_WAIT_TIME_SECONDS)
async_fire_time_changed(hass, future)
getattr(mock_account.robots[0], command).assert_called_once()
assert (f"'{DOMAIN}.{service}' service is deprecated" in caplog.text) is deprecated
async def test_invalid_wait_time(hass: HomeAssistant, mock_account):
async def test_invalid_wait_time(hass: HomeAssistant, mock_account: MagicMock) -> None:
"""Test an attempt to send an invalid wait time to the vacuum."""
await setup_integration(hass, mock_account, PLATFORM_DOMAIN)