Heizungstemperaturen plotten
Ziel dieses Projekts sollte es sein, mittels eines Raspberry Pi Computers die wichtigsten Temperaturen an meiner Heizung vollautomatisch, autark und permanent zu erfassen, zu strukturieren und grafisch für jeden Tag aufzuarbeiten. Nach diversen Heizungsproblemen in der Vergangenheit, hatte sich gezeigt, dass Datenplotts bei der Funktionsbeurteilung und Fehlersuche ein große Hilfe sein können. Auch können die Handwerker hiermit gut auf eventuelle Probleme aufmerksam gemacht werden, wodurch eine wesentlich zielgerichtetere Vorgehensweise möglich wird.
Was wird benötigt?
• Raspberry Pi B mit SD-Karte und BS
• USB WLAN-Stick
• Netzteil mit USB-Kabel
• Temperatursensoren
• Ein C-Programm zur Verarbeitung und Protokollierung der Sensordaten
• Ein Bash-Skript zur Ansteuerung von Gnuplot
Der Raspberry Pi
Für dieses Projekt habe ich einen Raspberry Pi Typ B eingesetzt. Als Betriebssystem kommt Raspbian zum Einsatz.
USB WLAN-Stick
Hier hat mich diese Webseite inspiriert. Der Rapi hängt also mit einem EDIMAX EW-7811UN im lokalen WLAN.
Die Temperatursensoren
Nach meinem Arduinoprojekt habe ich mich wieder für die Maxim DS18B20 Temperatursensoren entschieden. Der Rapi und sein BS sind hierfür schon bestens aufgestellt. Es müssen nur noch die Module w1-gpio und w1-therm nachgeladen werden.
Damit das nicht nach jedem Reboot fällig wird, habe ich sie in die modules Datei (/etc/modules) eintragen. Nun werden die Module bei jedem Reboot automatisch mit geladen.
Eine große Hilfe war mir bei diesem Projekt das Tutorial hier in diesem Forum.
Wie noch zu sehen sein wird, gibt es in diesem Projekt fünf Sensoren. Das ist einzig darauf zurück zu führen, dass ich bei ebay ein Angebot mit fünf Sensoren erstanden habe. Diese Sensoren hatten jeweils ein 3 Meter langes Anschlusskabel und einen fertig konfektionierten Stecker.
Das C-Programm
Das C-Programm sammelt kontinuierlich die Daten ein und schreibt diese in eine ASCII-Plotdatei.
/*
* tempsens_13.c
*
* Heizung: Auswertung der Temperatursensoren
*
* Kompilierung mit: gcc -Wall -O3 -o Heiztemp tempsens_13.c
*
* Aufruf: ./Heiztemp & disown
*
* 14.09.2013 11:19
*
*/
#define _GNU_SOURCE // wird von getline() benötigt
#define ENTWICKLUNG // für die bedingte Kompilierung
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
int main(void) {
// Dateistrukturen (Streams) anlegen
FILE *w1_slave_dat; // für die auszulesenden Sensordaten
#ifndef ENTWICKLUNG
FILE *log_dat; // für die Datenprotokollierung
#endif
// Zugriffspfade der einzelnen Sensoren
char *vl_hk =
"/sys/bus/w1/devices/w1_bus_master1/28-000004bbeac6/w1_slave"; // VL_HK
char *rl_hk =
"/sys/bus/w1/devices/w1_bus_master1/28-000004bb38c2/w1_slave"; // RL_HK
char *vl_ww =
"/sys/bus/w1/devices/w1_bus_master1/28-000004bb6547/w1_slave"; // vl_ww
char *rl_ww =
"/sys/bus/w1/devices/w1_bus_master1/28-000004bbf459/w1_slave"; // rl_ww
char *vl_so =
"/sys/bus/w1/devices/w1_bus_master1/28-000004b86ce8/w1_slave"; // vl_so
// Anzeigetexte
char *text_vlhk = "VL Heizkreis:\t\t %.2f Grad\n";
char *text_rlhk = "RL Heizkreis:\t\t %.2f Grad\n";
char *text_vlww = "VL Warmwasserspeicher:\t %.2f Grad\n";
char *text_rlww = "RL Warmwasserspeicher:\t %.2f Grad\n";
char *text_vlso = "VL Solarkollektor:\t %.2f Grad\n";
// für die Zeit
struct tm *zeit; // Zeitpunktsbeschreibung
time_t sekunde; // Zeitstempel in Sekunden
// ab 1.1.1970, 0:00 Uhr
char zeit_string[80]; // der fertige Zeitstempel
char datei_name[20]; // Name der Protokolldatei
// allgemeine Variablen
char *tmp_wort; // temporärer Zwischenspeicher
double temp_vl_so, temp_vl_hk, temp_rl_hk, temp_rl_ww, temp_vl_ww;
double last_vl_so, last_vl_hk, last_rl_hk, last_rl_ww, last_vl_ww;
// Speicher für die einzulesenden Dateizeilen
size_t *t = malloc(0); //
char **gptr = malloc(sizeof(char*)); //
*gptr = NULL; // NULL-Zeiger
/* die tatsächliche Größe wird von der Funktion getline() ermittelt.
* Wird *gptr vor dem Aufruf mit einem NULL-Zeiger initialisiert
* und *t auf 0 gesetzt, übernimmt getline() die Bereitstellung des
* Speichers für die Zeile vollkommen selbstständig
*/
// allgemeine Funktionen
void daten_holen( char *pfad, //
double *temperatur, //
double *last_temp, //
char *anz_text){
// Datei zum lesen der Temperatur öffnen
if ( (w1_slave_dat = fopen(pfad,"r")) == NULL) {
fprintf(stderr, "\nKonnte Datei %s nicht öffnen!\n", pfad);
*temperatur = 0.0; // Wert zu 0 setzen
}
else {
getline(gptr, t, w1_slave_dat); // 1. Zeile auslesen, ignor
getline(gptr, t, w1_slave_dat); // 2. Zeile auslesen
fclose( w1_slave_dat ); // Datei wieder schließen
// den 2. String (der die Temperatur enthält) zerlegen
tmp_wort = strtok(*gptr, "="); // der vordere Teil
tmp_wort = strtok(NULL, "="); // die Temperatur
// fehlertolerante Umwandlung
*temperatur = atof(tmp_wort)/1000.0; // wandeln + umrechnen
if ( *temperatur > 1.0 && *temperatur < 100.0 ) {
*last_temp = *temperatur; // merken für Fehlerkor.
}
else { // sonst: Messfehler!!!
*temperatur = *last_temp; // Fehlerkorrektur mit
} // letztem Wert
// bedingte Kompilierung
#ifdef ENTWICKLUNG
printf(anz_text, *temperatur);
#endif
}
// Funktionsende
}
// bedingte Kompilierung
#ifndef ENTWICKLUNG
while(1) { // Dauerschleife nur im produktivem Programm
#endif
// Daten holen und bearbeiten
daten_holen(vl_hk, &temp_vl_hk, &last_vl_hk,
text_vlhk); // VL-Temperatur Heizkreis
daten_holen(rl_hk, &temp_rl_hk, &last_rl_hk,
text_rlhk); // RL-Temperatur Heizkreis
daten_holen(vl_ww, &temp_vl_ww, &last_vl_ww,
text_vlww); // VL-Temperatur WWS
daten_holen(rl_ww, &temp_rl_ww, &last_rl_ww,
text_rlww); // RL-Temperatur WWS
daten_holen(vl_so, &temp_vl_so, &last_vl_so,
text_vlso); // VL-Temperatur Solarkoll.
/* Beispiel für das Zeitformat aus dem arduino-Projekt:
* 17.7.2013-19:45:59
* Beispiel für eine komplette Protokollzeile (3 Sensoren):
* 17.7.2013-19:45:59 23.37 21.12 20.62
*/
// Zeit und Datum ermitteln und Stringvariablen bilden
time(&sekunde); // Die aktuelle Zeit bilden
zeit = localtime(&sekunde); // Konvertiert time_t in eine
// tm Struktur als lokale Zeit.
// Zeitstempel:
strftime(zeit_string, // Formatiert eine tm Struktur
80, // in einen string
"%d.%m.%Y-%H:%M:%S", // Formatvorgabe
zeit); // die tm Struktur
// Name der Protokolldatei:
strftime(datei_name, // Formatiert eine tm Struktur
20, // in einen string
"%Y-%m-%d.dat", // Formatvorgabe
zeit); // die tm Struktur
// Daten protokollieren
#ifndef ENTWICKLUNG
if ( (log_dat = fopen(datei_name, "a")) != NULL) {
fprintf( log_dat, "%s %.2f %.2f %.2f %.2f %.2f\n",
zeit_string, // Zeitstempel
temp_vl_hk, // VL-Heizkreis
temp_rl_hk, // RL-Heizkreis
temp_vl_ww, // VL-Warmwasserspeicher
temp_rl_ww, // RL-Warmwasserspeicher
temp_vl_so); // VL-Solarkollektor
fclose( log_dat ); // Datei wieder schließen
}
sleep(60);
// Ende der Dauerschleife
}
#else
printf( "%s %.2f %.2f %.2f %.2f %.2f\n",
zeit_string, // Zeitstempel
temp_vl_hk, // VL-Heizkreis
temp_rl_hk, // RL-Heizkreis
temp_vl_ww, // VL-Warmwasserspeicher
temp_rl_ww, // RL-Warmwasserspeicher
temp_vl_so); // VL-Solarkollektor
#endif
// Programmende
return EXIT_SUCCESS;
}
Alles anzeigen
Fehlertoleranz
Bei Auswertung der ersten Plotdaten musste ich leider feststellen, dass die Sensoren immer wieder Messfehler mit dem Wert -0,06 abliefern. Auch war mal ein Ausreißer nach oben ( > 200 Grad) dabei. Zunächst habe ich diese Fehler händisch mit einem Texteditor korrigiert. Das war natürlich keine Lösung. Ich habe daher das C-Programm um eine Fehlerabfangfunktion erweitert. Wenn ein Messwert kleiner 1 oder größer 100 ist, wird er verworfen und statt dessen der vorhergehende Wert erneut werwendet. Das funktioniert seit dem wunderbar und fällt bei einem Messraster von einer Minute auch überhaupt nicht auf.
Das Bash-Skript
Das folgende Bash-Skript übergibt die zu bearbeitenden Daten an Gnuplot und passt ein paar Einstellungen jeweils an. Bearbeitete Protokolldatendateien werden umbenannt um eine erneute Bearbeitung am Folgetag zu unterbinden. Über Cron wird dieses Skript einmal pro Tag aufgerufen.
#!/bin/bash
#-- Die Tagesgrafik erstellen
#-- Version: 15.09.2013
# in das entsprechende Verzeichnis wechseln
# ganz wichtig für die crontab-Bearbeitung !!!
# crontab:
# 44 4 * * * /pfad/zu/der/datei/do_grafik.sh > /dev/null 2>&1
cd /pfad/zu/der/datei/
# aktuelle Plotdatei
DATEI_HEUTE=$(date +%Y-%m-%d.dat)
#echo "aktuelle Datei = "$DATEI_HEUTE # TEST ======
for INPUT in *.dat
do
#echo $INPUT # Ausgabe TEST ======
# bedingte Ausführung
# die aktuelle Plotdatei nicht mit gnuplot bearbeiten
# nur die der Vortage
if [ "$INPUT" != "$DATEI_HEUTE" ]
then
# Titel der Grafik
TAG=$(echo $INPUT | cut -c9-10)
MONAT=$(echo $INPUT | cut -c6-7)
TITEL="Tageskurve "$TAG"-"$MONAT"-" # den Titel bilden
#echo $TITEL # TEST ======
# Grafik mit gnuplot erzeugen
gnuplot <<PLOT
# =========
set encoding iso_8859_1 # Umlaute ermöglichen
set decimalsign '.' # Kommazeichen spezifizieren
set grid lt 0 lw 1 lc rgb "#1E90FF" # Gitter zeichnen (Farbdruck)
#set grid lt 0 lw 1 lc rgb "black" # Gitter zeichnen (SW-Druck)
set terminal pdf enhanced color solid font "Tahoma, 10" \
size 29cm,20cm # DIN A 4 quer (29,7 * 21)
# ========= Legende positionieren
set key left
# ========= X-Achse für Datum/Uhrzeit vorbereiten
set timefmt "%d.%m.%Y-%H:%M:%S" # Datenformat in der Tabelle
set xdata time # die X-Achse hat die Zeit
#set xrange ["28.04.2013-16:30:00":"28.04.2013-23:59:00"]
# ========= Y-Achsen vorbereiten
#set format y "%.0f" # keine Nachkommastellen, normale Darstellung
#set format y2 "%.0f" # keine Nachkommastellen, normale Darstellung
#set ytics
#set y2tics
set ylabel "Temperatur (Grad C)" # Y-Achse beschriften
# ========= Variablen deklarieren
# INPUT = "2013-09-05.dat"
LINE1 = "VL-HK"
LINE2 = "RL-HK"
LINE3 = "VL-BW"
LINE4 = "RL-BW"
LINE5 = "VL-Sol"
JAHR = "2013"
# TITEL = "Tageskurve 05-09-"
# ========= wichtige Einstellungen setzen
set xtics 7200 # jede Stunde (3600)
set xlabel "Uhrzeit" # X-Achse beschriften
set format x "%H" # Texte der X-Achse formatieren
set ytics 2
set yrange [10:90]
# ========= die Daten plotten
set terminal pdf monochrome enhanced dashed # monochrome color
set title "$TITEL"."".JAHR # Titel setzen
set output "$TITEL"."".JAHR.".pdf" # Datei benennen
# smooth {unique | frequency | csplines | acsplines | bezier | sbezier}
plot "$INPUT" using 1:2 title LINE1 axis x1y1 smooth unique with lines \
linetype 1 linewidth 3 linecolor 10, \
"$INPUT" using 1:3 title LINE2 axis x1y1 smooth unique with lines \
linetype 1 linewidth 3 linecolor 12, \
"$INPUT" using 1:4 title LINE3 axis x1y1 smooth unique with lines \
linetype 1 linewidth 3 linecolor 13, \
"$INPUT" using 1:5 title LINE4 axis x1y1 smooth unique with lines \
linetype 1 linewidth 3 linecolor 14, \
"$INPUT" using 1:6 title LINE5 axis x1y1 smooth unique with lines \
linetype 1 linewidth 3 linecolor 11
# ========= Ende
quit
PLOT
# keine 2-malige Bearbeitung der Daten. Daher:
INPUT_NEU=${INPUT%???}txt # neuer Dateiname
#echo $INPUT_NEU # Ausgabe TEST ======
mv $INPUT $INPUT_NEU # bearbeitete Datendatei umbenennen
fi # Ende bedingte Ausführung
done
exit 0
Alles anzeigen
Ein Beispielplot