Hält sich die Hitze noch im Rahmen? Beim Überwachen der Zimmertemperatur mit einem preiswerten USB-Sensor bietet das Holt-Winters-Verfahren aus dem RRDTool-Werkzeugkasten einen Ansatz, um gewöhnliche Schwankungen von Ausreißern zu unterscheiden.
Per USB eingestöpselte Zusatzgeräte verlangten von experimentierfreudigen Linuxern noch vor einigen Jahren heftige Klimmzüge. Der Bastler musste passende Treiber aufstöbern oder selbst schreiben und sie dann in den Kernel einbinden. Heutzutage funktioniert das mit aktuellen Distributionen erstaunlicherweise oft völlig automatisch. Das neulich für 7 Dollar (inklusive Versand) auf Ebay gekaufte USB-Thermometer TEMPer (Abbildung 1, [2]) funktionierte sofort – ohne dass ich auch nur die Bedienungsanleitung durchlesen musste.

Abbildung 1: Den preisgünstige Temperaturfühler TEMPer USB bietet auch Amazon, zumindest das amerikanische.
Sofort erkannt
Beim Einstöpseln des Fühlers in einen USB-Port erkennt der Kernel das Device als generisches HID (Human Interface Device) und weist ihm den ungeschliffenen Treiber »hidraw« zu (Abbildung 2). Dieser Allerweltstreiber kommuniziert mit unterschiedlichen Geräten, kennt aber deren Eigenheiten nicht. Die Bit-Pfriemelei, die zur Verständigung zwischen Hardwarekomponenten notwendig ist, läuft in diesem Fall über eine Software im Userspace ab, die das CPAN-Modul Device::USB::PCSensor::HidTEMPer implementiert.

Abbildung 2: Beim Einstöpseln des Temperaturfühlers erkennt Ubuntu das neue USB-Device korrekt und weist ihm einen generischen Treiber zu.
Um die vom Fühler gemessene Temperatur auszulesen, sucht Listing 1 zunächst mit der Methode »device()« das zuständige Gerät im USB-Baum. Da nur ein Fühler eingestöpselt ist, kommt auch nur ein Eintrag zurück, bei mehreren Geräten gäbe die Methode »list_devices()« eine Liste aller gefundenen Fühler aus. Den internen Sensor des Geräts spricht die Methode »internal()« an und »celsius()« liefert die von ihm gemessene Temperatur als Fließkommazahl mit einer Auflösung von einem halben Grad zurück.
|
Listing 1: |
|---|
01 #!/usr/bin/perl -w
02 use strict;
03 use local::lib;
04 use Device::USB::PCSensor::HidTEMPer;
05
06 my $temper =
07 Device::USB::PCSensor::HidTEMPer->new();
08
09 my $sensor = $temper->device();
10
11 if( defined $sensor->internal() ) {
12 print "Temperature: ",
13 $sensor->internal()->celsius(),
14 " Cn";
15 }
|
Ein Blick in den Sourcecode des CPAN-Moduls offenbart, dass hinter dem schnieken objektorientierten API, das einfach die ausgelesene Temperatur in Grad Celsius liefert, Bitwerte hin und her sausen, Datenpuffer zusammengestellt und zerlegt sowie Prüfsummen ermittelt werden. Damit Linux den Fühler beim Einstöpseln als solchen erkennt, liest es dessen Vendor-ID (»1130«) und Product-ID (»660C«) aus. So ist es egal, in welchem USB-Port oder an welchem USB-Hub der User das Kabel einsteckt. Das Perl-Modul Device::USB (beziehungsweise die dahintersteckende C-Library »libusb«) durchstöbert den gesamten USB-Baum, bis es ein Gerät mit der gesuchten Kombination aus Hersteller- und Produkt-ID findet.
Minutiöse Buchführung
Mit dem CPAN-Modul App::Daemon entsteht nun in Listing 2 ein Daemon-Prozess, den der Admin mit »logtemp start« und »logtemp stop« hoch- und wieder herunterfährt. Während er im Hintergrund läuft, legt er in der Logdatei »/var/log/temper.log« nach Abbildung 3 minütlich den aktuell ausgelesenen Temperaturwert ab. Das Log4perl-Framework schreibt noch einen Zeitstempel davor.
|
Listing 2: |
|---|
01 #!/usr/bin/perl -w
02 use strict;
03 use local::lib;
04 use Device::USB::PCSensor::HidTEMPer;
05 use App::Daemon 0.10 qw(daemonize);
06 use Log::Log4perl qw(:easy);
07 use Sysadm::Install qw(:all);
08 use File::Basename;
09
10 sudo_me();
11
12 $App::Daemon::logfile =
13 "/var/log/temper.log";
14 $App::Daemon::pidfile =
15 "/var/run/temper.pid";
16 $App::Daemon::as_user = $ENV{SUDO_USER};
17
18 daemonize();
19
20 while(1) {
21 my $temper =
22 Device::USB::PCSensor::HidTEMPer->new();
23
24 my $sensor = $temper->device();
25
26 if( defined $sensor->internal() ) {
27 INFO "READ ",
28 $sensor->internal()->celsius();
29 } else {
30 ERROR "No reading available";
31 }
32
33 sleep 60;
34 }
|
Der Befehl »sudo_me()« in Zeile 10 von Listing 2 stammt aus dem CPAN-Modul Sysadm::Install und stellt sicher, dass das Skript als Superuser läuft. Es startet sich selbst mit einem Sudo-Aufruf, falls dies noch nicht der Fall ist. Root-Rechte sind notwendig, damit der Daemon die Logdatei in »/var/log« anlegen und seine Prozess-ID in »/var/run/temper.pid« speichern kann.
Gleich darauf gibt App::Daemon die Privilegien aus Sicherheitsgründen ab und brummt anschließend unter der ID des in der Variablen »$App::Daemon::as_user« abgelegten Users weiter. Dessen ID bezieht das Skript aus der Environment-Variablen »SUDO_USER«, die das Sudo-Kommando mit der ID des aufrufenden Users füllt.





