mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Convert LitterRobotHub to a DataUpdateCoordinator (#136283)
This commit is contained in:
parent
e3c836aa7d
commit
33f966a12e
@ -4,15 +4,12 @@ from __future__ import annotations
|
||||
|
||||
from pylitterbot import FeederRobot, LitterRobot, LitterRobot3, LitterRobot4, Robot
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceEntry
|
||||
|
||||
from .const import DOMAIN
|
||||
from .hub import LitterRobotHub
|
||||
|
||||
type LitterRobotConfigEntry = ConfigEntry[LitterRobotHub]
|
||||
from .coordinator import LitterRobotConfigEntry, LitterRobotDataUpdateCoordinator
|
||||
|
||||
PLATFORMS_BY_TYPE = {
|
||||
Robot: (
|
||||
@ -41,11 +38,11 @@ def get_platforms_for_robots(robots: list[Robot]) -> set[Platform]:
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: LitterRobotConfigEntry) -> bool:
|
||||
"""Set up Litter-Robot from a config entry."""
|
||||
hub = LitterRobotHub(hass, entry.data)
|
||||
await hub.login(load_robots=True, subscribe_for_updates=True)
|
||||
entry.runtime_data = hub
|
||||
coordinator = LitterRobotDataUpdateCoordinator(hass, entry)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
if platforms := get_platforms_for_robots(hub.account.robots):
|
||||
if platforms := get_platforms_for_robots(coordinator.account.robots):
|
||||
await hass.config_entries.async_forward_entry_setups(entry, platforms)
|
||||
return True
|
||||
|
||||
|
@ -17,7 +17,7 @@ from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import LitterRobotConfigEntry
|
||||
from .coordinator import LitterRobotConfigEntry
|
||||
from .entity import LitterRobotEntity, _RobotT
|
||||
|
||||
|
||||
@ -66,10 +66,12 @@ async def async_setup_entry(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Litter-Robot binary sensors using config entry."""
|
||||
hub = entry.runtime_data
|
||||
coordinator = entry.runtime_data
|
||||
async_add_entities(
|
||||
LitterRobotBinarySensorEntity(robot=robot, hub=hub, description=description)
|
||||
for robot in hub.account.robots
|
||||
LitterRobotBinarySensorEntity(
|
||||
robot=robot, coordinator=coordinator, description=description
|
||||
)
|
||||
for robot in coordinator.account.robots
|
||||
for robot_type, entity_descriptions in BINARY_SENSOR_MAP.items()
|
||||
if isinstance(robot, robot_type)
|
||||
for description in entity_descriptions
|
||||
|
@ -13,7 +13,7 @@ from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import LitterRobotConfigEntry
|
||||
from .coordinator import LitterRobotConfigEntry
|
||||
from .entity import LitterRobotEntity, _RobotT
|
||||
|
||||
|
||||
@ -51,14 +51,15 @@ async def async_setup_entry(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Litter-Robot cleaner using config entry."""
|
||||
hub = entry.runtime_data
|
||||
entities = [
|
||||
LitterRobotButtonEntity(robot=robot, hub=hub, description=description)
|
||||
for robot in hub.account.robots
|
||||
coordinator = entry.runtime_data
|
||||
async_add_entities(
|
||||
LitterRobotButtonEntity(
|
||||
robot=robot, coordinator=coordinator, description=description
|
||||
)
|
||||
for robot in coordinator.account.robots
|
||||
for robot_type, description in ROBOT_BUTTON_MAP.items()
|
||||
if isinstance(robot, robot_type)
|
||||
]
|
||||
async_add_entities(entities)
|
||||
)
|
||||
|
||||
|
||||
class LitterRobotButtonEntity(LitterRobotEntity[_RobotT], ButtonEntity):
|
||||
@ -69,4 +70,4 @@ class LitterRobotButtonEntity(LitterRobotEntity[_RobotT], ButtonEntity):
|
||||
async def async_press(self) -> None:
|
||||
"""Press the button."""
|
||||
await self.entity_description.press_fn(self.robot)
|
||||
self.coordinator.async_set_updated_data(True)
|
||||
self.coordinator.async_set_updated_data(None)
|
||||
|
@ -1,64 +1,66 @@
|
||||
"""A wrapper 'hub' for the Litter-Robot API."""
|
||||
"""The Litter-Robot coordinator."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Generator, Mapping
|
||||
from collections.abc import Generator
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from pylitterbot import Account, FeederRobot, LitterRobot
|
||||
from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
UPDATE_INTERVAL_SECONDS = 60 * 5
|
||||
UPDATE_INTERVAL = timedelta(minutes=5)
|
||||
|
||||
type LitterRobotConfigEntry = ConfigEntry[LitterRobotDataUpdateCoordinator]
|
||||
|
||||
|
||||
class LitterRobotHub:
|
||||
"""A Litter-Robot hub wrapper class."""
|
||||
class LitterRobotDataUpdateCoordinator(DataUpdateCoordinator[None]):
|
||||
"""The Litter-Robot data update coordinator."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, data: Mapping[str, Any]) -> None:
|
||||
"""Initialize the Litter-Robot hub."""
|
||||
self._data = data
|
||||
self.account = Account(websession=async_get_clientsession(hass))
|
||||
config_entry: LitterRobotConfigEntry
|
||||
|
||||
async def _async_update_data() -> bool:
|
||||
"""Update all device states from the Litter-Robot API."""
|
||||
await self.account.refresh_robots()
|
||||
return True
|
||||
|
||||
self.coordinator = DataUpdateCoordinator(
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, config_entry: LitterRobotConfigEntry
|
||||
) -> None:
|
||||
"""Initialize the Litter-Robot data update coordinator."""
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
config_entry=config_entry,
|
||||
name=DOMAIN,
|
||||
update_method=_async_update_data,
|
||||
update_interval=timedelta(seconds=UPDATE_INTERVAL_SECONDS),
|
||||
update_interval=UPDATE_INTERVAL,
|
||||
)
|
||||
|
||||
async def login(
|
||||
self, load_robots: bool = False, subscribe_for_updates: bool = False
|
||||
) -> None:
|
||||
"""Login to Litter-Robot."""
|
||||
self.account = Account(websession=async_get_clientsession(hass))
|
||||
|
||||
async def _async_update_data(self) -> None:
|
||||
"""Update all device states from the Litter-Robot API."""
|
||||
await self.account.refresh_robots()
|
||||
|
||||
async def _async_setup(self) -> None:
|
||||
"""Set up the coordinator."""
|
||||
try:
|
||||
await self.account.connect(
|
||||
username=self._data[CONF_USERNAME],
|
||||
password=self._data[CONF_PASSWORD],
|
||||
load_robots=load_robots,
|
||||
subscribe_for_updates=subscribe_for_updates,
|
||||
username=self.config_entry.data[CONF_USERNAME],
|
||||
password=self.config_entry.data[CONF_PASSWORD],
|
||||
load_robots=True,
|
||||
subscribe_for_updates=True,
|
||||
)
|
||||
except LitterRobotLoginException as ex:
|
||||
raise ConfigEntryAuthFailed("Invalid credentials") from ex
|
||||
except LitterRobotException as ex:
|
||||
raise ConfigEntryNotReady("Unable to connect to Litter-Robot API") from ex
|
||||
raise UpdateFailed("Unable to connect to Litter-Robot API") from ex
|
||||
|
||||
def litter_robots(self) -> Generator[LitterRobot]:
|
||||
"""Get Litter-Robots from the account."""
|
@ -9,44 +9,39 @@ from pylitterbot.robot import EVENT_UPDATE
|
||||
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import EntityDescription
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
)
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .hub import LitterRobotHub
|
||||
from .coordinator import LitterRobotDataUpdateCoordinator
|
||||
|
||||
_RobotT = TypeVar("_RobotT", bound=Robot)
|
||||
|
||||
|
||||
class LitterRobotEntity(
|
||||
CoordinatorEntity[DataUpdateCoordinator[bool]], Generic[_RobotT]
|
||||
CoordinatorEntity[LitterRobotDataUpdateCoordinator], Generic[_RobotT]
|
||||
):
|
||||
"""Generic Litter-Robot entity representing common data and methods."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self, robot: _RobotT, hub: LitterRobotHub, description: EntityDescription
|
||||
self,
|
||||
robot: _RobotT,
|
||||
coordinator: LitterRobotDataUpdateCoordinator,
|
||||
description: EntityDescription,
|
||||
) -> None:
|
||||
"""Pass coordinator to CoordinatorEntity."""
|
||||
super().__init__(hub.coordinator)
|
||||
super().__init__(coordinator)
|
||||
self.robot = robot
|
||||
self.hub = hub
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{self.robot.serial}-{description.key}"
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return the device information for a Litter-Robot."""
|
||||
assert self.robot.serial
|
||||
return DeviceInfo(
|
||||
identifiers={(DOMAIN, self.robot.serial)},
|
||||
manufacturer="Litter-Robot",
|
||||
model=self.robot.model,
|
||||
name=self.robot.name,
|
||||
sw_version=getattr(self.robot, "firmware", None),
|
||||
self._attr_unique_id = f"{robot.serial}-{description.key}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, robot.serial)},
|
||||
manufacturer="Whisker",
|
||||
model=robot.model,
|
||||
name=robot.name,
|
||||
serial_number=robot.serial,
|
||||
sw_version=getattr(robot, "firmware", None),
|
||||
)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
|
@ -14,9 +14,8 @@ from homeassistant.const import EntityCategory, UnitOfTime
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import LitterRobotConfigEntry
|
||||
from .coordinator import LitterRobotConfigEntry, LitterRobotDataUpdateCoordinator
|
||||
from .entity import LitterRobotEntity, _RobotT
|
||||
from .hub import LitterRobotHub
|
||||
|
||||
_CastTypeT = TypeVar("_CastTypeT", int, float, str)
|
||||
|
||||
@ -72,14 +71,15 @@ async def async_setup_entry(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Litter-Robot selects using config entry."""
|
||||
hub = entry.runtime_data
|
||||
entities = [
|
||||
LitterRobotSelectEntity(robot=robot, hub=hub, description=description)
|
||||
for robot in hub.account.robots
|
||||
coordinator = entry.runtime_data
|
||||
async_add_entities(
|
||||
LitterRobotSelectEntity(
|
||||
robot=robot, coordinator=coordinator, description=description
|
||||
)
|
||||
for robot in coordinator.account.robots
|
||||
for robot_type, description in ROBOT_SELECT_MAP.items()
|
||||
if isinstance(robot, robot_type)
|
||||
]
|
||||
async_add_entities(entities)
|
||||
)
|
||||
|
||||
|
||||
class LitterRobotSelectEntity(
|
||||
@ -92,11 +92,11 @@ class LitterRobotSelectEntity(
|
||||
def __init__(
|
||||
self,
|
||||
robot: _RobotT,
|
||||
hub: LitterRobotHub,
|
||||
coordinator: LitterRobotDataUpdateCoordinator,
|
||||
description: RobotSelectEntityDescription[_RobotT, _CastTypeT],
|
||||
) -> None:
|
||||
"""Initialize a Litter-Robot select entity."""
|
||||
super().__init__(robot, hub, description)
|
||||
super().__init__(robot, coordinator, description)
|
||||
options = self.entity_description.options_fn(self.robot)
|
||||
self._attr_options = list(map(str, options))
|
||||
|
||||
|
@ -19,7 +19,7 @@ from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfMass
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import LitterRobotConfigEntry
|
||||
from .coordinator import LitterRobotConfigEntry
|
||||
from .entity import LitterRobotEntity, _RobotT
|
||||
|
||||
|
||||
@ -159,12 +159,13 @@ async def async_setup_entry(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Litter-Robot sensors using config entry."""
|
||||
hub = entry.runtime_data
|
||||
entities = [
|
||||
LitterRobotSensorEntity(robot=robot, hub=hub, description=description)
|
||||
for robot in hub.account.robots
|
||||
coordinator = entry.runtime_data
|
||||
async_add_entities(
|
||||
LitterRobotSensorEntity(
|
||||
robot=robot, coordinator=coordinator, description=description
|
||||
)
|
||||
for robot in coordinator.account.robots
|
||||
for robot_type, entity_descriptions in ROBOT_SENSOR_MAP.items()
|
||||
if isinstance(robot, robot_type)
|
||||
for description in entity_descriptions
|
||||
]
|
||||
async_add_entities(entities)
|
||||
)
|
||||
|
@ -13,7 +13,7 @@ from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import LitterRobotConfigEntry
|
||||
from .coordinator import LitterRobotConfigEntry
|
||||
from .entity import LitterRobotEntity, _RobotT
|
||||
|
||||
|
||||
@ -48,14 +48,13 @@ async def async_setup_entry(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Litter-Robot switches using config entry."""
|
||||
hub = entry.runtime_data
|
||||
entities = [
|
||||
RobotSwitchEntity(robot=robot, hub=hub, description=description)
|
||||
coordinator = entry.runtime_data
|
||||
async_add_entities(
|
||||
RobotSwitchEntity(robot=robot, coordinator=coordinator, description=description)
|
||||
for description in ROBOT_SWITCHES
|
||||
for robot in hub.account.robots
|
||||
for robot in coordinator.account.robots
|
||||
if isinstance(robot, (LitterRobot, FeederRobot))
|
||||
]
|
||||
async_add_entities(entities)
|
||||
)
|
||||
|
||||
|
||||
class RobotSwitchEntity(LitterRobotEntity[_RobotT], SwitchEntity):
|
||||
|
@ -15,7 +15,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from . import LitterRobotConfigEntry
|
||||
from .coordinator import LitterRobotConfigEntry
|
||||
from .entity import LitterRobotEntity, _RobotT
|
||||
|
||||
|
||||
@ -52,15 +52,15 @@ async def async_setup_entry(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Litter-Robot cleaner using config entry."""
|
||||
hub = entry.runtime_data
|
||||
coordinator = entry.runtime_data
|
||||
async_add_entities(
|
||||
[
|
||||
LitterRobotTimeEntity(
|
||||
robot=robot, hub=hub, description=LITTER_ROBOT_3_SLEEP_START
|
||||
)
|
||||
for robot in hub.litter_robots()
|
||||
if isinstance(robot, LitterRobot3)
|
||||
]
|
||||
LitterRobotTimeEntity(
|
||||
robot=robot,
|
||||
coordinator=coordinator,
|
||||
description=LITTER_ROBOT_3_SLEEP_START,
|
||||
)
|
||||
for robot in coordinator.litter_robots()
|
||||
if isinstance(robot, LitterRobot3)
|
||||
)
|
||||
|
||||
|
||||
|
@ -17,7 +17,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import LitterRobotConfigEntry
|
||||
from .coordinator import LitterRobotConfigEntry
|
||||
from .entity import LitterRobotEntity
|
||||
|
||||
SCAN_INTERVAL = timedelta(days=1)
|
||||
@ -34,12 +34,14 @@ async def async_setup_entry(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Litter-Robot update platform."""
|
||||
hub = entry.runtime_data
|
||||
entities = [
|
||||
RobotUpdateEntity(robot=robot, hub=hub, description=FIRMWARE_UPDATE_ENTITY)
|
||||
for robot in hub.litter_robots()
|
||||
coordinator = entry.runtime_data
|
||||
entities = (
|
||||
RobotUpdateEntity(
|
||||
robot=robot, coordinator=coordinator, description=FIRMWARE_UPDATE_ENTITY
|
||||
)
|
||||
for robot in coordinator.litter_robots()
|
||||
if isinstance(robot, LitterRobot4)
|
||||
]
|
||||
)
|
||||
async_add_entities(entities, True)
|
||||
|
||||
|
||||
|
@ -20,7 +20,7 @@ from homeassistant.helpers import config_validation as cv, entity_platform
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from . import LitterRobotConfigEntry
|
||||
from .coordinator import LitterRobotConfigEntry
|
||||
from .entity import LitterRobotEntity
|
||||
|
||||
SERVICE_SET_SLEEP_MODE = "set_sleep_mode"
|
||||
@ -49,12 +49,13 @@ async def async_setup_entry(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Litter-Robot cleaner using config entry."""
|
||||
hub = entry.runtime_data
|
||||
entities = [
|
||||
LitterRobotCleaner(robot=robot, hub=hub, description=LITTER_BOX_ENTITY)
|
||||
for robot in hub.litter_robots()
|
||||
]
|
||||
async_add_entities(entities)
|
||||
coordinator = entry.runtime_data
|
||||
async_add_entities(
|
||||
LitterRobotCleaner(
|
||||
robot=robot, coordinator=coordinator, description=LITTER_BOX_ENTITY
|
||||
)
|
||||
for robot in coordinator.litter_robots()
|
||||
)
|
||||
|
||||
platform = entity_platform.async_get_current_platform()
|
||||
platform.async_register_entity_service(
|
||||
|
@ -117,7 +117,7 @@ def mock_account_with_side_effects() -> MagicMock:
|
||||
async def setup_integration(
|
||||
hass: HomeAssistant, mock_account: MagicMock, platform_domain: str | None = None
|
||||
) -> MockConfigEntry:
|
||||
"""Load a Litter-Robot platform with the provided hub."""
|
||||
"""Load a Litter-Robot platform with the provided coordinator."""
|
||||
entry = MockConfigEntry(
|
||||
domain=litterrobot.DOMAIN,
|
||||
data=CONFIG[litterrobot.DOMAIN],
|
||||
@ -126,7 +126,7 @@ async def setup_integration(
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.litterrobot.hub.Account",
|
||||
"homeassistant.components.litterrobot.coordinator.Account",
|
||||
return_value=mock_account,
|
||||
),
|
||||
patch(
|
||||
|
@ -63,7 +63,7 @@ async def test_entry_not_setup(
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.litterrobot.hub.Account.connect",
|
||||
"homeassistant.components.litterrobot.coordinator.Account.connect",
|
||||
side_effect=side_effect,
|
||||
):
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
|
Loading…
x
Reference in New Issue
Block a user