Last active
April 5, 2026 18:43
-
-
Save alexdetsch/42867668841d5c5f37f20ead89faba62 to your computer and use it in GitHub Desktop.
Homeassistant Blueprint Shutter Automation
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: 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