Created
October 27, 2025 17:28
-
-
Save BoBBer446/e94b479d06713f25ca5d6003dd48c132 to your computer and use it in GitHub Desktop.
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 wake-up light with sunrise effect. Default: warm white (≈2200 K) → daylight white (≈6500 K), | |
| automatically adjusted to device limits. Optionally, use an RGB color ramp 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: > | |
| The light or light group that should be used for the wake-up sequence. | |
| selector: | |
| entity: | |
| domain: light | |
| # --- Alarm Source --- | |
| timestamp_sensor: | |
| name: Timestamp Entity (optional) | |
| description: > | |
| Sensor with device_class=timestamp (e.g., smartphone alarm). If 'none', the manual time will be used. | |
| default: none | |
| selector: | |
| entity: | |
| device_class: timestamp | |
| manual_time: | |
| name: Manual Wake Time | |
| description: > | |
| Daily wake-up 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 brightness and color ramp. | |
| 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 lamps 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 clamped to device limits. | |
| default: 2200 | |
| selector: | |
| number: | |
| min: 1500 | |
| max: 9000 | |
| step: 50 | |
| unit_of_measurement: K | |
| mode: slider | |
| end_kelvin: | |
| name: End Kelvin (cool/daylight) | |
| description: > | |
| Color temperature at the end of the ramp (e.g., 6500 K = daylight white). Automatically clamped to device limits. | |
| default: 6500 | |
| selector: | |
| number: | |
| min: 1500 | |
| max: 9000 | |
| step: 50 | |
| unit_of_measurement: K | |
| mode: slider | |
| # --- Alternative: RGB Color Ramp --- | |
| enable_color_ramp: | |
| name: Use Color Ramp (RGB) Instead of Kelvin | |
| description: > | |
| Enable this to use an RGB color ramp from start to end color instead of Kelvin temperature. | |
| default: false | |
| selector: | |
| boolean: {} | |
| start_color_rgb: | |
| name: Start Color (RGB) | |
| description: > | |
| Starting color for the RGB ramp (only relevant if color ramp is enabled). | |
| default: | |
| r: 255 | |
| g: 120 | |
| b: 20 | |
| selector: | |
| color_rgb: {} | |
| end_color_rgb: | |
| name: End Color (RGB) | |
| description: > | |
| Ending color for the RGB ramp (only relevant if color ramp is enabled). | |
| default: | |
| r: 255 | |
| g: 255 | |
| b: 255 | |
| selector: | |
| color_rgb: {} | |
| ramp_steps: | |
| name: Ramp Steps (Smoothness) | |
| description: > | |
| Number of intermediate steps for brightness and 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 required. | |
| default: none | |
| selector: | |
| entity: {} | |
| require_workday: | |
| name: Only on Workdays | |
| description: > | |
| Execute only when the specified workday sensor is 'on'. | |
| default: false | |
| selector: | |
| boolean: {} | |
| workday_sensor: | |
| name: Workday Sensor | |
| description: > | |
| Workday sensor to be checked when "Only on Workdays" is enabled. | |
| default: binary_sensor.workday | |
| selector: | |
| entity: | |
| domain: binary_sensor | |
| respect_quiet_hours: | |
| name: Respect Quiet Hours | |
| description: > | |
| Prevents the wake-up sequence 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: Turning Off Cancels Ramp | |
| description: > | |
| If the light is turned off during the ramp, the wake-up process will be cancelled. | |
| default: true | |
| selector: | |
| boolean: {} | |
| # --- Pre/Post Actions --- | |
| pre_actions: | |
| name: Pre-Actions | |
| description: > | |
| Actions to execute immediately before starting the ramp (e.g., increasing room temperature). | |
| default: [] | |
| selector: | |
| action: {} | |
| post_actions: | |
| name: Post-Actions | |
| description: > | |
| Actions to execute after the ramp completes (e.g., start music). | |
| default: [] | |
| selector: | |
| action: {} | |
| # --- Variables, Triggers, Actions --- | |
| 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 color temperature limits (prefer native Kelvin, else derive from mireds) | |
| 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 %} | |
| # Clamp user Kelvin values to device range and ensure 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), at least 1 s | |
| tick: >- | |
| {% set t = (seconds | float) / (steps | float) %} | |
| {{ [ t, 1 ] | max | int }} | |
| # RGB start/end | |
| 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 base (timestamp present OR manual time) | |
| - wait_template: >- | |
| {{ sensor_ts == 'none' or as_timestamp(states(sensor_ts), None) != None }} | |
| # 2) Wait until within wake-up window (0 < alarm - now <= duration) + all 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) Re-check conditions right before start | |
| - condition: template | |
| value_template: "{{ cond_check_entity_ok and cond_workday_ok and cond_quiet_ok }}" | |
| # 5) Initial light turn-on | |
| - choose: | |
| # 5a) RGB mode – 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 mode – 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) Main ramp loop | |
| - 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 | |
| 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) Ensure 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