adaptive-lighting/webapp/app.py
Bas Nijholt 69355b8c3a
Small WebApp improvements (#716)
* Small WebApp improvements

* Add link
2023-08-07 00:20:58 +00:00

284 lines
9 KiB
Python

"""Simple web app to visualize brightness over time."""
import math
import matplotlib.pyplot as plt
import numpy as np
from shiny import App, render, ui
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)
def plot_brightness(
min_brightness,
max_brightness,
brightness_mode_time_dark,
brightness_mode_time_light,
sunrise_time=6, # 6 AM
sunset_time=18, # 6 PM
):
# 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:.1f} hours",
f"Time Dark = {brightness_mode_time_dark:.1f} hours",
),
)
# these are matplotlib.patch.Patch properties
props = {"boxstyle": "round", "facecolor": "wheat", "alpha": 0.5}
plt.legend()
plt.grid(True)
# 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,
)
return plt.gcf()
SEC_PER_HR = 60 * 60
desc = """
**Experience the Dynamics of [Adaptive Lighting](https://github.com/basnijholt/adaptive-lighting) in Real-Time.**
Have you ever wondered how the intricate settings of [Adaptive Lighting](https://github.com/basnijholt/adaptive-lighting) impact your home ambiance? The Adaptive Lighting Simulator WebApp is here to demystify just that.
Harnessing the technology of the popular Adaptive Lighting integration for Home Assistant, this webapp provides a hands-on, visual platform to explore, tweak, and understand the myriad of parameters that dictate the behavior of your smart lights. Whether you're aiming for a subtle morning glow or a cozy evening warmth, observe firsthand how each tweak changes the ambiance.
**Why Use the Simulator?**
- **Interactive Exploration**: No more guesswork. See in real-time how changes to settings influence the lighting dynamics.
- **Circadian Cycle Preview**: Understand how Adaptive Lighting adjusts throughout the day based on specific parameters, ensuring your lighting aligns with your circadian rhythms.
- **Tailored Testing**: Play with parameters and find the perfect combination that suits your personal or family's needs.
- **Educational Experience**: For both newbies and experts, delve deep into the intricacies of Adaptive Lighting's logic and potential.
Dive into the simulator, experiment with different settings, and fine-tune the behavior of Adaptive Lighting to perfection. Whether you're setting it up for the first time or optimizing an existing setup, this tool ensures you get the most out of your smart lighting experience.
"""
# Shiny UI
app_ui = ui.page_fluid(
ui.panel_title("🌞 Adaptive Lighting Simulator WebApp 🌛"),
ui.layout_sidebar(
ui.panel_sidebar(
ui.input_slider("min_brightness", "min_brightness", 0, 100, 30, post="%"),
ui.input_slider("max_brightness", "max_brightness", 0, 100, 100, post="%"),
ui.input_slider(
"dark_time",
"brightness_mode_time_dark",
0,
5 * SEC_PER_HR,
3 * SEC_PER_HR,
post=" sec",
),
ui.input_slider(
"light_time",
"brightness_mode_time_light",
0,
5 * SEC_PER_HR,
0.5 * SEC_PER_HR,
post=" sec",
),
ui.input_slider(
"sunrise_time",
"sunrise_time",
0,
24,
6,
step=0.5,
post=" hr",
),
ui.input_slider(
"sunset_time",
"sunset_time",
0,
24,
18,
step=0.5,
post=" hr",
),
),
ui.panel_main(ui.markdown(desc), ui.output_plot(id="brightness_plot")),
),
)
def server(input, output, session):
@output
@render.plot
def brightness_plot():
return plot_brightness(
min_brightness=input.min_brightness() / 100,
max_brightness=input.max_brightness() / 100,
brightness_mode_time_dark=input.dark_time() / SEC_PER_HR,
brightness_mode_time_light=input.light_time() / SEC_PER_HR,
sunrise_time=input.sunrise_time(),
sunset_time=input.sunset_time(),
)
app = App(app_ui, server)