Add Daylight Cycle value source type
New value source that outputs brightness (0-1) based on the daylight color LUT, computing BT.601 luminance from the simulated sky color. Supports real-time wall-clock mode or configurable simulation speed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"""Value stream — runtime scalar signal generators.
|
||||
|
||||
A ValueStream wraps a ValueSource config and computes a float (0.0–1.0)
|
||||
on demand via ``get_value()``. Five concrete types:
|
||||
on demand via ``get_value()``. Six concrete types:
|
||||
|
||||
StaticValueStream — returns a constant
|
||||
AnimatedValueStream — evaluates a periodic waveform (sine/triangle/square/sawtooth)
|
||||
@@ -9,6 +9,7 @@ on demand via ``get_value()``. Five concrete types:
|
||||
sensitivity and temporal smoothing
|
||||
TimeOfDayValueStream — interpolates brightness along a 24h schedule (adaptive_time)
|
||||
SceneValueStream — derives brightness from a picture source's frame luminance (adaptive_scene)
|
||||
DaylightValueStream — brightness derived from daylight cycle LUT (real-time or simulated)
|
||||
|
||||
ValueStreams are cheap (trivial math or single poll), so they compute inline
|
||||
in the caller's processing loop — no background threads required.
|
||||
@@ -541,6 +542,64 @@ class SceneValueStream(ValueStream):
|
||||
self._live_stream = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Daylight
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class DaylightValueStream(ValueStream):
|
||||
"""Brightness derived from the daylight color cycle LUT.
|
||||
|
||||
Computes BT.601 luminance from the RGB sky color at the current
|
||||
simulated (or real-time) minute of day, then maps to [min, max].
|
||||
Cheap inline computation — no background thread needed.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
speed: float = 1.0,
|
||||
use_real_time: bool = False,
|
||||
latitude: float = 50.0,
|
||||
min_value: float = 0.0,
|
||||
max_value: float = 1.0,
|
||||
):
|
||||
from wled_controller.core.processing.daylight_stream import _get_daylight_lut
|
||||
self._lut = _get_daylight_lut()
|
||||
self._speed = speed
|
||||
self._use_real_time = use_real_time
|
||||
self._latitude = latitude
|
||||
self._min = min_value
|
||||
self._max = max_value
|
||||
self._start_time = time.perf_counter()
|
||||
|
||||
def get_value(self) -> float:
|
||||
if self._use_real_time:
|
||||
now = datetime.now()
|
||||
minute_of_day = now.hour * 60 + now.minute + now.second / 60.0
|
||||
else:
|
||||
t_elapsed = time.perf_counter() - self._start_time
|
||||
cycle_seconds = 240.0 / max(self._speed, 0.01)
|
||||
phase = (t_elapsed % cycle_seconds) / cycle_seconds
|
||||
minute_of_day = phase * 1440.0
|
||||
|
||||
idx = int(minute_of_day) % 1440
|
||||
r, g, b = self._lut[idx]
|
||||
|
||||
# BT.601 luminance → 0..1
|
||||
luminance = (0.299 * float(r) + 0.587 * float(g) + 0.114 * float(b)) / 255.0
|
||||
|
||||
return self._min + luminance * (self._max - self._min)
|
||||
|
||||
def update_source(self, source: "ValueSource") -> None:
|
||||
from wled_controller.storage.value_source import DaylightValueSource
|
||||
if isinstance(source, DaylightValueSource):
|
||||
self._speed = source.speed
|
||||
self._use_real_time = source.use_real_time
|
||||
self._latitude = source.latitude
|
||||
self._min = source.min_value
|
||||
self._max = source.max_value
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Manager
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -635,6 +694,7 @@ class ValueStreamManager:
|
||||
AdaptiveValueSource,
|
||||
AnimatedValueSource,
|
||||
AudioValueSource,
|
||||
DaylightValueSource,
|
||||
StaticValueSource,
|
||||
)
|
||||
|
||||
@@ -663,6 +723,15 @@ class ValueStreamManager:
|
||||
audio_template_store=self._audio_template_store,
|
||||
)
|
||||
|
||||
if isinstance(source, DaylightValueSource):
|
||||
return DaylightValueStream(
|
||||
speed=source.speed,
|
||||
use_real_time=source.use_real_time,
|
||||
latitude=source.latitude,
|
||||
min_value=source.min_value,
|
||||
max_value=source.max_value,
|
||||
)
|
||||
|
||||
if isinstance(source, AdaptiveValueSource):
|
||||
if source.source_type == "adaptive_scene":
|
||||
return SceneValueStream(
|
||||
|
||||
Reference in New Issue
Block a user