Home AssistantESPHome

Humidity window-vent guide

Indoor versus outdoor absolute humidity decides when opening a window will actually dry a room — notify to open, keep airing, or close, using an ESPHome climate node plus a weather entity.

Difficulty · IntermediateSetup · 45–90 minutesHA · current stable (blueprint)ESPHome · current stable
Get the pack · from $19Have us set it up

Some hardware links on this page are affiliate links. As an Amazon Associate I earn from qualifying purchases. We may earn a commission if you buy through them, at no extra cost to you. See the affiliate disclosure.

Relative humidity on a dashboard is a poor venting coach. Outdoor air at 80% RH can still be drier than a warm bathroom; 55% outside on a cold morning can add moisture if you throw the sash open. This pack compares absolute humidity (grams of water per cubic metre) indoors and outdoors, then sends one clear push: open, keep airing, or close.

Entity ids are blueprint inputs. Do not publish a live room inventory.

Pairs with the ESP32-C6 climate sensor for the indoor numbers, and with window-open climate pause so a heat pump does not fight the sash you just opened.

What problem it solves

You want a “should I open this window” nudge that uses indoor versus outdoor moisture, not a fixed indoor RH threshold, and you want the opposite nudge when leaving the window open has stopped helping.

How it works

  1. Indoor climate — an ESPHome node (ESP32-C6 + AHT20 or DHT22) publishes temperature and relative humidity. A second outdoor node is better than a city weather station; a HA weather entity is the no-extra-hardware fallback.
  2. Absolute humidity — two template sensors convert each T + RH pair with the Magnus formula. Relative humidity is not compared. The useful number is indoor AH minus outdoor AH (positive = indoor air holds more water; opening should dry the room).
  3. Recommendation — a template select / text helper: open when the delta is above the open margin and indoor RH is above a comfort floor (so you do not nag a dry room); keep while a contact is already open and the delta is still worth it; close when the delta drops below the close margin, outdoor air is wetter, or an outdoor temperature floor is breached (pack default: do not recommend opening below about 5 °C).
  4. Notify on change — the blueprint fires only when the recommendation changes, not every 30-second sensor tick. Title and message are inputs. Optional: skip open if nobody is home (person or zone input).
  5. Contact-aware close — if you wired a reed, close is suppressed while the contact is already shut. open is suppressed while it is already open. Without a contact, you get the raw recommendation.

This is a decision helper, not a mould inspection and not a mechanical-ventilation controller.

Hardware notes

  • Indoor node. Same Super Mini + AHT20 pattern as the climate-sensor pack (I²C on GPIO0 / GPIO1). DHT22 on a single GPIO works if that is what you have; AHT20 is the more stable reading.
  • Outdoor. Prefer a second ESPHome climate in a shaded, ventilated enclosure. A weather entity (Met.no, OpenWeather, AccuWeather) is fine for a first pass; city humidity lags a garden and has no room-by-room temperature.
  • Contact. Any binary sensor that is on when the opening is open. Optional. The pack still works as a “should I?” notify without it.
  • Placement. Keep the indoor sensor out of the shower steam plume and off a sunny sill. You want room air, not the pane.

Wi-Fi credentials stay in secrets.yaml. Never paste SSIDs, PSKs, or API keys into a published YAML.

Snippet

Indoor node (substitutions, not a live house). Trimmed from the pack YAML:

substitutions:
  friendly_name: Bathroom climate
  sda_pin: GPIO0
  scl_pin: GPIO1

sensor:
  - platform: aht10
    variant: AHT20
    i2c_id: bus_a
    temperature:
      name: Temperature
      id: indoor_t
    humidity:
      name: Humidity
      id: indoor_rh

Absolute humidity templates (blueprint inputs replace entity ids):

template:
  - sensor:
      - name: Indoor absolute humidity
        unit_of_measurement: g/m³
        state: >
          {% set t = states(indoor_temp) | float(none) %}
          {% set rh = states(indoor_humid) | float(none) %}
          {% if t is none or rh is none %}
            unknown
          {% else %}
            {% set es = 6.112 * e ** ((17.67 * t) / (t + 243.5)) %}
            {{ ((es * rh * 2.1674) / (273.15 + t)) | round(2) }}
          {% endif %}
      - name: Outdoor absolute humidity
        unit_of_measurement: g/m³
        state: >
          {% set t = state_attr(weather_entity, 'temperature') | float(none) %}
          {% set rh = state_attr(weather_entity, 'humidity') | float(none) %}
          {% if t is none or rh is none %}
            unknown
          {% else %}
            {% set es = 6.112 * e ** ((17.67 * t) / (t + 243.5)) %}
            {{ ((es * rh * 2.1674) / (273.15 + t)) | round(2) }}
          {% endif %}

Recommendation notify (change only):

trigger:
  - platform: state
    entity_id: !input vent_recommendation
action:
  - condition: template
    value_template: "{{ trigger.to_state.state != trigger.from_state.state }}"
  - service: !input notify_service
    data:
      title: "Vent guide"
      message: >
        {{ trigger.to_state.state }} · indoor
        {{ states(indoor_ah) }} g/m³ vs outdoor
        {{ states(outdoor_ah) }} g/m³

Setup outline

  1. Flash the indoor ESPHome YAML (or reuse an existing climate node). Confirm T and RH in Home Assistant.
  2. Pick the outdoor source: a second node, or a weather entity that actually has a humidity attribute (Developer Tools → the weather entity).
  3. Add the two AH templates and the recommendation template. Create the optional contact input.
  4. Import the blueprint. Fill indoor T/RH, outdoor weather or outdoor sensors, notify service, open/close margins, outdoor temperature floor.
  5. Test: bathroom after a shower with drier outdoor air → open; open the sash and wait as indoor AH falls → keep then close; cold wet outdoor air → no open.

Troubleshooting

  • Always says open. You compared relative humidity, or the outdoor entity has no humidity attribute (templates stay unknown and the default branch is wrong). Check AH numbers first; they should be a few g/m³, not 40–90.
  • Always says close. Open margin is too high, or the outdoor weather station is a city average that is drier than your garden. A local outdoor node fixes most of this.
  • Spam. You notified on every sensor write instead of recommendation changes. Use the pack’s state-change trigger.
  • Outdoor AH is unknown. That weather integration does not publish humidity. Switch provider, or add the outdoor ESPHome node.
  • DHT22 jumps ±8%. That sensor is noisy; raise the open/close hysteresis (pack default a few tenths of a g/m³) or swap to AHT20.

Credits

Based on the community project FreshAirIQ – Intelligent Ventilation for Home Assistant and the Magnus absolute-humidity relationship used in that work. This write-up is an original overview of a two-sensor AH-delta plus notify pack, not a paste of the integration.

Changelog

v1.0 · Oct 18, 2026: First publish.