Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dim to warm #452

Draft
wants to merge 37 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
9dfb839
untested
th3w1zard1 Mar 18, 2023
f3b2e20
don't update brightness if dim_to_warm is set
th3w1zard1 Mar 18, 2023
314d49d
ensure brightness is a feature of lightbulb
th3w1zard1 Mar 18, 2023
0f7fb92
fixed equation
th3w1zard1 Mar 18, 2023
256bb5d
fixed a typo
th3w1zard1 Mar 18, 2023
aebe5d5
fixed a typo
th3w1zard1 Mar 18, 2023
2fb5010
changed source for max/min brightness
th3w1zard1 Mar 18, 2023
e1b9a28
derp moment
th3w1zard1 Mar 18, 2023
bf49721
formatting
th3w1zard1 Mar 18, 2023
e17a89b
formatting
th3w1zard1 Mar 18, 2023
c4d8499
Update strings.json
th3w1zard1 Mar 22, 2023
8c56bc7
Update strings.json
th3w1zard1 Mar 22, 2023
dbcb154
Ran pre-commit
th3w1zard1 Mar 22, 2023
b7520bd
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 22, 2023
6ba511b
More pre-commit nonsense.
th3w1zard1 Mar 22, 2023
afbe85a
Merge branch 'dim-to-warm' of https://github.com/th3w1zard1/adaptive-…
th3w1zard1 Mar 22, 2023
7d6064e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 22, 2023
e27c5d5
More pre-commit nonsense
th3w1zard1 Mar 22, 2023
ab96ac0
Merge branch 'dim-to-warm' of https://github.com/th3w1zard1/adaptive-…
th3w1zard1 Mar 22, 2023
94064fc
Fix variable scope.
th3w1zard1 Mar 25, 2023
4f1312c
use update_entity for ensured functionality
th3w1zard1 Mar 25, 2023
7589ea3
merge upstream/master
th3w1zard1 Mar 25, 2023
8731b2f
Merge branch 'master' into dim-to-warm
th3w1zard1 Mar 27, 2023
d0f6810
Merge remote-tracking branch 'upstream/master' into dim-to-warm
th3w1zard1 Mar 27, 2023
8549525
Fix brightness being null while light is off.
th3w1zard1 Mar 28, 2023
c24c553
Added `dim_to_warm_brightness_check`
th3w1zard1 Mar 30, 2023
efdc652
Use difference of both ct calcs.
th3w1zard1 Mar 30, 2023
22356a8
brightness check is more strict
th3w1zard1 Mar 30, 2023
5b018bb
merge upstream/master
th3w1zard1 Mar 30, 2023
98f4c36
merge origin/main
th3w1zard1 Apr 10, 2023
89f131b
pass the flake8 test
th3w1zard1 Apr 10, 2023
2cd478c
fixes and cleanup
th3w1zard1 Apr 10, 2023
5d0352f
Update switch.py
th3w1zard1 Apr 10, 2023
be9defb
fix everything
th3w1zard1 Apr 10, 2023
5dbadcd
cleanup
th3w1zard1 Apr 10, 2023
28364bf
whoops
th3w1zard1 Apr 10, 2023
5237735
Update switch.py
th3w1zard1 Apr 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions custom_components/adaptive_lighting/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,12 @@
)

CONF_TRANSITION, DEFAULT_TRANSITION = "transition", 45

CONF_DIM_TO_WARM, DEFAULT_DIM_TO_WARM = "dim_to_warm", False
CONF_DIM_TO_WARM_BRIGHTNESS_CHECK, DEFAULT_DIM_TO_WARM_BRIGHTNESS_CHECK = (
"dim_to_warm_brightness_check",
False,
)
DOCS[CONF_TRANSITION] = "Duration of transition when lights change, in seconds. 🕑"

CONF_ADAPT_UNTIL_SLEEP, DEFAULT_ADAPT_UNTIL_SLEEP = (
Expand Down Expand Up @@ -265,6 +271,8 @@ def int_between(min_int, max_int):
(CONF_DETECT_NON_HA_CHANGES, DEFAULT_DETECT_NON_HA_CHANGES, bool),
(CONF_SEPARATE_TURN_ON_COMMANDS, DEFAULT_SEPARATE_TURN_ON_COMMANDS, bool),
(CONF_SEND_SPLIT_DELAY, DEFAULT_SEND_SPLIT_DELAY, int_between(0, 10000)),
(CONF_DIM_TO_WARM, DEFAULT_DIM_TO_WARM, bool),
(CONF_DIM_TO_WARM_BRIGHTNESS_CHECK, DEFAULT_DIM_TO_WARM_BRIGHTNESS_CHECK, bool),
(CONF_ADAPT_DELAY, DEFAULT_ADAPT_DELAY, cv.positive_float),
(
CONF_AUTORESET_CONTROL,
Expand Down
Empty file modified custom_components/adaptive_lighting/services.yaml
100644 → 100755
Empty file.
31 changes: 30 additions & 1 deletion custom_components/adaptive_lighting/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,33 @@
"title": "Adaptive Lighting options",
"description": "All settings for a Adaptive Lighting component. The option names correspond with the YAML settings. No options are shown if you have this entry defined in YAML.",
"data": {
"lights": "lights",
"initial_transition": "initial_transition: When lights turn 'off' to 'on'. (seconds)",
"include_config_in_attributes": "include_config_in_attributes: All config options will be listed as attributes under the adaptive-lighting switch this integration creates. (default: false)",
"sleep_transition": "sleep_transition: When 'sleep_state' changes. (seconds)",
"interval": "interval: Time between switch updates. (seconds)",
"max_brightness": "max_brightness: Highest brightness of lights during a cycle. (%)",
"max_color_temp": "max_color_temp: Coldest hue of the color temperature cycle. (Kelvin)",
"min_brightness": "min_brightness: Lowest brightness of lights during a cycle. (%)",
"min_color_temp": "min_color_temp, Warmest hue of the color temperature cycle. (Kelvin)",
"only_once": "only_once: Only adapt the lights when turning them on.",
"prefer_rgb_color": "prefer_rgb_color: Use 'rgb_color' rather than 'color_temp' when possible.",
"separate_turn_on_commands": "separate_turn_on_commands: Separate the commands for each attribute (color, brightness, etc.) in 'light.turn_on' (required for some lights).",
"send_split_delay": "send_split_delay: wait between commands (milliseconds), when separate_turn_on_commands is used. May ensure that both commands are handled by the bulb correctly.",
"sleep_brightness": "sleep_brightness, Brightness setting for Sleep Mode. (%)",
"sleep_rgb_or_color_temp": "sleep_rgb_or_color_temp, use 'rgb_color' or 'color_temp'",
"sleep_rgb_color": "sleep_rgb_color, in RGB",
"sleep_color_temp": "sleep_color_temp: Color temperature setting for Sleep Mode. (Kelvin)",
"sunrise_offset": "sunrise_offset: How long before(-) or after(+) to define the sunrise point of the cycle (+/- seconds)",
"sunrise_time": "sunrise_time: Manual override of the sunrise time, if 'None', it uses the actual sunrise time at your location (HH:MM:SS)",
"max_sunrise_time": "max_sunrise_time: Manual override of the maximum sunrise time, if 'None', it uses the actual sunrise time at your location (HH:MM:SS)",
"sunset_offset": "sunset_offset: How long before(-) or after(+) to define the sunset point of the cycle (+/- seconds)",
"sunset_time": "sunset_time: Manual override of the sunset time, if 'None', it uses the actual sunset time at your location (HH:MM:SS)",
"min_sunset_time": "min_sunset_time: Manual override of the minimum sunset time, if 'None', it uses the actual sunset time at your location (HH:MM:SS)",
"take_over_control": "take_over_control: If anything but Adaptive Lighting calls 'light.turn_on' when a light is already on, stop adapting that light until it (or the switch) toggles off -> on.",
"detect_non_ha_changes": "detect_non_ha_changes: detects all >10% changes made to the lights (also outside of HA), requires 'take_over_control' to be enabled (calls 'homeassistant.update_entity' every 'interval'!)",
"transition": "Transition time when applying a change to the lights (seconds)",
"adapt_delay": "adapt_delay: wait time between light turn on (seconds), and Adaptive Lights applying changes to the light state. May avoid flickering.",
"lights": "lights: List of light entity_ids to be controlled (may be empty). 🌟",
"prefer_rgb_color": "prefer_rgb_color: Whether to prefer RGB color adjustment over light color temperature when possible. 🌈",
"include_config_in_attributes": "include_config_in_attributes: Show all options as attributes on the switch in Home Assistant when set to `true`. 📝",
Expand Down Expand Up @@ -47,7 +74,9 @@
"separate_turn_on_commands": "separate_turn_on_commands: Use separate `light.turn_on` calls for color and brightness, needed for some light types. 🔀",
"send_split_delay": "send_split_delay: Delay (ms) between `separate_turn_on_commands` for lights that don't support simultaneous brightness and color setting. ⏲️",
"adapt_delay": "adapt_delay: Wait time (seconds) between light turn on and Adaptive Lighting applying changes. Might help to avoid flickering. ⏲️",
"autoreset_control_seconds": "autoreset_control_seconds: Automatically reset the manual control after a number of seconds. Set to 0 to disable. ⏲️"
"autoreset_control_seconds": "autoreset_control_seconds: Automatically reset the manual control after a number of seconds. Set to 0 to disable. ⏲️",
"dim to warm": "dim_to_warm: Adjust color temperature even further on a parabolic curve depending on the current brightness of the light. Does not work with take_over_control",
"dim_to_warm_brightness_check": "dim_to_warm_brightness_check: When true, fire manual control events when dim to warm is true and the brightness is manually controlled."
}
}
},
Expand Down
119 changes: 117 additions & 2 deletions custom_components/adaptive_lighting/switch.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@
CONF_ADAPT_UNTIL_SLEEP,
CONF_AUTORESET_CONTROL,
CONF_DETECT_NON_HA_CHANGES,
CONF_DIM_TO_WARM,
CONF_DIM_TO_WARM_BRIGHTNESS_CHECK,
CONF_INCLUDE_CONFIG_IN_ATTRIBUTES,
CONF_INITIAL_TRANSITION,
CONF_INTERVAL,
Expand Down Expand Up @@ -686,6 +688,62 @@ def _supported_features(hass: HomeAssistant, light: str):
return supported, supports_colors


def pop_keys_with_none(data):
new_data = {}
for key, val in data.items():
if val is not None:
new_data[key] = val
return new_data


def remove_color_attributes(data):
for attr in COLOR_ATTRS:
if attr in data and attr != ATTR_COLOR_TEMP_KELVIN:
_LOGGER.debug("Remove color attr %s from service data", attr)
data[attr] = None
return data


def build_with_supported(
switch: AdaptiveSwitch, light, data, features, prefer_rgb_color, supports_colors
):
if not prefer_rgb_color and ATTR_COLOR_TEMP_KELVIN in features:
if ATTR_COLOR_TEMP_KELVIN in data:
remove_color_attributes(data)
elif prefer_rgb_color is False:
_LOGGER.debug(
"%s: 'prefer_rgb_color: false' but light %s does not support color_temp."
" Using rgb_color to build service data instead...",
switch._name,
light,
)
data.pop(ATTR_COLOR_TEMP_KELVIN)
elif prefer_rgb_color:
color_attrs_in_data = {k for k, _ in COLOR_ATTRS.keys() ^ data.keys()}
if supports_colors and color_attrs_in_data:
_LOGGER.debug(
"%s: 'prefer_rgb_color: true', using rgb_color for light %s",
switch._name,
light,
)
data.pop(ATTR_COLOR_TEMP_KELVIN)
elif ATTR_COLOR_TEMP_KELVIN in data:
_LOGGER.debug(
"%s: 'prefer_rgb_color: true' but light %s does not support rgb."
" Using color temp to build service data instead...",
switch._name,
light,
)
remove_color_attributes(data)
else:
_LOGGER.error(ATTR_COLOR_TEMP_KELVIN + " not in service data")
for attr, val in data.items():
if attr not in COLOR_ATTRS and attr not in features:
_LOGGER.debug("pop unsupported %s val %s", attr, val)
data[attr] = None
return data


def color_difference_redmean(
rgb1: tuple[float, float, float], rgb2: tuple[float, float, float]
) -> float:
Expand Down Expand Up @@ -903,6 +961,8 @@ def _set_changeable_settings(
self._sleep_transition = data[CONF_SLEEP_TRANSITION]
self._only_once = data[CONF_ONLY_ONCE]
self._prefer_rgb_color = data[CONF_PREFER_RGB_COLOR]
self._dim_to_warm = data[CONF_DIM_TO_WARM]
self._dim_to_warm_brightness_check = data[CONF_DIM_TO_WARM_BRIGHTNESS_CHECK]
self._separate_turn_on_commands = data[CONF_SEPARATE_TURN_ON_COMMANDS]
self._transition = data[CONF_TRANSITION]
self._adapt_delay = data[CONF_ADAPT_DELAY]
Expand Down Expand Up @@ -1108,6 +1168,43 @@ async def _async_update_at_interval(self, now=None) -> None:
context=self.create_context("interval"),
)

def calc_dim_to_warm_ct(
self,
light: str,
brightness: float,
):
min_ct = (
self._sun_light_settings.min_color_temp
) # pylint: disable=protected-access
max_ct = (
self._sun_light_settings.max_color_temp
) # pylint: disable=protected-access
min_brightness = (
self._sun_light_settings.min_brightness
) # pylint: disable=protected-access
max_brightness = (
self._sun_light_settings.max_brightness
) # pylint: disable=protected-access
min_brightness = min((min_brightness * 2.55), brightness)
max_brightness = max((max_brightness * 2.55), brightness)
_LOGGER.debug(
"Setting dim_to_warm color temp using the following values in eq:"
" max_brightness: %s, min_brightness: %s, max_ct: %s,"
" min_ct: %s, brightness: %s",
max_brightness,
min_brightness,
max_ct,
min_ct,
brightness,
)
# y = a(x-h)**2+k where h,k is the vertex (255,6500) or (max_brightness,max_ct)
# a = (min_ct-max_ct)/(min_brightness-max_brightness)**2
# check: y = (1000-6500)/((1-h)**2)*(x-255)**2+6500 if x=2 then y=1043.221836
# ^ when 1=min_brightness,255=max_brightness,6500=max_ct,1000=min_ct ^
return ((min_ct - max_ct) / (min_brightness - max_brightness) ** 2) * (
brightness - max_brightness
) ** 2 + max_ct

async def _adapt_light(
self,
light: str,
Expand Down Expand Up @@ -1161,8 +1258,24 @@ async def _adapt_light(
min_kelvin = features[ATTR_MIN_COLOR_TEMP_KELVIN]
max_kelvin = features[ATTR_MAX_COLOR_TEMP_KELVIN]
color_temp_kelvin = self._settings["color_temp_kelvin"]
color_temp_kelvin = max(min(color_temp_kelvin, max_kelvin), min_kelvin)
service_data[ATTR_COLOR_TEMP_KELVIN] = color_temp_kelvin
if self._dim_to_warm and "brightness" in features:
cur_state = None
if self._dim_to_warm_brightness_check:
await self.hass.helpers.entity_component.async_update_entity(light)
cur_state = self.hass.states.get(light)
if cur_state:
brightness = cur_state.attributes[ATTR_BRIGHTNESS]
else:
brightness = service_data[ATTR_BRIGHTNESS]
dimmed_ct = self.calc_dim_to_warm_ct(
light,
brightness,
)
median = (dimmed_ct + color_temp_kelvin) / 2
color_temp_kelvin = median
service_data[ATTR_COLOR_TEMP_KELVIN] = max(
min(color_temp_kelvin, max_kelvin), min_kelvin
)
elif supports_colors and adapt_color:
_LOGGER.debug("%s: Setting rgb_color of light %s", self._name, light)
service_data[ATTR_RGB_COLOR] = self._settings["rgb_color"]
Expand Down Expand Up @@ -1972,6 +2085,8 @@ async def significant_change(
last_service_data = self.last_service_data.get(light)
if last_service_data is None:
return
if switch._dim_to_warm_brightness_check:
adapt_brightness = False
compare_to = functools.partial(
_attributes_have_changed,
light=light,
Expand Down