mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 09:17:53 +00:00
Add panel brightness control for Litter-Robot 4 (#86269)
* Add panel brightness control for Litter-Robot 4 * Use translation_key * Fix test
This commit is contained in:
parent
091932c3ac
commit
cdefc48fcd
@ -12,5 +12,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pylitterbot"],
|
||||
"requirements": ["pylitterbot==2023.1.1"]
|
||||
"requirements": ["pylitterbot==2023.1.2"]
|
||||
}
|
||||
|
@ -3,10 +3,10 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable, Coroutine
|
||||
from dataclasses import dataclass
|
||||
import itertools
|
||||
from typing import Any, Generic, TypeVar
|
||||
|
||||
from pylitterbot import FeederRobot, LitterRobot
|
||||
from pylitterbot import FeederRobot, LitterRobot, LitterRobot4, Robot
|
||||
from pylitterbot.robot.litterrobot4 import BrightnessLevel
|
||||
|
||||
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@ -18,14 +18,21 @@ from .const import DOMAIN
|
||||
from .entity import LitterRobotEntity, _RobotT
|
||||
from .hub import LitterRobotHub
|
||||
|
||||
_CastTypeT = TypeVar("_CastTypeT", int, float)
|
||||
_CastTypeT = TypeVar("_CastTypeT", int, float, str)
|
||||
|
||||
BRIGHTNESS_LEVEL_ICON_MAP: dict[BrightnessLevel | None, str] = {
|
||||
BrightnessLevel.LOW: "mdi:lightbulb-on-30",
|
||||
BrightnessLevel.MEDIUM: "mdi:lightbulb-on-50",
|
||||
BrightnessLevel.HIGH: "mdi:lightbulb-on",
|
||||
None: "mdi:lightbulb-question",
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class RequiredKeysMixin(Generic[_RobotT, _CastTypeT]):
|
||||
"""A class that describes robot select entity required keys."""
|
||||
|
||||
current_fn: Callable[[_RobotT], _CastTypeT]
|
||||
current_fn: Callable[[_RobotT], _CastTypeT | None]
|
||||
options_fn: Callable[[_RobotT], list[_CastTypeT]]
|
||||
select_fn: Callable[[_RobotT, str], Coroutine[Any, Any, bool]]
|
||||
|
||||
@ -37,26 +44,42 @@ class RobotSelectEntityDescription(
|
||||
"""A class that describes robot select entities."""
|
||||
|
||||
entity_category: EntityCategory = EntityCategory.CONFIG
|
||||
icon_fn: Callable[[_RobotT], str] | None = None
|
||||
|
||||
|
||||
LITTER_ROBOT_SELECT = RobotSelectEntityDescription[LitterRobot, int](
|
||||
key="cycle_delay",
|
||||
name="Clean cycle wait time minutes",
|
||||
icon="mdi:timer-outline",
|
||||
unit_of_measurement=UnitOfTime.MINUTES,
|
||||
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)),
|
||||
)
|
||||
ROBOT_SELECT_MAP: dict[type[Robot], RobotSelectEntityDescription] = {
|
||||
LitterRobot: RobotSelectEntityDescription[LitterRobot, int](
|
||||
key="cycle_delay",
|
||||
name="Clean cycle wait time minutes",
|
||||
icon="mdi:timer-outline",
|
||||
unit_of_measurement=UnitOfTime.MINUTES,
|
||||
current_fn=lambda robot: robot.clean_cycle_wait_time_minutes,
|
||||
options_fn=lambda robot: robot.VALID_WAIT_TIMES,
|
||||
select_fn=lambda robot, opt: robot.set_wait_time(int(opt)),
|
||||
),
|
||||
LitterRobot4: RobotSelectEntityDescription[LitterRobot4, str](
|
||||
key="panel_brightness",
|
||||
name="Panel brightness",
|
||||
translation_key="brightness_level",
|
||||
current_fn=lambda robot: bri.name.lower()
|
||||
if (bri := robot.panel_brightness) is not None
|
||||
else None,
|
||||
options_fn=lambda _: [level.name.lower() for level in BrightnessLevel],
|
||||
select_fn=lambda robot, opt: robot.set_panel_brightness(
|
||||
BrightnessLevel[opt.upper()]
|
||||
),
|
||||
icon_fn=lambda robot: BRIGHTNESS_LEVEL_ICON_MAP[robot.panel_brightness],
|
||||
),
|
||||
FeederRobot: 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, opt: robot.set_meal_insert_size(float(opt)),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@ -66,22 +89,16 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Set up Litter-Robot selects using config entry."""
|
||||
hub: LitterRobotHub = hass.data[DOMAIN][config_entry.entry_id]
|
||||
entities: list[LitterRobotSelect] = list(
|
||||
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()
|
||||
),
|
||||
)
|
||||
)
|
||||
entities = [
|
||||
LitterRobotSelectEntity(robot=robot, hub=hub, description=description)
|
||||
for robot in hub.account.robots
|
||||
for robot_type, description in ROBOT_SELECT_MAP.items()
|
||||
if isinstance(robot, robot_type)
|
||||
]
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class LitterRobotSelect(
|
||||
class LitterRobotSelectEntity(
|
||||
LitterRobotEntity[_RobotT], SelectEntity, Generic[_RobotT, _CastTypeT]
|
||||
):
|
||||
"""Litter-Robot Select."""
|
||||
@ -99,6 +116,13 @@ class LitterRobotSelect(
|
||||
options = self.entity_description.options_fn(self.robot)
|
||||
self._attr_options = list(map(str, options))
|
||||
|
||||
@property
|
||||
def icon(self) -> str | None:
|
||||
"""Return the icon to use in the frontend, if any."""
|
||||
if icon_fn := self.entity_description.icon_fn:
|
||||
return str(icon_fn(self.robot))
|
||||
return super().icon
|
||||
|
||||
@property
|
||||
def current_option(self) -> str | None:
|
||||
"""Return the selected entity option to represent the entity state."""
|
||||
|
@ -62,6 +62,15 @@
|
||||
"spf": "Pinch Detect At Startup"
|
||||
}
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"brightness_level": {
|
||||
"state": {
|
||||
"low": "Low",
|
||||
"medium": "Medium",
|
||||
"high": "High"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1753,7 +1753,7 @@ pylibrespot-java==0.1.1
|
||||
pylitejet==0.5.0
|
||||
|
||||
# homeassistant.components.litterrobot
|
||||
pylitterbot==2023.1.1
|
||||
pylitterbot==2023.1.2
|
||||
|
||||
# homeassistant.components.lutron_caseta
|
||||
pylutron-caseta==0.18.1
|
||||
|
@ -1269,7 +1269,7 @@ pylibrespot-java==0.1.1
|
||||
pylitejet==0.5.0
|
||||
|
||||
# homeassistant.components.litterrobot
|
||||
pylitterbot==2023.1.1
|
||||
pylitterbot==2023.1.2
|
||||
|
||||
# homeassistant.components.lutron_caseta
|
||||
pylutron-caseta==0.18.1
|
||||
|
@ -1,9 +1,12 @@
|
||||
"""Test the Litter-Robot select entity."""
|
||||
from pylitterbot import LitterRobot3
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from pylitterbot import LitterRobot3, LitterRobot4
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.select import (
|
||||
ATTR_OPTION,
|
||||
ATTR_OPTIONS,
|
||||
DOMAIN as PLATFORM_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
)
|
||||
@ -14,6 +17,7 @@ from homeassistant.helpers import entity_registry as er
|
||||
from .conftest import setup_integration
|
||||
|
||||
SELECT_ENTITY_ID = "select.test_clean_cycle_wait_time_minutes"
|
||||
PANEL_BRIGHTNESS_ENTITY_ID = "select.test_panel_brightness"
|
||||
|
||||
|
||||
async def test_wait_time_select(
|
||||
@ -63,3 +67,38 @@ async def test_invalid_wait_time_select(hass: HomeAssistant, mock_account) -> No
|
||||
blocking=True,
|
||||
)
|
||||
assert not mock_account.robots[0].set_wait_time.called
|
||||
|
||||
|
||||
async def test_panel_brightness_select(
|
||||
hass: HomeAssistant,
|
||||
mock_account_with_litterrobot_4: MagicMock,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Tests the wait time select entity."""
|
||||
await setup_integration(hass, mock_account_with_litterrobot_4, PLATFORM_DOMAIN)
|
||||
|
||||
select = hass.states.get(PANEL_BRIGHTNESS_ENTITY_ID)
|
||||
assert select
|
||||
assert len(select.attributes[ATTR_OPTIONS]) == 3
|
||||
|
||||
entity_entry = entity_registry.async_get(PANEL_BRIGHTNESS_ENTITY_ID)
|
||||
assert entity_entry
|
||||
assert entity_entry.entity_category is EntityCategory.CONFIG
|
||||
|
||||
data = {ATTR_ENTITY_ID: PANEL_BRIGHTNESS_ENTITY_ID}
|
||||
|
||||
robot: LitterRobot4 = mock_account_with_litterrobot_4.robots[0]
|
||||
robot.set_panel_brightness = AsyncMock(return_value=True)
|
||||
count = 0
|
||||
for option in select.attributes[ATTR_OPTIONS]:
|
||||
count += 1
|
||||
data[ATTR_OPTION] = option
|
||||
|
||||
await hass.services.async_call(
|
||||
PLATFORM_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
data,
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert robot.set_panel_brightness.call_count == count
|
||||
|
Loading…
x
Reference in New Issue
Block a user