Skip to content

Instantly share code, notes, and snippets.

@yougotborked
Last active October 14, 2025 19:33
Show Gist options
  • Select an option

  • Save yougotborked/24c5384ed4a2a28e1151f1f26d98b2e6 to your computer and use it in GitHub Desktop.

Select an option

Save yougotborked/24c5384ed4a2a28e1151f1f26d98b2e6 to your computer and use it in GitHub Desktop.
blueprint:
name: Kids Room Sleep/Wake (RGB Status) + Overhead Early-On Monitor (v3.1)
description: >
Shows child-friendly status with an RGB lamp **only at phase-change moments**
(Stay → Play → Get Up) and during **overhead violations**. Respects manual
"off" on the status lamp and does **not** keep turning it back on via
periodic enforcement. Audio cues are only reactions to the child's action
(overhead on) until the Get Up alarm.
Handles cross-midnight schedules; optional Quiet Play; optional auto-off of
overhead if still too early. On Get Up, optionally turns on overhead and
announces to the child. Temporarily bumps child TTS volume, then restores.
domain: automation
input:
child_name:
name: Child name
selector: { text: {} }
status_rgb_light:
name: Status RGB light (lamp/sconce)
selector:
entity: { domain: light }
overhead_entity:
name: Overhead light/switch to monitor
selector:
entity:
multiple: false
domain: [light, switch]
# Times
stay_in_bed_time:
name: Stay-in-Bed time
selector: { time: {} }
quiet_play_enabled:
name: Enable Quiet Play phase
default: true
selector: { boolean: {} }
quiet_play_time:
name: Quiet Play time (ignored if disabled)
default: "00:00:00"
selector: { time: {} }
get_up_time:
name: Get Up time
selector: { time: {} }
# Colors/brightness
stay_color:
name: Stay-in-Bed color
default: [255, 40, 0]
selector: { color_rgb: {} }
stay_brightness:
name: Stay-in-Bed brightness (1-255)
default: 25
selector: { number: { min: 1, max: 255, mode: slider } }
play_color:
name: Quiet Play color
default: [255, 180, 50]
selector: { color_rgb: {} }
play_brightness:
name: Quiet Play brightness (1-255)
default: 60
selector: { number: { min: 1, max: 255, mode: slider } }
go_color:
name: Get Up color
default: [40, 255, 40]
selector: { color_rgb: {} }
go_brightness:
name: Get Up brightness (1-255)
default: 180
selector: { number: { min: 1, max: 255, mode: slider } }
# Overhead monitoring (seconds)
reminder_delay_seconds:
name: Early reminder delay (seconds)
description: Speak to child this many seconds after overhead turns on (during Stay/Play).
default: 20
selector: { number: { min: 0, max: 300, step: 1, mode: slider } }
auto_off_after_seconds:
name: Auto-off cutoff (seconds)
description: Turn overhead off after this many seconds if still too early (0 = never).
default: 90
selector: { number: { min: 0, max: 900, step: 5, mode: slider } }
auto_turn_off_overhead:
name: Turn overhead off at cutoff if still too early
default: true
selector: { boolean: {} }
notify_parent_immediately:
name: Announce to bedside as soon as overhead turns on (during night window)
default: true
selector: { boolean: {} }
# On-Get-Up actions
turn_on_overhead_at_get_up:
name: Turn overhead on at Get Up
default: true
selector: { boolean: {} }
announce_get_up_to_child:
name: Announce Get Up to child
default: true
selector: { boolean: {} }
# Stay phase visual policy
keep_stay_light_off_by_default:
name: Keep status light OFF during Stay-in-Bed (only flash on violations)
default: true
selector: { boolean: {} }
stay_flash_brightness:
name: Stay-in-Bed flash brightness (1-255)
default: 25
selector: { number: { min: 1, max: 255, mode: slider } }
# Speakers & TTS
tts_engine:
name: TTS engine (e.g., tts.cloud_say / tts.piper)
selector:
entity: { domain: tts }
parent_speaker:
name: Parent bedside speaker
selector:
entity: { domain: media_player }
child_speaker:
name: Child room speaker
selector:
entity: { domain: media_player }
# Child TTS volume bump
child_tts_volume:
name: Child TTS volume (0.0–1.0)
description: Temporary volume used for child TTS lines.
default: 0.65
selector: { number: { min: 0.0, max: 1.0, step: 0.05, mode: slider } }
restore_child_volume:
name: Restore child speaker’s previous volume after TTS
default: true
selector: { boolean: {} }
# Messaging
speak_to_child_on_violation:
name: Speak to child (reminder + cutoff if still too early)
default: true
selector: { boolean: {} }
potty_clause:
name: Potty clause (always appended)
default: "It's always okay to use the potty."
selector: { text: {} }
msg_back_to_bed:
name: Message - Too early (before Quiet Play)
default: "It's still sleep time. Please get back in bed."
selector: { text: {} }
msg_quiet_play:
name: Message - Quiet Play window
default: "You may play quietly in your room."
selector: { text: {} }
msg_get_dressed:
name: Message - Get Up window
default: "It's okay to get up and get dressed."
selector: { text: {} }
mode: restart
max_exceeded: silent
variables:
child_name: !input child_name
status_rgb_light: !input status_rgb_light
overhead_entity: !input overhead_entity
stay_in_bed_time: !input stay_in_bed_time
quiet_play_enabled: !input quiet_play_enabled
quiet_play_time: !input quiet_play_time
get_up_time: !input get_up_time
stay_color: !input stay_color
stay_brightness: !input stay_brightness
play_color: !input play_color
play_brightness: !input play_brightness
go_color: !input go_color
go_brightness: !input go_brightness
reminder_delay_seconds: !input reminder_delay_seconds
auto_off_after_seconds: !input auto_off_after_seconds
auto_turn_off_overhead: !input auto_turn_off_overhead
turn_on_overhead_at_get_up: !input turn_on_overhead_at_get_up
announce_get_up_to_child: !input announce_get_up_to_child
keep_stay_light_off_by_default: !input keep_stay_light_off_by_default
stay_flash_brightness: !input stay_flash_brightness
tts_engine: !input tts_engine
parent_speaker: !input parent_speaker
child_speaker: !input child_speaker
child_tts_volume: !input child_tts_volume
restore_child_volume: !input restore_child_volume
notify_parent_immediately: !input notify_parent_immediately
speak_to_child_on_violation: !input speak_to_child_on_violation
potty_clause: !input potty_clause
msg_back_to_bed: !input msg_back_to_bed
msg_quiet_play: !input msg_quiet_play
msg_get_dressed: !input msg_get_dressed
# --- Restart-safe time math (timestamp-based; no timedelta concat) ---
day: 86400
now_ts: "{{ as_timestamp(now()) }}"
stay_ts: "{{ as_timestamp(today_at(stay_in_bed_time)) }}"
up_ts_raw: "{{ as_timestamp(today_at(get_up_time)) }}"
has_qp: "{{ quiet_play_enabled and (quiet_play_time != stay_in_bed_time) }}"
qp_ts_raw: "{{ as_timestamp(today_at(quiet_play_time)) if has_qp else None }}"
crosses_midnight: "{{ up_ts_raw <= stay_ts }}"
up_ts: "{{ up_ts_raw if not crosses_midnight else (up_ts_raw + day) }}"
qp_ts: >
{% if has_qp %}
{% if not crosses_midnight %}
{{ qp_ts_raw }}
{% else %}
{{ (qp_ts_raw + day) if qp_ts_raw < stay_ts else qp_ts_raw }}
{% endif %}
{% else %}{{ None }}{% endif %}
now_norm_ts: "{{ now_ts if now_ts >= stay_ts else (now_ts + day) }}"
in_window: "{{ (now_norm_ts >= stay_ts) and (now_norm_ts < up_ts) }}"
phase: >
{% if not in_window %} outside
{% else %}
{% if not has_qp %} stay
{% else %}
{% if now_norm_ts < qp_ts %} stay
{% elif now_norm_ts < up_ts %} play
{% else %} outside {% endif %}
{% endif %}
{% endif %}
triggers:
- id: at_stay
platform: time
at: !input stay_in_bed_time
- id: at_quiet_play
platform: time
at: !input quiet_play_time
- id: at_get_up
platform: time
at: !input get_up_time
- id: overhead_on_immediate
platform: state
entity_id: !input overhead_entity
from: "off"
to: "on"
- id: ha_started
platform: homeassistant
event: start
conditions: []
actions:
- choose:
# --- GET UP ALARM ---
- conditions:
- condition: trigger
id: at_get_up
sequence:
- variables:
_modes: "{{ state_attr(status_rgb_light, 'supported_color_modes') or [] }}"
_rgb_ok: "{{ 'rgb' in _modes or 'rgb_color' in _modes or 'hs' in _modes or 'xy' in _modes }}"
- choose:
- conditions: "{{ _rgb_ok }}"
sequence:
- service: light.turn_on
entity_id: !input status_rgb_light
data:
rgb_color: "{{ go_color }}"
brightness: "{{ go_brightness }}"
default:
- service: light.turn_on
entity_id: !input status_rgb_light
data:
brightness: "{{ go_brightness }}"
- if:
- condition: template
value_template: "{{ turn_on_overhead_at_get_up }}"
then:
- service: homeassistant.turn_on
entity_id: !input overhead_entity
- if:
- condition: template
value_template: "{{ announce_get_up_to_child }}"
then:
- variables:
__prev_child_vol: "{{ state_attr(child_speaker, 'volume_level') }}"
- service: media_player.volume_set
target: { entity_id: !input child_speaker }
data:
volume_level: "{{ child_tts_volume|float }}"
- service: tts.speak
target: { entity_id: !input tts_engine }
data:
media_player_entity_id: !input child_speaker
message: "{{ msg_get_dressed }} {{ potty_clause }}"
- if:
- condition: template
value_template: "{{ restore_child_volume and (__prev_child_vol is not none) }}"
then:
- delay: "00:00:02"
- service: media_player.volume_set
target: { entity_id: !input child_speaker }
data:
volume_level: "{{ __prev_child_vol }}"
# --- PHASE CUES (stay / quiet play / HA start) ---
- conditions:
- condition: or
conditions:
- condition: trigger
id: at_stay
- condition: trigger
id: at_quiet_play
- condition: trigger
id: ha_started
sequence:
- variables:
_modes: "{{ state_attr(status_rgb_light, 'supported_color_modes') or [] }}"
_rgb_ok: "{{ 'rgb' in _modes or 'rgb_color' in _modes or 'hs' in _modes or 'xy' in _modes }}"
- choose:
# Stay-in-Bed
- conditions:
- condition: template
value_template: "{{ trigger.id == 'ha_started' or trigger.id == 'at_stay' }}"
sequence:
- if:
- condition: template
value_template: "{{ keep_stay_light_off_by_default }}"
then:
- service: light.turn_off
entity_id: !input status_rgb_light
else:
- choose:
- conditions: "{{ _rgb_ok }}"
sequence:
- service: light.turn_on
entity_id: !input status_rgb_light
data:
rgb_color: "{{ stay_color }}"
brightness: "{{ stay_brightness }}"
default:
- service: light.turn_on
entity_id: !input status_rgb_light
data:
brightness: "{{ stay_brightness }}"
# Quiet Play
- conditions:
- condition: template
value_template: "{{ trigger.id == 'at_quiet_play' and has_qp }}"
sequence:
- choose:
- conditions: "{{ _rgb_ok }}"
sequence:
- service: light.turn_on
entity_id: !input status_rgb_light
data:
rgb_color: "{{ play_color }}"
brightness: "{{ play_brightness }}"
default:
- service: light.turn_on
entity_id: !input status_rgb_light
data:
brightness: "{{ play_brightness }}"
default: []
# --- OVERHEAD TURNED ON DURING NIGHT WINDOW ---
- conditions:
- condition: trigger
id: overhead_on_immediate
- condition: template
value_template: "{{ in_window }}"
sequence:
# Parent heads-up
- if:
- condition: template
value_template: "{{ notify_parent_immediately }}"
then:
- service: tts.speak
target: { entity_id: !input tts_engine }
data:
media_player_entity_id: !input parent_speaker
message: "{{ child_name }} turned on the overhead light."
# If Stay + keep-off policy, flash status light now (capability-aware)
- variables:
_modes: "{{ state_attr(status_rgb_light, 'supported_color_modes') or [] }}"
_rgb_ok: "{{ 'rgb' in _modes or 'rgb_color' in _modes or 'hs' in _modes or 'xy' in _modes }}"
- if:
- condition: template
value_template: "{{ phase == 'stay' and keep_stay_light_off_by_default }}"
then:
- choose:
- conditions: "{{ _rgb_ok }}"
sequence:
- service: light.turn_on
entity_id: !input status_rgb_light
data:
rgb_color: "{{ stay_color }}"
brightness: "{{ stay_flash_brightness }}"
default:
- service: light.turn_on
entity_id: !input status_rgb_light
data:
brightness: "{{ stay_flash_brightness }}"
# Reminder after delay (re-evaluate phase/time AFTER delay)
- delay:
seconds: "{{ reminder_delay_seconds|int }}"
- variables:
_now: "{{ as_timestamp(now()) }}"
_now_norm: "{{ _now if _now >= stay_ts else (_now + day) }}"
_in_window_now: "{{ (_now_norm >= stay_ts) and (_now_norm < up_ts) }}"
_phase_now: >
{% if not _in_window_now %} outside
{% else %}
{% if not has_qp %} stay
{% else %}
{% if _now_norm < qp_ts %} stay
{% elif _now_norm < up_ts %} play
{% else %} outside {% endif %}
{% endif %}
{% endif %}
_enforce_early_now: "{{ _phase_now in ['stay','play'] }}"
_guidance_now: >
{% if _phase_now == 'stay' %}{{ msg_back_to_bed }}
{% elif _phase_now == 'play' %}{{ msg_quiet_play }}
{% else %}{{ msg_get_dressed }}
{% endif %}
- if:
- condition: state
entity_id: !input overhead_entity
state: "on"
- condition: template
value_template: "{{ _in_window_now and _enforce_early_now }}"
then:
- if:
- condition: template
value_template: "{{ speak_to_child_on_violation }}"
then:
- variables:
__prev_child_vol2: "{{ state_attr(child_speaker, 'volume_level') }}"
- service: media_player.volume_set
target: { entity_id: !input child_speaker }
data:
volume_level: "{{ child_tts_volume|float }}"
- service: tts.speak
target: { entity_id: !input tts_engine }
data:
media_player_entity_id: !input child_speaker
message: "{{ _guidance_now }} {{ potty_clause }}"
- if:
- condition: template
value_template: "{{ restore_child_volume and (__prev_child_vol2 is not none) }}"
then:
- delay: "00:00:02"
- service: media_player.volume_set
target: { entity_id: !input child_speaker }
data:
volume_level: "{{ __prev_child_vol2 }}"
- if:
- condition: template
value_template: "{{ phase == 'stay' and keep_stay_light_off_by_default }}"
then:
- service: light.turn_off
entity_id: !input status_rgb_light
# Auto-off at cutoff (re-evaluate right before action)
- if:
- condition: template
value_template: >
{{ auto_turn_off_overhead and (auto_off_after_seconds|int > 0) }}
then:
- delay:
seconds: >
{{ [ (auto_off_after_seconds|int - reminder_delay_seconds|int) , 0 ] | max }}
- variables:
__now: "{{ as_timestamp(now()) }}"
__now_norm: "{{ __now if __now >= stay_ts else (__now + day) }}"
__in_window_now: "{{ (__now_norm >= stay_ts) and (__now_norm < up_ts) }}"
__phase_now: >
{% if not __in_window_now %} outside
{% else %}
{% if not has_qp %} stay
{% else %}
{% if __now_norm < qp_ts %} stay
{% elif __now_norm < up_ts %} play
{% else %} outside {% endif %}
{% endif %}
{% endif %}
- if:
- condition: state
entity_id: !input overhead_entity
state: "on"
- condition: template
value_template: "{{ __in_window_now and (__phase_now in ['stay','play']) }}"
then:
- service: homeassistant.turn_off
entity_id: !input overhead_entity
default: []
# Notes
# - v3.1 adds a temporary child TTS volume bump with optional restore.
# - Uses !input for targets (no templated entity_id) to avoid silent no-ops.
# - Capability-aware light.turn_on: RGB if supported, otherwise brightness-only.
# - Recomputes phase after delays to avoid stale decisions around boundaries.
# - Guards Quiet Play timing across midnight and odd equal-times cases.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment