Nachdem bei uns zu Hause nun der gefühlt achtundvierzigste
Außensensor einer handelsüblichen Wetterstation defekt war, wollte ich
eine Wetterdatenerfassung und -anzeige die etwas langlebiger (und
zugleich "komfortabler" bzw. flexibler) ist. Hierbei dachte ich zunächst
nur an einen DHT11-/DHT22-Sensor (1-Wire) zur Erfassung von Temperatur
und Luftfeuchte im Außenbreich. Da mir entsprechende Möglichkeiten für
die "wetterfeste Verpackung" von Spannungsversorgung, Microcontroller,
Sensor und den dazu nötigen Extra-Bauteilen in einem Gehäuse zu
kompliziert und zeitaufwendig waren, verwarf ich diese Idee.
Nach weiterer Suche bin ich auf den SimpleLink™ SensorTag von Texas Instruments (im Speziellen: CC2650STK) gestoßen. Dieses Gerät enthält 10 verschiedene Sensoren (Oberflächen- und Umgebungstemperatur, Luftfeuchte, Helligkeit, Luftdruck, Beschleunigung, Gyroskop [Lagesensor], Magnetoskop [Kompass], Reed-Kontakt, Digital-Mikrofon) und kostet ca. 40 Euro (Die Verfügbarkeit ist aktuell (23.05.2018) stark eingeschränkt.). Die Daten werden standardmäßig über Bluetooth übertragen (BLE - Bluetooth low energy) und die Energieversorgung* übernimmt eine CR2032-Knopfzelle (3 V).
Als Mikrocontroller diente zunächst ein Raspberry Pi 3 Model B (WLAN und Bluetooth integriert, SensorTag-Bibliotheken für verschiedene Programmiersprachen vorhanden).
In meiner ersten Variante sollen die erfassten Wetterdaten auf einem Webspace gespeichert (CSV-Datei oder SQL-Datenbank) und über eine Internetseite visualisiert werden (HTML/JavaScript).
Nach weiterer Suche bin ich auf den SimpleLink™ SensorTag von Texas Instruments (im Speziellen: CC2650STK) gestoßen. Dieses Gerät enthält 10 verschiedene Sensoren (Oberflächen- und Umgebungstemperatur, Luftfeuchte, Helligkeit, Luftdruck, Beschleunigung, Gyroskop [Lagesensor], Magnetoskop [Kompass], Reed-Kontakt, Digital-Mikrofon) und kostet ca. 40 Euro (Die Verfügbarkeit ist aktuell (23.05.2018) stark eingeschränkt.). Die Daten werden standardmäßig über Bluetooth übertragen (BLE - Bluetooth low energy) und die Energieversorgung* übernimmt eine CR2032-Knopfzelle (3 V).
Als Mikrocontroller diente zunächst ein Raspberry Pi 3 Model B (WLAN und Bluetooth integriert, SensorTag-Bibliotheken für verschiedene Programmiersprachen vorhanden).
In meiner ersten Variante sollen die erfassten Wetterdaten auf einem Webspace gespeichert (CSV-Datei oder SQL-Datenbank) und über eine Internetseite visualisiert werden (HTML/JavaScript).
...
Die Wetterdatenerfassung mit einem Mikrocontroller (bzw. eine Raspberry Pi-Wetterstation) ist schon ein alter Hut - das Internet ist voll von verschiedenen Ansätzen und Lösungen. Für mein Projekt habe ich mich im Wesentlichen an einem SEHR ähnlichen Projekt von Evdin Ursan (Datenerfassung mit dem CC2650STK-SensorTag über node.js am Raspberry Pi 3) und einem Projekt von Reinhard Nickels (Datenübertragung zum Webserver/Visualisierung) orientiert. [Etwaige Ähnlickeiten von HTML-, PHP- und JavaScript-Code zu den o.a. Projekten sind beabsichtigt und sind z.T. NICHT mein geistiges Eigentum. Dank und Anerkennung dafür geht an die genannten Personen.]
...
Nach ungefähr 10 leeren Knopfzellen hatte ich die Nase voll und habe ich mir aus einem alten USB-Kabel und einem LM317 einen Battery-Dummy gebaut.
Bei eingesetzter Knopfzelle schaltet man den SensorTag am linken Taster ein. Er ist dann für ca 2 ... 3 Minuten als Bluetooth-Gerät "sichtbar" (grüne LED am SensorTag blinkt). Am Raspberry Pi nun Bluetooth einschalten, falls noch nicht geschehen. Mit
Stromlaufplan:
4. Programm auf dem Raspberry Pi:
Die Initialisierung und das Lesen der Sensordaten vom Sensortag benötigt etwas Zeit.
Das Ganze wird in der Datei 'logdata.js' nacheinander asynchron ausgeführt. Wegen der Stromaufnahme sind alle Sensoren zunächst deaktiviert. Der Lesevorgang folgt immer dem gleichen Ablauf:
1 - Sensor einschalten, 2 - warten (5 s), 3 - Sensor lesen, 4 - Sensor ausschalten
Beim Lesen des DHT-Sensors kommt es immer wieder mal zu Fehlern (try - catch - finally).
Die vier Leuchtdioden sollen Fehler anzeigen (siehe Kommentar). Die Daten werden zunächst via HTTP-POST an die Datei 'data2csv.php' übertragen...
kann man das Anforderungsniveau erhöhen und die Daten in einer MySQL-Datenbank speichern.
Dazu muss Zeile 29 der Datei 'logdata.js' angepasst werden:
Wenn die Punkte 5 und 6 erledigt sind, kann man das Script (in seinem Ordner) starten:
5. Programm auf dem Webserver - Teil 1:
Die Daten sind in einem Array gespeichert und werden an eine Datei 'logdata.csv' angehängt.
In der Datei 'data2sql.php' müssen die Zugangsdaten für die MySQL-Datenbank hinterlegt werden.
Die Wetterdatenerfassung mit einem Mikrocontroller (bzw. eine Raspberry Pi-Wetterstation) ist schon ein alter Hut - das Internet ist voll von verschiedenen Ansätzen und Lösungen. Für mein Projekt habe ich mich im Wesentlichen an einem SEHR ähnlichen Projekt von Evdin Ursan (Datenerfassung mit dem CC2650STK-SensorTag über node.js am Raspberry Pi 3) und einem Projekt von Reinhard Nickels (Datenübertragung zum Webserver/Visualisierung) orientiert. [Etwaige Ähnlickeiten von HTML-, PHP- und JavaScript-Code zu den o.a. Projekten sind beabsichtigt und sind z.T. NICHT mein geistiges Eigentum. Dank und Anerkennung dafür geht an die genannten Personen.]
...
1. Raspberry Pi vorbereiten
Wie bereits erwähnt, soll die Programmierung des clientseitigen Programms (das auf dem Raspi) mit JavaScript und node.js (Ausführen von JavaScript-Code ohne Browser) umgesetzt werden. Im Internet gibt es dazu eine große Anzahl von JavaScript-Bibliotheken. Diese können über den Node Package Manager (npm) installiert werden.a) Raspberry Pi - Update
(Hinweis: Meine ersten Gehversuche erfolgten mit Raspbian Jessie. Hier haben alle nötigen Software-Installationen funktioniert. Ein späterer Versuch mit Raspian Stretch Lite führte allerdings nicht zum Erfolg, so dass ich dann auf Raspbian Stretch Desktop gewechselt bin, wo wieder alles funktioniert.)
- Verbindung zum Raspberry Pi über ssh oder eine Remotedesktopverbindung herstellen und eine Console öffnen:
pi@raspberrypi:~ $ sudo apt-get update
pi@raspberrypi:~ $ sudo apt-get dist-upgrade
pi@raspberrypi:~ $ sudo apt-get dist-upgrade
b) Software-Installation 1: Node.js und npm
- Node.js aktualisieren bzw. installieren (mindestens Version 4 sollte vorhanden sein):
Im Zweifelsfall npm manuell installieren:
pi@raspberrypi:~ $ curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
pi@raspberrypi:~ $ sudo apt-get install -y nodejs
Im Normalfall sollte die aktuellste Version von npm (Node Package Manager) enthalten sein. Mitpi@raspberrypi:~ $ sudo apt-get install -y nodejs
pi@raspberrypi:~ $ node -v
pi@raspberrypi:~ $ npm -v
die beiden Programmversionen überprüfen (23.05.2018: node.js - v10.1.0, npm - v5.6.0).pi@raspberrypi:~ $ npm -v
Im Zweifelsfall npm manuell installieren:
pi@raspberrypi:~ $ sudo apt-get install npm
Nun am besten den Raspi neu starten.c) Software-Installation 2: Treiber und Node-Pakete (JavaScript-Bibliotheken)
- Den Node.js-Modul-Kompiler node-gyp installieren:
pi@raspberrypi:~ $ sudo npm install -g node-gyp
- Diverse Bluetooth-Treiber werden benötigt:
pi@raspberrypi:~ $ sudo apt-get install bluetooth bluez libbluetooth-dev libudev-dev
- Node.js - Bluetooth low energy - Paket (noble) installieren:
pi@raspberrypi:~ $ npm install noble
- Node.js - SensorTag - Paket (sensortag) installieren:
pi@raspberrypi:~ $ npm install sensortag
- Zur Sicherheit - falls es im sensortag-Paket fehlt - das Node.js - Async - Paket (async) installieren:
pi@raspberrypi:~ $ npm install async
- Für die Messung von Temperatur und Luftfeuchte mit einem DHT11 oder DHT22 im Innenbereich wird z.B. das Node.js - DHT-Sensor - Paket (dht-sensor) benötigt:
pi@raspberrypi:~ $ npm install dht-sensor
- Für Statusmeldungen mit LED's brauchen wir z.B. das Node.js - OnOff - Paket (onoff):
pi@raspberrypi:~ $ npm install onoff
- Den Webserver kontaktieren wir z.B. über das Node.js - Request - Paket (request):
pi@raspberrypi:~ $ npm install request
2. SensorTag und Bluetooth-Verbindung testen
Der SimpleLink™ SensorTag CC2650STK von Texas Instruments wird mit einer 3 V - Knopfzelle (CR2032) versorgt. *Laut Beschreibung des Herstellers soll das Gerät damit ca. 1 Jahr funktionieren. Diese Aussage ist in meinen Augen reine Bauernfängerei. Wenn man NUR die wetterbezogenen Sensoren (Temperatur, Luftfeuchte, Helligkeit, Luftdruck) alle 10 - 15 Minuten ausliest, schafft man im besten Fall 6 Tage! Die Angabe von Texas Instruments bezieht sich wohl nur auf den Bluetooth-Sendebetrieb OHNE das Lesen von Sensordaten. Allerdings: Was ist ein SensorTag ohne die Sensordaten? Ein Tag? - und somit nutzlos! Es hat eine Weile gedauert, bis ich bemerkt habe, dass die Angaben zur Batterielebensdauer totaler Blödsinn sind. Der SensorTag war zwar noch brav mit dem Raspi verbunden, aber nach einer bestimmten Zeit lieferten manche Sensoren unmögliche Werte - was schließlich mit der leeren Batterie zu tun hatte.Nach ungefähr 10 leeren Knopfzellen hatte ich die Nase voll und habe ich mir aus einem alten USB-Kabel und einem LM317 einen Battery-Dummy gebaut.
Bei eingesetzter Knopfzelle schaltet man den SensorTag am linken Taster ein. Er ist dann für ca 2 ... 3 Minuten als Bluetooth-Gerät "sichtbar" (grüne LED am SensorTag blinkt). Am Raspberry Pi nun Bluetooth einschalten, falls noch nicht geschehen. Mit
pi@raspberrypi:~ $ sudo hcitool lescan
werden alle "sichtbaren" Bluetooth-Geräte in der Umgebung angezeigt. Der CC2650 SensorTag sollte dabei sein.3. Verdrahten von RPi, DHT-Sensor und LED's
Materialliste/Zuordnungstabelle:
Anzahl | Bauteil | BCM | wiringPi | Name | Header-Pin |
---|---|---|---|---|---|
1 | Raspberry Pi mit Spannungsversorgung | ||||
1 | Breadboard | ||||
1 | DHT11 oder DHT22 - Sensor | 4 | 7 | GPIO. 7 | 7 |
1 | 10 kΩ - Widerstand für DHT-Sensor | ||||
1 | LED rot + Vorwiderstand 120 Ω | 27 | 2 | GPIO. 2 | 13 |
1 | LED gelb + Vorwiderstand 82 Ω | 23 | 4 | GPIO. 4 | 16 |
1 | LED grün + Vorwiderstand 82 Ω | 22 | 3 | GPIO. 3 | 15 |
1 | LED blau + Vorwiderstand 27 Ω | 26 | 25 | GPIO.25 | 37 |
LED-Vorwiderstände bei I = 16 mA und U = 3,3 V) | |||||
verschiedene Drähte für Breadboard/Raspi | |||||
1 | CC2650STK Sensortag |
Stromlaufplan:
4. Programm auf dem Raspberry Pi:
Daten sammeln und zum Webserver senden (JavaScript)
Die Initialisierung und das Lesen der Sensordaten vom Sensortag benötigt etwas Zeit.
Das Ganze wird in der Datei 'logdata.js' nacheinander asynchron ausgeführt. Wegen der Stromaufnahme sind alle Sensoren zunächst deaktiviert. Der Lesevorgang folgt immer dem gleichen Ablauf:
1 - Sensor einschalten, 2 - warten (5 s), 3 - Sensor lesen, 4 - Sensor ausschalten
Beim Lesen des DHT-Sensors kommt es immer wieder mal zu Fehlern (try - catch - finally).
Die vier Leuchtdioden sollen Fehler anzeigen (siehe Kommentar). Die Daten werden zunächst via HTTP-POST an die Datei 'data2csv.php' übertragen...
// Importieren der notwendigen Module
var dht = require('dht-sensor');
var SensorTag = require('sensortag');
var request = require('request');
var async = require('async');
const Gpio = require('onoff').Gpio;
// GPIO-Pins für LED's als Ausgang festlegen:
const redLED = new Gpio(27, 'out'); // SensorTag-Verbindungsabbruch
const yellowLED = new Gpio(23, 'out'); // DHT-Lesefehler
const greenLED = new Gpio(22, 'out'); // Erfolgreiche SensorTag-Initialisierung
const blueLED = new Gpio(26,'out'); // HTTP-/Übertragungsfehler
// Variablen für DHT-Lesefehler und Durchschnittstemperatur intialisieren:
var oldhum = 0, oldtemp = 0, avgOutTemp = 0;
// Sensor-Lese-Intervall in Minuten setzen:
var readInterval = 15;
// Headers für http-request setzen:
var headers = {
'User-Agent': 'Super Agent/0.0.1',
'Content-Type': 'application/x-www-form-urlencoded'
};
// options für http-request konfigurieren:
// (bei myURL.com die URL des Webspace eintragen)
var options = {
url: 'http://myURL.com/data2csv.php',
method: 'POST',
headers: headers
};
var readings = { sensorId: '00000000' };//Objekt für Sensordaten anlegen
var sensorTag; // globale Variable (SensorTag Instanzkopie)
SensorTag.discover(function (myTag) {
console.log('discovered: ', new Date().toLocaleString());
sensorTag = myTag;
myTag.on('disconnect', function () {
redLED.writeSync(1); // bei SensorTag-Verbindungsabbruch rote LED einschalten
console.log('disconnected! ', new Date().toLocaleString() );
process.exit(0);
});
async.series([
function (callback) {
sensorTag.connectAndSetUp(callback);
redLED.writeSync(0);
},
function (callback) {
sensorTag.readManufacturerName(function (error, manufacturerName) {
console.log('\tHersteller: ' + manufacturerName);
callback();
});
},
function (callback) {
sensorTag.readDeviceName(function (error, deviceName) {
console.log('\tGeraetetyp: ' + deviceName);
callback();
});
},
function (callback) {
sensorTag.readSystemId(function (error, systemId) {
console.log('\tSystem-ID: ' + systemId);
callback();
});
},
function (callback) {
sensorTag.readFirmwareRevision(function (error, firmwareRevision) {
console.log('\tFirmware-Revision: ' + firmwareRevision);
greenLED.writeSync(1); // bei erfolgreicher SensorTag-Initialisierung grüne LED einschalten
callback();
});
}
]);
});
setInterval(function () {
readings.sensorId = sensorTag.id;
try {
var dhtdata = dht.read(22, 4); // DHT22 an GPIO4 (Pin 7)
readings.indoorhum = dhtdata.humidity;
readings.indoortemp = dhtdata.temperature;
yellowLED.writeSync(0); // gelbe LED ausschalten
}
catch (err) {
yellowLED.writeSync(1); // bei DHT-Fehler gelbe LED einschalten
readings.indoorhum = oldhum; // letzten Messwert verwenden
readings.indoortemp = oldtemp; // letzten Messwert verwenden
}
finally {
oldhum = readings.indoorhum;
oldtemp = readings.indoortemp;
};
async.series([
function (callback) {
sensorTag.enableIrTemperature(callback); // Sensor einschalten
},
function (callback) {
setTimeout(callback, 5000); // warten
},
function (callback) {
sensorTag.readIrTemperature(function (error, objectTemperature, ambientTemperature) {
//readings.objectTemperature = objectTemperature; // Oberflächentemperaturmessung nicht sinnvoll
readings.temperatureFromIr = ambientTemperature; // Sensordaten speichern
callback();
});
},
function (callback) {
sensorTag.disableIrTemperature(callback); // Sensor ausschalten
},
function (callback) {
sensorTag.enableHumidity(callback);
},
function (callback) {
setTimeout(callback, 5000);
},
function (callback) {
sensorTag.readHumidity(function (error, temperature, humidity) {
readings.humidity = humidity;
readings.temperatureFromHumidity = temperature;
callback();
});
},
function (callback) {
sensorTag.disableHumidity(callback);
},
function (callback) {
sensorTag.enableBarometricPressure(callback);
},
function (callback) {
setTimeout(callback, 5000);
},
function (callback) {
sensorTag.readBarometricPressure(function (error, pressure) {
readings.pressure = pressure;
callback();
});
},
function (callback) {
sensorTag.disableBarometricPressure(callback);
},
function (callback) {
sensorTag.enableLuxometer(callback);
},
function (callback) {
setTimeout(callback, 5000);
},
function (callback) {
sensorTag.readLuxometer(function (error, lux) {
readings.lux = lux;
callback();
});
},
function (callback) {
sensorTag.disableLuxometer(callback);
}
], function () {
readings.dataTime = new Date();
// Temperatur-Mittelwert von IR-Temperatur- und Feuchtesensor bilden:
avgOutTemp = (readings.temperatureFromIr + readings.temperatureFromHumidity) / 2;
// HTTP-Form mit Sensordaten und Zeitstempel (UNIX) füllen (vgl. data2csv.php auf Webserver):
options.form = {
'TimeKey': readings.dataTime.getTime(), 'avgOutdoorTemp': avgOutTemp, 'OutdoorHum': readings.humidity,
'IndoorTemp': readings.indoortemp, 'IndoorHum': readings.indoorhum,
'Pressure': readings.pressure, 'Light': readings.lux
};
request(options, function (error, response, body) {
if (!error && response.statusCode == 200) {
greenLED.writeSync(0); // bei erfolreicher 1. Übertragung grüne LED ausschalten
blueLED.writeSync(0); // bei erfolreicher Übertragung blaue LED ausschalten
} else {
blueLED.writeSync(1); // bei HTTP/Übertragungsfehlern blaue LED einschalten
console.log(new Date().toLocaleString());
console.log("statusCode:", response && response.statusCode);
console.log("Fehler:", error);
};
})
})
}, readInterval * 60 * 1000);
...wenn die Lösung mit der .csv-Datei zufriedenstellend funktioniert,kann man das Anforderungsniveau erhöhen und die Daten in einer MySQL-Datenbank speichern.
Dazu muss Zeile 29 der Datei 'logdata.js' angepasst werden:
url: 'http://myURL.com/data2sql.php',
Wer seinen Raspberry Pi über WLAN mit dem Internet verbindet und nachts das Funknetzwerk schlafen schickt,
hat bei der dargestellten Lösung unweigerlich Aufzeichnungslücken. Diesem Problem stellen wir uns später ...Wenn die Punkte 5 und 6 erledigt sind, kann man das Script (in seinem Ordner) starten:
pi@raspberrypi:~ $ sudo node logdata.js
5. Programm auf dem Webserver - Teil 1:
Daten empfangen und speichern (PHP, SQL)
a) Datenspeicherung in einer .csv-Datei
An die Datei 'data2csv.php' werden die Messwerte einzeln übergeben.Die Daten sind in einem Array gespeichert und werden an eine Datei 'logdata.csv' angehängt.
<?php
isset($_POST['TimeKey']) ? $a0=$_POST['TimeKey'] : $a0=''; // Raspi-Zeit
isset($_POST['avgOutdoorTemp']) ? $a1=$_POST['avgOutdoorTemp'] : $a1=''; // mittlere Temperatur (außen)
isset($_POST['OutdoorHum']) ? $a2=$_POST['OutdoorHum'] : $a2=''; // rel. Feuchte (außen)
isset($_POST['IndoorTemp']) ? $a3=$_POST['IndoorTemp'] : $a3=''; // Temperatur (DHT, innen)
isset($_POST['IndoorHum']) ? $a4=$_POST['IndoorHum'] : $a4=''; // rel. Feuchte (DHT, innen)
isset($_POST['Pressure']) ? $a5=$_POST['Pressure'] : $a5=''; // Luftdruck (außen)
isset($_POST['Light']) ? $a6=$_POST['Light'] : $a6=''; // Helligkeit (außen)
$handle = fopen("./logdata.csv", 'a');
fputcsv($handle, array($a0,$a1,$a2,$a3,$a4,$a5,$a6));
fclose($handle);
?>
b) Datenspeicherung in einer MySQL-Datenbank
In der vorab erstellten MySQL-Datenbank wird folgende Tabelle mit den dargestellten Spalten angelegt:CREATE TABLE IF NOT EXISTS `weather_data` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`datum` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`TimeKey` bigint(13) NOT NULL COMMENT 'Zeit vom Raspberry Pi',
`avgOutdoorTemp` float NOT NULL COMMENT 'mittlere Aussentemperatur',
`OutdoorHum` float NOT NULL COMMENT 'Luftfeuchte Aussen',
`IndoorTemp` float NOT NULL COMMENT 'Temperatur innen (DHT)',
`IndoorHum` float NOT NULL COMMENT 'Luftfeuchte innen (DHT)',
`Pressure` float NOT NULL COMMENT 'Luftdruck (Aussen)',
`Light` float NOT NULL COMMENT 'Helligkeit (Aussen)',
PRIMARY KEY (`id`)
)
In der Datei 'data2sql.php' müssen die Zugangsdaten für die MySQL-Datenbank hinterlegt werden.
<?php
$mysql_host = "localhost"; // wenn SQL-Datenbank über die gleiche URL erreichbar ist
$mysql_db = "SQL-Datenbankname";
$mysql_user = "DB-Benutzername";
$mysql_pw = "DB-Passwort";
isset($_POST['TimeKey']) ? $a0=$_POST['TimeKey'] : $a0=''; //Raspi-Zeit
isset($_POST['avgOutdoorTemp']) ? $a1=$_POST['avgOutdoorTemp'] : $a1=''; // mittlere Temperatur (außen)
isset($_POST['OutdoorHum']) ? $a2=$_POST['OutdoorHum'] : $a2=''; // rel. Feuchte (außen)
isset($_POST['IndoorTemp']) ? $a3=$_POST['IndoorTemp'] : $a3=''; // Temperatur (DHT, innen)
isset($_POST['IndoorHum']) ? $a4=$_POST['IndoorHum'] : $a4=''; // rel. Feuchte (DHT, innen)
isset($_POST['Pressure']) ? $a5=$_POST['Pressure'] : $a5=''; // Luftdruck (außen)
isset($_POST['Light']) ? $a6=$_POST['Light'] : $a6=''; // Helligkeit (außen)
$connection = mysql_connect($mysql_host, $mysql_user, $mysql_pw) or die("Verbindung zur Datenbank fehlgeschlagen.");
mysql_select_db($mysql_db, $connection) or die("Datenbank konnte nicht ausgewaehlt werden.");
$insert_data = "INSERT INTO weather_data (TimeKey, avgOutdoorTemp, OutdoorHum, IndoorTemp, IndoorHum, Pressure, Light) VALUES ($a0, $a1, $a2, $a3, $a4, $a5, $a6)";
mysql_query($insert_data, $connection) or die("Fehler beim Eintragen der Daten in die Datenbank!");
?>
6. Programm auf dem Webserver - Teil 2:
Daten visualisieren (HTML, PHP, JavaScript)
Um die aktuell letzten Wetterdaten (auch am Smartphone) anschauen zu können, habe ich eine kleine Tabelle auf der Startseite 'index.php' angelegt. Die Diagramme mit den alten Werten sind verlinkt. Die beiden dargestellten Code-Abschnitte arbeiten mit den Daten aus der MySQL-Datenbank. Eine Umsetzung mit der .csv-Datei findet man hier.
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<style>
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
td {
text-align: right;
padding: 5px;
}
</style>
</head>
<body>
<p style="font-family:Tahoma,sans-serif; font-size:24px; color:blue;">
<?php
$mysql_host = "localhost";
$mysql_db = "SQL-Datenbankname";
$mysql_user = "DB-Benutzername";
$mysql_pw = "DB-Passwort";
$phys = array("Datum", "Temperatur außen ", "rel. Luftfeuchte außen ", "Temperatur innen ", "rel. Luftfeuchte innen ", "Luftdruck ", "Helligkeit ");
$columns = array("TimeKey", "avgOutdoorTemp", "OutdoorHum", "IndoorTemp", "IndoorHum", "Pressure", "Light");
$units = array("d", " °C", " %rH", " °C", " %rH", " hPa", " lux");
$connection = mysql_connect($mysql_host, $mysql_user, $mysql_pw) or die("Verbindung zur Datenbank fehlgeschlagen.");
mysql_select_db($mysql_db, $connection) or die("Datenbank konnte nicht ausgewaehlt werden.");
$abfrage = "SELECT TimeKey, avgOutdoorTemp, OutdoorHum, IndoorTemp, IndoorHum, Pressure, Light FROM weather_data ORDER BY TimeKey DESC LIMIT 1";
$ergebnis = mysql_query($abfrage);
echo "<table style=\"font-family:Arial,sans-serif; font-size:24px; color:black;\">";
while($row = mysql_fetch_array($ergebnis))
{
echo "<tr><th>";
$d1 = floor($row[TimeKey]/1000); // Sekunden (abgerundet)
$datum = date("d.m.Y H:i:s", $d1);
echo "letzte Wetterdaten: "."<br>";
echo $datum;
echo "</th></tr>";
for ($c=1; $c < 7; $c++)
{
echo "<tr>";
echo "<td>".$phys[$c]." </td>";
echo "<td>".round($row[$columns[$c]],2);
echo $units[$c]."</td>";
echo "</tr>";
}
}
echo "</table>";
?>
<a href="graphsql.php">Wetterverlauf</a>
</p>
</body>
</html>
Für die grafische Darstellung der Wetterdaten in einem Liniendiagramm wurde die Javascript-Bibliothek dygraphs benutzt. Die Daten können entweder direkt aus einer .csv-Datei oder einem Array übernommen werden. Wenn man die Wettermesswerte in einer SQL-Datenbank gespeichert hat, muss zunächst ein Array angelegt werden (PHP-Code). Bei der Datenbank-Abfrage benötige ich jedes mal andere Werte und habe diesen Code-Abschnitt 3 mal implementiert (ja, ich weiß: schlecht programmiert). Außerdem möchte ich nur die Daten der letzten 24 Stunden (= 86400 s) darstellen. (Die Zeitfunktion time() in PHP liefert die UNIX-Zeit in Sekunden, die Zeitfunktion in JavaScript liefert Millisekunden [TimeKey].) Der Luftdruck des Sensortag wird auf Ortshöhe gemessen und NICHT auf Meereshöhe umgerechnet. Deshalb habe ich im Diagramm die Ortshöhe (hhh) üNN angegeben - wer will, kann den Wert auch auf Normal-Null hochrechnen.
<html>
<head>
<script type="text/javascript" src="dygraph.js"></script>
<script type="text/javascript" src="synchronizer.js"></script>
<link rel="stylesheet" href="../dygraph.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
</head>
<body>
<table>
<tr>
<td>
<p style="font-family:Arial;">Wetteraufzeichung: ORT </p><br>
</td>
</tr>
<tr>
<td>
<div id="graphdiv1" style="width: 700; height: 500"></div>
<script type="text/javascript">
g1 = new Dygraph(
document.getElementById("graphdiv1"),
<?php
$mysql_host = "localhost";
$mysql_db = "SQL-Datenbankname";
$mysql_user = "DB-Benutzername";
$mysql_pw = "DB-Passwort";
$last24 = (time() - 86400)*1000;
$connection = mysql_connect($mysql_host, $mysql_user, $mysql_pw) or die("Verbindung zur Datenbank fehlgeschlagen.");
mysql_select_db($mysql_db, $connection) or die("Datenbank konnte nicht ausgewaehlt werden.");
$abfrage = "SELECT TimeKey, avgOutdoorTemp, OutdoorHum, IndoorTemp, IndoorHum FROM weather_data WHERE TimeKey > ".$last24." ORDER BY TimeKey ASC";
$ergebnis = mysql_query($abfrage);
echo "["; // Start des 2-dimensionalen Arrays
while($row = mysql_fetch_array($ergebnis))
{
$d = Date("Y/m/d H:i:s", $row[TimeKey]/1000);
echo "["."new Date(\"".$d."\")".",".$row[avgOutdoorTemp].",".$row[OutdoorHum].",".$row[IndoorTemp].",".$row[IndoorHum]."],";
}
echo "]";
?> ,
{
strokeWidth: 2,
labels: ['Date', 'avgOutdoorTemp', 'OutdoorHum', 'IndoorTemp', 'IndoorHum'],
ylabel: 'Temperatur [°C]',
y2label: 'rel. Luftfeuchte [%rH]',
visibility: [true, true, true, true],
series: {
'OutdoorHum': {
axis: 'y2',
color: "green"
},
'IndoorHum': {
axis: 'y2',
color: "green",
strokePattern: [10, 2, 5, 2]
},
'IndoorTemp': {
axis: 'y',
color: "red",
strokePattern: [10, 2, 5, 2]
},
'avgOutdoorTemp': {
axis: 'y',
color: "red"
}
},
axes: {
y2: {
axisLineColor: "green",
axisLineWidth: 2,
drawGrid: true,
independentTicks: true,
gridLinePattern: [2, 2]
},
y: {
axisLineColor: "red",
axisLineWidth: 2,
drawGrid: true,
independentTicks: true,
},
x: {
axisLineColor: "black",
axisLineWidth: 2,
valueFormatter: function (x) {
var d = new Date(x);
return d.toLocaleTimeString("de-DE", { year: "numeric", month: "numeric", day: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit" })
},
ticker: Dygraph.dateTicker
}
}
}
);
</script>
</td>
<td>
<div id="graphdiv2" style="width: 700; height: 500"></div>
<script type="text/javascript">
g2 = new Dygraph(
document.getElementById("graphdiv2"),
<?php
$mysql_host = "localhost";
$mysql_db = "SQL-Datenbankname";
$mysql_user = "DB-Benutzername";
$mysql_pw = "DB-Passwort";
$last24 = (time() - 86400)*1000;
$connection = mysql_connect($mysql_host, $mysql_user, $mysql_pw) or die("Verbindung zur Datenbank fehlgeschlagen.");
mysql_select_db($mysql_db, $connection) or die("Datenbank konnte nicht ausgewaehlt werden.");
$abfrage = "SELECT TimeKey, Pressure, Light FROM weather_data WHERE TimeKey > ".$last24." ORDER BY TimeKey ASC";
$ergebnis = mysql_query($abfrage);
echo "[";
while($row = mysql_fetch_array($ergebnis))
{
$d = Date("Y/m/d H:i:s", $row[TimeKey]/1000);
echo "["."new Date(\"".$d."\")".",".$row[Pressure].",".$row[Light]."],";
}
echo "]";
?>,
{
strokeWidth: 2,
labels: ['Date', 'Pressure (hhh m \u00fcNN)', 'Light'],
ylabel: 'Luftdruck [hPa]',
y2label: 'Helligkeit [lux]',
visibility: [true, true],
series: {
'Pressure (hhh m \u00fcNN)': {
axis: 'y',
color: "darkmagenta"
},
'Light': {
axis: 'y2',
color: "orange"
}
},
axes: {
x: {
axisLineColor: "black",
axisLineWidth: 2,
valueFormatter: function (x) {
var d = new Date(x);
return d.toLocaleTimeString("de-DE", { year: "numeric", month: "numeric", day: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit" })
},
ticker: Dygraph.dateTicker
},
y: {
axisLineColor: "darkmagenta",
axisLineWidth: 2,
valueRange: [800, 1020]
},
y2: {
axisLineColor: "orange",
axisLineWidth: 2,
drawGrid: true,
independentTicks: true,
labelsKMG2: true,
gridLinePattern: [2, 2]
}
}
}
);
var sync = Dygraph.synchronize(g1, g2, {
selection: true,
zoom: true
});
</script>
</td>
</tr>
<tr>
<td>
<br>
</td>
</tr>
</table>
<p style="font-family:Arial;">
<?php
$mysql_host = "localhost";
$mysql_db = "SQL-Datenbankname";
$mysql_user = "DB-Benutzername";
$mysql_pw = "DB-Passwort";
$phys = array("Datum", "Temperatur außen: ", "rel. Luftfeuchte außen: ", "Temperatur innen: ", "rel. Luftfeuchte innen: ", "Luftdruck: ", "Helligkeit: ");
$columns = array("TimeKey", "avgOutdoorTemp", "OutdoorHum", "IndoorTemp", "IndoorHum", "Pressure", "Light");
$units = array("d", " °C", " %rH", " °C", " %rH", " hPa", " lux");
$connection = mysql_connect($mysql_host, $mysql_user, $mysql_pw) or die("Verbindung zur Datenbank fehlgeschlagen.");
mysql_select_db($mysql_db, $connection) or die("Datenbank konnte nicht ausgewaehlt werden.");
$abfrage = "SELECT TimeKey, avgOutdoorTemp, OutdoorHum, IndoorTemp, IndoorHum, Pressure, Light FROM weather_data ORDER BY TimeKey DESC LIMIT 1";
$ergebnis = mysql_query($abfrage);
while($row = mysql_fetch_array($ergebnis))
{
$d1 = floor($row[TimeKey]/1000); // Sekunden (abgerundet)
$datum = date("d.m.Y H:i:s", $d1);
echo $datum."<br>";
for ($c=1; $c < 7; $c++) {
echo $phys[$c].round($row[$columns[$c]],2).$units[$c]."<br>";
}
}
?>
</p>
</body>
</html>
Die vorgestellte Lösung war für mich noch nicht optimal. Zum einen muss man immer auf ein internetfähiges Gerät zurückgreifen, wenn man die aktuellen Wetterdaten (meist die Außentemperatur) wissen will. Zum anderen hat man den wild verdrahteten Raspberry Pi 3 rumstehen, der eventuell noch andere Aufgaben wahrnehmen soll und ggf. zu weit vom Sensortag entfernt ist (Verbindungsabbrüche). Eine Verbesserung stellt meine 2. Variante dar.
Keine Kommentare:
Kommentar veröffentlichen