WLAN-Thermometer mit Himbeergerät

Aus Fingers Wiki
Wechseln zu: Navigation, Suche
Screenshot der Browser-Ausgabe. Die Aufzeichnung läuft erst seit vier Tagen, also gibt's noch keine Langzeitdaten.

Inhaltsverzeichnis

Einleitung

Jeder kennt diese Funk-Thermometer. Manche funktionieren einwandfrei und manche eben nicht (merkwürdigerweise ist die Zuverlässigkeit bei uns antiproportional zum Preis der Geräte). In unserem Haus gehörten zwei von drei zur letzten Sorte, sodass meine Eltern mir regelmäßig ihr Leid über nicht funktionierende Funkthermometer klagten. Das kann so nicht bleiben, und weil die Herrschaften mittlerweile beide ein Smartphone haben, ich in I2C einsteigen wollte und vier Himbeergeräte mit Netzteil und SD-Karte rumlagen, wurde kurzerhand ein 5er Pack LM75-Sensoren aus China geordert.

Ziel: Außentemperatur überall im Haus per PC und Smartphone abrufbar


Update 22.02.2018: heute hat das Thermometer ein Betriebssystem-Update auf das aktuelle Debian Stretch bekommen. Ich habe den Artikel entsprechend überarbeitet.

Hardware

Schaltplan
Außensensor, fachmännisch in Heißkleber ersoffen. Der 100nF-Kondensator schimmert durch.

Der LM75 (speziell: LM75A) ist ein I2C-Temperatursensor, der bereits in der "Grundkonfiguration" für diesen Zweck benutzbar ist.

Eckdaten:

  • Temperaturbereich von -55°C bis 125°C
  • 11 bit Auflösung, das entspricht 0,125°C
  • Toleranz +/-2 K (-25 - 100°C, laut NXP Datenblatt)
  • Betriebsspannung: 2,8V-5,5V
  • Adressbereich von 0x48 bis 0x55 (max. 8 Sensoren an einem Bus)


Adressierung:

I2C nutzt theoretisch einen Adressraum von sieben bit, wobei die ersten vier Adressbits (die vier Höchsten) im LM75 bereits hart auf 1001 verdrahtet sind. Man kann nur die letzten drei Bits über die herausgeführten Pins A0-A2 beeinflussen, womit sich eine Slave-Adresse zwischen einschließlich 0x48 und 0x55 realisieren lässt. In diesem Projekt habe ich alle drei Pins auf Masse gelegt, um gleich die erste verfügbare Adresse 0x48 zu verwenden.


Anschluss ans Himbeergerät:

Aufgrund der variablen Betriebsspannung des LM75 hätte man ihn auch an die 3,3V des Raspberry Pi anschließen können. Das von mir verwendete Tutorial (Link Nr. 1) verwendete jedoch die 5V-Versorgung und als ich das gesehen hab, war er schon angeschlossen und hat ohne Probleme funktioniert. Dem I2C-Bus macht das ohnehin nichts aus, da die Busspannung (3,3V) vom Raspberry Pi über eingebaute Pullup-Widerstände zur Verfügung gestellt wird und die I2C-Slaves den Bus zur Kommunikation auf Masse ziehen. Der obligatorische 100nF-Kondensator in unmittelbarer Nähe zum LM75 fängt Störungen auf dem Kabel ab. Der Schirm des Kabels sollte idealerweise mit an GND angeschlossen sein.

RPi GPIO Pin LM75 Pin
2 (+5V) 8 (VCC)
3 (SDA) 1 (SDA)
5 (SCL) 2 (SCL)
6 (GND) 4 (GND), 5, 6, 7 (Adressbits A0-A2)

Wenn das alles so angeschlossen ist und nichts raucht, gehts weiter zum Softwareteil.


WLAN

Update 22.02.2018: den folgenden Eintrag in der Optionsdatei habe ich erstmal weggelassen, weil das WLAN auch so funktioniert.

Mein WLAN-Stick (lsusb: 0bda:8176 Realtek Semiconductor Corp. RTL8188CUS 802.11n WLAN Adapter, Kernelmodul heißt 8192cu) hat einen Stromsparmodus, der dafür sorgt dass der Stick sich gelegentlich schlafen legt oder sich aus dem WLAN ausbucht, wenn der Pi keine Daten senden will. Das sorgte für lange Wartezeiten beim Seitenaufbau, manchmal Timeouts und gelegentlich musste ich den Pi neu starten, weil er nicht mehr im WLAN eingebucht war. Um das speziell bei diesem Kernelmodul abzuschalten, legt man als Root die Datei /etc/modprobe.d/8192cu.conf an und schreibt folgendes hinein:

options 8192cu rtw_power_mgnt=0 rtw_enusbss=0


Software

Vorbereitung:

Als Betriebssystem kommt ein Standard-Raspbian Raspbian Lite ohne Desktopumgebung (Update 22.02.2018) zum Einsatz. Wie man das auf eine SD-Karte installiert und eine Netzwerkverbindung herstellt, werde ich hier wegen der tausenden Tutorials im Netz nicht weiter erklären. Ich gehe davon aus, dass der Standardbenutzer "Pi" benutzt wird und sudo installiert ist, um sich Root-Rechte zu verschaffen.

Beim ersten Boot geht das Tool raspi-config auf. Als erstes sollte man hier die Datenpartition der SD-Karte vergrößern, den Shared Memory der Grafikkarte auf 16MB setzen (es sei denn, man will eine Desktopoberfläche) und die CPU auf 950MHz oder 900MHz übertakten. Letzteres habe ich nach Abschluss aller Arbeiten zu Gunsten der Lebensdauer wieder rückgängig gemacht. Die restlichen Einstellungen kann man nach persönlichen Geschmack hinfrickeln, speziell weise ich hier mal auf die Zeitzone und das Tastaturlayout hin.

Update 22.02.2018: das Tool öffnet sich auf Debian Stretch nicht mehr automatisch und die Partition auf der SD-Karte wird selbstständig beim ersten Start des Systems vergrößert. Allerdings muss man unter Interfacing Options den I2C-Bus aktivieren. Den Shared Memory für die Grafikkarte und die Übertaktung kann man so beibehalten.

Bevor irgendetwas gemacht wird, sollte man das frisch installierte System erst mal updaten:

  • sudo apt-get update (holt aktuelle Paketliste)
  • sudo apt-get dist-upgrade (führt das Update durch)

Nun die nötige Software installieren:

  • sudo apt-get install g++ build-essential make i2c-tools rrdtool nginx php-fpm php-apcu (installiert benötigte Software)
  • sudo rpi-update (holt aktuelle Firmware aus dem Netz)
  • sudo reboot


Damit man als normaler User auf den I2C-Bus zugreifen darf, muss der Benutzer in der Gruppe i2c sein. Da ich mit dem Standarduser Pi und mit dem Webserver über PHP auf den Bus zugreifen möchte, müssen diese beiden Benutzer zur Gruppe hinzugefügt werden: usermod -a -G i2c pi und usermod -a -G i2c www-data


Webserver konfigurieren

Damit der Nginx-Webserver funktioniert, müssen folgende Befehle ausgeführt werden:

  • systemctl enable php7.0-fpm aktiviert den PHP-Parser als Hintergrunddienst
  • service php7.0-fpm start startet den Dienst (ansonsten nur mit Reboot)

Nun muss man noch die Datei /etc/nginx/sites-available/default editieren und folgendes zwischen server { und } einfügen, damit der Webserver die PHP-Dateien vor der Auslieferung an den Client durch den PHP-Interpreter schickt:

location ~ \.php$ {
                include snippets/fastcgi-php.conf;
                fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
        }
Außerdem muss der Webserver den PHP-Dateityp für Index-Dateien akzeptieren, dazu hängt man index.php mit einer Leertaste als Trennung an folgende, existierende Zeile vor dem Semikolon an:
index index.html index.htm index.nginx-debian.html;

Die neue Zeile lautet dann:

index index.php index.html index.htm index.nginx-debian.html;

Falls gewünscht kann man den Eintrag root /var/www/html; auf root /var/www; ändern, um sich ein Unterverzeichnis zu sparen. Die Änderung ist später beim Einfügen der PHP-Dateien zu berücksichtigen!

Inbetriebnahme des I2C-Bus:

Update 22.02.2018: die Schritte aus dem folgenden Absatz können beim aktuellen Raspbian Stretch entfallen. Der I2C-Bus kann genutzt werden, sobald man den Bus im Tool raspi-config aktiviert hat.

Damit der I2C-Bus funktioniert, müssen die Kernel-Module i2c-dev und i2c_bcm2708 geladen sein. Die aktuell geladenen Module kann man mit dem Befehl lsmod anzeigen lassen. Sind die Module bereits geladen, gehts mit dem nächsten Schritt weiter. Falls nein, muss man die beiden Module in die Datei /etc/modules eintragen (sudo nano /etc/modules), ein Modul pro Zeile. Alternativ kann man den I2C-Bus auch über das Programm raspi-config aktivieren, jedoch bin ich mir nicht sicher, ob damit auch wirklich beide Module aktiviert sind. Trägt man sie selbst ein, kann man sicher sein, dass es so passt. Nach einem Reboot sollten die Module dann geladen sein, alternativ kann man sie auch von Hand ohne Reboot einbinden: modprobe modulname


Sind die Module im Kernel geladen, ist der I2C-Bus einsatzbereit. Der Befehl sudo i2cdetect -y n scannt den Bus ab und schaut, wer so da ist. Das n muss man durch die Nummer des I2C-Bus ersetzen. Bei der ersten Revision des Pi ist das 0 und bei der Zweiten 1. In meinem Fall kam ein RPi Rev. B zum Einsatz. Hat man bis hier alles richtig gemacht, sollte der LM75 sich an der Adresse melden, die man über die Adresspins A0-A2 eingestellt hat:

pi@temp-pi:~$ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --


Die Temperatur holt man sich testweise mit dem Befehl sudo i2cget -y 1 0x48 0x00 w als Datenwort (w, 2 byte) ab. Außerdem muss man die Startposition für den Lesezugriff (0x00), die Adresse des I2C-Slave (0x48) und die Busnummer (-y 1) mitgeben. Hier sind es gerade 0,2°C, also passende Ausgabe:

pi@temp-pi:~$ sudo i2cget -y 1 0x48 0x00 w
0x4000

WiringPi

Update 22.02.2018: WiringPi gibt es mittlerweile auch im Debian-Softwareverzeichnis. Zur Installation genügt ein einziger Befehl:

apt-get install wiringpi


Alte Anleitung (manuelle Installation):

Von Gordon Henderson gibt es eine Bibliothek, um den Hardwarezugriff auf dem Raspberry Pi aus einem C(++)-Programm zu vereinfachen. Leider gibt es die nicht fertig aus den Raspbian-Repos, deshalb muss man sie selbst erstellen:

  • git clone git://git.drogon.net/wiringPi holt den Quelltext vom GIT-Server und speichert ihn in einem Unterordner</code>
  • cd wiringPi
  • ./build startet den automatischen Build- und Installationsprozess. An dieser Stelle muss das Paket libi2c-dev installiert sein, sonst wird die Bibliothek ohne I2C-Fähigkeit erstellt.

Eine Befehlsreferenz und eine Updateanleitung findet sich auf der Projektseite (Link siehe unten).

Der C++-Teil

Das folgende Programm holt das Temperatur-Datenwort vom Sensor und schiebt die Bits so hin, dass man sie mit der Auflösung von 0,125°C multiplizieren kann, um so die Temperatur zu erhalten. Anschließend wird die Temperatur auf eine Nachkommastelle genau in die Konsole ausgegeben. Wie man die Bits hinschieben muss, geht aus dem Datenblatt des LM75 hervor. In meinem Fall heißt die Quellcodedatei lm75temp.cpp und die erzeugte Binary lm75temp.

#define TEST 0
#include <iostream>
#include <wiringPiI2C.h>
#include <stdio.h>
using namespace std;
 
void getraw(unsigned short &raw) {
 
	int fd = wiringPiI2CSetup(0x48);
 
	if (TEST) raw = 0xa0fd;
	else raw = wiringPiI2CReadReg16(fd,0x00);
 
	}
 
int main() {
 
	unsigned short raw;
	getraw(raw);
 
	if (TEST) printf("Raw: 0x%x \n\n", raw);
 
	unsigned char low;
	unsigned char high;
 
	low = ((raw << 8) >> 8);
	high = (raw >> 8);
 
	if (TEST) printf("Low: %x \n",low);
	if (TEST) printf("High: %x \n\n", high);
 
	short temphex;
	temphex = low;
	temphex = (temphex << 8);
	temphex = (temphex | high);
	if (TEST) printf("Temphex: %x\n\n", temphex);
	temphex = (temphex >> 5);
 
	float temp = temphex * 0.125;
 
	if (TEST) printf("Temperatur: ");
	printf("%.1f\n",temp);
 
 
	return 0;
	}


  • Kompilieren: g++ -lwiringPi -o lm75temp lm75temp.cpp (lm75temp ist die erzeugte Binary und lm75temp.cpp die Quellcodedatei)
  • Ins systemweite Binary-Verzeichnis kopieren: sudo cp lm75temp /usr/bin/
  • Eigentümer auf Root setzen: chown root:root /usr/bin/lm75temp
  • Datei ausführbar machen: sudo chmod +x /usr/bin/lm75temp
  • Rechte für global ausführbar setzen: sudo chmod 755 /usr/bin/lm75temp


RRDtool

RRDtool von Tobias Oetiker ist für die Speicherung und Verwaltung der Messwerte und für die Graphen zuständig. Es handelt sich um ein sehr mächtiges Tool, weshalb die Bedienung entsprechend komplex und die Dokumentation groß ist. Auf der Homepage (siehe unten) findet sich auch ein recht einfach gehaltenes Tutorial.


Datenbank erstellen

Zunächst erstellt man eine Datenbank, in der später die Messwerte gespeichert werden. Man kann an der Datenbank später nichts mehr verändern, deshalb muss man sich vorher überlegen, wie viele Sensoren man einlesen will, in welchen Zeitabständen Werte eingelesen werden, wie viele Werte ("Samples") gespeichert werden, ob eine Mittelwertbildung erfolgt, usw. Der folgende Befehl erstellt eine Datenbank für einen einzigen Sensor.

rrdtool create temperatur.rrd --step 1800 DS:temp:GAUGE:1900:-40:80 RRA:AVERAGE:0.5:1:480 RRA:AVERAGE:0.5:48:3600
  • temperatur.rrd heißt die Datei, in der die Datenbank liegt, also die Werte vorgehalten werden. Der Speicherplatz wird im Voraus reserviert.
  • --step 1800 gibt das Aktualisierungsintervall in Sekunden an. Ich habe mich hier für einen 30min-Intervall entschieden. Kommen die Werte häufiger, wird über die Werte für diesen Intervall gemittelt.
  • DS:temp:GAUGE:1900:-40:80 initialisiert eine Datenquelle mit dem Namen "temp" vom Typ GAUGE. Kommt 1900 Sekunden kein Wert, wird der Messwert als Unbekannt definiert. Der gültige Messbereich beträgt -40 bis 80. Darüber oder darunter wird der Messwert verworfen und ebenfalls als Unbekannt deklariert.
  • RRA:AVERAGE:0.5:1:480 definiert eine Datenbank mit 480 Samples (10 Tage). AVERAGE gibt an, dass die Einträge gemittelt werden und die 1 gibt an, dass dies aus einem einzigen Messwert geschieht, also wird der Messwert selbst in die Datenbank eingetragen. Wir erhalten so eine Datenbank, in der die letzten 480 Messwerte im Original gespeichert werden.
  • RRA:AVERAGE:0.5:48:3600 erstellt eine zweite Datenbank, deren Einträge über 48 Messwerte (24 Stunden) gemittelt werden. Man erhält dadurch das Tagesmittel, wovon 3600 Stück gespeichert werden.

Die Anzahl der Samples passen nicht ganz zu den Zeiträumen, für die ich die Daten später ausgebe. Das liegt daran, dass ich die Datenbank nur "Pi mal Daumen" erstellt hab. Wer sich die Mühe machen möchte und das durchrechnet, kann die Werte gerne ändern.


Datenbank mit Messwerten füttern

Um Messwerte in die Datenbank zu stecken, habe ich im Netz (hier) ein Script gefunden und für meine Ansprüche modifiziert. Der Pfad zur Datenbank sollte natürlich entsprechend angepasst werden, falls sie nicht im Verzeichnis /home/pi liegt. Das Script ist bei der Fehlerbehandlung sicher nicht perfekt, tut aber in diesem Fall seinen Dienst. Nicht vergessen: Script mit chmod +x scriptname ausführbar machen. Bei mir heißt das Script tempupdate.

#!/bin/bash
data=`lm75temp`
if [ -n "$data" ] ; then
        rrdtool update /home/pi/temperatur.rrd N:$data
 
fi

Ob das Script funktioniert, kann man mit rrdtool info /home/pi/temperatur.rrd prüfen, nachdem man es ausgeführt hat. Im oberen Bereich bei der DS steht der zuletzt eingegangene Messwert: ds[temp].last_ds = "xxxxx"


Das Script wird viertelstündlich aufgerufen, dafür sorgt der Dienst cron. Die zu erledigenden Jobs werden für jeden Benutzer in der sog. crontab gespeichert. Um sie zu editieren, benutzt man den Befehl crontab -e. Dort erstellt man folgenden Eintrag:

*/15     *       *       *       *       /home/pi/tempupdate > /dev/null 2> /dev/null

> /dev/null leitet alle normalen Ausgaben ins Nichts und 2> /dev/null alle Fehlermeldungen. Falls Probleme auftreten, kann man die beiden Ausgaben z.B. in eine Datei umleiten.

Damit werden die Messwerte erfasst und gespeichert.


Der PHP-Teil

Update 22.02.2018: ab jetzt kommt Nginx als Webserver zum Einsatz. Die zwei Dateien in diesem Teil sind im Ordner /var/www/html zu platzieren, dabei muss man auf die Zugriffsrechte achten! Der Webserver-Benutzer www-data muss mindestens Leserechte haben!

Als Webserver habe ich lighttpd eingesetzt. Der kann PHP und ist schön schlank. PHP muss im Webserver erst aktiviert werden, Google hilft da weiter.

Der PHP-Teil besteht aus den Dateien index.php, die das Grundgerüst der Seite ausgibt und dabei auch gleich die Temperatur von der C++-Anwendung abholt. Die Erzeugung der Graphen habe ich in die Datei graph.php ausgelagert. Sie wird von der Hauptseite aufgerufen und gibt je nach Parameter den entsprechenden Graphen zurück. Dazu ruft sie über die Kommandozeile das rrdtool auf und reicht das erzeugte Bild direkt an den Browser durch.

<html><head><title>Außentemperatur</title></head>
 
<body>
 
<h1>Außentemperatur: <?php echo shell_exec("lm75temp 2>&1"); ?>°C</h1><br/>
<?php echo date("l, d.m.Y, G:i");
echo " 	Uhr"; ?><br/><br/>
 
<b>Letzte 72 Stunden:</b><br/>
<img src="graph.php?type=72"><br/><br/>
 
<b>Letzte 18 Monate:</b><br/>
<img src="graph.php?type=18"><br/><br/>
 
<b>Letzte 8 Jahre:</b><br/>
<img src="graph.php?type=8"><br/><br/>
<?php 
$uptime = shell_exec("cut -d. -f1 /proc/uptime");
$days = floor($uptime/60/60/24);
$hours = $uptime/60/60%24;
$mins = $uptime/60%60;
echo "Server-Laufzeit seit Neustart: $days Tage $hours Stunden $mins Min"; ?>
</body></html>


<?php
$type = $_GET["type"];
 
if ($type != "8" && $type != "18" && type != "72") { echo ""; }
 
if ($type == "8") {
$command="rrdtool graph - -E --end now --start 'now - 8 years' --right-axis 1:0 DEF:mytemp=/home/pi/temperatur.rrd:temp:AVERAGE:step=86400 LINE1:mytemp#0000FF";
}
 
if ($type == "18") {
$command = "rrdtool graph - -E --end now --start 'now - 18 months' --right-axis 1:0 DEF:mytemp=/home/pi/temperatur.rrd:temp:AVERAGE:step=86400 LINE1:mytemp#0000FF";
}
 
if ($type == "72") {
$command="rrdtool graph - -E --start 'now - 72 hours' --end 'now' --right-axis 1:0 DEF:mytemp=/home/pi/temperatur.rrd:temp:AVERAGE LINE2:mytemp#0000FF:\"Temperatur (Wochentag = 12h mittags)\"";
}
 
echo shell_exec($command);
?>


Die Zeitintervalle für die Graphen kann man natürlich beliebig über die Parameter --start und --end des rrdtool anpassen. Wenn einem die Beschriftung der X-Achse nicht passt, hilft --x-grid.Hier im Wiki-Artikel habe ich beim ersten Graphen noch 72 Stunden drin stehen, mittlerweile habe ich den Zeitraum auf eine Woche erhöht. Sieht einfach schöner aus. PHP-Zeile für den Graph:

$command="rrdtool graph - -E --start 'now - 1 week' --end 'now' --right-axis 1:0 --x-grid HOUR:6:DAY:1:DAY:1:86400:%a DEF:mytemp=/home/pi/temperatur.rrd:temp:AVERAGE LINE2:mytemp#0000FF:\"Temperatur (Wochentag = 12h mittags, rechte Seite entspricht jetzt)\"";


Sonstiges

Stromverbrauch

Der Stromverbrauch liegt bei meinem Setup mit Realtek-WLAN-Stick ohne Ethernet bei 3,1W im Leerlauf und 3,3-3,4W unter Last wenn eine Anfrage kommt und die Graphen generiert werden.


Absicherung gegen Stromausfall

Auf eine Absicherung gegen Stromausfall habe ich in diesem Fall verzichtet. Unser Stromnetz ist ziemlich stabil und ein Außenthermometer ist nicht überlebenswichtig. Dennoch sollte man sich darüber im Klaren sein, dass die SD-Karte und somit das Dateisystem einen Stromausfall durchaus übel nehmen kann und dann möglicherweise die Temperaturwerte verschütt gehen. Mein Pi hat eine Freigabe meines NAS per NFS gemountet und kopiert jeden Tag um Mitternacht die Werte dorthin, weil das gleichzeitig gegen einen plötzlichen Tod der SD-Karte absichert.


Lebensdauer

Stand 22.02.2018: der Raspberry Pi hängt seit Januar 2015 in einem ungeheizten, trockenen Schuppen ohne viel Luftzirkulation. Bisher gab es keinerlei Ausfälle, nicht mal die SD-Karte ist ausgestiegen. Ursprünglich wollte ich ihn mal mit Plastik 70 einräuchern, das schiebe ich noch erfolgreich vor mir her. Mal sehen wie lange er ohne Plastiklack durchhält.

Quellen und Links