Skip to content

Instantly share code, notes, and snippets.

@alexdetsch
Last active April 5, 2026 18:43
Show Gist options
  • Select an option

  • Save alexdetsch/42867668841d5c5f37f20ead89faba62 to your computer and use it in GitHub Desktop.

Select an option

Save alexdetsch/42867668841d5c5f37f20ead89faba62 to your computer and use it in GitHub Desktop.
Homeassistant Blueprint Shutter Automation
blueprint:
name: Shutter Control with Window Sensor
description: >
v1.4 | Controls a shutter on a time schedule and reacts to window open/close
events. Features: close/open/20%-dim at configurable times (or via an entity
for dynamic times like sunset), window reaction, ventilation position when the
window is open at closing time, smart re-close logic in the morning, and
guest/away modes.
Guest mode: suppresses ALL time-based actions (open, dim, close) but keeps
window-triggered open/close working.
Away mode: suppresses automatic opening only; closing continues normally.
source_url: https://gist.github.com/alexdetsch/42867668841d5c5f37f20ead89faba62
domain: automation
input:
cover_entity:
name: Shutter
selector:
entity:
domain: cover
window_sensor:
name: Window Sensor
description: Binary sensor -- "on" = window open
selector:
entity:
domain: binary_sensor
device_class: window
# -- Closing time ----------------------------------------------------------
time_close:
name: Closing Time (fixed)
description: >
Fixed time at which the shutter closes. Ignored when "Closing Time
Entity" is set.
default: "22:00:00"
selector:
time:
time_close_entity:
name: Closing Time Entity (optional)
description: >
Entity whose state determines the closing time -- overrides the fixed
closing time. Suitable for input_datetime helpers, sensors, or template
sensors that provide e.g. the local sunset time. The state must be in
"HH:MM" or "HH:MM:SS" format.
Tip: sun.sun does not expose the time directly, but a template sensor
can convert state_attr('sun.sun', 'next_setting') to local time
(see the example at the bottom of this file).
default:
selector:
entity: {}
# -- Opening time ----------------------------------------------------------
time_open:
name: Opening Time
description: The shutter opens fully at this time.
default: "07:30:00"
selector:
time:
# -- Dim time --------------------------------------------------------------
time_dim:
name: Dim Time (20%)
description: >
At this time the shutter moves to 20% so you can see whether it is
already light outside without illuminating the room.
default: "06:00:00"
selector:
time:
# -- No re-close -----------------------------------------------------------
time_no_reclose:
name: No Re-close After
description: >
If the window is closed after the closing time but it is already LATER
than this time, the shutter will NOT be closed again. Useful for early
risers who open the window briefly -- closing it should not pull the
shutter back down. Typically set just before the opening time (e.g. 05:00).
default: "05:00:00"
selector:
time:
# -- Guest mode ------------------------------------------------------------
guest_mode:
name: Guest Mode Switch
description: >
When this switch is on, ALL time-based shutter actions are suppressed
(open, dim, AND close). The shutter still reacts to window open/close
events, so ventilation and re-close logic continue to work.
selector:
entity:
domain: input_boolean
# -- Away mode -------------------------------------------------------------
away_mode:
name: Away Mode Switch
description: >
When this switch is on, automatic opening is suppressed (including
window-triggered opening). Intended for holidays or longer absences.
Automatic closing at night continues to work normally.
selector:
entity:
domain: input_boolean
variables:
# Entity for dynamic closing time (empty = not configured)
time_close_entity: !input time_close_entity
time_close_fixed: !input time_close
time_no_reclose: !input time_no_reclose
guest_mode_entity: !input guest_mode
away_mode_entity: !input away_mode
# True when guest mode is active — blocks ALL timed actions
guest_on: >
{{ states(guest_mode_entity) == 'on' }}
# True when away mode is active — blocks opening only
away_on: >
{{ states(away_mode_entity) == 'on' }}
# True when automatic opening should be suppressed (any timed or
# window-triggered opening). Used for open, dim, and window-open triggers.
block_open: >
{{ guest_on or away_on }}
# Returns the effective closing time as an "HH:MM" string.
# When an entity is configured its state is used; otherwise the fixed time.
# Handles both plain "HH:MM"/"HH:MM:SS" strings and full ISO timestamps
# (e.g. "2026-03-26T18:32:00+00:00") as returned by sun.sun sensors.
effective_close_time: >
{% if time_close_entity is not none and time_close_entity != '' %}
{% set raw = states(time_close_entity) | trim %}
{% if 'T' in raw %}
{{ as_local(as_datetime(raw)).strftime('%H:%M') }}
{% else %}
{{ raw[:5] }}
{% endif %}
{% else %}
{{ time_close_fixed[:5] }}
{% endif %}
trigger:
# Closing time: we register both a fixed-time trigger and an entity trigger.
# The action below checks which one is relevant given the current config.
- platform: time
at: !input time_close
id: trigger_close_fixed
- platform: time
at: !input time_close_entity
id: trigger_close_entity
- platform: time
at: !input time_open
id: trigger_open
- platform: time
at: !input time_dim
id: trigger_dim
- platform: state
entity_id: !input window_sensor
to: "on"
id: trigger_window_open
- platform: state
entity_id: !input window_sensor
to: "off"
id: trigger_window_close
action:
- choose:
# -- Closing time (fixed, only when no entity is configured) -------------
- conditions:
- condition: trigger
id: trigger_close_fixed
- condition: template
value_template: >
{{ time_close_entity is none or time_close_entity == '' }}
- condition: template
value_template: "{{ not guest_on }}"
sequence: &close_sequence
- choose:
- conditions:
- condition: state
entity_id: !input window_sensor
state: "on"
sequence:
# Window open -> ventilation position: position 30 = 70% closed
- service: cover.set_cover_position
target:
entity_id: !input cover_entity
data:
position: 30
default:
# Window closed -> close fully
- service: cover.close_cover
target:
entity_id: !input cover_entity
# -- Closing time (via entity) -------------------------------------------
- conditions:
- condition: trigger
id: trigger_close_entity
- condition: template
value_template: "{{ not guest_on }}"
sequence: *close_sequence
# -- Opening time --------------------------------------------------------
- conditions:
- condition: trigger
id: trigger_open
- condition: template
value_template: "{{ not block_open }}"
sequence:
- service: cover.open_cover
target:
entity_id: !input cover_entity
# -- Dim time -> 20% -----------------------------------------------------
- conditions:
- condition: trigger
id: trigger_dim
- condition: template
value_template: "{{ not block_open }}"
sequence:
- service: cover.set_cover_position
target:
entity_id: !input cover_entity
data:
position: 20
# -- Window opens -> raise shutter ---------------------------------------
- conditions:
- condition: trigger
id: trigger_window_open
# In guest mode window-triggered opening still works.
# Only away mode suppresses it (nobody home -> don't open).
- condition: template
value_template: "{{ not away_on }}"
sequence:
- service: cover.open_cover
target:
entity_id: !input cover_entity
# -- Window closes -> re-close shutter if within the night window --------
- conditions:
- condition: trigger
id: trigger_window_close
# Re-close works in ALL modes (including guest & away).
# Only the time window check applies.
- condition: template
value_template: >
{% set close_t = effective_close_time | trim %}
{% set nore_t = time_no_reclose[:5] | trim %}
{% set now_m = now().hour * 60 + now().minute %}
{% set close_m = close_t.split(':')[0] | int * 60 + close_t.split(':')[1] | int %}
{% set nore_m = nore_t.split(':')[0] | int * 60 + nore_t.split(':')[1] | int %}
{% if close_m > nore_m %}
{# Range spans midnight (e.g. 20:16-05:30): use OR #}
{{ now_m >= close_m or now_m < nore_m }}
{% else %}
{# Same-day range (e.g. 10:00-15:00): use AND #}
{{ now_m >= close_m and now_m < nore_m }}
{% endif %}
sequence:
- service: cover.close_cover
target:
entity_id: !input cover_entity
mode: parallel
max: 10
# -----------------------------------------------------------------------------
# TIP: Template sensor for sunset as closing time
#
# sun.sun exposes next_setting as a UTC timestamp, not a usable time string.
# Add this template sensor to convert it to a local "HH:MM" value:
#
# template:
# - sensor:
# - name: "Sunset Time"
# unique_id: sunset_time_hhmm
# state: >
# {{ as_local(as_datetime(state_attr('sun.sun', 'next_setting')))
# .strftime('%H:%M') }}
# icon: mdi:weather-sunset
#
# Then select this sensor as the "Closing Time Entity" in the blueprint.
# -----------------------------------------------------------------------------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment