2021-04-11 20:35:25 +00:00
|
|
|
"""Litter-Robot entities for common data and methods."""
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
from datetime import time
|
|
|
|
import logging
|
|
|
|
from types import MethodType
|
|
|
|
from typing import Any
|
|
|
|
|
|
|
|
from pylitterbot import Robot
|
|
|
|
from pylitterbot.exceptions import InvalidCommandException
|
|
|
|
|
|
|
|
from homeassistant.core import callback
|
2021-05-01 22:37:19 +00:00
|
|
|
from homeassistant.helpers.entity import DeviceInfo
|
2021-04-11 20:35:25 +00:00
|
|
|
from homeassistant.helpers.event import async_call_later
|
|
|
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
|
|
|
import homeassistant.util.dt as dt_util
|
|
|
|
|
|
|
|
from .const import DOMAIN
|
|
|
|
from .hub import LitterRobotHub
|
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
REFRESH_WAIT_TIME_SECONDS = 8
|
|
|
|
|
|
|
|
|
|
|
|
class LitterRobotEntity(CoordinatorEntity):
|
|
|
|
"""Generic Litter-Robot entity representing common data and methods."""
|
|
|
|
|
|
|
|
def __init__(self, robot: Robot, entity_type: str, hub: LitterRobotHub) -> None:
|
|
|
|
"""Pass coordinator to CoordinatorEntity."""
|
|
|
|
super().__init__(hub.coordinator)
|
|
|
|
self.robot = robot
|
|
|
|
self.entity_type = entity_type
|
|
|
|
self.hub = hub
|
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self) -> str:
|
|
|
|
"""Return the name of this entity."""
|
|
|
|
return f"{self.robot.name} {self.entity_type}"
|
|
|
|
|
|
|
|
@property
|
|
|
|
def unique_id(self) -> str:
|
|
|
|
"""Return a unique ID."""
|
|
|
|
return f"{self.robot.serial}-{self.entity_type}"
|
|
|
|
|
|
|
|
@property
|
2021-05-01 22:37:19 +00:00
|
|
|
def device_info(self) -> DeviceInfo:
|
2021-04-11 20:35:25 +00:00
|
|
|
"""Return the device information for a Litter-Robot."""
|
|
|
|
return {
|
|
|
|
"identifiers": {(DOMAIN, self.robot.serial)},
|
|
|
|
"name": self.robot.name,
|
|
|
|
"manufacturer": "Litter-Robot",
|
|
|
|
"model": self.robot.model,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class LitterRobotControlEntity(LitterRobotEntity):
|
|
|
|
"""A Litter-Robot entity that can control the unit."""
|
|
|
|
|
|
|
|
def __init__(self, robot: Robot, entity_type: str, hub: LitterRobotHub) -> None:
|
|
|
|
"""Init a Litter-Robot control entity."""
|
|
|
|
super().__init__(robot=robot, entity_type=entity_type, hub=hub)
|
|
|
|
self._refresh_callback = None
|
|
|
|
|
|
|
|
async def perform_action_and_refresh(
|
|
|
|
self, action: MethodType, *args: Any, **kwargs: Any
|
|
|
|
) -> bool:
|
|
|
|
"""Perform an action and initiates a refresh of the robot data after a few seconds."""
|
|
|
|
|
|
|
|
try:
|
|
|
|
await action(*args, **kwargs)
|
2021-05-25 18:15:37 +00:00
|
|
|
except InvalidCommandException as ex: # pragma: no cover
|
|
|
|
# this exception should only occur if the underlying API for commands changes
|
2021-04-11 20:35:25 +00:00
|
|
|
_LOGGER.error(ex)
|
|
|
|
return False
|
|
|
|
|
|
|
|
self.async_cancel_refresh_callback()
|
|
|
|
self._refresh_callback = async_call_later(
|
|
|
|
self.hass, REFRESH_WAIT_TIME_SECONDS, self.async_call_later_callback
|
|
|
|
)
|
|
|
|
return True
|
|
|
|
|
|
|
|
async def async_call_later_callback(self, *_) -> None:
|
|
|
|
"""Perform refresh request on callback."""
|
|
|
|
self._refresh_callback = None
|
|
|
|
await self.coordinator.async_request_refresh()
|
|
|
|
|
|
|
|
async def async_will_remove_from_hass(self) -> None:
|
|
|
|
"""Cancel refresh callback when entity is being removed from hass."""
|
|
|
|
self.async_cancel_refresh_callback()
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def async_cancel_refresh_callback(self):
|
|
|
|
"""Clear the refresh callback if it has not already fired."""
|
|
|
|
if self._refresh_callback is not None:
|
|
|
|
self._refresh_callback()
|
|
|
|
self._refresh_callback = None
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def parse_time_at_default_timezone(time_str: str) -> time | None:
|
|
|
|
"""Parse a time string and add default timezone."""
|
|
|
|
parsed_time = dt_util.parse_time(time_str)
|
|
|
|
|
|
|
|
if parsed_time is None:
|
|
|
|
return None
|
|
|
|
|
|
|
|
return (
|
|
|
|
dt_util.start_of_local_day()
|
|
|
|
.replace(
|
|
|
|
hour=parsed_time.hour,
|
|
|
|
minute=parsed_time.minute,
|
|
|
|
second=parsed_time.second,
|
|
|
|
)
|
|
|
|
.timetz()
|
|
|
|
)
|