Add support for Feeder-Robot select (#77512)

* Add support for Feeder-Robot select

* Use lambda to get current selected option

* Use generics and required keys mixin

* Code improvements

* Even more generics

* Fix missing type hint

* Apply suggestions from code review

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
This commit is contained in:
Nathan Spencer 2022-08-31 05:23:51 -06:00 committed by GitHub
parent cfa838b27a
commit 5bc2f37bf8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1,18 +1,61 @@
"""Support for Litter-Robot selects."""
from __future__ import annotations
from pylitterbot import LitterRobot
from collections.abc import Callable, Coroutine
from dataclasses import dataclass
import itertools
from typing import Any, Generic, TypeVar
from homeassistant.components.select import SelectEntity
from pylitterbot import FeederRobot, LitterRobot
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from .entity import LitterRobotConfigEntity
from .entity import LitterRobotConfigEntity, _RobotT
from .hub import LitterRobotHub
TYPE_CLEAN_CYCLE_WAIT_TIME_MINUTES = "Clean Cycle Wait Time Minutes"
_CastTypeT = TypeVar("_CastTypeT", int, float)
@dataclass
class RequiredKeysMixin(Generic[_RobotT, _CastTypeT]):
"""A class that describes robot select entity required keys."""
current_fn: Callable[[_RobotT], _CastTypeT]
options_fn: Callable[[_RobotT], list[_CastTypeT]]
select_fn: Callable[
[_RobotT, str],
tuple[Callable[[_CastTypeT], Coroutine[Any, Any, bool]], _CastTypeT],
]
@dataclass
class RobotSelectEntityDescription(
SelectEntityDescription, RequiredKeysMixin[_RobotT, _CastTypeT]
):
"""A class that describes robot select entities."""
LITTER_ROBOT_SELECT = RobotSelectEntityDescription[LitterRobot, int](
key="clean_cycle_wait_time_minutes",
name="Clean Cycle Wait Time Minutes",
icon="mdi:timer-outline",
current_fn=lambda robot: robot.clean_cycle_wait_time_minutes,
options_fn=lambda robot: robot.VALID_WAIT_TIMES,
select_fn=lambda robot, option: (robot.set_wait_time, int(option)),
)
FEEDER_ROBOT_SELECT = RobotSelectEntityDescription[FeederRobot, float](
key="meal_insert_size",
name="Meal insert size",
icon="mdi:scale",
unit_of_measurement="cups",
current_fn=lambda robot: robot.meal_insert_size,
options_fn=lambda robot: robot.VALID_MEAL_INSERT_SIZES,
select_fn=lambda robot, option: (robot.set_meal_insert_size, float(option)),
)
async def async_setup_entry(
@ -22,30 +65,46 @@ async def async_setup_entry(
) -> None:
"""Set up Litter-Robot selects using config entry."""
hub: LitterRobotHub = hass.data[DOMAIN][config_entry.entry_id]
async_add_entities(
LitterRobotSelect(
robot=robot, entity_type=TYPE_CLEAN_CYCLE_WAIT_TIME_MINUTES, hub=hub
itertools.chain(
(
LitterRobotSelect(robot=robot, hub=hub, description=LITTER_ROBOT_SELECT)
for robot in hub.litter_robots()
),
(
LitterRobotSelect(robot=robot, hub=hub, description=FEEDER_ROBOT_SELECT)
for robot in hub.feeder_robots()
),
)
for robot in hub.litter_robots()
)
class LitterRobotSelect(LitterRobotConfigEntity[LitterRobot], SelectEntity):
class LitterRobotSelect(
LitterRobotConfigEntity[_RobotT], SelectEntity, Generic[_RobotT, _CastTypeT]
):
"""Litter-Robot Select."""
_attr_icon = "mdi:timer-outline"
entity_description: RobotSelectEntityDescription[_RobotT, _CastTypeT]
def __init__(
self,
robot: _RobotT,
hub: LitterRobotHub,
description: RobotSelectEntityDescription[_RobotT, _CastTypeT],
) -> None:
"""Initialize a Litter-Robot select entity."""
assert description.name
super().__init__(robot, description.name, hub)
self.entity_description = description
options = self.entity_description.options_fn(self.robot)
self._attr_options = list(map(str, options))
@property
def current_option(self) -> str | None:
"""Return the selected entity option to represent the entity state."""
return str(self.robot.clean_cycle_wait_time_minutes)
@property
def options(self) -> list[str]:
"""Return a set of selectable options."""
return [str(minute) for minute in self.robot.VALID_WAIT_TIMES]
return str(self.entity_description.current_fn(self.robot))
async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
await self.perform_action_and_refresh(self.robot.set_wait_time, int(option))
action, adjusted_option = self.entity_description.select_fn(self.robot, option)
await self.perform_action_and_refresh(action, adjusted_option)