mirror of
https://github.com/basnijholt/adaptive-lighting.git
synced 2026-09-12 14:54:04 +02:00
Fix intensity restoration and RGB output
This commit is contained in:
parent
51e0b11860
commit
844c7ced85
9 changed files with 220 additions and 45 deletions
14
README.md
14
README.md
|
|
@ -79,22 +79,24 @@ Every configuration provides a number entity, `number.adaptive_lighting_living_r
|
|||
output = floor_value + (adaptive_value - floor_value) × intensity / 100
|
||||
```
|
||||
|
||||
At 100% — the default — the lights receive the adaptive values unchanged, so the dial costs nothing until you move it.
|
||||
At 100%, the default, the lights receive the adaptive values unchanged and interpolation is skipped.
|
||||
At 0% they receive the floor.
|
||||
In between they remain fully adaptive: the sun keeps moving them at every setting, so the dial scales the curve rather than freezing the lights at a level.
|
||||
In between they remain adaptive: the sun keeps moving them through a smaller range. At 0%, the target stays at the configured endpoint.
|
||||
|
||||
The floor is set per configuration with `intensity_floor`:
|
||||
|
||||
- `sleep` (the default) interpolates towards `sleep_brightness` and `sleep_color_temp`, so 0% matches what sleep mode would do.
|
||||
- `sleep` (the default) interpolates towards `sleep_brightness` and the configured sleep color, so 0% matches what sleep mode would do. Color-capable lights use `sleep_rgb_color` when configured; CT-only lights use `sleep_color_temp`.
|
||||
- `minimum` interpolates towards `min_brightness` and `min_color_temp`. This gives a shallower dial that never takes a light below what the adaptive curve reaches on its own, at the cost of doing progressively less as the evening goes on — and of leaving color alone after sunset, where the adaptive color temperature already *is* `min_color_temp`.
|
||||
|
||||
Enabling `transition_until_sleep` forces the `sleep` floor whatever `intensity_floor` says.
|
||||
With that option on, the adaptive color temperature after sunset descends *below* `min_color_temp` towards `sleep_color_temp`, so a `min_color_temp` floor would sit above the adaptive value and turning the dial down would make the light cooler.
|
||||
With warmer sleep settings, the adaptive color after sunset goes below `min_color_temp`; using the minimum endpoint could then make dial-down cool the light.
|
||||
The switch's `intensity_floor` attribute reports the floor actually in use.
|
||||
|
||||
Sleep mode ignores the dial entirely — its output already *is* the sleep value.
|
||||
|
||||
This is what you want for a room-wide "mood" level that coexists with Adaptive Lighting: point an automation or a dashboard slider at the number entity, rather than rewriting every light's brightness band and putting it out of step with the configuration.
|
||||
The sleep endpoint can go below `min_brightness`. Lowering intensity dims and warms only when the endpoint is dimmer and warmer than the current adaptive target. Intensity 0 means the endpoint, not off.
|
||||
|
||||
Changes affect eligible, already-on lights and preserve manual control. Restarts restore intensity before adaptation and respect `only_once`; moving the dial explicitly adapts immediately. Runtime settings resets preserve intensity. See [Intensity](https://basnijholt.github.io/adaptive-lighting/advanced/intensity/) for interactions and the existing helper-automation alternative.
|
||||
<!-- SECTION:intensity:END -->
|
||||
|
||||
## :books: Table of Contents
|
||||
|
|
@ -173,7 +175,7 @@ The YAML and frontend configuration methods support all of the options listed be
|
|||
| `sleep_rgb_color` | RGB color in sleep mode (used when `sleep_rgb_or_color_temp` is "rgb_color"). 🌈 | `[255, 56, 0]` | RGB color |
|
||||
| `sleep_transition` | Duration of transition when "sleep mode" is toggled in seconds. 😴 | `1` | `float` 0-6553 |
|
||||
| `transition_until_sleep` | When enabled, Adaptive Lighting will treat sleep settings as the minimum, transitioning to these values after sunset. 🌙 | `False` | `bool` |
|
||||
| `intensity_floor` | What the bottom of the intensity dial means. `sleep` interpolates towards `sleep_brightness`/`sleep_color_temp`, `minimum` towards `min_brightness`/`min_color_temp`. Has no effect while `transition_until_sleep` is enabled: the sleep settings are then the bottom of the adaptive curve itself, so 0% is always the sleep settings. 🎚️ | `sleep` | one of `['sleep', 'minimum']` |
|
||||
| `intensity_floor` | What 0% on the intensity dial means. `sleep` blends towards `sleep_brightness` and the configured sleep color; `minimum` towards `min_brightness`/`min_color_temp`. `transition_until_sleep` forces the sleep endpoint. Lower intensity dims only when the endpoint is below the current adaptive value. 🎚️ | `sleep` | one of `['sleep', 'minimum']` |
|
||||
| `sunrise_time` | Set a fixed time (HH:MM:SS) for sunrise. 🌅 | `None` | `str` |
|
||||
| `min_sunrise_time` | Set the earliest virtual sunrise time (HH:MM:SS), allowing for later sunrises. 🌅 | `None` | `str` |
|
||||
| `max_sunrise_time` | Set the latest virtual sunrise time (HH:MM:SS), allowing for earlier sunrises. 🌅 | `None` | `str` |
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ from .switch import (
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS = ["switch", "number"] # "number" is the intensity dial
|
||||
PLATFORMS = ["number", "switch"]
|
||||
|
||||
|
||||
def _all_unique_names(value: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
|
|
@ -110,7 +110,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
|||
|
||||
undo_listener = config_entry.add_update_listener(async_update_options)
|
||||
data[config_entry.entry_id] = {UNDO_UPDATE_LISTENER: undo_listener}
|
||||
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
|
||||
# Restore the number before the switch can send its first adaptation.
|
||||
for platform in PLATFORMS:
|
||||
await hass.config_entries.async_forward_entry_setups(config_entry, [platform])
|
||||
|
||||
return True
|
||||
|
||||
|
|
|
|||
|
|
@ -416,13 +416,10 @@ class SunLightSettings:
|
|||
def intensity_floor_is_sleep(self) -> bool:
|
||||
"""Whether the dial's 0% end is the sleep settings.
|
||||
|
||||
`adapt_until_sleep` forces it, whatever `intensity_floor` says. With
|
||||
that option on, the adaptive colour temperature after sunset descends
|
||||
*below* `min_color_temp` towards `sleep_color_temp`, so a
|
||||
`min_color_temp` floor would sit above the adaptive value and turning
|
||||
the dial down would make the light cooler -- backwards. The sleep
|
||||
settings are the bottom of the curve there, so they are the only
|
||||
sensible floor.
|
||||
`adapt_until_sleep` forces it, whatever `intensity_floor` says. When
|
||||
sleep is warmer than the minimum, the adaptive colour after sunset
|
||||
goes below `min_color_temp`. Using the minimum endpoint could then
|
||||
make dial-down cool the light during that period.
|
||||
"""
|
||||
return self.intensity_floor == "sleep" or self.adapt_until_sleep
|
||||
|
||||
|
|
@ -441,8 +438,7 @@ class SunLightSettings:
|
|||
out = floor_value + (adaptive_value - floor_value) * intensity / 100
|
||||
|
||||
so 100 returns the adaptive value untouched and 0 returns the floor value.
|
||||
Scaling towards zero instead (the obvious implementation) is wrong for a
|
||||
mood dial.
|
||||
Unlike multiplication towards zero, this retains the configured endpoint.
|
||||
|
||||
The floor is the sleep settings by default. ``intensity_floor:
|
||||
minimum`` anchors it to ``min_brightness``/``min_color_temp`` instead --
|
||||
|
|
@ -529,6 +525,14 @@ class SunLightSettings:
|
|||
rgb_color,
|
||||
is_sleep=is_sleep,
|
||||
)
|
||||
if (
|
||||
not is_sleep
|
||||
and self.intensity < 100
|
||||
and self.intensity_floor_is_sleep
|
||||
and self.sleep_rgb_or_color_temp == "rgb_color"
|
||||
):
|
||||
# Select the blended RGB target even on lights that also support CT.
|
||||
force_rgb_color = True
|
||||
|
||||
# backwards compatibility for versions < 1.3.1 - see #403
|
||||
color_temp_mired: float = math.floor(1000000 / color_temp_kelvin)
|
||||
|
|
|
|||
|
|
@ -238,12 +238,11 @@ DOCS[CONF_ADAPT_UNTIL_SLEEP] = (
|
|||
|
||||
CONF_INTENSITY_FLOOR, DEFAULT_INTENSITY_FLOOR = "intensity_floor", "sleep"
|
||||
DOCS[CONF_INTENSITY_FLOOR] = (
|
||||
"What the bottom of the intensity dial means. `sleep` interpolates towards "
|
||||
"`sleep_brightness`/`sleep_color_temp`, `minimum` towards "
|
||||
"`min_brightness`/`min_color_temp`. Has no effect while "
|
||||
"`transition_until_sleep` is enabled: the sleep settings are then the "
|
||||
"bottom of the adaptive curve itself, so 0% is always the sleep "
|
||||
"settings. 🎚️"
|
||||
"What 0% on the intensity dial means. `sleep` blends towards "
|
||||
"`sleep_brightness` and the configured sleep color; `minimum` towards "
|
||||
"`min_brightness`/`min_color_temp`. `transition_until_sleep` forces the "
|
||||
"sleep endpoint. Lower intensity dims only when the endpoint is below "
|
||||
"the current adaptive value. 🎚️"
|
||||
)
|
||||
|
||||
CONF_ADAPT_DELAY, DEFAULT_ADAPT_DELAY = "adapt_delay", 0
|
||||
|
|
|
|||
|
|
@ -8,9 +8,8 @@ Adapt Brightness / Adapt Color switches on the same device:
|
|||
100% is the normal adaptive behaviour. 0% is the switch's floor, set by the
|
||||
``intensity_floor`` option: its sleep settings (``sleep_brightness`` and
|
||||
``sleep_color_temp``) by default, or its ``min_brightness``/``min_color_temp``.
|
||||
Anything in between is a straight interpolation between the two, recomputed
|
||||
continuously, so the sun still moves the light at every setting -- it is a
|
||||
scaled adaptive curve, not a frozen snapshot.
|
||||
Anything in between blends the two, recomputed continuously so the sun still
|
||||
moves the light through a smaller range. At 0%, the target stays at the endpoint.
|
||||
|
||||
The value survives restarts via RestoreEntity, and is re-applied to the switch
|
||||
whenever it changes so the lights follow immediately instead of waiting for the
|
||||
|
|
@ -118,10 +117,9 @@ class AdaptiveIntensityNumber(NumberEntity, RestoreEntity):
|
|||
last_state.state,
|
||||
DEFAULT_INTENSITY,
|
||||
)
|
||||
# Push even at 100 so the switch and the entity can never disagree.
|
||||
# Re-adapt only if this is an actual dimmed level being restored, so a
|
||||
# plain restart does not push a redundant adaptation at every switch.
|
||||
await self._push(adapt=self._value != DEFAULT_INTENSITY)
|
||||
# The switch starts after this platform and owns startup adaptation,
|
||||
# including the decision to skip it for only_once configurations.
|
||||
await self._push(adapt=False)
|
||||
|
||||
async def async_set_native_value(self, value: float) -> None:
|
||||
"""Set a new intensity and re-adapt the lights straight away."""
|
||||
|
|
@ -131,11 +129,9 @@ class AdaptiveIntensityNumber(NumberEntity, RestoreEntity):
|
|||
|
||||
async def _push(self, *, adapt: bool) -> None:
|
||||
switch = self._switch
|
||||
if switch is None:
|
||||
# Home Assistant does not guarantee that the "switch" platform
|
||||
# finishes before "number", so the switch may not exist yet. Leave
|
||||
# the value where AdaptiveSwitch.async_added_to_hass will find it
|
||||
# rather than dropping a restored dial on the floor.
|
||||
if switch is None or switch.hass is None or switch.is_on is None:
|
||||
# The parent may not have started yet, or may be disabled in the
|
||||
# entity registry. It adopts this value when added to Home Assistant.
|
||||
entry_data = self.hass.data[DOMAIN][self._config_entry.entry_id]
|
||||
entry_data[PENDING_INTENSITY] = self._value
|
||||
_LOGGER.debug(
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@
|
|||
"sleep_rgb_or_color_temp": "Use either `\"rgb_color\"` or `\"color_temp\"` in sleep mode. 🌙",
|
||||
"sleep_rgb_color": "RGB color in sleep mode (used when `sleep_rgb_or_color_temp` is \"rgb_color\"). 🌈",
|
||||
"sleep_transition": "Duration of transition when \"sleep mode\" is toggled in seconds. 😴",
|
||||
"intensity_floor": "What the bottom of the intensity dial means. `sleep` interpolates towards `sleep_brightness`/`sleep_color_temp`, `minimum` towards `min_brightness`/`min_color_temp`. Has no effect while `transition_until_sleep` is enabled: the sleep settings are then the bottom of the adaptive curve itself, so 0% is always the sleep settings. 🎚️",
|
||||
"intensity_floor": "What 0% on the intensity dial means. `sleep` blends towards `sleep_brightness` and the configured sleep color; `minimum` towards `min_brightness`/`min_color_temp`. `transition_until_sleep` forces the sleep endpoint. Lower intensity dims only when the endpoint is below the current adaptive value. 🎚️",
|
||||
"sunrise_time": "Set a fixed time (HH:MM:SS) for sunrise. 🌅",
|
||||
"min_sunrise_time": "Set the earliest virtual sunrise time (HH:MM:SS), allowing for later sunrises. 🌅",
|
||||
"max_sunrise_time": "Set the latest virtual sunrise time (HH:MM:SS), allowing for earlier sunrises. 🌅",
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@
|
|||
"sleep_rgb_or_color_temp": "Use either `\"rgb_color\"` or `\"color_temp\"` in sleep mode. 🌙",
|
||||
"sleep_rgb_color": "RGB color in sleep mode (used when `sleep_rgb_or_color_temp` is \"rgb_color\"). 🌈",
|
||||
"sleep_transition": "Duration of transition when \"sleep mode\" is toggled in seconds. 😴",
|
||||
"intensity_floor": "What the bottom of the intensity dial means. `sleep` interpolates towards `sleep_brightness`/`sleep_color_temp`, `minimum` towards `min_brightness`/`min_color_temp`. Has no effect while `transition_until_sleep` is enabled: the sleep settings are then the bottom of the adaptive curve itself, so 0% is always the sleep settings. 🎚️",
|
||||
"intensity_floor": "What 0% on the intensity dial means. `sleep` blends towards `sleep_brightness` and the configured sleep color; `minimum` towards `min_brightness`/`min_color_temp`. `transition_until_sleep` forces the sleep endpoint. Lower intensity dims only when the endpoint is below the current adaptive value. 🎚️",
|
||||
"sunrise_time": "Set a fixed time (HH:MM:SS) for sunrise. 🌅",
|
||||
"min_sunrise_time": "Set the earliest virtual sunrise time (HH:MM:SS), allowing for later sunrises. 🌅",
|
||||
"max_sunrise_time": "Set the latest virtual sunrise time (HH:MM:SS), allowing for earlier sunrises. 🌅",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ icon: lucide/sliders-horizontal
|
|||
|
||||
# Intensity
|
||||
|
||||
Intensity scales how far the adaptive settings travel from their floor, without leaving adaptive mode. It is the option to reach for when you want a dimmer room that still tracks the sun — a "mood" level, rather than a fixed brightness.
|
||||
Intensity blends the adaptive settings toward a configured endpoint, without leaving adaptive mode. It provides a room-wide "mood" level that still tracks the sun.
|
||||
|
||||
## The Dial
|
||||
|
||||
|
|
@ -32,13 +32,13 @@ output = floor_value + (adaptive_value - floor_value) × intensity / 100
|
|||
|
||||
| Intensity | Result |
|
||||
|-----------|--------|
|
||||
| `100` (default) | The adaptive values, unchanged. The dial costs nothing until you move it. |
|
||||
| `100` (default) | The adaptive values, unchanged; interpolation is skipped. |
|
||||
| `0` | The floor. |
|
||||
| in between | A scaled adaptive curve — the sun still moves the lights at every setting. |
|
||||
|
||||
The key property is that the lights stay adaptive at every value. Intensity does not freeze a light at a level; it moves the whole curve closer to the floor and keeps following the sun from there. Both brightness and color are scaled together.
|
||||
Between 0 and 100, the light keeps following the sun with a smaller range. At 0, the target stays at the endpoint. Both brightness and color are blended, subject to the profile's adaptation switches and each light's manual-control status and supported color modes.
|
||||
|
||||
Changing the dial re-adapts the lights immediately rather than waiting for the next `interval`, and the value survives a restart.
|
||||
Changing the dial re-adapts eligible, already-on lights immediately rather than waiting for the next `interval`, even with `only_once` enabled. It does not turn lights on or clear manual control. While the main switch is off, it stores the value for later. The value survives a restart and is restored before startup adaptation; `only_once` still prevents startup adaptation.
|
||||
|
||||
## Choosing the Floor
|
||||
|
||||
|
|
@ -46,16 +46,16 @@ The floor is set per configuration with `intensity_floor`:
|
|||
|
||||
| `intensity_floor` | 0% gives | Use it when |
|
||||
|-------------------|----------|-------------|
|
||||
| `sleep` (default) | `sleep_brightness` / `sleep_color_temp` | You want the dial to reach as low as the configuration goes. 0% then matches [sleep mode](sleep-mode.md). |
|
||||
| `sleep` (default) | `sleep_brightness` and the configured sleep color | You want 0% to match [sleep mode](sleep-mode.md). RGB sleep colors are used on color-capable lights; CT-only lights use `sleep_color_temp`. |
|
||||
| `minimum` | `min_brightness` / `min_color_temp` | You want the dial to stay inside the range the adaptive curve already uses. |
|
||||
|
||||
`minimum` never takes a light below what Adaptive Lighting would have done at its darkest anyway. The trade is that it does less and less as the evening goes on, and **color stops moving after sunset** — the adaptive color temperature is already `min_color_temp` there, so the floor and the value being interpolated from are the same number.
|
||||
|
||||
`sleep` keeps the dial useful at every hour, because the sleep settings sit below the adaptive curve at all times.
|
||||
The `sleep` endpoint can go below `min_brightness`. It dims and warms the light only when the sleep settings are dimmer and warmer than the current adaptive target. If you configured a brighter or cooler sleep setting, lowering intensity instead moves toward that setting. Intensity 0 means the configured endpoint, not off.
|
||||
|
||||
## `transition_until_sleep` Overrides the Floor
|
||||
|
||||
With [`transition_until_sleep`](sleep-mode.md) enabled, the adaptive color temperature after sunset descends *below* `min_color_temp` towards `sleep_color_temp`. A `min_color_temp` floor would then sit **above** the adaptive value, and turning the dial down would make the light *cooler* — the opposite of what a dimmer should do.
|
||||
With [`transition_until_sleep`](sleep-mode.md) enabled, the adaptive color after sunset moves toward the sleep color. When sleep is warmer than `min_color_temp`, using the minimum endpoint could make dial-down cool the light during that period.
|
||||
|
||||
For that reason the `sleep` floor is forced whenever `transition_until_sleep` is on, whatever `intensity_floor` says. The switch's `intensity_floor` attribute reports the floor actually in use, so you can see when this applies:
|
||||
|
||||
|
|
@ -76,8 +76,10 @@ The main switch exposes both values:
|
|||
| `intensity` | The current dial value, 0-100. |
|
||||
| `intensity_floor` | The floor in use, `sleep` or `minimum`, after the `transition_until_sleep` override. |
|
||||
|
||||
`change_switch_settings` preserves intensity, including when resetting settings to factory or configuration defaults. Set the number to 100 to restore the unmodified adaptive curve. Automations watching `brightness_pct` see the blended target, so changing intensity can trigger their brightness thresholds.
|
||||
|
||||
## Why Not Just Scale `min_brightness` and `max_brightness`?
|
||||
|
||||
Rescaling the brightness band from an automation works, but it hardcodes each configuration's band in the automation. Retuning a light in Adaptive Lighting then silently puts the two out of step, and the automation has to be updated in parallel forever. It also leaves color untouched unless you cap `max_color_temp` separately.
|
||||
An `input_number` helper and `change_switch_settings` automation can reproduce this brightness curve by replacing each original bound `B` with `floor + (B - floor) * intensity / 100`. Both bounds keep following the sun; this does not freeze the target. The automation needs the original bounds and must keep them in sync when the profile is retuned. Ordinary color-temperature bounds can be transformed similarly, but this alone does not reproduce sleep-RGB blending.
|
||||
|
||||
The dial reads the configuration's own numbers, so there is nothing to keep in sync, and it moves color along with brightness.
|
||||
|
|
|
|||
|
|
@ -6239,3 +6239,173 @@ async def test_unloaded_polling_profile_preserves_other_split_adaptation(
|
|||
await hass.async_block_till_done()
|
||||
assert len(calls) == 2
|
||||
assert ATTR_COLOR_TEMP_KELVIN in calls[-1]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("only_once", [False, True])
|
||||
async def test_intensity_restore_before_adaptation(hass, only_once):
|
||||
"""Restoration never emits a full-intensity command or overrides only_once."""
|
||||
from tests.common import async_mock_service, mock_restore_cache
|
||||
|
||||
await setup_lights(hass)
|
||||
mock_restore_cache(
|
||||
hass,
|
||||
[State("number.adaptive_lighting_default_intensity", "25")],
|
||||
)
|
||||
calls = async_mock_service(hass, LIGHT_DOMAIN, SERVICE_TURN_ON)
|
||||
_, switch = await setup_switch(
|
||||
hass,
|
||||
{
|
||||
CONF_LIGHTS: [ENTITY_LIGHT_1],
|
||||
CONF_MIN_BRIGHTNESS: 100,
|
||||
CONF_MAX_BRIGHTNESS: 100,
|
||||
"sleep_brightness": 4,
|
||||
CONF_ONLY_ONCE: only_once,
|
||||
},
|
||||
)
|
||||
assert switch.extra_state_attributes["intensity"] == 25
|
||||
assert [call.data[ATTR_BRIGHTNESS] for call in calls] == ([] if only_once else [71])
|
||||
|
||||
|
||||
async def test_intensity_with_disabled_main_switch(hass):
|
||||
"""A disabled parent must not prevent its intensity entity from loading."""
|
||||
from tests.common import mock_restore_cache
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={CONF_NAME: DEFAULT_NAME, CONF_INTERCEPT: False},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
registry = entity_registry.async_get(hass)
|
||||
registry.async_get_or_create(
|
||||
SWITCH_DOMAIN,
|
||||
DOMAIN,
|
||||
DEFAULT_NAME,
|
||||
config_entry=entry,
|
||||
disabled_by=entity_registry.RegistryEntryDisabler.USER,
|
||||
)
|
||||
mock_restore_cache(
|
||||
hass,
|
||||
[State("number.adaptive_lighting_default_intensity", "25")],
|
||||
)
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
number_id = "number.adaptive_lighting_default_intensity"
|
||||
assert hass.states.get(number_id).state == "25.0"
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
"set_value",
|
||||
{ATTR_ENTITY_ID: number_id, "value": 50},
|
||||
blocking=True,
|
||||
)
|
||||
assert hass.states.get(number_id).state == "50.0"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("color_modes", "floor", "intensity", "expected_attribute"),
|
||||
[
|
||||
([ColorMode.COLOR_TEMP, ColorMode.RGB], "sleep", 0, ATTR_RGB_COLOR),
|
||||
([ColorMode.COLOR_TEMP, ColorMode.RGB], "sleep", 50, ATTR_RGB_COLOR),
|
||||
([ColorMode.COLOR_TEMP, ColorMode.RGB], "sleep", 100, ATTR_COLOR_TEMP_KELVIN),
|
||||
([ColorMode.COLOR_TEMP, ColorMode.RGB], "minimum", 0, ATTR_COLOR_TEMP_KELVIN),
|
||||
([ColorMode.COLOR_TEMP], "sleep", 0, ATTR_COLOR_TEMP_KELVIN),
|
||||
([ColorMode.RGB], "sleep", 0, ATTR_RGB_COLOR),
|
||||
],
|
||||
)
|
||||
async def test_intensity_uses_sleep_rgb_in_light_command(
|
||||
hass,
|
||||
color_modes,
|
||||
floor,
|
||||
intensity,
|
||||
expected_attribute,
|
||||
):
|
||||
"""Select the blended RGB target on lights that also support Kelvin."""
|
||||
entry, switch = await setup_switch(
|
||||
hass,
|
||||
{
|
||||
"sleep_rgb_color": [255, 0, 0],
|
||||
CONF_SLEEP_RGB_OR_COLOR_TEMP: "rgb_color",
|
||||
"sleep_color_temp": 2000,
|
||||
"intensity_floor": floor,
|
||||
CONF_ADAPT_UNTIL_SLEEP: False,
|
||||
},
|
||||
)
|
||||
hass.states.async_set(
|
||||
ENTITY_LIGHT_1,
|
||||
STATE_ON,
|
||||
{
|
||||
"supported_color_modes": color_modes,
|
||||
"min_color_temp_kelvin": 2000,
|
||||
"max_color_temp_kelvin": 6500,
|
||||
},
|
||||
)
|
||||
await hass.data[DOMAIN][entry.entry_id][INTENSITY_NUMBER].async_set_native_value(
|
||||
intensity,
|
||||
)
|
||||
data = await switch.prepare_adaptation_data(ENTITY_LIGHT_1, transition=0)
|
||||
assert data is not None
|
||||
command = await data.next_service_call_data()
|
||||
assert expected_attribute in command
|
||||
other_attribute = (
|
||||
ATTR_RGB_COLOR
|
||||
if expected_attribute == ATTR_COLOR_TEMP_KELVIN
|
||||
else ATTR_COLOR_TEMP_KELVIN
|
||||
)
|
||||
assert other_attribute not in command
|
||||
if expected_attribute == ATTR_RGB_COLOR and intensity == 0:
|
||||
assert command[ATTR_RGB_COLOR] == (255, 0, 0)
|
||||
|
||||
|
||||
async def test_intensity_set_preserves_control_and_runtime_settings(hass):
|
||||
"""Dial changes affect on, adaptive lights and survive settings changes/reload."""
|
||||
from tests.common import async_mock_service
|
||||
|
||||
await setup_lights(hass)
|
||||
entry, switch = await setup_switch(
|
||||
hass,
|
||||
{
|
||||
CONF_LIGHTS: [ENTITY_LIGHT_1, ENTITY_LIGHT_2, ENTITY_LIGHT_3],
|
||||
CONF_MIN_BRIGHTNESS: 100,
|
||||
CONF_MAX_BRIGHTNESS: 100,
|
||||
"sleep_brightness": 4,
|
||||
CONF_ONLY_ONCE: True,
|
||||
},
|
||||
)
|
||||
switch.manager.set_manual_control_attributes(ENTITY_LIGHT_2)
|
||||
calls = async_mock_service(hass, LIGHT_DOMAIN, SERVICE_TURN_ON)
|
||||
number_id = "number.adaptive_lighting_default_intensity"
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
"set_value",
|
||||
{ATTR_ENTITY_ID: number_id, "value": 25},
|
||||
blocking=True,
|
||||
)
|
||||
assert [
|
||||
(call.data[ATTR_ENTITY_ID], call.data[ATTR_BRIGHTNESS]) for call in calls
|
||||
] == [
|
||||
(ENTITY_LIGHT_1, 71),
|
||||
]
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_CHANGE_SWITCH_SETTINGS,
|
||||
{ATTR_ENTITY_ID: switch.entity_id, CONF_MIN_BRIGHTNESS: 80},
|
||||
blocking=True,
|
||||
)
|
||||
assert switch.extra_state_attributes["intensity"] == 25
|
||||
assert switch._sun_light_settings.intensity == 25
|
||||
assert switch.manager.get_manual_control_attributes(ENTITY_LIGHT_2).has_all()
|
||||
await switch.async_turn_off()
|
||||
calls.clear()
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
"set_value",
|
||||
{ATTR_ENTITY_ID: number_id, "value": 50},
|
||||
blocking=True,
|
||||
)
|
||||
assert not calls
|
||||
assert switch.extra_state_attributes["intensity"] == 50
|
||||
assert await hass.config_entries.async_reload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
restored = hass.data[DOMAIN][entry.entry_id][SWITCH_DOMAIN]
|
||||
assert restored.extra_state_attributes["intensity"] == 50
|
||||
assert not restored.is_on
|
||||
assert not calls
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue