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 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.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
@ -9,7 +14,7 @@ from homeassistant.exceptions import ConfigEntryNotReady
from .const import DOMAIN from .const import DOMAIN
from .hub import LitterRobotHub 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: 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".""" """Support for Litter-Robot "Vacuum"."""
from __future__ import annotations from __future__ import annotations
import logging
from typing import Any from typing import Any
from pylitterbot.enums import LitterBoxStatus from pylitterbot.enums import LitterBoxStatus
@ -29,6 +30,8 @@ from .const import DOMAIN
from .entity import LitterRobotControlEntity from .entity import LitterRobotControlEntity
from .hub import LitterRobotHub from .hub import LitterRobotHub
_LOGGER = logging.getLogger(__name__)
SUPPORT_LITTERROBOT = ( SUPPORT_LITTERROBOT = (
SUPPORT_START | SUPPORT_STATE | SUPPORT_STATUS | SUPPORT_TURN_OFF | SUPPORT_TURN_ON 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: async def async_reset_waste_drawer(self) -> None:
"""Reset the waste drawer level.""" """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() await self.robot.reset_waste_drawer()
self.coordinator.async_set_updated_data(True) 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: async def async_set_wait_time(self, minutes: int) -> None:
"""Set the wait time.""" """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) await self.perform_action_and_refresh(self.robot.set_wait_time, minutes)
@property @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.""" """Test the Litter-Robot vacuum entity."""
from __future__ import annotations
from datetime import timedelta from datetime import timedelta
from typing import Any
from unittest.mock import MagicMock
import pytest import pytest
from voluptuous.error import MultipleInvalid 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.""" """Tests the vacuum entity was set up."""
await setup_integration(hass, mock_account, PLATFORM_DOMAIN) await setup_integration(hass, mock_account, PLATFORM_DOMAIN)
assert hass.services.has_service(DOMAIN, SERVICE_RESET_WASTE_DRAWER) 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( 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.""" """Tests the vacuum status when sleeping."""
await setup_integration(hass, mock_account_with_sleeping_robot, PLATFORM_DOMAIN) 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)" 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.""" """Tests the vacuum entity was set up."""
await setup_integration(hass, mock_account_with_no_robots, PLATFORM_DOMAIN) await setup_integration(hass, mock_account_with_no_robots, PLATFORM_DOMAIN)
assert not hass.services.has_service(DOMAIN, SERVICE_RESET_WASTE_DRAWER) 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.""" """Tests a vacuum entity with an error."""
await setup_integration(hass, mock_account_with_error, PLATFORM_DOMAIN) 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_START, "start_cleaning", None),
(SERVICE_TURN_OFF, "set_power_status", None), (SERVICE_TURN_OFF, "set_power_status", None),
(SERVICE_TURN_ON, "set_power_status", None), (SERVICE_TURN_ON, "set_power_status", None),
( (SERVICE_RESET_WASTE_DRAWER, "reset_waste_drawer", {"deprecated": True}),
SERVICE_RESET_WASTE_DRAWER,
"reset_waste_drawer",
None,
),
( (
SERVICE_SET_SLEEP_MODE, SERVICE_SET_SLEEP_MODE,
"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, SERVICE_SET_WAIT_TIME,
"set_sleep_mode", "set_wait_time",
{"enabled": True}, {"data": {"minutes": 3}, "deprecated": True},
),
(
SERVICE_SET_SLEEP_MODE,
"set_sleep_mode",
{"enabled": False},
), ),
( (
SERVICE_SET_WAIT_TIME, SERVICE_SET_WAIT_TIME,
"set_wait_time", "set_wait_time",
{"minutes": 3}, {"data": {"minutes": "15"}, "deprecated": True},
),
(
SERVICE_SET_WAIT_TIME,
"set_wait_time",
{"minutes": "15"},
), ),
], ],
) )
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.""" """Test sending commands to the vacuum."""
await setup_integration(hass, mock_account, PLATFORM_DOMAIN) 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
assert vacuum.state == STATE_DOCKED assert vacuum.state == STATE_DOCKED
data = {ATTR_ENTITY_ID: VACUUM_ENTITY_ID} extra = extra or {}
if extra: data = {ATTR_ENTITY_ID: VACUUM_ENTITY_ID, **extra.get("data", {})}
data.update(extra) deprecated = extra.get("deprecated", False)
await hass.services.async_call( await hass.services.async_call(
COMPONENT_SERVICE_DOMAIN.get(service, PLATFORM_DOMAIN), 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) future = utcnow() + timedelta(seconds=REFRESH_WAIT_TIME_SECONDS)
async_fire_time_changed(hass, future) async_fire_time_changed(hass, future)
getattr(mock_account.robots[0], command).assert_called_once() 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.""" """Test an attempt to send an invalid wait time to the vacuum."""
await setup_integration(hass, mock_account, PLATFORM_DOMAIN) await setup_integration(hass, mock_account, PLATFORM_DOMAIN)