Raspberry Pi DIY Wetterstation – Webserver
In diesem Teil der Artikelserie richten wir uns einen Webserver soweit her, dass dieser über eine einfache Schnittstelle die Wetterdaten aufnimmt. Dort speichern wir diese in einer MySQL Datenbank.
Raspberry Pi DIY Wetterstation – Webserver
Jeder günstige Webserver bietet zumindest folgende Funktionen an:
- MySQL Datenbank
- PHP Scriptsprache
- Speicherplatz
Aus diesem Grund habe ich mich für dieses Tutorial an den kleinsten gemeinsamen Nenner gehalten. Ursprünglich war die Azure Cloud geplant, da die Speicherung jedoch nicht ohne kostenpflichtigem Service möglich ist habe ich davon abgesehen. Eventuell folgt aber noch ein alternativer Artikel für die Microsoft Cloud.
Der Prozess im Detail
Der Prozess sieht wie folgt aus:
- Daten beschaffen
der Raspberry Pi steht im Keller und wird alle 15 Minuten die Messwerte der Sensoren abfragen. Die Daten werden als JSON aufbereitet und an den Webserver geschickt - Webserver API
der Webserver bekommt eine einfache REST Schnittstelle die Daten in Form von JSON aufnimmt und in einer Datenbank speichert - Datenbank
in einer MySQL Datenbank werden die Daten in einer Tabelle persistiert. Von dort können wir später Auswertungen erstellen.
Der Prozess forder für eine lückenlose Protokollierung der Daten, dass der Raspberry Pi rund um die Uhr läuft und Messwerte liefert. Er muss immer eine Verbindung mit dem Internet haben. Der Webserver muss immer abrufbereit sein um die Daten zu speichern. Selbst im besten Fall werden wir über das Jahr aber nur ca. 95% schaffen. Stromausfälle und kurze offline Zeiten sind nicht zu verhindern. Fehlende Datensätze und Ausreißer sind in der Statistik und den KI Algorithmen kein Problem, es gibt dafür Lösungen.
Den kompletten Code für meine Raspberry Pi DIY Wetterstation findet ihr auch auf meiner GitHub Projektseite.
Daten beschaffen
Die letzten Artikel haben gezeigt wie die Daten einzelner Sensoren abgefragt werden. Außerdem holen wir uns über eine Wetter API Daten aus der Umgebung. All diese haben wir in Variablen gespeichert die wir nun in einem JSON Objekt zusammenführen und an die API des Webservers schicken. Das Python Script, welches alle 15 Minuten am Raspberry Pi läuft sieht wie folgt aus:
# this script collects data from 3 sensors connected to a Raspberry Pi # additionally it requests weather data from an API # all this data is computed and sent to a storage cloud for further usage import requests import json import re import os import Adafruit_BMP.BMP085 as BMP085 import Adafruit_DHT import time import base64 debug = 0 api_current_pressure = ""; api_current_temperature = ""; api_current_humidity = ""; sensor_18B20_temperature = ""; sensor_dht11_temperature = ""; sensor_dht11_humidity = ""; sensor_bmp085_temperature = ""; sensor_bmp085_pressure = ""; start = time.time() ######################################################################## # temperature from connected 18B20 sensor directory = "/sys/bus/w1/devices/" # find all connected 1-wire devices devices = os.listdir(directory) for f in devices: if f != "w1_bus_master1": file = open(directory+f+"/w1_slave") line = file.readline() if re.match(r"([0-9a-f]{2} ){9}: crc=[0-9a-f]{2} YES",line): line = file.readline() m = re.match(r"([0-9a-f]{2} ){9}t=([+-]?[0-9]+)",line) if m: sensor_18B20_temperature = str(float(m.group(2)) / 1000.0) ######################################################################## ######################################################################## # humidity and temperature from connected DHT11 sensor pin = 4 # GPIO Pin number sensor = Adafruit_DHT.DHT11 h, t = Adafruit_DHT.read_retry(sensor, pin) sensor_dht11_temperature = str(t) sensor_dht11_humidity = str(h) ######################################################################## ######################################################################## # pressure and humidity from bmp085 sensor sensor = BMP085.BMP085() sensor_bmp085_temperature = format(sensor.read_temperature()) sensor_bmp085_pressure = format(sensor.read_pressure() * 0.01) # Pa to hPa ######################################################################## ######################################################################## # weather api code # hard coded API params appid = "WetterAPIKeyHierEingeben" # create your own at openweathermap.org city = "Mank" state = "at" # create API Url string url = "https://api.openweathermap.org/data/2.5/weather?q="+city+","+state+"&APPID="+appid # get weather information by calling REST API interface r = requests.get(url); # convert to JSON object js = r.json() # print needed values api_current_pressure = str(js['main']['pressure']) api_current_temperature = str(js['main']['temp'] - 273.15) # Kelvin to Celsius api_current_humidity = str(js['main']['humidity']) ######################################################################## end = time.time() # debug output if debug: print "API current pressure: "+api_current_pressure print "API current temperature: "+api_current_temperature print "API current humidity: "+api_current_humidity print "18B20 sensor temperature: "+sensor_18B20_temperature print "DHT11 sensor temperature: "+sensor_dht11_temperature print "DHT11 sensor humidity: "+sensor_dht11_humidity print "BMP085 sensor temperature: "+sensor_bmp085_temperature print "BMP085 sensor pressure: "+sensor_bmp085_pressure print "{:5.3f}s".format(end-start) data = {} data["api_pressure"] = base64.b64encode(api_current_pressure) data["api_humidity"] = base64.b64encode(api_current_humidity) data["api_temperature"] = base64.b64encode(api_current_temperature) data["sensor_18b20_temperature"] = base64.b64encode(sensor_18B20_temperature) data["sensor_dht11_temperature"] = base64.b64encode(sensor_dht11_temperature) data["sensor_dht11_humidity"] = base64.b64encode(sensor_dht11_humidity) data["sensor_bmp085_temperature"] = base64.b64encode(sensor_bmp085_temperature) data["sensor_bmp085_pressure"] = base64.b64encode(sensor_bmp085_pressure) data["script_time"] = base64.b64encode("{:5.3f}".format(end-start)) json_data = json.dumps(data) ######################################################################## # send values as json file to webserver url = "https://domain.com/api.php/" token = "securitytoken" r = requests.post(url+token, data=json_data) if debug: print r.text ########################################################################
Neu ist für euch der letzte Teil. In einem benannten Array data werden die Messwerte abgelegt. Dieses Arry kopieren wir mit json.dumps(data) in ein JSON Objekt. Über die requests.post() Funktion geben wir der REST Schnittstelle das JSON Objekt mit. Dafür gebt ihr als url Parameter den Pfad zur api.php Datei an, welche die REST Schnittstelle repräsentiert. Damit nicht jeder Daten schicken kann wird diese durch ein von euch erstelltes token gesichert.
Webserver API
Am Webserver installieren wir eine api.php PHP Script Datei. Diese nimmt die über einen POST Request geschickte Daten auf und repräsentiert den Endpunkt der REST Schnittstelle. Dabei wird geprüft ob es sich um ein JSON Objekt handelt und der richtige Security Token mitgegeben wurde. Erst wenn das der Fall ist werden die übergebenen Daten als neues Recordset in die Datenbank geschrieben. Das Script sieht dabei so aus:
<?php // database connection variables $db_host="localhost"; $db_name="NAMEDERDATENBANK"; $db_user="DATENBANKBENUTZERNAME"; $db_passwd="DATENBANKBENUTZERPASSWORT"; // REST API security token $app_token = "SECURITYTOKEN"; // database functions function getDBConnection($db_host, $db_name, $db_user, $db_passwd) { try{ $con = new PDO("mysql:host=$db_host;dbname=$db_name", $db_user, $db_passwd, array( PDO::ATTR_PERSISTENT => true )); $con->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); $con->exec("SET CHARACTER SET utf8"); }catch(PDOException $e) { return 1; } return $con; } function saveSensorData($params, $data) { try{ $con = $params["con"]; $sql = "INSERT INTO sensor_data (script_time, api_pressure, api_humidity, api_temperature, sensor_18b20_temperature, sensor_dht11_temperature, sensor_dht11_humidity, sensor_bmp085_temperature, sensor_bmp085_pressure) VALUES (:script_time, :api_pressure, :api_humidity, :api_temperature, :sensor_18b20_temperature, :sensor_dht11_temperature, :sensor_dht11_humidity, :sensor_bmp085_temperature, :sensor_bmp085_pressure)"; $ps = $con->prepare($sql); $ps->bindValue('script_time', $data['script_time']); $ps->bindValue('api_pressure', $data['api_pressure']); $ps->bindValue('api_humidity', $data['api_humidity']); $ps->bindValue('api_temperature', $data['api_temperature']); $ps->bindValue('sensor_18b20_temperature', $data['sensor_18b20_temperature']); $ps->bindValue('sensor_dht11_temperature', $data['sensor_dht11_temperature']); $ps->bindValue('sensor_dht11_humidity', $data['sensor_dht11_humidity']); $ps->bindValue('sensor_bmp085_temperature', $data['sensor_bmp085_temperature']); $ps->bindValue('sensor_bmp085_pressure', $data['sensor_bmp085_pressure']); $ps->execute(); return "ok"; }catch(Exception $e) { return "Error"; } }; header('Content-type: text/html; charset=utf-8'); $con = getDBConnection($db_host, $db_name, $db_user, $db_passwd); if(!$con) { echo "Error"; die(); } // get the HTTP method, path and body of the request $method = $_SERVER['REQUEST_METHOD']; $ip = $_SERVER['REMOTE_ADDR']; $request = explode('/', trim($_SERVER['PATH_INFO'],'/')); $input = json_decode(file_get_contents('php://input'),true); //base64 decoding of $input if($input) { foreach($input as $key=>$value) { $input[$key] = base64_decode($value); } } // get uri details $token = preg_replace('/[^a-z0-9_]+/i','',array_shift($request)); // check if token is valid if($token!=$app_token) { echo json_encode(array("response" => "Service currently not available!")); die(); } $params = array("con" => $con, "ip" => $ip); echo saveSensorData($params, $input); $con = null; //close DB Connection die();
Im Variablen Bereich müsst ihr die Parameter der MySQL Datenbank Verbindung eingeben. Außerdem legt ihr einen Security Token an. Das kann jede beliebige Zeichenkette sein. Ihr müsst das selbe Token für das Python Script setzen, ansonsten wird jeder Request abgelehnt. Im nächsten Bereich werden 2 Datenbankfunktionen definiert. Die erste baut eine Verbindung auf, die zweite nutzt diese um die erhaltene Daten geprüft als Datensatz abzuspeichern.
In weiterer Folge gibt es einige Sicherheitsprüfungen, werden diese alle bestanden, dann werden die Daten aus dem Request als JSON behandelt und in die Datenbank geschrieben.
Datenbank
Die Datenbank enthält lediglich eine einzige Tabelle. Diese wird mit dem folgenden Create Statement erstellt:
CREATE TABLE IF NOT EXISTS `sensor_data` ( `id` int(11) NOT NULL AUTO_INCREMENT, `datetime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `script_time` float NOT NULL, `api_pressure` float NOT NULL, `api_humidity` float NOT NULL, `api_temperature` float NOT NULL, `sensor_18b20_temperature` float NOT NULL, `sensor_dht11_temperature` float NOT NULL, `sensor_dht11_humidity` float NOT NULL, `sensor_bmp085_temperature` float NOT NULL, `sensor_bmp085_pressure` float NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1;
Details wie man eine MySQL Datenbank erstellt habe ich in meinem Blog bereits mehrfach behandelt. Kleiner Tipp: wird die Datenbank lediglich über diese PHP Schnittstelle befüllt fährt man sicherer, wenn man den Remote Zugriff darauf deaktiviert. Der Zugriff über localhost ist ausreichend.
Fazit
Hosen runter! So könnte man diesen Artikel beschreiben. Ich habe euch nun detailliert alle Komponenten beschrieben und den Source Code veröffentlicht den man für eine Funktionierende Schnittstelle auf einem Webserver benötigt. Über diese können wir nun auf einen sicheren Weg die Daten speichern. Dank den täglichen Backups des Hosters gehen keine Daten verloren. Selbst ein Totalausfall der Speicherkarte vom Raspberry Pi könnte daran nichts ändern.
Diese Artikelserie besteht aus folgenden Artikeln:
- Raspberry Pi DIY Wetterstation
- Raspberry Pi DIY Wetterstation – initiales Setup
- Raspberry Pi DIY Wetterstation – Temperatursensor
- Raspberry Pi DIY Wetterstation – Luftfeuchtigkeitsensor
- Raspberry Pi DIY Wetterstation – Luftdrucksensor
- Raspberry Pi DIY Wetterstation – Wetter API
- Raspberry Pi DIY Wetterstation – Webserver