Open Source im professionellen Einsatz
Newsletter abonnieren
ABO & ARCHIV | NEWS | TECHNICAL REVIEW | VIDEOS | WHITEPAPER | EVENTS

 

Helfen Sie mit
diese Webseite
weiter zu verbessern!
Als Dankeschön erhält jeder Teilnehmer ein Linux-Magazin Sonderheft.

Zur Umfrage

ABO & ARCHIV

Linkes Menü

Events

« August 2007 »
Mo Di Mi Do Fr Sa So
  1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31 

Service


Anzeige

Server, DSL-Flatrate, Linux-Software, WLAN, Digitalkamera, Notebook, Software, Grafikkarten, Desktop-PCs, CPU, Netzwerk, Linux-News, Testberichte, Hardware und Software

  Home » ABO & ARCHIV » Ausgaben » 2006 » 03 » Ist das nicht cool?  


© photocase.com

Temperatursensoren mit Perl auslesen

Ist das nicht cool?

von Michael Schilli
Erschienen im Linux-Magazin 2006/03

Mit Linux hängt der kreative Bastler relativ leicht selbst gebaute Hardware an seinen Rechner. Dieser Snapshot vermittelt an einem Beispiel Grundlagen dafür und infiziert mit Lötkolbenfieber.

Es ist noch gar nicht so lange her, da musste man noch Device-Treiber schreiben, um exotische Eigenbau-Hardware anzusteuern. Seit aber USB-Hotplugging im 2.6er Kernel anstandslos funktioniert, geht alles viel einfacher. Der in der folgenden Beispielanwendung verwendete Temperaturfühler DS18S20 [3], hergestellt von der Firma Dallas Semiconductor, ist über einen so genannten One-Wire-Bus anzusteuern, den wiederum ein im Rechner steckender USB-Dongle betreibt. Die unter [2] frei erhältliche OWFS-Steuerungssoftware fragt die Daten über eine Perl-Schnittstelle ab.

Statt One-Wire sollte der Bus allerdings besser Two-Wire heißen, denn zwei dünne Kupferkabel (meist in einer einzigen Umhüllung) gehen vom Sensor zum USB-Dongle (siehe Abbildung 1). Am anderen Ende des Kabels findet sich ein Telefonstecker (RJ11), der sich in den USB-Dongle einklicken lässt. Den erwähnten Temperatursensor gibt es im einschlägigen Elektronikfachhandel für wenige Euro zu kaufen (zum Beispiel 4,50 Euro bei [http://www.conrad.de]). Er ist zwischen -55°C und +125°C einsetzbar. Der One-Wire-USB-Dongle DS9490R, an den der Bastler mit den handelsüblichen Telefon-Mehrfachsteckern viele Sensoren zugleich hängen kann, schlägt mit etwa 15 bis 25 Dollar zu Buche (zum Beispiel bei Hobby-boards.com).

Das OWFS-Projekt [2] auf Sourceforge bietet eine Reihe von Schnittstellen an, um die Temperaturwerte der Sensoren auszulesen. Eine davon nutzt das User-Filesystem Fuse und bildet die Sensordaten auf das Filesystem ab, ähnlich der »/proc«-Hierarchie in Linux. Abbildung 2 zeigt, wie ein Dongle mit zwei Sensoren sich dem Benutzer präsentiert: Nicht nur die ausgelesenen Temperaturwerte sind verfügbar, sondern auch noch eindeutige IDs der Sensoren, deren Typbezeichnung und vieles mehr. In den kleinen Transistor-ähnlichen Gehäusen steckt nämlich ein winziger Microcontroller, der einiges auf dem Kasten hat.

Ablesung

Die Werte der Messstationen liest ein einfacher »cat«-Befehl aus den Dateien, die Abbildung 2 verwendet allerdings »perl -ple1«, um ein Newline anzuhängen. Unter dem Eintrag »10B2A7C7000800/temperature« findet sich der vom ersten Sensor gemessene Wert: 22,8125°C. Der zweite Sensor mit der ID »10.E0E3C7000800«, der in einer Winternacht in San Francisco draußen hing, misst hingegen kühlere 14,4375°C (in Kalifornien wird es selten richtig kalt). Unter dem Eintrag »type« steht die Typbezeichnung des Fühlers (DS18S20), damit sich über die Programmierschnittstelle herausfinden lässt, welche Sorte Sensor mit dem One-Wire-Bus verbunden ist. Der Hersteller Dalles bietet alles Mögliche an, darunter auch Schalter, Spannungs- und Strommesser.

Lötkolben anheizen

Der USB-Dongle hat als Eingang eine Telefonbuchse. Um die Sensoren dort einzuhängen, muss ich vorher noch jeweils ein langes Kabel mit einem abschließenden Telefonstecker an die Beinchen der Temperaturfühler anlöten. Am einfachsten geht das, indem ich von einem normalen Telefonverlängerungskabel mit Steckern an beiden Enden einen der Stecker brutal mit einer Zange abzwacke. Dann muss ich noch die äußere Hülle des Kabels abisolieren. Dabei kommen entweder zwei oder vier dünne Drähte zum Vorschein.

Ich benötige nur das rote und das grüne Kabel, die restlichen dürfen einer Kneifzange zum Opfer fallen. Der Temperaturfühler hat drei Beinchen, von denen das ganz rechts (wenn man das Gehäuse mit der abgeflachten Seite nach vorne ansieht und die Beinchen nach unten zeigen) überflüssig ist. Es dient dazu, dem Fühler extra Spannung zuzuführen, aber der begnügt sich auch damit, Strom aus der Datenleitung zu stehlen [8].

Mit einer Zange zwicke ich also das rechte Fühlerbeinchen ab und bereite das Telefonkabel mit drei Schrumpfschlauchstücken vor (Abbildung 3). Später erhitze ich die Schlauchstücke vorsichtig, was sie elegant zusammenschmurgeln lässt, um dem Fühler ein einigermaßen Wohnzimmer-kompatibles Aussehen zu verleihen.

Das grüne innere Telefonkabel löte ich danach ans linke Bein des DS18S20, das rote kommt ans mittlere (Abbildung 4). Dann fahre ich mit dem Lötkolben nahe an den zwei roten inneren Schrumpfschläuchen entlang, worauf diese einschrumpfen und so die abisolierten Drahtstücke umschließen. Falls sie nicht weit genug schrumpfen, hilft ein Stück Isolierband, sie so zu befestigen, dass sie sich nicht berühren und einen Kurzschluss verursachen.

Anschließend führe ich das dickere (gelbe) Schrumpfschlauchstück vor, bis der Sensor nur noch leicht rausspitzelt, und lasse den Schlauch sich unter der Lötkolbenhitze zusammenziehen. Abbildung 5 zeigt den fertigen Fühler, dessen Telefonstecker entweder direkt im Dongle Platz findet oder aber - falls gleichzeitig mehrere Sensoren zum Einsatz kommen - über einen Mehrfachstecker (Abbildung 6).

Zu Testzwecken lasse ich nun einen Sensor im Zimmer und führe den anderen durchs Fenster ins Freie. Das OWFS-Projekt liefert mit dem Modul »OW« eine generische Perl-Schnittstelle mit, die das Modul »OWTemp.pm« (siehe Listing 1) auf die konkret verwendeten Temperaturfühler zuschneidet.


Abbildung 1: Die Temperatursensoren sind über den One-Wire-Bus mit dem USB-Dongle verbunden.

Such den Sensor

Zunächst ist nicht bekannt, wie viele Geräte am Bus hängen, welche davon Temperaturfühler sind und was ihre eindeutigen IDs sind. Die vom Konstruktor »new« aufgerufene »discover«-Methode findet dies heraus, indem sie einfach die »type«-Einträge aller am Bus hängenden Geräte öffnet und nachsieht, ob sich dort ein DS18S20 zu erkennen gibt.

Mit »OW::init(\'u\')« nimmt das Modul dann Kontakt zum USB-Dongle auf, die nachfolgenden Aufrufe der Methode »temperatures()« liefern Paare aus Fühlernummer und ausgelesenem Temperaturwert. Der Destruktor in Zeile 44 ruft »OW::finish()« auf, um die Verbindung zum USB-Dongle aufzulösen.

Das Skript »rrdmon« (siehe Listing 2) zeigt eine typische Sensor-Anwendung. Es nutzt das Modul »RRDTool::OO« vom CPAN, das eine objektorientierte Schnittstelle zu Tobi Oetikers Werkzeug RRDtool bereitstellt.


Abbildung 2: Abfrage des One-Wire-USB-Dongle mit den angeschlossenen Temperatursensoren von der Kommandozeile aus.

Kurven malen

Ohne Optionen aufgerufen liest es alle Sensoren aus und legt ihre aktuellen Werte in der Round-Robin-Datenbank ab. Falls diese noch nicht existiert, legt die »create()«-Methode in Zeile 26 sie mit zwei Datensourcen, »Inside« und »Outside«, für den Zimmer- und den Außentemperaturfühler an. Die Zeilen 15 und 16 ordnen den Sensoren-IDs diese einfacher zu merkenden Bezeichnungen zu. Diese IDs sind weltweit eindeutig, neu gekaufte Sensoren haben jeweils eigene Identitäten. Welcher Sensor welche ID hat, lässt sich einfach herausfinden, indem man nur einen Sensor anschließt und dann die Verzeichnisstruktur anzeigen lässt (Abbildung 2).


Abbildung 3: Vor dem Anlöten: Grün ans linke, rot ans mittlere Bein des DS18S20.

Der Parameter »step« in Zeile 27 gibt mit 300 ein Auffrischintervall von 300 Sekunden (5 Minuten) vor, und die Datenbank speichert 5000 Werte, bevor sie alte überschreibt. Ab Zeile 56 findet dann der Auslese- und Auffrischungsvorgang statt, mit den von »OWTemp« bereitgestellten Methoden und »update()« von »RRDTool::OO«. Die Zeilen 15 und 16 weisen den wenig aussagekräftigen Fühler-IDs lesbare Bezeichnungen zu: »Outside« und »Inside«. Wird »rrdmon« mit dem Parameter »-g« aufgerufen, erzeugt es aus den RRD-Daten eine grafische Darstellung der Temperaturverläufe beider Fühler und legt sie in der PNG-Datei »/tmp/temperature.png« ab (Abbildung 7). Der Innensensor wird rot, der Außensensor grün dargstellt.


Abbildung 4: Der Sensor in der Klemme mit einem angelöteten Kabel.

Listing 1:
»OWTemp.pm«

01 ###########################################
02 package OWTemp;
03 # Mike Schilli, 2005 (m@perlmeister.com)
04 ###########################################
05
06 use Log::Log4perl qw(:easy);
07 use OW;
08
09 ###########################################
10 sub new {
11 ###########################################
12     my($class, @options) = @_;
13
14     my $self = {
15         type => "DS18S20",
16     };
17
18     bless $self, $class;
19
20     OW::init('u');
21
22     $self->{devices} = [$self->discover()];
23
24     return $self;
25 }
26
27 ###########################################
28 sub temperatures {
29 ###########################################
30     my($self) = @_;
31
32     my @temperatures = ();
33
34     for my $dev ( @{ $self->{devices} } ) {
35         my($val) = owread("$dev/temperature");
36         $val =~ s/s//g;
37         push @temperatures, [$dev, $val];
38     }
39
40     return @temperatures;
41 }
42
43 ###########################################
44 sub DESTROY {
45 ###########################################
46     OW::finish();
47 }
48
49 ###########################################
50 sub discover {
51 ###########################################
52     my($self) = @_;
53
54     my @found = ();
55
56     for my $entry (owread("")) {
57         DEBUG "Found top entry '$entry'";
58         next if $entry !~ /^d/;
59
60         my($type) = owread("$entry/type");
61
62         DEBUG "Found type '$type'";
63         next if defined $type and
64            $type ne $self->{type};
65         push @found, $entry;
66     }
67     return @found;
68 }
69
70 ###########################################
71 sub owread {
72 ###########################################
73     my($entry) = @_;
74
75     my @found = ();
76
77     my $result = OW::get($entry) or
78         LOGDIE "Failed to read $entry";
79
80     DEBUG "owread result='$result'";
81
82     for my $entry (split /,/, $result) {
83         $entry =~ s#/$##;
84         push @found, $entry;
85     }
86
87     return @found;
88 }
89
90 1;

Hinausposaunt

Wer auf die Temperaturausgabe lieber remote und im Textformat zugreifen möchte (um etwa im Urlaub nachzuprüfen, ob er vergessen hat daheim den Herd auszuschalten), schreibt sich einfach einen IRC-Bot wie in Listing 3 »tempbot«. Der verbindet sich mit dem IRC-Server auf »irc.freenode.org« und macht den Chatroom »#sftemp« auf. Der besorgte Urlauber nutzt dann einen IRC-Client oder auch den IM-Client Gaim, um dem Bot im Chatroom einen Besuch abzustatten. Abbildung 8 zeigt das Kommando, um in den Chatroom einzusteigen, wo der Bot schon auf das Stichwort »temp« wartet. Fällt es, extrahiert er die letzten gemessenen Fühlerwerte aus dem RRD-Archiv und sendet sie zurück in den Chatroom (Abbildung 9).


Abbildung 5: Der fertige Sensor in dem zusammengeschrumpften Schlauchstück.

»Bot::BasicBot« ist ein gutes Beispiel dafür, wie ein CPAN-Modul und denkbar wenig Code auch hochkomplizierte Funktionen erledigen können. Man erzeugt einfach eine von »Bot::BasicBot« abgeleitete Klasse und definiert dort die Methode »said()«, die genau dann aufgerufen wird, wenn jemand im Chatroom irgendetwas sagt.


Abbildung 6: Zwei Sensoren stecken über einen Telefonkabel-Splitter im One-Wire-USB-Dongle, der wiederum an ein Linux-System angeschlossen ist.

Die Methode »said()« erhält die Nachricht als Parameter und kann überprüfen, ob der Bot etwas erwidern will. Je nachdem wird sie danach entweder eine entsprechende Nachricht oder »undef« zurückgeben. »Bot::BasicBot« verursacht übrigens in der Version 0.65 beim Starten die Warnung »Use of ->new() is deprecated, please use spawn()«, die man jedoch unbesorgt ignorieren darf.

Die Distribution der OWFS-Software, die über die USB-Schnittstelle den One-Wire-Bus anspricht, steht unter [2] bereit. Zur Zeit der Drucklegung dieses Artikels funktionierte nur die neueste Version, die »cvs -d :pserver:anonymous@cvs.sourceforge.net:/cvsroot/owfs co owfs« holt. Außerdem steht auf [5] ein Tarball bereit, der erwiesenermaßen mit den in diesem Beitrag vorgestellten Skripten funktioniert.


Abbildung 7: Der mit RRDTool gezeichnete Graph veranschaulicht den Temperaturverlauf des Außen- und des Innensensors.

Listing 2:
»rrdmon«

01 #!/usr/bin/perl -w
02 use strict;
03 use Getopt::Std;
04 use Log::Log4perl qw(:easy);
05 use Sysadm::Install qw(:all);
06 use RRDTool::OO;
07 use OWTemp;
08 
09 Log::Log4perl->easy_init($DEBUG);
10 
11 my  $RRDDB   = "/tmp/temperature.rrd";
12 my  $GRAPH   = "/tmp/temperature.png";
13 
14 my %sensors = (
15     "10.E0E3C7000800" => "Outside",
16     "10.B2A7C7000800" => "Inside",
17 );
18 
19 getopts("g", my %o);
20 
21     # Constructor
22 my $rrd = RRDTool::OO->new(
23              file => $RRDDB);
24 
25     # Create a round-robin database
26 $rrd->create(
27      step        => 300,
28      data_source => { name      => "Outside",
29                       type      => "GAUGE" },
30      data_source => { name      => "Inside",
31                       type      => "GAUGE" },
32      archive     => { rows      => 5000 }) unless -f $RRDDB;
33 
34 if($o{g}) {
35         # Draw a graph in a PNG image
36     $rrd->graph(
37       start          => time() - 24*3600*3,
38       image          => $GRAPH,
39       vertical_label => 'Temperatures',
40       draw           => {
41           color  => '00FF00',
42           type   => "line",
43           dsname => 'Outside',
44           legend => 'Outside',
45       },
46       draw           => {
47           type   => "line",
48           color  => 'FF0000',
49           dsname => 'Inside',
50           legend => 'Inside',
51       },
52       width          => 300,
53       height         => 75,
54       lower_limit    => 0,
55     );
56 } else {
57     my $ow = OWTemp->new();
58     my %values = ();
59     for my $station ($ow->temperatures()) {
60         my($dev, $temp) = @$station;
61         $values{$sensors{$dev}} = $temp;
62     }
63     $rrd->update(time => time(), values => %values);
64 }

Installation

Um »owfs« zu installieren, bedarf es der neuesten Version von Swig [7], die Entwicklerversion 1.3.27 funktioniert tadellos. Wer nicht nur die Perl-Schnittstelle installieren will, sondern auch noch mit dem Kommandozeilen-Tool »owfs« den One-Wire-Bus auf das Dateisystem abbilden will (siehe Abbildung 2), braucht außerdem das User-Filesystem Fuse von [4], wenn es nicht schon der verwendeten Linux-Distribution beiliegt. Ob dies der Fall ist, lässt sich mit dem Kommando »ls -l /usr/local/bin/fusermount« leicht testen. Anschließend erfolgt der Build-Prozess mit:

./bootstrap
./configure
make

Ein anschließendes »make install« installiert das Kommandozeilen-Tool. Das Perl-Modul »OW« ist mit

cd module/swig/perl5
perl Makefile.PL
make install

ebenfalls in der »owfs«-Distribution zu installieren. Ein Cronjob, der alle fünf Minuten aufgerufen wird, füllt stetig das RRD-Archiv:

*/5 * * * * cd /Pfad; ./rrdmon; ./rrdmon -g

Unter /Pfad sollte dann nicht nur »rrdmon«, sondern auch das Perl-Modul »OWTemp.pm« liegen. Die von »rrdmon« erzeugten Dateien liegen in »/tmp«. Wem das zu wackelig ist, der sollte die Pfadvariablen in »rrdmon« (Zeilen 11 und 12) umsetzen. Da jeder Sensor eine eigene ID hat, sind die Zeilen 15 und 16 an die lokalen Gegebenheiten anzupassen. Die IDs der angeschlossenen Sensoren lassen sich mit dem in Abbildung 2 gezeigten Verfahren ermitteln.

Die restlichen Module »Sysadm::Install« und »Log::Log4perl« stehen auf dem CPAN bereit. »RRDTool::OO« erfordert eine funktionierende »rrdtool«-Installation oder versucht es selbst, eine vom Netz zu laden. Der Bot benötigt »Bot::BasicBot«, was wiederum automatisch die Installation der POE-Distribution nach sich zieht.


Abbildung 8: Das Kommando zum Eintreten in den Channel »#sftemp«.


Abbildung 9: Der IRC-Bot antwortet mit den gerade herrschenden Temperaturen.

Sicher einstöpseln

Sobald ich jetzt den USB-Dongle in den Rechner stecke, schaltet sich der Hotplug-Mechanismus ein und erzeugt für den Dongle ein USB-Device: »root -rw-r--r-- /proc/bus/usb/003/008«. Da OWFS auch beim Datenlesen schreibend auf den Dongle zugreift, funktioniert das Auslesen der Temperaturwerte im Moment nur für Root. Es wäre aber schlechter Stil, alle Skripte privilegiert laufen zu lassen.

Einen Ausweg aus diesem Dilemma weist ein kleines Hotplug-Skript, das ausführbar in der Datei »/etc/hotplug/usb/ds2940« steht:

#!/bin/bash
# /etc/hotplug/usb/ds2940
chmod a+rwx "${DEVICE}"

Damit der Hotplugger das Skript beim Einschieben des Dongle ausführt und damit die Berechtigungen des Device-Eintrags korrigiert, muss folgende Zeile ans Ende von »/etc/hotplug/usb.usermap«:

# DS2940 one-wire USB device ds2940 0x0003 0x4fa 0x2490 0x0000 0x0000 0x00 0x00 0x00 0x00 0x00 0x00 0x00000000

Damit laufen alle hier vorgestellten Skripte auch unter den ganz normalen User-IDs. (jcb)

Listing 3:
»tempbot«

01 #!/usr/bin/perl -w
02 
03 use strict;
04 use Bot::BasicBot;
05 
06 package TempBot;
07 use base qw( Bot::BasicBot );
08 use Log::Log4perl qw(:easy);
09 use RRDTool::OO;
10 
11 ###########################################
12 sub said {
13 ###########################################
14   my($self, $mesg) = @_;
15 
16   return unless $mesg->{body} eq "temp";
17 
18   my $rrd = RRDTool::OO->new(
19       file => "/tmp/temperature.rrd" );
20 
21   my $dsnames = $rrd->meta_data("dsnames");
22 
23   $rrd->fetch_start(
24     start => time() - 5*60,
25     end   => time()
26   );
27 
28   my $string;
29 
30   while(my($time, @values) =
31                       $rrd->fetch_next()) {
32     for(my $i=0; $i<@$dsnames; $i++) {
33       $string .= sprintf "%10s: %.1fn",
34                          $dsnames->[$i],
35                          $values[$i];
36     }
37     return $string;
38   }
39 }
40 
41 $^W = undef;
42 
43 TempBot->new(
44   server   => 'irc.freenode.net',
45   channels => ['#sftemp'],
46   nick     => 'tempbot',
47 )->run();
48 @KE

Der Autor



Michael Schilli arbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter [mschilli@perlmeister.com] zu erreichen. Seine Homepage ist: [http://perlmeister.com]

Infos

[1] Listings zu diesem Artikel: [ftp://www.linux-magazin.de/pub/listings/magazin/2006/03/Perl]

[2] One-Wire-File-System-Projekt: [http://owfs.sourceforge.net]

[3] Temperatursensor DS18S20: [http://www.maxim-ic.com/quick_view2.cfm/qv_pk/2815]

[4] Website des Fuse-Projekts: [http://fuse.sourceforge.net]

[5] CVS-Schnappschuss von OWFS: [http://perlmeister.com/devel/owfs-2.2p0RC-cvssnap.tgz]

[6] Datenblatt des One-Wire-USB-Dongle DS9490R: [http://pdfserv.maxim-ic.com/en/ds/DS9490-DS9490R.pdf]

[7] Development-Version von Swig: [http://prdownloads.sourceforge.net/swig/swig-1.3.27.tar.gz]

[8] One-wire Bus: [http://en.wikipedia.org/wiki/1-Wire]

Ähnliche Artikel
Na, wie geht's uns denn heute? Speichermedien automatisiert überwachen
Backup über Bande Ivman steuert per Hotplug beliebige Jobs
E-Werke Perl misst Stromverbrauch mit Multimeter
Füttern nach Programm Tipps täglich verschicken
Stochern im Minenfeld Verbote, Fallen und Mogeleien bei Benchmarks
Persönlicher Spürhund Desktop-Suche mit Perl-Skript

Themen-Special
STORAGE
Alles was Sie zum Thema Storage wissen müssen:
»Administration
»Software
»Grundlagen
»LVM & Co.
Impressum | © 2007Linux New Media AG
Partner-Sites
Deutschland: [LinuxUser] [EasyLinux] [Linux-Community] [Linux-Nachrichten] [Linux Events] [OpenBytes]
Europa: [EasyLinux Polen] [Linux Magazine Polen] [Darmowe Programy] [EasyLinux Rumänien] [Linux Magazin Rumänien] [Linux Magazine Spanien]
International: [Linux Magazine International] [Linux Magazine Brasilien]