Get your basement / bathroom dry with a fan controlled by dew point monitoring (ESP Home)

Hi everyone,

as I sometimes forget to close the windows after I have taken a shower or to air out the basement (so it doesn’t get wet and moldy) I have decided to built a dew point controlled fan system. Even basic appliances cost mutiple hundred Euros and I needed a fun little project over christmas.

A dew point controlled fan system needs two sensors (one outside, one inside). The fan switches on when the dew points have a possitive delta (in - out > 5). Goal → get the wet air out the house, replace it with dry fresh air.

Inspired by this article from heise and the formulas from here I built my own system with home assistant integration using the following parts:

  • ESP32 Wroom microcontroller (flashed with ESPHome)
  • 2 BME sensors on I2C bus - address 0x77 / 0x76 (temperature, humidity, airpressure) (you can also use DHT 22s, i had two BMEs lying around)
  • 5V Relais
  • 12 Volt (2A) DC power brick from an old switch which is long gone
  • 12 Volt to 5 Volt step down converter
  • 12 Volt PC FAN (9 or 12 cm) depending on what you need
  • 1 m of 4 wire cable (old telephone cable) for outside BME sensor
  • 2 m of 4 wire cable (old telephone cable) for inside BME sensor

before you start build a simple sketch to identify the deltas of the two sensors to adjust for that in the software. Put them in a small box und plotbthe values over an hour, calculate the average of the deviation of the both sensors. I ignored deviations of 0,2 C.

Hardware assembly:

Picture will follow :wink:

Basic functionality:

  • The ESP32 monitors humidity and temperature inside and outside without relying on home assistant (I drilled a hole in the wooden frame of the window and routed the cable with the sensor through)
  • Every minute the dew point for inside and outside is calculated on the ESP32 and when the delta is > 5 C the fan starts pushing out the air
  • ESP reports the following to home assistant / accepts changes & control from HomeAssistant without reprogramming

Control options / available values in Homeassistant

  • FAN (on/off)
  • humidity inside / outside** temperature inside / outside
  • air presure inside / outside
  • dew point inside / outside
  • adjust via HomeAssistant front end: dew point delta when the FAN should start
  • adjust via HomeAssistant front end: Hysteresis
  • adjust via HomeAssistant front end: minmal temperatures (e.g. only air out when outside temp over -5 °C)
  • adjust via HomeAssistant front end: Switch FAN on / off manually → get the bathroom smell out after you’ve done your business

I found the following steps to make OTA updates and web logging working for my boards in ESP Home:

  1. use web. esphome .io to install the first image to your ESP via USB / serial
  2. adopt it in your ESPHome installation
  3. add your code
  4. compile the yaml and download the full image (not OTA) to your computer
  5. go back to web .esphome .io upload and install the full image to the ESP via USB / serial
  6. the next code change can be pushed without hassle via your ESPHome installation

You can probably shorten it by starting in step 3 by creating the yaml manually :wink:

Here’s my esphome yaml:

esphome:
  name: esphome-web-f30390
  friendly_name: dewpoint-13
  min_version: 2024.11.0
  name_add_mac_suffix: false


substitutions:
  # Modify variables based on your settings
  devicename: dewpoint-13

esp32:
  board: esp32dev
  framework:
    type: esp-idf


# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: !secret esphome_encryption_key

# Allow Over-The-Air updates
ota:
- platform: esphome

# Allow provisioning Wi-Fi via serial
improv_serial:

wifi:
  # Set up a wifi access point
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  #fast_connect: True
  ap: {}

# adjust to your respective GPIO Pins
i2c:
  sda: GPIO21
  scl: GPIO22
  scan: true
  id: bus_a

# adjust to your respective GPIO Pin 
switch:
  - platform: gpio
    id: fan1
    pin: GPIO16
    name: "fan"
    inverted: true


  - platform: template
    name: "dew point fan"
    id: taupunktlueftung
    restore_mode: RESTORE_DEFAULT_ON
    optimistic: True
        


  # The following can be omitted but neat to remotly restart the whole thing xD
  - platform: restart
    name: ${devicename} restart

number:
  - platform: template
    name: "hysteresis"
    id: hystval
    initial_value: 1
    restore_value: True
    step: 0.5
    min_value: 1 
    max_value: 10
    mode: SLIDER
    unit_of_measurement: "°C"
    optimistic: True

  - platform: template
    name: "min dew point delta"
    id: mintpd
    initial_value: 5
    restore_value: True
    step: 1
    min_value: 1 
    max_value: 15
    mode: SLIDER
    unit_of_measurement: "°C"
    optimistic: True

  - platform: template
    name: "min inside temperature"
    id: mininn
    initial_value: 10
    restore_value: True
    step: 1
    min_value: 5 
    max_value: 20
    mode: SLIDER
    unit_of_measurement: "°C"
    optimistic: True

  - platform: template
    name: "min outside temperature"
    id: minaus
    initial_value: -5
    restore_value: True
    step: 1
    min_value: -10 
    max_value: 10
    mode: SLIDER
    unit_of_measurement: "°C"
    optimistic: True

sensor:
  - platform: bme280_i2c
    i2c_id: bus_a
    address: 0x77
    temperature:
      name: "temperature outside"
      id: tempau
    pressure:
      name: "air pressure outside"
      id: hpaau
    humidity:
      name: "humidity outside"
      id: humau
      filters:
        - offset: +3.6
    update_interval: 60s

  - platform: bme280_i2c
    i2c_id: bus_a
    address: 0x76
    temperature:
      name: "temperature inside"
      id: tempinn
    pressure:
      name: "air pressure inside"
      id: hpainn
    humidity:
      name: "humidity inside"
      id: huminn
    update_interval: 60s

  - platform: wifi_signal
    name: ${devicename} wifi signal
    update_interval: 600s

  - platform: template
    name: "Taupunkt aussen"
    id: taupaus
    unit_of_measurement: "°C"
    accuracy_decimals: 2
    update_interval: never

  - platform: template
    name: "Taupunkt innen"
    id: taupinn
    unit_of_measurement: "°C"
    accuracy_decimals: 2
    update_interval: never



interval:
  startup_delay: 20s
  interval: 1min
  then: 
    - lambda:  |-
        float a; //dew point constant variable
        float b; //dew point constant variable
        float v; // v-Parameter
        float arsensor[2][3];
        arsensor[0][0] = id(tempinn).state; // Temp inside
        arsensor[0][1] = id(huminn).state; // rel humidity inside
        arsensor[1][0] = id(tempau).state; // Temp outside
        arsensor[1][1] = id(humau).state; // rel humidity outside
        // dew point calc constants for below and above 0
        for (int i = 0; i < 2; ++i) 
          {
            if (arsensor[i][0] >= 0) {
              a = 7.5;
              b = 237.3;
              ESP_LOGD("main","above 0 °C"); 
              } else if (arsensor[i][0] < 0) {
              a = 7.6;
              b = 240.7;
              }
            ESP_LOGD("main", "below 0 °C");
            // dew point calculations
            // saturtation pressure in hPa
            float sdd = 6.1078 * pow(10, (a*arsensor[i][0])/(b+arsensor[i][0]));
            // steam pressurein hPa
            float dd = sdd * (arsensor[i][1]/100);
            // v-Parameter
            float v = log10(dd/6.1078);
            arsensor[i][2] = (b*v) / (a-v); // calc dew point temp
            ESP_LOGD("main", "Taupunkt %i ist %f °C", i, arsensor[i][2]);  
          }
        // publish new dew point
        id(taupinn).publish_state(arsensor[0][2]);
        id(taupaus).publish_state(arsensor[1][2]);
        //dew point delta calculated
        bool rel; // var for state to enabled/disable FAN
        float DeltaTP = arsensor[0][2]- arsensor[1][2];
        ESP_LOGD("main", "dew point delta is %f °C", DeltaTP); 
        if (id(taupunktlueftung).state) // check if dew point automatic is enabled in HA default on
        {
          ESP_LOGD("main", "dew point inside: %f °C - dewpoint outside %f °C", arsensor[0][2], arsensor[1][2]); 
          if (DeltaTP > (id(mintpd).state + id(hystval).state))rel = true;
          if (DeltaTP < id(mintpd).state)
          {
            rel = false;
          }
          if (id(tempinn).state < id(mininn).state)
          {
            rel = false;
          }
          if (id(tempau).state < id(minaus).state)
          {
            rel = false;
          }
          if (rel == true)
          {
            id(fan1).turn_on(); // switch relais on
            ESP_LOGD("main", "Conditions met: Fan ON");  
          } else {                             
            id(fan1).turn_off(); // switch relais off
            ESP_LOGD("main", "Conditions NOT met: Fan OFF"); 
          }
        } else {
          ESP_LOGD("main", "AUTOMODE OFF"); 
        }
        
        
        
2 Likes