Last active
January 17, 2026 17:36
-
-
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 (Clean UI & Stable) | |
| description: > | |
| Modern wake-up light with sunrise effect. | |
| VERSION 2.2: Removed all default entity strings to prevent 'Unknown Entity' UI warnings. | |
| Works flawlessly with empty inputs. | |
| domain: automation | |
| input: | |
| light_entity: | |
| name: Target Light | |
| description: The light or light group to use. | |
| selector: | |
| entity: | |
| domain: light | |
| # --- Alarm Source --- | |
| timestamp_sensor: | |
| name: Timestamp Entity (optional) | |
| description: > | |
| Sensor with device_class=timestamp (e.g., smartphone alarm). | |
| Leave empty to use manual time. | |
| default: [] | |
| selector: | |
| entity: | |
| device_class: timestamp | |
| manual_time: | |
| name: Manual Wake Time | |
| description: Daily wake-up time used if no timestamp sensor is set. | |
| default: "07:00:00" | |
| selector: | |
| time: {} | |
| # --- Duration & Brightness --- | |
| sunrise_duration_min: | |
| name: Sunrise Duration (Minutes) | |
| default: 25 | |
| selector: | |
| number: | |
| min: 5 | |
| max: 120 | |
| step: 5 | |
| unit_of_measurement: min | |
| mode: slider | |
| start_brightness_pct: | |
| name: Start Brightness (%) | |
| default: 3 | |
| selector: | |
| number: | |
| min: 1 | |
| max: 100 | |
| step: 1 | |
| mode: slider | |
| end_brightness_pct: | |
| name: End Brightness (%) | |
| default: 100 | |
| selector: | |
| number: | |
| min: 5 | |
| max: 100 | |
| step: 1 | |
| mode: slider | |
| # --- Kelvin Ramp --- | |
| 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 (cool) | |
| default: 6500 | |
| selector: | |
| number: | |
| min: 1500 | |
| max: 9000 | |
| step: 50 | |
| unit_of_measurement: K | |
| mode: slider | |
| # --- RGB Ramp --- | |
| enable_color_ramp: | |
| name: Use Color Ramp (RGB) Instead of Kelvin | |
| default: false | |
| selector: | |
| boolean: {} | |
| start_color_rgb: | |
| name: Start Color (RGB) | |
| default: [255, 120, 20] | |
| selector: | |
| color_rgb: {} | |
| end_color_rgb: | |
| name: End Color (RGB) | |
| default: [255, 255, 255] | |
| selector: | |
| color_rgb: {} | |
| ramp_steps: | |
| name: Ramp Steps (Smoothness) | |
| description: Number of updates during the ramp. | |
| default: 60 | |
| selector: | |
| number: | |
| min: 10 | |
| max: 240 | |
| step: 10 | |
| # --- Conditions --- | |
| check_entity: | |
| name: Check Entity (optional) | |
| description: Must be 'on' or 'home' to run. | |
| default: [] | |
| selector: | |
| entity: {} | |
| require_workday: | |
| name: Only on Workdays | |
| default: false | |
| selector: | |
| boolean: {} | |
| workday_sensor: | |
| name: Workday Sensor | |
| description: Select your binary sensor for workdays (only needed if 'Only on Workdays' is enabled). | |
| default: [] | |
| selector: | |
| entity: | |
| domain: binary_sensor | |
| respect_quiet_hours: | |
| name: Respect Quiet Hours | |
| default: false | |
| selector: | |
| boolean: {} | |
| quiet_start: | |
| name: Quiet Hours Start | |
| default: "22:00:00" | |
| selector: | |
| time: {} | |
| quiet_end: | |
| name: Quiet Hours End | |
| default: "06:00:00" | |
| selector: | |
| time: {} | |
| off_cancels: | |
| name: Turning Off Cancels Ramp | |
| description: If enabled, turning off the light manually during the ramp will stop the automation. | |
| default: true | |
| selector: | |
| boolean: {} | |
| # --- Actions --- | |
| pre_actions: | |
| name: Pre-Actions | |
| default: [] | |
| selector: | |
| action: {} | |
| post_actions: | |
| name: Post-Actions | |
| 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 | |
| # --- Time Calculation Logic --- | |
| manual_target_ts: "{{ today_at(manual_time).timestamp() }}" | |
| # Handle empty/null input correctly | |
| sensor_target_ts: >- | |
| {% if sensor_ts is defined and sensor_ts and states(sensor_ts) not in ['unknown', 'unavailable', 'none', ''] %} | |
| {{ as_timestamp(states(sensor_ts), 0) }} | |
| {% else %} | |
| 0 | |
| {% endif %} | |
| alarm_ts: >- | |
| {% if sensor_target_ts > 0 %} | |
| {{ sensor_target_ts }} | |
| {% else %} | |
| {{ manual_target_ts }} | |
| {% endif %} | |
| start_ts: "{{ alarm_ts - (seconds | float) }}" | |
| now_ts: "{{ as_timestamp(now()) }}" | |
| # --- Device Capabilities --- | |
| 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 %} | |
| startK_in: !input start_kelvin | |
| endK_in: !input end_kelvin | |
| startK_clamped: >- | |
| {% set s = startK_in | int(2200) %} | |
| {{ [[s, device_k_min]|max, device_k_max]|min }} | |
| endK_clamped: >- | |
| {% set e = endK_in | int(6500) %} | |
| {{ [[e, device_k_min]|max, device_k_max]|min }} | |
| start_kelvin_final: "{{ [startK_clamped, endK_clamped] | min }}" | |
| end_kelvin_final: "{{ [startK_clamped, endK_clamped] | max }}" | |
| tick: >- | |
| {% set t = (seconds | float) / (steps | float) %} | |
| {{ [ t, 1 ] | max | int }} | |
| rgb_start: !input start_color_rgb | |
| rgb_end: !input end_color_rgb | |
| sr: "{{ (rgb_start[0] | int) }}" | |
| sg: "{{ (rgb_start[1] | int) }}" | |
| sb: "{{ (rgb_start[2] | int) }}" | |
| er: "{{ (rgb_end[0] | int) }}" | |
| eg: "{{ (rgb_end[1] | int) }}" | |
| eb: "{{ (rgb_end[2] | int) }}" | |
| 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 }} | |
| # Condition Logic Updated for empty input | |
| cond_check_entity_ok: >- | |
| {{ check_entity == none or check_entity == '' or check_entity == [] or states(check_entity) in ['on','home'] }} | |
| # Workday Logic Updated for empty input | |
| # If require_workday is False, we are good. | |
| # If require_workday is True, we MUST have a sensor that is 'on'. | |
| cond_workday_ok: >- | |
| {% if not require_workday %} | |
| true | |
| {% else %} | |
| {{ workday_sensor is defined and workday_sensor and states(workday_sensor) == 'on' }} | |
| {% endif %} | |
| 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. Start Check | |
| - condition: template | |
| value_template: >- | |
| {{ start_ts <= now_ts < (start_ts + 60) }} | |
| # 2. Condition Check | |
| - condition: template | |
| value_template: "{{ cond_check_entity_ok and cond_workday_ok and cond_quiet_ok }}" | |
| # 3. Pre-Actions | |
| - choose: [] | |
| default: !input pre_actions | |
| # 4. Turn On | |
| - choose: | |
| - 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 | |
| - 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 | |
| # 5. Ramp Loop | |
| - repeat: | |
| while: | |
| - condition: template | |
| value_template: "{{ not (off_cancels and is_state(le, 'off')) }}" | |
| - condition: template | |
| value_template: "{{ as_timestamp(now()) < alarm_ts }}" | |
| sequence: | |
| - delay: | |
| seconds: "{{ tick | int }}" | |
| - variables: | |
| remain: "{{ (alarm_ts - as_timestamp(now())) | float }}" | |
| frac: "{{ (remain / (seconds | float)) | float }}" | |
| frac_clamped: "{{ [0, [frac, 1]|min]|max }}" | |
| bri_now: >- | |
| {{ ((end_pct | float) - ((range_pct | float) * frac_clamped)) | round(0) | int }} | |
| r_now: "{{ (sr + (er - sr) * (1 - frac_clamped)) | round(0) | int }}" | |
| g_now: "{{ (sg + (eg - sg) * (1 - frac_clamped)) | round(0) | int }}" | |
| b_now: "{{ (sb + (eb - sb) * (1 - frac_clamped)) | round(0) | int }}" | |
| kelv_now: "{{ ((end_kelvin_final | float) - ((end_kelvin_final | float - start_kelvin_final | float) * frac_clamped)) | 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) - 0.5, 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) - 0.5, 0 ] | max }}" | |
| default: | |
| - service: light.turn_on | |
| entity_id: !input light_entity | |
| data: | |
| brightness_pct: "{{ [bri_now, 1] | max }}" | |
| transition: "{{ [ (tick | int) - 0.5, 0 ] | max }}" | |
| # 6. Safety Stop (if turned off) | |
| - condition: template | |
| value_template: "{{ not (off_cancels and is_state(le, 'off')) }}" | |
| # 7. 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