Skip to content

Instantly share code, notes, and snippets.

@BoBBer446
Last active January 17, 2026 17:36
Show Gist options
  • Select an option

  • Save BoBBer446/e94b479d06713f25ca5d6003dd48c132 to your computer and use it in GitHub Desktop.

Select an option

Save BoBBer446/e94b479d06713f25ca5d6003dd48c132 to your computer and use it in GitHub Desktop.
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