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
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:
- use web. esphome .io to install the first image to your ESP via USB / serial
- adopt it in your ESPHome installation
- add your code
- compile the yaml and download the full image (not OTA) to your computer
- go back to web .esphome .io upload and install the full image to the ESP via USB / serial
- 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
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");
}