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:
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«:
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]
Dieser Online-Artikel kann Links enthalten, die auf nicht mehr vorhandene Seiten verweisen. Wir ändern solche "broken links"
nur in wenigen Ausnahmefällen. Der Online-Artikel soll möglichst unverändert der gedrucken Fassung entsprechen.
Themen-Special STORAGE
Alles was Sie zum Thema Storage wissen müssen: »Administration »Software »Grundlagen »LVM & Co.