-
-
Save psychowood/94ae85bca884c6973cbdcf1032273a0c to your computer and use it in GitHub Desktop.
Wake-up Sunrise (Kelvin, smooth & smart)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| blueprint: | |
| name: Wake-up Sunrise (Kelvin, smooth & smart) | |
| description: > | |
| Modern alarm clock with sunrise effect. Default: warm white (≈2200 K) → daylight white (≈6500 K), | |
| automatically adapted to device limits. Optionally use an RGB color curve from start to end color instead of Kelvin. | |
| Future-proof thanks to color_temp_kelvin. For multiple lamps, please select a Light Group. | |
| domain: automation | |
| input: | |
| light_entity: | |
| name: Target Light | |
| description: > | |
| Light or Light Group to be woken up. | |
| selector: | |
| entity: | |
| domain: light | |
| # --- Alarm Source --- | |
| timestamp_sensor: | |
| name: Timestamp Entity (optional) | |
| description: > | |
| Sensor with device_class=timestamp (e.g. phone alarm). If 'none', manual time is used. | |
| default: none | |
| selector: | |
| entity: | |
| device_class: timestamp | |
| manual_time: | |
| name: Manual Wake Time | |
| description: > | |
| Daily wake time when no timestamp sensor is set (format HH:MM:SS). | |
| default: "07:00:00" | |
| selector: | |
| time: {} | |
| # --- Duration & Brightness --- | |
| sunrise_duration_min: | |
| name: Sunrise Duration (Minutes) | |
| description: > | |
| Total duration of the ramp for brightness and color. | |
| default: 25 | |
| selector: | |
| number: | |
| min: 5 | |
| max: 120 | |
| step: 5 | |
| unit_of_measurement: min | |
| mode: slider | |
| start_brightness_pct: | |
| name: Start Brightness (%) | |
| description: > | |
| Initial brightness at the beginning of the ramp. 1–10% recommended (some lights ignore 1%). | |
| default: 3 | |
| selector: | |
| number: | |
| min: 1 | |
| max: 100 | |
| step: 1 | |
| mode: slider | |
| end_brightness_pct: | |
| name: End Brightness (%) | |
| description: > | |
| Target brightness at the end of the ramp. | |
| default: 100 | |
| selector: | |
| number: | |
| min: 5 | |
| max: 100 | |
| step: 1 | |
| mode: slider | |
| # --- Kelvin Ramp (Default) --- | |
| start_kelvin: | |
| name: Start Kelvin (Warm) | |
| description: > | |
| Color temperature at the beginning of the ramp (e.g. 2200 K = warm). Automatically adjusted to device limits. | |
| default: 2200 | |
| selector: | |
| number: | |
| min: 1500 | |
| max: 9000 | |
| step: 50 | |
| unit_of_measurement: K | |
| mode: slider | |
| end_kelvin: | |
| name: End Kelvin (Cooler / Daylight) | |
| description: > | |
| Color temperature at the end of the ramp (e.g. 6500 K = daylight white). Automatically adjusted to device limits. | |
| default: 6500 | |
| selector: | |
| number: | |
| min: 1500 | |
| max: 9000 | |
| step: 50 | |
| unit_of_measurement: K | |
| mode: slider | |
| # --- Alternative: RGB Color Curve --- | |
| enable_color_ramp: | |
| name: Color Gradient (RGB) instead of Kelvin | |
| description: > | |
| Enable to use an RGB color gradient from start to end color instead of Kelvin. | |
| default: false | |
| selector: | |
| boolean: {} | |
| start_color_rgb: | |
| name: Start Color (RGB) | |
| description: > | |
| Start color for the RGB gradient (only relevant if color gradient is enabled). | |
| default: | |
| r: 255 | |
| g: 120 | |
| b: 20 | |
| selector: | |
| color_rgb: {} | |
| end_color_rgb: | |
| name: End Color (RGB) | |
| description: > | |
| End color for the RGB gradient (only relevant if color gradient is enabled). | |
| default: | |
| r: 255 | |
| g: 255 | |
| b: 255 | |
| selector: | |
| color_rgb: {} | |
| ramp_steps: | |
| name: Step Count (Smoothness) | |
| description: > | |
| Number of intermediate steps for brightness/color (more = smoother ramp, but more service calls). | |
| default: 60 | |
| selector: | |
| number: | |
| min: 10 | |
| max: 240 | |
| step: 10 | |
| # --- Conditions (optional) --- | |
| check_entity: | |
| name: Check Entity | |
| description: > | |
| Must be 'on' or 'home' for the alarm to start (e.g. Workday, Device Tracker, or Person entity). | |
| Leave empty (= none) if no condition is desired. | |
| default: none | |
| selector: | |
| entity: {} | |
| require_workday: | |
| name: Workdays Only | |
| description: > | |
| Execute only when the Workday sensor is 'on'. | |
| default: false | |
| selector: | |
| boolean: {} | |
| workday_sensor: | |
| name: Workday Sensor | |
| description: > | |
| Workday sensor to check when "Workdays Only" is active. | |
| default: binary_sensor.workday | |
| selector: | |
| entity: | |
| domain: binary_sensor | |
| respect_quiet_hours: | |
| name: Respect Quiet Hours | |
| description: > | |
| Prevents startup if the current time is within the quiet hours window. | |
| default: false | |
| selector: | |
| boolean: {} | |
| quiet_start: | |
| name: Quiet Hours Start | |
| description: > | |
| Start of the quiet hours window (format HH:MM:SS). | |
| default: "22:00:00" | |
| selector: | |
| time: {} | |
| quiet_end: | |
| name: Quiet Hours End | |
| description: > | |
| End of the quiet hours window (format HH:MM:SS). | |
| default: "06:00:00" | |
| selector: | |
| time: {} | |
| off_cancels: | |
| name: Turn Off Cancels | |
| description: > | |
| If the light is turned off during the ramp, the process is cancelled. | |
| default: true | |
| selector: | |
| boolean: {} | |
| # --- Pre/Post Actions --- | |
| pre_actions: | |
| name: Pre-Actions | |
| description: > | |
| Actions executed immediately before the ramp starts (e.g. increase heating). | |
| default: [] | |
| selector: | |
| action: {} | |
| post_actions: | |
| name: Post-Actions | |
| description: > | |
| Actions at the end of the ramp (e.g. start music). | |
| default: [] | |
| selector: | |
| action: {} | |
| variables: | |
| le: !input light_entity | |
| sensor_ts: !input timestamp_sensor | |
| manual_time: !input manual_time | |
| duration_min: !input sunrise_duration_min | |
| seconds: "{{ (duration_min | float(25)) * 60 }}" | |
| start_pct: !input start_brightness_pct | |
| end_pct: !input end_brightness_pct | |
| range_pct: "{{ (end_pct | float) - (start_pct | float) }}" | |
| 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 | |
| # Device limits for Kelvin (prefer native, otherwise derive from Mired) | |
| 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') }}" | |
| 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 %} | |
| # User values (Kelvin) → clamp to device limits and ensure order (start <= end) | |
| startK_in: !input start_kelvin | |
| endK_in: !input end_kelvin | |
| startK_clamped: >- | |
| {% set s = startK_in | int(2200) %} | |
| {% set s1 = [s, device_k_min] | max %} | |
| {{ [s1, device_k_max] | min }} | |
| endK_clamped: >- | |
| {% set e = endK_in | int(6500) %} | |
| {% set e1 = [e, device_k_min] | max %} | |
| {{ [e1, device_k_max] | min }} | |
| start_kelvin_final: "{{ [startK_clamped, endK_clamped] | min }}" | |
| end_kelvin_final: "{{ [startK_clamped, endK_clamped] | max }}" | |
| # Tick time (seconds), minimum 1 s | |
| tick: >- | |
| {% set t = (seconds | float) / (steps | float) %} | |
| {{ [ t, 1 ] | max | int }} | |
| # RGB start/end (+ components) | |
| rgb_start: !input start_color_rgb | |
| rgb_end: !input end_color_rgb | |
| 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) }}" | |
| # Device capabilities | |
| supported_modes: "{{ state_attr(le, 'supported_color_modes') | default([]) }}" | |
| 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 }} | |
| # Conditions | |
| 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 %} | |
| trigger: | |
| - platform: time_pattern | |
| minutes: "*" | |
| condition: [] | |
| action: | |
| # 1) Wait for valid alarm basis (timestamp present OR manual time) | |
| - wait_template: >- | |
| {{ sensor_ts == 'none' or as_timestamp(states(sensor_ts), None) != None }} | |
| # 2) Start window (0 < Alarm - now <= Duration) + conditions met | |
| - 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(states('sensor.date_time_iso')) %} | |
| {{ 0 < (alarm_ts - now_ts) <= (seconds | float) and | |
| cond_check_entity_ok and cond_workday_ok and cond_quiet_ok }} | |
| # 3) Pre-actions | |
| - choose: [] | |
| default: !input pre_actions | |
| # 4) Recheck conditions shortly before start | |
| - condition: template | |
| value_template: "{{ cond_check_entity_ok and cond_workday_ok and cond_quiet_ok }}" | |
| # 5) Initial turn on | |
| - choose: | |
| # 5a) RGB – if enabled and supported | |
| - conditions: "{{ enable_rgb and can_rgb }}" | |
| sequence: | |
| - service: light.turn_on | |
| entity_id: !input light_entity | |
| data: | |
| brightness_pct: "{{ start_pct | int }}" | |
| rgb_color: ["{{ sr }}","{{ sg }}","{{ sb }}"] | |
| transition: 1 | |
| # 5b) Kelvin – Default (if supported) | |
| - conditions: "{{ can_ct }}" | |
| sequence: | |
| - service: light.turn_on | |
| 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 | |
| entity_id: !input light_entity | |
| data: | |
| brightness_pct: "{{ start_pct | int }}" | |
| transition: 1 | |
| # 6) Running ramp | |
| - 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 }}" # 1 → 0 über die Laufzeit | |
| bri_now: >- | |
| {{ ((end_pct | float) - ((range_pct | float) * frac)) | round(0) | int }} | |
| # RGB linear: start + (1 - frac) * (end - start) | |
| 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 }}" | |
| # Kelvin linear: start → end | |
| 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 | |
| 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 | |
| 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 | |
| entity_id: !input light_entity | |
| data: | |
| brightness_pct: "{{ [bri_now, 1] | max }}" | |
| transition: "{{ [ (tick | int) - 1, 0 ] | max }}" | |
| # 7) Set final state | |
| - choose: | |
| - conditions: "{{ enable_rgb and can_rgb }}" | |
| sequence: | |
| - service: light.turn_on | |
| 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 | |
| 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 | |
| entity_id: !input light_entity | |
| data: | |
| brightness_pct: "{{ end_pct | int }}" | |
| transition: 1 | |
| # 8) Post-actions | |
| - choose: [] | |
| default: !input post_actions | |
| mode: single | |
| max_exceeded: silent |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment