-
Notifications
You must be signed in to change notification settings - Fork 141
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
feat: add different brightness ramping mechanisms #699
Conversation
98a117b
to
fb36952
Compare
471e93b
to
b1deda5
Compare
ec85c78
to
fb88a2b
Compare
Some code to plot the results: import numpy as np
import matplotlib.pyplot as plt
import math
from typing import Tuple
import mplcyberpunk
plt.style.use("cyberpunk")
def lerp(x, x1, x2, y1, y2):
"""Linearly interpolate between two values."""
return y1 + (x - x1) * (y2 - y1) / (x2 - x1)
def clamp(value: float, minimum: float, maximum: float) -> float:
"""Clamp value between minimum and maximum."""
return max(minimum, min(value, maximum))
def find_a_b(x1: float, x2: float, y1: float, y2: float) -> Tuple[float, float]:
a = (math.atanh(2 * y2 - 1) - math.atanh(2 * y1 - 1)) / (x2 - x1)
b = x1 - (math.atanh(2 * y1 - 1) / a)
return a, b
def scaled_tanh(
x: float,
a: float,
b: float,
y_min: float = 0.0,
y_max: float = 1.0,
) -> float:
"""Apply a scaled and shifted tanh function to a given input."""
return y_min + (y_max - y_min) * 0.5 * (math.tanh(a * (x - b)) + 1)
def is_closer_to_sunrise_than_sunset(time, sunrise_time, sunset_time):
"""Return True if the time is closer to sunrise than sunset."""
return abs(time - sunrise_time) < abs(time - sunset_time)
def brightness_linear(
time,
sunrise_time,
sunset_time,
time_light,
time_dark,
max_brightness,
min_brightness,
):
"""Calculate the brightness for the 'linear' mode."""
closer_to_sunrise = is_closer_to_sunrise_than_sunset(
time, sunrise_time, sunset_time
)
if closer_to_sunrise:
brightness = lerp(
time,
x1=sunrise_time - time_dark,
x2=sunrise_time + time_light,
y1=min_brightness,
y2=max_brightness,
)
else:
brightness = lerp(
time,
x1=sunset_time - time_light,
x2=sunset_time + time_dark,
y1=max_brightness,
y2=min_brightness,
)
return clamp(brightness, min_brightness, max_brightness)
def brightness_tanh(
time,
sunrise_time,
sunset_time,
time_light,
time_dark,
max_brightness,
min_brightness,
):
"""Calculate the brightness for the 'tanh' mode."""
closer_to_sunrise = is_closer_to_sunrise_than_sunset(
time, sunrise_time, sunset_time
)
if closer_to_sunrise:
a, b = find_a_b(
x1=-time_dark,
x2=time_light,
y1=0.05, # be at 5% of range at x1
y2=0.95, # be at 95% of range at x2
)
brightness = scaled_tanh(
time - sunrise_time,
a=a,
b=b,
y_min=min_brightness,
y_max=max_brightness,
)
else:
a, b = find_a_b(
x1=-time_light, # shifted timestamp for the start of sunset
x2=time_dark, # shifted timestamp for the end of sunset
y1=0.95, # be at 95% of range at the start of sunset
y2=0.05, # be at 5% of range at the end of sunset
)
brightness = scaled_tanh(
time - sunset_time,
a=a,
b=b,
y_min=min_brightness,
y_max=max_brightness,
)
return clamp(brightness, min_brightness, max_brightness)
# Define the constants
sunrise_time = 6 # 6 AM
sunset_time = 18 # 6 PM
max_brightness = 1.0 # 100%
min_brightness = 0.3 # 30%
brightness_mode_time_dark = 3.0
brightness_mode_time_light = 0.5
for min_brightness, brightness_mode_time_dark, brightness_mode_time_light in [
(0.0, 2, 0),
(0.0, 0, 2),
(0.3, 3.0, 0.5),
(0.1, 4, 4)
]:
# Define the time range for our simulation
time_range = np.linspace(0, 24, 1000) # From 0 to 24 hours
# Calculate the brightness for each time in the time range for both modes
brightness_linear_values = [
brightness_linear(
time,
sunrise_time,
sunset_time,
brightness_mode_time_light,
brightness_mode_time_dark,
max_brightness,
min_brightness,
)
for time in time_range
]
brightness_tanh_values = [
brightness_tanh(
time,
sunrise_time,
sunset_time,
brightness_mode_time_light,
brightness_mode_time_dark,
max_brightness,
min_brightness,
)
for time in time_range
]
# Plot the brightness over time for both modes
plt.figure(figsize=(10, 6))
plt.plot(time_range, brightness_linear_values, label="Linear Mode")
plt.plot(time_range, brightness_tanh_values, label="Tanh Mode")
plt.vlines(sunrise_time, 0, 1, color="C2", label="Sunrise", linestyles="dashed")
plt.vlines(sunset_time, 0, 1, color="C3", label="Sunset", linestyles="dashed")
plt.xlim(0, 24)
plt.xticks(np.arange(0, 25, 1))
yticks = np.arange(0, 1.05, 0.05)
ytick_labels = [f"{100*label:.0f}%" for label in yticks]
plt.yticks(yticks, ytick_labels)
plt.xlabel("Time (hours)")
plt.ylabel("Brightness")
plt.title("Brightness over Time for Different Modes")
# Add text box
textstr = "\n".join(
(
f"Sunrise Time = {sunrise_time}:00:00",
f"Sunset Time = {sunset_time}:00:00",
f"Max Brightness = {max_brightness*100:.0f}%",
f"Min Brightness = {min_brightness*100:.0f}%",
f"Time Light = {brightness_mode_time_light} hours",
f"Time Dark = {brightness_mode_time_dark} hours",
)
)
# these are matplotlib.patch.Patch properties
props = dict(boxstyle="round", facecolor="wheat", alpha=0.5)
plt.legend()
plt.grid(True)
mplcyberpunk.add_glow_effects()
# place a text box in upper left in axes coords
plt.gca().text(
0.4,
0.55,
textstr,
transform=plt.gca().transAxes,
fontsize=10,
verticalalignment="center",
bbox=props,
)
plt.show() |
34360cb
to
16a4f86
Compare
16a4f86
to
0b8321f
Compare
92a351e
to
a670a09
Compare
Can we have something like this for temperature too? |
Yes that might be possible but I would like to make something to go between a set of custom colors or color temperatures. However, I need to think of a good interface to set it. |
How about just allowing users to put in a specific formula and then maybe add some examples to readme (with link in the HA options menu)? |
I think this will lead to a lot of trouble and confusion because the math is likely non-trivial for most folks. For example check the adaptive-lighting/custom_components/adaptive_lighting/helpers.py Lines 19 to 102 in 104664d
|
Check out this new webapp to visualize the parameters https://basnijholt.github.io/adaptive-lighting/ adaptive-lighting.mp4 |
Not sure if I got this correct but a while aso I was thinking it would be great if it used colour temp during the day and then RGB colours at night. This may already be done through the sleep settings but I dont quite understand how this works (from a user POV). I often set lights to amber at night and doing this automatialy would be really nice. |
@funkytwig, that is a good suggestion and close to my plan. I think one should be able to provide 3 or 5 RGB colors or color temperatures and it will continuously interpolate between those colors. |
@basnijholt not sure if I quite follow. My thought was something like a checkbox that if you are using temp it cross fades to night colour or another defined colour from temp. The duration of the cross fade ime not sure about but it could be the last 25 present of brightness could be a nightcolour_percent setting. We may be getting too many settings. Makes me think of a verry old book called 'inmates are running the asylum' about UI design. https://www.oreilly.com/library/view/inmates-are-running/0672326140/. A verry interesting read. Sets out how to design mobile and web apps years before they existed. The fact that I have done video colour grading and stage lighting may explain my approach and why I love your add-on. |
You are right about the number of parameters/variables getting out of control, which is why I would like to keep it "simple". I will check out that book, thanks for the suggestion! |
The The defaults shift the adaptation quite a bit into the I guess what I am actually asking here is if there's data which indicates a preference, physiologically. |
This is why I thought a single checkbox 'Use night color at night if using color temp' was the way to do it. This would use the color set above for the night. Crossfading from color temp to night color at night. It may be that this would only be supported if using the brightness_mode_time settings. I think one of the problems is parameters need a bit more explanation. Maybe add a description field for each parameter and if the text is in this field it is displayed between the current parameter names and the value (maybe in a thin box). I think sections would also be useful with section titles and some text under them. I thought this would make the page a lot longer but think it is worth it. I think the first section (Basic Settings) would be up to prefer_rgb_colours and the second section Sleep Settings.... If you want to do this I can help work out what text to put in and what sections to have. |
Hi. That was my idea. May be worth reading this thread to understand the logic. Basically, I wanted to be able to easily define the duration from the 100% daytime setting to the minimum % setting. Basically, I wanted the lights to start dimming 45 mins before sunset and end dimming 45 minutes after it (but the actual durations depend on how close/far you are from the equator). I wanted the lights to be at their dimmest for the whole time the sun was totaly set and not have lights coming back up until sun started rising. I was finding early in the morning before the sun started rising, the lights were coming up too much for me. So having a sunset/sunrise offset did not help. Hope this makes sence. |
Thanks, that makes sense (for reference, the mentioned thread is #616).
Regarding the "too many parameters" discussion above, adding a sunrise/sunset duration parameter to the offset would fix that and simplify the two |
It simply moves the the curve. It does not change how quickly the transition from brightest to darkest happens for a light. Have a look at https://basnijholt.github.io/adaptive-lighting/. This is why the devs implemented it. Maybe they will chip in to clarify but simply changing the offsets does not do what I wanted. This has all been implemented in the beta (and possibly in the main verison now). |
PS Sorry, misread your comment. I personally prefer absolute numbers to offsets. They are easier (certainly for me) to get my head around. I think the reason they opted for two parameters is because it gives more flexibility (this may not be a good thing, see 'The inmates are running the asylum' reference above, https://www.oreilly.com/library/view/inmates-are-running/0672326140/). I would be very happy with just a single parameter as an absolute but as I said not like offsets as much. However, removing parameters makes migration tricky. |
A bit of context te ''The inmates are running the asylum'. It's a book written about UI design that was written when there were barely any web apps, let alone mobile apps. The general gist has to do with the 80/20 rule (although I am not sure if it actually references it directly). Basically, 80% of the users of most software use only 20% of its functionality. Like 80% of your shopping in a supermarket trolly accounts for 20% of the cost. The remaining 20% accounts for 80% of the cost. So what has happened with web apps, and mobile apps, is they tend to concentrate on the 20% of the functionality that 80% of people use. Streamlining and simplifying them compared with traditional computer programs. If you get computer developers to design software and UI they tend to put as much functionality in as possible which can be confusing to a lot of users. If you get others (UI designers/analysts/software designers etc.) to decide the functionality/UI you end up with less confusing software which more people will use/want to use). Anyway, that is the general idea. The book is a great read, the other book that is very interesting is They Mythical Man Month by Brooks, but will leave that for another day ;). |
Initially, I considered an offset too. However, this would only allow for a symmetric offset around the sunset or sunrise. To allow for asymmetric offsets, one has to introduce yet another parameter to apply a shift in the offset, which also results in two parameters. Regarding the defaults, I have not given this a lot of thought at all, I only made them sufficiently different in the app to highlight that one can apply a different offset to before and after sunset/sunrise. I’m happy to change them to a perhaps more sensible default if you have a suggestion. |
Is it possible to have the ramping for "RGB/colour Intensity over time" follow the same curve as the brightness? I would like my colour temperature to be at the highest intensity for much longer then is possible currently. |
This PR allows setting different
brightness_mode
s which determine how the brightness changes around sunrise and sunset. Herebrightness_mode
can be either"default"
(current behavior),"linear"
, or"tanh"
. Additionally, when not using"default"
, you can setbrightness_mode_time_dark
andbrightness_mode_time_light
.with
brightness_mode: "linear"
:max_brightness
attime=sunset_time - brightness_mode_time_light
tomin_brightness
attime=sunset_time + brightness_mode_time_dark
.min_brightness
attime=sunrise_time - brightness_mode_time_dark
tomax_brightness
attime=sunrise_time + brightness_mode_time_light
.with
brightness_mode: "tanh"
it uses the smooth shape of a hyperbolic tangent function:max_brightness
attime=sunset_time - brightness_mode_time_light
to 5% of themin_brightness
attime=sunset_time + brightness_mode_time_dark
.min_brightness
attime=sunrise_time - brightness_mode_time_dark
to 95% of themax_brightness
attime=sunrise_time + brightness_mode_time_light
.Closes #616, #437, #218, #242, #127, #94, #72