You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* Add support for constraint functions to bound dynamic start times.
`notBefore`, `notAfter`, `clamp`, `min`, `max`
* Add mix() function for weighted blending of solar times and fixed times
* Add implementation of smooth() for exponentially smoothing a time expression over past days
Copy file name to clipboardExpand all lines: CHANGELOG.md
+13-1Lines changed: 13 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,7 +1,19 @@
1
+
## [0.16.0] - 2026-04-06
2
+
3
+
### Added
4
+
-**Start time function expressions** (#48): New composable functions for constraining and blending solar and fixed times in start time expressions. All arguments may be fixed times, sun keywords (with optional offset), or nested function calls.
5
+
-`notBefore(expr, min)` — ensures the result is not earlier than `min`; returns the latter of the two. Alias: `max(a, b)`.
6
+
-`notAfter(expr, max)` — ensures the result is not later than `max`; returns the earlier of the two. Alias: `min(a, b)`.
7
+
-`clamp(expr, min, max)` — constrains the result to the `[min, max]` range.
8
+
-`mix(a, b, weight)` — linearly interpolates between two times with the given `weight` in `[0..1]` or as a percentage (`0%..100%`). A weight of `1` returns `a`, `0` returns `b`. Example: `mix(sunrise, 08:00, 0.35)` blends toward a fixed anchor, softening seasonal drift.
9
+
-`smooth(expr, halfLife)` — exponentially smooths a time expression over past days to reduce day-to-day variation. `halfLife` specifies how many days it takes for the smoothing influence to halve (e.g. `14d`). Useful for softening sharp seasonal sunrise/sunset swings. Example: `smooth(sunrise, 14d)`.
10
+
- Functions are case-insensitive and fully composable, e.g. `clamp(smooth(sunrise, 14d), 07:00, 09:00)`.
11
+
- Full documentation is available in the [Light Configuration docs](/docs/light_configuration.md#constraint-functions).
12
+
1
13
## [0.15.0] - 2026-03-18
2
14
3
15
### Added
4
-
-**Scene scheduling for groups** (`scene:<name>`) (#47):
16
+
-**Scene scheduling for groups** (`scene:<name>`) (#29):
5
17
- Load per-light states from an existing Hue scene and schedule them as a group state. Each light retains its individual brightness, color temperature, color, effect, and gradient settings.
6
18
- Auto-reloads the light states on scene changes.
7
19
- Proportional brightness scaling with `bri` (e.g., `bri:50%` dims all lights to half; values above `100%` boost proportionally, capped per light).
Copy file name to clipboardExpand all lines: docs/light_configuration.md
+87-2Lines changed: 87 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -60,6 +60,8 @@ Each state has a start time, specified either as a fixed time (24-hour `HH:mm[:s
60
60
61
61
These times vary by location and date. To see your current values, start Hue Scheduler with an empty input file.
62
62
63
+
> Note: Background on twilight terms: [Twilight - Wikipedia](https://en.wikipedia.org/wiki/Twilight) and [Twilight - commons-suncalc](https://shredzone.org/maven/commons-suncalc/usage.html#twilight).
64
+
63
65
You can also **offset** solar times:
64
66
65
67
```yacas
@@ -68,7 +70,91 @@ You can also **offset** solar times:
68
70
69
71
Examples: `sunset-30` (30 minutes before sunset), `sunrise+60` (one hour after sunrise). Offsets update daily with the sun.
70
72
71
-
> Note: Background on twilight terms: [Twilight - Wikipedia](https://en.wikipedia.org/wiki/Twilight) and [Twilight - commons-suncalc](https://shredzone.org/maven/commons-suncalc/usage.html#twilight).
73
+
### Constraint Functions
74
+
75
+
You can wrap any start time expression in a **constraint function** to bound dynamic solar times to fixed limits. This is useful when sunrise or sunset varies too much across seasons.
|`notBefore(expr, limit)`| 2 | The **later** of `expr` and `limit` (ensures start is not before `limit`). E.g. `notBefore(sunrise, 06:30)`|
82
+
|`notAfter(expr, limit)`| 2 | The **earlier** of `expr` and `limit` (ensures start is not after `limit`). E.g. `notAfter(sunset+30, 21:00)`|
83
+
|`clamp(expr, min, max)`| 3 |`expr` bounded to `[min, max]`; if `min > max`, logs a warning and returns `expr` unchanged. E.g. `clamp(sunrise, 06:30, 08:00)`|
84
+
|`max(a, b)`| 2 | Alias for `notBefore` — returns the later of two times |
85
+
|`min(a, b)`| 2 | Alias for `notAfter` — returns the earlier of two times |
86
+
|`mix(a, b, w)`| 3 |**Experimental**: Places the time between `a` and `b` using weight `w` (`0..1` or `%`). E.g. `mix(sunrise, 07:30, 35%)`|
87
+
|`smooth(expr, halfLife)`| 2 |**Experimental**: Smooths `expr` by averaging it over past days. E.g. `smooth(sunrise, 14d)`|
88
+
89
+
Each argument can be a fixed time (`HH:mm[:ss]`), a solar keyword with optional offset, or another nested function call.
90
+
91
+
Function names are **case-insensitive**. Whitespace inside arguments is trimmed.
92
+
93
+
Further examples:
94
+
95
+
```
96
+
# Ensure lights don't turn on before 06:30 even in summer when sunrise is early
97
+
Kitchen notBefore(sunrise, 06:30) bri:100%
98
+
99
+
# Cap sunset-based scheduling to no later than 21:00
#### Experimental: `mix(...)` — blend two time expressions
128
+
129
+
`mix(a, b, w)` places the trigger time between two time expressions `a` and `b`. The weight `w` controls how close the result is to `a`:
130
+
131
+
-`w = 1` → exactly `a`
132
+
-`w = 0` → exactly `b`
133
+
-`w = 0.5` → midpoint between `a` and `b`
134
+
135
+
A common use is blending a solar time with a fixed clock time: `mix(sunrise, 07:30, 0.35)` takes sunrise but pulls it 65% toward `07:30` — so the schedule still moves with the seasons, but much more gently. You can also blend two solar times directly: `mix(golden_hour, sunset, 0.5)`. The order of `a` and `b` does not matter — the result is always between the two.
136
+
137
+
**Why use it:** If you like solar-based schedules but want them to behave more like a stable routine (e.g., "around 07:30, but still season-aware"), `mix`**narrows the seasonal range**. However, since both values are recalculated fresh each day, day-to-day jumps are not smoothed out.
138
+
139
+
#### Experimental: `smooth(...)` — keep it fully solar, but slow down seasonal swings
140
+
141
+
`smooth(expr, halfLife)` keeps the schedule 100% solar-based, but smooths out rapid seasonal changes by averaging the solar time over past days — more recent days count more, older days fade out.
142
+
143
+
-`halfLife` is in days (e.g., `14d`, `14`) and controls how "inert" the time is. With `halfLife = 14d`, the value from ~14 days ago still contributes about half as much as today's; older days fade out quickly
**Why use it:** If sunrise/sunset schedules feel like they shift too quickly in spring and autumn, `smooth` addresses that directly — without introducing a fixed routine time. The schedule still tracks the seasons, just more gradually. Over time, it will still reach the full seasonal extreme. To add hard limits, wrap in `clamp`: `clamp(smooth(sunrise, 14d), 06:30, 08:00)`.
|**How it works**| Blends two expressions evaluated *today*| Averages one expression over past days |
153
+
|**Requires a second expression?**| Yes (`b`) | No |
154
+
|**Reduces seasonal range?**| Yes — pulls toward `b`| No — eventually reaches the true extreme |
155
+
|**Reduces day-to-day jumps?**| No — recalculated fresh each day | Yes — changes gradually |
156
+
157
+
**Rule of thumb:** Use `mix` to narrow the seasonal range; use `smooth` to slow day-to-day changes. Combine them — or add `clamp` — for maximum control.
0 commit comments