mirror of
https://github.com/home-assistant/core.git
synced 2026-09-25 17:04:04 -04:00
Use hardware volume scale and gate manual brightness on auto mode
Max volume limits are on the player's 0-16 hardware step scale, not a percentage. Manual day/night display brightness sliders are unavailable while automatic brightness is active. Also add the missing repeat all icon. https://claude.ai/code/session_015G3sygJ3js3qaMvw7Kn2Jo
This commit is contained in:
@@ -95,6 +95,9 @@
|
||||
},
|
||||
"pause_volume_down": {
|
||||
"default": "mdi:volume-minus"
|
||||
},
|
||||
"repeat_all": {
|
||||
"default": "mdi:repeat"
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
|
||||
@@ -30,16 +30,18 @@ class YotoNumberEntityDescription(NumberEntityDescription):
|
||||
|
||||
value_fn: Callable[[PlayerConfig], int | None]
|
||||
config_field: str
|
||||
available_fn: Callable[[PlayerConfig], bool] = lambda config: True
|
||||
|
||||
|
||||
NUMBERS: tuple[YotoNumberEntityDescription, ...] = (
|
||||
# Max volume limits use the player's 16 hardware volume steps, not a
|
||||
# percentage — the same scale the firmware reports for playback volume.
|
||||
YotoNumberEntityDescription(
|
||||
key="day_max_volume_limit",
|
||||
translation_key="day_max_volume_limit",
|
||||
native_min_value=0,
|
||||
native_max_value=100,
|
||||
native_max_value=16,
|
||||
native_step=1,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
value_fn=lambda config: config.day_max_volume_limit,
|
||||
config_field="day_max_volume_limit",
|
||||
@@ -48,15 +50,15 @@ NUMBERS: tuple[YotoNumberEntityDescription, ...] = (
|
||||
key="night_max_volume_limit",
|
||||
translation_key="night_max_volume_limit",
|
||||
native_min_value=0,
|
||||
native_max_value=100,
|
||||
native_max_value=16,
|
||||
native_step=1,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
value_fn=lambda config: config.night_max_volume_limit,
|
||||
config_field="night_max_volume_limit",
|
||||
),
|
||||
# Day/night display brightness report None while auto brightness is
|
||||
# active; writing a value switches the player to manual brightness.
|
||||
# Manual day/night display brightness only applies while automatic
|
||||
# brightness is off; the sliders go unavailable while auto is active
|
||||
# (turn the matching auto-brightness switch off to use them).
|
||||
YotoNumberEntityDescription(
|
||||
key="day_display_brightness",
|
||||
translation_key="day_display_brightness",
|
||||
@@ -67,6 +69,7 @@ NUMBERS: tuple[YotoNumberEntityDescription, ...] = (
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
value_fn=lambda config: config.day_display_brightness,
|
||||
config_field="day_display_brightness",
|
||||
available_fn=lambda config: not config.day_display_brightness_auto,
|
||||
),
|
||||
YotoNumberEntityDescription(
|
||||
key="night_display_brightness",
|
||||
@@ -78,6 +81,7 @@ NUMBERS: tuple[YotoNumberEntityDescription, ...] = (
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
value_fn=lambda config: config.night_display_brightness,
|
||||
config_field="night_display_brightness",
|
||||
available_fn=lambda config: not config.night_display_brightness_auto,
|
||||
),
|
||||
YotoNumberEntityDescription(
|
||||
key="display_dim_brightness",
|
||||
@@ -149,6 +153,13 @@ class YotoNumber(YotoEntity, NumberEntity):
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{player.id}_{description.key}"
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return if the entity is available."""
|
||||
return super().available and self.entity_description.available_fn(
|
||||
self.player.info.config
|
||||
)
|
||||
|
||||
@property
|
||||
def native_value(self) -> int | None:
|
||||
"""Return the number value."""
|
||||
|
||||
@@ -95,13 +95,13 @@ def _build_player() -> YotoPlayer:
|
||||
day_time=dt_time(7, 0),
|
||||
day_display_brightness_auto=True,
|
||||
day_ambient_colour="#40bfd9",
|
||||
day_max_volume_limit=80,
|
||||
day_max_volume_limit=12,
|
||||
day_sounds_off=False,
|
||||
night_time=dt_time(19, 0),
|
||||
night_display_brightness_auto=False,
|
||||
night_display_brightness=40,
|
||||
night_ambient_colour="#000000",
|
||||
night_max_volume_limit=50,
|
||||
night_max_volume_limit=8,
|
||||
night_sounds_off=True,
|
||||
hour_format=12,
|
||||
bluetooth_enabled=True,
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
'day_ambient_colour': '#40bfd9',
|
||||
'day_display_brightness': None,
|
||||
'day_display_brightness_auto': True,
|
||||
'day_max_volume_limit': 80,
|
||||
'day_max_volume_limit': 12,
|
||||
'day_sounds_off': False,
|
||||
'day_time': dict({
|
||||
'__type': "<class 'datetime.time'>",
|
||||
@@ -78,7 +78,7 @@
|
||||
'night_ambient_colour': '#000000',
|
||||
'night_display_brightness': 40,
|
||||
'night_display_brightness_auto': False,
|
||||
'night_max_volume_limit': 50,
|
||||
'night_max_volume_limit': 8,
|
||||
'night_sounds_off': True,
|
||||
'night_time': dict({
|
||||
'__type': "<class 'datetime.time'>",
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': 'unavailable',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[number.nursery_yoto_day_maximum_volume-entry]
|
||||
@@ -127,7 +127,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 100,
|
||||
'max': 16,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 1,
|
||||
@@ -159,25 +159,24 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'day_max_volume_limit',
|
||||
'unique_id': 'player-test_day_max_volume_limit',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[number.nursery_yoto_day_maximum_volume-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Nursery Yoto Day maximum volume',
|
||||
'max': 100,
|
||||
'max': 16,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 1,
|
||||
'unit_of_measurement': '%',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.nursery_yoto_day_maximum_volume',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '80',
|
||||
'state': '12',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[number.nursery_yoto_dimmed_display_brightness-entry]
|
||||
@@ -368,7 +367,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 100,
|
||||
'max': 16,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 1,
|
||||
@@ -400,24 +399,23 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'night_max_volume_limit',
|
||||
'unique_id': 'player-test_night_max_volume_limit',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[number.nursery_yoto_night_maximum_volume-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Nursery Yoto Night maximum volume',
|
||||
'max': 100,
|
||||
'max': 16,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 1,
|
||||
'unit_of_measurement': '%',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.nursery_yoto_night_maximum_volume',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '50',
|
||||
'state': '8',
|
||||
})
|
||||
# ---
|
||||
|
||||
@@ -11,7 +11,7 @@ from homeassistant.components.number import (
|
||||
DOMAIN as NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, Platform
|
||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
@@ -48,22 +48,16 @@ async def test_all_entities(
|
||||
[
|
||||
pytest.param(
|
||||
"number.nursery_yoto_day_maximum_volume",
|
||||
70,
|
||||
{"day_max_volume_limit": 70},
|
||||
14,
|
||||
{"day_max_volume_limit": 14},
|
||||
id="day-max-volume",
|
||||
),
|
||||
pytest.param(
|
||||
"number.nursery_yoto_night_maximum_volume",
|
||||
30,
|
||||
{"night_max_volume_limit": 30},
|
||||
5,
|
||||
{"night_max_volume_limit": 5},
|
||||
id="night-max-volume",
|
||||
),
|
||||
pytest.param(
|
||||
"number.nursery_yoto_day_display_brightness",
|
||||
90,
|
||||
{"day_display_brightness": 90},
|
||||
id="day-display-brightness",
|
||||
),
|
||||
pytest.param(
|
||||
"number.nursery_yoto_night_display_brightness",
|
||||
20,
|
||||
@@ -114,6 +108,21 @@ async def test_set_value(
|
||||
mock_yoto_client.update_player_info.assert_awaited_once_with(PLAYER_ID)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_yoto_client")
|
||||
async def test_brightness_unavailable_while_auto(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Manual brightness sliders are unavailable while auto brightness is on."""
|
||||
await _setup(hass, mock_config_entry)
|
||||
|
||||
assert (
|
||||
hass.states.get("number.nursery_yoto_day_display_brightness").state
|
||||
== STATE_UNAVAILABLE
|
||||
)
|
||||
assert hass.states.get("number.nursery_yoto_night_display_brightness").state == "40"
|
||||
|
||||
|
||||
async def test_set_value_failure(
|
||||
hass: HomeAssistant,
|
||||
mock_yoto_client: MagicMock,
|
||||
@@ -129,6 +138,6 @@ async def test_set_value_failure(
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{ATTR_ENTITY_ID: "number.nursery_yoto_day_maximum_volume", ATTR_VALUE: 50},
|
||||
{ATTR_ENTITY_ID: "number.nursery_yoto_day_maximum_volume", ATTR_VALUE: 10},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user