TRAMANN PROJECTS
TRAMANN SHOP

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
(GPIO0, GPIO2, GPIO5, GPIO12, GPIO15 have to be left unconnected for booting.)

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 // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / // / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / } }

>> DOWNLOAD TVS SOURCE CODE

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.