Funktionen
- Forkt sich selbst um in Hintergrund zu laufen
- Automatisches erkennen der angeschlossenen Sensoren
- Automatisches hinzufügen von neuen Sensoren
- Erkennen von Übertragungsfehlern (CRC)
- Ausgabe der Daten wahlweise:
- Erstellen eines CSV-Files pro Stunde im Webserver-Bereich
- MySQL-Datenbank
- Heartbeat-LED (usr0) zeigt Messzyklus an
- Sensor-Offset zum korrigieren von Toleranzen (nur bei der Verwendung von MySQL)
- Sauberes beenden bei „kill“ und „killall“
- Eigenes Cape mit On-Board Sensor
#!/usr/bin/perl use strict; use warnings; use DBI; use Time::HiRes qw/ time sleep /; use constant false => 0; use constant true => 1; # Signale (HUP, TERM) my $sig; $SIG{TERM} = \&TERMhandler; my $MYSQL = true; my $CSV = false; led_off(); my $PID = fork(); if ( $PID ) { print "Prozess ID = $PID\n"; exit; } #exit; # Definitionen MYSQL my $DBuser = "root"; my $DBpass = "XXXXXXXXXXXX"; my @dsn = ("DBI:mysql:database=measure;host=localhost", $DBuser, $DBpass); my $dbh; my $sth; my $SQLcmd; my %sensors; my %offset; my $sensorMax=0; my $aref; # Sonstige Definitionen my $WritePath = "/var/www/measure/"; my $temp; my @files; my @timeData; my $row; my $file; my $Sleeptime; my $tm = time(); my $looptm = 60; while(1) { $tm = time() - $tm; $Sleeptime = $looptm - $tm; if ( $Sleeptime > 1 ) { sleep $Sleeptime; } $tm = time(); if ($sig) # Catch KILLs { last; } opendir DIR, "/sys/bus/w1/devices" or die "Cannot open directory: $!"; @files = readdir DIR; closedir DIR; if ( $CSV ) { $file = $WritePath . getLogFileName(); if (-e $file) { if ( (time()-((stat($file))[9]))/60 > 1380 ) { unlink $file; } open( LOG, '>>', $file) or die "Could not open file '$file' $!"; } else { open( LOG, '>', $file) or die "Could not open file '$file' $!"; print LOG "Date,Time,ID,Temp\n"; } } if ( $MYSQL ) { $dbh = DBI->connect(@dsn, { PrintError => 0, AutoCommit => 1, }) or die "SQL-Error" . $dbh->errstr(); $sth = $dbh->prepare("INSERT INTO measure (SensorID, value) VALUES (?,?)") or die $dbh->errstr(); $SQLcmd = "SELECT id, SensorID, offset FROM sensor"; $aref = $dbh->selectall_arrayref($SQLcmd) or die $dbh->errstr(); for $row (@$aref) { $sensors{(@$row)[1]} = (@$row)[0]; $offset{(@$row)[1]} = (@$row)[2]; if ( $sensorMax < (@$row)[0] ) { $sensorMax = (@$row)[0]; } } } led_flash(); foreach (@files) { if ($sig) { last; } if ( $_ =~ "^28-.[0-9a-f]" ) { $file = "/sys/bus/w1/devices/" . $_ . "/w1_slave"; open(SENSOR, '<', $file) or die "cannot open > $file: $!"; while ($row = <SENSOR>) { chomp $row; if ( $row =~ ".crc=.. NO" ) { last; } if ( $row =~ ".t=.[0-9]*" ) { $temp = (split('=',$row))[1]; if ( $CSV ) { print LOG getLoggingTime() . ",$_,$temp\n"; } if ( $MYSQL ) { if (! $sensors{$_} ) { $sensorMax ++; $SQLcmd = "insert into sensor (id, SensorID, offset ) values (" . $sensorMax . ", '" . $_ . "', 0)"; $dbh->do( $SQLcmd ) or die $dbh->errstr(); $sensors{$_} = $sensorMax; $offset{$_} = 0; } $sth->execute($sensors{$_}, ($temp + $offset{$_}) ) or die $dbh->errstr(); } } } close ( SENSOR ); } } if ( $CSV ) { close ( LOG ); } if ( $MYSQL ) { $dbh->disconnect(); } led_off(); # exit; # print "Laufzeit: $tm \n"; } print "\nmeasure.pl: Clean Shutdown.\n"; exit; sub getLoggingTime { my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time); my $nice_timestamp = sprintf ( "%04d%02d%02d,%02d:%02d:%02d", $year+1900,$mon+1,$mday,$hour,$min,$sec); return $nice_timestamp; } sub getLogFileName { my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time); my $nice_timestamp = sprintf ( "%02d.log",$hour); return $nice_timestamp; } sub led_off { my $LED_trigger = "/sys/class/leds/beaglebone:green:usr0/trigger"; open ( LED, '>', $LED_trigger) or die "Could not control LED $!"; print LED "none\n"; close ( LED ); } sub led_flash { my $LED_trigger = "/sys/class/leds/beaglebone:green:usr0/trigger"; my $LED_on = "/sys/class/leds/beaglebone:green:usr0/delay_on"; my $LED_off = "/sys/class/leds/beaglebone:green:usr0/delay_off"; open ( LED, '>', $LED_trigger) or die "Could not control LED $!"; print LED "timer\n"; close ( LED ); open ( LED, '>', $LED_on) or die "Could not control LED $!"; print LED "1"; close ( LED ); open ( LED, '>', $LED_off) or die "Could not control LED $!"; print LED "75"; close ( LED ); } sub TERMhandler { print "\nmeasure.pl: TERM erhalten!\n"; $sig = shift; }
In Zeile 7 und 8 kann die Art der Ausgebe aktiviert werden. Einfach eine 0 für aus oder 1 für ein hinterlegen. Aktuell stet es auf MySQL.
Vorangegangenes Script kann mit diesem Script gestartet werden wenn die entsprechenden W1-Module erstellt wurden:
#!/bin/bash killall measure.pl echo BB-W1:00A0 > /sys/devices/bone_capemgr.9/slots /etc/measure/measure.pl
Das BeagelBone kann die Datenbank aus Sicht von CPU und RAM leicht stemmen. Vorausgesetzt es hat, wie bei mir, nichts anderes zu tun. Das Betroffenen Bord wird bei mir nur als Datenlogger genutzt…
Die MySQL-Datenbank verwalte auf dem Board mit http://www.heidisql.com/.
phpMyAdmin würde natürlich auch gehen. Dies ist aber nur unnötige Last und Platz da zusätzlich noch einige Module installiert werden müssten und der Webserver umkonfiguriert werden muss.
Hier der SQL-Code zum erstellen der Datenbank-Tabellen:
CREATE TABLE `measure` ( `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, `ts` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `SensorID` TINYINT(4) UNSIGNED NOT NULL, `value` MEDIUMINT(9) NULL DEFAULT NULL, PRIMARY KEY (`id`) ) COLLATE='latin1_general_ci' ENGINE=InnoDB ROW_FORMAT=COMPRESSED AUTO_INCREMENT=1; CREATE TABLE `sensor` ( `id` TINYINT(3) UNSIGNED NOT NULL AUTO_INCREMENT, `ts` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `SensorID` CHAR(16) NOT NULL COLLATE 'latin1_general_ci', `name` CHAR(50) NULL DEFAULT NULL COLLATE 'latin1_general_ci', `offset` SMALLINT(6) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE INDEX `SensorID` (`SensorID`) ) COLLATE='latin1_general_ci' ENGINE=InnoDB ROW_FORMAT=COMPRESSED AUTO_INCREMENT=1;
In der my.cnf muss die Bindung noch auf 0.0.0.0 gestellt werden. Internetzugriff sollte durch eine Firewall oder Router unterbunden sein!
Der Benutzer „root“ muss dann noch Zugriff erhalten. Dazu mit dem Kommando “mysql -p“ anmelden und folgendes eingeben.
UPDATE mysql.user SET Host='%' WHERE User='root' and Host='::1'; FLUSH PRIVILEGES;
Das obige Kommando ersetzt die Zeile in der Zugriff per localhost (IPv6) erlaubt wird und ändert die Zeile auf „Darf von überall“
Die Korrektur der Temperaturwerte erfolgt in der Tabelle „sensor“. Dort kann man pro Sensor einen Wert in die Spalte „offset“ eintragen. Da die Temperaturen sozusagen in „Miligrad“ vom Sensor geliefert werden und auch so in der Datenbank stehen, muss für eine Korrektur von -1 °C (Der Sensor misst eine zu hohe Temp.) der Wert -1000 eingetragen werden.
Die Konfiguration der 1-Wire-Schnittstelle habe ich nach folgender, wirklich guter, Anleitung gemacht:
http://hipstercircuits.com/dallas-one-wire-temperature-reading-on-beaglebone-black-with-dto/
…und hier die Bilder von meinem selbstgemachtem Cape:
(im Löten bin ich etwas aus der Übung. Layout hab ich vorher keines gezeichnet. Sowas geht immer noch „on the Fly“)
Auf dem linken Bild kann man unten links die selbständig rückstellende Sicherung gut erkennen.
Ich hab hierfür einen mit 0,2 A verwendet.
Oben rechts auf dem gleichen Bild zwischen des Steckern ist der zehnte Sensor zu sehen.