Add globe light settings for Litter-Robot 4 (#152190)

This commit is contained in:
Nathan Spencer
2025-09-12 14:55:50 -04:00
committed by GitHub
parent 3de701a9ab
commit 124a63d846
5 changed files with 120 additions and 38 deletions

View File

@@ -31,6 +31,21 @@
"cycle_delay": {
"default": "mdi:timer-outline"
},
"globe_brightness": {
"default": "mdi:lightbulb-question",
"state": {
"low": "mdi:lightbulb-on-30",
"medium": "mdi:lightbulb-on-50",
"high": "mdi:lightbulb-on"
}
},
"globe_light": {
"state": {
"off": "mdi:lightbulb-off",
"on": "mdi:lightbulb-on",
"auto": "mdi:lightbulb-auto"
}
},
"meal_insert_size": {
"default": "mdi:scale"
}

View File

@@ -7,7 +7,7 @@ from dataclasses import dataclass
from typing import Any, Generic, TypeVar
from pylitterbot import FeederRobot, LitterRobot, LitterRobot4, Robot
from pylitterbot.robot.litterrobot4 import BrightnessLevel
from pylitterbot.robot.litterrobot4 import BrightnessLevel, NightLightMode
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.const import EntityCategory, UnitOfTime
@@ -32,8 +32,9 @@ class RobotSelectEntityDescription(
select_fn: Callable[[_WhiskerEntityT, str], Coroutine[Any, Any, bool]]
ROBOT_SELECT_MAP: dict[type[Robot], RobotSelectEntityDescription] = {
LitterRobot: RobotSelectEntityDescription[LitterRobot, int]( # type: ignore[type-abstract] # only used for isinstance check
ROBOT_SELECT_MAP: dict[type[Robot], tuple[RobotSelectEntityDescription, ...]] = {
LitterRobot: (
RobotSelectEntityDescription[LitterRobot, int]( # type: ignore[type-abstract] # only used for isinstance check
key="cycle_delay",
translation_key="cycle_delay",
unit_of_measurement=UnitOfTime.MINUTES,
@@ -41,7 +42,39 @@ ROBOT_SELECT_MAP: dict[type[Robot], RobotSelectEntityDescription] = {
options_fn=lambda robot: robot.VALID_WAIT_TIMES,
select_fn=lambda robot, opt: robot.set_wait_time(int(opt)),
),
LitterRobot4: RobotSelectEntityDescription[LitterRobot4, str](
),
LitterRobot4: (
RobotSelectEntityDescription[LitterRobot4, str](
key="globe_brightness",
translation_key="globe_brightness",
current_fn=(
lambda robot: bri.name.lower()
if (bri := robot.night_light_level) is not None
else None
),
options_fn=lambda _: [level.name.lower() for level in BrightnessLevel],
select_fn=(
lambda robot, opt: robot.set_night_light_brightness(
BrightnessLevel[opt.upper()]
)
),
),
RobotSelectEntityDescription[LitterRobot4, str](
key="globe_light",
translation_key="globe_light",
current_fn=(
lambda robot: mode.name.lower()
if (mode := robot.night_light_mode) is not None
else None
),
options_fn=lambda _: [mode.name.lower() for mode in NightLightMode],
select_fn=(
lambda robot, opt: robot.set_night_light_mode(
NightLightMode[opt.upper()]
)
),
),
RobotSelectEntityDescription[LitterRobot4, str](
key="panel_brightness",
translation_key="brightness_level",
current_fn=(
@@ -51,10 +84,14 @@ ROBOT_SELECT_MAP: dict[type[Robot], RobotSelectEntityDescription] = {
),
options_fn=lambda _: [level.name.lower() for level in BrightnessLevel],
select_fn=(
lambda robot, opt: robot.set_panel_brightness(BrightnessLevel[opt.upper()])
lambda robot, opt: robot.set_panel_brightness(
BrightnessLevel[opt.upper()]
)
),
),
FeederRobot: RobotSelectEntityDescription[FeederRobot, float](
),
FeederRobot: (
RobotSelectEntityDescription[FeederRobot, float](
key="meal_insert_size",
translation_key="meal_insert_size",
unit_of_measurement="cups",
@@ -62,6 +99,7 @@ ROBOT_SELECT_MAP: dict[type[Robot], RobotSelectEntityDescription] = {
options_fn=lambda robot: robot.VALID_MEAL_INSERT_SIZES,
select_fn=lambda robot, opt: robot.set_meal_insert_size(float(opt)),
),
),
}
@@ -77,8 +115,9 @@ async def async_setup_entry(
robot=robot, coordinator=coordinator, description=description
)
for robot in coordinator.account.robots
for robot_type, description in ROBOT_SELECT_MAP.items()
for robot_type, descriptions in ROBOT_SELECT_MAP.items()
if isinstance(robot, robot_type)
for description in descriptions
)

View File

@@ -144,6 +144,22 @@
"cycle_delay": {
"name": "Clean cycle wait time minutes"
},
"globe_brightness": {
"name": "Globe brightness",
"state": {
"low": "[%key:common::state::low%]",
"medium": "[%key:common::state::medium%]",
"high": "[%key:common::state::high%]"
}
},
"globe_light": {
"name": "Globe light",
"state": {
"auto": "[%key:common::state::auto%]",
"off": "[%key:common::state::off%]",
"on": "[%key:common::state::on%]"
}
},
"meal_insert_size": {
"name": "Meal insert size"
},

View File

@@ -39,8 +39,9 @@ ROBOT_4_DATA = {
"cleanCycleWaitTime": 15,
"isKeypadLockout": False,
"nightLightMode": "OFF",
"nightLightBrightness": 85,
"nightLightBrightness": 50,
"isPanelSleepMode": False,
"panelBrightnessHigh": 50,
"panelSleepTime": 0,
"panelWakeTime": 0,
"weekdaySleepModeEnabled": {

View File

@@ -19,7 +19,6 @@ 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(
@@ -69,26 +68,38 @@ async def test_invalid_wait_time_select(hass: HomeAssistant, mock_account) -> No
assert not mock_account.robots[0].set_wait_time.called
async def test_panel_brightness_select(
@pytest.mark.parametrize(
("entity_id", "initial_value", "robot_command"),
[
("select.test_globe_brightness", "medium", "set_night_light_brightness"),
("select.test_globe_light", "off", "set_night_light_mode"),
("select.test_panel_brightness", "medium", "set_panel_brightness"),
],
)
async def test_litterrobot_4_select(
hass: HomeAssistant,
mock_account_with_litterrobot_4: MagicMock,
entity_registry: er.EntityRegistry,
entity_id: str,
initial_value: str,
robot_command: str,
) -> None:
"""Tests the wait time select entity."""
"""Tests a Litter-Robot 4 select entity."""
await setup_integration(hass, mock_account_with_litterrobot_4, SELECT_DOMAIN)
select = hass.states.get(PANEL_BRIGHTNESS_ENTITY_ID)
select = hass.states.get(entity_id)
assert select
assert len(select.attributes[ATTR_OPTIONS]) == 3
assert select.state == initial_value
entity_entry = entity_registry.async_get(PANEL_BRIGHTNESS_ENTITY_ID)
entity_entry = entity_registry.async_get(entity_id)
assert entity_entry
assert entity_entry.entity_category is EntityCategory.CONFIG
data = {ATTR_ENTITY_ID: PANEL_BRIGHTNESS_ENTITY_ID}
data = {ATTR_ENTITY_ID: entity_id}
robot: LitterRobot4 = mock_account_with_litterrobot_4.robots[0]
robot.set_panel_brightness = AsyncMock(return_value=True)
setattr(robot, robot_command, AsyncMock(return_value=True))
for count, option in enumerate(select.attributes[ATTR_OPTIONS]):
data[ATTR_OPTION] = option
@@ -100,4 +111,4 @@ async def test_panel_brightness_select(
blocking=True,
)
assert robot.set_panel_brightness.call_count == count + 1
assert getattr(robot, robot_command).call_count == count + 1