Raspberry Pi Webserver Header

Raspberry Pi Webserver mit Flask

Hier auf Pollux Labs findest du bereits ein Tutorial für einen ESP8266 Webserver – in diesem Projekt programmierst du jedoch einen Raspberry Pi Webserver. Dieser wird regelmäßig Messdaten von einem ESP8266 empfangen und auf einer Webseite anzeigen. Neben einem Raspberry Pi benötigst du für dieses Tutorial nur noch einen Sensor – ich verwende im Folgenden den BMP180, um Temperatur und Luftfeuchtigkeit zu messen.

Außerdem verwende ich das Python-Modul Flask, um den Webserver zu programmieren. Damit kannst du sowohl einfache Webseiten erstellen als auch komplexere Applikationen, mit denen du, wie in unserem Fall, Hardware steuern und Messdaten anzeigen kannst.

Eine erste Webseite mit Flask

Bevor du dich einem Webserver widmest, lass uns mit den Grundlagen anfangen. Falls du Flask noch nicht auf deinem Raspberry Pi installiert hast, hole das im Terminal nach:

pip install Flask

Öffne nun einen Editor (z.B. Visudal Studio Code) und erstelle ein Python-Script mit folgendem Inhalt:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def home():
    return "Hallo, Raspberry Pi!"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Speichere es anschließend ab unter einem Namen deiner Wahl und starte es. Solltest du im Terminal daraufhin die Fehlermeldung „Port 5000 is in use by another program.“ erhalten, wähle hinter port= eine andere Zahl, zum Beispiel 5010. Sobald das Script ordnungsgemäß auf einem freien Port läuft, siehst du im Terminal die IP-Adresse deines Raspberry Pis. Zum Beispiel: „Running on http://127.0.0.1:5010“

Ausgabe des Servers im Terminal

Wenn du die IP-Adresse (127.0.0.1:5010) kopierst und sie im Browser öffnest, solltest du den kleinen Gruß sehen, der im Code hinterlegt ist. Neben dieser IP-Adresse erhältst du auch eine Alternative – hier 192.168.0.143:5000. Falls sich die erste Adresse nicht öffnen lässt, probiere es einmal mit dieser.

So funktioniert der code

Lass uns nun einen genaueren Blick auf das Python-Script werfen, um zu verstehen, was dort passiert. Zunächst importierst du die Klasse Flask (Mehr über Flask) aus dem gleichnamigen Modul:

from flask import Flask

Anschließend erstellst du eine Instanz dieser Klasse namens app. Mit (__name__) dahinter teilst du Python mit, dass alle etwaigen zusätzlichen Dateien im selben Ordner liegen, wie das Script selbst. Dazu später mehr.

Die folgenden drei Zeilen

@app.route('/')
def home():
    return "Hallo, Raspberry Pi!"

stellen nun die Webseite bereit – in unserem Fall ist das nur eine einzelne Startseite. Mit dem sogenannten Dekorator @app.route(‚/‘) bestimmst du, was passiert, wenn diese Startseite aufgerufen wird – also einfach nur die IP-Adresse. Hierfür dient der Schrägstrich /.

Wenn das nun also passiert (so wie du es vorhin im Browser getan hast), dann wird die darauf folgende Funktion def home(): aufgerufen. Darin befindet sich einfach nur die Anweisung, den String Hallo, Raspberry Pi! wiederzugeben – den du dann im Browser siehst. Später folgen nun noch weitere Dekoratoren und Funktionen, die deinem Raspberry Pi Webserver mehr Leben einhauchen werden.

Zuletzt die beiden Zeilen

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Dieser Code wird ausgeführt, wenn wir dieses Skript direkt ausführen (anstatt es als Modul in ein anderes Script zu importieren). In dem Fall startet der Webserver von Flask, der auf Anfragen von jedem Gerät im gleichen WLAN-Netzwerk am Port 5000 reagiert.

Sensordaten empfangen und anzeigen

So ein kleiner Gruß ist ja nett, aber eine wirklich praktische Anwendung ist besser. Wie wäre es, wenn dein Raspberry Pi Webserver regelmäßig Daten von einem ESP8266 empfängt und diese auf einer Webseite anzeigt?

Im Folgenden lernst du, wie du mit dem Sensor BMP180 an einem ESP8266 die Temperatur und Luftfeuchtigkeit misst und die Messdaten an den Raspberry Pi sendest. Du selbst kannst die aktuellen Daten dann auf einer Webseite einsehen.

Temperatur und Luftdruck auf dem Raspberry Pi Webserver

Hierfür benötigst du zweimal Code – einmal für den Raspberry Pi Webserver und einen Sketch für den ESP8266.

Das Python-Script für den Server

Zunächst kümmern wir uns um den Server. Damit dein Raspberry Pi die Daten in Empfang nehmen und auf einer Webseite anzeigen kann, ist nicht viel Code nötig. Du benötigst im Prinzip eine Funktion, die die gesendeten Messdaten entgegennimmt und eine weitere, um diese auf einer Webseite anzuzeigen. Hier das vollständige Script:

//Raspberry Pi Webserver
//polluxlabs.net

from flask import Flask, request, render_template_string
from datetime import datetime

app = Flask(__name__)

temperature = 0
pressure = 0
last_update = "Noch keine Daten empfangen"

@app.route('/update-sensor', methods=['POST'])
def update_sensor():
    global temperature, pressure, last_update
    temperature = request.form.get('temp')
    pressure = request.form.get('pressure')
    last_update = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    return "Daten aktualisiert", 200

@app.route('/')
def index():
    html = """
    <!DOCTYPE html>
    <html lang="de">
    <head>
        <meta charset="UTF-8">
        <title>BMP180 Sensordaten</title>
    </head>
    <body>
        <h1>BMP180 Sensordaten</h1>
        <p>Temperatur: {{ temperature }}°C</p>
        <p>Luftdruck: {{ pressure }} hPa</p>
        <p>Letzte Aktualisierung: {{ last_update }}</p>
    </body>
    </html>
    """
    return render_template_string(html, temperature=temperature, pressure=pressure, last_update=last_update)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

Kopiere den obigen Code uns speichere ihn in einer Date, die du z.B. webserver.py nennst.

So funktioniert das Script

Ein kurzer Blick auf die Funktionsweise des Raspberry Pi Webservers:

Daten empfangen

Der Teil des Scripts, der neue Daten empfängt, sieht so aus:

   @app.route('/update-sensor', methods=['POST'])
   def update_sensor():
       global temperature, pressure, last_update
       temperature = request.form.get('temp')
       pressure = request.form.get('pressure')
       last_update = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
       return "Daten aktualisiert", 200
  • Dieser Code wartet auf neue Messwerte vom Sensor.
  • Wenn neue Daten ankommen, werden sie in den Variablen temperature und pressure gespeichert.
  • last_update speichert den Zeitpunkt, an dem die Daten ankamen.

Daten anzeigen

Die Webseite, die die Daten anzeigt, wird hier erstellt:

   @app.route('/')
   def index():
       html = """
       <!DOCTYPE html>
       <html lang="de">
       <head>
           <meta charset="UTF-8">
           <title>BMP180 Sensordaten</title>
       </head>
       <body>
           <h1>BMP180 Sensordaten</h1>
           <p>Temperatur: {{ temperature }}°C</p>
           <p>Luftdruck: {{ pressure }} hPa</p>
           <p>Letzte Aktualisierung: {{ last_update }}</p>
       </body>
       </html>
       """
       return render_template_string(html, temperature=temperature, pressure=pressure, last_update=last_update)
  • Dieser Code erstellt eine einfache Webseite mithilfe von HTML.
  • Die Seite zeigt die aktuelle Temperatur, den Luftdruck und die Zeit der letzten Aktualisierung an.

Webseite einrichten

Der Server wird hier gestartet:

   if __name__ == '__main__':
       app.run(host='0.0.0.0', port=5000, debug=True)
  • Diese Zeilen starten den Webserver.
  • host='0.0.0.0' bedeutet, dass der Server von anderen Geräten im Netzwerk erreichbar ist.
  • port=5000 legt fest, dass die Webseite über Port 5000 erreichbar ist.

So arbeiten alle Teile zusammen: Der Sensor sendet Daten, das Script empfängt und speichert sie, und die Webseite zeigt sie an.

Starte den Raspberry Pi WebServer

Öffne nun auf deinem Raspberry Pi das Terminal und öffne dein Script, wobei du natürlich den Script-Namen verwendest, den du vergeben hast:

python3 webserver.py 

Anschluss des BMP180 und der Sketch für den ESP8266

Nun zum Sender, dem ESP8266, der mit dem Sensor BMP180 die aktuelle Temperatur und Luftfeuchtigkeit misst und an den Raspberry Pi weiterleitet. Bevor wir zum Code kommen, hier eine Skizze, wie du den Sensor am ESP8266 anschließt:

BMP180 am ESP8266 angeschlossen

Wenn du den Sensor angeschlossen hast, kann es direkt mit dem Sketch weitergehen. Nur ein Hinweis vorab: Solltest du noch nie ein Projekt mit dem BMP180 gebaut haben, fehlt dir vermutlich noch die zugehörige Bibliothek. Öffne in diesem Fall den Bibliotheksmanager in der Arduino IDE und installiere die Bibliothek Adafruit BMP085 Library. Falls du gefragt wirst, ob du weitere benötigte Bibliotheken installieren möchtest, antworte bitte mit Ja.

Doch nun zum Sketch:

//Sending data to the Raspberry Pi Webserver
//polluxlabs.net

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <Wire.h>
#include <Adafruit_BMP085.h>

const char* ssid = "DEIN NETZWERK";
const char* password = "DEIN PASSWORT";
const char* serverName = "http://SERVER-IP/update-sensor";

Adafruit_BMP085 bmp;

void setup() {
  Serial.begin(115200);
  
  WiFi.begin(ssid, password);
  Serial.println("Verbinde mit WLAN");
  while(WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Verbunden mit IP-Adresse: ");
  Serial.println(WiFi.localIP());

  if (!bmp.begin()) {
    Serial.println("BMP180 nicht gefunden, überprüfen Sie die Verkabelung!");
    while (1) {}
  }
}

void loop() {
  if(WiFi.status() == WL_CONNECTED) {
    WiFiClient client;
    HTTPClient http;

    float temperature = bmp.readTemperature();
    float pressure = bmp.readPressure() / 100.0F;

    // Daten für den POST-Request vorbereiten
    String httpRequestData = "temp=" + String(temperature) + "&pressure=" + String(pressure);

    // HTTP POST Request senden
    http.begin(client, serverName);
    http.addHeader("Content-Type", "application/x-www-form-urlencoded");
    
    int httpResponseCode = http.POST(httpRequestData);

    if (httpResponseCode > 0) {
      Serial.print("HTTP Response code: ");
      Serial.println(httpResponseCode);
    }
    else {
      Serial.print("Fehler code: ");
      Serial.println(httpResponseCode);
    }
    http.end();
  }
  else {
    Serial.println("WiFi getrennt");
  }

  delay(30000);  // Alle 30 Sekunden senden
}

So funktioniert der Sketch

Werfen wir nun einen Blick auf die einzelnen Bestandteile des Sketchs.

Einbindung der Bibliotheken

Am Anfang werden verschiedene Bibliotheken eingebunden, die für die WiFi-Verbindung, HTTP-Anfragen und die Kommunikation mit dem BMP180 Sensor benötigt werden.

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <Wire.h>
#include <Adafruit_BMP085.h>

Konfiguration

Anschließend legst du die WLAN-Zugangsdaten (SSID und Passwort) fest und hinterlegst die Adresse des Raspberry Pi Webservers. Ersetze hierbei SERVER-IP zum Beispiel durch 192.168.0.143:5000.

const char* ssid = "NETZWERK";
const char* password = "PASSWORT";
const char* serverName = "http://SERVER-IP/update-sensor";

Adafruit_BMP085 bmp;

Setup-Funktion

Hier stellst du die Verbindung zum WLAN her und initialisierst den Sensor:

void setup() {
  Serial.begin(115200);

  WiFi.begin(ssid, password);
  Serial.println("Verbinde mit WLAN");
  while(WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Verbunden mit IP-Adresse: ");
  Serial.println(WiFi.localIP());

  if (!bmp.begin()) {
    Serial.println("BMP180 nicht gefunden, überprüfen Sie die Verkabelung!");
    while (1) {}
  }
}

Loop-Funktion

Nun zum Kern, hier prüfst du zunächst, ob die Internetverbindung steht, liest Temperatur und Luftdruck ein und sendest die Messdaten an den Raspberry Pi Webserver per HTTP POST. Anschließend prüfst du die Antwort des Servers, kappst die Verbindung zum WLAN und wartest 30 Sekunden bis zur nächsten Messung.

void loop() {
  if(WiFi.status() == WL_CONNECTED) {
    WiFiClient client;
    HTTPClient http;

    float temperature = bmp.readTemperature();
    float pressure = bmp.readPressure() / 100.0F;

    // Daten für den POST-Request vorbereiten
    String httpRequestData = "temp=" + String(temperature) + "&pressure=" + String(pressure);

    // HTTP POST Request senden
    http.begin(client, serverName);
    http.addHeader("Content-Type", "application/x-www-form-urlencoded");

    int httpResponseCode = http.POST(httpRequestData);

    // Überprüfung der Antwort
    if (httpResponseCode > 0) {
      Serial.print("HTTP Response code: ");
      Serial.println(httpResponseCode);
    }
    else {
      Serial.print("Fehler code: ");
      Serial.println(httpResponseCode);
    }
    http.end();
  }
  else {
    Serial.println("WiFi getrennt");
  }

  delay(30000);  // Alle 30 Sekunden senden
}

Lade den Sketch nun auf deinen ESP8266. Wenn du nun die IP-Adresse des Raspberry Pi Webservers in einem Browser öffnest, solltest du bald darauf die aktuellen Messdaten des ESP8266 darauf sehen.

Hübsche die Webseite etwas auf

Klappt alles? Dann wäre vielleicht eine etwas ansprechendere Webseite eine gute Idee. Im Prinzip sind dir hier keine Grenzen gesetzt, du kannst hier mit HTML und CSS schalten und walten wie du möchtest. Eine weiteres Layout inklusive eines Graphen für den Verlauf der Messdaten könnte z.B. dieses hier sein:

Webseite mit Temperatur und Luftdruck

Das zugehörige Python-Script sieht folgendermaßen aus:

//Raspberry Pi Webserver
//polluxlabs.net

from flask import Flask, request, jsonify, render_template_string
from datetime import datetime
import json

app = Flask(__name__)

temperature = 20.0
pressure = 1013.25
last_update = "Noch keine Daten empfangen"
history = []

@app.route('/update-sensor', methods=['POST'])
def update_sensor():
    global temperature, pressure, last_update, history
    temperature = float(request.form.get('temp'))
    pressure = float(request.form.get('pressure'))
    last_update = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    
    history.append({"time": last_update, "temperature": temperature, "pressure": pressure})
    if len(history) > 10:
        history.pop(0)
    
    return "Daten aktualisiert", 200

@app.route('/get-data')
def get_data():
    return jsonify({
        "temperature": temperature,
        "pressure": pressure,
        "last_update": last_update,
        "history": history
    })

@app.route('/')
def index():
    html = """
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>BMP180 Sensordaten Dashboard</title>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <style>
        :root {
            --primary-color: #3498db;
            --secondary-color: #2c3e50;
            --background-color: #ecf0f1;
            --card-background: #ffffff;
        }
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            line-height: 1.6;
            color: var(--secondary-color);
            background-color: var(--background-color);
            margin: 0;
            padding: 0;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
        }
        header {
            background-color: var(--primary-color);
            color: white;
            text-align: center;
            padding: 1rem;
            margin-bottom: 2rem;
        }
        h1 {
            margin: 0;
        }
        .dashboard {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
            gap: 20px;
        }
        .card {
            background-color: var(--card-background);
            border-radius: 8px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
            padding: 20px;
            text-align: center;
        }
        .card-title {
            font-size: 1.2rem;
            color: var(--secondary-color);
            margin-bottom: 10px;
        }
        .card-value {
            font-size: 2.5rem;
            font-weight: bold;
            color: var(--primary-color);
        }
        .card-icon {
            font-size: 3rem;
            margin-bottom: 10px;
            color: var(--primary-color);
        }
        #updateTime {
            text-align: center;
            margin-top: 20px;
            font-style: italic;
        }
        #chart {
            width: 100%;
            height: 300px;
        }
    </style>
</head>
<body>
    <header>
        <h1>BMP180 Sensordaten Dashboard</h1>
    </header>
    <div class="container">
        <div class="dashboard">
            <div class="card">
                <i class="fas fa-thermometer-half card-icon"></i>
                <div class="card-title">Temperatur</div>
                <div class="card-value" id="temperature">--</div>
                <div>°C</div>
            </div>
            <div class="card">
                <i class="fas fa-tachometer-alt card-icon"></i>
                <div class="card-title">Luftdruck</div>
                <div class="card-value" id="pressure">--</div>
                <div>hPa</div>
            </div>
        </div>
        <div class="card" style="margin-top: 20px;">
            <canvas id="chart"></canvas>
        </div>
        <div id="updateTime">Letzte Aktualisierung: <span id="lastUpdate">--</span></div>
    </div>

    <script>
        let chart;

        function updateData() {
            fetch('/get-data')
                .then(response => response.json())
                .then(data => {
                    document.getElementById('temperature').textContent = data.temperature.toFixed(1);
                    document.getElementById('pressure').textContent = data.pressure.toFixed(2);
                    document.getElementById('lastUpdate').textContent = data.last_update;
                    
                    updateChart(data.history);
                });
        }

        function updateChart(history) {
            const ctx = document.getElementById('chart').getContext('2d');
            
            if (chart) {
                chart.destroy();
            }
            
            chart = new Chart(ctx, {
                type: 'line',
                data: {
                    labels: history.map(entry => entry.time),
                    datasets: [{
                        label: 'Temperatur (°C)',
                        data: history.map(entry => entry.temperature),
                        borderColor: 'rgb(255, 99, 132)',
                        tension: 0.1
                    }, {
                        label: 'Luftdruck (hPa)',
                        data: history.map(entry => entry.pressure),
                        borderColor: 'rgb(54, 162, 235)',
                        tension: 0.1
                    }]
                },
                options: {
                    responsive: true,
                    scales: {
                        x: {
                            display: true,
                            title: {
                                display: true,
                                text: 'Zeit'
                            }
                        },
                        y: {
                            display: true,
                            title: {
                                display: true,
                                text: 'Wert'
                            }
                        }
                    }
                }
            });
        }

        // Initialer Datenabruf
        updateData();

        // Aktualisiere Daten alle 30 Sekunden
        setInterval(updateData, 30000);
    </script>
</body>
</html>
    """
    return render_template_string(html)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

Wie geht es weiter?

Du hast nun einen Raspberry Pi Webserver, der Daten empfangen und auf einer Webseite visualisieren kann. Der nächste Schritt wäre eine Möglichkeit, über diese Webseite auch Geräte zu steuern, die wiederum z.B. an einem ESP8266 hängen und darüber gesteuert werden.

Bausätze für dein nächstes Projekt

Alles, was du für dein nächstes Projekt brauchst – Bausätze inklusive Anleitung als E-Book und der benötigten Hardware.

ESP8266 Projekt

Wetterstation & Vorhersage

Miss die aktuelle Temperatur und Luftfeuchtigkeit und zeige dazu die Wettervorhersage auf einem OLED-Display an.

Bausatz anschauen

Arduino Projekt

Pflanzenwächter

Braucht deine Pflanze Wasser? Dieses Arduino Projekt gibt dir Bescheid, wenn es so weit ist.

Bausatz anschauen

ESP8266 Projekt

Webserver

Lerne, wie du mit deinem Webserver Messdaten ausgibst, Geräte steuerst und dir mit HTML und CSS ein Interface erstellst.

Bausatz anschauen

Arduino Projekt

Wetterstation

Baue deine eigene Wetterstation, die dir Temperatur und Luftfeuchtigkeit anzeigt.

Bausatz anschauen

Auch interessant

Mehr Projekte für Arduino, ESP32, Raspi & Co
Werde Mitglied bei Pollux Labs und finde dein nächstes Projekt. Zum Beispiel:
Über 100 Maker sind bereits Mitglied bei Pollux Labs
  • ESP32 Internetradio
  • Arduino Wetterstation
  • ESP8266 & Raspi Webserver
  • Automatische Bewässerung
  • ... und viele mehr!