Mit Linux ist es relativ leicht, selbstgebaute Hardware einzuhängen und kreativ einzusetzen. Heute wird der Lökolben ausgepackt, denn das Bastelfieber ist ausgebrochen!
Es ist noch gar nicht so lange her, da musste man noch Device-Treiber schreiben, um exotische Selbstbau-Hardware anzusteuern. Seit aber USB zum Standard erwachsen ist und Hotplugging im 2.6-Kernel anstandslos funktioniert, geht es viel einfacher.
Der heute vorgestellte Temperaturfühler DS18S20 ([3]) von der Firma
Dallas Semiconductor lässt sich über
einen sogenannten One-Wire-Bus ansteuern, den wiederum ein im
Rechner steckender USB-Dongle betreibt. Die unter [2] frei erhältliche
owfs
-Steuerungssoftware fragt die Daten dann
unter anderem über eine Perl-Schnittstelle ab.
Statt One-Wire sollte der Bus allerdings 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 wiederum
in den USB-Dongle eingeklickt wird.
Abbildung 1: Diagramm: Die Temperatursensoren hängen über den One-Wire-Bus am USB-Dongle. |
Den Temperatursensor DS18S20 gibt es im einschlägigen Elektronikfachhandel für etwa $5 zu kaufen (zum Beispiel bei digikey.com). Er ist zwischen -55°C und +125°C einsetzbar. Der One-Wire-USB-Dongle DS9490R, in den man mit handelsüblichen Telefon-Mehrfachsteckern viele Sensoren hängen kann, schlägt mit etwa $15 - $25 zu Buche (zum Beispiel bei hobby-boards.com).
Abbildung 2: Abfrage des Onewire-USB-Dongle mit den angeschlossenen Temperatursensoren von der Kommandozeile. |
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 Sensor-Daten auf dem File-System
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 andere mehr. In
den kleinen Transistor-ähnlichen Gehäusen steckt ein kleiner
Microcontroller, der einiges auf dem Kasten hat.
Werte der als Dateien erscheinenden Messstationen liest ein einfacher
cat
-Befehl aus, in Abbildung 2 wurde allerdings perl -ple1
verwendet, um ein Newline anzuhängen. Unter dem Eintrag
10B2A7C7000800/temperature
findet sich die
im ersten Sensor gemessene Wert, 22.8125 Grad Celsius Zimmertemperatur.
Der zweite Sensor mit der ID 10.E0E3C7000800
,
der in einer kühlen Winternacht in San Francisco draußen hing,
misst hingegen kühlere 14.4375 Grad (in Kalifornien wird es selten richtig kalt).
Unter dem Eintrag type
steht die Typbezeichnung des Fühlers ("DS18S20"
), damit man
über die Programmierschnittstelle herausfinden kann, ob es sich
um einen Temperatursensor oder um ein anderes Teil mit
One-Wire-Bus handelt. Der Hersteller Dalles bietet alles mögliche an,
darunter auch Schalter, Spannungs- und Strommesser.
Abbildung 3: Zwei Sensoren stecken über einen Telefonkabel-Splitter im One-Wired-USB-Dongle, der wiederum an einem Linux-System angeschlossen ist. |
Der USB-Dongle hat am Ausgang eine Telefonbuchse. Um die Sensoren dort einzuhängen, muss vorher noch jeweils ein langes Kabel mit einem abschließenden Telefonstecker an die Beinchen der Temperaturfühler angelötet werden.
Am einfachsten geht das, indem man ein normales Telefonverlängerungskabel mit Steckern an beiden Enden einkauft und einen davon brutal mit einer Zange abzwackt. Dann wird die äußere Hülle des Kabels abisoliert und es kommen entweder zwei oder vier dünne Kabel zum Vorschein.
Abbildung 4: Vor dem Anlöten: Grün ans linke Bein, Rot ans Mittlere Bein des DS18S20. |
Zum Einsatz kommen nur das rote und das grüne Kabel, die restlichen können abgezwickt werden. Der Temperaturfühler hat drei Beinchen, von denen das rechteste (wenn man das Gehäuse mit der abgeflachten Seite nach vorne ansieht und die Beinchen nach unten zeigen lässt) ü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 wird also das rechteste Fühlerbeinchen abgezwickt und das Telefonkabel mit drei Schrumpfschlauchstücken vorbereitet (Abbildung 4). Später werden die Schlauchstücke leicht erhitzt, was sie elegant zusammenschmurgeln lässt, um dem Fühler ein einigermaßen Wohnzimmer-kompatibles Aussehen zu verleihen.
Abbildung 5: Der Sensor in der Klemme mit einem angelöeteten Kabel. |
Das grüne innere Telefonkabel wird anschließend ans linke Bein des DS18S20 angelötet, das rote kommt ans mittlere dran (Abbildung 5). Dann fährt man 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 erzeugen. Anschließend führt man das dickere (gelbe) Schrumpfschlauchstück vor, bis der Sensor nur noch leicht rausspitzelt und lässt den Schlauch unter der Lötkolbenhitze zusammenschmurgeln. Abbildung 6 zeigt den fertigen Fühler, dessen Telefonstecker entweder direkt im Dongle Platz findet oder aber -- falls mehrere Sensoren zum Einsatz kommen -- über einen Mehrfachstecker (Abbildung 3).
Abbildung 6: Der fertige Sensor in dem zusammengeschrumpften Schrumpfschlauch. |
Zu Testzwecken wird nun ein Sensor
im Zimmer belassen und der andere durchs Fenster ins Freie geführt.
Das owfs-Projekt liefert mit dem Modul OW
eine generische Perl-Schnittstelle mit, die das Modul
in Listing OWTemp.pm
auf die verwendeten
Temperaturfühler zuschneidert. Zunächst
ist nicht bekannt, wieviele 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 und nachfolgende
Aufrufe der Methode temperatures()
liefern Paare aus Fühler-Nummer
und dem ausgelesenen Temperaturwert.
Der Destruktor in Zeile 44 ruft OW::finish()
auf, um die Verbindung
zum USB-Dongle aufzulösen.
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;
Eine typische Sensor-Anwendung zeigt das Skript in Listing rrdmon
.
Es nutzt das Modul RRDTool::OO
vom CPAN, das eine objektorientierte
Schnittstelle zu Tobi Oetikers Werkzeug rrdtool
bereitstellt.
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 an, ``Inside'' und ``Outside'', für den Zimmer- und den
Außentemperaturfühler. Die Zeilen 15 und 16 ordnen den Sensoren-IDs
diese einfacher zu merkenden Bezeichnungen zu. Diese IDs sind weltweit
eindeutig, neu gekaufte Sensoren haben andere IDs.
Der Parameter step
in Zeile 27 gibt mit 300 ein Auffrischintervall
von 300 Sekunden (5 Minuten) vor und 5000 Werte speichert die Datenbank
bevor sie alte Werte ü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''. Welcher Sensor welche ID hat, lässt
sich einfach herausfinden, indem man nur einen Sensor anschliesst und dann
wie in Abbildung 2 gezeigt, die Verzeichnisstruktur anzeigen lässt.
Wird rrdmon
aber 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 Aussensensor blau dargstellt.
Abbildung 7: Der mit RRDTool gezeichnete Graph veranschaulicht den Temperaturverlauf des Aussen- und des Innensensors. |
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 }
Wer die Temperaturausgabe lieber als Text und global zugreifbar möchte
(zum Beispiel, um im Urlaub nachzuprüfen, ob man vergessen hat, daheim
den Herd auszuschalten), schreibt sich einfach einen IRC-Bot wie in
Listing tempbot
. Der Bot verbindet sich mit dem IRC-Server
auf irc.freenode.org
und macht den Chatroom #sftemp
auf.
Der besorgte Urlauber nutzt dann einfach einen IRC-Client oder auch
dem IM-Client gaim
, um dem Bot im Chatroom einen Besuch abzustatten.
Abbildung 8 zeigt die Gaim-Konfiguration und Abbildung 9 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 10).
Abbildung 8: Der IM-Client gaim beherrscht auch das IRC-Protokoll. Einfach einen Account anlegen, auf "Online" klicken und ... |
Abbildung 9: ... das Kommando zum Eintreten in den Channel #sftemp geben. |
Abbildung 10: Der IRC-Bot antwortet mit den gerade herrschenden Temperaturen. |
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: %.1f\n", 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();
Bot::BasicBot
ist ein gutes Beispiel, wie man mit einem CPAN-Modul und
denkbar wenig Code hochkomplizierte Funktionen erledigen kann.
Man erzeugt einfach eine von Bot::BasicBot
abgeleitete Klasse und
definiert dort die Methode said()
, die dann aufgerufen wird, falls
jemand im Chatroom etwas sagt. said()
erhält die Nachricht als Parameter
und kann dann überprüfen, ob der Bot etwas erwidern will und entweder
eine Nachricht oder undef
zurückgeben.
Mit Bot::BasicBot
in der Version 0.65 kommt beim Starten die Warnung
``Use of ->new()
is deprecated, please use spawn()'' hoch, sie darf aber
ignoriert werden.
Die Distribution der owfs
-Software, die über die USB-Schnittstelle
den One-Wire-Bus anspricht, steht unter [2] bereit. Zur Drucklegung
dieses Artikels funktionierte nur die aktuellste Version aus dem
CVS-Repository des owfs
-Projekts, die mit
cvs -d :pserver:anonymous@cvs.sourceforge.net:/cvsroot/owfs co owfs
geholt wird. Außerdem steht auf [5] ein Tarball bereit, der erwiesenermaßen mit den heute vorgestellten Skripts funktioniert.
Um owfs
zu installieren, wird die aktuellste Version von SWIG ([7])
gebraucht, die Entwicklerversion 1.3.27 funktionierte tadellos.
Wer nicht nur die Perl-Schnittstelle installieren will, sondern auch
noch mit dem Kommandozeilentool owfs
den One-Wire-Bus auf das
Dateisystem abbilden will (Abbildung 2), braucht außerdem das
User-Filesystem FUSE von [4], wenn es nicht schon der verwendeten
Linuxdistribution beiliegt (Testen mit
ls -l /usr/local/bin/fusermount
). Dann erfolgt mit
./bootstrap ./configure make
der Build-Prozess im explodierten Tarball. Ein anschließendes
make install
installiert das Kommandozeilentool. Das Perl-Modul
OW
installiert man mit
cd module/swig/perl5 perl Makefile.PL make install
ebenfalls in der owfs
-Distribution.
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/12) umsetzen. Und da jeder Sensor eine eigene ID hat,
müssen die Zeilen 15 und 16 müssen an die lokalen
Gegebenheiten angepasst werden. Die IDs der angeschlossenen Sensoren findet
man nach dem in Abbildung 2 gezeigten Verfahren heraus.
Die restlichen Module Sysadm::Install
und Log::Log4perl
stehen auf dem CPAN. RRDTool::OO erfordert entweder
eine funktionierende rrdtool
-Installation oder wird versuchen,
eine vom Netz zu laden.
Für den Bot wird Bot::BasicBot
benötigt, das wiederum
automatisch die POE
-Distribution installiert.
Steckt man den USB-Dongle in den Rechner, schaltet sich der Hotplug-Mechanismus ein und erzeugt 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 nur für root
. Es wäre aber schlechter Stil,
alle Skripts als root
laufen zu lassen. Abhilfe schafft hier ein
Hotplug-Skript, das ausführbar in der Datei /etc/hotplug/usb/ds2940
stehen
muss:
#!/bin/bash # /etc/hotplug/usb/ds2940 chmod a+rwx "${DEVICE}"
Damit der Hotplugger es beim Einschieben des Dongles ausführt und damit die
Berechtigungen des Device-Eintrages korrigiert, muss
folgende Zeile ans Ende von /etc/hotplug/usb.usermap
:
# /etc/hotplug/usb.usermap # DS2940 one-wire USB device ds2940 0x0003 0x4fa 0x2490 0x0000 0x0000 0x00 0x00 0x00 0x00 0x00 0x00 0x00000000
So können alle vorgestellten Skripts unter normalen User-IDs laufen und der Sicherheitsbeauftragte kriegt keinen roten Kopf.
Michael Schilliarbeitet 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: http://perlmeister.com. |