With transition_until_sleep and sleep_rgb, after sunset, use RGB colors (#656)

* With transition_until_sleep and sleep_rgb, after sunset, use RGB colors

Closes #624

* Add comment

* Add test
This commit is contained in:
Bas Nijholt 2023-07-23 13:40:03 -07:00 committed by GitHub
commit 97f388608a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 125 additions and 5 deletions

View file

@ -1123,6 +1123,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
and adapt_color
and not (prefer_rgb_color and "color" in features)
and not (sleep_rgb and "color" in features)
and not (self._settings["force_rgb_color"] and "color" in features)
):
_LOGGER.debug("%s: Setting color_temp of light %s", self._name, light)
attributes = self.hass.states.get(light).attributes
@ -1296,9 +1297,9 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
else:
filtered_lights.append(light)
_LOGGER.debug("%s: filtered_lights: '%s'", self._name, filtered_lights)
if not filtered_lights:
return
adapt_brightness = self.adapt_brightness_switch.is_on
adapt_color = self.adapt_color_switch.is_on
@ -1337,6 +1338,13 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
else:
_fire_manual_control_event(self, light, context)
else:
_LOGGER.debug(
"%s: Calling _adapt_light from _update_attrs_and_maybe_adapt_lights:"
" '%s' with transition %s",
self._name,
light,
transition,
)
await self._adapt_light(light, transition, context=context)
async def _sleep_mode_switch_state_event_action(self, event: Event) -> None:
@ -1487,6 +1495,17 @@ class SimpleSwitch(SwitchEntity, RestoreEntity):
self._state = False
def lerp_color(
rgb1: tuple[int, int, int], rgb2: tuple[int, int, int], t: float
) -> tuple[int, int, int]:
"""Linearly interpolate between two RGB colors."""
return (
int(rgb1[0] + t * (rgb2[0] - rgb1[0])),
int(rgb1[1] + t * (rgb2[1] - rgb1[1])),
int(rgb1[2] + t * (rgb2[2] - rgb1[2])),
)
@dataclass(frozen=True)
class SunLightSettings:
"""Track the state of the sun and associated light settings."""
@ -1654,15 +1673,29 @@ class SunLightSettings:
if transition is not None
else self.calc_percent(0)
)
rgb_color: tuple[float, float, float]
# Variable `force_rgb_color` is needed for RGB color after sunset (if enabled)
force_rgb_color = False
brightness_pct = self.calc_brightness_pct(percent, is_sleep)
if is_sleep:
color_temp_kelvin = self.sleep_color_temp
rgb_color: tuple[float, float, float] = self.sleep_rgb_color
rgb_color = self.sleep_rgb_color
elif (
self.sleep_rgb_or_color_temp == "rgb_color"
and self.adapt_until_sleep
and percent < 0
):
# Feature requested in
# https://github.com/basnijholt/adaptive-lighting/issues/624
# This will result in a perceptible jump in color at sunset and sunrise
# because the `color_temperature_to_rgb` function is not 100% accurate.
min_color_rgb = color_temperature_to_rgb(self.min_color_temp)
rgb_color = lerp_color(min_color_rgb, self.sleep_rgb_color, percent)
color_temp_kelvin = self.calc_color_temp_kelvin(percent)
force_rgb_color = True
else:
color_temp_kelvin = self.calc_color_temp_kelvin(percent)
rgb_color: tuple[float, float, float] = color_temperature_to_rgb(
color_temp_kelvin
)
rgb_color = color_temperature_to_rgb(color_temp_kelvin)
# backwards compatibility for versions < 1.3.1 - see #403
color_temp_mired: float = math.floor(1000000 / color_temp_kelvin)
xy_color: tuple[float, float] = color_RGB_to_xy(*rgb_color)
@ -1675,6 +1708,7 @@ class SunLightSettings:
"xy_color": xy_color,
"hs_color": hs_color,
"sun_position": percent,
"force_rgb_color": force_rgb_color,
}

View file

@ -56,6 +56,7 @@ from custom_components.adaptive_lighting.const import (
ADAPT_BRIGHTNESS_SWITCH,
ADAPT_COLOR_SWITCH,
ATTR_ADAPTIVE_LIGHTING_MANAGER,
CONF_ADAPT_UNTIL_SLEEP,
CONF_AUTORESET_CONTROL,
CONF_DETECT_NON_HA_CHANGES,
CONF_INITIAL_TRANSITION,
@ -64,6 +65,7 @@ from custom_components.adaptive_lighting.const import (
CONF_MIN_COLOR_TEMP,
CONF_PREFER_RGB_COLOR,
CONF_SEPARATE_TURN_ON_COMMANDS,
CONF_SLEEP_RGB_OR_COLOR_TEMP,
CONF_SUNRISE_OFFSET,
CONF_SUNRISE_TIME,
CONF_SUNSET_TIME,
@ -74,6 +76,7 @@ from custom_components.adaptive_lighting.const import (
DEFAULT_NAME,
DEFAULT_SLEEP_BRIGHTNESS,
DEFAULT_SLEEP_COLOR_TEMP,
DEFAULT_SLEEP_RGB_COLOR,
DOMAIN,
SERVICE_APPLY,
SERVICE_CHANGE_SWITCH_SETTINGS,
@ -1430,6 +1433,7 @@ async def test_proactive_adaptation(hass):
{
ATTR_BRIGHTNESS_PCT: 67,
ATTR_COLOR_TEMP_KELVIN: 3448,
"force_rgb_color": False,
},
)
@ -1464,6 +1468,7 @@ async def test_proactive_adaptation_with_separate_commands(hass):
{
ATTR_BRIGHTNESS_PCT: 67,
ATTR_COLOR_TEMP_KELVIN: 3448,
"force_rgb_color": False,
},
)
@ -1615,3 +1620,84 @@ async def test_two_switches_for_single_light(hass):
after_color_temp = attrs[ATTR_COLOR_TEMP_KELVIN]
assert before_brightness != after_brightness
assert before_color_temp != after_color_temp
async def test_adapt_until_sleep_and_rgb_colors(hass):
"""Test setting up the Adaptive Lighting switches with different timezones.
Also test the (sleep) brightness and color temperature settings.
"""
lat, long, timezone = (32.87336, -117.22743, "US/Pacific")
await config_util.async_process_ha_core_config(
hass,
{"latitude": lat, "longitude": long, "time_zone": timezone},
)
switch, lights = await setup_lights_and_switch(
hass,
{
CONF_SUNRISE_TIME: datetime.time(SUNRISE.hour),
CONF_SUNSET_TIME: datetime.time(SUNSET.hour),
CONF_ADAPT_UNTIL_SLEEP: True,
CONF_SLEEP_RGB_OR_COLOR_TEMP: "rgb_color",
},
)
context = switch.create_context("test") # needs to be passed to update method
min_color_temp = switch._sun_light_settings.min_color_temp
sunset = SUNSET.replace(tzinfo=dt_util.DEFAULT_TIME_ZONE).astimezone(dt_util.UTC)
before_sunset = sunset - datetime.timedelta(hours=1)
after_sunset = sunset + datetime.timedelta(hours=1)
sunrise = SUNRISE.replace(tzinfo=dt_util.DEFAULT_TIME_ZONE).astimezone(dt_util.UTC)
before_sunrise = sunrise - datetime.timedelta(hours=1)
after_sunrise = sunrise + datetime.timedelta(hours=1)
async def patch_time_and_update(time):
with patch("homeassistant.util.dt.utcnow", return_value=time):
await switch._update_attrs_and_maybe_adapt_lights(context=context)
await hass.async_block_till_done()
# At sunset the brightness should be max and color_temp at the smallest value
await patch_time_and_update(sunset)
assert not switch._settings["force_rgb_color"]
assert switch._settings[ATTR_BRIGHTNESS_PCT] == DEFAULT_MAX_BRIGHTNESS
assert switch._settings["color_temp_kelvin"] == min_color_temp
# One hour before sunset the brightness should be max and color_temp
# not at the smallest value yet.
await patch_time_and_update(before_sunset)
assert not switch._settings["force_rgb_color"]
assert switch._settings[ATTR_BRIGHTNESS_PCT] == DEFAULT_MAX_BRIGHTNESS
assert switch._settings["color_temp_kelvin"] > min_color_temp
assert "color_temp_kelvin" in switch.manager.last_service_data[ENTITY_LIGHT]
# One hour after sunset the brightness should be down
await patch_time_and_update(after_sunset)
assert switch._settings["force_rgb_color"]
assert switch._settings[ATTR_BRIGHTNESS_PCT] < DEFAULT_MAX_BRIGHTNESS
assert "rgb_color" in switch.manager.last_service_data[ENTITY_LIGHT]
# At sunrise the brightness should be max and color_temp at the smallest value
await patch_time_and_update(sunrise)
assert switch._settings[ATTR_BRIGHTNESS_PCT] == DEFAULT_MAX_BRIGHTNESS
assert switch._settings["color_temp_kelvin"] == min_color_temp
assert "color_temp_kelvin" in switch.manager.last_service_data[ENTITY_LIGHT]
# One hour before sunrise the brightness should smaller than max
# and color_temp at the min value.
await patch_time_and_update(before_sunrise)
assert switch._settings[ATTR_BRIGHTNESS_PCT] < DEFAULT_MAX_BRIGHTNESS
assert "rgb_color" in switch.manager.last_service_data[ENTITY_LIGHT]
# One hour after sunrise the brightness should be up
await patch_time_and_update(after_sunrise)
assert switch._settings[ATTR_BRIGHTNESS_PCT] == DEFAULT_MAX_BRIGHTNESS
assert switch._settings["color_temp_kelvin"] > min_color_temp
assert "color_temp_kelvin" in switch.manager.last_service_data[ENTITY_LIGHT]
# Turn on sleep mode which make the brightness and color_temp
# deterministic regardless of the time
await switch.sleep_mode_switch.async_turn_on()
await switch._update_attrs_and_maybe_adapt_lights(context=context)
assert switch._settings[ATTR_BRIGHTNESS_PCT] == DEFAULT_SLEEP_BRIGHTNESS
assert switch._settings["rgb_color"] == DEFAULT_SLEEP_RGB_COLOR