[
  {
    "path": "Lochraster/Lochraster.md",
    "content": "# Lochraster-Version des Taupunktlüfters\r\n\r\n**von Patrick alias _resipat265_ im [heise-Forum](https://www.heise.de/forum/Make/Heft-Projekte/Artikelforum-Heft-1-2022/Re-Taupunkt-Lueftungssystem-Bastelfrage/posting-41215886/show/)**\r\n\r\nHier ein Bild vom Ergebnis von oben:\r\n\r\n![Draufsicht](./Lochraster_Draufsicht.jpg)\r\n\r\nZu den Nummern, welche die Anschlüsse durchnummerieren:\r\n1 = Relais-Anschluss\r\n2 = VCC/GND Zuleitung von 5V Netzteil\r\n3 = Modusschalter\r\n4 = Displayanschluss\r\n5 = Reset-Taster. Hier habe ich einen Pin zuviel gelötet…\r\n6 = DHT22 außen\r\n7 = DHT22 innen\r\n\r\nHier ein Bild von unten:\r\n\r\n![Untenansicht](./Lochraster_Untenansicht.jpg)\r\n\r\nDie drei [PDFs](./PDF) sind aus Fritzing exportiert, mit Gesamtschaltplan, Draufsicht und Sicht von unten. **Achtung**: Die Sicht von unten muss natürlich gespiegelt werden. \r\n"
  },
  {
    "path": "README.md",
    "content": "![GitHub Logo](http://www.heise.de/make/icons/make_logo.png)\n\nMaker Media GmbH\n\n***\n\n# Taupunktlüfter\n\n**Lüften ist die beste und billigste Maßnahme gegen feuchte Keller  – jedenfalls, wenn man es richtig macht und sich nicht von außen zusätzliche Nässe ins Gewölbe holt. Bei unserem Projekt behält ein Arduino Nano die aktuellen Taupunkte drinnen und draußen rund um die Uhr im Auge und legt durch gezieltes Lüften den Keller trocken.**\n\n![Taupunktluefter](./Taupunktluefter.jpg)\n\nHier gibt es den Arduino-Code zum Projekt des Taupunktlüfters sowohl in der Fassung ohne Datenlogger (Code im Ordner _Taupunkt_Lueftung_), wie in Make 1/22 beschrieben, als auch in der Fassung mit Datenlogger (Code im Ordner _Taupunkt_Lueftung_Datenlogging_Version_2.13_), die in Make 2/22 beschrieben wird. Diese Version zeigt auch der erweiterte Schaltplan:\n\n![Schaltplan Taupunktlüfter mit Logger](./TaupunktluefterMitLogger.jpg)\n\nDer im Schaltplan gezeigte _Modusschalter_ ist ein Schalter mit drei Schaltstellungen: links, Mitte, rechts. In der Mittelstellung sind alle Pole frei, bei rechts wird der mittlere Pol nach rechts verbunden und bei links der mittlere Pol nach links. Beschaltet wird dann beispielsweise so: linker Kontakt des Schalters auf GND (linke Stellung ist dann für **aus**), rechter Kontakt auf VCC (5V, rechte Stellung ist dann für **an**) und Mittelkontakt auf Arduino-IO-Pin 6 (Verbindung zum Relais, Mittelstellung ist dann für **Automatik**).\n\nDie wichtigsten Fragen rund um den Taupunktlüfter beantwortet eine **[FAQ](https://heise.de/-6526328)**. Die vollständigen Artikel zum Projekt gibt es in der **[Make-Ausgabe 1/22 ab Seite 22](https://www.heise.de/select/make/2022/1/2135511212557842576)** und in der **[Make-Ausgabe 2/22 ab Seite 82](https://www.heise.de/select/make/2022/2/2204711461516715363)** zu lesen.\n\nDank Patrick alias _resipat265_ aus dem heise-Forum gibt es jetzt auch eine Vorlage für den Nachbau auf **[Lochraster-Platine](./Lochraster/Lochraster.md)**. Vielen Dank dafür!\n"
  },
  {
    "path": "Taupunkt_Lueftung/Taupunkt_Lueftung.ino",
    "content": "// Dieser Code benötigt zwingend die folgenden Libraries:\r\n#include \"DHT.h\"\r\n#include <Wire.h> \r\n#include <LiquidCrystal_I2C.h>\r\n#include <avr/wdt.h>\r\n\r\n#define RELAIPIN 6 // Anschluss des Lüfter-Relais\r\n#define DHTPIN_1 5 // Datenleitung für den DHT-Sensor 1 (innen)\r\n#define DHTPIN_2 4 // Datenleitung für den DHT-Sensor 2 (außen)\r\n\r\n#define RELAIS_EIN LOW\r\n#define RELAIS_AUS HIGH\r\nbool rel;\r\n\r\n#define DHTTYPE_1 DHT22 // DHT 22 \r\n#define DHTTYPE_2 DHT22 // DHT 22  \r\n\r\n// *******  Korrekturwerte der einzelnen Sensorwerte  *******\r\n#define Korrektur_t_1  -3 // Korrekturwert Innensensor Temperatur\r\n#define Korrektur_t_2  -4 // Korrekturwert Außensensor Temperatur\r\n#define Korrektur_h_1  0  // Korrekturwert Innensensor Luftfeuchtigkeit\r\n#define Korrektur_h_2  0  // Korrekturwert Außensensor Luftfeuchtigkeit\r\n//***********************************************************\r\n\r\n#define SCHALTmin   5.0 // minimaler Taupunktunterschied, bei dem das Relais schaltet\r\n#define HYSTERESE   1.0 // Abstand von Ein- und Ausschaltpunkt\r\n#define TEMP1_min  10.0 // Minimale Innentemperatur, bei der die Lüftung aktiviert wird\r\n#define TEMP2_min -10.0 // Minimale Außentemperatur, bei der die Lüftung aktiviert wird\r\n\r\nDHT dht1(DHTPIN_1, DHTTYPE_1); //Der Innensensor wird ab jetzt mit dht1 angesprochen\r\nDHT dht2(DHTPIN_2, DHTTYPE_2); //Der Außensensor wird ab jetzt mit dht2 angesprochen\r\n\r\nLiquidCrystal_I2C lcd(0x27,20,4); // LCD: I2C-Addresse und Displaygröße setzen\r\n\r\nbool fehler = true;\r\n\r\nvoid setup() {\r\n  wdt_enable(WDTO_8S); // Watchdog timer auf 8 Sekunden stellen\r\n  \r\n  pinMode(RELAIPIN, OUTPUT);          // Relaispin als Output definieren\r\n  digitalWrite(RELAIPIN, RELAIS_AUS); // Relais ausschalten\r\n  \r\n  Serial.begin(9600);  // Serielle Ausgabe, falls noch kein LCD angeschlossen ist\r\n  Serial.println(F(\"Teste Sensoren..\"));\r\n\r\n  lcd.init();\r\n  lcd.backlight();                      \r\n  lcd.setCursor(2,0);\r\n  lcd.print(F(\"Teste Sensoren..\"));\r\n  \r\n  byte Grad[8] = {B00111,B00101,B00111,B0000,B00000,B00000,B00000,B00000};      // Sonderzeichen ° definieren\r\n  lcd.createChar(0, Grad);\r\n  byte Strich[8] = {B00100,B00100,B00100,B00100,B00100,B00100,B00100,B00100};   // Sonderzeichen senkrechter Strich definieren\r\n  lcd.createChar(1, Strich);\r\n    \r\n  dht1.begin(); // Sensoren starten\r\n  dht2.begin();   \r\n}\r\n\r\nvoid loop() {\r\n\r\n  float h1 = dht1.readHumidity()+Korrektur_h_1;       // Innenluftfeuchtigkeit auslesen und unter „h1“ speichern\r\n  float t1 = dht1.readTemperature()+ Korrektur_t_1;   // Innentemperatur auslesen und unter „t1“ speichern\r\n  float h2 = dht2.readHumidity()+Korrektur_h_2;       // Außenluftfeuchtigkeit auslesen und unter „h2“ speichern\r\n  float t2 = dht2.readTemperature()+ Korrektur_t_2;   // Außentemperatur auslesen und unter „t2“ speichern\r\n  \r\n  if (fehler == true)  // Prüfen, ob gültige Werte von den Sensoren kommen\r\n  {\r\n    fehler = false; \r\n    if (isnan(h1) || isnan(t1) || h1 > 100 || h1 < 1 || t1 < -40 || t1 > 80 )  {\r\n      Serial.println(F(\"Fehler beim Auslesen vom 1. Sensor!\"));\r\n      lcd.setCursor(0,1);\r\n      lcd.print(F(\"Fehler Sensor 1\"));\r\n      fehler = true;\r\n    }else {\r\n     lcd.setCursor(0,1);\r\n     lcd.print(F(\"Sensor 1 in Ordnung\"));\r\n   }\r\n  \r\n    delay(2000);  // Zeit um das Display zu lesen\r\n  \r\n      if (isnan(h2) || isnan(t2) || h2 > 100 || h2 < 1 || t2 < -40 || t2  > 80)  {\r\n        Serial.println(F(\"Fehler beim Auslesen vom 2. Sensor!\"));\r\n        lcd.setCursor(0,2);\r\n        lcd.print(F(\"Fehler Sensor 2\"));\r\n        fehler = true;\r\n      } else {\r\n        lcd.setCursor(0,2);\r\n        lcd.print(F(\"Sensor 2 in Ordnung\"));\r\n     }\r\n\r\n    delay(2000);  // Zeit um das Display zu lesen\r\n  }\r\n  if (isnan(h1) || isnan(t1) || isnan(h2) || isnan(t2)) fehler = true;\r\n   \r\n if (fehler == true) {\r\n    digitalWrite(RELAIPIN, RELAIS_AUS); // Relais ausschalten \r\n    lcd.setCursor(0,3);\r\n    lcd.print(F(\"CPU Neustart.....\"));\r\n    while (1);  // Endlosschleife um das Display zu lesen und die CPU durch den Watchdog neu zu starten\r\n }\r\n wdt_reset();  // Watchdog zurücksetzen\r\n\r\n//**** Taupunkte errechnen********\r\nfloat Taupunkt_1 = taupunkt(t1, h1);\r\nfloat Taupunkt_2 = taupunkt(t2, h2);\r\n\r\n// Werteausgabe auf Serial Monitor\r\n Serial.print(F(\"Sensor-1: \" ));\r\n  Serial.print(F(\"Luftfeuchtigkeit: \"));\r\n  Serial.print(h1);                     \r\n  Serial.print(F(\"%  Temperatur: \"));\r\n  Serial.print(t1);\r\n  Serial.print(F(\"°C  \"));\r\n  Serial.print(F(\"  Taupunkt: \"));\r\n  Serial.print(Taupunkt_1);\r\n  Serial.println(F(\"°C  \"));\r\n\r\n  Serial.print(\"Sensor-2: \" );\r\n  Serial.print(F(\"Luftfeuchtigkeit: \"));\r\n  Serial.print(h2);\r\n  Serial.print(F(\"%  Temperatur: \"));\r\n  Serial.print(t2);\r\n  Serial.print(F(\"°C \"));\r\n  Serial.print(F(\"   Taupunkt: \"));\r\n  Serial.print(Taupunkt_2);\r\n  Serial.println(F(\"°C  \"));\r\n\r\n\r\n Serial.println();\r\n\r\n  // Werteausgabe auf dem I2C-Display\r\n  lcd.clear();\r\n  lcd.setCursor(0,0);\r\n  lcd.print(F(\"S1: \"));\r\n  lcd.print(t1); \r\n  lcd.write((uint8_t)0); // Sonderzeichen °C\r\n  lcd.write(('C'));\r\n  lcd.write((uint8_t)1); // Sonderzeichen |\r\n  lcd.print(h1);\r\n  lcd.print(F(\" %\"));\r\n\r\n  lcd.setCursor(0,1);\r\n  lcd.print(F(\"S2: \"));\r\n  lcd.print(t2); \r\n  lcd.write((uint8_t)0); // Sonderzeichen °C\r\n  lcd.write(('C'));\r\n  lcd.write((uint8_t)1); // Sonderzeichen |\r\n  lcd.print(h2);\r\n  lcd.print(F(\" %\"));\r\n\r\n  lcd.setCursor(0,2);\r\n  lcd.print(F(\"Taupunkt 1: \"));\r\n  lcd.print(Taupunkt_1); \r\n  lcd.write((uint8_t)0); // Sonderzeichen °C\r\n  lcd.write(('C'));\r\n\r\n  lcd.setCursor(0,3);\r\n  lcd.print(F(\"Taupunkt 2: \"));\r\n  lcd.print(Taupunkt_2); \r\n  lcd.write((uint8_t)0); // Sonderzeichen °C\r\n  lcd.write(('C'));\r\n\r\ndelay(6000); // Zeit um das Display zu lesen\r\nwdt_reset(); // Watchdog zurücksetzen\r\n\r\n  lcd.clear();\r\n  lcd.setCursor(0,0);\r\n  \r\nfloat DeltaTP = Taupunkt_1 - Taupunkt_2;\r\n\r\nif (DeltaTP > (SCHALTmin + HYSTERESE))rel = true;\r\nif (DeltaTP < (SCHALTmin))rel = false;\r\nif (t1 < TEMP1_min )rel = false;\r\nif (t2 < TEMP2_min )rel = false;\r\n\r\nif (rel == true)\r\n{\r\n  digitalWrite(RELAIPIN, RELAIS_EIN); // Relais einschalten\r\n  lcd.print(F(\"Lueftung AN\"));  \r\n} else {                             \r\n  digitalWrite(RELAIPIN, RELAIS_AUS); // Relais ausschalten\r\n  lcd.print(F(\"Lueftung AUS\"));\r\n}\r\n\r\n lcd.setCursor(0,1);\r\n lcd.print(\"Delta TP: \");\r\n lcd.print(DeltaTP);\r\n lcd.write((uint8_t)0); // Sonderzeichen °C\r\n lcd.write('C');\r\n\r\n delay(4000);   // Wartezeit zwischen zwei Messungen\r\n wdt_reset();   // Watchdog zurücksetzen \r\n \r\n}\r\n\r\nfloat taupunkt(float t, float r) {\r\n  \r\nfloat a, b;\r\n  \r\n  if (t >= 0) {\r\n    a = 7.5;\r\n    b = 237.3;\r\n  } else if (t < 0) {\r\n    a = 7.6;\r\n    b = 240.7;\r\n  }\r\n  \r\n  // Sättigungsdampfdruck in hPa\r\n  float sdd = 6.1078 * pow(10, (a*t)/(b+t));\r\n  \r\n  // Dampfdruck in hPa\r\n  float dd = sdd * (r/100);\r\n  \r\n  // v-Parameter\r\n  float v = log10(dd/6.1078);\r\n  \r\n  // Taupunkttemperatur (°C)\r\n  float tt = (b*v) / (a-v);\r\n  return { tt };  \r\n}\r\n\r\n\r\n void software_Reset() // Startet das Programm neu, nicht aber die Sensoren oder das LCD \r\n  {\r\n    asm volatile (\"  jmp 0\");  \r\n  }\r\n"
  },
  {
    "path": "Taupunkt_Lueftung.ino",
    "content": "// Dieser Code benötigt zwingend die folgenden Libraries:\r\n#include \"DHT.h\"\r\n#include <Wire.h> \r\n#include <LiquidCrystal_I2C.h>\r\n#include <avr/wdt.h>\r\n\r\n#define RELAIPIN 6 // Anschluss des Lüfter-Relais\r\n#define DHTPIN_1 5 // Datenleitung für den DHT-Sensor 1 (innen)\r\n#define DHTPIN_2 4 // Datenleitung für den DHT-Sensor 2 (außen)\r\n\r\n#define RELAIS_EIN LOW\r\n#define RELAIS_AUS HIGH\r\nbool rel;\r\n\r\n#define DHTTYPE_1 DHT22 // DHT 22 \r\n#define DHTTYPE_2 DHT22 // DHT 22  \r\n\r\n// *******  Korrekturwerte der einzelnen Sensorwerte  *******\r\n#define Korrektur_t_1  -3 // Korrekturwert Innensensor Temperatur\r\n#define Korrektur_t_2  -4 // Korrekturwert Außensensor Temperatur\r\n#define Korrektur_h_1  0  // Korrekturwert Innensensor Luftfeuchtigkeit\r\n#define Korrektur_h_2  0  // Korrekturwert Außensensor Luftfeuchtigkeit\r\n//***********************************************************\r\n\r\n#define SCHALTmin   5.0 // minimaler Taupunktunterschied, bei dem das Relais schaltet\r\n#define HYSTERESE   1.0 // Abstand von Ein- und Ausschaltpunkt\r\n#define TEMP1_min  10.0 // Minimale Innentemperatur, bei der die Lüftung aktiviert wird\r\n#define TEMP2_min -10.0 // Minimale Außentemperatur, bei der die Lüftung aktiviert wird\r\n\r\nDHT dht1(DHTPIN_1, DHTTYPE_1); //Der Innensensor wird ab jetzt mit dht1 angesprochen\r\nDHT dht2(DHTPIN_2, DHTTYPE_2); //Der Außensensor wird ab jetzt mit dht2 angesprochen\r\n\r\nLiquidCrystal_I2C lcd(0x27,20,4); // LCD: I2C-Addresse und Displaygröße setzen\r\n\r\nbool fehler = true;\r\n\r\nvoid setup() {\r\n  wdt_enable(WDTO_8S); // Watchdog timer auf 8 Sekunden stellen\r\n  \r\n  pinMode(RELAIPIN, OUTPUT);          // Relaispin als Output definieren\r\n  digitalWrite(RELAIPIN, RELAIS_AUS); // Relais ausschalten\r\n  \r\n  Serial.begin(9600);  // Serielle Ausgabe, falls noch kein LCD angeschlossen ist\r\n  Serial.println(F(\"Teste Sensoren..\"));\r\n\r\n  lcd.init();\r\n  lcd.backlight();                      \r\n  lcd.setCursor(2,0);\r\n  lcd.print(F(\"Teste Sensoren..\"));\r\n  \r\n  byte Grad[8] = {B00111,B00101,B00111,B0000,B00000,B00000,B00000,B00000};      // Sonderzeichen ° definieren\r\n  lcd.createChar(0, Grad);\r\n  byte Strich[8] = {B00100,B00100,B00100,B00100,B00100,B00100,B00100,B00100};   // Sonderzeichen senkrechter Strich definieren\r\n  lcd.createChar(1, Strich);\r\n    \r\n  dht1.begin(); // Sensoren starten\r\n  dht2.begin();   \r\n}\r\n\r\nvoid loop() {\r\n\r\n  float h1 = dht1.readHumidity()+Korrektur_h_1;       // Innenluftfeuchtigkeit auslesen und unter „h1“ speichern\r\n  float t1 = dht1.readTemperature()+ Korrektur_t_1;   // Innentemperatur auslesen und unter „t1“ speichern\r\n  float h2 = dht2.readHumidity()+Korrektur_h_2;       // Außenluftfeuchtigkeit auslesen und unter „h2“ speichern\r\n  float t2 = dht2.readTemperature()+ Korrektur_t_2;   // Außentemperatur auslesen und unter „t2“ speichern\r\n  \r\n  if (fehler == true)  // Prüfen, ob gültige Werte von den Sensoren kommen\r\n  {\r\n    fehler = false; \r\n    if (isnan(h1) || isnan(t1) || h1 > 100 || h1 < 1 || t1 < -40 || t1 > 80 )  {\r\n      Serial.println(F(\"Fehler beim Auslesen vom 1. Sensor!\"));\r\n      lcd.setCursor(0,1);\r\n      lcd.print(F(\"Fehler Sensor 1\"));\r\n      fehler = true;\r\n    }else {\r\n     lcd.setCursor(0,1);\r\n     lcd.print(F(\"Sensor 1 in Ordnung\"));\r\n   }\r\n  \r\n    delay(2000);  // Zeit um das Display zu lesen\r\n  \r\n      if (isnan(h2) || isnan(t2) || h2 > 100 || h2 < 1 || t2 < -40 || t2  > 80)  {\r\n        Serial.println(F(\"Fehler beim Auslesen vom 2. Sensor!\"));\r\n        lcd.setCursor(0,2);\r\n        lcd.print(F(\"Fehler Sensor 2\"));\r\n        fehler = true;\r\n      } else {\r\n        lcd.setCursor(0,2);\r\n        lcd.print(F(\"Sensor 2 in Ordnung\"));\r\n     }\r\n\r\n    delay(2000);  // Zeit um das Display zu lesen\r\n  }\r\n  if (isnan(h1) || isnan(t1) || isnan(h2) || isnan(t2)) fehler = true;\r\n   \r\n if (fehler == true) {\r\n    digitalWrite(RELAIPIN, RELAIS_AUS); // Relais ausschalten \r\n    lcd.setCursor(0,3);\r\n    lcd.print(F(\"CPU Neustart.....\"));\r\n    while (1);  // Endlosschleife um das Display zu lesen und die CPU durch den Watchdog neu zu starten\r\n }\r\n wdt_reset();  // Watchdog zurücksetzen\r\n\r\n//**** Taupunkte errechnen********\r\nfloat Taupunkt_1 = taupunkt(t1, h1);\r\nfloat Taupunkt_2 = taupunkt(t2, h2);\r\n\r\n// Werteausgabe auf Serial Monitor\r\n Serial.print(F(\"Sensor-1: \" ));\r\n  Serial.print(F(\"Luftfeuchtigkeit: \"));\r\n  Serial.print(h1);                     \r\n  Serial.print(F(\"%  Temperatur: \"));\r\n  Serial.print(t1);\r\n  Serial.print(F(\"°C  \"));\r\n  Serial.print(F(\"  Taupunkt: \"));\r\n  Serial.print(Taupunkt_1);\r\n  Serial.println(F(\"°C  \"));\r\n\r\n  Serial.print(\"Sensor-2: \" );\r\n  Serial.print(F(\"Luftfeuchtigkeit: \"));\r\n  Serial.print(h2);\r\n  Serial.print(F(\"%  Temperatur: \"));\r\n  Serial.print(t2);\r\n  Serial.print(F(\"°C \"));\r\n  Serial.print(F(\"   Taupunkt: \"));\r\n  Serial.print(Taupunkt_2);\r\n  Serial.println(F(\"°C  \"));\r\n\r\n\r\n Serial.println();\r\n\r\n  // Werteausgabe auf dem I2C-Display\r\n  lcd.clear();\r\n  lcd.setCursor(0,0);\r\n  lcd.print(F(\"S1: \"));\r\n  lcd.print(t1); \r\n  lcd.write((uint8_t)0); // Sonderzeichen °C\r\n  lcd.write(('C'));\r\n  lcd.write((uint8_t)1); // Sonderzeichen |\r\n  lcd.print(h1);\r\n  lcd.print(F(\" %\"));\r\n\r\n  lcd.setCursor(0,1);\r\n  lcd.print(F(\"S2: \"));\r\n  lcd.print(t2); \r\n  lcd.write((uint8_t)0); // Sonderzeichen °C\r\n  lcd.write(('C'));\r\n  lcd.write((uint8_t)1); // Sonderzeichen |\r\n  lcd.print(h2);\r\n  lcd.print(F(\" %\"));\r\n\r\n  lcd.setCursor(0,2);\r\n  lcd.print(F(\"Taupunkt 1: \"));\r\n  lcd.print(Taupunkt_1); \r\n  lcd.write((uint8_t)0); // Sonderzeichen °C\r\n  lcd.write(('C'));\r\n\r\n  lcd.setCursor(0,3);\r\n  lcd.print(F(\"Taupunkt 2: \"));\r\n  lcd.print(Taupunkt_2); \r\n  lcd.write((uint8_t)0); // Sonderzeichen °C\r\n  lcd.write(('C'));\r\n\r\ndelay(6000); // Zeit um das Display zu lesen\r\nwdt_reset(); // Watchdog zurücksetzen\r\n\r\n  lcd.clear();\r\n  lcd.setCursor(0,0);\r\n  \r\nfloat DeltaTP = Taupunkt_1 - Taupunkt_2;\r\n\r\nif (DeltaTP > (SCHALTmin + HYSTERESE))rel = true;\r\nif (DeltaTP < (SCHALTmin))rel = false;\r\nif (t1 < TEMP1_min )rel = false;\r\nif (t2 < TEMP2_min )rel = false;\r\n\r\nif (rel == true)\r\n{\r\n  digitalWrite(RELAIPIN, RELAIS_EIN); // Relais einschalten\r\n  lcd.print(F(\"Lueftung AN\"));  \r\n} else {                             \r\n  digitalWrite(RELAIPIN, RELAIS_AUS); // Relais ausschalten\r\n  lcd.print(F(\"Lueftung AUS\"));\r\n}\r\n\r\n lcd.setCursor(0,1);\r\n lcd.print(\"Delta TP: \");\r\n lcd.print(DeltaTP);\r\n lcd.write((uint8_t)0); // Sonderzeichen °C\r\n lcd.write('C');\r\n\r\n delay(4000);   // Wartezeit zwischen zwei Messungen\r\n wdt_reset();   // Watchdog zurücksetzen \r\n \r\n}\r\n\r\nfloat taupunkt(float t, float r) {\r\n  \r\nfloat a, b;\r\n  \r\n  if (t >= 0) {\r\n    a = 7.5;\r\n    b = 237.3;\r\n  } else if (t < 0) {\r\n    a = 7.6;\r\n    b = 240.7;\r\n  }\r\n  \r\n  // Sättigungsdampfdruck in hPa\r\n  float sdd = 6.1078 * pow(10, (a*t)/(b+t));\r\n  \r\n  // Dampfdruck in hPa\r\n  float dd = sdd * (r/100);\r\n  \r\n  // v-Parameter\r\n  float v = log10(dd/6.1078);\r\n  \r\n  // Taupunkttemperatur (°C)\r\n  float tt = (b*v) / (a-v);\r\n  return { tt };  \r\n}\r\n\r\n\r\n void software_Reset() // Startet das Programm neu, nicht aber die Sensoren oder das LCD \r\n  {\r\n    asm volatile (\"  jmp 0\");  \r\n  }\r\n"
  },
  {
    "path": "Taupunkt_Lueftung_Datenlogging_Version_2.13/SD.ino",
    "content": "//////////////////////////////////////////////////////////////////////////////\r\n// Das Taupunkt-Lüftungssystem \r\n// mit Datenlogging\r\n// \r\n// Routinen zur Speicherung auf einer SD-Karte\r\n//\r\n// veröffentlicht in der MAKE 2/2022\r\n//\r\n// Ulrich Schmerold\r\n// 3/2022\r\n//////////////////////////////////////////////////////////////////////////////\r\n\r\nFile logFile;                    // Variable für die csv-Datei\r\n#define CS_PIN 10                // An diesem Pin ist die CS-Leitung angeschlossen\r\n\r\nunsigned long TimeDaten = 0;    // zur Berechnung, wann wieder gespeichert wird\r\n\r\n\r\nvoid test_SD(){\r\n  if (logging == true)\r\n  {\r\n    // --------- SD-Karte suchen  -------------------\r\n  lcd.setCursor(0,0);\r\n  lcd.print(F(\"Suche SD Karte..\"));\r\n  lcd.setCursor(2,1);\r\n\r\n  if (!SD.begin(CS_PIN)){\r\n   lcd.print(F(\"SD nicht gefunden!\"));\r\n   logging = false;\r\n   } else {\r\n    lcd.print(F(\"SD  gefunden\"));\r\n    if (not SD.exists(logFileName) )  \r\n    {\r\n      logFile = SD.open(logFileName, FILE_WRITE); \r\n      logFile.println(Headerzeile); // Header schreiben\r\n      logFile.close();  \r\n    }    \r\n   }\r\n   delay(3000);   // Zeit um das Display zu lesen\r\n   wdt_reset();   // Watchdog zurücksetzen\r\n  }\r\n}\r\n\r\n\r\nvoid save_to_SD()\r\n{ \r\n  unsigned long t; \r\n\r\n t = millis() / 60000;               \r\n  if (  TimeDaten == 0 ) TimeDaten =  t;  \r\n\r\n  // -------------------------  Sensorenwerte abspeichern ----------------------\r\n  if (((TimeDaten + LogInterval) <= t)  or ( Tageswechsel ))\r\n  {\r\n    TimeDaten = t;\r\n    Tageswechsel=false;\r\n    test_SD();\r\n    wdt_reset(); // Watchdog zurücksetzen\r\n      lcd.clear();\r\n      lcd.print(F(\"Speichere Datensatz\"));   \r\n      make_time_stamp();\r\n      File logFile = SD.open(logFileName, FILE_WRITE);  // Oeffne Datei\r\n      // Serial.print( stamp);  Serial.print( ';' + LogData );  Serial.println(Luefterzeit);\r\n       logFile.print (stamp);\r\n      logFile.println( ';' + LogData );\r\n    logFile.close(); \r\n    delay(4000);\r\n  } \r\n}\r\n"
  },
  {
    "path": "Taupunkt_Lueftung_Datenlogging_Version_2.13/Taupunkt_Lueftung_Datenlogging_Version_2.13.ino",
    "content": "//////////////////////////////////////////////////////////////////////////////\r\n// Das Taupunkt-Lüftungssystem \r\n// mit Datenlogging\r\n// \r\n// veröffentlicht in der MAKE 1/2022 und 2/2022\r\n//\r\n// Ulrich Schmerold\r\n// 3/2022\r\n//////////////////////////////////////////////////////////////////////////////\r\n#define Software_version \"Version: 2.13\"\r\n\r\n// Dieser Code benötig zwingend die folgenden Libraries:\r\n#include \"DHT.h\"\r\n#include <Wire.h> \r\n#include <LiquidCrystal_I2C.h>\r\n#include <avr/wdt.h>\r\n#include <DS1307RTC.h>\r\n#include <SD.h>\r\n#include <SPI.h>\r\n\r\ntmElements_t tm;\r\n\r\n#define RELAIPIN 6     // Anschluss des LüfterRelais\r\n#define DHTPIN_1 5     // Datenleitung für den DHT Sensor 1 (innen)\r\n#define DHTPIN_2 4     // Datenleitung für den DHT Sensor 2 (außen)\r\n\r\n#define RELAIS_EIN LOW\r\n#define RELAIS_AUS HIGH\r\nbool rel;\r\nbool fehler = true;\r\n\r\n#define DHTTYPE_1 DHT22   // DHT 22 \r\n#define DHTTYPE_2 DHT22   // DHT 22  \r\n\r\n// ***************************   Korrekturwerte der einzelnen Sensorwerten.  ***************\r\n#define Korrektur_t_1  -3   // Korrekturwert Innensensor Temperatur\r\n#define Korrektur_t_2  -4   // Korrekturwert Außensensor Temperatur\r\n#define Korrektur_h_1  0    // Korrekturwert Innensensor Luftfeuchtigkeit\r\n#define Korrektur_h_2  0    // Korrekturwert Außensensor Luftfeuchtigkeit\r\n//******************************************************************************************\r\n\r\n#define SCHALTmin   5.0   // minimaler Taupuntunterschied, bei dem das Relais schaltet\r\n#define HYSTERESE   1.0   // Abstand von Ein- und Ausschaltpunkt\r\n#define TEMP1_min  10.0   // Minimale Innentemperatur, bei der die Lüftung aktiviert wird\r\n#define TEMP2_min -10.0   // Minimale Außentemperatur, bei der die Lüftung aktiviert wird\r\n\r\nDHT dht1(DHTPIN_1, DHTTYPE_1);  //Der Innensensor wird ab jetzt mit dht1 angesprochen\r\nDHT dht2(DHTPIN_2, DHTTYPE_2);  //Der Außensensor wird ab jetzt mit dht2 angesprochen\r\n\r\nLiquidCrystal_I2C lcd(0x27,20,4); // LCD Display I2C Addresse und Displaygröße setzen\r\n\r\n//++++++++++++++++++++++++++++++++ Variablen für das Datenlogging ***************************************\r\n#define Headerzeile F(\"Datum|Zeit;Temperatur_S1;Feuchte_H1;Taupunkt_1;Temperatur_S2;Feuchte_H2;Taupunkt_2;Luefter_Ein/Aus;Laufzeit_Luefter;\")\r\n\r\n#define logFileName F(\"Luefter1.csv\")  // Name der Datei zum Abspeichern der Daten (Dateinamen-Format: 8.3)!!!!\r\n#define LogInterval 10                   // Wie oft werden die Messwerte aufgezeichnet ( 5 = alle 5 Minuten)\r\n\r\nbool logging = true;                    // Sollen die Daten überhaupt protokolliert werden?\r\nString LogData = \"\" ;                   // Variable zum Zusammensetzen des Logging-Strings.\r\nchar stamp[17];                         // Variable für den Zeitstempel.\r\nunsigned int LuefterStart = 0;          // Wann wurde der Lüfter eingeschaltet?\r\nunsigned int LuefterLaufzeit = 0;       // Wie lange lief der Lüfter?\r\nchar StrLuefterzeit[6];                 // Lüfterlaufzeit als String zur weiteren Verwendung.\r\nuint8_t Today = 0;                      // Das heutige Datum (nur Tag), zur Speicherung bei Tageswechsel.\r\nbool Tageswechsel=false;\r\n//********************************************************************************************************\r\n\r\nvoid setup() {\r\n  wdt_enable(WDTO_8S); // Watchdog timer auf 8 Sekunden stellen\r\n  \r\n  pinMode(RELAIPIN, OUTPUT);          // Relaispin als Output definieren\r\n  digitalWrite(RELAIPIN, RELAIS_AUS); // Relais ausschalten\r\n  \r\n  lcd.init();\r\n  lcd.backlight();\r\n  lcd.clear();\r\n\r\n   wdt_reset();  // Watchdog zurücksetzen\r\n   \r\n  //--------------------- Logging ------------------------------------------------------------------------------------  \r\n  if (logging == true)\r\n  { \r\n    lcd.setCursor(0,1);\r\n    lcd.print(Software_version);  // Welche Softwareversion läuft gerade\r\n    RTC_start();     // RTC-Modul testen. Wenn Fehler, dann kein Logging\r\n    delay (4000);    // Zeit um das Display zu lesen\r\n    lcd.clear(); \r\n     wdt_reset();  // Watchdog zurücksetzen\r\n    test_SD();       // SD-Karte suchen. Wenn nicht gefunden, dann kein Logging ausführen\r\n    Today = tm.Day ;\r\n    //------------------------------------------------ Neustart aufzeichnen -------------------------------------\r\n   if (logging == true) // kann sich ja geändert haben wenn Fehler bei RTC oder SD\r\n   {   \r\n    make_time_stamp();   \r\n    File logFile = SD.open(logFileName, FILE_WRITE);\r\n    logFile.print(stamp);\r\n    logFile.println(F(\": Neustart\"));                    // Damit festgehalten wird, wie oft die Steuerung neu gestartet ist\r\n    logFile.close();  \r\n   }\r\n  } //---------------------------------------------------------------------------------------------------------------------\r\n    \r\n  byte Grad[8] = {B00111,B00101,B00111,B0000,B00000,B00000,B00000,B00000};      // Sonderzeichen ° definieren\r\n  lcd.createChar(0, Grad);\r\n  byte Strich[8] = {B00100,B00100,B00100,B00100,B00100,B00100,B00100,B00100};   // Sonderzeichen senkrechter Strich definieren\r\n  lcd.createChar(1, Strich);\r\n       \r\n  dht1.begin(); // Sensoren starten\r\n  dht2.begin(); \r\n}\r\n\r\nvoid loop() {\r\n  RTC.read(tm);    \r\n  float h1 = dht1.readHumidity()+Korrektur_h_1;       // Innenluftfeuchtigkeit auslesen und unter „h1“ speichern\r\n  float t1 = dht1.readTemperature()+ Korrektur_t_1;   // Innentemperatur auslesen und unter „t1“ speichern\r\n  float h2 = dht2.readHumidity()+Korrektur_h_2;       // Außenluftfeuchtigkeit auslesen und unter „h2“ speichern\r\n  float t2 = dht2.readTemperature()+ Korrektur_t_2;   // Außentemperatur auslesen und unter „t2“ speichern\r\n     \r\n  if (fehler == true)  // Prüfen, ob gültige Werte von den Sensoren kommen\r\n  {   \r\n      lcd.clear();\r\n      lcd.setCursor(2,0);\r\n      lcd.print(F(\"Teste Sensoren..\"));\r\n    fehler = false; \r\n    if (isnan(h1) || isnan(t1) || h1 > 100 || h1 < 1 || t1 < -40 || t1 > 80 )  {\r\n      // Serial.println(F(\"Fehler beim Auslesen vom 1. Sensor!\"));\r\n      lcd.setCursor(0,1);\r\n      lcd.print(F(\"Fehler Sensor 1\"));\r\n      fehler = true;\r\n    }else {\r\n     lcd.setCursor(0,1);\r\n     lcd.print(F(\"Sensor 1 in Ordnung\"));\r\n   }\r\n  \r\n    delay(2000);  // Zeit um das Display zu lesen\r\n  \r\n      if (isnan(h2) || isnan(t2) || h2 > 100 || h2 < 1 || t2 < -40 || t2  > 80)  {\r\n       // Serial.println(F(\"Fehler beim Auslesen vom 2. Sensor!\"));\r\n        lcd.setCursor(0,2);\r\n        lcd.print(F(\"Fehler Sensor 2\"));\r\n        fehler = true;\r\n      } else {\r\n        lcd.setCursor(0,2);\r\n        lcd.print(F(\"Sensor 2 in Ordnung\"));\r\n     }\r\n\r\n    delay(2000);  // Zeit um das Display zu lesen\r\n  }\r\n  if (isnan(h1) || isnan(t1) || isnan(h2) || isnan(t2)) fehler = true;\r\n   \r\n if (fehler == true) {\r\n    digitalWrite(RELAIPIN, RELAIS_AUS); // Relais ausschalten \r\n    lcd.setCursor(0,3);\r\n    lcd.print(F(\"CPU Neustart.....\"));\r\n    while (1);  // Endlosschleife um das Display zu lesen und die CPU durch den Watchdog neu zu starten\r\n }\r\n wdt_reset();  // Watchdog zurücksetzen\r\n\r\n//**** Taupunkte errechnen********\r\nfloat Taupunkt_1 = taupunkt(t1, h1);\r\nfloat Taupunkt_2 = taupunkt(t2, h2);\r\n\r\n  // Werteausgabe auf dem I2C-Display\r\n  lcd.clear();\r\n  lcd.setCursor(0,0);\r\n  lcd.print(F(\"S1: \"));\r\n  lcd.print(t1); \r\n  lcd.write((uint8_t)0); // Sonderzeichen °C\r\n  lcd.write(('C'));\r\n  lcd.write((uint8_t)1); // Sonderzeichen |\r\n  lcd.print(h1);\r\n  lcd.print(F(\" %\"));\r\n\r\n  lcd.setCursor(0,1);\r\n  lcd.print(F(\"S2: \"));\r\n  lcd.print(t2); \r\n  lcd.write((uint8_t)0); // Sonderzeichen °C\r\n  lcd.write(('C'));\r\n  lcd.write((uint8_t)1); // Sonderzeichen |\r\n  lcd.print(h2);\r\n  lcd.print(F(\" %\"));\r\n\r\n  lcd.setCursor(0,2);\r\n  lcd.print(F(\"Taupunkt 1: \"));\r\n  lcd.print(Taupunkt_1); \r\n  lcd.write((uint8_t)0); // Sonderzeichen °C\r\n  lcd.write(('C'));\r\n\r\n  lcd.setCursor(0,3);\r\n  lcd.print(F(\"Taupunkt 2: \"));\r\n  lcd.print(Taupunkt_2); \r\n  lcd.write((uint8_t)0); // Sonderzeichen °C\r\n  lcd.write(('C'));\r\n \r\ndelay(5000); // Zeit um das Display zu lesen\r\nwdt_reset(); // Watchdog zurücksetzen\r\n\r\n  lcd.clear();\r\n  lcd.setCursor(0,0);\r\n  \r\nfloat DeltaTP = Taupunkt_1 - Taupunkt_2;\r\n\r\nif (DeltaTP > (SCHALTmin + HYSTERESE))rel = true;\r\nif (DeltaTP < (SCHALTmin))rel = false;\r\nif (t1 < TEMP1_min )rel = false;\r\nif (t2 < TEMP2_min )rel = false;\r\n\r\nif (rel == true)\r\n{  \r\n  digitalWrite(RELAIPIN, RELAIS_EIN); // Relais einschalten\r\n  lcd.print(F(\"Lueftung AN\"));  \r\n  if (LuefterStart <=0 && logging == true){ LuefterStart = tm.Hour*60 + tm.Minute;}\r\n} else {                             \r\n  digitalWrite(RELAIPIN, RELAIS_AUS); // Relais ausschalten\r\n  lcd.print(F(\"Lueftung AUS\"));\r\n  if( LuefterStart > 0 && logging == true){\r\n    LuefterLaufzeit += (tm.Hour*60 + tm.Minute - LuefterStart);\r\n    LuefterStart = 0;\r\n  }\r\n}   \r\n  lcd.setCursor(0,1);\r\n lcd.print(F(\"Delta TP: \"));\r\n lcd.print(DeltaTP);\r\n lcd.write((uint8_t)0); // Sonderzeichen °C\r\n lcd.write('C');\r\n \r\n make_time_stamp();\r\n lcd.setCursor(0,3);\r\n lcd.print(stamp);\r\n lcd.setCursor(0,2);\r\n if (logging == true)lcd.print(F(\"Logging AN\")); else lcd.print(F(\"Logging AUS\"));\r\n\r\n delay(4000);   // Wartezeit zwischen zwei Messungen\r\n wdt_reset();   // Watchdog zurücksetzen \r\n\r\n //--------------------------------------------logging-----------------------------------------------------\r\nif (logging == true)\r\n { \r\n  if  ( Today  != tm.Day)                                                     // Tageswechsel ==> Lüfterzeit abspeichern\r\n   {  \r\n      Tageswechsel = true;                                                    // ==> Sofort speichern (siehe SD.ino) ==> Nicht erst wenn LogIntervall abgelaufen ist\r\n       if (LuefterStart > 0 ) LuefterLaufzeit += (1440 - LuefterStart);       // ==>Lüfter läuft gerade\r\n       snprintf(StrLuefterzeit,sizeof(StrLuefterzeit),\"%d;\",LuefterLaufzeit); \r\n      Today = tm.Day;\r\n      LuefterLaufzeit = 0;\r\n    } else {\r\n      strcpy( StrLuefterzeit , \"0;\");    // Kein Tageswechsel, nur Platzhalter abspeichern\r\n   }\r\n\r\n  char buff[4];\r\n  LogData=\"\";\r\n  dtostrf(t1, 2, 1, buff); LogData += buff ; LogData += ';';\r\n  dtostrf(h1, 2, 1, buff); LogData += buff ; LogData += ';';\r\n  dtostrf(Taupunkt_1, 2, 1, buff); LogData += buff ; LogData += ';';\r\n  dtostrf(t2, 2, 1, buff); LogData += buff ; LogData += ';';\r\n  dtostrf(h2, 2, 1, buff); LogData += buff;LogData += ';';\r\n  dtostrf(Taupunkt_2, 2, 1, buff); LogData += buff;LogData += ';';\r\n  if (rel == true) LogData +=\"1;\"; else LogData += \"0;\";\r\n  LogData += StrLuefterzeit;\r\n  \r\n  save_to_SD(); // Daten auf die SD Karte speichern\r\n }\r\n}\r\n//--------------------------------------------------------------------------------------------------------\r\n\r\nfloat taupunkt(float t, float r) {\r\n  \r\nfloat a, b;\r\n  \r\n  if (t >= 0) {\r\n    a = 7.5;\r\n    b = 237.3;\r\n  } else if (t < 0) {\r\n    a = 7.6;\r\n    b = 240.7;\r\n  }\r\n  \r\n  // Sättigungsdampfdruck in hPa\r\n  float sdd = 6.1078 * pow(10, (a*t)/(b+t));\r\n  \r\n  // Dampfdruck in hPa\r\n  float dd = sdd * (r/100);\r\n  \r\n  // v-Parameter\r\n  float v = log10(dd/6.1078);\r\n  \r\n  // Taupunkttemperatur (°C)\r\n  float tt = (b*v) / (a-v);\r\n  return { tt };  \r\n}\r\n\r\n\r\n void software_Reset() // Startet das Programm neu, nicht aber die Sensoren oder das LCD \r\n  {\r\n    asm volatile (\"  jmp 0\");  \r\n  }\r\n"
  },
  {
    "path": "Taupunkt_Lueftung_Datenlogging_Version_2.13/Zeit.ino",
    "content": "//////////////////////////////////////////////////////////////////////////////\r\n// Das Taupunkt-Lüftungssystem \r\n// mit Datenlogging\r\n// \r\n// Routinen zur Erzeugung eines Zeitstempels\r\n// \r\n// veröffentlicht in der MAKE 2/2022\r\n//\r\n// Ulrich Schmerold\r\n// 3/2022\r\n//////////////////////////////////////////////////////////////////////////////\r\n\r\n\r\nvoid make_time_stamp ()\r\n{\r\n  RTC.read(tm);\r\n  snprintf(stamp,sizeof(stamp),\"%02d.%02d.%d %02d:%02d\",tm.Day,tm.Month,tmYearToCalendar(tm.Year),tm.Hour,tm.Minute);\r\n}\r\n\r\n\r\nbool RTC_start()\r\n{ \r\n if (logging == true)\r\n { \r\n  if (RTC.read(tm)) \r\n  {     \r\n    make_time_stamp ();\r\n    lcd.setCursor(2,0); \r\n    lcd.print(stamp);\r\n  } else {\r\n    if (RTC.chipPresent()) {\r\n      lcd.clear();\r\n      lcd.print(F(\"RTC hat keine Zeit\"));\r\n      delay(2000);\r\n    } else {\r\n      lcd.clear();\r\n      lcd.print(F(\"Kein Signal vom RTC\"));\r\n      delay(2000);\r\n    }\r\n    logging = false;\r\n     return(false);\r\n  }\r\n }\r\n}\r\n"
  }
]