HOW IT WORKS
MODULAR DESIGN
The ventilation system follows a modular design, so it is easier for you to modify the TVS, personalizing it to your needs.For example combinations can be:
SeparatorPrechamber + HeatExchangerSegment + SeparatorPrechamber, if your walls are thin
or SeparatorPrechamber + HeatExchangerSegment + HeatExchangerSegment + SeparatorPrechamber
or SeparatorPrechamber + HeatExchangerSegment + ChannelConverter + HeatExchangerSegment + SeparatorPrechamber, as seen below.
AIRFLOW
But not only the bigger parts are modular, also the parts connecting to the SeparatorPrechamber are designed in that way. There is only one model for the SeparatorPrechamber which is used on the inside and on the outside and connected to either filters or propellers.HEAT EXCHANGER
The heat exchanger works using the counterflow mechanism.The following diagram shows the relative efficiency levels of other ventilation systems using the pendulum mechanism in comparison with the TVS using the counterflow mechanism under equal conditions.
WIRING
Please refer to the diagram below for the wiring of the electronic components.The pins are connected as follows:
pin | name | connected to |
---|---|---|
IO16 | GPIO16 | button 0 (manual mode) |
IO17 | GPIO17 | button 1 (automatic mode) |
IO18 | GPIO18 | button 2 (programmed mode) |
IO19 | GPIO19 | sensor for air humidity and temperature (outside) |
IO23 | GPIO23 | sensor for air humidity and temperature (inside) |
IO21 | GPIO21 (SDA) | OLED display (SDA) |
IO22 | GPIO22 (SCL) | OLED display (SCL) |
IO25 | GPIO25 | propeller 1 (inner channels, leading inside) |
IO26 | GPIO26 | propeller 2 (outer channel, blowing outside) |
GND | GND | GND |
VCC | 5V | 5V |
Down below there is the plan for a printed circuit board of a TVS.
SOFTWARE
The TVS source code is written in yaml and can be modified easily. As standard, the device supports three different modes: manual mode (control the device manually by pressing the button, turn on for 20 minutes and then resume to automatic mode), automatic mode (the device decides independently, based on measured values and formulas (this setting is default and recommended)) and programmed mode (if you want, you can insert your own code here to program the device yourself) You can switch between the modes by pressing the corresponding buttons and the currently selected mode is displayed in the top right-hand corner of the screen.
# about
# / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
# TVS - TRAMANN VENTILATION SYSTEM
# version 3.17
#
# yaml code for ESPHome and Home Assistant
#
# table of contents
# about (where you are right now)
# setup (configurations, especially for communication over the air)
# initial code (setting the sensors and actors, inputs and outputs (in particular the screen) and preparing them for Home Assistant)
# deep sleep code (by default commented out, can be commented in to save power (but it's not recommended))
# operating code (calculating, deciding and acting)
# manual mode (control the device manually by pressing the button, turn on for 20 minutes and then resume to automatic mode)
# automatic mode (the device decides independently, based on measured values and formulas (this setting is default and recommended))
# programmed mode (if you want, you can insert your own code here to program the device yourself)
# setup
# / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
esphome:
name: tvs-tramann-ventilation-system
friendly_name: TVS-Tramann-Ventilation-System
esp32:
board: wemos_d1_mini32
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "gPpyRTf3kx4IeRhtaDaOnErk9SZJ0KPcMrpSQuq3ByI="
ota:
password: "c4356f8f88ed28d0070f120ba9e54b74"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "TVS-Tramann-Ventilation-System"
password: "TVSLocalNetwork"
captive_portal:
# initial code
# / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
# configuration of the buttons
binary_sensor:
- platform: gpio
pin: GPIO16
name: "button 0"
id: button_0
- platform: gpio
pin: GPIO17
name: "button 1"
id: button_1
- platform: gpio
pin: GPIO18
name: "button 2"
id: button_2
# configuration of the sensors for humidity and temperature
sensor:
- platform: dht
pin: GPIO19
temperature:
name: "outside temperature"
id: outside_temperature
humidity:
name: "outside humidity"
id: outside_humidity
update_interval: 60s
model: DHT22
- platform: dht
pin: GPIO23
temperature:
name: "inside temperature"
id: inside_temperature
humidity:
name: "inside humidity"
id: inside_humidity
update_interval: 60s
model: DHT22
# time configuration
time:
- platform: sntp
id: sntp_time
servers:
- 0.pool.ntp.org
- 1.pool.ntp.org
- 2.pool.ntp.org
# configuration of the OLED display
i2c:
sda: GPIO21
scl: GPIO22
scan: true
frequency: 400kHz
# configuration of the fonts
font:
- file: "gfonts://Roboto"
id: font_16
size: 16
- file: "gfonts://Roboto"
id: font_12
size: 12
- file: "gfonts://Roboto"
id: font_10
size: 10
# display configuration
display:
- platform: ssd1306_i2c
model: "SSD1306 128x64"
address: 0x3C
lambda: |-
static std::string button_status = "auto"; // default to "auto" at startup
static bool button_0_prev_state = true; // default ON
static bool button_1_prev_state = true; // default ON
static bool button_2_prev_state = true; // default ON
// update the button status only when the state changes from ON to OFF
if (!id(button_0).state && button_0_prev_state) {
button_status = "man";
}
button_0_prev_state = id(button_0).state;
if (!id(button_1).state && button_1_prev_state) {
button_status = "auto";
}
button_1_prev_state = id(button_1).state;
if (!id(button_2).state && button_2_prev_state) {
button_status = "prog";
}
button_2_prev_state = id(button_2).state;
static int state = 0;
static unsigned long last_change = millis();
const unsigned long interval_1 = 2000;
const unsigned long interval_2 = 4000;
if (millis() - last_change > interval_1 && state == 0) {
state = 1;
last_change = millis();
} else if (millis() - last_change > interval_2 && state == 1) {
state = 2;
last_change = millis();
}
it.fill(COLOR_OFF);
if (state == 0) {
it.print(it.get_width() / 2, it.get_height() / 2, id(font_16), TextAlign::CENTER, "TRAMANN");
} else if (state == 1) {
it.print(it.get_width() / 2, it.get_height() / 2 - 8, id(font_16), TextAlign::CENTER, "TVS");
it.print(it.get_width() / 2, it.get_height() / 2 + 8, id(font_10), TextAlign::CENTER, "Tramann Ventilation System");
} else if (state == 2) {
it.printf(128, 0, id(font_12), TextAlign::TOP_RIGHT, button_status.c_str());
it.print(0, 0, id(font_12), "TVS");
it.printf(0, 17, id(font_10), "intmp: %.1f°C, hum: %.1f%%", id(inside_temperature).state, id(inside_humidity).state);
it.printf(0, 29, id(font_10), "outtmp: %.1f°C, hum: %.1f%%", id(outside_temperature).state, id(outside_humidity).state);
auto time = id(sntp_time).now();
it.strftime(0, 53, id(font_10), "%H:%M", time);
it.strftime(74, 53, id(font_10), "%d-%m-%Y", time);
}
# configuration of the propellers
output:
- platform: gpio
pin: GPIO25
id: propeller_1
- platform: gpio
pin: GPIO26
id: propeller_2
switch:
- platform: output
name: "propeller 1"
output: propeller_1
- platform: output
name: "propeller 2"
output: propeller_2
# deep sleep code
# / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
# warning: the device can't be reprogrammed over the air during the sleep periode, please use USB instead (or wait until the run periode begins)
#deep_sleep:
# run_duration: 10min
# sleep_duration: 50min
# operating code
# / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
# implement different operating modes
interval:
- interval: 1s
then:
- lambda: |-
// update mode and switch status
static std::string button_status = "auto";
if (!id(button_0).state) {
button_status = "man";
} else if (!id(button_1).state) {
button_status = "auto";
} else if (!id(button_2).state) {
button_status = "prog";
}
// timestamp for manual mode
static unsigned long manual_mode_start = 0;
static bool propeller_state = false;
// manual mode
// / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
if (button_status == "man") {
// toggle propellers for 20 minutes when button is pressed (1200000), after that turn off for 5 seconds (5000)
if (!id(button_0).state && (millis() - manual_mode_start > 5000)) {
manual_mode_start = millis();
propeller_state = !propeller_state;
if (propeller_state) {
id(propeller_1).turn_on();
id(propeller_2).turn_on();
} else {
id(propeller_1).turn_off();
id(propeller_2).turn_off();
}
}
// toggle propellers for 20 minutes when button is pressed (1200000), after that turn off for 5 seconds (5000)
else if (propeller_state && millis() - manual_mode_start > 1200000) {
id(propeller_1).turn_off();
id(propeller_2).turn_off();
}
} else {
// automatic mode
// / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
//
// The sensors have an inaccuracy of around 5°C and the aim of the automatic mode
// is to achieve a temperature range of around 19°C to 21°C.
// The humidity should be regulated to the target value of 40% to 60%.
// The system is switched off at night, during the usual time
// for falling asleep between 21:00 and 23:30.
//
if (button_status == "auto") {
// calculate dew point
auto dew_point = [](float temp, float humidity) {
const float a = 17.27;
const float b = 237.7;
float alpha = ((a * temp) / (b + temp)) + log(humidity / 100.0);
return (b * alpha) / (a - alpha);
};
// temperature and humidity conditions
bool temp_condition = (id(inside_temperature).state > 24.0 && id(outside_temperature).state < 24.0) ||
(id(inside_temperature).state < 16.0 && id(outside_temperature).state > 16.0);
bool humidity_condition = ((id(inside_humidity).state > 60 && id(outside_humidity).state < 60) ||
(id(inside_humidity).state < 40 && id(outside_humidity).state > 40));
float inside_dew_point = dew_point(id(inside_temperature).state, id(inside_humidity).state);
float outside_dew_point = dew_point(id(outside_temperature).state, id(outside_humidity).state);
bool refined_humidity_condition = ((inside_dew_point > outside_dew_point && id(inside_humidity).state > 60) ||
(inside_dew_point < outside_dew_point && id(inside_humidity).state < 40));
// combine conditions
if (temp_condition || refined_humidity_condition) {
id(propeller_1).turn_on();
id(propeller_2).turn_on();
} else {
id(propeller_1).turn_off();
id(propeller_2).turn_off();
}
}
// programmed mode
// / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
if (button_status == "prog") {
// implement programmed control
// / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
// / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
// if you want, you can insert your own code here to program the device yourself
// / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
// / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
}
}
The sensors have an inaccuracy of around 5°C and the aim of the automatic mode is to achieve a temperature range of around 19°C to 21°C. The humidity will be regulated to the target value of 40% to 60% and the dew point is also considered using the Magnus formula. The system is switched off at night, during the usual time for falling asleep between 21:00 and 23:30.
Aren't these very, very professional looking diagrams convincing?
Maybe you want to take a look at the used PARTS and the BUILDING INSTRUCTIONS.