virtual sunrise blueprint

This commit is contained in:
Reindl David (IT-PTR-CEN2-SL10) 2026-01-18 23:50:28 +01:00
parent 5f6a2223ad
commit 9729d17355
2 changed files with 352 additions and 0 deletions

View File

@ -0,0 +1,3 @@
# Home Assistant Playground
Tinkering place to extend, amend and try out different aspects of Home Assistant like configuration, blueprints, …

View File

@ -0,0 +1,349 @@
blueprint:
name: Wake-up Sunrise (Kelvin, smooth & smart) - Fixed
description: >
Moderner Wecker mit Sonnenaufgangseffekt. Korrigierte Version mit sequenzieller Variablen-Berechnung.
domain: automation
input:
light_entity:
name: Ziel-Licht
selector:
entity:
domain: light
timestamp_sensor:
name: Zeitstempel-Entität (optional)
default: none
selector:
entity:
device_class: timestamp
manual_time:
name: Manuelle Weckzeit
default: "07:00:00"
selector:
time: {}
sunrise_duration_min:
name: Dauer des Sonnenaufgangs (Minuten)
default: 25
selector:
number:
min: 5
max: 120
step: 5
unit_of_measurement: min
mode: slider
start_brightness_pct:
name: Starthelligkeit (%)
default: 3
selector:
number:
min: 1
max: 100
step: 1
mode: slider
end_brightness_pct:
name: Endhelligkeit (%)
default: 100
selector:
number:
min: 5
max: 100
step: 1
mode: slider
start_kelvin:
name: Start-Kelvin (warm)
default: 2200
selector:
number:
min: 1500
max: 9000
step: 50
unit_of_measurement: K
mode: slider
end_kelvin:
name: End-Kelvin (kühler / tageslicht)
default: 6500
selector:
number:
min: 1500
max: 9000
step: 50
unit_of_measurement: K
mode: slider
enable_color_ramp:
name: Farbverlauf (RGB) statt Kelvin
default: false
selector:
boolean: {}
start_color_rgb:
name: Startfarbe (RGB)
default: { r: 255, g: 120, b: 20 }
selector:
color_rgb: {}
end_color_rgb:
name: Endfarbe (RGB)
default: { r: 255, g: 255, b: 255 }
selector:
color_rgb: {}
ramp_steps:
name: Schrittanzahl (Glätte)
default: 60
selector:
number:
min: 10
max: 240
step: 10
check_entity:
name: Check-Entität
default: none
selector:
entity: {}
require_workday:
name: Nur an Arbeitstagen
default: false
selector:
boolean: {}
workday_sensor:
name: Workday-Sensor
default: binary_sensor.workday
selector:
entity:
domain: binary_sensor
respect_quiet_hours:
name: Ruhezeiten beachten
default: false
selector:
boolean: {}
quiet_start:
name: Ruhezeit Beginn
default: "22:00:00"
selector:
time: {}
quiet_end:
name: Ruhezeit Ende
default: "06:00:00"
selector:
time: {}
off_cancels:
name: Ausschalten bricht ab
default: true
selector:
boolean: {}
pre_actions:
name: Vor-Aktionen
default: []
selector:
action: {}
post_actions:
name: Nach-Aktionen
default: []
selector:
action: {}
# Globale Variablen (nur statische Zuweisungen aus Inputs)
variables:
le: !input light_entity
sensor_ts: !input timestamp_sensor
manual_time: !input manual_time
duration_min: !input sunrise_duration_min
start_pct: !input start_brightness_pct
end_pct: !input end_brightness_pct
steps: !input ramp_steps
enable_rgb: !input enable_color_ramp
off_cancels: !input off_cancels
check_entity: !input check_entity
require_workday: !input require_workday
workday_sensor: !input workday_sensor
respect_quiet: !input respect_quiet_hours
quiet_start: !input quiet_start
quiet_end: !input quiet_end
startK_in: !input start_kelvin
endK_in: !input end_kelvin
rgb_start: !input start_color_rgb
rgb_end: !input end_color_rgb
trigger:
- platform: time_pattern
minutes: "*"
action:
# Ebene 1: Basis-Berechnungen und Geräte-Attribute holen
- variables:
seconds: "{{ (duration_min | float(25)) * 60 }}"
range_pct: "{{ (end_pct | float) - (start_pct | float) }}"
minK_native: "{{ state_attr(le, 'min_color_temp_kelvin') }}"
maxK_native: "{{ state_attr(le, 'max_color_temp_kelvin') }}"
minM: "{{ state_attr(le, 'min_mireds') }}"
maxM: "{{ state_attr(le, 'max_mireds') }}"
sr: "{{ rgb_start.r | int }}"
sg: "{{ rgb_start.g | int }}"
sb: "{{ rgb_start.b | int }}"
er: "{{ rgb_end.r | int }}"
eg: "{{ rgb_end.g | int }}"
eb: "{{ rgb_end.b | int }}"
supported_modes: "{{ state_attr(le, 'supported_color_modes') | default([]) }}"
# Ebene 2: Berechnungen, die auf Ebene 1 basieren (z.B. device_k_min)
- variables:
device_k_min: >-
{% if minK_native is number %}{{ minK_native | int }}
{% elif maxM is number %}{{ (1000000 / (maxM | float)) | int }}
{% else %}2000{% endif %}
device_k_max: >-
{% if maxK_native is number %}{{ maxK_native | int }}
{% elif minM is number %}{{ (1000000 / (minM | float)) | int }}
{% else %}6500{% endif %}
tick: >-
{% set t = (seconds | float) / (steps | float) %}
{{ [ t, 1 ] | max | int }}
can_rgb: >-
{{ 'hs' in supported_modes or 'rgb' in supported_modes or 'rgbw' in supported_modes or 'rgbww' in supported_modes or 'xy' in supported_modes }}
can_ct: >-
{{ 'color_temp' in supported_modes or minM is number or minK_native is number or maxM is number or maxK_native is number }}
# Ebene 3: Finale Kelvin-Werte und Bedingungen
- variables:
startK_clamped: >-
{% set s1 = [startK_in | int, device_k_min] | max %}
{{ [s1, device_k_max] | min }}
endK_clamped: >-
{% set e1 = [endK_in | int, device_k_min] | max %}
{{ [e1, device_k_max] | min }}
cond_check_entity_ok: >-
{{ check_entity == 'none' or states(check_entity) in ['on','home','unknown'] }}
cond_workday_ok: >-
{{ (not require_workday) or (is_state(workday_sensor, 'on')) }}
cond_quiet_ok: >-
{% if not respect_quiet %}true
{% else %}
{% set nowt = now().time() %}
{% set qs = strptime(quiet_start, '%H:%M:%S').time() %}
{% set qe = strptime(quiet_end, '%H:%M:%S').time() %}
{% if qs <= qe %}{{ not (nowt >= qs and nowt < qe) }}
{% else %}{{ not (nowt >= qs or nowt < qe) }}{% endif %}
{% endif %}
# Ebene 4: Finalisierung der Kelvin-Werte
- variables:
start_kelvin_final: "{{ [startK_clamped, endK_clamped] | min }}"
end_kelvin_final: "{{ [startK_clamped, endK_clamped] | max }}"
# --- Ablauf ---
- wait_template: >-
{{ sensor_ts == 'none' or as_timestamp(states(sensor_ts), None) != None }}
- wait_template: >-
{% set alarm_ts = as_timestamp(sensor_ts != 'none' and states(sensor_ts) or (states('sensor.date') ~ ' ' ~ manual_time)) %}
{% set now_ts = as_timestamp(now()) %}
{{ 0 < (alarm_ts - now_ts) <= (seconds | float) and cond_check_entity_ok and cond_workday_ok and cond_quiet_ok }}
- choose: []
default: !input pre_actions
- condition: template
value_template: "{{ cond_check_entity_ok and cond_workday_ok and cond_quiet_ok }}"
- choose:
- conditions: "{{ enable_rgb and can_rgb }}"
sequence:
- service: light.turn_on
target:
entity_id: !input light_entity
data:
brightness_pct: "{{ start_pct | int }}"
rgb_color: ["{{ sr }}","{{ sg }}","{{ sb }}"]
transition: 1
- conditions: "{{ can_ct }}"
sequence:
- service: light.turn_on
target:
entity_id: !input light_entity
data:
brightness_pct: "{{ start_pct | int }}"
color_temp_kelvin: "{{ start_kelvin_final | int }}"
transition: 1
default:
- service: light.turn_on
target:
entity_id: !input light_entity
data:
brightness_pct: "{{ start_pct | int }}"
transition: 1
- repeat:
while:
- >-
{% set alarm_ts = as_timestamp(sensor_ts != 'none' and states(sensor_ts) or (states('sensor.date') ~ ' ' ~ manual_time)) %}
{{ 0 < (alarm_ts - as_timestamp(now())) <= (seconds | float) }}
- "{{ not (off_cancels and is_state(le, 'off')) }}"
sequence:
- delay:
seconds: "{{ tick | int }}"
- variables:
alarm_ts: >-
{{ as_timestamp(sensor_ts != 'none' and states(sensor_ts) or (states('sensor.date') ~ ' ' ~ manual_time)) }}
remain: "{{ (alarm_ts - as_timestamp(now())) | float }}"
frac: "{{ (remain / (seconds | float)) | float }}"
bri_now: "{{ ((end_pct | float) - ((range_pct | float) * frac)) | round(0) | int }}"
r_now: "{{ (sr + (er - sr) * (1 - frac)) | round(0) | int }}"
g_now: "{{ (sg + (eg - sg) * (1 - frac)) | round(0) | int }}"
b_now: "{{ (sb + (eb - sb) * (1 - frac)) | round(0) | int }}"
kelv_now: "{{ ((end_kelvin_final | float) - ((end_kelvin_final | float - start_kelvin_final | float) * frac)) | round(0) | int }}"
- choose:
- conditions: "{{ enable_rgb and can_rgb }}"
sequence:
- service: light.turn_on
target:
entity_id: !input light_entity
data:
brightness_pct: "{{ [bri_now, 1] | max }}"
rgb_color: ["{{ r_now }}","{{ g_now }}","{{ b_now }}"]
transition: "{{ [ (tick | int) - 1, 0 ] | max }}"
- conditions: "{{ can_ct }}"
sequence:
- service: light.turn_on
target:
entity_id: !input light_entity
data:
brightness_pct: "{{ [bri_now, 1] | max }}"
color_temp_kelvin: "{{ kelv_now }}"
transition: "{{ [ (tick | int) - 1, 0 ] | max }}"
default:
- service: light.turn_on
target:
entity_id: !input light_entity
data:
brightness_pct: "{{ [bri_now, 1] | max }}"
transition: "{{ [ (tick | int) - 1, 0 ] | max }}"
- choose:
- conditions: "{{ enable_rgb and can_rgb }}"
sequence:
- service: light.turn_on
target:
entity_id: !input light_entity
data:
brightness_pct: "{{ end_pct | int }}"
rgb_color: ["{{ er }}","{{ eg }}","{{ eb }}"]
transition: 1
- conditions: "{{ can_ct }}"
sequence:
- service: light.turn_on
target:
entity_id: !input light_entity
data:
brightness_pct: "{{ end_pct | int }}"
color_temp_kelvin: "{{ end_kelvin_final | int }}"
transition: 1
default:
- service: light.turn_on
target:
entity_id: !input light_entity
data:
brightness_pct: "{{ end_pct | int }}"
transition: 1
- choose: []
default: !input post_actions
mode: single
max_exceeded: silent