Dew Point Automation with Aqara T/H Sensors + Home Assistant — Know When (Not) to Ventilate

Hi everyone,

I want to share one of my favorite automations built on Aqara temperature/humidity sensors: dew point based ventilation advice.

The problem: In summer, many people open basement or bathroom windows on hot days to “dry out” the room. But warm outdoor air carries a lot of moisture — when it hits cool walls, it condenses. You’re actually making the room wetter, which leads to mold.

The physics: Relative humidity alone is misleading, because it depends on temperature. The dew point tells you the absolute moisture content of the air. Simple rule: only ventilate when the outdoor dew point is lower than the indoor dew point. Then, and only then, airing out actually removes moisture.

My setup:

  • Aqara temperature/humidity sensors in the rooms I monitor (basement, bathroom) + one outdoor reference on the terrace
    • Aqara Hub M3, everything integrated into Home Assistant
    • Home Assistant calculates the dew point from temperature + relative humidity for each sensor (template sensor with the Magnus formula)
    • An automation compares indoor vs. outdoor dew point and notifies me when it’s a good time to ventilate — and warns me when opening the window would make things worse
    • On my dashboard I use color coding: green = ventilate now, red = keep windows closed
      The Aqara T/H sensors are perfect for this — cheap enough to put one in every critical room, and accurate enough for dew point math.

Happy to share the template sensor code if anyone is interested. How do you manage humidity in your homes?

13 Likes

StarterTips — submitting this as my entry for the Smart Home Tips activity. @AqaraBot / forum team: I can’t edit tags yet with my trust level — could you kindly add the StarterTips tag to this topic? Thanks!

3 Likes

Excellent point! Good to know that dew point is more accurate than humidity here.

1 Like

As promised in the original post — here is the template sensor code for the dew point calculation. It goes into configuration.yaml under template:sensor:. One block per indoor room, plus one outdoor reference (mine sits on the terrace):

- name: "Dew Point Basement"
  unique_id: dew_point_basement
  device_class: temperature
  state_class: measurement
  unit_of_measurement: "°C"
  icon: mdi:water-thermometer
  availability: >
    {{ states('sensor.basement_temperature') not in ['unknown', 'unavailable', 'none']
       and states('sensor.basement_humidity') not in ['unknown', 'unavailable', 'none'] }}
  state: >
    {% set t = states('sensor.basement_temperature') | float(0) %}
    {% set rh = states('sensor.basement_humidity') | float(50) %}
    {% set a = log(rh / 100) + (17.62 * t) / (243.12 + t) %}
    {{ (243.12 * a / (17.62 - a)) | round(1) }}

- name: "Dew Point Outdoor"
  unique_id: dew_point_outdoor
  device_class: temperature
  state_class: measurement
  unit_of_measurement: "°C"
  icon: mdi:water-thermometer-outline
  availability: >
    {{ states('sensor.terrace_temperature') not in ['unknown', 'unavailable', 'none']
       and states('sensor.terrace_humidity') not in ['unknown', 'unavailable', 'none'] }}
  state: >
    {% set t = states('sensor.terrace_temperature') | float(0) %}
    {% set rh = states('sensor.terrace_humidity') | float(50) %}
    {% set a = log(rh / 100) + (17.62 * t) / (243.12 + t) %}
    {{ (243.12 * a / (17.62 - a)) | round(1) }}

How it works: the two {% set %} lines read temperature and relative humidity from your Aqara sensors, then the Magnus formula converts them into the dew point. Swap the entity IDs for your own sensor names and duplicate the block for every room you want to monitor.

Three details that proved themselves in daily use:

  1. The availability block matters. Without it, a sensor going offline makes the template happily calculate with 0 °C — and your dashboard shows a phantom dew point.
  2. device_class + state_class make the dew points behave like real temperature sensors: proper long-term statistics, history graphs, and unit handling for free.
  3. For the actual recommendation, compare indoor vs. outdoor: ventilating helps when the outdoor dew point is at least ~2–3 °C below the indoor one — a small margin avoids constant flip-flopping around zero difference.

If anyone builds this and runs into issues, post your template here and I’ll take a look.

2 Likes

In my Home Assistant setup I use Aqara temperature and humidity sensors as room references, and I also compare them with separate room sensors. Before relying on a narrow dew point margin, I would place the sensors next to each other for a while and check for a consistent offset. That helps separate a real moisture difference from placement effects such as an exterior wall, sunlight, or airflow.

I would also treat the recommendation as a state instead of reacting to every single update. Require the outdoor dew point to remain below the indoor value by the chosen margin for a few minutes, then use a slightly different threshold before switching back. That reduces repeated notifications from small sensor changes. A door or window contact can suppress the prompt once ventilation is already in progress.

2 Likes

Excellent points, both of them. The side-by-side calibration is something I’d recommend to anyone copying this setup - my Aqara T/H sensors happened to track within a few tenths of a degree, but you only know that after checking, and with a 2–3 °C margin a 1 °C offset would absolutely matter.

Update -implemented, here’s the full recipe. Since @technotron007’s suggestions made a lot of sense, the setup now works exactly that way: the recommendation is a state with hysteresis, and the notification only fires on state changes. For anyone who wants to copy it:

Step 1 -Template binary sensor (goes into configuration.yaml under template:, one block per room; requires the dew point sensors from my earlier post):

- binary_sensor:
    # Thresholds: ON at >= 3.0 °C gap (indoor minus outdoor), OFF below 1.5 °C
    - name: "Ventilation useful Basement"
      unique_id: ventilation_useful_basement
      icon: mdi:window-open-variant
      delay_on: "00:10:00"    # gap must hold for 10 min before turning ON
      delay_off: "00:05:00"   # gap must be gone for 5 min before turning OFF
      availability: >
        {{ states('sensor.dew_point_basement') not in ['unknown', 'unavailable', 'none']
           and states('sensor.dew_point_outdoor') not in ['unknown', 'unavailable', 'none'] }}
      state: >
        {% set diff = states('sensor.dew_point_basement') | float(0) - states('sensor.dew_point_outdoor') | float(0) %}
        {% if diff >= 3.0 %}
          true
        {% elif diff < 1.5 %}
          false
        {% else %}
          {{ is_state('binary_sensor.ventilation_useful_basement', 'on') }}
        {% endif %}
      attributes:
        gap: >
          {{ (states('sensor.dew_point_basement') | float(0) - states('sensor.dew_point_outdoor') | float(0)) | round(1) }}

The else branch is the hysteresis: between 1.5 and 3.0 °C the sensor simply keeps its previous state, so it can’t flap around a single threshold. delay_on/delay_off filter short spikes on top of that.

Step 2 -Notification automation (Automations → new → Edit in YAML):

alias: Ventilation advice basement
triggers:
  - trigger: state
    entity_id: binary_sensor.ventilation_useful_basement
    from: "off"
    to: "on"
  - trigger: state
    entity_id: binary_sensor.ventilation_useful_basement
    from: "on"
    to: "off"
actions:
  - variables:
      useful: "{{ trigger.to_state.state == 'on' }}"
      # Replace with your real window contact; a non-existent entity counts as "not open"
      window: binary_sensor.window_basement
      message: >-
        {% if useful %}
          Ventilating the basement is worth it now: dew point indoors {{ states('sensor.dew_point_basement') }} °C, outdoors {{ states('sensor.dew_point_outdoor') }} °C.
        {% else %}
          Ventilating the basement no longer helps -close the window.
        {% endif %}
  # Suppress the prompt if the window is already open
  - condition: template
    value_template: "{{ not (useful and is_state(window, 'on')) }}"
  # Push: always
  - action: notify.mobile_app_your_phone
    data:
      title: "{{ '🪟 Ventilate: basement' if useful else '✅ Close window: basement' }}"
      message: "{{ message }}"
  # Voice announcement: only during the day
  - if:
      - condition: time
        after: "08:00:00"
        before: "21:00:00"
    then:
      - action: notify.alexa_media_everywhere
        data:
          message: "{{ message }}"
          data:
            type: announce
mode: restart

Testing without waiting for the weather: Developer Tools → States → pick the binary sensor → set state to on → the notification must arrive immediately; set it back to off for the “close the window” message. The override only lasts until the next real sensor update.

Two notes: the 3.0 / 1.5 °C values are a starting point for a cool basement -adjust to your building. And the side-by-side sensor calibration @technotron007 mentioned is still step zero: a 1 °C offset between sensors would eat a third of that margin.