Central heating or cooling has no idea a window is open; it just works harder to hold the setpoint against the outside air. This recipe gives it eyes. When any window you pick has been open past a short grace period, it turns the thermostat off, and once every window is shut again it puts the thermostat back to the exact mode it was running before, heating or cooling.
The grace period keeps it from reacting to a window cracked for a minute. Two minutes is the default, long enough to ignore a quick airing but short enough to catch a window that stays open. It runs entirely on your own sensors and thermostat.
Remembering the mode
The recipe does not guess how to resume. When it pauses, it records whatever mode the thermostat was in, and resuming restores that same mode, so a system in cooling comes back in cooling and one in heating comes back in heating. If it ever cannot read the saved mode, it falls back to the mode you set as the default.
Doors and the outside temperature
Add exterior door sensors as the optional door role and a patio or back door left open pauses the thermostat just like a window. Pick an outdoor temperature sensor as the optional temperature role and the notification that fires on a pause includes the current outdoor reading, so a window open on a mild day and one open in a cold snap read differently at a glance.
Files Selora AI's pipeline reads to install this recipe. The manifest declares roles, inputs, and integrations; the package files are Jinja-templated YAML applied to Home Assistant.
# Window Open HVAC Pause — turn the thermostat off while a window (or door) is
# open, and restore the mode it was in once everything is shut.
#
# One thermostat (single, so there is one saved-mode helper). Windows are
# required; exterior doors are optional and treated as openings too. On pause
# the recipe records the thermostat's current mode to an input_text helper,
# then sets it off; on resume it reads that helper back. A picked temperature
# sensor is echoed into the pause notification. The thermostat-not-off guard on
# pause keeps a second open window from overwriting the saved mode with "off".
slug: window-open-hvac-pause
version: 1.0.0
title: Window Open HVAC Pause
tagline: A window left open turns the thermostat off, and shutting every window puts it back exactly as it was.
description: >-
Turns the thermostat off when any window has been open past a short grace
period, and restores the exact mode it was running once every window is shut.
Optional exterior door sensors count as openings too, and an optional outdoor
temperature sensor is included in the pause notification.
author: Selora AI
released: "2026-08-28"
min_integration_version: "0.12.0"
tags: [climate, windows, energy-saving, thermostat]
roles:
# Single thermostat: the recipe keeps one saved-mode helper, so it targets
# one thermostat (run the recipe again for another zone). kind climate.
- id: thermostat
title: Thermostat
kind: climate
min_count: 1
max_count: 1
selection: required
description: >-
The thermostat to pause and resume. The recipe remembers the mode it was
in and restores it when the windows are shut.
# Window contact sensors — multi-select. binary_sensor + device_class window.
# min_count 1.
- id: windows
title: Window sensors
kind: binary_sensor
device_class: window
min_count: 1
selection: required
description: >-
The window contact sensors that pause the thermostat. Any one open past
the grace period pauses it.
# Optional exterior door sensors treated as openings. device_class door.
- id: doors
title: Door sensors
kind: binary_sensor
device_class: door
min_count: 0
selection: required
description: >-
Optional. Exterior door sensors, a patio or back door, that pause the
thermostat the same way a window does. Leave empty to watch windows only.
# Optional outdoor temperature sensor echoed into the pause notification.
- id: temperature
title: Outdoor temperature sensor
kind: sensor
device_class: temperature
min_count: 0
max_count: 1
selection: required
description: >-
Optional. An outdoor temperature sensor whose reading is included in the
notification when the thermostat pauses. Leave empty for a plain
notification.
inputs:
# Grace period, in minutes, a window may be open before the pause. 2 minutes
# ignores a quick airing but catches a window that stays open.
- id: open_minutes
type: number
label: Grace period (minutes)
description: >-
How long a window may be open before the thermostat pauses.
default: 2
min: 0
max: 120
# Fallback mode for resume when the saved mode cannot be read (for example a
# restart cleared the helper). heat_cool suits most systems that both heat
# and cool.
- id: default_mode
type: select
label: Fallback mode
description: >-
The mode to resume in if the saved mode cannot be read.
default: "heat_cool"
choices: ["heat_cool", "heat", "cool", "auto"]
package_files:
- package/helpers/pause_state.yaml.j2
- package/automations/pause.yaml.j2
{# Window Open HVAC Pause: one automation pauses the thermostat when an opening
is left open, one resumes it when everything is shut.
Pause records the thermostat's current mode to the helper, sets it off, and
raises a "paused by us" flag. The "not already off" condition means a second
window opening while paused cannot overwrite the saved mode with "off".
Resume waits for every opening to be shut and that flag to be on (so it only
resumes a pause it caused, never a thermostat the homeowner turned off
themselves), restores the saved mode, falling back to the default mode if
the helper is empty, then clears the flag. Windows and any optional doors are
treated the same. #}
{% set su = slug | replace('-', '_') %}
{% set openings = roles.windows + roles.doors %}
{% set thermo = roles.thermostat[0] %}
{% set saved = 'input_text.' ~ su ~ '_saved_mode' %}
{% set paused = 'input_boolean.' ~ su ~ '_paused' %}
automation:
- id: selora_recipe_{{ su }}_pause
alias: "Window Open HVAC Pause: pause"
description: >-
When any window or door has been open past the grace period, save the
thermostat's mode and switch it off.
mode: single
trigger:
{% for opening in openings %}
- platform: state
entity_id: {{ opening }}
to: "on"
for:
minutes: {{ inputs.open_minutes }}
{% endfor %}
condition:
- condition: template
value_template: "{% raw %}{{ not is_state('{% endraw %}{{ thermo }}{% raw %}', 'off') }}{% endraw %}"
action:
- service: input_text.set_value
target:
entity_id: {{ saved }}
data:
value: "{% raw %}{{ states('{% endraw %}{{ thermo }}{% raw %}') }}{% endraw %}"
- service: climate.set_hvac_mode
target:
entity_id: {{ thermo }}
data:
hvac_mode: "off"
- service: input_boolean.turn_on
target:
entity_id: {{ paused }}
- service: notify.notify
data:
title: "Heating and cooling paused"
{% if roles.temperature %}
message: "{% raw %}A window or door has been open for a while, so heating and cooling is paused. It is {{ states('{% endraw %}{{ roles.temperature[0] }}{% raw %}') }}{{ state_attr('{% endraw %}{{ roles.temperature[0] }}{% raw %}', 'unit_of_measurement') }} outside. It will resume when everything is shut.{% endraw %}"
{% else %}
message: "A window or door has been open for a while, so heating and cooling is paused. It will resume when everything is shut."
{% endif %}
- id: selora_recipe_{{ su }}_resume
alias: "Window Open HVAC Pause: resume"
description: >-
Once every window and door is shut, restore the thermostat to the mode it
was in before the pause.
mode: single
trigger:
{% for opening in openings %}
- platform: state
entity_id: {{ opening }}
to: "off"
{% endfor %}
# Also re-check on startup. Resume is otherwise driven purely by an
# opening closing, so a window shut while Home Assistant was down leaves
# no edge to fire on and the thermostat stays off indefinitely with the
# flag still raised. The conditions below are the same either way.
- platform: homeassistant
event: start
condition:
- condition: state
state: "off"
entity_id:
{% for opening in openings %}
- {{ opening }}
{% endfor %}
- condition: state
entity_id: {{ paused }}
state: "on"
action:
- service: climate.set_hvac_mode
target:
entity_id: {{ thermo }}
data:
hvac_mode: "{% raw %}{% set m = states('{% endraw %}{{ saved }}{% raw %}') %}{{ m if m not in ['', 'unknown', 'unavailable', 'off'] else '{% endraw %}{{ inputs.default_mode }}{% raw %}' }}{% endraw %}"
- service: input_boolean.turn_off
target:
entity_id: {{ paused }}
{# Window Open HVAC Pause helpers: a text helper that remembers the
thermostat's mode while it is paused, and a boolean flag marking that the
recipe is the one that paused it. The pause automation writes the mode and
raises the flag before switching the thermostat off; the resume automation
reads the mode back and only acts while the flag is on, so it never turns on
a thermostat the homeowner switched off themselves. Both survive a restart,
and the resume automation re-checks them on startup, so a pause still lifts
even when the window was shut while Home Assistant was down. #}
input_text:
{{ slug | replace('-', '_') }}_saved_mode:
name: "Window HVAC Pause: saved thermostat mode"
max: 20
icon: mdi:thermostat
input_boolean:
{{ slug | replace('-', '_') }}_paused:
name: "Window HVAC Pause: paused by recipe"
icon: mdi:pause-octagon