Inzwischen ist auch der Fenstermotor eingebaut und wir können nicht nur Erfolge verkünden, sondern auch den Blog schließen. Denn die Aufgabe die Automatisierung einer Keller-Lüftung mittels Raspberry PI, Temperatur- und Feuchtesensoren, Relais und Ventilatoren (und nun sogar Fenstermotoren) umzusetzen, ist abgeschlossen und zwar mit einem wirklich guten Ergebnis.
Aber in diesem letzten Kapitel kommen doch noch einige Änderungen auf euch zu. Ich habe sowohl das Haupt-Skript überarbeitet, um etwas mehr Einstellmöglichkeiten zu erhalten, habe es auch noch verschoben und einige Bezeichner geändert, damit es leichter zu pflegen ist…. als auch den Ventilator und der Fenstermotor auf je einem Relais in Betrieb genommen, daher wurde die Relaisansteuerung um eine weitere Transistorschaltung erweitert und natürlich auch der Fenstermotor selbst in Betrieb genommen. Insofern hat sich doch noch einiges getan.
Wer am Anfang dieses Projekts steht, sollte daher auch am Anfang dieses inzwischen umfangreichen Blogs beginnen und sich bis hierher durcharbeiten. Das mag anstrengend werden, aber ihr bekommt eine fertige Lösung, die ihr nur noch auf eure Gegebenheiten anpassen müsst. Ihr könnt sie sogar skalieren – ob ihr nun unbedingt CACTI nutzen wollt, also eine graphische Oberfläche für die Visualisierung, oder ob euch das einfache automatische Schalten reicht, liegt ganz bei euch. Das Skript läuft ebenso gut wenn CACTI nicht installiert ist.
Nun aber weiter für die, die sich bis hierher durchgearbeitet haben oder gar den gesamten Weg live begleiteten:
Ich beginne mit einem wichtigen Tipp: Ich habe mein Skript aufgemotzt und dabei so viele Dinge verändert, dass ein intensives Debugging notwendig war. Da wir aber nicht mit einem Compiler arbeiten, können wir leider keine Schritt-für-Schritt Abarbeitung starten und uns dabei unsere Variablen angucken. Das macht die Sache deutlich komplizierter, wenn es um Fehlersuche geht aber auch dann, wenn wir einfach nur die Funktionstüchtigkeit aller Wege prüfen wollen.
Aber immerhin kann die Bash etwas unterstützen. Wenn ihr das Skript wie folgt startet:
bash -x SKRIPTNAME.SH
werden sowohl die Code-Zeilen als auch die Rückmeldungen mit angezeigt, was ungemein hilft. Bei langen Skripten wird es aber unübersichtlich, daher kann auch mitten im Skript ein Bereich markiert werden:
#GUTER CODE
set -x
#DEBUG-CODE
set +x
#GUTER CODE
Das Skript kann dann ohne -x Parameter gestartet werden und wird in dem markierten Bereich dennoch genauso behandelt. Sehr hilfreich!
Bevor wir uns dem finalen Skript zuwenden, hier noch die letzten Schritte:
Ich habe inzwischen beide Relais des Relais-Moduls an der Raspberry hängen. Daher muss ich im Skript auch immer 2 GPIOs nun schalten.
Mein Relais-Modul schaltet über GND und ich habe als Sicherheit eine Transistorvorstufe vorgeschaltet – hier nun die einfache Beschaltung:
Wie in dem vorherigen Kapiteln erwähnt, müssen es nicht genau 1KOhm sein. Ich nutze 2KOhm, das ist auch okay. Eine typische Größe für einen Basiswiderstand, wenn der Transistor voll durchschalten soll. Wir hängen unsere Relais-Eingänge an die Kollektoren der bipolaren NPN-Transistoren. Laut Spezifikation ist das Relais sowohl bei 5V als auch bei High-Z „non energized“ und bei GND dann „energized“. D.h. wenn wir unseren GPIO auf 3.3V setzen, ziehen wir das Relais auf MASSE und schalten es. Liegt der GPIO auf 0V, ist der Transistor geschlossen und das Relais hängt in der Luft und schaltet in seine Ruheposition.
An den Relais hängt sowohl mein Ventilator für die Abluft als inzwischen auch mein Fenstermotor für die Zuluft. Ich habe den Luftstrom gemessen und ohne Zuluftsteuerung war kein Erfolg zu verzeichnen.
Das ist ein Keller von 1890 – die sehen leider so schäbig aus 🙂
Über dem Fenster ist der Stellmotor, den ich nicht exakt so anbauen konnte, wie es gedacht ist, weil mir dort über der Laibung der Platz fehlt. Rechts sieht man den Lüfter, den ich in eine Wetterfeste Bauplatte gesetzt habe, die mein altes Fenster überdeckt. Beides ist inzwischen zusätzlich über Fenstergitter gesichert – aber ich habe noch Videos ohne, da kann man die Funktionsweise gut sehen:
Fenster-Steuerung
Ventilator-Ansteuerung
Das ist die Infrastruktur. Wir haben nun einen idealen Luftstrom. Mit Testmessungen bekomme ich bei sehr kurzer Lüftungsdauer, z.B. 5min, direkte Rückmeldungen auf dem Feuchtemesser, während ohne Zuluftsteuerung selbst nach 45min keine signifikante Veränderung eintrat.
Eigentlich wollte ich noch ein Video von der Strömungsmessung mit dem Dräger Flow-Check machen – vielleicht reiche ich das noch nach.
In den vorherigen Kapiteln hatte ich die notwendigen Informationen hierzu schon gegeben, z.B. die Initialisierung der GPIOs als Ausgänge und die Verwendung der Wiring PI, die wir vorher nur indirekt genutzt haben für das Auslesen der DHTs.
Ich habe übrigens aus verschiedenen Gründen zwei getrennte Relais verwendet. Einerseits bietet das die Möglichkeit, bei Bedarf Lüftung und Fenster getrennt zu steuern, was ich aber noch nicht tue.
Auf der anderen Seite entkoppelt es die beiden aber. Die Fenstersteuerung benötigt immer eine Phase – entweder auf Eingang 1 oder auf Eingang 2. Wenn nur ein Relais verwendet würde, wäre der Eingang in „Fenster zu Position“ ohne Phase dennoch zumindest mit dem Ventilator verbunden. Das kann zu unerwünschten Nebeneffekten führen – daher trennt man das besser galvanisch.
Wie erwähnt habe ich nun auch noch das Haupt-Skript überarbeitet. Ich habe die Variablen eindeutiger benannt, aber vor allem habe ich ein paar zusätzliche Optionen eingebaut. Es gibt nun mehrere Abbruchmöglichkeiten einer Lüftung:
- Die Lüftungsempfehlung aufgrund der Messung der Temperatur und Luftfeuchtigkeit wird zurück genommen (Standard)
- Die Lüftung dauert lange an, bis ein time out erreicht ist (on_time_session)
- An einem Tag wurde sehr lange gelüftet, auch in mehreren Etappen und so ist ein anderer time out erreicht (on_time_day)
- Eine Lüftung zeigt keine Wirkung (lueften_pass_fail)
Zu 4: Hier kann natürlich beliebig viel Intelligenz eingebaut werden. Man könnte die Differenzialrechnung bemühen und die erste Ableitung bilden. Man könnte Durchschnittwerte über mehrere Messungen bilden. Man kann versuchen eine Reihe zu bilden oder eine Funktion zu beschreiben, um eine Vorhersage der Entwicklung zu treffen usw. (wobei anzumerken ist, dass die Bash-Onboard-Tools für Rechnenoperationen sehr beschränkt sind).
Im ersten Schritt habe ich hier nur einen Vergleich des aktuellen Werts mit dem Wert davor vorgenommen und einen zusätzlichen Abgleich mit dem Startwert. Verbessert sich die Situation nicht, wird ein Zähler erhöht. Nach einer Zahl X wird das Lüften abgebrochen, da es offensichtlich nichts bringt.
Ein anderes Thema ist das Starten einer Lüftung. Auch hier habe ich eine Änderung vorgenommen: Um bei Fehlmessungen nicht gleich eine Lüftung zu starten, müssen zwei Lüftungsempfehlungen nacheinander gegeben werden.
Für die Realisierung dieser Funktionen musste ich etwas mehr Daten ablegen und wieder einlesen, da ich die Historie benötige.
Bewusst habe ich die CACTI-Seite aber so gelassen wie bisher, nur dass ich nicht die Schaltungsempfehlung übergebe, sondern wirklich nur den Zustand ON/OFF.
Dazu kommen weitere kleinere Verbesserungen. Daher nun zum Abschluß dieses Blogs der komplette Code des Hauptsskripts – alles andere findet ihr in den Kapiteln zuvor.
Für mich war das ein spannendes Projekt und ich habe selbst auch wieder einiges lernen können und nebenbei bin ich inzwischen überzeugt, dass es auch für den Keller eine sinnvolle Investition ist.
Falls ich Fehler finde, werde ich das Skript in diesem Blog hier ersetzen, ansonsten bin ich erstmal raus und wünsche euch viel Spaß mit diesem Projekt.
PS: Als Nerd-Ausblick kann ich noch ein Philips HUE Projekt mit der Raspberry ankündigen, was aber deutlich schlanker wird als dieser Blog hier. Es wird nur um eine Urlaubssteuerung gehen…
Nun noch der Code:
#!/bin/bash
#Kellerlüftung mittels Raspberry PI, DHT22/21 Sensoren, Relais für Ventilator und Fenstermotor
#Copyright Malte Berndt 2017
#Ab welcher Feuchtigkeit im Keller soll nicht mehr gelüftet werden / untere Grenze?
MIN_FEUCHTE=55
#Beide DHT22 auslesen und als Variablen ablegen
Sensor_Innen=$(/home/pi/lol_dht22/loldht 0)
Sensor_Aussen=$(/home/pi/lol_dht22/loldht 2)
#In Einzelwerte aufteilen
Sensor_Innen_HUM=$(echo $Sensor_Innen | awk '{printf $1}')
Sensor_Innen_TEM=$(echo $Sensor_Innen | awk '{printf $2}')
Sensor_Aussen_HUM=$(echo $Sensor_Aussen | awk '{printf $1}')
Sensor_Aussen_TEM=$(echo $Sensor_Aussen | awk '{printf $2}')
#Näherungsformel Außen-Luftfeuchtigkeit-3*(Innentemperatur-Außentemperatur)=R_InnenLuftfeuchtigkeit
R_InnenFeuchte=$(echo "$Sensor_Aussen_HUM-3*($Sensor_Innen_TEM-($Sensor_Aussen_TEM))" | bc -l)
#Schaltempfehlung JA/NEIN 1/0
#Lüftungsmepfehlung wenn min. 10% Abstand vorliegen und die Minimalfeuchtigkeit noch nicht erreicht ist
#Vorher auf INT wechseln, damit wir nicht mit expr etc. arbeiten müssen. Also Mal 10...
R_InnenFeuchte_10=$(echo "$R_InnenFeuchte*10" | bc)
R_InnenFeuchte_10=${R_InnenFeuchte_10%.*}
Sensor_Innen_HUM_10=$(echo "$Sensor_Innen_HUM*10" | bc)
Sensor_Innen_HUM_10=${Sensor_Innen_HUM_10%.*}
SCHALT_HUM_10=$(echo "$Sensor_Innen_HUM_10-100" | bc -l)
MIN_FEUCHTE_10=$(echo "$MIN_FEUCHTE*10" | bc)
if [ $R_InnenFeuchte_10 -le $SCHALT_HUM_10 -a $Sensor_Innen_HUM_10 -gt $MIN_FEUCHTE_10 ]; then
SCHALTEN=1
else
SCHALTEN=0
fi
#Prüfen ob Historie vorhanden sonst Werte auf 0
if [ ! -f "/home/pi/lueften_historie.txt" ]; then
on_off=0
on_time_session=0
on_time_day=0
on_off_count=0
start_wert_lueften=0
vorher_wert_lueften=0
lueften_pass_fail=0
break=0
counter=0
else
#Historie einlesen
lueften_historie=$(head /home/pi/lueften_historie.txt)
on_off=$(echo $lueften_historie | awk '{printf $1}')
on_time_session=$(echo $lueften_historie | awk '{printf $2}')
on_time_day=$(echo $lueften_historie | awk '{printf $3}')
on_off_count=$(echo $lueften_historie | awk '{printf $4}')
start_wert_lueften=$(echo $lueften_historie | awk '{printf $5}')
vorher_wert_lueften=$(echo $lueften_historie | awk '{printf $6}')
lueften_pass_fail=$(echo $lueften_historie | awk '{printf $7}')
break=$(echo $lueften_historie | awk '{printf $8}')
counter=$(echo $lueften_historie | awk '{printf $9}')
fi
#wir schalten erst bei 2x Empfehlung für Lüften in Folge, daher zählen
if [ $SCHALTEN -eq 1 ]; then
on_off_count=$(($on_off_count+1))
fi
#falls es eine Empfehlung gab, aber diese nun zurückgenommen wurde, setzen wir auch den Zähler zurück
if [ $SCHALTEN -eq 0 -a $on_off_count -ne 0 ]; then
on_off_count=0
fi
# Wenn bereits gelüftet wird...
if [ $on_off -eq 1 ]; then
on_time_session=$(($on_time_session+5))
on_time_day=$(($on_time_day+5))
#Wir prüfen ob lüften erfolgreich ist durch Vergleich mit Start- und Vorher-Wert
if [ $Sensor_Innen_HUM -ge $vorher_wert_lueften -a $Sensor_Innen_HUM -ge $start_wert_lueften ]; then
lueften_pass_fail=$(($lueften_pass_fail+1))
fi
#wenn lueften nicht erfolgreich oder Zeiten übeschritten, Pause setzen und lüften stoppen
if [ $lueften_pass_fail -gt 3 -o $on_time_session -gt 30 ]; then
break=45
/usr/local/bin/gpio -g write 14 0
/usr/local/bin/gpio -g write 15 0
on_off=0
on_off_count=0
EMAILTEXT=$(printf "Erfolgloses Lüften: %s Mal\nOn_Time_Session: %smin\nLüften gestoppt!\n" "$lueften_pass_fail" "$on_time_session")
/home/pi/skripte/mail.sh "Lüften des Kellers gestoppt" "$EMAILTEXT"
fi
#wir nehmen den neuen Raum-Feuchte-Wert mit in die Historie
vorher_wert_lueften=$Sensor_Innen_HUM
fi
# Falls on_off_count >1 (also ab 2) und noch nicht gelüftet wird und break nicht gesetzt ist, LÜFTEN.
if [ $on_off_count -gt 1 -a $on_off -eq 0 -a $break -eq 0 ]; then
EMAILTEXT=$(printf "Luftfeuchtigkeit Keller: %s%%\nLuftfeuchtigkeit durch Lüftung: %s%%\nLüften gestartet!\n" "$Sensor_Innen_HUM" "$R_InnenFeuchte")
/home/pi/skripte/mail.sh "Lüften des Kellers gestartet" "$EMAILTEXT"
/usr/local/bin/gpio -g write 14 1
/usr/local/bin/gpio -g write 15 1
on_off=1
on_time_session=0
start_wert_lueften=$Sensor_Innen_HUM
vorher_wert_lueften=0
lueften_pass_fail=0
fi
#Bei Schaltempfehlung 0 bei aktiver Lüftung, LÜFTEN stoppen
if [ $SCHALTEN -eq 0 -a $on_off -eq 1 ]; then
EMAILTEXT=$(printf "Luftfeuchtigkeit Keller: %s%%\nLuftfeuchtigkeit durch Lüftung: %s%%\nLüften gestoppt!\n" "$Sensor_Innen_HUM" "$R_InnenFeuchte")
/home/pi/skripte/mail.sh "Lüften des Kellers gestoppt" "$EMAILTEXT"
/usr/local/bin/gpio -g write 14 0
/usr/local/bin/gpio -g write 15 0
on_off=0
on_off_count=0
fi
#falls break gesetzt, 5min reduzieren
if [ $break -gt 0 ]; then
break=$(($break-5))
fi
#falls Tageslüftung überschritten, break setzen bzw. wieder setzen
if [ $on_time_day -gt 180 ]; then
break=5
fi
#counter erhöhen oder rücksetzen wenn 24h (288*5min) um sind
if [ $counter -lt 288 ]; then
counter=$(($counter+1))
else
counter=0
on_time_day=0
fi
#Debug-Vergleich: GPIO ist gesetzt aber on_off ist aus... Fehlermeldung per Mail
PIN14=$(/usr/local/bin/gpio -g read 14)
PIN15=$(/usr/local/bin/gpio -g read 15)
if [ $PIN14 -eq 1 -a $on_off -eq 0 ]; then
/home/pi/skripte/mail.sh "Fehlerbericht" "PIN14 aktiv während on_off auf 0"
fi
if [ $PIN15 -eq 1 -a $on_off -eq 0 ]; then
/home/pi/skripte/mail.sh "Fehlerbericht" "PIN14 aktiv während on_off auf 0"
fi
#-------------EXPORT in DATEIEN---------------
#Debug-CSV erstellen
#CSV mit Header erstellen, falls nicht vorhanden
if [ ! -f "/home/pi/keller_lueftung_log.csv" ]; then
printf "Date;Time;Humidity_Sensor_Innen;Temperature_Sensor_Innen;Humidity_Sensor_Aussen;Calc_Humidity;Temperature_Sensor_Aussen;on_off;on_time_session;on_time_day;on_off_count;start_wert_lueften;vorher_wert_lueften;lueften_pass_fail;break;counter\n" > /home/pi/keller_lueftung_log.csv
fi
#Datum und Zeit ablegen und in CSV Datei anfügen
date=$(date -I)
time=$(date +%T)
printf "%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s\n" "$date" "$time" "$Sensor_Innen_HUM" "$Sensor_Innen_TEM" "$Sensor_Aussen_HUM" "$Sensor_Aussen_TEM" "$R_InnenFeuchte" "$on_off" "$on_time_session" "$on_time_day" "$on_off_count" "$start_wert_lueften" "$vorher_wert_lueften" "$lueften_pass_fail" "$break" "$counter">> /home/pi/keller_lueftung_log.csv
#Ausgabe für CACTI in Datei
#Für CACTI on_off auf 0 und 100 skalieren
cacti_on_off=$(($on_off*100))
echo $Sensor_Innen | awk '{printf "HUM1:" $1 " TEM1:" $2" "}' > /home/pi/cactiwerte.txt
echo $Sensor_Aussen | awk '{printf "HUM2:" $1 " TEM2:" $2" "}' >> /home/pi/cactiwerte.txt
printf "SCHALT:"$cacti_on_off" " >> /home/pi/cactiwerte.txt
printf "RHUM:"$R_InnenFeuchte"\n" >> /home/pi/cactiwerte.txt
#Schalthistorie in Datei schreiben
printf "%s %s %s %s %s %s %s %s %s" "$on_off" "$on_time_session" "$on_time_day" "$on_off_count" "$start_wert_lueften" "$vorher_wert_lueften" "$lueften_pass_fail" "$break" "$counter"> /home/pi/lueften_historie.txt