Smart Home – Pollux Labs https://polluxlabs.net Arduino, ESP32 & ESP8266 | Projekte & Tutorials Tue, 14 Oct 2025 09:04:32 +0000 de hourly 1 https://wordpress.org/?v=6.8.3 https://polluxlabs.net/wp-content/uploads/2020/05/cropped-pollux-labs-p-32x32.png Smart Home – Pollux Labs https://polluxlabs.net 32 32 So erzeugst du Laufschrift auf einer LED-Matrix https://polluxlabs.net/arduino-projekte/laufschrift-auf-der-led-matrix/ Thu, 09 Oct 2025 09:39:55 +0000 https://polluxlabs.net/?p=19953 Eine LED-Matrix mit Laufschrift ist ein tolles Projekt für alle, die sich mit Displays und Effekten am Arduino beschäftigen möchten. Mit nur wenigen Bauteilen lässt sich ein beeindruckender Effekt erzielen. In diesem Projekt lernst du zunächst, wie du einen Text als Laufschrift über die Matrix wandern lässt. Danach machst du das Projekt etwas dynamischer: Du kannst den Text über den Seriellen Monitor aktualisieren. Und zuletzt lässt du die LED-Matrix aktuelle Daten vom Temperatur- und Luftfeuchtigkeitsensor DHT11 anzeigen.

Was ist eine LED-Matrix?

Eine LED-Matrix ist ein Display aus vielen kleinen Leuchtdioden (LEDs), die in Reihen und Spalten angeordnet sind – meist in 8×8 Feldern. Die Ansteuerung übernimmt ein spezieller Treiberchip, z. B. der MAX7219.

Diese Matrizen gibt es einzeln (also mit den erwähnten 8×8 Feldern) zu kaufen. Ein Projekt, das diese Matrix verwendet ist z.B. das Babyphone. Damit wäre eine Laufschrift allerdings sehr schwer zu lesen. Besser eignen sich hierfür Module, bei denen mehrere dieser Quadrate aneinandergereiht sind – im Folgenden sind das vier.

Der Anschluss am Arduino

Für dieses Projekt brauchst du diese Hardware:

  • 1× Arduino UNO oder ein kompatibles Board
  • 1× LED-Matrix mit MAX7219 (4 Module)
  • Breadboard und Kabel
  • (optional) Temperatursensor DHT11

Die LED-Matrix verbindest du über die SPI-Schnittstelle des Arduinos – deshalb musst du zwingend die entsprechenden Pins 10, 11 und 13 verwenden. Orientiere dich beim Aufbau an dieser Skizze:

Anschluss einer LED Matrix am Arduino UNO

Und hier noch einmal als Tabelle:

Matrix-PinArduino-Pin
VCC5 V
GNDGND
DIN11
CS10
CLK13

Die benötigten Bibliotheken

Für die Steuerung der LED-Matrix und um die Laufschrift zu erzeugen benötigst du diese beiden Bibliotheken:

  • MD_Parola – für Textanimationen und Effekte
  • MD_MAX72xx – für die Steuerung der Matrix selbst

Beide kannst du bequem über den Bibliotheksverwalter in der Arduino IDE installieren. Normalerweise wirst du bereits bei der Installation der Bibliothek MD_Parola gefragt, ob du MD_MAX72xx gleich mit installieren möchtest. Bestätige das mit einfach mit Ja.

Der Sketch für die Laufschrift

Der folgende Sketch zeigt, wie einfach man eine Laufschrift anzeigt. Hier läuft der Text Pollux Labs kontinuierlich über die Anzeige.

// -----------------------------------------------------------
// Einfache Laufschrift mit MAX7219-LED-Matrix
// polluxlabs.net
// -----------------------------------------------------------

#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>

#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4
#define DATA_PIN 11
#define CLK_PIN  13
#define CS_PIN   10

MD_Parola P = MD_Parola(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);

void setup() {
  P.begin();
  P.setIntensity(5);
  P.displayClear();
  P.displayText("Pollux Labs", PA_CENTER, 50, 0, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
}

void loop() {
  if (P.displayAnimate()) {
    P.displayReset();
  }
}

So funktioniert der Sketch

Lass uns einen kurzen Blick auf die wichtigsten Teile des Sketchs werfen:

  • Die Bibliothek MD_Parola übernimmt das Anzeigen und Animieren des Textes.
  • Mit dem Befehl P.setIntensity(5); stellst du die Helligkeit der LED-Matrix ein.
  • Die Zeile P.displayText("Pollux Labs", PA_CENTER, 50, 0, PA_SCROLL_LEFT, PA_SCROLL_LEFT); steuert, wie und wo der Text auf dem Display angezeigt wird. Der erste Parameter ist der Text selbst, PA_CENTER richtet ihn zentriert aus, 50 bestimmt die Scroll-Geschwindigkeit (je kleiner, desto schneller), 0 ist die Pause vor dem erneuten Start, und die beiden PA_SCROLL_LEFT definieren den Ein- und Ausblendeeffekt (hier beides nach links).
  • Die Funktion displayAnimate() wird im Loop aufgerufen und steuert den Ablauf.
  • Mit displayReset() startet die Animation nach dem Durchlauf erneut.

___STEADY_PAYWALL___

So änderst du die Laufrichtung des Texts

Wenn du etwas mit dem Sketch und der LED-Matrix experimentieren möchtest, probiere einmal, die Laufrichtung des Textes zu ändern. Die Befehle hierfür lauten PA_SCROLL_RIGHT, PA_SCROLL_UP und PA_SCROLL_DOWN.

Aktualisiere die Laufschrift über den Seriellen Monitor

Deine Laufschrift funktioniert schon einmal – allerdings ist der Text fest im Sketch integriert. Für einen neuen Text müsstest du den Sketch also anpassen und neu auf den Arduino laden. Eleganter geht das mit dem Seriellen Monitor. Der folgende Sketch startet wieder mit dem Schriftzug Pollux Labs – wenn du allerdings den Seriellen Monitor in der Arduino IDE öffnest und im Feld Nachricht einen neuen Text eingibst sowie Enter drückst, erscheint dieser auf deiner LED-Matrix. Achte hierfür darauf, dass im Seriellen Monitor die gleiche Baudrate (9600) wie im Sketch eingestellt ist.


// -----------------------------------------------------------
// Laufschrift mit MAX7219-LED-Matrix und Texteingabe per Serial
// -----------------------------------------------------------
// Bibliotheken
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>

// Hardware-Einstellungen
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4
#define DATA_PIN 11
#define CLK_PIN  13
#define CS_PIN   10

// Parola-Objekt erstellen
MD_Parola P = MD_Parola(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);

// Variablen
String inputText = "Pollux Labs";  // Standardtext beim Start
bool newText = false;

void setup() {
  Serial.begin(9600);
  Serial.println("Gib einen Text ein und drücke ENTER:");
  
  P.begin();
  P.setIntensity(5);
  P.displayClear();
  P.displayText(inputText.c_str(), PA_CENTER, 50, 0, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
}

void loop() {
  // Prüfen, ob neuer Text vom Serial Monitor vorliegt
  if (Serial.available() > 0) {
    inputText = Serial.readStringUntil('\n');  // Eingabe bis Zeilenende lesen
    inputText.trim();                          // Entfernt evtl. Leerzeichen
    if (inputText.length() > 0) {
      newText = true;
    }
  }

  // Wenn eine Animation abgeschlossen ist, neu starten oder neuen Text setzen
  if (P.displayAnimate()) {
    if (newText) {
      P.displayClear();
      P.displayText(inputText.c_str(), PA_CENTER, 50, 0, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
      newText = false;
    }
    P.displayReset();
  }
}

Der Trick besteht darin, dass das Programm im loop() ständig überprüft, ob neue Daten über die serielle Schnittstelle eintreffen. Wenn der Benutzer im seriellen Monitor eine Zeile eingibt und mit der Eingabetaste bestätigt, erkennt das Programm dies über den Befehl Serial.available(). Anschließend liest es die eingegebene Zeile mit Serial.readStringUntil('\n') ein, also bis zum Zeilenendezeichen. Danach wird der Text mithilfe von displayText() auf der LED-Matrix angezeigt, sodass jede neue Eingabe sofort als Laufschrift sichtbar wird.

Ein Problem gibt es jedoch: Sobald du deinen Arduino neu startest, erscheint wieder die im Sketch hinterlegte Laufschrift Pollux Labs. Das behebst du, indem du deinen Text im EEPROM deines Arduinos speicherst.

Neuen Text im EEPROM speichern

Der folgende Sketch unterscheidet sich von der vorigen Version in mehreren Punkten und sorgt dafür, dass der eingegebene Text dauerhaft gespeichert bleibt und sich weiterhin über den Seriellen Monitor ändern lässt.

Zunächst wird der eingegebene Text jetzt im EEPROM gespeichert – also in einem kleinen, nichtflüchtigen Speicherbereich des Arduinos. Dadurch bleibt der Text auch nach einem Neustart oder Stromausfall erhalten. Beim Einschalten liest der Sketch den zuletzt gespeicherten Text wieder aus dem EEPROM und zeigt ihn automatisch auf der LED-Matrix an. Ist der Speicher leer oder enthält ungültige Daten, wird automatisch der Standardtext Pollux Labs geladen und gespeichert.

Außerdem überprüft der Code im loop(), ob über die serielle Schnittstelle neue Eingaben vorliegen. Wenn du im Seriellen Monitor eine Zeile eingibst und mit Enter bestätigst, wird der neue Text sofort angezeigt und gleichzeitig dauerhaft im EEPROM abgelegt. Damit das funktioniert, musst du im Seriellen Monitor wie gehabt die Baudrate auf 9600 einstellen und bei den Zeilenende-Optionen Beides CR/LF oder Neue Zeile wählen. Nur so erkennt der Sketch das Ende deiner Eingabe korrekt.

// -----------------------------------------------------------
// Laufschrift mit MAX7219-LED-Matrix, Texteingabe per Serial
// und Speicherung im EEPROM (robuste Version für Arduino UNO/Nano)
// -----------------------------------------------------------

#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <EEPROM.h>

#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4
#define DATA_PIN 11
#define CLK_PIN  13
#define CS_PIN   10

MD_Parola P = MD_Parola(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);

String inputText = "Pollux Labs";
bool newText = false;

// --- Text aus EEPROM lesen ---
void readEEPROMString(String &text) {
  char c;
  text = "";
  for (int i = 0; i < 100; i++) {
    c = EEPROM.read(i);
    if (c == '\0' || c == 0xFF) break;  // Ende oder leerer Speicher
    text += c;
  }
}

// --- Text ins EEPROM schreiben ---
void writeEEPROMString(const String &text) {
  int len = text.length();
  for (int i = 0; i < len; i++) {
    EEPROM.write(i, text[i]);
  }
  EEPROM.write(len, '\0');  // Nullterminator
}

void setup() {
  Serial.begin(9600);
  delay(200);
  Serial.println("Gib einen Text ein und drücke ENTER:");
  Serial.println("(Zeilenende: 'Neue Zeile' oder 'Beides NL & CR')");

  readEEPROMString(inputText);

  // Wenn EEPROM leer oder ungültig, Standardtext schreiben
  if (inputText.length() == 0) {
    inputText = "Pollux Labs";
    writeEEPROMString(inputText);
  }

  P.begin();
  P.setIntensity(5);
  P.displayClear();
  P.displayText(inputText.c_str(), PA_CENTER, 50, 0, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
}

void loop() {
  // Prüfen, ob neuer Text über Serial kommt
  if (Serial.available() > 0) {
    inputText = Serial.readStringUntil('\n');
    inputText.trim();

    if (inputText.length() > 0) {
      writeEEPROMString(inputText);
      newText = true;
    }
  }

  if (P.displayAnimate()) {
    if (newText) {
      P.displayClear();
      P.displayText(inputText.c_str(), PA_CENTER, 50, 0, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
      newText = false;
    }
    P.displayReset();
  }
}

Die wichtigsten Funktionen in diesem Sketch

Lass uns kurz einen Blick darauf werfen, wie hier das Speichern und Lesen von Text mit dem EEPROM funktioniert.

  1. Beim Start ruft der Sketch readEEPROMString() auf → liest gespeicherten Text aus.
  2. Ist der EEPROM leer, wird der Standardtext Pollux Labs gespeichert.
  3. Wenn du im Seriellen Monitor etwas Neues eintippst und ENTER drückst,
    wird der Text mit writeEEPROMString() gespeichert.
  4. Beim nächsten Einschalten liest der Arduino diesen Text wieder aus – und zeigt ihn automatisch an.
c = EEPROM.read(i);

Hier wird Zeichen für Zeichen aus dem Speicher gelesen, bis ein Ende-Zeichen ('\0') gefunden wird. So entsteht wieder der komplette Text, den du vorher gespeichert hattest.

EEPROM.write(i, text[i]);

Hier werden alle Zeichen deines Textes nacheinander im EEPROM abgelegt.

Hinweis: Der EEPROM hat eine begrenzte Lebensdauer (typisch etwa 100.000 Schreibvorgänge pro Speicherzelle). Das heißt, du solltest ihn nicht ständig in jeder Loop-Schleife beschreiben – aber für Texteingaben ist das völlig unproblematisch.

EEPROM.write(len, '\0');

Am Ende des Schreibvorgangs wird ein sogenannter Nullterminator gespeichert ('\0'). Das markiert das Ende des Textes. Beim Auslesen weiß der Sketch dann, wo der gespeicherte String aufhört.

Wetterdaten als Laufschrift

Lass uns zuletzt noch schnell ein praktisches Projekt bauen: Eine kleine Wetterstation. Der Sensor DHT11 ermittelt hier die aktuelle Temperatur und Luftfeuchtigkeit und dein Arduino zeigt beides als Laufschrift auf der LED-Matrix an.

Ergänze den DHT11 auf deinem Breadboard folgendermaßen:

DHT11 und LED-Matrix am Arduino UNO

Neben der Stromversorgung mit 5V schließt du hier den DHT11 an den Digitalpin 7 deines Arduinos an.

Der Sketch für die Wetterstation

Und hier nun der passende Sketch. Falls du bisher noch nicht mit dem DHT11 gearbeitet hast, wirf einen Blick in dieses Tutorial. Dort erfährst du, wie du die benötigten Bibliotheken installierst und wie du die aktuellen Wetterdaten ausliest.

Lade anschließend den folgenden Sketch auf deinen Arduino:

#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <DHT.h>

// --- Display Setup ---
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4  // Anzahl der Module (4 x 8x8 = 32 Spalten)
#define CS_PIN 10      // Chip Select (LOAD)

MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);

// --- DHT11 Setup ---
#define DHTPIN 7
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);

// --- Ablauf-Variablen ---
uint8_t state = 0;         // 0="Temp" Wort, 1=Temp Wert, 2="Luft" Wort, 3=Luft Wert
bool stageActive = false;  // true wenn current displayText bereits gesetzt wurde

const uint8_t speed = 25;               // Scroll-Geschwindigkeit (kleiner = schneller)
const unsigned long pauseWord = 2000;   // 2s Pause für das Wort
const unsigned long pauseValue = 5000;  // 5s Pause für den Wert

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

  // Display init
  P.begin();
  P.setIntensity(3);  // Helligkeit 0-15
  P.displayClear();

  // DHT init
  dht.begin();
  delay(1000);  // kurzer Warmup

  // Start mit "Temp"
  stageActive = false;  // initial wird im loop der erste Schritt gesetzt
}

void loop() {
  // Wenn noch kein Text für den aktuellen Step gesetzt wurde -> setze ihn
  if (!stageActive) {
    char buf[20];

    switch (state) {
      case 0:  // Wort "Temp" linksbündig, scroll in/out, 1s Pause
        strcpy(buf, "Temp");
        P.displayText(buf, PA_LEFT, speed, pauseWord, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
        stageActive = true;
        break;

      case 1:
        {  // Temperaturwert rechtsbündig, scroll in/out, 2s Pause
          float t = dht.readTemperature();
          if (isnan(t)) {
            strcpy(buf, "Err");
          } else {
            dtostrf(t, 4, 1, buf);  // Format: z.B. "23.5"
            strcat(buf, "C");
          }
          P.displayText(buf, PA_RIGHT, speed, pauseValue, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
          stageActive = true;
          break;
        }

      case 2:  // Wort "Luft" linksbündig, scroll in/out, 1s Pause
        strcpy(buf, "Luft");
        P.displayText(buf, PA_LEFT, speed, pauseWord, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
        stageActive = true;
        break;

      case 3:
        {  // Luftfeuchte rechtsbündig, scroll in/out, 2s Pause
          float h = dht.readHumidity();
          if (isnan(h)) {
            strcpy(buf, "Err");
          } else {
            dtostrf(h, 3, 0, buf);  // Format: z.B. "45"
            strcat(buf, "%");
          }
          P.displayText(buf, PA_RIGHT, speed, pauseValue, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
          stageActive = true;
          break;
        }
    }
  }

  // Animation durchlaufen lassen; wenn fertig, auf nächsten Schritt wechseln
  if (P.displayAnimate()) {
    stageActive = false;
    state = (state + 1) % 4;  // Zyklus 0..3
  }
}

Anschließend sollten die aktuelle Temperatur und Luftfeuchtigkeit im Wechsel durch das Display laufen. Die Geschwindigkeit bzw. die Zeit, wie lange der Text (Temp und Luft) und der Wert stehen bleiben, stellst du hier im Sketch ein:

const unsigned long pauseWord = 2000;   // 2s Pause für das Wort
const unsigned long pauseValue = 5000;  // 5s Pause für den Wert

Und das soll es erst einmal gewesen sein. Viel Erfolg und Spaß mit deiner LED-Matrix!

]]>
Mit einem Durchflussmesser den Wasserverbrauch messen https://polluxlabs.net/arduino-projekte/mit-einem-durchflussmesser-den-wasserverbrauch-messen/ Thu, 25 Sep 2025 11:19:44 +0000 https://polluxlabs.net/?p=19918 In diesem Projekt erfährst du, wie du mit einem Arduino, einem Durchflussmesser und einem OLED-Display einen einfachen, aber effektiven Wasserzähler baust. Der Zähler misst die verbrauchte Wassermenge in Echtzeit und visualisiert sie direkt auf dem Display.

Benötigte Bauteile

Diese Bauteile brauchst du für dieses Projekt:

  • Mikrocontroller: Zum Beispiel ein Arduino Nano oder Arduino UNO
  • Durchflussmesser: Ein Turbinen-Durchflussmesser (z.B. YF-S201C*)
  • Display: Ein 0,91“ OLED-Display (SSD1306-Chip)
  • Breadboard und Jumper-Kabel
  • Messbecher: Für die Kalibrierung, idealerweise 500 ml oder 1 Liter

Aufbau des Wasserzählers

Der Aufbau ist kompakt und nutzt die I²C-Pins des OLED-Displays und deines Arduinos, um die Verkabelung zu vereinfachen. Der Durchflussmesser benötigt (neben 5V und Erde) nur eine Verbindung. Auf der folgenden Skizze verwende ich einen Arduino Nano, du kannst aber auch einen UNO oder jeden anderen Arduino oder ESP32/8266 verwenden.

Anschluss des Durchflussmessers am Arduino

Hier noch einmal die Verbindungen im Detail:

Verbindung des Durchflussmessers:

  • VCC: An den 5V-Pin des Arduinos
  • GND: An einen GND-Pin des Arduinos
  • Signal: An den digitalen Pin D2 des Arduinos. Dieser Pin ist für Interrupts geeignet, was eine präzise Messung der Impulse ermöglicht.

Verbindung des OLED-Displays:

  • VCC: An den 3.3V-Pin des Arduinos
  • GND:An einen GND-Pin des Arduinos
  • SDA (Daten): An den SDA-Pin des Arduino. Beim Nano und UNO ist das Pin A4.
  • SCL (Takt): An den SCL-Pin des Arduinos. Beim Nano und UNO ist das Pin A5.

Die benötigten Bibliotheken

Für dieses Projekt benötigst du zwei Bibliotheken für das OLED-Display. Suche hierfür im Bibliotheksverwalter nach Adafruit SSD1306. Bei der Installation wirst du gefragt, ob du auch die Bibliothek Adafruit GFX Library installieren möchtest – bestätige das bitte mit Ja.

Für den Durchflussmesser benötigst du in der Regel keine Bibliothek – seine Impulse verarbeitest du selbst im Sketch.

Der Sketch für den Wasserzähler

Der folgende Code liest die Impulse des Durchflussmessers und zeigt die gemessene Wassermenge auf dem OLED-Display an. Im Folgenden gehe ich von einer Wassermenge von 2,25ml pro Impuls aus. Ich empfehle dir jedoch, deinen Sensor selbst zu kalibrieren.

//Wasserverbrauch messen mit einem Durchflussmesser
//polluxlabs.net

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// Deklariere die Display-Parameter für ein 0,91'' Display
#define SCREEN_WIDTH 128 // OLED-Breite in Pixel
#define SCREEN_HEIGHT 32 // OLED-Höhe in Pixel

// Deklariere ein OLED-Display Objekt
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// Pin für den Durchflussmesser
const int sensorPin = 2;

// Variable für die Impulszählung. `volatile` ist wichtig für Interrupts.
volatile long impulse_count = 0;

// Die kalibrierte Konstante für deinen Sensor (2.25 ml/impuls)
const float ML_PER_IMPULSE = 2.25;

void setup() {
  // Startet die serielle Kommunikation zur Fehlersuche
  Serial.begin(9600);

  // Initialisiert das OLED-Display
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306-Zuweisung fehlgeschlagen"));
    for (;;); // Endlosschleife, wenn das Display nicht gefunden wird
  }
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  
  // Initialisiert den Sensor-Pin als Eingang
  pinMode(sensorPin, INPUT);
  
  // Richtet einen Interrupt ein, der die Funktion count_impulses bei jedem Impuls aufruft
  attachInterrupt(digitalPinToInterrupt(sensorPin), count_impulses, RISING);
}

void loop() {
  // Eine Kopie des Zählers erstellen, um race conditions zu vermeiden
  long current_impulses = impulse_count; 

  // Berechnung des Wasserverbrauchs
  float water_volume_ml = current_impulses * ML_PER_IMPULSE;

  // Löscht den Display-Inhalt
  display.clearDisplay();

  // Zeigt den Titel an
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.println("Wasserverbrauch:");

  // Zeigt den Wert an
  display.setTextSize(2);
  display.setCursor(0, 16);
  display.print(water_volume_ml);
  display.print(" ml");

  // Aktualisiert das Display
  display.display();

  // Kleine Pause, um das Display nicht zu überfordern
  delay(100);
}

// Interrupt-Service-Routine (ISR)
// Diese Funktion wird automatisch bei jedem Impuls aufgerufen
void count_impulses() {
  impulse_count++;
}

Kalibrierung des Sensors

Der Wert in der Zeile const float ML_PER_IMPULSE = 2.25; ist entscheidend für die Genauigkeit deiner Messung. Je nachdem, welches Modell du verwendest oder auch, wenn dein Sensor nicht präzise misst, kann dieser Wert abweichen. Um die richtige Wassermenge pro Impuls zu ermitteln, gehe wie folgt vor:

  1. Vorbereitung: Halte einen Messbecher mit bekanntem Volumen (z. B. 500 ml) bereit. Lade den obenstehenden Sketch auf deinen Arduino.
  2. Nullstellung: Starte den Sketch neu, indem du den Reset-Knopf auf deinem Arduino drückst. Das Display sollte 0.00 ml anzeigen.
  3. Messung: Lasse genau 500 ml Wasser konstant schnell durch den Sensor in den Messbecher laufen.
  4. Wert ablesen: Warte, bis die Messung auf dem Display nicht mehr steigt. Notiere den angezeigten Wert (z.B. 650 ml).
  5. Neuer Menge: Berechne die korrekte Menge Wasser pro Impuls: 2,25ml x (500ml / 650ml) = 1,73ml/Impuls
  6. Code anpassen: Ändere die Zeile im Sketch zu const float ML_PER_IMPULSE = 1.73;.
  7. Testen: Lade den Sketch erneut hoch und wiederhole den Test mit 500 ml Wasser. Der angezeigte Wert sollte nun sehr nah an 500 ml liegen.

Um das Ergebnis genauer zu machen, kannst du auch mehrere Messungen durchführen und einen Mittelwert finden. Es kann durchaus sein, dass z.B. die Fließgeschwindigkeit des Wassers Auswirkungen auf die Zahl der gemessenen Impulse hat.

So funktioniert der Sketch

Lass uns schnell einen Blick auf die wichtigsten Teile des Sketchs werfen:

1. Die Setup-Funktion

Der Befehl attachInterrupt() ist hier das Herzstück. Anstatt im Loop ständig zu prüfen, ob ein Impuls vom Sensor kommt, richtest du hier einen sogenannten Hardware-Interrupt ein. Das bedeutet, dass der Arduino-Chip sofort seine aktuelle Aufgabe unterbricht und direkt zu der kleinen Funktion count_impulses() springt, sobald er ein Signal am Pin D2 empfängt. Das macht die Messung extrem reaktionsschnell und präzise.

2. Der Loop: Die Hauptlogik

Dieser Teil wird immer wieder wiederholt.

  • Impulse auslesen: long current_impulses = impulse_count; kopiert den Wert des Volatile-Zählers. Das ist eine Schutzmaßnahme. Da der Zähler impulse_count jederzeit von der Interrupt-Funktion geändert werden könnte, sichern wir den Wert, bevor wir damit rechnen, um Fehler zu vermeiden.
  • Berechnung: float water_volume_ml = current_impulses * ML_PER_IMPULSE; ist der Kern des Projekts. Hier wird die Anzahl der Impulse mit dem Kalibrierungsfaktor multipliziert, um das tatsächliche Wasservolumen in Millilitern zu erhalten.
  • Display-Update: Die folgenden Befehle, wie display.clearDisplay(), display.setCursor() und display.print(), kümmern sich darum, die berechneten Daten auf dem OLED-Display darzustellen. Der Befehl display.display() sendet am Ende die gesammelten Daten an das Display, um die Anzeige zu aktualisieren.
  • Pause: delay(100); sorgt für eine kurze Pause. Das ist wichtig, um zu verhindern, dass das Display zu schnell aktualisiert wird und um die CPU-Ressourcen des Arduinos zu schonen.

3. Die Funktion count_impulses(): Der Interrupt

Dies ist die kleine, aber sehr wichtige Funktion, die durch den Interrupt-Befehl ausgelöst wird. Sie wird nicht vom Loop aufgerufen, sondern springt automatisch in Aktion, sobald der Sensor einen Impuls sendet. Ihre einzige Aufgabe ist es, den Zähler impulse_count um eins zu erhöhen.

Wie geht es weiter?

Du kannst du nun den Wasserverbrauch messen und auf einem Display anzeigen. Eine interessante Weiterentwicklung könnte z.B. ein LED-Ring sein, der anzeigt, wie sehr du dich beim Händewaschen einem festgelegten Maximalverbrauch näherst. Oder du misst in deinem Garten den Wasserverbrauch und speicherst die Daten z.B. bei Adafruit IO in der Cloud.

]]>
RFID-Karten mit dem Raspberry Pi 5 und RC522 auslesen https://polluxlabs.net/raspberry-pi-projekte/rfid-karten-mit-dem-raspberry-pi-5-und-rc522-auslesen/ Tue, 01 Jul 2025 07:22:58 +0000 https://polluxlabs.net/?p=19458 Dieses Tutorial führt dich durch den Prozess, einen RC522 RFID-Reader über die SPI-Schnittstelle mit einem Raspberry Pi 5 zu verbinden und mit einer minimalistischen Python-Bibliothek die eindeutige ID (UID) von RFID-Karten auszulesen. Viele bestehende Anleitungen für den RC522 nutzen die RPi.GPIO-Bibliothek, die auf dem Raspberry Pi 5 nicht mehr ohne Weiteres funktioniert. Dieses Tutorial löst dieses Problem, indem es eine schlanke Python-Bibliothek nutzt, die auf der modernen und kompatiblen gpiod-Bibliothek aufbaut.

✨ Kernfunktionen der Bibliothek

  • Schlankes Design: Alle notwendigen Funktionen sind in einer einzigen, leichtgewichtigen Datei zusammengefasst.
  • Einfache Integration: Die Bibliothek kann einfach durch Kopieren der Datei in dein Projekt eingebunden werden.
  • Raspberry Pi 5 Kompatibel: Nutzt die moderne gpiod-Bibliothek anstelle des veralteten RPi.GPIO.
  • SPI-Fokus: Optimiert für eine stabile und schnelle SPI-Kommunikation.

Hardware: Voraussetzungen und Verkabelung

Zuerst verbinden wir den RC522-Reader mit dem Raspberry Pi.

Benötigte Hardware

  • Raspberry Pi 5
  • RC522 RFID-Reader-Modul
  • RFID-Karten oder -Tags (z.B. MIFARE Classic 1K)
  • Jumper-Kabel (Dupont-Kabel)

Verkabelungsplan (SPI)

Verbinde das RC522-Modul wie in der folgenden Tabelle beschrieben mit den GPIO-Pins deines Raspberry Pi.

RC522 PinPi 5 Pin (Physisch)GPIO (BCM)Beschreibung
SDA/SSPin 24GPIO 8Chip Select (CS)
SCKPin 23GPIO 11SPI Clock
MOSIPin 19GPIO 10Master Out -> Slave In
MISOPin 21GPIO 9Master In <- Slave Out
RSTPin 15GPIO 22Reset
GNDPin 6, 9, etc.Ground (Masse)
VCCPin 1 oder 173.3V Stromversorgung

Wichtiger Hinweis: Schließe das RC522-Modul ausschließlich an einen 3.3V-Pin an. Die Verwendung eines 5V-Pins kann das Modul dauerhaft beschädigen!

Software: System einrichten

Bevor wir den Code ausführen können, müssen die SPI-Schnittstelle aktiviert und die notwendigen Bibliotheken installiert werden.

a) SPI-Schnittstelle aktivieren

Falls noch nicht geschehen, aktiviere die SPI-Schnittstelle auf deinem Raspberry Pi.

  1. Öffne ein Terminal und gib sudo raspi-config ein.
  2. Navigiere zu 3 Interface Options -> I4 SPI.
  3. Bestätige die Frage, ob die SPI-Schnittstelle aktiviert werden soll, mit „Ja“ oder „Yes“.
  4. Beende das Konfigurationstool. Ein Neustart kann erforderlich sein.

b) Systemabhängigkeiten installieren

Installiere die Python-Bibliotheken spidev für die SPI-Kommunikation und libgpiod für die GPIO-Steuerung.

sudo apt update
sudo apt install python3-spidev python3-libgpiod -y

Bibliothek und Beispiel-Code

Das gesamte Projekt besteht aus nur zwei Dateien: der Bibliothek selbst und einem Beispielskript, das die Anwendung demonstriert. Du findest den Code hier, kannst ihn dir aber auch aus unserem GitHub-Repository herunterladen.

a) Die Bibliotheksdatei: rc522_spi_library.py

Dies ist die eigentliche Bibliothek. Sie ist in sich geschlossen und enthält alle Klassen, Konstanten und Funktionen, um mit dem RC522 zu kommunizieren. Kopiere den folgenden Code und speichere ihn in einer Datei mit dem Namen rc522_spi_library.py.

# -*- coding: utf-8 -*-
#
# A lean Python library for the RC522 RFID reader on the Raspberry Pi 5 via SPI.
# Combines the necessary classes and constants for easy integration.
#
# Pollux Labs
# polluxlabs.net
#

import time
import logging

try:
    import gpiod
    import spidev
except ImportError:
    print("Important Note: The hardware libraries 'gpiod' and 'spidev' could not be imported.")
    print("This library is intended for use on a Raspberry Pi with the SPI interface enabled.")
    gpiod = None
    spidev = None

# --- Constants ---
# From `constants.py`

class RC522Registers:
    COMMAND_REG = 0x01
    COM_IRQ_REG = 0x04
    DIV_IRQ_REG = 0x05
    ERROR_REG = 0x06
    STATUS2_REG = 0x08
    FIFO_DATA_REG = 0x09
    FIFO_LEVEL_REG = 0x0A
    CONTROL_REG = 0x0C
    BIT_FRAMING_REG = 0x0D
    TX_CONTROL_REG = 0x14
    CRC_RESULT_REG_MSB = 0x21
    CRC_RESULT_REG_LSB = 0x22
    VERSION_REG = 0x37
    T_MODE_REG = 0x2A
    T_PRESCALER_REG = 0x2B
    T_RELOAD_REG_H = 0x2C
    T_RELOAD_REG_L = 0x2D
    MODE_REG = 0x11
    TX_AUTO_REG = 0x15

class RC522Commands:
    IDLE = 0x00
    CALC_CRC = 0x03
    TRANSCEIVE = 0x0C
    MF_AUTHENT = 0x0E
    SOFT_RESET = 0x0F

class MifareCommands:
    REQUEST_A = 0x26
    ANTICOLL_1 = 0x93
    SELECT_1 = 0x93
    HALT = 0x50
    READ = 0x30
    AUTH_A = 0x60

class StatusCodes:
    OK = 0
    ERROR = 1
    TIMEOUT = 3
    AUTH_ERROR = 5

DEFAULT_KEY = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]

# --- Exceptions ---
# From `exceptions.py`

class RC522Error(Exception):
    """Base exception for RC522 operations."""
    pass

class RC522CommunicationError(RC522Error):
    """Exception for communication errors with the RC522."""
    pass

# --- Main Class ---
# Combined and simplified logic from `rc522_reader.py`

class RC522SPILibrary:
    """
    A lean and standalone Python library for the RC522 RFID reader
    on the Raspberry Pi 5, focusing on SPI communication.
    """

    def __init__(self, spi_bus=0, spi_device=0, rst_pin=22, debug=False):
        """
        Initializes the reader.

        Args:
            spi_bus (int): The SPI bus (default: 0).
            spi_device (int): The SPI device (default: 0 for CE0).
            rst_pin (int): The GPIO pin for the reset (BCM numbering).
            debug (bool): Enables detailed log output.
        """
        self.logger = logging.getLogger(__name__)
        if debug:
            self.logger.setLevel(logging.DEBUG)
        
        if not spidev or not gpiod:
            raise RC522CommunicationError("The hardware libraries 'spidev' and 'gpiod' are not available.")

        self.spi = spidev.SpiDev()
        self.spi.open(spi_bus, spi_device)
        self.spi.max_speed_hz = 1000000  # 1 MHz
        self.spi.mode = 0

        # GPIO setup for the reset pin using gpiod
        try:
            # Chip 'gpiochip4' is for physical pins on the Pi 5.
            # For Pi 4, this might be 'gpiochip0'.
            self.gpio_chip = gpiod.Chip('gpiochip4')
            self.rst_line = self.gpio_chip.get_line(rst_pin)
            self.rst_line.request(consumer="RC522_RST", type=gpiod.LINE_REQ_DIR_OUT)
        except Exception as e:
            raise RC522CommunicationError(f"Error initializing GPIO pin via gpiod: {e}")

        self._initialized = False
        self.initialize()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.cleanup()

    def _write_register(self, reg, value):
        self.spi.xfer2([reg << 1 & 0x7E, value])

    def _read_register(self, reg):
        return self.spi.xfer2([(reg << 1 & 0x7E) | 0x80, 0])[1]

    def _set_bit_mask(self, reg, mask):
        current = self._read_register(reg)
        self._write_register(reg, current | mask)

    def _clear_bit_mask(self, reg, mask):
        current = self._read_register(reg)
        self._write_register(reg, current & (~mask))

    def _reset(self):
        """Performs a hardware reset of the RC522."""
        self.rst_line.set_value(0)
        time.sleep(0.05)
        self.rst_line.set_value(1)
        time.sleep(0.05)

    def initialize(self):
        """Initializes the RC522 chip."""
        self._reset()
        self._write_register(RC522Registers.COMMAND_REG, RC522Commands.SOFT_RESET)
        time.sleep(0.05)

        self._write_register(RC522Registers.T_MODE_REG, 0x8D)
        self._write_register(RC522Registers.T_PRESCALER_REG, 0x3E)
        self._write_register(RC522Registers.T_RELOAD_REG_L, 30)
        self._write_register(RC522Registers.T_RELOAD_REG_H, 0)
        self._write_register(RC522Registers.TX_AUTO_REG, 0x40)
        self._write_register(RC522Registers.MODE_REG, 0x3D)
        self.antenna_on()
        self._initialized = True
        self.logger.info("RC522 initialized successfully.")

    def antenna_on(self):
        if not (self._read_register(RC522Registers.TX_CONTROL_REG) & 0x03):
            self._set_bit_mask(RC522Registers.TX_CONTROL_REG, 0x03)

    def cleanup(self):
        """Resets the RC522 and releases resources."""
        if self._initialized:
            self._reset()
        if hasattr(self, 'rst_line') and self.rst_line:
            self.rst_line.release()
        if hasattr(self, 'gpio_chip') and self.gpio_chip:
            self.gpio_chip.close()
        self.spi.close()
        self.logger.info("RC522 resources have been released.")

    def _communicate_with_card(self, command, send_data, timeout=0.1):
        """Internal method for card communication."""
        irq_en = 0x77
        wait_irq = 0x30
        
        self._write_register(RC522Registers.COMMAND_REG, RC522Commands.IDLE)
        self._write_register(RC522Registers.COM_IRQ_REG, 0x7F)
        self._set_bit_mask(RC522Registers.FIFO_LEVEL_REG, 0x80)

        for byte in send_data:
            self._write_register(RC522Registers.FIFO_DATA_REG, byte)

        self._write_register(RC522Registers.COMMAND_REG, command)
        
        if command == RC522Commands.TRANSCEIVE:
            self._set_bit_mask(RC522Registers.BIT_FRAMING_REG, 0x80)

        start_time = time.time()
        while time.time() - start_time < timeout:
            n = self._read_register(RC522Registers.COM_IRQ_REG)
            if n & wait_irq:
                break
        
        self._clear_bit_mask(RC522Registers.BIT_FRAMING_REG, 0x80)

        if time.time() - start_time >= timeout:
            return StatusCodes.TIMEOUT, [], 0

        if self._read_register(RC522Registers.ERROR_REG) & 0x1B:
            return StatusCodes.ERROR, [], 0
            
        status = StatusCodes.OK
        back_data = []
        back_len = 0

        if n & 0x01:
            status = StatusCodes.ERROR

        if command == RC522Commands.TRANSCEIVE:
            fifo_size = self._read_register(RC522Registers.FIFO_LEVEL_REG)
            last_bits = self._read_register(RC522Registers.CONTROL_REG) & 0x07
            if last_bits != 0:
                back_len = (fifo_size - 1) * 8 + last_bits
            else:
                back_len = fifo_size * 8

            if fifo_size == 0:
                fifo_size = 1

            if fifo_size > 16:
                fifo_size = 16

            for _ in range(fifo_size):
                back_data.append(self._read_register(RC522Registers.FIFO_DATA_REG))

        return status, back_data, back_len

    def request(self):
        """
        Scans for cards in the antenna field.
        """
        self._write_register(RC522Registers.BIT_FRAMING_REG, 0x07)
        status, back_data, _ = self._communicate_with_card(RC522Commands.TRANSCEIVE, [MifareCommands.REQUEST_A])
        if status != StatusCodes.OK or len(back_data) != 2:
            return StatusCodes.ERROR, None
        return status, back_data

    def anticoll(self):
        """
        Performs an anti-collision procedure to get a card's UID.
        """
        self._write_register(RC522Registers.BIT_FRAMING_REG, 0x00)
        status, back_data, _ = self._communicate_with_card(RC522Commands.TRANSCEIVE, [MifareCommands.ANTICOLL_1, 0x20])
        
        if status == StatusCodes.OK and len(back_data) == 5:
            # Checksum of UID
            checksum = 0
            for i in range(4):
                checksum ^= back_data[i]
            if checksum != back_data[4]:
                return StatusCodes.ERROR, None
            return StatusCodes.OK, back_data[:4]
            
        return StatusCodes.ERROR, None

b) Die Beispieldatei: example.py

Dieses Skript importiert die soeben erstellte Bibliothek und zeigt, wie man eine Kartenerkennung und das Auslesen der UID in einer einfachen Schleife implementiert. Speichere diesen Code im selben Verzeichnis wie die Bibliotheksdatei unter dem Namen example.py.

# -*- coding: utf-8 -*-
# Pollux Labs
# polluxlabs.net

import time
import logging
# Importiere die zuvor erstellte Bibliothek
from rc522_spi_library import RC522SPILibrary, StatusCodes

# Konfiguriere das Logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

def main():
    """
    Allgemeines Beispiel zum Auslesen der UID von einer beliebigen RFID-Karte.
    """
    print("Starte den RFID-Kartenleser...")
    print("Halte eine beliebige RFID-Karte vor den Reader.")
    print("Drücke STRG+C zum Beenden.")

    reader = None
    try:
        # Initialisiere die Bibliothek.
        # RST-Pin 22 (BCM) entspricht dem physischen Pin 15.
        reader = RC522SPILibrary(rst_pin=22)
        
        # Speichert die UID der zuletzt gesehenen Karte, um ständige Wiederholungen zu vermeiden
        last_uid = None

        while True:
            # 1. Suche nach einer Karte im Feld
            status, _ = reader.request()

            if status == StatusCodes.OK:
                # 2. Wenn eine Karte im Feld ist, hole ihre UID (Anti-Kollision)
                status, uid = reader.anticoll()
                
                if status == StatusCodes.OK:
                    # Reagiere nur, wenn es eine neue Karte ist
                    if uid != last_uid:
                        last_uid = uid
                        
                        # Konvertiere die UID in ein lesbares Format
                        uid_str = ":".join([f"{i:02X}" for i in uid])
                        
                        print("\n================================")
                        print(f"Karte erkannt!")
                        print(f"  UID: {uid_str}")
                        print("================================")
                        print("INFO: Du kannst diese UID nun in deinem eigenen Code verwenden.")
            else:
                # 3. Wenn keine Karte mehr im Feld ist, setze `last_uid` zurück
                if last_uid is not None:
                    print("\nKarte entfernt. Der Reader ist bereit für die nächste Karte.")
                    last_uid = None

            # Kurze Pause zur Reduzierung der CPU-Last
            time.sleep(0.1)

    except Exception as e:
        logging.error(f"Ein unerwarteter Fehler ist aufgetreten: {e}")
    except KeyboardInterrupt:
        print("\nProgramm wird beendet.")
    finally:
        # Stelle sicher, dass die Ressourcen am Ende freigegeben werden
        if reader:
            reader.cleanup()
            print("RC522-Ressourcen erfolgreich freigegeben.")

if __name__ == '__main__':
    main()

Anwendung und Test

Jetzt ist alles bereit, um den RFID-Reader zu testen.

  1. Navigiere in deinem Terminal in das Verzeichnis, in dem du die beiden Python-Dateien (rc522_spi_library.py und example.py) gespeichert hast.
  2. Führe das Beispiel-Skript aus:Bashpython3 example.py
  3. Das Programm startet und fordert dich auf, eine Karte an den Reader zu halten.
  4. Wenn du eine RFID-Karte in die Nähe des Lesegeräts hältst, sollte deren UID auf dem Bildschirm erscheinen.

Beispiel-Ausgabe:

Starte den RFID-Kartenleser...
Halte eine beliebige RFID-Karte vor den Reader.
Drücke STRG+C zum Beenden.

================================
Karte erkannt!
  UID: 4A:F3:8B:1E
================================
INFO: Du kannst diese UID nun in deinem eigenen Code verwenden.

Diese UID kannst du nun kopieren und in deinen eigenen Projekten für Zugangskontrollen, zur Identifikation von Objekten oder zum Auslösen von Aktionen verwenden.

]]>
So baust du einen ESP32 Webserver https://polluxlabs.net/esp8266-projekte/so-baust-du-einen-esp32-webserver/ Tue, 24 Jun 2025 08:55:13 +0000 https://polluxlabs.net/?p=19423 In diesem Projekt baust du dir deinen eigenen ESP32 Webserver. Hierbei arbeitest du dich schrittweise voran: vom einfachen „Hello World“ über die Steuerung einer LED bis zur Abfrage der aktuellen Temperatur. All dies stellt dein Microcontroller auf einer Webseite zur Verfügung, die du von einem Gerät deiner Wahl aufrufen kannst. Los geht’s!

Warum ein Webserver auf dem ESP32?

Der ESP32 ist ein echtes Kraftpaket. Mit seinem integrierten WLAN-Modul ist er wie geschaffen für Projekte im „Internet der Dinge“ (IoT). Ein eigener kleiner Webserver auf dem ESP32 erlaubt es euch, von jedem Gerät in eurem WLAN-Netzwerk – sei es euer Smartphone, Tablet oder Laptop – auf den Mikrocontroller zuzugreifen und ihn zu steuern. Ihr könnt Zustände abfragen, Aktionen auslösen und Daten visualisieren, und das alles über einen einfachen Webbrowser.

Teil 1: „Hallo Welt!“ – Dein erster ESP32 Webserver

Wie bei jedem guten Programmier-Tutorial starten wir auch hier mit den absoluten Grundlagen. Unser erstes Ziel: eine Webseite zu erstellen, die uns im Browser ein freundliches „Hallo Welt von deinem ESP32!“ anzeigt.

Was du hierfür brauchst:

Der Sketch

Da du für diesen ersten Test deines ESP32 Webservers außer dem Microcontroller keine weitere Hardware brauchst, kann es direkt mit dem Code losgehen. Kopiere den folgenden Sketch, trage im oberen Bereich des Sketchs deine WLAN-Zugangsdaten ein und lade ihn auf deinen ESP32:

//ESP32 Webserver
//polluxlabs.net

#include <WiFi.h>

// Deine WLAN-Zugangsdaten
const char* ssid = "DEIN_WLAN_NAME";
const char* password = "DEIN_WLAN_PASSWORT";

// Wir erstellen ein Server-Objekt auf Port 80 (Standard für HTTP)
WiFiServer server(80);

void setup() {
  Serial.begin(115200);
  while (!Serial) { ; }

  // Mit dem WLAN verbinden
  Serial.println();
  Serial.print("Verbinde mit ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WLAN verbunden.");
  Serial.println("IP-Adresse: ");
  Serial.println(WiFi.localIP());

  server.begin();
}

void loop() {
  WiFiClient client = server.available();   // Auf neue Clients warten

  if (client) {                             // Wenn sich ein Client verbindet...
    Serial.println("Neuer Client verbunden.");
    String currentLine = "";                // String, um die ankommenden Daten vom Client zu speichern
    while (client.connected()) {            // solange der Client verbunden ist...
      if (client.available()) {             // wenn der Client Daten sendet...
        char c = client.read();             // ...lese ein Byte
        Serial.write(c);
        if (c == '\n') {                    // wenn das Byte ein Zeilenumbruch ist...
          // Eine leere Zeile vom Client bedeutet das Ende der HTTP-Anfrage
          if (currentLine.length() == 0) {
            // HTTP-Header senden
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println();

            // Die eigentliche Webseite
            client.print("<h1>Hallo Welt von deinem ESP32!</h1>");
            
            // Die HTTP-Antwort endet mit einer leeren Zeile
            client.println();
            break;
          } else {    
            currentLine = "";
          }
        } else if (c != '\r') {  
          currentLine += c;      
        }
      }
    }
    // Verbindung schließen
    client.stop();
    Serial.println("Client getrennt.");
  }
}

Was passiert hier?

  1. setup(): Der ESP32 verbindet sich mit dem von dir angegebenen WLAN. Sobald die Verbindung steht, gibt er seine IP-Adresse im Seriellen Monitor aus. Diese Adresse brauchen wir gleich! Anschließend wird der Webserver gestartet.
  2. loop(): Die Schleife wartet auf eingehende Verbindungen. Sobald du die IP-Adresse in einem Browser öffnest, wird der Browser zu einem „Client“. Der ESP32 erkennt das, sendet einen simplen HTML-Code (<h1>Hallo Welt...</h1>) zurück und schließt die Verbindung wieder.

Kopiere dir die IP-Adresse deines Webservers und öffne sie in einem Browser deiner Wahl. Du solltest nun die folgende (sehr einfache) Webseite sehen:

Webseite mit Hallo Welt vom ESP32 Webserver

Teil 2: Licht an! – Steuerung einer LED über den Webserver

Nachdem die Grundlagen sitzen, wollen wir nun etwas Handfestes steuern. Eine LED ist dafür das perfekte Beispiel. Wir erweitern unseren Code so, dass auf unserer Webseite zwei simple Schaltflächen erscheinen, mit denen wir eine LED ein- und ausschalten können.

Was du zusätzlich brauchst:

  • Eine LED
  • Ein 220-Ohm-Widerstand
  • Ein Breadboard und ein paar Jumperkabel

Der Aufbau auf dem Breadboard

Verbinde die Anode (das lange Beinchen) der LED über den Widerstand mit einem GPIO-Pin des ESP32 (z.B. am Arduino ESP32 der Pin D2). Die Kathode (das kurze Beinchen) verbindest du mit GND.

Aufbau des ESP32 Webservers mit LED

Der Sketch

Du baust auf deinen ersten Sketch auf. Die größten Änderungen finden in der loop()-Funktion statt, wo wir die Anfrage des Browsers auswerten müssen.

___STEADY_PAYWALL___

//ESP32 Webserver mit LED
//polluxlabs.net

#include <WiFi.h>

// Deine WLAN-Zugangsdaten
const char* ssid = "DEIN_WLAN_NAME";
const char* password = "DEIN_WLAN_PASSWORT";

WiFiServer server(80);

// Pin für die LED definieren
const int ledPin = D2; //Am Arduino ESP32 Pin D2
String ledStatus = "aus";

void setup() {
  Serial.begin(115200);
  while (!Serial) { ; }

  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);

  // ... (WLAN-Verbindungscode aus Teil 1)
  // Mit dem WLAN verbinden
  Serial.println();
  Serial.print("Verbinde mit ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WLAN verbunden.");
  Serial.println("IP-Adresse: ");
  Serial.println(WiFi.localIP());

  server.begin();
}

void loop() {
  WiFiClient client = server.available();

  if (client) {
    Serial.println("Neuer Client verbunden.");
    String header = ""; // Speichert den Header der Anfrage
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        header += c;
        if (c == '\n') {
          if (header.indexOf("GET /led/an") >= 0) {
            digitalWrite(ledPin, HIGH);
            ledStatus = "an";
          } else if (header.indexOf("GET /led/aus") >= 0) {
            digitalWrite(ledPin, LOW);
            ledStatus = "aus";
          }
          
          if (header.endsWith("\r\n\r\n")) {
            // HTTP-Header senden
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println();

            // Die Webseite mit Schaltflächen
            client.print("<!DOCTYPE html><html><head><title>ESP32 LED Steuerung</title></head><body>");
            client.print("<h1>LED Steuerung</h1>");
            client.print("<p>Der Zustand der LED ist: <strong>" + ledStatus + "</strong></p>");
            client.print("<a href=\"/led/an\"><button>LED AN</button></a>");
            client.print("<a href=\"/led/aus\"><button>LED AUS</button></a>");
            client.print("</body></html>");
            
            client.println();
            break;
          }
        }
      }
    }
    client.stop();
    Serial.println("Client getrennt.");
  }
}

Was ist neu?

  • Wir lesen den kompletten „Header“ der Anfrage vom Browser.
  • Wir suchen im Header nach bestimmten URLs, die wir selbst definieren: /led/an und /led/aus.
  • Je nachdem, welche URL aufgerufen wird (indem ihr auf den entsprechenden Button klickt), schalten wir die LED mit digitalWrite() ein oder aus.
  • Die Webseite selbst enthält nun klickbare Links (<a>-Tags), die genau diese URLs aufrufen.

Lade den neuen Code hoch und rufe die IP-Adresse auf. Du kannst jetzt die LED über die Webseite steuern:

Webseite des ESP32 Webservs mit LED-Steuerung

Teil 3: Temperaturanzeige mit einem DHT11

Jetzt wird es richtig spannend! Wir lesen einen Sensor aus und zeigen die Daten live auf unserer Webseite an. Dafür nutzen wir den beliebten und günstigen DHT11 Temperatur- und Feuchtigkeitssensor. Auf Pollux Labs findest du jedoch auch Anleitungen für den Anschluss anderer Temperatursensoren wie den DHT22 oder den BMP180.

Was du zusätzlich brauchst:

  • Einen DHT11-Sensor
  • Die Adafruit DHT Sensor Library und die Adafruit Unified Sensor Library. Beide findest du im Bibliotheksmanager der Arduino IDE.

Der Aufbau auf dem Breadboard

Der DHT11 hat meistens vier Pins, von denen jedoch nur drei verwendet werden: VCC (oder +), DATA und GND. Orientiere dich beim Aufbau an folgender Skizze. Die LED kannst du auf dem Breadboard lassen, im nächsten Teil kombinieren wir die LED-Steuerung und die Temperaturmessung für deinen ESP32 Webserver.

  • Verbinde VCC mit dem 3.3V-Pin des ESP32.
  • Verbinde GND mit der Erde.
  • Verbinde den DATA-Pin mit einem digitalen Pin, zum Beispiel Pin D4.
Aufbau des ESP32 Webservsers mit LED und DHT11

Der Sketch

Hier der Sketch, mit dem dein ESP32 die Temperatur misst und auf der Webseite anzeigt.

//ESP32 Webserver mit Temperaturmessung
//polluxlabs.net

#include <WiFi.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>

// Deine WLAN-Zugangsdaten
const char* ssid = "DEIN_WLAN_NAME";
const char* password = "DEIN_WLAN_PASSWORT";

WiFiServer server(80);

// DHT Sensor Konfiguration
#define DHTPIN D4       // Pin, an dem der DHT11 angeschlossen ist
#define DHTTYPE DHT11  // Sensortyp
DHT dht(DHTPIN, DHTTYPE);

void setup() {
  Serial.begin(115200);
  while (!Serial) { ; }
  
  // ... (WLAN-Verbindungscode aus Teil 1)
  // Mit dem WLAN verbinden
  Serial.println();
  Serial.print("Verbinde mit ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WLAN verbunden.");
  Serial.println("IP-Adresse: ");
  Serial.println(WiFi.localIP());

  dht.begin();
  server.begin();
}

void loop() {
  WiFiClient client = server.available();

  if (client) {
    Serial.println("Neuer Client verbunden.");
    String header = "";
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        header += c;
        if (c == '\n' && header.endsWith("\r\n\r\n")) {
          // Temperatur auslesen
          float temperatur = dht.readTemperature();

          // Prüfen, ob das Auslesen erfolgreich war
          if (isnan(temperatur)) {
            Serial.println("Fehler beim Auslesen des DHT-Sensors!");
            temperatur = 0; // Standardwert bei Fehler
          }

          // HTTP-Header senden
          client.println("HTTP/1.1 200 OK");
          client.println("Content-type:text/html; charset=UTF-8"); // Wichtig für Umlaute wie °
          client.println();

          // Die Webseite mit Temperaturanzeige
          client.print("<!DOCTYPE html><html><head><title>ESP32 Wetterstation</title>");
          client.print("<meta http-equiv='refresh' content='10'>"); // Seite alle 10s neu laden
          client.print("</head><body>");
          client.print("<h1>ESP32 Mini-Wetterstation</h1>");
          client.print("<p>Aktuelle Temperatur: <strong>" + String(temperatur) + " &deg;C</strong></p>");
          client.print("</body></html>");
          
          client.println();
          break;
        }
      }
    }
    client.stop();
    Serial.println("Client getrennt.");
  }
}

Das ist neu

  • Wir binden die DHT.h-Bibliothek ein und initialisieren den Sensor.
  • In der loop() lesen wir bei jeder Anfrage die Temperatur mit dht.readTemperature() aus.
  • Wir haben einen kleinen meta-Tag in unser HTML eingefügt, der den Browser anweist, die Seite alle 10 Sekunden automatisch neu zu laden. So siehst du immer die aktuellen Werte!
  • Die Temperatur wird dann als Teil der Webseite an den Browser gesendet.

Teil 4: LED-Steuerung und Temperatur zusammen

Wir haben gelernt, wie man eine LED schaltet und wie man Sensordaten anzeigt. Jetzt bringen wir beides zusammen! Unser Ziel ist eine einzige Webseite, auf der wir die aktuelle Raumtemperatur sehen und gleichzeitig eine LED ein- und ausschalten können. Das ist wie eine kleine, feine Smart-Home-Zentrale.

Auf dem Breadboard musst du hierfür nichts verändern – du wirst nur den Sketch anpassen:

//ESP32 Webserver mit LED-Steuerung und Temperaturmessung
//polluxlabs.net

#include <WiFi.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>

// Deine WLAN-Zugangsdaten
const char* ssid = "DEIN_WLAN_NAME";
const char* password = "DEIN_WLAN_PASSWORT";

WiFiServer server(80);

// --- Konfiguration für die LED ---
const int ledPin = 2;
String ledStatus = "aus";

// --- Konfiguration für den DHT Sensor ---
#define DHTPIN 4       // Pin, an dem der DHT11 angeschlossen ist
#define DHTTYPE DHT11  // Sensortyp
DHT dht(DHTPIN, DHTTYPE);

void setup() {
  Serial.begin(115200);
  while (!Serial) { ; }
  
  // --- Initialisierung für die LED ---
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);

  // --- Initialisierung für den DHT Sensor ---
  dht.begin();
  
  // Mit dem WLAN verbinden (Code aus den vorherigen Teilen)
  Serial.println();
  Serial.print("Verbinde mit ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WLAN verbunden.");
  Serial.println("IP-Adresse: ");
  Serial.println(WiFi.localIP());

  server.begin();
}

void loop() {
  WiFiClient client = server.available();

  if (client) {
    Serial.println("Neuer Client verbunden.");
    String header = "";
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        header += c;

        // Wenn die Anfrage des Browsers komplett empfangen wurde
        if (c == '\n' && header.endsWith("\r\n\r\n")) {

          // --- URL auswerten für die LED-Steuerung ---
          if (header.indexOf("GET /led/an") >= 0) {
            digitalWrite(ledPin, HIGH);
            ledStatus = "an";
          } else if (header.indexOf("GET /led/aus") >= 0) {
            digitalWrite(ledPin, LOW);
            ledStatus = "aus";
          }

          // --- Temperatur vom DHT-Sensor auslesen ---
          float temperatur = dht.readTemperature();
          if (isnan(temperatur)) {
            Serial.println("Fehler beim Auslesen des DHT-Sensors!");
            temperatur = 0; // Standardwert bei Fehler
          }

          // --- HTTP-Antwort & kombinierte Webseite senden ---
          client.println("HTTP/1.1 200 OK");
          client.println("Content-type:text/html; charset=UTF-8");
          // Wichtiger Hinweis: Die folgende Zeile sorgt für einen Auto-Refresh.
          // Das ist super für die Temperatur, setzt aber auch den angezeigten LED-Status
          // alle 10 Sekunden auf den echten Zustand zurück.
          client.println("Refresh: 10");
          client.println(); // Leere Zeile nach den Headern

          // Die eigentliche Webseite
          client.print("<!DOCTYPE html><html><head><title>ESP32 Kombi-Steuerung</title>");
          client.print("<style> body { font-family: sans-serif; } button { padding: 10px; font-size: 16px; } </style>");
          client.print("</head><body>");
          
          client.print("<h1>ESP32 Kombi-Steuerung</h1>");
          
          // Abschnitt für den Sensor
          client.print("<h2>Mini-Wetterstation</h2>");
          client.print("<p>Aktuelle Temperatur: <strong>" + String(temperatur) + " &deg;C</strong></p>");

          // Abschnitt für die LED
          client.print("<h2>LED Steuerung</h2>");
          client.print("<p>Der Zustand der LED ist: <strong>" + ledStatus + "</strong></p>");
          client.print("<a href=\"/led/an\"><button>LED AN</button></a>");
          client.print("<a href=\"/led/aus\"><button>LED AUS</button></a>");

          client.print("</body></html>");
          
          client.println();
          break; // Schleife verlassen, da die Antwort gesendet wurde
        }
      }
    }
    client.stop();
    Serial.println("Client getrennt.");
  }
}

Was ist neu?

  1. Variablen zusammengeführt: Wir haben die Deklarationen für den ledPin und den ledStatus ganz oben zu denen für den DHT-Sensor hinzugefügt.
  2. setup() erweitert: Im setup() wird jetzt nicht nur der Sensor, sondern auch der LED-Pin initialisiert.
  3. Der loop(): Das ist der wichtigste Teil. Bevor die Webseite zusammengebaut wird, passiert jetzt beides:
    • Zuerst wird wie in Teil 2 geprüft, ob die aufgerufene URL /led/an oder /led/aus enthält, und die LED wird entsprechend geschaltet.
    • Danach wird wie in Teil 3 die Temperatur vom Sensor ausgelesen.
  4. HTML kombiniert: Der HTML-Code, der an den Browser gesendet wird, enthält jetzt einfach beide Abschnitte – die Temperaturanzeige und die Schaltflächen für die LED. Es wurde auch ein paar simple <h2>-Überschriften zur Strukturierung und ein winziges bisschen CSS für die Buttons hinzugefügt.

Wenn du diesen Code nun hochlädst und die IP-Adresse im Browser aufrufst, siehst du eine Webseite, die dir die Temperatur anzeigt und dir gleichzeitig erlaubt, das Licht ein- und auszuschalten.

Teil 5: Der Feinschliff – Eine moderne und responsive Oberfläche

Dein ESP32 Webserver funktioniert also – nur die Webseite könnte etwas moderner sein. Hier kommt etwas CSS (Cascading Style Sheets) zum Einsatz. Die Schaltung bleibt hingegen genau dieselbe wie in Teil 4.

Der finale Code mit schicker Oberfläche: Lade diesen Code auf deinen ESP32. Das Design der Webseite passiert in den langen client.print()-Zeilen, die den HTML- und CSS-Code enthalten.

//ESP32 Webserver mit LED-Steuerung und Temperaturmessung//polluxlabs.net

#include <WiFi.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>

// Deine WLAN-Zugangsdaten
const char* ssid = "DEIN_WLAN_NAME";
const char* password = "DEIN_WLAN_PASSWORT";

WiFiServer server(80);

// --- Konfiguration für die LED ---
const int ledPin = 2;

// --- Konfiguration für den DHT Sensor ---
#define DHTPIN 4       // Pin, an dem der DHT11 angeschlossen ist
#define DHTTYPE DHT11  // Sensortyp
DHT dht(DHTPIN, DHTTYPE);

void setup() {
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);
  dht.begin();
  
  // Mit dem WLAN verbinden
  Serial.println();
  Serial.print("Verbinde mit ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WLAN verbunden.");
  Serial.println("IP-Adresse: ");
  Serial.println(WiFi.localIP());

  server.begin();
}

void loop() {
  WiFiClient client = server.available();

  if (client) {
    String header = "";
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        header += c;

        if (c == '\n' && header.endsWith("\r\n\r\n")) {
          // --- URL auswerten für die LED-Steuerung ---
          if (header.indexOf("GET /led/an") >= 0) {
            digitalWrite(ledPin, HIGH);
          } else if (header.indexOf("GET /led/aus") >= 0) {
            digitalWrite(ledPin, LOW);
          }

          // --- Aktuellen Status von LED und Sensor auslesen ---
          String ledStatus = digitalRead(ledPin) ? "an" : "aus";
          float temperatur = dht.readTemperature();

          if (isnan(temperatur)) {
            Serial.println("Fehler beim Auslesen des DHT-Sensors!");
            temperatur = 0.0;
          }

          // --- HTTP-Antwort & moderne Webseite senden ---
          client.println("HTTP/1.1 200 OK");
          client.println("Content-type:text/html; charset=UTF-8");
          client.println();

          client.print("<!DOCTYPE html>");
          client.print("<html lang='de'>");
          client.print("<head>");
          client.print("<meta charset='UTF-8'>");
          // Diese Zeile ist ENTSCHEIDEND für die korrekte Darstellung auf Smartphones!
          client.print("<meta name='viewport' content='width=device-width, initial-scale=1.0'>");
          client.print("<title>Pollux Labs - ESP32 Steuerung</title>");
          client.print("<meta http-equiv='refresh' content='10'>");
          // --- HIER BEGINNT DAS CSS STYLING ---
          client.print("<style>");
          client.print("body{background-color:#1e1e1e;color:#e0e0e0;font-family:system-ui,sans-serif;text-align:center;margin:0;padding:20px;}");
          client.print(".container{max-width:800px;margin:auto;display:flex;flex-wrap:wrap;justify-content:center;gap:20px;}");
          client.print(".card{background-color:#2a2a2a;border-radius:15px;padding:20px;box-shadow:0 4px 8px rgba(0,0,0,0.2);flex-basis:300px;flex-grow:1;}");
          client.print("h1{color:#00aaff;}");
          client.print("h2{border-bottom:2px solid #00aaff;padding-bottom:10px;margin-top:0;}");
          client.print(".sensor-value{font-size:3.5rem;font-weight:bold;color:#fff;}");
          client.print(".unit{font-size:1.5rem;color:#00aaff;}");
          client.print(".led-status{font-size:1.2rem;margin-bottom:20px;}");
          client.print(".buttons{display:flex;gap:15px;justify-content:center;}");
          client.print("a.button{text-decoration:none;color:#fff;padding:15px 30px;border-radius:10px;font-weight:bold;transition:transform 0.2s ease;}");
          client.print(".on-button{background-color:#28a745;}"); // Grün
          client.print(".off-button{background-color:#dc3545;}"); // Rot
          client.print("a.button:active{transform:scale(0.95);}");
          client.print("footer{color:#555;margin-top:40px;}");
          client.print("</style>");
          // --- HIER ENDET DAS CSS STYLING ---
          
          client.print("</head>");
          client.print("<body>");
          
          client.print("<h1>Pollux Labs ESP32 Steuerung</h1>");
          client.print("<div class='container'>");
          
          // Karte für den Temperatursensor
          client.print("<div class='card'>");
          client.print("<h2>Temperatur</h2>");
          client.print("<p class='sensor-value'>" + String(temperatur, 1) + "<span class='unit'>&deg;C</span></p>");
          client.print("</div>");
          
          // Karte für die LED-Steuerung
          client.print("<div class='card'>");
          client.print("<h2>Beleuchtung</h2>");
          client.print("<p class='led-status'>Status: <strong>" + ledStatus + "</strong></p>");
          client.print("<div class='buttons'>");
          client.print("<a href='/led/an' class='button on-button'>EINSCHALTEN</a>");
          client.print("<a href='/led/aus' class='button off-button'>AUSSCHALTEN</a>");
          client.print("</div>");
          client.print("</div>");
          
          client.print("</div>"); // Ende .container
          client.print("<footer>Diese Seite wird alle 10 Sekunden automatisch aktualisiert.</footer>");
          client.print("</body></html>");
          
          client.println();
          break;
        }
      }
    }
    client.stop();
  }
}

Das Ergebnis

Wenn du jetzt die IP-Adresse deines ESP32 aufrufst, wirst du mit einer modernen Oberfläche begrüßt, die in etwa so aussieht:

Webseite des ESP32 Webservers

Was ist neu und warum?

  • <meta name='viewport'...>: Diese eine Zeile im <head> ist der Schlüssel für responsives Design. Sie sagt dem Smartphone-Browser: „Hey, betrachte die Webseite nicht als winzige Desktop-Seite, sondern passe die Breite an den Bildschirm an und starte ohne Zoom.“
  • .card: Jede Funktion hat ihre eigene „Karte“. Das schafft eine tolle optische Trennung. Durch display:flex im .container ordnen sich die Karten auf großen Bildschirmen nebeneinander an und auf schmalen Bildschirmen automatisch untereinander.
  • .sensor-value: Wir geben der reinen Temperaturzahl eine eigene Klasse, um sie mit font-size riesig und fett darzustellen – so sticht der wichtigste Wert sofort ins Auge.
  • Farbige Buttons: Die Buttons sind jetzt nicht nur größer und rund, sondern auch farbig. Grün für „AN“, Rot für „AUS“. Das ist intuitiv und sieht professionell aus.

Fühl dich frei, mit den Farbwerten (#1e1e1e, #00aaff, etc.) im CSS-Teil zu experimentieren und der Seite deinen ganz persönlichen Anstrich zu geben. Viel Spaß mit deinem neuen, schicken Web-Interface!

Fazit

Herzlichen Glückwunsch! Du hast dich vom einfachen „Hallo Welt“ über die LED-Steuerung bis hin zur Anzeige von Live-Sensordaten hochgearbeitet. Du hast jetzt eine solide Grundlage, um deine eigenen, viel komplexeren IoT-Projekte zu realisieren. Stell dir vor, du könntest die Heizung fernsteuern, die Rollläden automatisieren oder eine E-Mail erhalten, wenn die Pflanzen Wasser braucht – all das ist mit dem Wissen, das du heute gelernt hast, nicht weit entfernt.

]]>
Arduino RFID: Der Einstieg mit dem RC522-Reader https://polluxlabs.net/arduino-tutorials/arduino-rfid-der-einstieg-mit-dem-rc522-reader/ Mon, 23 Jun 2025 08:57:01 +0000 https://polluxlabs.net/?p=19411 In diesem Tutorial tauchen wir in eine faszinierende Technologie ein, die uns im Alltag ständig begegnet: RFID. Ob beim kontaktlosen Bezahlen, bei der Zeiterfassung oder in modernen Schließsystemen – die kleinen Chips sind überall. Aber hast du dich schon mal gefragt, wie das eigentlich funktioniert? Und noch besser: Wie du es selbst für deine eigenen Projekte nutzen kannst?

Du wirst einen RFID-Reader an einen Arduino anschließen und die einzigartige ID von RFID-Tags und -Karten auslesen. Also, schnapp dir deinen Arduino und lass uns loslegen!

Was ist RFID eigentlich?

RFID steht für „Radio-Frequency Identification“, also die Identifizierung mithilfe von Radiowellen. Ein RFID-System besteht immer aus zwei Teilen:

  1. Einem Transponder (der Tag oder die Karte): Dieser enthält einen winzigen Chip mit einer Antenne. Auf dem Chip ist eine eindeutige Kennung (UID – Unique Identifier) gespeichert.
  2. Einem Lesegerät (der Reader): Dieses erzeugt ein elektromagnetisches Feld. Kommt ein Transponder in die Nähe dieses Feldes, wird er mit Energie versorgt und sendet seine Informationen zurück an das Lesegerät.

Das Schöne daran ist, dass dies alles kontaktlos und ohne Sichtverbindung funktioniert.

Für dieses Arduino RFID Tutorial brauchst du

  • Ein Arduino-Board (im Folgenden ein Arduino Uno)
  • Ein RFID-RC522 Modul (meistens kommen hier schon eine Karte und ein Schlüsselanhänger-Tag mit)
  • Ein Breadboard & Kabel

Schritt 1: Die Verkabelung

Das Herzstück ist das RC522-Modul. Es kommuniziert mit dem Arduino über eine Schnittstelle namens SPI (Serial Peripheral Interface).

Wichtiger Hinweis: Das RC522-Modul arbeitet mit 3.3V! Schließe es auf keinen Fall an den 5V-Pin deines Arduinos an, da es sonst beschädigt werden könnte.

Verbinde die Pins des RC522-Moduls wie folgt mit deinem Arduino Uno:

RC522 PinArduino Uno PinBeschreibung
3.3V3.3VSpannungsversorgung
RSTPin 9Reset-Pin
GNDGNDMasse
MISOPin 12Master In, Slave Out (SPI)
MOSIPin 11Master Out, Slave In (SPI)
SCKPin 13Serial Clock (SPI)
SDAPin 10Slave Select (SPI)
IRQ(wird nicht verbunden)

Nimm dir für die Verkabelung einen Moment Zeit und prüfe alles doppelt. Eine saubere Verkabelung ist die halbe Miete!

Schritt 2: Die Bibliothek installieren

Damit dein Arduino versteht, wie er mit dem RFID-Modul sprechen soll, benötigst du eine passende Bibliothek. Für das RC522-Modul gibt es eine sehr beliebte und gut gepflegte Bibliothek von „GitHubCommunity“.

So installierst du sie ganz einfach über die Arduino IDE:

  1. Öffne deine Arduino IDE.
  2. Gehe zum Menüpunkt Werkzeuge > Bibliotheken verwalten….
  3. Gib in das Suchfeld MFRC522 ein.
  4. Suche nach der Bibliothek „MFRC522 by GithubCommunity“ und klicke auf Installieren.

Das war’s schon! Jetzt kennt dein Arduino die Befehle, um mit dem RFID-Modul zu kommunizieren.

Schritt 3: Einen Tag auslesen

Jetzt wird es spannend! Hier kommt der Sketch, um die einzigartige ID eines Tags auszulesen. Diese UID ist quasi der Personalausweis des Chips, den du in deinen Projekten verwenden kannst.

Öffne die Arduino IDE und füge den folgenden Code ein:

// Arduino RFID
//polluxlabs.net

#include <SPI.h>
#include <MFRC522.h>

#define RST_PIN 9          // Reset-Pin, den wir an Pin 9 angeschlossen haben
#define SS_PIN 10          // Slave-Select-Pin, den wir an Pin 10 angeschlossen haben

// Erstellen einer Instanz des MFRC522-Objekts
MFRC522 mfrc522(SS_PIN, RST_PIN);  

void setup() {
  Serial.begin(9600);   // Starten der seriellen Kommunikation für die Ausgabe am PC
  while (!Serial);      // Warten, bis der serielle Monitor bereit ist
  SPI.begin();          // Initialisieren der SPI-Kommunikation
  mfrc522.PCD_Init();   // Initialisieren des RFID-Moduls
  Serial.println("Halte einen RFID-Tag an das Lesegerät...");
}

void loop() {
  // Suche nach neuen Karten
  if ( ! mfrc522.PICC_IsNewCardPresent()) {
    return;
  }

  // Wähle eine der Karten aus
  if ( ! mfrc522.PICC_ReadCardSerial()) {
    return;
  }

  // Zeige die UID im Seriellen Monitor an
  Serial.print("UID des Tags: ");
  String content= "";
  for (byte i = 0; i < mfrc522.uid.size; i++) {
     Serial.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " ");
     Serial.print(mfrc522.uid.uidByte[i], HEX);
     content.concat(String(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " "));
     content.concat(String(mfrc522.uid.uidByte[i], HEX));
  }
  Serial.println();
  Serial.println("------------");
  
  // Halte die Kommunikation an, damit derselbe Tag nicht ständig neu gelesen wird
  mfrc522.PICC_HaltA();
}

Was macht dieser Code?

  • #include: Wir binden die benötigten Bibliotheken für SPI und das RFID-Modul ein.
  • #define: Wir legen fest, welche Arduino-Pins wir für RST und SS verwenden.
  • setup(): Hier wird die serielle Kommunikation gestartet und das RFID-Modul initialisiert.
  • loop(): Die Hauptschleife wartet darauf, dass ein neuer Tag (PICC_IsNewCardPresent()) in Reichweite kommt. Wenn einer erkannt wird, wird seine UID ausgelesen (PICC_ReadCardSerial()) und anschließend in einer lesbaren Form (Hexadezimal) auf dem Seriellen Monitor ausgegeben.

Lade den Sketch auf deinen Arduino hoch und halte nun eine RFID-Karte oder den Schlüsselanhänger an dein Modul. Voilà! Du solltest die UID des Tags auf dem Monitor sehen.

Wie geht es weiter?

Glückwunsch! Du hast soeben deinen eigenen RFID-Reader in Betrieb genommen und gelernt, wie man die ID eines Tags ausliest. Allein damit sind die Möglichkeiten schon riesig:

  • Baue ein smartes Türschloss: Speichere die UID deines Tags im Code. Wird diese UID erkannt, steuert der Arduino ein Servo an, um ein Schloss zu öffnen.
  • Erstelle eine Musikbox: Weise verschiedenen UIDs im Code unterschiedliche Lieder zu. Je nachdem, welchen Tag du an den Reader hältst, wird eine andere Melodie abgespielt.
  • Ein personalisiertes Begrüßungssystem: Der Arduino erkennt deine UID und begrüßt dich mit Namen auf einem LCD-Display.

Experimentiere mit dem Code und entwickle deine eigenen Ideen.

]]>
Sprachmodelle mit Ollama auf dem Raspberry Pi nutzen https://polluxlabs.net/raspberry-pi-projekte/sprachmodelle-mit-ollama-auf-dem-raspberry-pi-nutzen/ Fri, 07 Mar 2025 11:31:10 +0000 https://polluxlabs.net/?p=19301 KI-Sprachmodelle wie ChatGPT, Claude, Gemini, Deepseek etc. sind angekommen und unterstützen bei vielen Anwendungen – von der Frage, warum der Himmel eigentlich blau ist, bis zur Unterstützung beim Programmieren. Und auch bei vielen Projekten hier bei Pollux Labs kommt ihre künstliche Intelligenz zum Einsatz.

Einige dieser Modell kannst du nur über die zugehörige Webseite oder über eine API verwenden – zum Beispiel OpenAIs ChatGPT oder auch Claude von Anthropic. Andere kannst du hingegen auch lokal installieren – und damit kostenlos und ohne Preisgabe deiner Daten nutzen. Wie du Sprachmodelle mit Ollama lokal verwendest, liest du auch bei uns. Hier ist es allerdings so, dass du schon einen recht modernen und leistungsfähigen Computer benötigst, um eine zufriedenstellende Antwortgeschwindigkeit zu erreichen.

Aber immer wieder kommt die Frage auf, ob man ein Sprachmodell auch auf einem Raspberry Pi verwenden könnte. Prinzipiell ist das kein Problem, der Knackpunkt ist jedoch die Hardware: Ein Raspi ist für die Modelle, die du vielleicht schon im Browser genutzt hast, einfach nicht leistungsstark genug. Es gibt jedoch auch Modellversionen, die so stark reduziert wurden, dass du sie auch auf einem relativ schwachen Computer einsetzen kannst – also auch auf dem Raspberry Pi.

Wie das funktioniert und mit welchen Einschränkungen du rechnen musst, erfährst du in diesem Artikel.

Das passende Raspberry Pi Modell

An einem Raspberry Pi 5 mit mindestens 8GB RAM führt leider kein Weg vorbei. Die 4er-Reihe ist leider nicht leistungsstark genug. Installiere am besten ein frisches Betriebssystem: Raspberry Pi OS (64-bit). Falls du den Raspberry Pi Imager verwendest, aktiviere auch gleich SSH, um von einem anderen Computer auf deinen Raspberry Pi zugreifen zu können. Du kannst ihn aber natürlich auch an einen Monitor anschließen und das Terminal des Raspi verwenden. Wie du SSH verwendest, erfährst du in diesem Projekt.

Update: Weiter unten liest du, wie gut Sprachmodelle auf dem Raspberry Pi mit 16GB RAM funktionieren.

Sobald du auf den Raspberry Pi zugreifen kannst, bzw. darauf ein Terminal offen hast, aktualisiere zunächst das Betriebssystem:

sudo apt update
sudo apt upgrade

Anschließend benötigst du cURL (ein Werkzeug, das häufig verwendet wird, um HTTP-Anfragen zu senden und Daten von Webservern abzurufen oder an diese zu senden). Oft ist es bereits installiert, aber um sicherzugehen, verwende diesen Befehl im Terminal:

sudo apt install curl

Ollama auf dem Raspberry Pi installieren

Nun kann es direkt mit der Installation von Ollama weitergehen. Dieses Programm benötigst du, um Sprachmodelle herunterzuladen und dann auf deinem Raspberry Pi auszuführen.

___STEADY_PAYWALL___

Zum Einsatz kommt hierfür ein sogenannter „One-liner“, mit dem du da Installationsscript herunterlädst und direkt ausführst. Wenn du möchtest (in der Regel ist das empfehlenswert), wirf einen Blick in den Code des Scripts.

curl -fsSL https://ollama.com/install.sh | sh

Sobald die Installation abgeschlossen ist, kannst du die Ollama-Version mit diesem Befehl einsehen:

ollama --version

Aktuell (März 2025) ist das die Version 0.5.13

Ollama Version im Terminal

Sprachmodelle laden und starten

Zeit für dein erstes Sprachmodell auf dem Raspberry Pi.

Deepseek-r1

Zunächst ein Test mit Deepseek-r1 – dieses Modell ist bei Ollama in einer ganzen Reihe von Versionen verfügbar: bis zu einer Größe von 671 Milliarden Parametern, das satte 404 GB Platz (und sehr teure Hardware) benötigt. Für den Raspberry Pi soll es zunächst das kleinste Modell mit 1.5 Milliarden Parametern sein.

Installiere dieses Sprachmodell und starte es direkt mit diesem Befehl:

ollama run deepseek-r1:1.5b

Die Installation des 1,1 GB großen Modells dauert etwas, aber dann siehst du im Terminal die Eingabemöglichkeit für deine Frage:

Ich habe als erstes die Frage „Warum ist der Himmel blau?“ gestellt. Eine Besonderheit von Deepseek-r1 ist, dass das Modell zunächst „denkt“, bevor es eine Antwort ausspuckt. Diesem Denkprozess kannst du folgen, denn er erscheint Zeichen für Zeichen in deinem Terminal, zwischen den beiden Tags <think> </think>.

Bei meinem Test, hat Deepseek allerdings aufs Denken verzichtet und die folgende, leider völlig abstruse Antwort geliefert:

Ich verstehe, dass du den Begriff „Himmel“ und die Farbcode „blau“ vermutlich verwechseln kannst. In deutscher Sprachkultur wird der **Himmel** eine vierteltnälle (eine von der Erde manch Zeitendatum backstependen seebaren Stile) genannt, die als ein abgeschlossenes, wissenshares Gravität generally vermutlich ist. 

Die Geschwindigkeit hierbei war allerdings in Ordnung – kein Vergleich zum Browser, aber schnell genug. Für dieses Sprachmodell reicht also tatsächlich ein Raspberry Pi aus.

Ein zweiter Versuch, diesmal auf Englisch. Auch hier verzichtet Deepseek aufs Denken, antwortet aber richtig (zumindest soweit ich das beurteilen kann):

Antwort von Deepseek auf die Frage "Why is the Sky blue?"

Die Unterstützung für die deutsche Sprache ist offensichtlich wenn, nur unzureichend in diesem kleinen Modell vorhanden.

Übrigens, Deepseek versteht hier im Terminal auch Folgefragen – nicht wie bei der Nutzung über die API. Du kannst also auch eine Art Konversation mit dem Modell führen.

Wenn du das Sprachmodell verlassen möchtest, tippe einfach /bye ein. Anschließend landest du wieder bei der Eingabeaufforderung deines Raspis. Wenn du das Modell wieder starten möchtest, verwende erneut den obigen Befehl. Die Installation entfällt dann natürlich.

TinyLlama

Das Sprachmodell TinyLlama ist ein für limitierte Hardware angepasstes Modell mit 1,1 Milliarden Parametern, das auf Metas Llama-Modell mit 3 Milliarden Parametern basiert. Um es zu installieren und zu testen, verwende den folgenden Befehl:

ollama run tinyllama

Auch hier ist eine Konversation auf Englisch brauchbar. Die Geschwindigkeit ist jedoch mit Deepseek vergleichbar, genauso wie die Qualität:

Antwort von TinyLlama auf die Frage "Why is the Sky blue?"

Phi-3

Noch ein dritter Test. Phi-3 ist ein Sprachmodell von Microsoft mit 3,3 Milliarden Parametern – also doppelt bis dreimal so groß wie Deepseek-r1 und TinyLlama. So installierst du es:

ollama run phi3

Dieses Modell kannst du nun auch auf Deutsch befragen. Warum der Himmel blau ist, beantwortet es folgendermaßen:

Antwort von Phi-3 auf die Frage "Warum ist der Himmel blau?"

Abstriche musst du hierfür allerdings bei der Geschwindigkeit machen, dein Raspberry Pi hat mit diesem Modell ganz schön zu arbeiten. Dafür sind auch Folgefragen wie „Und warum ist er abends rot?“ möglich. Phi-3 weiß dann immer noch, dass du dich auf die Farbe des Himmels beziehst.

Gemma 3 1B

Das kleinste Modell benötigt gerade einmal 815MB auf deinem Raspberry Pi. Da es damit auch für das 8GB-Modell geeignet sein sollte, erwarte ich auf dem Raspi mit doppelt so viel Arbeitsspeicher eine entsprechende Leistungsteigerung. Installiere das Modell:

ollama run gemma3:1b 

Die obligatorische Frage, warum der Himmel blau ist, kommt tatsächlich flott. Und sie ist auch recht kompetent, hier der Anfang:

Der Himmel ist blau aus einem faszinierenden physikalischen Phänomen namens **Rayleigh-Streuung**. Hier ist eine einfache Erklärung:

1. Sonnenlicht und seine Farben: Sonnenlicht erscheint uns weiß, aber es besteht tatsächlich aus allen Farben des Regenbogens (Rot, Orange, Gelb, Grün, Blau, Indigo, Violett).

2. Die Atmosphäre: Die Erde ist von einer dichten Atmosphäre umgeben, die aus Gasmolekülen wie Stickstoff und Sauerstoff besteht.

3. Die Rayleigh-Streuung: Wenn Sonnenlicht in die Atmosphäre eintritt, trifft es auf diese Gasmoleküle.  Die Farben des Lichts werden in unterschiedlichen Winkeln gestreut. Die Rayleigh-Streuung ist besonders effektiv bei kürzeren Wellenlängen des Lichts, also den blauen und violetten Farben.

Und hier ist noch längst nicht Schluss. Die Ausführungen gehen noch weiter und beinhalten auch Überlegungen zu Sonnenauf- und -untergang sowie den Wolken. Auch am Deutsch lässt sich nicht mäkeln, die Antwort ist einwandfrei formuliert.

Gemma 3 4B

Weiter zum nächstgrößeren Modell mit 4 Billionen Parametern. Dieses benötigt bereits 3,3GB Platz. Lade und starte es mit:

ollama run gemma3:4b

Hier benötigst du schon etwas mehr Geduld, für die Antwort benötigt Gemma 3 dreimal so lang wie beim kleineren Modell. Sehr viel kompetenter ist sie dabei nicht (aber gut, die Antwort des kleineren Modells war ja schon in Ordnung). Hier der Anfang der Antwort:

Der Himmel ist blau aufgrund eines Phänomens namens **Rayleigh-Streuung**. Hier ist eine einfache Erklärung:

1. **Sonnenlicht ist weiß:** Sonnenlicht, das die Erde erreicht, ist eigentlich eine Mischung aus allen Farben des Regenbogens.

2. **Lichtwellen und die Atmosphäre:** Die Erdatmosphäre besteht aus winzigen Teilchen wie Stickstoff- und Sauerstoffmolekülen. Wenn Sonnenlicht auf diese Moleküle trifft, wird es in verschiedene Richtungen gestreut – ähnlich wie ein Glasglas Licht bricht.

Gemma 3 gibt es auch noch mit 12 und 27 Billionen Paramtern – allerdings erübrigt sich hier ein weiterer Test, denn die Ausführung dürfte entweder noch um einiges langsamer sein, oder sogar unmöglich.

Fazit

Es ist also durchaus möglich, Ollama und damit verschiedene KI-Sprachmodelle auf dem Raspberry Pi zu verwenden. Abstriche musst du allerdings hinsichtlich der Eingabesprache (Deutsch), der Geschwindigkeit und sicherlich auch bei der Qualität der Antworten machen.

Nichtsdestotrotz ist das ein spannender Ansatz, den du in einem Projekt einsetzen kannst!

]]>
Update fürs ESP32 Internetradio: Songs in Spotify speichern https://polluxlabs.net/raspberry-pi-projekte/update-fuers-esp32-internetradio-songs-in-spotify-speichern/ Tue, 25 Feb 2025 11:07:00 +0000 https://polluxlabs.net/?p=19195 Hast du schon einmal einen Song im Radio gehört, den du dir merken wolltest, damit du ihn später auf Spotify hören kannst? Vielleicht hast du dich auf dein Gedächtnis verlassen oder Stift und Papier verwendet. Aber es geht auch eleganter:

In diesem Projekt baust du dir eine Erweiterung für das ESP32 Internetradio, mit der du per Knopfdruck den Song, der gerade im Radio läuft, in deinen Lieblingssongs auf Spotify speicherst.

Für dieses Update benötigst du:

Da du deinen Raspberry Pi per SSH (mehr dazu später) programmieren wirst, benötigst du noch einen weiteren Computer, auf dem du eine Konsole bzw. Terminal verwenden kannst. Die Software für das Internetradio schreibst du in Python – hier bietet sich ein Editor wie z.B. Visual Studio Code an. Es reicht aber auch ein einfacher Texteditor. Die SD-Karte mit dem Betriebssystem für den Raspberry Pi erstellst du am besten mit dem kostenlosen Tool Raspberry Pi Imager.

Den Raspberry Pi vorbereiten

Bevor du dein erstes Bauteil anschließt, musst du deinen Raspberry Pi vorbereiten, indem du das Betriebssystem und die benötigten Python-Pakete und -Bibliotheken installierst. Lass uns das Schritt für Schritt durchgehen:

Das Betriebssystem installieren

Um das Betriebssystem zu konfigurieren und auf eine SD-Karte zu schreiben, gehe wie folgt vor:

  • Lade den Raspberry Pi Imager von der offiziellen Website herunter
  • Starte den Imager und wähle dein Modell (in diesem Projekt ist das ein Raspberry Pi Zero 2) sowie „Raspberry Pi OS Lite (64-bit)“ aus. Das findest du unter Raspberry Pi OS (other) und kommt ohne grafische Oberfläche, da wir diese nicht brauchen.
  • Wähle deine SD-Karte als Ziel
Raspberry Pi Imager Einstellungen

  • Klicke im nächsten Screen auf Einstellungen bearbeiten und
    • Setze einen Benutzernamen und Passwort
    • Konfiguriere dein WLAN (SSID und Passwort)
    • Aktiviere SSH im Reiter Dienste (Passwort zur Authentifizierung verwenden)
  • Übernimm deine Einstellungen mit einem Klick auf Ja und schreibe das Image auf die SD-Karte

Verbinde dich per SSH mit dem Raspberry Pi

Sobald der Raspberry Imager fertig ist, stecke die SD-Karte in den entsprechenden Slot des Raspi und starte ihn. Jetzt benötigst du etwas Geduld – der erste Start mit dem neuen Betriebssystem kann einige Minuten dauern. Öffne auf deinem Computer das Terminal bzw. die Konsole und verbinde dich mit dem folgenden Befehl – wobei du „pi“ durch den Benutzernamen ersetzen musst, den du zuvor im Raspberry Pi Imager vergeben hast.

sudo ssh pi@raspberrypi.local

Sobald dein Raspberry Pi bereit ist, wirst du zweimal aufgefordert, das Passwort einzugeben, das du im Pi Imager festgelegt hast.

Installiere die benötigten Pakete

Nun kannst du die Pakete und Bibliotheken installieren, die du für das Internetradio benötigst. Doch zunächst kümmerst du dich um das Update des Betriebssystems:

sudo apt update
sudo apt upgrade

Anschließend benötigst du Pip, mit dem du gleich die Bibliothek installierst, mit der du Spotify verwendest.

sudo apt install python3-pip

Eine virtuelle Umgebung einrichten

Mit der Einführung des Betriebssystems Bookworm wurde es erforderlich, Python-Bibliotheken in einer virtuellen Umgebung zu installieren. Durch deren Installation in einem geschützten Bereich soll verhindert werden, dass die systemweite Python-Installation verändert wird. Um eine virtuelle Umgebung einzurichten, verwende diese Befehle:

sudo apt install python3-venv
python3 -m venv RadioSpotify

Leider musst du die virtuelle Umgebung jedes Mal neu aktivieren, sobald du deinen Raspberry Pi neu gestartet hast. Später im Projekt wirst du das automatisieren, jetzt musst du es allerdings erst einmal noch manuell tun. Das geht mit diesem Befehl:

source RadioSpotify/bin/activate

Übrigens, deaktivieren kannst du sie einfach mit dem Befehl deactivate. Jetzt, wo deine virtuelle Umgebung also läuft, kannst du mit der Installation der folgenden Python-Bibliothek fortfahren:

pip3 install spotipy

Spotify vorbereiten

Damit dein ESP32 Internetradio bzw. dein Raspberry Pi einen Song bei Spotify suchen und deiner Playlist hinzufügen kann, benötigst du dort eine selbsterstellte App. Diese ist schnell eingerichtet:

Eine neue App im Spotify Entwickler-Konto erstellen

In den Feldern App name und App description kannst einen Namen und eine kurze Beschreibung deiner Wahl eintragen. In das Feld Redirect URIs musst du allerdings die Adresse http://localhost:8080 eintragen. Klicke anschließend auf Save.

Klicke nun auf dem nächsten Screen auf Settings. Dort findest du deine Client ID und Client Secret. Beide Schlüssel benötigst du gleich im Python-Script für den Raspberry Pi.

Client ID und Client Secret im Spotify Entwicklerkonto

Und das war es auch schon an dieser Stelle. Später wirst du deinen Raspberry Pi bei Spotify authentifizieren, um die Verbindung zwischen den beiden abschließend einzurichten.

Das Python-Script für das Radio

Der Code für das Raspberry Pi Internetradio ist der folgende. Kopiere ihn und erstelle ein neues Script namens radiospotify.py

___STEADY_PAYWALL___

import sys
import os
import fcntl
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
import time
import json
import spotipy
from spotipy.oauth2 import SpotifyOAuth
import logging

from http.server import HTTPServer, BaseHTTPRequestHandler
import webbrowser
# Set up logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
# Spotify API credentials
SPOTIPY_CLIENT_ID = 'deine_client_id'
SPOTIPY_CLIENT_SECRET = 'dein_client_secret'
SPOTIPY_REDIRECT_URI = 'http://localhost:8080'
SCOPE = 'user-library-modify'
# Define cache path in user's home directory
CACHE_PATH = os.path.expanduser('~/.spotify_token_cache')
# Global variable to store the authentication code
auth_code = None
class AuthHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        global auth_code
        query_components = parse_qs(urlparse(self.path).query)
        if "code" in query_components:
            auth_code = query_components["code"][0]
            self.send_response(200)
            self.send_header('Content-type', 'text/html')
            self.end_headers()
            self.wfile.write(b"Authentication successful! You can close this window.")
            logging.info("Received authentication code")
        else:
            self.send_response(400)
            self.send_header('Content-type', 'text/html')
            self.end_headers()
            self.wfile.write(b"Authentication failed! No code received.")
def wait_for_auth_code(port=8080):
    server = HTTPServer(('', port), AuthHandler)
    server.handle_request()  # Handle one request then close
    return auth_code
def initialize_spotify():
    global auth_code
    auth_manager = SpotifyOAuth(
        client_id=SPOTIPY_CLIENT_ID,
        client_secret=SPOTIPY_CLIENT_SECRET,
        redirect_uri=SPOTIPY_REDIRECT_URI,
        scope=SCOPE,
        cache_path=CACHE_PATH,
        open_browser=False
    )
    # Try to get cached token
    token_info = auth_manager.get_cached_token()
    if not token_info or auth_manager.is_token_expired(token_info):
        auth_url = auth_manager.get_authorize_url()
        print(f"\nPlease visit this URL to authorize the application:\n{auth_url}\n")
        # Start local server to receive the auth code
        received_code = wait_for_auth_code()
        if received_code:
            # Get and cache the token
            token_info = auth_manager.get_access_token(received_code)
            logging.info("New authentication token obtained and cached")
        else:
            logging.error("No authentication code received")
            return None
    return spotipy.Spotify(auth_manager=auth_manager)
class SpotifyServerHandler(BaseHTTPRequestHandler):
    def log_message(self, format, *args):
        logging.info(f"{self.client_address[0]}:{self.client_address[1]} - {format%args}")
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-type', 'application/json')
        self.end_headers()
        parsed_path = urlparse(self.path)
        params = parse_qs(parsed_path.query)
        logging.info(f"Received GET request with params: {params}")
        response = {"message": "Received GET request", "params": params}
        if 'song' in params:
            song_title = params['song'][0]
            spotify_response = self.save_to_spotify(song_title)
            response.update(spotify_response)
        self.wfile.write(json.dumps(response).encode())
    def do_POST(self):
        content_length = int(self.headers['Content-Length'])
        post_data = self.rfile.read(content_length).decode('utf-8')
        self.send_response(200)
        self.send_header('Content-type', 'application/json')
        self.end_headers()
        logging.info(f"Received POST data: {post_data}")
        params = parse_qs(post_data)
        response = {"message": "Received POST request", "data": params}
        if 'song' in params:
            song_title = params['song'][0]
            spotify_response = self.save_to_spotify(song_title)
            response.update(spotify_response)
        self.wfile.write(json.dumps(response).encode())
    def save_to_spotify(self, song_title):
        global sp
        if sp is None:
            logging.error("Spotify client is not initialized")
            return {
                "spotify_status": "error",
                "message": "Spotify client is not initialized"
            }
        logging.info(f"Attempting to save song: {song_title}")
        try:
            # Search for the track
            results = sp.search(q=song_title, type='track', limit=1)
            if results['tracks']['items']:
                track = results['tracks']['items'][0]
                # Save the track to the user's library
                sp.current_user_saved_tracks_add(tracks=[track['id']])
                logging.info(f"Successfully saved track: {track['name']} by {track['artists'][0]['name']}")
                return {
                    "spotify_status": "success",
                    "saved_track": f"{track['name']} by {track['artists'][0]['name']}"
                }
            else:
                logging.warning(f"Track not found on Spotify: {song_title}")
                return {
                    "spotify_status": "not_found",
                    "message": f"Track not found on Spotify: {song_title}"
                }
        except Exception as e:
            logging.error(f"An error occurred while saving to Spotify: {e}")
            return {
                "spotify_status": "error",
                "message": f"An error occurred: {str(e)}"
            }
def run_server(port=8080):
    server_address = ('', port)
    try:
        httpd = HTTPServer(server_address, SpotifyServerHandler)
        logging.info(f"Server running on port {port}")
        httpd.serve_forever()
    except OSError as e:
        if e.errno == 98:
            logging.error(f"Error: Port {port} is already in use. Try a different port.")
        else:
            logging.error(f"Error: {e}")
        sys.exit(1)
if __name__ == '__main__':
    lock_file = '/tmp/spotify_server.lock'
    try:
        lock_handle = open(lock_file, 'w')
        fcntl.lockf(lock_handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
    except IOError:
        logging.error("Another instance of this script is already running.")
        sys.exit(1)
    try:
        # Initialize Spotify client
        sp = initialize_spotify()
        if not sp:
            logging.error("Failed to initialize Spotify client")
            sys.exit(1)
        port = int(sys.argv[1]) if len(sys.argv) > 1 else 8080
        run_server(port)
    except KeyboardInterrupt:
        logging.info("\nServer stopped.")
    finally:
        fcntl.lockf(lock_handle, fcntl.LOCK_UN)
        lock_handle.close()
        os.unlink(lock_file)

Darin musst du oben die folgenden zwei Schlüssel hinterlegen – diese findest du in deinem Spotify Developer Account.

SPOTIPY_CLIENT_ID = 'deine_client_id'
SPOTIPY_CLIENT_SECRET = 'dein_client_secret'

Und das war es schon mit den Anpassungen im Python Script. Falls du im Terminal noch auf deinem Rasperry Pi eingeloggt bist, logge dich mit logout aus und bewege dich in den Ordner, in dem dein gerade erstelltes Python Script liegt. Falls du unsicher bist, wie das geht, wirf einen Blick in die Tipps der Uni Düsseldorf.

Im Verzeichnis angekommen führe folgenden Befehl aus:

scp radiospotify.py pi@raspberrypi.local:/home/pi/RadioSpotify/

Den Code ausführen

Jetzt, wo der Code auf deinem Raspberry Pi ist, kannst du ihn aufrufen. Logge dich dafür zunächst wieder per SSH ein (ersetze pi wieder durch deinen Benutzernamen):

sudo ssh pi@raspberrypi.local

Anschließend kannst du das Script wie folgt starten:

source RadioSpotify/bin/activate
python3 RadioSpotify/radiospotify.py

Als nächstes musst deinen Rasperry Pi bei Spotify Zugriff auf dein dortiges Konto geben. Das ist etwas umständlich – aber du musst es zum Glück nur einmal machen. Der entsprechende Token wird auf deinem Raspberry Pi gespeichert, sodass du dich beim nächsten Start des Scripts nicht noch einmal authentifizieren musst.

Im Terminal wirst du nach dem Start des Scripts aufgefordert, eine Adresse im Browser zu öffnen – das kannst du in einem Browser deiner Wahl auf deinem Computer machen. Du wirst dort von Spotify gefragt, ob es die Verbindung zu deiner App herstellen darf – stimme dem zu. Daraufhin solltest du zwar im Browser etwas in der Art von „Webseite nicht erreichbar“ sehen – in der Ardresszeile jedoch eine andere URL stehen haben.

Kopiere die gesamte neue URL aus der Adresszeile, öffne ein neues Terminal-Fenster auf deinem Computer, logge dich auch darin per SSH auf deinem Raspi ein und verwende den folgenden Befehl:

curl "DEINE KOPIERTE URL"

Nun sollte in diesem Terminal-Fenster die Info Authentication successful! und in deinem ersten Fenster die Zeile INFO – Server running on port 8080 erscheinen. Das bedeutet, dass der Login bei Spotify funktioniert hat und dein Raspberry Pi nun bereit ist, Songs vom ESP32 Internetradio zu empfangen und in deiner Spotify-Playlist zu speichern. Weiter geht es mit deinem ESP32.

Ein weiteres Kabel am ESP32

Damit du Songs an Spotify senden kannst, benötigst du einen Button, mit dem diese Funktion auslöst. Praktischerweise bringt dein Rotary Encode schon einen mit – du kannst diesen nicht nur drehen, sondern auch hörbar eindrücken. Dieser Druck wird am Pin SW ausgelesen. Verbinde deshalb diesen Pin mit dem GPIO 7 deines ESP32 – wenn du einen ESP32-S3 Zero verwendest, sieht die neue Verbindung so aus (das weiße Kabel ist die Verbindung vom Button zum ESP32):

ESP32 Internetradio mit Spotify-Anbindung

Du kannst natürlich auch einen anderen Pin des ESP32 verwenden, musst das dann allerdings entsprechend im folgenden Sketch ändern.

Aktualisiere den Sketch auf dem ESP32

In deinem ESP32 Internetradio fehlt nun nur noch die Funktion, den aktuellen Song zum Raspberry Pi zu senden und ihn von diesem in deinen Spotify-Lieblingssongs zu speichern. Hierfür ist ein Umbau nötig – hier der aktualisierte Sketch:

#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <Audio.h>
#include <AiEsp32RotaryEncoder.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ArduinoJson.h>

// Pin definitions bleiben unverändert
#define I2S_DOUT 2
#define I2S_BCLK 3
#define I2S_LRC 4
#define VOLUME_PIN 5

#define ROTARY_ENCODER_A_PIN 12
#define ROTARY_ENCODER_B_PIN 13
#define ROTARY_ENCODER_BUTTON_PIN 7
#define ROTARY_ENCODER_STEPS 4

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 32
#define OLED_RESET     -1
#define SCREEN_ADDRESS 0x3C

#define I2C_SDA 8
#define I2C_SCL 9

// Debug Level für ESP32
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE

// Watchdog Timeout
const int wdtTimeout = 5000;  // 5 Sekunden Watchdog Timeout

// Initialisierungs-Flags
bool isWiFiConnected = false;
bool isDisplayInitialized = false;
bool isAudioInitialized = false;

AiEsp32RotaryEncoder rotaryEncoder(ROTARY_ENCODER_A_PIN, ROTARY_ENCODER_B_PIN, ROTARY_ENCODER_BUTTON_PIN, -1, ROTARY_ENCODER_STEPS);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

Audio audio;

// WiFi credentials
const char ssid[] = "DEIN WLAN-NETZWERK";
const char password[] = "DEIN WLAN-PASSWORT";

// Spotify server details
const char* serverName = "IP-ADRESSE:8080";

// Radio stations bleiben unverändert
const char* stations[] = {
    "http://www.byte.fm/stream/bytefm.m3u",
    "https://st01.sslstream.dlf.de/dlf/01/128/mp3/stream.mp3",
    "https://frontend.streamonkey.net/fho-schwarzwaldradiolive/mp3-stream.m3u",
    "https://kexp-mp3-128.streamguys1.com/kexp128.mp3",
    "https://eagle.streemlion.com:2199/tunein/psychedelicj.asx"
};
const char* stationNames[] = {
    "Byte.fm",
    "Deutschlandfunk",
    "Schwarzwaldradio",
    "KEXP",
    "Psychedelic Jukebox"
};
const int NUM_STATIONS = sizeof(stations) / sizeof(stations[0]);
int currentStation = 0;

// Statische Buffer statt dynamischer Strings
char streamTitle[64] = "";
char urlBuffer[256] = "";
char jsonBuffer[512] = "";

// Volume control variables bleiben unverändert
const int SAMPLES = 5;
int volumeReadings[SAMPLES];
int readIndex = 0;
int total = 0;
int average = 0;
unsigned long lastVolumeCheck = 0;
const unsigned long VOLUME_CHECK_INTERVAL = 500;

void IRAM_ATTR readEncoderISR() {
    rotaryEncoder.readEncoder_ISR();
}

// Optimierte String-Ersetzungsfunktion mit statischem Buffer
void replaceSpecialChars(const char* input, char* output, size_t outputSize) {
    size_t i = 0, j = 0;
    while (input[i] && j < outputSize - 1) {
        char c = input[i++];
        switch (c) {
            case 'ä': memcpy(&output[j], "a", 1); j += 1; break;
            case 'ö': memcpy(&output[j], "o", 1); j += 1; break;
            case 'ü': memcpy(&output[j], "u", 1); j += 1; break;
            case 'Ä': memcpy(&output[j], "A", 1); j += 1; break;
            case 'Ö': memcpy(&output[j], "O", 1); j += 1; break;
            case 'Ü': memcpy(&output[j], "U", 1); j += 1; break;
            case 'ß': memcpy(&output[j], "ss", 2); j += 2; break;
            default: output[j++] = c;
        }
    }
    output[j] = '\0';
}

void setup() {
    Serial.begin(115200);
    
    // Debug Level setzen
    esp_log_level_set("*", ESP_LOG_VERBOSE);
    
    Serial.println(F("ESP32-S3 Internet Radio starting..."));
    Serial.printf("Initial free heap: %d bytes\n", ESP.getFreeHeap());
    
    // Basis-Setup
    pinMode(VOLUME_PIN, INPUT);
    
    // Encoder Setup
    rotaryEncoder.begin();
    rotaryEncoder.setup(readEncoderISR);
    rotaryEncoder.setBoundaries(0, NUM_STATIONS - 1, true);
    rotaryEncoder.setAcceleration(0);
    
    // Volume readings initialisieren
    for (int i = 0; i < SAMPLES; i++) {
        volumeReadings[i] = 0;
    }
    
    // Wire begin - grundlegende I2C-Initialisierung
    Wire.begin(I2C_SDA, I2C_SCL);
}

void loop() {
    static unsigned long lastInitAttempt = 0;
    const unsigned long initInterval = 5000;
    
    // Heap-Überwachung
    static unsigned long lastHeapCheck = 0;
    if (millis() - lastHeapCheck > 10000) {  // Alle 10 Sekunden
        Serial.printf("Free heap: %d bytes\n", ESP.getFreeHeap());
        lastHeapCheck = millis();
    }
    
    // Verzögerte Initialisierung
    if (!isDisplayInitialized && millis() - lastInitAttempt > initInterval) {
        initializeDisplay();
        lastInitAttempt = millis();
    }
    
    if (!isWiFiConnected && millis() - lastInitAttempt > initInterval) {
        connectToWiFi();
        lastInitAttempt = millis();
    }
    
    if (isWiFiConnected && !isAudioInitialized && millis() - lastInitAttempt > initInterval) {
        initializeAudio();
        lastInitAttempt = millis();
    }
    
    // Normale Loop-Funktionalität nur wenn alles initialisiert ist
    if (isDisplayInitialized && isWiFiConnected && isAudioInitialized) {
        audio.loop();
        yield();
        checkEncoder();
        yield();
        checkVolumeControl();
        yield();
    }
    
    delay(10);  // Kleine Pause für Stabilität
}

void initializeDisplay() {
    Serial.println(F("Initializing OLED display..."));
    if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
        Serial.println(F("SSD1306 initialization failed"));
        return;  // Statt Endlosschleife einfach zurückkehren
    }
    
    display.clearDisplay();
    display.setTextSize(1);
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(0,0);
    display.println(F("Initializing..."));
    display.display();
    
    isDisplayInitialized = true;
    Serial.println(F("Display initialized successfully"));
}

void connectToWiFi() {
    Serial.println(F("Connecting to WiFi..."));
    WiFi.begin(ssid, password);
    
    int attempts = 0;
    while (WiFi.status() != WL_CONNECTED && attempts < 20) {
        delay(500);
        Serial.print(".");
        attempts++;
    }
    
    if (WiFi.status() == WL_CONNECTED) {
        Serial.println(F("\nWiFi connected"));
        isWiFiConnected = true;
        if (isDisplayInitialized) {
            display.clearDisplay();
            display.setCursor(0,0);
            display.println(F("WiFi connected"));
            display.display();
        }
    } else {
        Serial.println(F("\nWiFi connection failed"));
    }
}

void initializeAudio() {
    audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
    audio.setVolume(10);
    connectToStation(currentStation);
    isAudioInitialized = true;
    Serial.println(F("Audio initialized"));
}

void checkEncoder() {
    if (rotaryEncoder.encoderChanged()) {
        currentStation = rotaryEncoder.readEncoder();
        connectToStation(currentStation);
    }
    
    if (rotaryEncoder.isEncoderButtonClicked()) {
        Serial.println(F("Encoder button clicked"));
        sendToSpotify();
    }
}

void connectToStation(int stationIndex) {
    audio.stopSong();
    audio.connecttohost(stations[stationIndex]);
    updateDisplay();
}

void checkVolumeControl() {
    unsigned long currentMillis = millis();
    if (currentMillis - lastVolumeCheck >= VOLUME_CHECK_INTERVAL) {
        lastVolumeCheck = currentMillis;
        
        total = total - volumeReadings[readIndex];
        volumeReadings[readIndex] = analogRead(VOLUME_PIN);
        total = total + volumeReadings[readIndex];
        readIndex = (readIndex + 1) % SAMPLES;
        
        average = total / SAMPLES;
        int volume = map(average, 0, 4095, 5, 23);
        
        static int lastVolume = -1;
        if (volume != lastVolume) {
            audio.setVolume(volume);
            lastVolume = volume;
            updateDisplay();
        }
    }
}

void updateDisplay() {
    if (!isDisplayInitialized) return;
    
    display.clearDisplay();
    display.setCursor(0,0);
    
    char buffer[64];
    replaceSpecialChars(stationNames[currentStation], buffer, sizeof(buffer));
    display.println(buffer);
    
    display.println();
    replaceSpecialChars(streamTitle, buffer, sizeof(buffer));
    display.println(buffer);
    
    display.display();
}

// Optimierte URL-Encoding Funktion mit statischem Buffer
void urlEncode(const char* input, char* output, size_t outputSize) {
    size_t j = 0;
    for (size_t i = 0; input[i] && j < outputSize - 4; i++) {
        char c = input[i];
        if (isalnum(c)) {
            output[j++] = c;
        } else if (c == ' ') {
            output[j++] = '+';
        } else {
            if (j + 3 >= outputSize) break;
            sprintf(&output[j], "%%%02X", c);
            j += 3;
        }
        yield();  // Watchdog füttern während langer Operationen
    }
    output[j] = '\0';
}

void sendToSpotify() {
    static unsigned long lastRequestTime = 0;
    unsigned long currentTime = millis();
    
    if (currentTime - lastRequestTime < 5000) {
        Serial.println(F("Request blocked: Too soon since last request"));
        return;
    }
    lastRequestTime = currentTime;
    
    if (WiFi.status() == WL_CONNECTED) {
        HTTPClient http;
        
        // URL-Encoding mit statischem Buffer
        char encodedTitle[128];
        urlEncode(streamTitle, encodedTitle, sizeof(encodedTitle));
        
        // URL zusammenbauen mit snprintf
        snprintf(urlBuffer, sizeof(urlBuffer), "%s/?song=%s", serverName, encodedTitle);
        
        Serial.println(F("--------- New Request ---------"));
        Serial.print(F("Request URL: "));
        Serial.println(urlBuffer);
        
        http.begin(urlBuffer);
        http.setTimeout(5000);  // 5 Sekunden Timeout
        http.setReuse(false);   // Keine Verbindungswiederverwendung
        
        yield();  // Watchdog füttern
        
        int httpResponseCode = http.GET();
        
        if (httpResponseCode > 0) {
            // Statisches JSON-Dokument
            StaticJsonDocument<512> doc;
            
            // Response lesen
            String payload = http.getString();
            yield();  // Watchdog füttern
            
            DeserializationError error = deserializeJson(doc, payload);
            
            if (!error) {
                const char* message = doc["message"];
                Serial.print(F("Server message: "));
                Serial.println(message);
                
                if (doc.containsKey("spotify_status")) {
                    const char* spotifyStatus = doc["spotify_status"];
                    Serial.print(F("Spotify status: "));
                    Serial.println(spotifyStatus);
                }
            }
        } else {
            Serial.print(F("Error on HTTP request: "));
            Serial.println(httpResponseCode);
        }
        
        http.end();
        Serial.println(F("Connection closed"));
    }
    
    yield();  // Watchdog füttern am Ende
}

// Audio callback functions
void audio_info(const char *info) { 
    Serial.print(F("info        ")); Serial.println(info);
}

void audio_id3data(const char *info) {
    Serial.print(F("id3data     ")); Serial.println(info);
}

void audio_eof_mp3(const char *info) {
    Serial.print(F("eof_mp3     ")); Serial.println(info);
}

void audio_showstation(const char *info) {
    Serial.print(F("station     ")); Serial.println(info);
}

void audio_showstreaminfo(const char *info) {
    Serial.print(F("streaminfo  ")); Serial.println(info);
}

void audio_showstreamtitle(const char *info) {
    Serial.print(F("streamtitle: ")); Serial.println(info);
    strncpy(streamTitle, info, sizeof(streamTitle) - 1);
    streamTitle[sizeof(streamTitle) - 1] = '\0';
    updateDisplay();
}

void audio_bitrate(const char *info) {
    Serial.print(F("bitrate     ")); Serial.println(info);
}

void audio_commercial(const char *info) {
    Serial.print(F("commercial  ")); Serial.println(info);
}

void audio_icyurl(const char *info) {
    Serial.print(F("icyurl      ")); Serial.println(info);
}

void audio_lasthost(const char *info) {
    Serial.print(F("lasthost    ")); Serial.println(info);
}

void audio_eof_speech(const char *info) {
    Serial.print(F("eof_speech  ")); Serial.println(info);
}

Im obigen Sketch musst du zunächst deine eigenen WLAN-Zugangsdaten eintragen:

const char ssid[] = "DEIN WLAN-NETZWERK";
const char password[] = "DEIN WLAN-PASSWORT";

Außerdem benötigst du die IP-Adresse deines Raspberry Pi. Diese findest du zum Beispiel mit dem folgenden Befehl heraus, den du im Terminal eingibst (während du per SSH mit ihm verbunden bist):

hostname -I

Daraufhin erscheint die IP-Adresse im Terminal (im rot markierten Teil):

IP-Adresse des Raspberry Pi im Terminal

Diese Adresse trägst du dann im Sketch hier ein, versehen mit dem Port :8080

const char* serverName = "http://192.168.0.45:8080";

Die Wahl der Radiostationen bzw. deren Stream-Adressen und Namen bleibt dir natürlich selbst überlassen – hier hast du sicherlich bestimmt schon eine Liste erstellt und kannst sie in diesen Sketch übernehmen.

Wenn du alles erledigt hast, kannst du diesen Sketch auf deinen ESP32 hochladen und damit wie gewohnt Radio hören.

Einen Song an Spotifiy übertragen

Jetzt wird es Zeit für den Test deiner neuen Funktion! Sobald du einen Song hörst, dessen Interpret und Titel du auf dem Display siehst, drücke den Button an deinem Rotary Encode. Der Song sollte daraufhin kurz abbrechen und dann wieder einsetzen. In der Zwischenzeit gibt dir der Serielle Monitor Auskunft darüber, was gerade passiert ist:

Erfolgreiche Übertragung eines Songs an Spotify

Für die Übertragung wurde die IP-Adresse deines Raspberry Pi um die Song-Informationen erweitert und von deinem ESP32 aufgerufen. Dein Raspberry hat den Song erfolgreich an Spotify weitergeleitet und sendet dem ESP32 ein Spotify status: success

Wirf nun in Spotify einen Blick in deine Playlist Lieblingssongs. Hier sollte der gerade übertragene Song bereits zu finden sein.

Das Script automatisch starten

Noch ein letzter Baustein für dieses Projekt: Es ist natürlich sehr unpraktisch, wenn du dein neues Feature immer erst aktivieren musst, indem du auf dem Raspberry Pi die virtuelle Umgebung aktivierst und das Python-Script manuell startest. Deshalb wirst du nun dafür sorgen, dass das Script automatisch startet, sobald du deinen Raspi hochfährst. Das geht folgendermaßen:

  • Erstelle eine Service-Datei:
sudo nano /etc/systemd/system/spotify-radio.service
  • Kopiere den den folgenden Code, füge ihn ein und speichere es ab mit STRG+O, STRG+X.
[Unit]
Description=Spotify Radio Service
After=network.target

[Service]
Type=simple
User=pi
WorkingDirectory=/home/pi/RadioSpotify
ExecStart=/home/pi/RadioSpotify/bin/python /home/pi/RadioSpotify/radiospotify.py
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
  • Lade die systemd-Konfiguration neu:
sudo systemctl daemon-reload
  • Aktiviere den Service für den Autostart:
sudo systemctl enable spotify-radio
  • Starte nun den Service:
sudo systemctl start spotify-radio

Ob der Service läuft, kannst du abschließend mit folgendem Befehl überprüfen:

sudo systemctl status spotify-radio

Im Terminal sollte nun etwas in dieser Art anzeigen, dass dein Spotify-Service läuft:

Spotify-Service läuft im Terminal

Starte testweise deinen Raspberry Pi neu und führe noch einmal die obige Prüfung aus. In deinem Terminal sollte wieder stehen, dass der Service aktiv ist.

Und das war es! Dein ESP32 Internetradio ist nun direkt mit Spotify verbunden und du hast die Möglichkeit, interessante Songs dort in deinen Lieblingssongs speichern. Viel Spaß damit!

]]>
Die Zimmer lüften mit dem Arduino https://polluxlabs.net/arduino-projekte/die-zimmer-lueften-mit-dem-arduino/ Thu, 05 Dec 2024 09:22:06 +0000 https://polluxlabs.net/?p=18059 Gehörst du zu den Leuten, die gerne die Zimmer lüften, aber dann vergessen, dass das Fenster offen ist? Mir passiert das regelmäßig – bis jetzt. In diesem Projekt überwacht ein Arduino Nano die Temperatur, sobald du das Fenster zum Lüften öffnest. Fällt die Temperatur im Raum um 0,5°C schlägt er Alarm. So kannst du sicher sein, dass der Raum oder sogar die ganze Wohnung nicht unnötig auskühlt.

Diese Bauteile benötigst du:

  • Arduino Nano
  • Temperatursensor DHT22
  • Piezo-Summer
  • Button
  • 2x 10kΩ Widerstände
  • Breadboard & Kabel

So funktioniert die Temperaturüberwachung

Wenn du das Fenster öffnest, drückst du einen Button am Arduino – damit wird der Startwert der Temperatur festgelegt und die Messung beginnt. Während nun (mal abgesehen vom Sommer) kühlere Luft ins Zimmer gelangt, fällt die Temperatur darin kontinuierlich. Sobald die Zimmertemperatur um 0,5°C gefallen ist, schlägt der Piezo-Summer Alarm und ruft dich ans geöffnete Fenster, um es zu schließen. Du drückst den Button erneut: der Alarm hört auf und der Arduino wird wieder in den Wartezustand zurückgesetzt – bis zum nächsten Lüften.

Natürlich kannst du den Wert von 0,5°C ändern und so einstellen, dass er zu deinen Wünschen, zur Positionierung des Arduinos im Zimmer etc. passt.

Aufgebauter Arduino Temperaturwächter

So baust du das Projekt zusammen

Orientiere dich beim Aufbau an der folgenden Skizze. Ich habe für dieses Projekt wegen seiner kompakten Größe einen Arduino Nano verwendet. Du kannst aber natürlich auch ein anderes Board verwenden, das du gerade zur Hand hast – also zum Beispiel einen Arduino UNO. Auch muss es nicht unbedingt der Sensor DHT22 sein – in diesem Tutorial lernst du, wie du den „kleinen Bruder“ DHT11 am Arduino anschließt und verwendest.

Aufbau des Projekts mit dem Arduino Nano und weiteren Bauteilen

Wenn du alles verkabelt hast, kann es mit dem Sketch weitergehen.

Der Sketch für das Projekt

Bevor du den folgenden Sketch kopierst und auf deinen Arduino hochlädst, benötigst du noch die passenden Bibliotheken für den Sensor DHT22 – falls du in der Arduino IDE noch nie ein Projekt mit diesem Sensor umgesetzt hast.

Öffne in diesem Fall den Bibliotheksmanager in der IDE und suche nach DHT sensor library und klicke auf Installieren. In einem Dialogfenster wirst du daraufhin gefragt, ob du die Bibliothek Adafruit Unified Sensor gleich mitinstallieren möchtest. Bestätige das mit einem Klick auf Ja.

Nun zum vollständigen Sketch:

___STEADY_PAYWALL___

#include <DHT.h>

// Pins
#define DHTPIN 7       // DHT22 an Pin D7
#define BUZZER_PIN 4   // Piezo-Summer an Pin D4
#define BUTTON_PIN 6   // Taster an Pin D6
#define LED_PIN 13     // Interne LED an Pin D13

// DHT Sensor
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);

// Variablen
float startTemperature = 0.0;
bool measuring = false;
bool alarmActive = false;

void setup() {
  Serial.begin(9600);
  dht.begin();
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP); // Interner Pull-Up-Widerstand aktivieren
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(BUZZER_PIN, LOW);
  digitalWrite(LED_PIN, LOW); // LED initial ausgeschaltet
}

void loop() {
  // Taster prüfen (wechseln zwischen Messung und Leerlauf)
  if (digitalRead(BUTTON_PIN) == LOW) { // Taster gedrückt
    delay(50); // Entprellung
    // Sicherstellen, dass der Taster weiterhin gedrückt ist
    if (digitalRead(BUTTON_PIN) == LOW) {
      if (!measuring) {
        // Messung starten
        measuring = true;
        alarmActive = false; // sicherstellen, dass der Alarm deaktiviert ist
        startTemperature = dht.readTemperature();
        if (isnan(startTemperature)) {
          Serial.println("Fehler beim Lesen des DHT22!");
          startTemperature = 0.0; // Standardwert
        }
        Serial.print("Messung gestartet. Starttemperatur: ");
        Serial.println(startTemperature);
        digitalWrite(LED_PIN, HIGH); // LED einschalten
      } else {
        // In Leerlauf wechseln: Alarm und Messung stoppen
        noTone(BUZZER_PIN); // Alarmton stoppen
        digitalWrite(LED_PIN, LOW); // LED ausschalten
        alarmActive = false; 
        measuring = false;
        Serial.println("Alarm ausgeschaltet und Leerlauf aktiviert.");
      }
      // Warten bis der Button losgelassen wird
      while (digitalRead(BUTTON_PIN) == LOW);
      delay(50); // Entprellzeit nach Loslassen
    }
  }
  
  // Messlogik, wenn aktiv
  if (measuring) {
    float currentTemperature = dht.readTemperature();
    if (isnan(currentTemperature)) {
      Serial.println("Fehler beim Lesen des DHT22!");
      delay(2000);
      return;
    }

    // Temperaturänderung prüfen
    if (currentTemperature <= startTemperature - 0.5) {
      alarmActive = true; // Alarm auslösen
    }

    // Alarm ausführen
    if (alarmActive) {
      tone(BUZZER_PIN, 1000); // Alarmton
      Serial.println("Alarm: Temperatur ist gesunken!");
    } else {
      noTone(BUZZER_PIN); // Alarmton deaktivieren
    }

    // Debug-Ausgabe
    Serial.print("Aktuelle Temperatur: ");
    Serial.println(currentTemperature);

    delay(2000); // Messung alle 2 Sekunden
  }
}

So funktioniert der sketch

Lass uns einen genaueren Blick auf den Sketch werfen. Zunächst bindest du die Bibliothek für den Temperatursensor ein und legst die Anschlüsse der Bauteile fest:

#include <DHT.h>

#define DHTPIN 7       // DHT22 an Pin D7
#define BUZZER_PIN 4   // Piezo-Summer an Pin D4
#define BUTTON_PIN 6   // Taster an Pin D6
#define LED_PIN 13     // Interne LED an Pin D13

#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);

Weiter geht es mit den benötigten Variablen. In startTemperature wird die Temperatur zu Beginn deiner Messung – also sobald du auf den Button drückst – erfasst. Die beiden Variablen measuring und alarmActive dienen später der Programmsteuerung.

float startTemperature = 0.0;
bool measuring = false;
bool alarmActive = false;

Setup-Funktion

Hier startest du den Seriellen Monitor und initialisierst den Sensor. Außerdem legst du die benötigten pinModes fest und aktivierst für den Button den internen Pullup-Widerstand des Arduinos. Zuletzt stellst du sicher, dass der Piezo-Summer und die interne LED ausgeschaltet sind.

void setup() {

  Serial.begin(9600);
  dht.begin();
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP); // Interner Pull-Up-Widerstand aktivieren
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(BUZZER_PIN, LOW);
  digitalWrite(LED_PIN, LOW); // LED initial ausgeschaltet
}

Loop-Funktion

Das ist der Hauptteil des Programms, der kontinuierlich läuft. Hier wird entschieden, ob die Messung durchgeführt wird oder nicht:

if (digitalRead(BUTTON_PIN) == LOW) {

  // Wurde der Button gedrückt? Dann beginnt die Messung, falls sie nicht bereits läuft.

}

Bei einem Druck auf den Button passiert Folgendes: Wenn die Messung nicht läuft (die Variable measuring ist false), wird sie gestartet. Die Variable measuring wird dann auf true gesetzt.

Wenn die Messung allerdings läuft, wird sie gestoppt. Der Alarm wird deaktiviert und die LED ausgeschaltet. Die Variable measuring wird wieder auf false gesetzt. Der Arduino befindet sich nun sozusagen im Leerlauf.

In der Messung selbst wird alle 2 Sekunden geprüft, ob die aktuelle Temperatur mehr als 0,5°C unterhalb der Starttemperatur liegt. Ist das der Fall, wird der Alarm aktiviert:

// Temperaturänderung prüfen
if (currentTemperature <= startTemperature - 0.5) {
  alarmActive = true; // Alarm auslösen
}

Hierfür wird die Variable alarmActive auf true gesetzt, was dazu führt, dass in der darauffolgenden Abfrage der Piezo-Summer aktiviert wird:

if (alarmActive) {
  tone(BUZZER_PIN, 1000); // Alarmton
  Serial.println("Alarm: Temperatur ist gesunken!");
} else {
  noTone(BUZZER_PIN); // Alarmton deaktivieren
}

Hier wird geprüft, ob alarmActive == true ist (in einer verkürzten Schreibweise) und der Piezo entsprechend aktiviert oder deaktiviert.

Wie geht es weiter?

Du hast nun einen Arduino, der laut Alarm schlägt, sobald die Temperatur entsprechend deiner Einstellung gefallen ist. Statt einen Piezo-Summers kannst du aber natürlich auch eine andere Benachrichtigung wählen. Wie wäre es z.B. mit einer Push Notification auf deinem Smartphone? Hierfür benötigst du allerdings einen Microcontroller mit WLAN, wie z.B. den ESP8266 oder ESP32.

]]>
Raspberry Pi Webserver mit Flask https://polluxlabs.net/raspberry-pi-projekte/raspberry-pi-webserver-mit-flask/ Tue, 01 Oct 2024 10:04:56 +0000 https://polluxlabs.net/?p=16293 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.

]]>
ESP32 Internetradio https://polluxlabs.net/esp8266-projekte/esp32-internetradio/ Wed, 07 Aug 2024 19:01:34 +0000 https://polluxlabs.net/?p=16902 Radio übers Internet zu hören, ist heute natürlich nichts Besonderes mehr – mit einem selbstgebauten ESP32 Internetradio allerdings schon! In diesem Tutorial baust du dir deinen eigenen Empfänger, mit dem du deine Lieblingssender hören kannst. Das Projekt ist mehrstufig aufgebaut – du erweiterst dein Internetradio nach und nach um weitere Bauteile und Funktionen, bis du ein vollwertiges Gerät mit Senderwahl, Display und Lautstärkeregelung hast.

Höre dir vorab die Projekt-Vorstellung an:

Diese Bauteile benötigst du:

Update: In diesem Tutorial lernst du zunächst, wie du ein Radio baust, das „nur“ einen Sender empfängt und abspielt. Weiter unten findest du die Erweiterung für ein ESP32 Internetradio mit Senderwahl und Display.

Aufbau des ESP32 Internetradios

Zunächst, wie angekündigt, die einfachste Schaltung für dein Internetradio. Hierbei benötigst du nur den ESP32-S3 Zero (z.B. von Waveshare), das Verstärkermodul und einen Lautsprecher. Orientiere dich beim Aufbau an folgender Skizze:

ESP32 Internetradio v1

Hier noch einmal als Tabelle:

VerstärkermodulESP32-S3
VIN5V
GNDGND
GAIN
DINGPIO 2
BCLKGPIO 3
LRCGPIO 4

Kurzer Exkurs zum Gain

Über den Gain-Pin am Verstärkermodul kannst du die Lautstärke steuern – unterschiedlich stark, je nachdem, wie du ihn anschließt:

  • 15dB mit einem 100kΩ Widerstand zwischen GAIN und GND
  • 12dB wenn GAIN direkt an GND angeschlossen ist
  • 9dB wenn GAIN überhaupt nicht verbunden ist (wie in der Skizze oben)
  • 6dB wenn GAIN mit Vin verbunden ist
  • 3dB mit einem 100kΩ Widerstand zwischen GAIN und Vin

Für deinen Lautsprecher hat das Verstärkermodul eine Buchse, in der du mit einer Schraube die Kabel fixieren kannst. Aufgebaut könnte dein Radio so aussehen:

Foto des ersten ESP32 Internetradios

Der Sketch für das Radio

Zentral für dein Internetradio ist eine Bibliothek, die du über den Bibliotheksverwalter der Arduino IDE installieren kannst. Suche dort nach ESP32-audioI2S und installiere die aktuelle Version.

Wichtiger Hinweis: Die folgenden Sketches benötigen die Bibliothek in der Version 3.4.1 – aber der Version 3.4.2 werden Infos (z.B. zum aktuell gespielten Song) anders bereitgestellt, was ich noch nicht berücksichtigt habe. Wähle bitte im Bibliotheksmanager die Version 3.4.1 aus.

Kopiere nun den folgenden Sketch, ergänze die Zugangsdaten für dein WLAN-Netz und lade ihn auf deinen ESP32 hoch. Solltest du hierbei Probleme mit dem ESP32-S3 Zero haben, prüfe, ob du das richtige Board ausgewählt hast. Funktionieren sollte es mit ESP32 S3 Dev Module. Schaue auch nach, ob du im Menü Tools den Eintrag USB CDC On Boot: „Enabled“ siehst. Falls dieser auf Disabled steht, ändere ihn entsprechend.

Falls du noch nie einen ESP32 mit der Arduino IDE programmiert hast, wirf zunächst einen Blick dieses Tutorial.

//ESP32 Internetradio
//https://polluxlabs.net

#include "Arduino.h"
#include "WiFi.h"
#include "Audio.h"

// Verbindungen ESP32 <-> Verstärkermodul
#define I2S_DOUT  2
#define I2S_BCLK  3
#define I2S_LRC   4

Audio audio;

// WLAN Zugangsdaten
String ssid =    "DEIN NETZWERK";
String password = "DEIN PASSWORT";

void setup() {

  Serial.begin(115200);

  WiFi.disconnect();
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid.c_str(), password.c_str());

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  Serial.println("");

  audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);

  // Lautstärke (0-100)
  audio.setVolume(10);

  // Verbindung zum gewünschten Stream, z.B. Byte.fm
  audio.connecttohost("http://www.byte.fm/stream/bytefm.m3u");

}

void loop()

{
  audio.loop();

}

// Print der Senderinfos

void audio_info(const char *info) {
  Serial.print("info        "); Serial.println(info);
}
void audio_id3data(const char *info) { //id3 metadata
  Serial.print("id3data     "); Serial.println(info);
}
void audio_eof_mp3(const char *info) { //end of file
  Serial.print("eof_mp3     "); Serial.println(info);
}
void audio_showstation(const char *info) {
  Serial.print("station     "); Serial.println(info);
}
void audio_showstreaminfo(const char *info) {
  Serial.print("streaminfo  "); Serial.println(info);
}
void audio_showstreamtitle(const char *info) {
  Serial.print("streamtitle "); Serial.println(info);
}
void audio_bitrate(const char *info) {
  Serial.print("bitrate     "); Serial.println(info);
}
void audio_commercial(const char *info) { //duration in sec
  Serial.print("commercial  "); Serial.println(info);
}
void audio_icyurl(const char *info) { //homepage
  Serial.print("icyurl      "); Serial.println(info);
}
void audio_lasthost(const char *info) { //stream URL played
  Serial.print("lasthost    "); Serial.println(info);
}
void audio_eof_speech(const char *info) {
  Serial.print("eof_speech  "); Serial.println(info);
}

Wenn alles funktioniert, solltest du nach dem Start des ESP32 Internetradios den Sender Byte.fm hören – ein mitgliederfinanzierter Sender aus Hamburg, der abseits des Mainstreams Programmradio macht – ohne Werbung.

Hinweis zum Speicherplatz: Falls du beim Upload eine Fehlermeldung erhältst, dass der Sketch den verfügbaren Speicherplatz deines ESP32 überschreitet, wirf bitte kurz einen Blick auf diesen Hinweis.

So funktioniert der Sketch

Zunächst bindest du wie immer einige Bibliotheken ein. Die ersten beiden sind hierbei bereits in deiner Arduino IDE verfügbar, die Bibliothek Audio.h hast du wie oben beschrieben heruntergeladen und in der IDE eingebunden.

Anschließend definierst du, an welchen Pins das Verstärkermodul am ESP32 angeschlossene ist. Diese kannst du natürlich frei wählen, vergiss nur nicht, sie entsprechend im Sketch zu hinterlegen:

#define I2S_DOUT  2
#define I2S_BCLK  3
#define I2S_LRC   4

Anschließend erstellst du ein Objekt der Audio-Bibliothek und hinterlegst die Zugangsdaten zu deinem WLAN-Netzwerk. In der Setup-Funktion startest du dann den Seriellen Monitor und verbindest den ESP32 mit dem WLAN. Wichtig sind die beiden Zeilen

audio.setVolume(10);
audio.connecttohost("http://www.byte.fm/stream/bytefm.m3u");

In der ersten stellst du die Lautstärke des Streams ein – mit einer Zahl zwischen 0 und 100. Anschließend hinterlegst du die Adresse des Streams, hier also jener von Byte.fm. Wenn du einen anderen Stream abspielen möchtest, wirst du meist recht einfach über eine Suche fündig.

Im Loop gibt es nur eine Funktion: Das Abspielen des Streams. Zuletzt folgen noch einige sogenannte Callback-Funktionen, die aufgerufen werden, wenn ich bestimmtes Ereignis auftritt oder um Informationen auszugeben. Hier sind das z.B. Infos zum Sender oder zum aktuell gespielten Titel, die dann in deinem Seriellen Monitor erscheinen.

erweiterung der Stromversorgung

Aktuell bezieht dein Internetradio bzw. dein ESP32 den Strom von deinem Computer oder vielleicht auch einer Powerbank. In diesem Abschnitt passen wir die Stromversorgung etwas an – entweder mit einem Akku oder zumindest mit dem dazugehörigen Lademodul. An diesem Modul wirst du später ein Poti mit integriertem Schalter anschließen, um damit deinen ESP32 und damit das Radio ein- und ausschalten zu können.

Einen Akku anschließen

Um dein ESP32 Internetradio unabhänging von Kabeln zur Stromversorgung zu machen, kannst du einen Akku samt Lademodul installieren. Ein wichtiger Hinweis jedoch vorab: Falls du keine Erfahrung mit Akkus für Arduino, ESP32 und Co. hast, bleibe bitte doch beim Kabel zu einer externen Stromquelle. Beachte auch auf jeden Fall die Sicherheitshinweise des Herstellers, da bei unsachgemäßem Gebrauch Feuer und sogar Explosionen drohen – für etwaige Schäden übernehme ich keine Gewähr und Haftung.

Falls du jedoch erfahren genug bist, orientiere dich beim Aufbau des ESP32 Internetradios an der folgenden Skizze:

ESP32 Internetradio mit Akku

Mit diesem Lademodul kannst du den Akku laden während das Radio läuft. Möchtest du den Akku laden ohne Musik zu hören, installiere noch einen Schalter zwischen dem Lademodul und dem ESP32 – dazu später mehr. Am Sketch ändert sich durch diese Erweiterung nichts.

Wie du oben siehst, ist die Stromversorgung auf 5 Volt umgezogen, da diese Spannung zu dem von mir verwendeten Lautsprecher passt. Je nachdem, welchen Widerstand (4 oder 8Ω) und welche Leistung dein Lautsprecher hat, verwende entweder 3,3V vom ESP32 oder (wie oben) 5V vom Lademodul. Hier eine Übersicht von Adafruit:

  • 5V into 4Ω – 3W max
  • 5V into 4Ω – 2.5W max
  • 3.3V into 4Ω – 1.3W max
  • 3.3V into 4Ω – 1.0W max
  • 5V into 8Ω – 1.8W max
  • 5V into 8Ω – 1.4W max
  • 3.3V into 8Ω – 0.8W max
  • 3.3V into 8Ω – 0.6W max

Nur das Lademodul verwenden

Du wirst dich vielleicht fragen, wozu es gut sein soll, nur das Lademodul für die Stromversorgung zu nutzen – du könntest das benötigte USB-Kabel ja auch weiterhin direkt im ESP32 unterbringen. Das stimmt – aber die abschließende Erweiterung soll ja ein Poti zum Ein- und Ausschalten sowie zur Lautstärkeregelung sein. Damit das am ESP32-S3 funktioniert benötigst du ein Verbindung, die du mit dem Schalter des Potis unterbrechen kannst – und das wird jene zwischen Lademodul und ESP32 sein.

Das in der obigen und in der folgenden Skizze verwendete Lademodul hat für Plus und Minus je zwei Anschlüsse. Einen (das mittlere Paar) für den Akku und einen (das äußere) für die dauerhafte Speisung eines Geräts, in unserem Fall also den ESP32.

Verbinde deinen ESP32 also wie folgt mit dem Lademodul:

ESP32 Internetradio nur Lademodul

Wenn du nun das Lademodul über USB mit Strom versorgst, erhält auch dein ESP32 hierüber Energie und das Internetradio springt an.

Fehlt noch der letzte Schritt – das Potentiometer.

Das Potentiometer installieren

Hierfür benötigst du kein „Standard“-Poti mit seinen drei Anschlüssen, sondern eines mit fünf Polen. Die äußersten zwei sind hierbei mit einem Schalter auf der Unterseite des Potis verbunden und können einen Stromkreis unterbrechen. In der Praxis drehst du das Poti nach rechts, bis ein Klicken des Schalters ertönt – nun fließt Strom (in unserem Fall springt der ESP32 an).

Drehst du nun weiter, übernimmt das Poti seine übliche Funktion und regelt den Widerstand, den du als Wert im ESP32 auslesen kannst. Orientiere dich beim Aufbau an der folgenden Skizze, wobei dort die oberen zwei Anschlüsse am Poti in der Realität oft die äußeren sind.

ESP32 Internetradio mit Poti

Wie du siehst, führt der Pluspol des Lademoduls zum Poti und von dort aus wieder zurück aufs Breadboard zu einem Kabel, das mit dem ESP32 verbunden ist. Sobald du das Poti anschaltest, fließt also Strom vom Lademodul zum ESP32.

Erweiterung des Sketchs

Nun ist das Poti zwar installiert und der Ein- und Ausschaltmechanismus funktioniert auch schon. Was jedoch deinem ESP32 Internetradio noch fehlt, ist die Lautstärkeregelung. Hierfür muss der bestehende Sketch um eine Funktion erweitert werden, die den aktuellen Wert des Potis ausliest und auf die Lautstärke des Radio-Streams „mappt“.

Das erreichst du mit dem folgenden Code:

void loop() {
  audio.loop();
  
  int volumeValue = analogRead(VOLUME_PIN);
  int volume = map(volumeValue, 0, 4095, 0, 21);
  audio.setVolume(volume);

Hier liest du das Poti aus, und verwendest die Funktion map(), um dessen Werte von 0 bis 4095 auf eine Lautstärke von 0 bis 21 zu „mappen“. Je weiter du also das Poti aufdrehst, desto lauter wird das Radio – bis ganz rechts der Wert 21 erreicht ist. Hier kannst du auch eine höhere Zahl eintragen, die zu deinem Lautsprecher passt – wie du oben gelesen hast, reicht die Spanne bis 100. Zuletzt übergibst du den gefunden Wert in der Variable volume an die Funktion audio.setVolume().

Das Problem an dieser Methode ist jedoch, dass die Funktion audio.loop(), also der Stream, immer wieder kurz unterbrochen wird. Dies führt zu unangenehmen Rucklern. Deshalb benötigen wir eine etwas ausgefeiltere Methode, um die Lautstärke (mehr oder weniger) unterbrechungsfrei zu gestalten.

Zum einen liest du nur noch alle 500ms den Werte des Potis aus und führst eine Anpassung in audio.setVolume() nur dann aus, wenn sich etwas geändert, du also am Poti gedreht hast. Außerdem glättest du die die Messwerte des Potis rechnerisch, was zu einer etwas sanfteren Anpassung führt.

Wenn du bereits das Poti installiert hast, lade den folgenden Sketch auf deinen ESP32:

//ESP32 Internetradio
//https://polluxlabs.net

#include "Arduino.h"
#include "WiFi.h"
#include "Audio.h"

#define I2S_DOUT 2
#define I2S_BCLK 3
#define I2S_LRC 4
#define VOLUME_PIN 5

Audio audio;

const char* ssid = "DEIN NETZWERK";
const char* password = "DEIN PASSWORT";

const int SAMPLES = 5;
int volumeReadings[SAMPLES];
int readIndex = 0;
int total = 0;
int average = 0;
unsigned long lastVolumeCheck = 0;
const unsigned long VOLUME_CHECK_INTERVAL = 500; // Check alle 500ms

void setup() {
  Serial.begin(115200);
  pinMode(VOLUME_PIN, INPUT);

  WiFi.disconnect();
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("\nWiFi connected");
  Serial.println("IP address: " + WiFi.localIP().toString());

  audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
  audio.setVolume(10);
  audio.connecttohost("http://www.byte.fm/stream/bytefm.m3u");

  // Erste Auswertung des Potis
  for (int i = 0; i < SAMPLES; i++) {
    volumeReadings[i] = 0;
  }
}

void loop() {
  audio.loop();
  
  unsigned long currentMillis = millis();
  if (currentMillis - lastVolumeCheck >= VOLUME_CHECK_INTERVAL) {
    lastVolumeCheck = currentMillis;
    
    // Letzten Wert entfernen
    total = total - volumeReadings[readIndex];
    // Werte des Potis auslesen
    volumeReadings[readIndex] = analogRead(VOLUME_PIN);
    // Wert hinzufügen
    total = total + volumeReadings[readIndex];
    // Zum nächsten Wert im Array wechseln
    readIndex = (readIndex + 1) % SAMPLES;

    // Durchschnitt berechnen
    average = total / SAMPLES;
    
    // Geglätteten Wert auf die Lautstärke mappen
    int volume = map(average, 0, 4095, 0, 31);
    
    // NUR wenn am Poti gedreht wurde, die Lautstärke anpassen
    static int lastVolume = -1;
    if (volume != lastVolume) {
      audio.setVolume(volume);
      lastVolume = volume;
      Serial.println("Volume set to: " + String(volume));
    }
  }
}

void audio_info(const char *info) {
  Serial.print("info        "); Serial.println(info);
}
void audio_id3data(const char *info) { //id3 metadata
  Serial.print("id3data     "); Serial.println(info);
}
void audio_eof_mp3(const char *info) { //end of file
  Serial.print("eof_mp3     "); Serial.println(info);
}
void audio_showstation(const char *info) {
  Serial.print("station     "); Serial.println(info);
}
void audio_showstreaminfo(const char *info) {
  Serial.print("streaminfo  "); Serial.println(info);
}
void audio_showstreamtitle(const char *info) {
  Serial.print("streamtitle "); Serial.println(info);
}
void audio_bitrate(const char *info) {
  Serial.print("bitrate     "); Serial.println(info);
}
void audio_commercial(const char *info) { //duration in sec
  Serial.print("commercial  "); Serial.println(info);
}
void audio_icyurl(const char *info) { //homepage
  Serial.print("icyurl      "); Serial.println(info);
}
void audio_lasthost(const char *info) { //stream URL played
  Serial.print("lasthost    "); Serial.println(info);
}
void audio_eof_speech(const char *info) {
  Serial.print("eof_speech  "); Serial.println(info);
}

Nun solltest du dein ESP32 Internetradio mit dem Poti einschalten und kurz darauf – unterbrechungsfrei – Byte.fm hören können.

Umzug auf eine Lochplatine und in das passende Gehäuse

Bis jetzt hast du die Komponenten deines ESP32 Internetradios auf einem Breadboard montiert. Wenn du es jedoch dauerhaft nutzen und es auch optisch etwas aufwerten möchtest, benötigst du ein Gehäuse – und vermutlich auch eine platzsparendere Methode für die Bauteile.

Als Gehäuse bietet sich eine alte Kassettenhülle an, in der du die Technik unterbringen kannst. Mit einer Säge oder Feile kannst du darin Löcher für das Potentiometer und das Ladekabel (falls du keinen Akku benutzt) bohren. Den Lautsprecher kannst du an der Außenseite oder innen anbringen – ganz wie es seine Maße dir ermöglichen.

So könnte anschließend dein ESP32 Internetradio aussehen:

Das Anbringen der Bauteile auf einer Lochplatine erfordert etwas Planung und durchaus fortgeschrittene Kenntnisse im Löten. Überlege dir vorher, wie du den ESP32, den Verstärker etc. anbringen möchtest, sodass du Platz hast für das Poti oder um auf die USB-Buchse zugreifen zu können. Auch die Platzierung des Lautsprechers spielt eine Rolle.

Eine gute Anleitung für das Löten auf einer Lochplatine findest du hier.

Und das war es mit dem „Einsender-Radio“. Aber was, wenn gerade Werbung kommt oder ein Song, den du nicht mehr hören kannst? Dann muss ein Radio mit Senderwahl her. Wie du dein aktuelles Radio damit erweiterst, erfährst du im folgenden Teil dieses Tutorials.

ESP32 Internetradio mit Senderwahl

Damit du an deinem Radio verschiedene Sender einstellen kannst, benötigst du eine geeignete Eingabemöglichkeit. Hierfür eignet sich ein Rotary Encoder* (oder auch Drehgeber). Dieser rastet, anders als ein stufenloses Poti, an festen Positionen ein und sendet einen entsprechenden Impuls an deinen ESP32. Mit jeder Drehung wechselst du dann zum nächsten Radiosender.

Auf deinem Breadboard ist hierfür nur ein kleiner Umbau nötig:

ESP32 Internetradio mit Senderwahl

Wie du siehst, wird der Rotary Encoder (rechts unten) auch vom Lademodul mit Strom versorgt. Die beiden Pins CLK und DT schließt du an die Pins 12 und 13 an.

Die bibliothek für den Rotary Encoder

Damit du die Signale des Rotary Encoders unkompliziert verarbeiten kannst, eignet sich die Bibliothek AiEsp32RotaryEncoder, die du einfach über den Bibliotheksmanager deiner Arduino IDE installieren kannst:

AiEsp32RotaryEncoder in der IDE installieren

Finde deine Lieblingssender

Als nächstes benötigst du eine Liste von Sendern, die dein ESP32 Internetradio abspielen können soll. Hierfür benötigst du die entsprechenden URLs der Streams. Die URL des Senders Byte.fm hast du ja bereits oben kennengelernt. Zusätzliche oder andere Adressen findest du oft ganz einfach über eine Suchmaschine. Suche hierfür zum Beispiel nach dem [Sendername] + Stream URL.

Ich habe in meinem Radio die folgenden vier Sender hinterlegt (als Array, das du gleich im vollständigen Sketch wiedersehen wirst):

const char* stations[] = {
    "http://www.byte.fm/stream/320.m3u", //Byte.fm
    "https://st01.sslstream.dlf.de/dlf/01/128/mp3/stream.mp3", //Deutschlandfunk
    "https://frontend.streamonkey.net/fho-schwarzwaldradiolive/mp3-stream.m3u", //Schwarzwaldradio
    "https://kexp-mp3-128.streamguys1.com/kexp128.mp3" //KEXP
};

Der vollständige Sketch des ESP32 Internetradios mit Senderwahl

Und hier nun der gesamte Sketch zum Herauskopieren und Anpassen:

//ESP32 Internetradio
//https://polluxlabs.net

#include "Arduino.h"
#include "WiFi.h"
#include "Audio.h"
#include "AiEsp32RotaryEncoder.h"

#define I2S_DOUT 2
#define I2S_BCLK 3
#define I2S_LRC 4
#define VOLUME_PIN 5

#define ROTARY_ENCODER_A_PIN 12
#define ROTARY_ENCODER_B_PIN 13
#define ROTARY_ENCODER_BUTTON_PIN 14
#define ROTARY_ENCODER_STEPS 4

AiEsp32RotaryEncoder rotaryEncoder = AiEsp32RotaryEncoder(ROTARY_ENCODER_A_PIN, ROTARY_ENCODER_B_PIN, ROTARY_ENCODER_BUTTON_PIN, -1, ROTARY_ENCODER_STEPS);

Audio audio;

// WiFi credentials
const char* ssid = "DEIN NETZWERK";
const char* password = "DEIN PASSWORT";

// Radio stations
const char* stations[] = {
    "http://www.byte.fm/stream/bytefm.m3u",
    "https://st01.sslstream.dlf.de/dlf/01/128/mp3/stream.mp3",
    "https://frontend.streamonkey.net/fho-schwarzwaldradiolive/mp3-stream.m3u",
    "https://kexp-mp3-128.streamguys1.com/kexp128.mp3"
};
const int NUM_STATIONS = sizeof(stations) / sizeof(stations[0]);
int currentStation = 0;

// Volume control variables
const int SAMPLES = 5;
int volumeReadings[SAMPLES];
int readIndex = 0;
int total = 0;
int average = 0;
unsigned long lastVolumeCheck = 0;
const unsigned long VOLUME_CHECK_INTERVAL = 500; // Check every 500ms

void IRAM_ATTR readEncoderISR() {
    rotaryEncoder.readEncoder_ISR();
}

void setup() {
    Serial.begin(115200);
    pinMode(VOLUME_PIN, INPUT);

    // Rotary Encoder setup
    rotaryEncoder.begin();
    rotaryEncoder.setup(readEncoderISR);
    rotaryEncoder.setBoundaries(0, NUM_STATIONS - 1, true); // circular behavior
    rotaryEncoder.setAcceleration(0); // no acceleration

    WiFi.disconnect();
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);

    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }

    Serial.println("\nWiFi connected");
    Serial.println("IP address: " + WiFi.localIP().toString());

    audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
    audio.setVolume(10);
    connectToStation(currentStation);

    // Initialize volume readings
    for (int i = 0; i < SAMPLES; i++) {
        volumeReadings[i] = 0;
    }
}

void loop() {
    audio.loop();
    checkVolumeControl();
    checkStationChange();
}

void checkVolumeControl() {
    unsigned long currentMillis = millis();
    if (currentMillis - lastVolumeCheck >= VOLUME_CHECK_INTERVAL) {
        lastVolumeCheck = currentMillis;
        
        total = total - volumeReadings[readIndex];
        volumeReadings[readIndex] = analogRead(VOLUME_PIN);
        total = total + volumeReadings[readIndex];
        readIndex = (readIndex + 1) % SAMPLES;

        average = total / SAMPLES;
        int volume = map(average, 0, 4095, 5, 23);
        
        static int lastVolume = -1;
        if (volume != lastVolume) {
            audio.setVolume(volume);
            lastVolume = volume;
            Serial.println("Volume set to: " + String(volume));
        }
    }
}

void checkStationChange() {
    if (rotaryEncoder.encoderChanged()) {
        int newStation = rotaryEncoder.readEncoder();
        if (newStation != currentStation) {
            currentStation = newStation;
            Serial.println("Changing to station: " + String(currentStation));
            connectToStation(currentStation);
        }
    }
    
    if (rotaryEncoder.isEncoderButtonClicked()) {
        Serial.println("Encoder button clicked");
        // You can add functionality here for when the encoder button is pressed
    }
}

void connectToStation(int stationIndex) {
    audio.stopSong();
    audio.connecttohost(stations[stationIndex]);
    Serial.println("Connected to: " + String(stations[stationIndex]));
}

// Audio status functions
void audio_info(const char *info) {
    Serial.print("info        "); Serial.println(info);
}
void audio_id3data(const char *info) {
    Serial.print("id3data     "); Serial.println(info);
}
void audio_eof_mp3(const char *info) {
    Serial.print("eof_mp3     "); Serial.println(info);
}
void audio_showstation(const char *info) {
    Serial.print("station     "); Serial.println(info);
}
void audio_showstreaminfo(const char *info) {
    Serial.print("streaminfo  "); Serial.println(info);
}
void audio_showstreamtitle(const char *info) {
    Serial.print("streamtitle "); Serial.println(info);
}
void audio_bitrate(const char *info) {
    Serial.print("bitrate     "); Serial.println(info);
}
void audio_commercial(const char *info) {
    Serial.print("commercial  "); Serial.println(info);
}
void audio_icyurl(const char *info) {
    Serial.print("icyurl      "); Serial.println(info);
}
void audio_lasthost(const char *info) {
    Serial.print("lasthost    "); Serial.println(info);
}
void audio_eof_speech(const char *info) {
    Serial.print("eof_speech  "); Serial.println(info);
}

Passe vor dem Upload zunächst wieder die Zugangsdaten für dein WLAN an und trage deine Lieblingssender ein. Wenn du den Sketch auf deinen ESP32 hochgeladen und gestartet hast, solltest du mit dem Rotary Encoder die einzelnen Sender auswählen können. Bei meinen Tests war es beim ersten Umschalten nötig, zweimal zu drehen. Anschließend reichte eine Drehung in die nächste Stellung.

Noch ein Hinweis: Im Sketch oben, ist auch der Button des Rotary Encoders (auf dem Modul oft SW benannt) an Pin 14 hinterlegt. Falls du deinen Drehgeber auch drücken kannst, hast du die Möglichkeit, darüber eine weitere Funktion deiner Wahl in deinem ESP32 Internetradio zu implementieren.

Auf deinem Breadboard könnte das Radio dann so aussehen:

ESP32 Internetradio mit Rotary Encoder auf dem Breadboard

Wie geht es weiter? Zwar erkennst du deine Radiosender vielleicht an der Musik, die dort läuft – ein Display wäre aber sicherlich hilfreich. Darüber könntest du nicht nur den Sendernamen, sondern auch den Song anzeigen, der gerade gespielt wird. Hierfür hilfreiche Funktionen findest du bereits im Sketch oben: void audio_showstreaminfo() zeigt dir den Sendernamen und void audio_showstreamtitle() den aktuellen Song oder den Namen der aktuellen Sendung.

Das Radio um ein OLED-Display erweitern

Nun also zum letzten Baustein deines ESP32 Internetradios – ein kleines Display, auf dem du den abgespielten Sender sowie den aktuellen Musiktitel ablesen kannst. Hierfür eignet sich ein OLED-Display mit 128×32 Pixeln*. Schließe dieses wie folgt an:

ESP32 Internetradio mit Display

Da sich auf deinem Breadboard (und auf der Skizze oben) mittlerweile eine Menge Kabel tummeln, hier noch mal die Anschlüsse im Detail:

OLED-DisplayESP32-S3 Zero
VCC (3,3V)3,3V
GNDGND
SDA8
SCK/SCL9

Hardwareseitig war es das schon – kommen wir zum Sketch. Hier musst du vor dem Upload allerdings noch eine Kleinigkeit in der Arduino IDE einstellen. Und zwar würde der folgende Sketch den standardmäßig vorgesehenen Speicherplatz auf deinem ESP32 sprengen, wenn du nicht vorab etwas mehr freigeben würdest. Das geht glücklicherweise ganz einfach:

Mehr Speicherplatz auf dem ESP32 bereitstellen

Klicke im Menü Werkzeuge (Tools) auf den Menüpunkt Partition Scheme und wähle die Einstellung Huge APP. Damit hast du nun statt den üblichen 1,2 MB ganze 3 MB zur Verfügung.

Partition Scheme ändern in Huge APP

Nun, wo du genug Speicherplatz auf dem ESP32 hast, kann es mit dem Upload des Sketchs weitergehen. Ein Hinweis noch vorab: Dein ESP32 hat mit all der angeschlossenen Peripherie ganz schön zu tun, weswegen es passieren kann, dass er nicht zuverlässig bootet. Um das zu verhindern, werden im folgenden Sketch das Display, die Verbindung zum WLAN und der Verstärker schrittweise initialisiert. Die Setup-Funktion hingegen ist nun aufgeräumter.

Damit dein Display den richtigen Sendernamen zum jeweiligen Stream anzeigen kann, benötigst du ein Array mit den Namen. Trage dort die Sendernamen in der gleichen Reihenfolge wie im Stream-Array ein:

const char* stationNames[] = {
    "Byte.fm",
    "Deutschlandfunk",
    "Schwarzwaldradio",
    "KEXP",
    "Psychedelic Jukebox"
};

Hier nun der vollständige Sketch:

//ESP32 Internetradio
//https://polluxlabs.net

#include <Arduino.h>
#include <WiFi.h>
#include <Audio.h>
#include <AiEsp32RotaryEncoder.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define I2S_DOUT 2
#define I2S_BCLK 3
#define I2S_LRC 4
#define VOLUME_PIN 5

#define ROTARY_ENCODER_A_PIN 12
#define ROTARY_ENCODER_B_PIN 13
#define ROTARY_ENCODER_BUTTON_PIN 14
#define ROTARY_ENCODER_STEPS 4

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 32
#define OLED_RESET     -1
#define SCREEN_ADDRESS 0x3C

#define I2C_SDA 8
#define I2C_SCL 9

AiEsp32RotaryEncoder rotaryEncoder(ROTARY_ENCODER_A_PIN, ROTARY_ENCODER_B_PIN, ROTARY_ENCODER_BUTTON_PIN, -1, ROTARY_ENCODER_STEPS);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

Audio audio;

// WiFi credentials
const char* ssid = "DEIN NETZWERK";
const char* password = "DEIN PASSWORT";

// Radio stations
const char* stations[] = {
    "http://www.byte.fm/stream/bytefm.m3u",
    "https://st01.sslstream.dlf.de/dlf/01/128/mp3/stream.mp3",
    "https://frontend.streamonkey.net/fho-schwarzwaldradiolive/mp3-stream.m3u",
    "https://kexp-mp3-128.streamguys1.com/kexp128.mp3",
    "https://eagle.streemlion.com:2199/tunein/psychedelicj.asx"
};
const char* stationNames[] = {
    "Byte.fm",
    "Deutschlandfunk",
    "Schwarzwaldradio",
    "KEXP",
    "Psychedelic Jukebox"
};

const int NUM_STATIONS = sizeof(stations) / sizeof(stations[0]);
int currentStation = 0;

char streamTitle[64] = "";  // Buffer to store the current stream title

// Volume control variables
const int SAMPLES = 5;
int volumeReadings[SAMPLES];
int readIndex = 0;
int total = 0;
int average = 0;
unsigned long lastVolumeCheck = 0;
const unsigned long VOLUME_CHECK_INTERVAL = 500; // Check every 500ms

// Flags für verzögerte Initialisierung
bool isWiFiConnected = false;
bool isDisplayInitialized = false;
bool isAudioInitialized = false;

void IRAM_ATTR readEncoderISR() {
    rotaryEncoder.readEncoder_ISR();
}

String replaceSpecialChars(String input) {
    input.replace("ä", "ae");
    input.replace("ö", "oe");
    input.replace("ü", "ue");
    input.replace("Ä", "AE");
    input.replace("Ö", "OE");
    input.replace("Ü", "UE");
    input.replace("ß", "ss");
    return input;
}

void setup() {
    delay(1000);  // Kurze Pause für stabilen Start
    Serial.begin(115200);
    while(!Serial) { delay(10); }  // Warte auf Serial-Verbindung
    Serial.println(F("ESP32-S3 Internet Radio startet..."));
    
    pinMode(VOLUME_PIN, INPUT);

    rotaryEncoder.begin();
    rotaryEncoder.setup(readEncoderISR);
    rotaryEncoder.setBoundaries(0, NUM_STATIONS - 1, true);
    rotaryEncoder.setAcceleration(0);

    Serial.println(F("Initialisiere I2C..."));
    Wire.begin(I2C_SDA, I2C_SCL);

    // Initialisiere Volumen-Readings
    for (int i = 0; i < SAMPLES; i++) {
        volumeReadings[i] = 0;
    }
}

void loop() {
    static unsigned long lastInitAttempt = 0;
    const unsigned long initInterval = 5000;  // 5 Sekunden zwischen Initialisierungsversuchen

    // Verzögerte Initialisierung
    if (!isDisplayInitialized && millis() - lastInitAttempt > initInterval) {
        initializeDisplay();
        lastInitAttempt = millis();
    }

    if (!isWiFiConnected && millis() - lastInitAttempt > initInterval) {
        connectToWiFi();
        lastInitAttempt = millis();
    }

    if (isWiFiConnected && !isAudioInitialized && millis() - lastInitAttempt > initInterval) {
        initializeAudio();
        lastInitAttempt = millis();
    }

    // Normale Loop-Funktionalität
    if (isDisplayInitialized && isWiFiConnected && isAudioInitialized) {
        audio.loop();
        checkEncoder();
        checkVolumeControl();
    }

    yield();  // Watchdog füttern
    delay(10);  // Kurze Pause für Stabilität
}

void initializeDisplay() {
    Serial.println(F("Initialisiere OLED Display..."));
    if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
        Serial.println(F("SSD1306 Initialisierung fehlgeschlagen"));
        return;
    }
    display.clearDisplay();
    display.setTextSize(1);
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(0,0);
    display.println(F("Initialisierung..."));
    display.display();
    isDisplayInitialized = true;
    Serial.println(F("OLED Display initialisiert"));
}

void connectToWiFi() {
    Serial.println(F("Verbinde mit WiFi..."));
    WiFi.begin(ssid, password);
    int attempts = 0;
    while (WiFi.status() != WL_CONNECTED && attempts < 20) {
        delay(500);
        Serial.print(".");
        attempts++;
    }
    if (WiFi.status() == WL_CONNECTED) {
        Serial.println(F("\nWiFi verbunden"));
        isWiFiConnected = true;
        if (isDisplayInitialized) {
            display.clearDisplay();
            display.setCursor(0,0);
            display.println(F("WiFi verbunden"));
            display.display();
        }
    } else {
        Serial.println(F("\nWiFi-Verbindung fehlgeschlagen"));
    }
}

void initializeAudio() {
    audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
    audio.setVolume(10);
    connectToStation(currentStation);
    isAudioInitialized = true;
    Serial.println(F("Audio initialisiert"));
}

void checkEncoder() {
    if (rotaryEncoder.encoderChanged()) {
        currentStation = rotaryEncoder.readEncoder();
        connectToStation(currentStation);
    }
    
    if (rotaryEncoder.isEncoderButtonClicked()) {
        Serial.println(F("Encoder-Taste gedrückt"));
        // Hier könnte eine Aktion für den Tastendruck implementiert werden
    }
}

void connectToStation(int stationIndex) {
    audio.stopSong();
    audio.connecttohost(stations[stationIndex]);
    updateDisplay();
}

void checkVolumeControl() {
    unsigned long currentMillis = millis();
    if (currentMillis - lastVolumeCheck >= VOLUME_CHECK_INTERVAL) {
        lastVolumeCheck = currentMillis;
        
        total = total - volumeReadings[readIndex];
        volumeReadings[readIndex] = analogRead(VOLUME_PIN);
        total = total + volumeReadings[readIndex];
        readIndex = (readIndex + 1) % SAMPLES;

        average = total / SAMPLES;
        int volume = map(average, 0, 4095, 5, 23);
        
        static int lastVolume = -1;
        if (volume != lastVolume) {
            audio.setVolume(volume);
            lastVolume = volume;
            Serial.println("Lautstärke eingestellt auf: " + String(volume));
            updateDisplay();
        }
    }
}

void updateDisplay() {
    if (!isDisplayInitialized) return;
    display.clearDisplay();
    display.setCursor(0,0);
    display.println(replaceSpecialChars(String(stationNames[currentStation])));
    display.println();
    display.println(replaceSpecialChars(String(streamTitle)));
    display.display();
    Serial.println(F("Display aktualisiert"));
}

// Audio callback functions
void audio_info(const char *info) { 
    Serial.print("info        "); Serial.println(info);
}
void audio_id3data(const char *info) {
    Serial.print("id3data     "); Serial.println(info);
}
void audio_eof_mp3(const char *info) {
    Serial.print("eof_mp3     "); Serial.println(info);
}
void audio_showstation(const char *info) {
    Serial.print("station     "); Serial.println(info);
}
void audio_showstreaminfo(const char *info) {
    Serial.print("streaminfo  "); Serial.println(info);
}
void audio_showstreamtitle(const char *info) {
    Serial.print("streamtitle: "); Serial.println(info);
    strncpy(streamTitle, info, sizeof(streamTitle) - 1);
    streamTitle[sizeof(streamTitle) - 1] = '\0'; // Ensure null-termination
    updateDisplay();
}
void audio_bitrate(const char *info) {
    Serial.print("bitrate     "); Serial.println(info);
}
void audio_commercial(const char *info) {
    Serial.print("commercial  "); Serial.println(info);
}
void audio_icyurl(const char *info) {
    Serial.print("icyurl      "); Serial.println(info);
}
void audio_lasthost(const char *info) {
    Serial.print("lasthost    "); Serial.println(info);
}
void audio_eof_speech(const char *info) {
    Serial.print("eof_speech  "); Serial.println(info);
}

Nach dem Upload sollte dein ESP32 Internetradio starten (was nun etwas länger dauert) und den ersten Sender in deiner Liste spielen. Auf dem Display erscheint der von dir hinterlegte Sendername und – sofern verfügbar – der Name des Songs oder der Sendung, der oder die gerade läuft.

Und das war es an dieser Stelle. Viel Spaß beim Tüfteln! 🙂

]]>
Deine persönlichen Radio-Nachrichten auf dem Raspberry Pi https://polluxlabs.net/raspberry-pi-projekte/deine-persoenlichen-radio-nachrichten-auf-dem-raspberry-pi/ Fri, 15 Mar 2024 09:12:31 +0000 https://polluxlabs.net/?p=16173 Aktuelle Nachrichten selbst zu lesen ist nicht immer möglich – manchmal sind sie vorgelesen gerade praktischer. Nur informiert dich das Radio üblicherweise nur zur vollen Stunde. Dieses Raspberry Pi Projekt schafft Abhilfe: Du besorgst dir die aktuellen Nachrichten von tagesschau.de, fasst sie mit ChatGPT radiotauglich zusammen und lässt sie dir vorlesen.

Mit diesem Projekt lernst du, wie du in Python mit Web Scraping Inhalte von Webseiten herunterlädst, wie du ChatGPT per API verwendest und Texte vertonen lassen kannst. Außerdem erfährst du, wie du per Python-Script MP3s erzeugst und diese auf deinem Raspberry Pi abspielst.

Aufbau des Raspberry Pis

Deine eigene Nachrichtensendung soll auf Knopfdruck starten – hierfür benötigst du einen Button, den du an die Pins des Raspberry Pis anschließt. Orientiere dich hierbei an folgender Skizze:

Button am Raspberry Pi

Einen Pullup- bzw. Pulldown-Widerstand benötigst du hier nicht, das erledigt dein Raspberry Pi intern. Neben dem Button benötigst du noch einen Lautsprecher, über den du die vertonten Nachrichten ausgeben kannst. Hierfür eignen sich z.B. Modelle, die du per 3,5mm Klinkenstecker direkt an den Audioausgang anschließen kannst.

Der API-Key von OpenAI

Falls du noch keinen Account bei OpenAI und auch noch keinen API-Key hast, musst du beides noch schnell einrichten, bevor du ChatGPT und die Funktion zum Vertonen von Texten nutzen kannst. In diesem Tutorial erfährst du, wie.

Im folgenden Python-Script kannst du dann deinen API-Key eintragen, um die Features von OpenAI verwenden zu können.

Das Python-Script

Kommen wir zum Code des Projekts. Hier führst du mehrere Funktionen aus – um die aktuellen Nachrichten von tagesschau.de zu laden, um sie zu kürzen und „radiotauglich“ zu machen, und um sie vorlesen zu lassen.

Hier zunächst das vollständige Script:

import requests
from bs4 import BeautifulSoup
from openai import OpenAI
from pathlib import Path
import pygame
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BOARD)
buttonPin = 16
GPIO.setup(buttonPin, GPIO.IN, pull_up_down=GPIO.PUD_UP)

def main():
    client = OpenAI(
      api_key="DEIN API-KEY",
    )

    url = 'https://www.tagesschau.de/'

    def getNews():
        nonlocal url
        response = requests.get(url)
        soup = BeautifulSoup(response.text, 'html.parser')
        links = soup.find_all('a', class_='teaser__link')
        for link in links:
            link['href'] = url + link['href']
        return links[:5]

    def getArticle(link):
        response = requests.get(link.get('href'))
        soup = BeautifulSoup(response.text, 'html.parser')
        headline = soup.find('meta', property='og:title')['content'] + '\n'
        paragraphs = soup.find_all('p')
        text = ""
        for paragraph in paragraphs:
            if paragraph.find_parent('div', class_='teaser-absatz__teaserinfo') is None and paragraph.find_parent('div', class_='teaser-xs__teaserinfo') is None:
                text += paragraph.get_text() + '\n'
        return headline, text

    def get_summary(text):
        completion = client.chat.completions.create(
        model="gpt-3.5-turbo", 
        messages=[
        {"role": "system", "content": "Du schreibst Nachrichten fürs Radio."},
        {"role": "user", "content": "Fasse den folgenden Text zusammen: {}".format(text)}]
        )
        summary_text = headline + completion.choices[0].message.content
        return summary_text

    def speech(summary_text):
        speech_file_path = Path(__file__).parent / "news.mp3"
        response = client.audio.speech.create(
        model="tts-1",
        voice="alloy",
        input=summary_text
        )
        response.stream_to_file(speech_file_path)
        pygame.init()
        pygame.mixer.init()
        pygame.mixer.music.load(speech_file_path)
        pygame.mixer.music.play()
        while pygame.mixer.music.get_busy():
            pass
        pygame.quit()

    news_links = getNews()
    for link in news_links:
        headline, text = getArticle(link)
        summary_text = get_summary(text)
        speech(summary_text)

if __name__ == "__main__":
    while True:
        buttonState = GPIO.input(buttonPin)
        if buttonState == GPIO.LOW: 
            main()
        else:
            print(".")

So funktioniert das Script

Sobald du den Button gedrückt hast, wird die Funktion main() aufgerufen. Den Code hierfür findest du ganz am Ende des Scripts. Innerhalb von main() werden mehrere verschachtelte Funktionen definiert, die jeweils für einen bestimmten Teil des Prozesses verantwortlich sind.

Die Funktion getNews() sendet eine GET-Anfrage an eine vordefinierte URL (in unserem Fall ‚https://www.tagesschau.de/‘), analysiert die HTML-Antwort, um alle Links mit der Klasse teaser__link zu finden, und gibt die ersten fünf dieser Links zurück.

Die Funktion getArticle(link) nimmt einen dieser Links als Argument, sendet eine GET-Anfrage an die URL des Links und analysiert ebenso die HTML-Antwort, um die Überschrift und den Text des Artikels zu extrahieren. Dazu sucht sie den meta-Tag mit der Eigenschaft og:title für die Überschrift und alle p-Tags für den Text. Anschließend werden die Überschrift und der Text zurückgegeben.

Die Funktion get_summary(text) nimmt den Text als Argument und verwendet ChatGPT, um eine Zusammenfassung des Textes zu erstellen. Anschließend werden die Überschrift des Artikels und die generierte Zusammenfassung miteinander verknüpft und zurückgegeben.

Die Funktion speech(summary_text) nimmt den zusammengefassten Text als Argument und verwendet die OpenAI-API, um den Text in Sprache umzuwandeln. Anschließend wird der vertonte Text in einer Datei gespeichert und mit pygame abgespielt.

Die benötigten Module

Du benötigst du folgenden Module bzw. Bibliotheken, damit das Script läuft:

import requests
from bs4 import BeautifulSoup
from openai import OpenAI
from pathlib import Path
import pygame
import RPi.GPIO as GPIO

Falls du BeautifulSoup, OpenAI und PyGame noch nicht auf deinem System installiert sind, hole das mit den folgenden Befehlen im Terminal nach:

pip install beautifulsoup4
pip install openai
pip install pygame

Die Bibliothek Beautiful Soup verwendest du fürs Web Scraping. Das bedeutet, dass du hiermit die Texte einer beliebigen Webseite auslesen und in deinem Script weiterverwenden kannst. Mit dem Modul von OpenAI greifst du auf deren Services zu und PyGame verwendest du für die Tonausgabe. Die anderen Module sollten bereits verfügbar sein.

Nachdem du eine Button an deinem Raspberry Pi angebracht und die benötigten Module installiert hast, trage deinen API-Key ein und starte das Python-Script. Nach wenigen Sekunden sollte deine persönlichen Radio-Nachrichten starten.

Möchtest du doch lieber „echtes“ Radio hören? In diesem Tutorial lernst du, wie du dir ein ESP32 Internetradio baust.

]]>
Text To Speech mit Python – So vertonst du Texte https://polluxlabs.net/raspberry-pi-projekte/text-to-speech-mit-python-so-vertonst-du-texte/ Mon, 13 Nov 2023 12:54:54 +0000 https://polluxlabs.net/?p=15278 In diesem Tutorial lernst du zwei Methoden kennen, mit denen du in Python Text in gesprochene Sprache umwandeln kannst. Mit Text to Speech kannst in deinen Projekten zum Beispiel eine Sprachausgabe umsetzen.

Die erste Möglichkeit ist die Python-Bibliothek gTTS, mit der du kostenlos Texte vertonen lassen kannst. Dieses Modul habe ich im Projekt ChatGPT im Telefon eingesetzt. Als zweite Methode lernst du eine API-Funktion von openAI kennen. Diese Variante ist kostenpflichtig – allerdings hört sich das Ergebnis hierfür auch weit besser an.

Text to Speech mit gTTS (Google Text-to-Speech)

Wenn dein Projekt keine astreine Aussprache erfordert, ist die Bibliothek gTTS eine gute Wahl. Die Qualität ist nicht schlecht, allerdings hakt es bei der Aussprache oft bei Abkürzungen oder die Betonung von Satzteilen kommt durch das ein oder andere Komma durcheinander. Dafür kannst du mit diesem Python-Modul den Google-Service kostenlos verwenden – was sicherlich ein gutes Argument für einen Test ist.

Installiere zunächst die Bibliothek mit dem Befehl

pip install gtts

Um die Sprachausgabe zu testen, reichen drei Zeilen Python-Code:

from gtts import gTTS

tts = gTTS('Hello, world. This is a first test.')
tts.save('hello.mp3')

Nachdem du das kleine Script ausgeführt hast, öffne die Datei hello.mp3 und lausche dem Ergebnis. Bist du zufrieden?

Du kannst übrigens auch deutsche Texte vertonen lassen. Füge hierfür den Parameter lang=’de‘ hinzu:

tts = gTTS('Hallo, das ist einer erster Test.', lang='de')

Das war im Prinzip schon alles. Wenn du wissen möchtest, wie du die MP3 direkt mit deinem Python-Script abspielen kannst, wirf einen Blick in das oben verlinkte Projekt. Weitere Infos über gTTS erhältst du hier.

Text To Speech mit openAI

Wenn dir eine gute Sprachqualität ein paar Cent wert ist, ist OpenAI einen Versuch wert. Neben dem allseits bekannten ChatGPT findest du dort auch eine API-Funktion, mit der du Text vertonen lassen kannst. Die Integration in dein Python-Script ist dabei ähnlich einfach wie mit gTTS. Allerdings kostet der Service derzeit (November 2023) 0,015 € je 1.000 Zeichen – was ein recht überschaubarer Preis ist. Die aktuelle Preisliste findest du hier unter Audio models.

Wenn du noch nicht mit OpenAI experimentiert hast, erfährst du in diesem Tutorial, wie du dort ein Konto und einen API-Key erstellst.

Nachdem du die Bibliothek openai (mit pip install openai) installiert hast, binde sie in deinem Python-Script ein. Zusätzlich benötigst du noch das Modul pathlib der Bibliothek Path, das aber bereits vorinstalliert ist.

from pathlib import Path
from openai import OpenAI

Anschließend hinterlegst du deine API-Key von OpenAI:

client = OpenAI(
  api_key="DEIN API-KEY",
)

Und schon kann es mit der Vertonung losgehen. Du gibst zunächst an, wie die erzeugte MP3 heißen soll – hier speech.mp3:

speech_file_path = Path(__file__).parent / "speech.mp3"

Anschließend legst du ein paar Parameter fest: Zunächst das Modell – hier hast du die Wahl zwischen tts-1 und tts-1-hd. Letzteres hat eine etwas höhere Qualität und ist auch teurer. Für die allermeisten Anwendungen dürfte das einfachere Modell jedoch ausreichen. Der Parameter voice gibt vor, welche Stimme verwendet werden soll. Derzeit gibt es sechs Stimmen, die du hier probehören kannst. Dort findest du auch aktuelle Informationen und Updates zu Text to Speech mit OpenAI.

Zuletzt fehlt nur noch der Text, den du vertonen lassen möchtest und der Befehl zum Speichern der MP3:

response = client.audio.speech.create(
  model="tts-1",
  voice="alloy",
  input="Das Pferd frisst keinen Gurkensalat."
)

response.stream_to_file(speech_file_path)

Und das war schon alles. Führe das Script aus – sobald es fertig ist, findest du im gleichen Ordner die Datei speech.mp3 mit deiner Sprachausgabe. Hier nun das gesamte Script:

from pathlib import Path
from openai import OpenAI

client = OpenAI(
  api_key="DEIN API-KEY",
)

speech_file_path = Path(__file__).parent / "speech.mp3"
response = client.audio.speech.create(
  model="tts-1",
  voice="alloy",
  input="Das Pferd frisst keinen Gurkensalat."
)

response.stream_to_file(speech_file_path)

Mit den oben beschriebenen Bibliotheken und Services hast du nun zwei Methoden zur Hand, wie du in deinem Projekt Text to Speech anwenden kannst. Ob dir eine kostenlose Vertonung reicht, oder du etwas qualitativ hochwertigeres benötigst, hängt natürlich vom Einsatzgebiet ab.

]]>
Objekterkennung mit Künstlicher Intelligenz und dem Raspberry Pi https://polluxlabs.net/raspberry-pi-projekte/objekterkennung-mit-kuenstlicher-intelligenz-und-dem-raspberry-pi/ Fri, 06 Oct 2023 09:16:16 +0000 https://polluxlabs.net/?p=14619 Objekte automatisch zu erkennen, kann in vielen Projekten zum Einsatz kommen – Hindernisse meidende Roboter, eine Kamera am 3D-Drucker oder auch eine Tür, die sich nur für bekannte Gesichter öffnet. Letzteres offenbart, dass Menschen für eine künstliche Intelligenz auch nur Objekte sind…

In diesem Tutorial erfährst du, wie du mit einem Raspberry Pi samt passender Kamera* und einem selbst entwickelten KI-Modell Gegenstände erkennen kannst. Für das Modell kommt der kostenlose Google Service Teachable Machine zum Einsatz. Den Code wirst du in Python schreiben.

Hinweis: Aktuell funktioniert dieses Tutorial nur für Raspberry Pi OS Bullseye. Eine Anpassung an die neuester Version Bookworm folgt.

Inhalt

Trainiere das KI-Modell mit Teachable Machine

Damit eine künstliche Intelligenz ein Objekt erkennen – bzw. Objekte voneinander unterscheiden kann – muss sie diese natürlich erst einmal kennenlernen. Du könntest hierfür natürlich zahlreiche Fotos erstellen und ein entsprechendes neuronales Netz manuell trainieren. Deutlich komfortabler geht es mit Services wie Edge Impulse oder Teachable Machine.

Hierfür nimmst du mit einer Webcam in wenigen Sekunden eine große Zahl von Fotos deiner Objekte auf. Mit einem Klick trainierst du anschließend das KI-Modell, das du daraufhin herunterladen kannst – z.B. als Tensorflow. Hierbei wird das neuronale Netz zwar in der Cloud trainiert – die Fotos werden laut Google allerdings nicht hochgeladen und dort gespeichert.

KI-Modell mit Teachable Machine trainieren

Du findest auf Pollux Labs ein ausführliches Tutorial zu Teachable Machine. Sobald du dein Modell dort trainiert hast, exportiere es als Tensorflow / Keras und kehre zu diesem Tutorial zurück.

Teachable Machine KI-Modell exportieren

Eine Kamera am Raspberry Pi anschließen

Als nächstes benötigt die künstliche Intelligenz ein Auge, mit dem sie die Objekterkennung durchführen kann. Hierfür eignet sich die „offizielle“ Raspberry Pi Kamera*. Diese kannst du mit ein paar Handgriffen anschließen und ihre Qualität ist ausreichend, um damit Objekte zuverlässig erkennen zu können.

Ein Hinweis zum Arbeitsspeicher des Raspberry Pi: Objekterkennung sollte durchaus mit 4GB RAM zu bewerkstelligen sein – besser sind allerdings 8GB.

Am Raspberry Pi findest du einen Steckplatz für deine Kamera. Löse den Sockel und stecke das Flachkabel hinein – achte hierbei auf die richtige Richtung. Drücke anschließend den Sockel herunter, um das Kabel zu fixieren.

Kamera am Raspberry Pi anschließen

Aktiviere die Kamera

Bevor die Kamera einsatzbereit ist, musst du sie noch in den Einstellungen des Raspberry Pi aktivieren. Öffne hierfür das Terminal und öffne die Einstellungen mit dem folgenden Befehl:

sudo raspi-config

Wähle nun den Menüpunkt Interface Options. Dahinter findest du dahinter den Menüpunkt Legacy Camera.

Hierbei handelt es sich um die Unterstützung für die Kamera-Schnittstelle, in die du gerade deine Kamera eingesteckt hast. Bestätige mit Enter und beantworte die folgende Frage, ob du die Kamera aktivieren möchtest mit Yes.

Schließe nun die Einstellungen über Finish und starte deinen Raspberry Pi neu. Nun kannst du mit dem folgenden Python Script auf die Kamera zugreifen.

Update: Das Thema Kamera am Raspberry Pi hat eine bewegte Geschichte. Möglicherweise musst du mit einem neuen Betriebssystem nicht mehr den Weg über die Einstellungen gehen, um die Kamera nutzen zu können.

Objekte erkennen mit künstlicher Intelligenz

Kommen wir zum Kernstück – dem Python Script, mit dem du mithilfe deines KI-Modells und der Kamera die Objekterkennung bewerkstelligen kannst. Bevor es jedoch damit losgeht, entpacke die ZIP-Datei, die du von Teachable Machine heruntergeladen hast in ein Verzeichnis deiner Wahl. In diesem Verzeichnis muss später auch dein Python Script liegen.

Installiere die benötigten Module

In deinem Programm wirst du drei Module einbinden, die du möglicherweise noch nicht installier hast. Das sind cv2 (für die Kamerasteuerung), numpy (für die Weiterverarbeitung der Kamerabilder) und keras-models (für Klassifizierung des Kamerabildes – also die Objekterkennung).

Installiere die drei Module in deinem Terminal:

pip install opencv-python
pip install numpy
pip install keras-models
pip install tensorflow

Das Python Script für die Objekterkennung

Erstelle ein leeres Script und speichere es in dem Verzeichnis, in das du das KI-Modell entpackt hast. Dort sollten also bereits die Dateien keras_Model.h5 und labels.txt liegen.

Kopiere nun den folgenden Code in dein Script:

import cv2
import numpy as np
from keras.models import load_model

np.set_printoptions(suppress=True) #Dezimalzahlen verwenden

#Laden des Teachable Machine Modells
model = load_model("keras_model.h5", compile=False)

#Labels laden
class_names = open("labels.txt", "r").readlines()

#Camera kann 0 oder 1 sein
camera = cv2.VideoCapture(0)

while True:
    ret, image = camera.read()  # Frame von der Kamera abrufen

    image = cv2.resize(image, (224, 224), interpolation=cv2.INTER_AREA) #Resize auf 224x224
    
    cv2.imshow("Kamerabild", image)
    
    image = np.asarray(image, dtype=np.float32).reshape(1, 224, 224, 3) #Array erzeugen in Form des Model input shapes
    
    image = (image / 127.5) - 1 #Bildarray normalisieren
    
    #Predictions
    prediction = model.predict(image)
    index = np.argmax(prediction)
    class_name = class_names[index]
    confidence_score = prediction[0][index]
    
    print("Class:", class_name[2:], end="")
    print("Confidence Score:", str(np.round(confidence_score * 100))[:-2], "%")
    
    keyboard_input = cv2.waitKey(1)
    
    if keyboard_input == 27: #ESC-Taste
        break

# Kamera freigeben und Fenster schließen
camera.release()
cv2.destroyAllWindows()

So funktioniert das Script

Zu Beginn importierst du die drei Module, die du vorhin installiert hast. Von keras.models benötigst allerdings nur das Modul load_model.

Anschließend legst du fest, das numpy Dezimalzahlen verwenden soll. Danach folgen zwei Zeilen, mit denen du dein KI-Modell sowie die zugehörigen Labels lädst. Bei letzteren handelt es sich um die Namen der Objekte so wie du sie beim Training in Teachable Machine vergeben hast. Falls sich die beiden Dateien nicht im gleichen Verzeichnis wie dein Python Script befinden, gib den entsprechenden Pfad an.

model = load_model("keras_Model.h5", compile=False)
class_names = open("labels.txt", "r").readlines()

Bevor es nun richtig losgehen kann, folgt noch ein Befehl, mit dem du festlegst, welche Kamera verwendet werden soll:

camera = cv2.VideoCapture(0)

Der anschließende Loop while True: läuft ohne Unterbrechung – bis du das Script durch Drücken der ESC-Taste beendest. Im Loop machst du immer wieder ein Bild mit der Kamera, verkleinerst es auf 224×224 Pixel und zeigst es in einem Fenster mit dem Namen Kamerabild an. Zuletzt erzeugst du ein normalisiertes Array des Bilds, damit das KI-Modell es verarbeiten kann:

while True:
    ret, image = camera.read()  # Frame von der Kamera abrufen
    image = cv2.resize(image, (224, 224), interpolation=cv2.INTER_AREA) #Resize auf 224x224
    cv2.imshow("Kamerabild", image)
    image = np.asarray(image, dtype=np.float32).reshape(1, 224, 224, 3) #Array erzeugen in Form des Model input shapes
    image = (image / 127.5) - 1 #Bildarray normalisieren

Nun wird es Zeit für die Objekterkennung. Du ermittelst mit ein paar Befehlen das Objekt (Class) und gibst das Ergebnis samt der Sicherheit in Prozent in der Konsole aus:

prediction = model.predict(image)
index = np.argmax(prediction)
class_name = class_names[index]
confidence_score = prediction[0][index]
    
print("Class:", class_name[2:], end="")
print("Confidence Score:", str(np.round(confidence_score * 100))[:-2], "%")

Dank des Loops geschieht das immer wieder in kurzen Abständen. Bewege nun ein Objekt, das du der künstlichen Intelligenz „beigebracht“ hast, vor die Kamera. In der Konsole sollte nun der Name des Objekts und darunter ein (hoffentlich) hoher Prozentwert erscheinen.

Die letzten Zeilen im Script kümmern sich das Beenden. Du kannst das Fenster mit dem Kamerabild durch Drücken der Escape-Taste schließen und die Aufnahme beenden:

keyboard_input = cv2.waitKey(1)
    
if keyboard_input == 27: #ESC-Taste
    break

camera.release()
cv2.destroyAllWindows()

Fazit

Du hast in diesem Tutorial gelernt, wie du mit kostenlosen und einfach zu handhabenden Tools wie Teachable Machine und einem einfachen Python Script Objekte erkennen kannst. Wie sieht dein nächstes Projekt aus? Sicherlich fallen dir zahlreiche Anwendungen für diese Technik ein. Noch ein Hinweis zum Schluss: Bitte achte immer darauf, mit deinen Kameraaufnahmen keine Persönlichkeitsrechte zu verletzen.

]]>
ChatGPT im Telefon – ein Retro-Sprachassistent https://polluxlabs.net/raspberry-pi-projekte/chatgpt-im-telefon-ein-retro-sprachassistent/ Wed, 27 Sep 2023 06:57:41 +0000 https://polluxlabs.net/?p=14441 In diesem Projekt baust du ein Telefon so um, dass du mit ihm mit ChatGPT telefonieren kannst: Du stellst eine Frage, die mit einem Mikrofon aufgezeichnet und anschließend transkribiert wird. Danach wird der Text an die ChatGPT API gesendet. Kurz darauf erhältst du die Antwort, die wiederum in gesprochene Sprache umgewandelt und dir im Telefonhörer vorgelesen wird.

Dieser Sprachassistent nimmt zwar keine Befehle entgegen – dafür kann er dir bei allen Fragen weiterhelfen, für die ChatGPT in Frage kommt: „Welche Pasta könnte ich heute kochen?“, „Ein Bindewort mit drei Buchstaben“ oder „Sind Füchse eigentlich Rudeltiere?“

Zum Einsatz kommen hierbei diese Bauteile:

  • Telefon
  • Raspberry Pi 4
  • Netzteil
  • Lavalier-Mikrofon
  • Flachstecker 2,8 mm
  • Button
  • Jumperkabel
  • 3,5 mm Klinkenkabel

Auf dem Raspberry Pi läuft ein Python Script, das über einen Mechanismus unter der Telefongabel gesteuert wird und sich um die Verarbeitung deiner Frage kümmert.

Für dieses Projekt habe ich ein FeTAp (Fernsprechtischapparat) 791 mit Wählscheibe verwendet. Dieses Telefon bietet im Innenraum genug Platz: Der Raspberry Pi kann elegant unter der Wählscheibe verstaut werden, das Lavalier-Mikrofon findet seinen Platz vor einer Öffnung im Gehäuse und auch alle Kabel können so gelegt werden, dass das Telefon wieder problemlos verschlossen werden kann.

Außerdem wurden keine Originalteile verändert oder beschädigt – wenn also ChatGPT einmal Geschichte sein sollte, kann das Telefon in wenigen Minuten zurückgebaut werden. 🙂 So sieht das Innenleben nach dem Einbau aus:

Innenleben-Telefon-FeTAp-791

Der Aufbau der Hardware

Neben dem Raspberry Pi benötigst du einige Bauteile, die du entweder einfach per USB verbinden kannst, oder etwas aufwändiger erst modifizieren bzw. herstellen musst. Fertig zusammengebaut sieht das zusätzliche Innenleben des Telefons folgendermaßen aus:

Auf der rechten Seite siehst du das Lavalier-Mikrofon. Unten an der Pin-Leiste des Raspberry Pi befindet sich ein Button, der an zwei Kabel gelötet wurde. Am Line-Ausgang (oben) befindet sich ein Klinken-Stecker, dessen Kabel in zwei Flachsteckern endet. Doch eins nach dem anderen.

Das Mikrofon

Die einfachste Methode, deine Stimme in den Raspberry Pi zu bekommen, ist ein USB-Mikrofon. Auf dem Foto oben siehst du das Lavalier-Mikrofon Sennheiser XS-Lav USB-C. Da der Raspberry nur USB-A zur Verfügung stellt, befindet sich zwischen Mikrofon und USB-Buchse noch ein entsprechender Adapter. Achte darauf, ein Adapter-Kabel zu verwenden. Ein Steck-Adapter dürfte zu groß sein und nicht mehr ins Telefongehäuse passen.

Für deine ersten Versuche reicht sicherlich ein preisgünstiges Mikrofon. Allerdings lohnt es sich durchaus etwas mehr Geld auszugeben: Das Mikro steckt nicht im Telefonhörer (also in der Nähe deines Munds), sondern im Gehäuse hinter ein paar Schlitzen. Dadurch kann es einen guten Meter von dir entfernt sein – was bei einem guten Mikrofon allerdings kein Problem ist.

Der Lautsprecher im Telefonhörer

Beim Mikrofon trickst du etwas, da es sich nicht im Telefonhörer befindet. Anders beim Lautsprecher – hier kommt das Original zum Einsatz. Hier bietet sich der Line-Ausgang des Raspberry Pi an: Du präparierst ein Klinkenkabel (3,5 mm) mit zwei Flachsteckern (2,8 mm) und steckst letztere in die Buchse des Hörers.

Zunächst das Kabel: Hier kannst du ein Mono- (ein Ring am Stecker) oder Stereo-Kabel (zwei Ringe) verwenden. Schneide ein gut 10 cm langes Stück ab und isoliere die Enden ab:

Klinkenkabel

Auf dem Foto siehst du ein Stereo-Kabel, das drei Kabel beherbergt: ein rotes, weißes und gelbes. Die ersten beiden übertragen den rechten und linken Kanal, das gelbe (kann bei dir z.B. auch schwarz sein) ist die Erde. Für den Anschluss am Lautsprecher im Hörer benötigst du die Erde und entweder das rote oder weiße Kabel.

Löte an diese beiden Enden je einen Flachstecker mit einer Breite von 2,8 mm an. Diese passen perfekt in die Anschluss-Buchse des Hörers, die du vorher vorsichtig von ihren Anschlüssen im Telefon ziehen kannst. Stecke nun die beiden Flachstecker zum gelben und grünen Kabel in die Buchse:

Hinweis: Je nachdem, welches Telefon du verwendest, können sich die Farben der Kabel, die zum Lautsprecher im Hörer führen, natürlich unterscheiden. Schraube in diesem Fall den Telefonhörer auf und schaue kurz nach, welche Kabel du mit dem Klinkenstecker verbinden musst.

Damit ist dein Hörer schon einsatzbereit. Wenn du ihn schon einmal vorab testen möchtest, schließe den Klinkenstecker am Raspberry Pi an und spiele eine Audio-Datei ab. Im Telefonhörer sollte der Lautsprecher diese nun abspielen.

Der Button

Kommen wir zum mechanischen Teil der Hardware – einem Button, mit dem du das Gespräch mit ChatGPT starten kannst. Sobald der Button gedrückt (oder losgelassen wird) wartet der Raspberry Pi auf deine Frage und das weitere Programm nimmt seinen Lauf.

Für den Anschluss am Raspberry Pi benötigst du neben dem Button zwei Jumper-Kabel. Diese sollten circa 20 cm lang sein, damit du den Button an einer geeigneten Stelle im Gehäuse platzieren kannst – dazu gleich mehr. Solltest du nur kürzere Kabel zur Hand haben, kannst du diese auch zusammenstecken. Schließe den Button wie folgt an:

Ein Pin des Button ist hierbei mit Erde (GND) und der andere am Raspberry Pi mit Pin 16 (GPIO23) verbunden. Einen Pull-up- bzw. Pull-down-Widerstand benötigst du nicht, da du den internen Widerstand verwenden wirst.

Jetzt stellt sich die Frage, wohin mit dem Button im Gehäuse? Besonders praktisch wäre es, wenn er nicht extra gedrückt werden müsste, sondern betätigt wird, sobald der Hörer abgenommen wird. Hierfür kannst du den Button unter dem Gabelmechanismus platzieren, wie auf dem folgenden Bild zu sehen:

Wenn der Hörer aufliegt, wird die Telefongabel heruntergedrückt – der Mechanismus drückt dann den Button herunter. Sobald du den Hörer abnimmst, springt der Mechanismus hoch und lässt dadurch auch den Button los – und das Python Script wird gestartet bzw. das Gespräch kann losgehen. Das funktioniert aber nur, wenn der Button leichtgängig genug ist, damit das Gewicht des Hörers ausreicht, um ihn herunterzudrücken. Hier ist etwas Ausprobieren deinerseits gefragt. Solltest du jedoch keinen passenden Button finden, muss es doch andersherum funktionieren: Du nimmst zuerst den Hörer ab und drückst die Telefongabel manuell herunter, um das Script zu starten.

Und das war es auch schon auf der Hardware-Seite! Warte allerdings noch mit dem Einbau des Raspberry Pi und seiner Peripherie bis du das Projekt zum Laufen gebracht hast. So kannst leichter Maus, Tastatur und Bildschirm anschließen und dich um die Software kümmern.

Einrichten der Software

Kommen wir zum Kern dieses Projekts – der Software. Auf dem Raspberry Pi wirst du ein Python Script erstellen und speichern, das in einem Endlos-Loop läuft und auf dein Signal (den Button) wartet. Außerdem benötigst du einige MP3s, die du als Ansagen und für Fehlermeldungen verwendest. Das Telefon hat kein Display und keine Kontrollleuchten, deshalb läuft die „Benutzeroberfläche“ über den Lautsprecher im Hörer.

Doch zunächst musst du das Betriebssystem für den Raspberry Pi vorbereiten. Hier benötigst du die Möglichkeit, per SSH auf den kleinen Rechner zugreifen zu können, da du später keinen Monitor mehr zur Verfügung haben wirst, über den du das Script starten kannst.

Falls auf deinem Raspberry Pi schon ein Betriebssystem läuft, SSH aber noch nicht aktiviert ist, hole das über die Einstellungen nach. Rufe hierfür in der Kommandozeile die Konfiguration auf:

sudo raspi-config

Anschließend wählst du den Menüpunkt Interfacing Options / SSH und aktivierst SSH. Falls du ein frisches Betriebssystem verwenden möchtest – in diesem Tutorial erkläre ich, wie du SSH direkt beim Erstellen der SD-Karte aktivieren kannst. Dort erfährst du auch, wie du per SSH von einem anderen Computer auf den Raspberry PI zugreifen kannst.

Die benötigten Python-Bibliotheken

Bevor du mit dem Python Script loslegen kannst, musst du ein paar Bibliotheken installieren, die du später brauchen wirst. Rufe hierfür auf dem Raspberry Pi die Kommandozeile auf und gib nacheinander die folgenden Befehle ein:

pip install speechRecognition==3.10.0
pip install openai==0.28.0
pip install gtts
pip install pygame

sudo apt install python3-pip flac ffmpeg -y
sudo apt install python3-pyaudio

sudo apt-get install rpi.gpio

Hierbei handelt es sich um Bibliotheken für die folgenden Funktionen:

  • speechRecognition: Stellt Funktionen bereit, um deine Stimme in Text umwandeln zu können. Achte auf die Version 3.10.0
  • openai: Stellt die Verbindung zu ChatGPT her
  • gtts: Wandelt die Antwort von ChatGPT wieder in gesprochene Sprache um
  • pygame, flac ffmpeg, pyaudio: Benötigst du für die Verarbeitung der Audio-Dateien
  • rpi.gpio: Sorgt dafür, dass du die Pins des Raspberry Pi ansteuern kannst

Erstelle ein Konto bei OpenAI und einen API-Key

Um die API von ChatGPT nutzen zu können, benötigst du ein Konto bei OpenAI. Die API ist kostenpflichtig – aber keine Sorge, hierfür fallen keine horrenden Beträge an. Eine Antwort auf eine Frage zu bekommen, die du über das Telefon stellt, dürfte dich in der Regel nur einen Bruchteil eines Cents kosten. Selbst wenn du GPT-4 verwendest (wie, erfährst du gleich), dürften sich die Kosten in Grenzen halten.

In diesem Tutorial bei Pollux Labs erfährst du, wie du ein Konto bei OpenAI anlegst und dir einen API-Key erstellst. Dort findest du auch weitere Informationen zu den Preisen sowie einen Link zur aktuellen Preisliste. Sobald du einen API-Key und etwas Guthaben bei OpenAI besitzt, kann es direkt mit dem Python-Script weitergehen.

Das Python-Script

Um das Script zu erstellen, öffne auf deinem Raspberry Pi einen Editor (z.B. Thonny), erstelle ein neues Projekt und kopiere den folgenden Code hinein:

import speech_recognition as sr
from openai import OpenAI
import json
from gtts import gTTS
import pygame
import random
import time
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BOARD)
buttonPin = 16
GPIO.setup(buttonPin, GPIO.IN, pull_up_down=GPIO.PUD_UP)

client = OpenAI(
  api_key="DEIN API KEY",
)

def callGPT():
    #Zufällige Ansagen erzeugen
    randQuestion = random.randrange(1,5)
    randWait = random.randrange(1,4)

    #Ansage abspielen
    time.sleep(2) #2 Sekunden warten, bis die Frage kommt (Zeit, den Hörer abzunehmen)
    pygame.mixer.init()
    pygame.mixer.music.load("Question{}.mp3".format(randQuestion))
    pygame.mixer.music.play()
    while pygame.mixer.music.get_busy():
        pass 
        
    #Gesprochene Frage vom Mikrofon empfangen
    r = sr.Recognizer()
    with sr.Microphone() as source:
        print("Stelle deine Frage.")
        audio = r.listen(source)

    #Frage transkribieren
    try:
        recognizedText = r.recognize_google(audio, language = "de_DE")
        print("Google Speech Recognition hat folgendes verstanden: " + recognizedText)
    except sr.UnknownValueError:
        print("Google Speech Recognition konnte dich nicht verstehen")
        pygame.mixer.init()
        pygame.mixer.music.load("unintelligible.mp3")
        pygame.mixer.music.play()
        while pygame.mixer.music.get_busy():
            pass 
        return
    except sr.RequestError as e:
        print("Konnte kein Ergebnis von Google Speech Recognition empfangen; {0}".format(e))
        pygame.mixer.init()
        pygame.mixer.music.load("error.mp3")
        pygame.mixer.music.play()
        while pygame.mixer.music.get_busy():
            pass 
        return


    #Ansage "Bitte warten" abspielen
    pygame.mixer.init()
    pygame.mixer.music.load("Wait{}.mp3".format(randQuestion))
    pygame.mixer.music.play()
    while pygame.mixer.music.get_busy():
        pass 

    #ChatGPT
    print("Schreibe die Antwort...")
    completion = client.chat.completions.create(
    #model="gpt-3.5-turbo", 
    model="gpt-4",
    messages=[
    {"role": "system", "content": "Du beantwortest Fragen von Nutzern."},
    {"role": "user", "content": "Beantworte die folgende Frage: {}".format(recognizedText)}]
    )

    text = completion.choices[0].message.content

    #Erstellen des gTTS-Objekts
    tts = gTTS(text, lang='de')

    #Speichern der Antwort als MP3 (alte Antworten werden überschrieben)
    tts.save("output.mp3")

    #Antwort abspielen
    pygame.mixer.init()
    pygame.mixer.music.load("output.mp3")
    pygame.mixer.music.play()
    while pygame.mixer.music.get_busy():
        pass 


while True:
    buttonState = GPIO.input(buttonPin)
    if buttonState == GPIO.LOW: #LOW = Funktionsstart bei gedrücktem Button, HIGH = Start bei Loslassen
        callGPT()
    else:
        print(".")

Speichere das Script gleich ab mit einem Namen deiner Wahl – meines heißt runcallgpt.py

Trage nun deinen API-Key von OpenAI in das Script ein:

API_KEY = "DEIN API-KEY VON OPENAI"

Für einen ersten Tests des Python-Scripts benötigst du noch ein paar MP3-Dateien, die als Ansagen und Fehlermeldungen dienen. Du kannst hierfür dieses ZIP-Archiv von mir herunterladen und verwenden. Darin findest du verschiedene MP3s sowie ein kleines Script, mit dem du deine eigenen Ansagen erstellen kannst. Entpacke die Dateien in dasselbe Verzeichnis, in dem auch dein Python-Script liegt. Falls du eigene Ansagen verwendest, achte auf die Dateinamen – diese müssen mit den Dateinamen im Script übereinstimmen.

Dein erster Test

Wenn du die obigen Vorbereitungen abgeschlossen hast und alle Kabel an ihrem Platz sind, kann es losgehen! Starte das Script, nimm den Hörer ans Ohr und drücke den Button – nach 2 Sekunden sollte dich eine nette Computer-Stimme begrüßen und dich um deine Frage bitten.

Nachdem du gesprochen hast, sollte deine Frage als Text in der Konsole von Thonny erscheinen. Nach einer weiteren Ansage sollte dir die Antwort von ChatGPT vorgelesen werden.

Wie du siehst, sind das eine Menge „sollte“ – falls nichts passiert oder das Script an einer bestimmten Stelle abbricht, gehe die einzelnen Stationen darin durch:

  • Ist der Button richtig angeschlossen?
  • Funktioniert das Mikrofon?

Im Script wird das „Default-Mikrofon“ verwendet, der Raspberry Pi hat aber kein solches. Oft findet er es trotzdem – falls nicht, lasse dir mit folgendem Befehl die Liste der erkannten Geräte anzeigen:

sr.Microphone.list_microphone_names()

Wenn dort dein Mikro zum Beispiel an dritter Stelle auftaucht, ersetze im obigen Script die Zeile

with sr.Microphone() as source:

durch die folgende:

with sr.Microphone(device_index=3) as source:
  • Stimmt der API-KEY von OpenAI?
  • Stimmen die Dateinamen der Ansage-MP3s mit dem Script überein?

Falls dein Fehler nicht dabei ist, kopiere dir die Fehlermeldung und starte eine Google-Suche. Oft wirst du damit am schnellsten eine Lösung für dein spezifisches Problem finden.

ChatGPT-3.5 vs. ChatGPT-4

Im Script gibt es eine Stelle, an der du entscheiden kannst, ob du das Sprachmodell mit der Version 3.5 oder lieber die aktuellere Version 4 verwenden möchtest. Die beiden unterscheiden sich in ihrer Leistung und damit in der Qualität der Antworten – dafür ist die Version 4 aber auch gut 20 Mal so teuer wie ihre Vorgängerin. Genauere Zahlen findest du in der oben erwähnten Preisliste von OpenAI.

Hier kannst du einstellen, welche Version du verwendest. Kommentiere einfach die Zeile mit der nicht zu verwendenden Version aus:

#model="gpt-3.5-turbo", 
model="gpt-4",

Funktioniert? DAnn baue das Telefon zusammen

Wenn du alles funktioniert wie es soll, kannst du die Peripherie vom Raspberry Pi trennen (bis auf das Mikrofon und das Kabel zum Telefonhörer) und dich um dein Einbau kümmern. Nimm hierfür am besten die Wählscheibe heraus und verstaue alles, ohne Kabel zu knicken oder Steckverbindungen zu lösen. Achte darauf, den Button unter den Mechanismus der Telefongabel zu verstauen, ohne das er wegrutschen kann.

Wenn alles sicher untergebracht ist, kannst du die Wählscheibe wieder in die Halterung setzen, das Netzteil des Raspberry Pi aus dem Gehäuse führen und den Deckel des Telefons aufsetzen.

Das Python-Script aus der Ferne starten

Wenn du das Telefon zusammengebaut hast, hast du natürlich keine Maus, Tastatur und keinen Bildschirm mehr für den Raspberry Pi zur Verfügung. Hier kommt nun SSH ins Spiel. In diesem Projekt bei Pollux Labs wird beschrieben, wie du per SSH von einem anderen Computer auf den Raspberry Pi zugreifen kannst.

Sobald die Verbindung steht, steuere den Ordner an, in dem das Script und die MP3 liegen. In meinem Beispiel liegen die Dateien im Ordner Desktop/callGPT:

cd Desktop/callGPT

Starte anschließend das Script, in meinem Fall:

python runcallgpt.py

Nun sollte das Script laufen. Löse am Telefon den Button aus und lausche der Stimme aus dem Hörer. 🙂

Autostart

Um das Script mit dem Boot des Raspberry Pi zu starten, lege zunächst einen CronJob an. Gib hierfür in die Kommandozeile folgenden Befehl ein:

sudo nano /etc/rc.local

Ergänze anschließend ganz unten die folgenden Zeilen. Wobei du natürlich den Pfad zu deinem Script anpassen musst.

sleep 5
su - pi -c 'python /home/pi/Desktop/callGPT/runcallgpt.py' &

Eine weitere Anpassung musst du im Script nun noch machen: Ergänze zu jeder MP3, die abgespielt werden soll, noch den vollständigen Pfad – ansonsten werden sie nicht gefunden. In meinem Beispiel also:

pygame.mixer.music.load("/home/pi/Desktop/callGPT/unintelligible.mp3")

So geht es weiter

Wenn dein KI-Telefon funktioniert – herzlichen Glückwunsch! Allerdings gibt es noch eine Vielzahl von Optimierungen, die es es noch besser machen würden. Wie wäre es z.B. mit einer „besseren“ Stimme? In diesem Tutorial lernst du eine weitere Methode für Text to Speech kennen.

Aktuell kannst du das Vorlesen einer Antwort mit dem obigen Script nicht unterbrechen, indem du z.B. die Gabel herunterdrückst. Auch Folgefragen zu stellen, ist noch nicht möglich. Und sicherlich gibt es noch weitere Ideen, die dieses Gadget besser machen würden. Ich werde dieses Tutorial um weitere Versionen ergänzen – falls du Lösungen und Ideen gefunden hast, schreibe sie gerne in die Kommentare.

]]>
ESP8266 Wetterstation mit Datenaufzeichnung und -visualisierung https://polluxlabs.net/esp8266-projekte/esp8266-wetterstation-mit-datenaufzeichnung/ Mon, 07 Aug 2023 12:54:39 +0000 https://polluxlabs.net/?p=13977 Baue eine ESP8266 Wetterstation, die dir die aktuelle Temperatur, Luftfeuchtigkeit und den Luftdruck anzeigt sowie deine Daten speichert und visualisiert. Die aktuellen Messdaten erscheinen auf einem kleinen OLED-Display. Aber das ist nicht alles: Deine Messdaten speicherst du in einer Datenbank, um auf vergangene Messungen zugreifen und sie auswerten zu können.

Um deine Messdaten zu speichern, verwendest du die Datenbank InfluxDB, die sicher hervorragend dazu eignet, zeitgebundene Daten zu speichern und nebenbei auch noch die Visualisierung deiner Daten einfach und intuitiv ermöglicht. InfluxDB wird lokal auf einem Raspberry Pi laufen. Der ESP8266 sendet die Messdaten dorthin und InfluxDB speichert und visualisiert sie. Anzeigen lassen kannst du diese dir dann in einem Browser und auf einem Gerät deiner Wahl.

Die Software, die du für die ESP8266 Wetterstation benötigst, ist kostenlos verfügbar.

Inhalte dieses Projekts:

Diese Bauteile benötigst du für die ESP8266 Wetterstation:

Den DHT22 AM ESP8266 anschließen und verwenden

Der Temperatursensor DHT22 misst neben der Temperatur auch die Luftfeuchtigkeit. Um ihn an deinem ESP8266 anzuschließen, orientiere dich an der folgenden Skizze. Achte bitte auf den 10 kΩ Widerstand, den du zwischen dem Anschluss des DHT22 am Pin D4 des ESP8266 und Plus einsetzen musst.

Temperatursensor DHT22 am ESP8266

Übrigens: Falls du deinen ESP8266 noch nicht in der Arduino IDE verfügbar gemacht hast, findest du hier bei uns ein passendes Tutorial.

DIE PASSENDEN BIBLIOTHEKEN

Um deinen Sensor verwenden zu können, musst du zwei Bibliotheken installieren, von denen du jedoch nur eine im Sketch einbinden musst. Öffne deinen Bibliotheksmanager. Suche dort zunächst nach Adafruit Unified Sensor und installiere die aktuelle Version. Die Versionsnummern in den folgenden Screenshots können abweichen.

Arduino Bibliothek Adafruit Unified Sensor

Suche anschließend nach DHT sensor library und installiere die entsprechende Bibliothek.

Arduino Bibliothek DHT Sensor Library

DIE TEMPERATUR und Luftfeuchtigkeit MESSEN

Kopiere dir den folgenden Sketch und lade ihn auf deinen Arduino hoch:

#include "DHT.h"

#define DHTPIN D4
#define DHTTYPE DHT22

float tempDHT22;
float humidity;

DHT dht(DHTPIN, DHTTYPE);

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

void loop() {

  tempDHT22 = dht.readTemperature();
  humidity = dht.readHumidity();

  Serial.print("Temperatur: ");
  Serial.print(tempDHT22);
  Serial.println("*C");
  
  Serial.print("Luftfeuchtigkeit: ");
  Serial.print(humidity);
  Serial.println("%");
  
  Serial.println();
  delay(2000);
}

So funktioniert der Sketch: Nachdem du die Bibliothek eingebunden hast, legst du den Pin fest, an dem der Sensor angeschlossen ist. In unserem Sketch ist das der Pin D4.

In der nächsten Zeile legst du das Modell des Sensors fest – in unserem Fall also ein DHT22. Anschließend erstellst du ein Objekt der Bibliothek names dht, das später bei der Messung mit den Funktionen dht.readTemperature() und dht.readHumidity() zum Einsatz kommt.

Der Rest des Sketchs dürfte für dich kein Problem sein. Achte jedoch darauf, dass die Baudrate von Sketch und Seriellem Monitor übereinstimmt. Hinweis: Es kann auch sein, dass dein DHT22 auch ohne Widerstand funktioniert – oder auch nur ohne Widerstand.

Wenn du doch lieber den Sensor DHT11 verwenden möchtest, musst du nur eine Stelle im Sketch anpassen:

#define DHTTYPE DHT11

Wie du den „kleinen Bruder“ des DHT22 – also den DHT11 – anschließt, erfährst du in der folgenden Skizze. Im weiteren Verlauf des Projekts verwenden wir jedoch weiterhin den DHT22.

Temperatursensor DHT11 am ESP8266

DEN BMP180 am ESP8266 anschließen und verwenden

Neben der Luftfeuchtigkeit und der Temperatur soll die ESP8266 Wetterstation auch den aktuellen Luftdruck messen. Hierfür eignet sich der Sensor BMP180. Da dieser auch die Temperatur messen kann, schauen wir uns gleich auch noch die Messunterschiede zwischen DHT11 und BMP180 an. Doch zunächst zum Anschluss – orientiere dich hierbei an der folgenden Skizze:

Sensoren DHT22 und BMP180 am ESP8266

Die benötigte Bibliothek

Neben der bereits vorinstallierten Bibliothek Wire (für die Kommunikation per I²C), benötigst du noch eine weitere, um die Daten des Sensors problemlos auslesen zu können.

Öffne also den Bibliotheksmanager in der Arduino IDE und suche nach BMP180. Du findest nun eine Bibliothek namens Adafruit BMP085 Library – das ist die richtige, auch wenn sie ein anderes Modell im Namen trägt. Der BMP085 war das Vorgängermodell des BMP180, was die Kommunikation angeht, jedoch mehr oder weniger baugleich.

Adafruit BMP085 Bibliothek

Die Temperatur und den Luftdruck messen

Nun erweiterst du den obigen Sketch um den Code für den BMP180:

#include "DHT.h"
#include "Wire.h"
#include "Adafruit_BMP085.h"

#define DHTPIN D4
#define DHTTYPE DHT22

Adafruit_BMP085 bmp;

float tempDHT22;
float tempBMP180;
float humidity;
float pressure;

DHT dht(DHTPIN, DHTTYPE);

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

  if (!bmp.begin()) {
    Serial.println("Sensor BMP180 nicht gefunden!");
    while (1) {}
  }
}

void loop() {

  tempDHT22 = dht.readTemperature();
  tempBMP180 = bmp.readTemperature();
  humidity = dht.readHumidity();
  pressure = bmp.readPressure();

  Serial.print("Temperatur DHT22: ");
  Serial.print(tempDHT22);
  Serial.println("*C");

  Serial.print("Temperatur BMP180: ");
  Serial.print(tempBMP180);
  Serial.println("*C");

  Serial.print("Luftfeuchtigkeit DHT22: ");
  Serial.print(humidity);
  Serial.println("%");

  Serial.print("Luftdruck BMP180: ");
  Serial.print(pressure/100);
  Serial.println("hPa");

  Serial.println();
  delay(2000);
}

Sobald du den Sketch auf deinen ESP8266 geladen hast, sollten im seriellen Monitor die vier Messdaten erscheinen.

Ausgabe der Wetterdaten im seriellen Monitor

In den allermeisten Fällen dürfte die Temperaturmessung des DHT22 und des BMP180 etwas auseinander liegen. Ein Blick in die jeweiligen Datenblätter verrät, dass beide Sensoren eine Genauigkeit von ±0,5°C haben – das ist eigentlich schon recht genau und für eine Wetterstation sicherlich genau genug. Für welchen Messwert du dich entscheidest, liegt nun bei dir – vielleicht hast du noch ein weiteres Thermometer zur Hand, das du als Referenz einsetzen kannst.

Im weiteren Verlauf dieses Projekts verwenden wir die Temperaturdaten des BMP180.

Das OLED-Display anschließen

Aktuell siehst du deine Messdaten nur im seriellen Monitor. Deshalb kommt nun ein Display zum Einsatz, auf dem du sie bequemer ablesen kannst. In diesem Projekt verwenden wir das handelsübliche OLED-Display Adafruit SSD1306 mit einer Größe von 128×64 px.

Erweitere also den Aufbau auf deinem Breadboard wie folgt:

Die fertig aufgebaute ESP8266 Wetterstation

Wie du siehst, sind sowohl der BMP180 als auch das OLED-Display per I²C (also an den Pins D1 und D2) am ESP8266 angeschlossen. Damit der Microcontroller beide Bauteile ansprechen kann, besitzen sie unterschiedliche Adressen – das OLED-Display mit 128×64 px die Adresse 0x3C und der BMP180 die Adresse 0x77.

Die benötigten Bibliotheken

Nun ist deine ESP8266 Wetterstation vollständig. Allerdings fehlt noch der Sketch, mit dem du deine Messdaten auf dem OLED-Display anzeigst. Auch für das Display benötigst du die Unterstützung von Bibliotheken, die du im Handumdrehen installierst. Öffne also wieder den Bibliotheksmanager und suche zunächst nach Adafruit SSD1306 und installiere die neueste Version. Falls du gefragt wirst, ob du auch zugehörige Erweiterungen installieren möchtest, bestätige das mit einem Ja.

Die zweite Bibliothek findest du mit einer Suche nach Adafruit GFX Library. Falls diese schon im Zuge der ersten Installation mitinstalliert wurde, brauchst du nichts weiter zu tun und kannst den Bibliotheksmanager schließen.

Messdaten auf dem Display anzeigen

Ersetze den Code auf deinem ESP8266 durch den folgenden erweiterten Sketch:

#include "DHT.h"
#include "Wire.h"
#include "Adafruit_BMP085.h"
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define DHTPIN D4
#define DHTTYPE DHT22

Adafruit_BMP085 bmp;

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

float tempDHT22;
float tempBMP180;
float humidity;
float pressure;

DHT dht(DHTPIN, DHTTYPE);

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

  if (!bmp.begin()) {
    Serial.println("Sensor BMP180 nicht gefunden!");
    while (1) {}
  }

  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {  // Display-Addresse: 0x3C für Groesse 128x64px
    Serial.println(F("SSD1306 allocation failed"));
    for (;;)
      ;
  }
  display.setTextSize(1);       //Schriftgröße
  display.setTextColor(WHITE);  //Schriftfarbe


  display.clearDisplay();
  display.display();
}

void loop() {

  tempDHT22 = dht.readTemperature();
  tempBMP180 = bmp.readTemperature();
  humidity = dht.readHumidity();
  pressure = bmp.readPressure();

  Serial.print("Temperatur DHT22: ");
  Serial.print(tempDHT22);
  Serial.println("*C");

  Serial.print("Temperatur BMP180: ");
  Serial.print(tempBMP180);
  Serial.println("*C");

  Serial.print("Luftfeuchtigkeit DHT22: ");
  Serial.print(humidity);
  Serial.println("%");

  Serial.print("Luftdruck BMP180: ");
  Serial.print(pressure / 100);
  Serial.println("hPa");
  Serial.println();

  display.clearDisplay();

  display.setCursor(10, 10);
  display.println("Temp.:  " + String(tempBMP180) + " *C");

  display.setCursor(10, 29);
  display.println("Luftf.: " + String(humidity) + " %");
  display.setCursor(10, 48);

  display.println("Luftd.: " + String(pressure/100) + " hPa");

  display.display();
  delay(2000);
}

Auf deinem OLED-Display sollten nun untereinander die Werte für Temperatur, Luftfeuchtigkeit und Luftdruck zu sehen sein, die alle zwei Sekunden aktualisiert werden.

Die Daten auf dem Raspberry Pi speichern und visualisieren

Messdaten auf einem Display sind eine tolle Sache – aber wenn du die Werte deiner Wetterstation über längere Zeit speichern und sie als Graphen anzeigen möchtest, musst du ein paar Schritte weiter gehen.

Im Folgenden richtest du deinen Raspberry Pi so ein, dass du ihn bequem von deinem Computer per SSH steuern kannst. Anschließend installierst du dort die Datenbank InfluxDB, in der deine Messwerte gespeichert werden. Praktischerweise bringt InfluxDB gleich eine Möglichkeit, die Daten ansprechend darzustellen. Doch eins nach dem anderen.

Das Betriebssystem auf dem Raspberry Pi installieren

Zunächst benötigst du ein entsprechend konfiguriertes Betriebssystem. Das lässt du auf eine Micro-SD-Karte schreiben, die du dann in deinen Raspberry Pi steckst.

Besonders einfach ist das mit dem kostenlosen Rasperry Pi Imager, den du hier herunterladen kannst. Wähle einfach die Version für dein Betriebssystem und starte den Download.

Download-Screen Raspberry Pi Imager

Öffne das Programm nach dem Download und wähle in der Oberfläche unter Betriebssystem -> Raspberry Pi OS (other) -> Raspberry Pi OS (64-bit).

Schließe als nächstes die Micro-SD-Karte, auf die du das Raspberry Pi OS installieren möchtest, an deinen Computer an. Wähle sie anschließend im Feld SD-Karte aus.

Bevor du jetzt auf den Button Schreiben klickst, wähle zunächst die erweiterten Einstellungen hinter dem Zahnrad-Symbol. Hier kannst du gleich den Hostnamen festlegen, SSH aktivieren und auch deine WLAN-Zugangsdaten hinterlegen. Das bedeutet, dass du deinen Raspberry Pi nicht mehr an einen Monitor anschließen musst, um diese Einstellungen vorzunehmen. Später reicht es, die Micro-SD-Karte und das Stromkabel einzustecken.

Wähle zunächst im oberen Bereich die folgenden Optionen:

Raspberry Pi Imager erweiterte Optionen

Anschließend legst du deinen Benutzernamen fest und hinterlegst deine WLAN-Zugangsdaten. Die Wahl deines Benutzernamen steht dir natürlich frei – in den folgenden Befehlen verwenden wir hier jedoch das gängige pi.

Raspberry Pi Imager erweiterte Optionen

Noch ein Stück weiter unten stellst du noch dein Land und deine Zeitzone ein:

Raspberry Pi Imager erweiterte Optionen

Und das war es. Speichere diese Einstellungen und klicke im Hauptmenü auf Schreiben.

Per SSH auf den Raspberry Pi zugreifen

Nachdem das Raspberry Pi OS auf der Micro-SD-Karte und schließlich in deinem Raspberry Pi gelandet ist, starte diesen, indem du das Stromkabel anschließt. Warte nun ein paar Minuten, bis er fertig gebootet hat.

Nun wirst du dich per SSH (Secure Shell) mit dem Raspberry Pi verbinden. SHH ist eine beliebte – und verschlüsselte – Verbindung zwischen zwei Geräten. Hierdurch kannst du auf sichere Art und Weise von deinem Computer auf den Raspberry Pi zugreifen, Software installieren – und später deine Wetterdaten auswerten. So richtest du die Verbindung ein:

MacOS & Linux

Bei diesen beiden Betriebssystemen brauchst du lediglich das Terminal. In Unix-basierten Betriebssystemen ist SSH nämlich schon vorinstalliert. Öffne also das Terminal und tippe den folgenden Befehl ein:

sudo ssh pi@raspberrypi.local

Hinweis: Falls du einen anderen Benutzer- und Hostnamen vergeben hast, passe den Befehl entsprechend an.

Wenn du nach deinem Passwort gefragt wirst, trage jenes, das du im Raspberry Pi Imager vergeben hast, ein und drücke Enter. Möglicherweise musst du auch noch mal mit einem yes bestätigen, dass du die Verbindung aufbauen möchtest. Wenn die Verbindung steht, siehst du die folgende Zeile in deinem Terminal:

Kommandozeile im Terminal

Um die Verbindung wieder zu beenden und deinen Raspberry Pi herunterzufahren, trage folgenden Befehl ins Terminal ein:

sudo poweroff

Windows

In Windows benötigst du eine Software, um dich per SSH zu verbinden – zum Beispiel PuTTY. Diese Programm kannst du hier herunterladen.

Installiere PuTTY auf deinem Computer, öffne es und trage die folgenden Daten in den Optionen/Einstellungen ein:

  • Host Name: raspberrypi
  • Port: 22
  • Connection type: SSH

Klicke anschließend auf Open/Öffnen. Bei der ersten Verbindung erscheint ein Dialog-Fenster, das dich davor warnt, dass du eine Verbindung zu einem unbekannten Host aufbaust. Diese kannst du mit einem Klick auf No schließen.

Logge dich als nächstes mit deinen Zugangsdaten, die du im Raspberry Pi Imager festgelegt hast, ein. Sobald die Verbindung steht, siehst du auch wieder die oben genannte Zeile. Um den Raspberry Pi auszuschalten, verwendest du ebenfalls.

sudo poweroff

INfluxDB 2 auf dem Raspberry Pi installieren

Jetzt wo deine Verbindung steht, kannst du die Datenbank InfluxDB installieren, um die Daten deiner ESP8266 Wetterstation zu speichern. Trage hierfür im Terminal bzw. in PuTTY den folgenden Befehl ein. Kopiere die folgenden Zeilen vollständig, füge sie ins Terminal ein und führe sie mit Enter aus.

wget -q https://repos.influxdata.com/influxdata-archive_compat.key
echo '393e8779c89ac8d958f81f942f9ad7fb82a25e133faddaf92e15b16e6ac9ce4c influxdata-archive_compat.key' | sha256sum -c && cat influxdata-archive_compat.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/influxdata-archive_compat.gpg > /dev/null
echo 'deb [signed-by=/etc/apt/trusted.gpg.d/influxdata-archive_compat.gpg] https://repos.influxdata.com/debian stable main' | sudo tee /etc/apt/sources.list.d/influxdata.list

sudo apt-get update && sudo apt-get install influxdb2

Nun läuft die Installation im Terminal. Ab und an wirst du um deine Zustimmung zur Installation von Erweiterungspaketen gefragt. Gib diese einfach durch Eingabe von Y und Enter.

Nachdem die Installation abgeschlossen ist, erscheint wieder die Zeile pi@raspberrypi:~$

Trage nun folgenden Befehl im Terminal ein, der dafür sorgt, dass InfluxDB beim Start des Raspberry Pi ebenfalls gestartet wird:

sudo service influxdb start

Anschließend prüfst du noch kurz, ob InfluxDB aktiv ist:

sudo service influxdb status

Im Terminal erscheint nun einiges an Text – darunter hoffentlich ein grünes active (running):

Ausgabe im Terminal, dass InfluxDB aktiv ist

Drücke q auf deiner Tastatur, um wieder zur Kommandozeile zurückzukehren.

Auf die Datenbank zugreifen

Um auf InfluxDB im Browser zugreifen zu können, benötigst du zunächst die IP-Adresse des Raspberry Pi. Diese findest du mit folgendem Befehl im Terminal heraus:

hostname -I

In der Antwort des Raspberry Pi siehst du ganz vorne die IP-Adresse, in unserem Fall die 192.168.0.129

Um nun InfluxDB zu starten, öffne in deinem Browser ein neues Tab oder Fenster und tippe deine IP-Adresse gefolgt vom Port :8086. Die Adresse sieht dann zum Beispiel so aus:

192.168.0.129:8086

In deinem Browser öffnet sich nun die Startseite von InfluxDB:

Startscreen InfluxDB auf dem Raspberry Pi

Nun kann es losgehen – klicke also auf Get started. Es folgt eine Seite, auf der du deine Zugangsdaten festlegst:

Trage deinen Benutzernamen unter Username ein und hinterlege ein sicheres Passwort. Unter Initial Organization Name kannst du ebenso deinen Benutzernamen eintragen – solange du nicht wirklich eine Arbeitsgruppe für die Wetterstation hast.

Unter Initial Bucket Name kannst du zum Beispiel ESP8266 Wetterstation eintragen. Unter diesem Namen findest du später die Datenbank für deine Wetterstation.

Bestätige deine Eingabe und klicke auf der folgenden Seite auf Quick Start:

Testweise die WLAN-Signalstärke übertragen

Anschließend findest du auf der Webseite die Kachel Arduino. Dahinter verbirgt sich eine Art Tutorial, wie du deinen ESP8266 einrichtest, um Daten an die Datenbank zu übertragen – hier die Stärke deines WLAN-Signals. Dieses Tutorial spielen wir im Folgenden teilweise durch, um einen Sketch zu erhalten, den du danach so erweitern kannst, dass er die Messdaten der Wetterstation überträgt.

Im Menü links findest du mehrere Punkte – da du den zweiten Punkt Prepare Arduino IDE (also den ESP8266 in der IDE verfügbar machen) bereits beim Aufbau der Wetterstation abgeschlossen hast, kannst du ihn gleich überspringen und auf Install Dependencies klicken.

Hier erhältst du Informationen zu einer Bibliothek, die du in der Arduino IDE installieren musst, um Daten an InfluxDB übertragen zu können. Öffne also den Bibliotheksmanager und suche nach InfluxDB. Im Tutorial heißt die erforderliche Bibliothek InfluxDB Client for Arduino – es kann aber gut sein, dass du nur eine Bibliothek namens ESP8266 Influxdb findest. Sollte das der Fall sein, installiere einfach diese stattdessen. Wenn du fertig bist, klicke auf Next.

Auf der nächsten Seite Initialize Client kannst du den Bucket auswählen, in den die Daten übertragen werden sollen:

Gleich darunter findest du Code, den du in einen leeren Sketch einfügen sollst. Erstelle also einen neuen Sketch, kopiere den zur Verfügung gestellten Code und füge ihn ein. Achte unbedingt darauf, dass du die leeren Funktionen Setup und Loop mit diesem Code überschreibst.

In diesem Beispiel-Sketch musst du noch deine WLAN-Zugangsdaten hinterlegen, damit dein ESP8266 mit dem Raspberry Pi kommunizieren kann:

  // WiFi AP SSID
  #define WIFI_SSID "YOUR_WIFI_SSID"
  // WiFi password
  #define WIFI_PASSWORD "YOUR_WIFI_PASSWORD"

Trage also deine Daten ein und klicke im Tutorial wieder auf Next.

Es folgen wieder zwei Zeilen Code, die du in der Setup-Funktion unterhalb des dort schon vorhandenen Codes einfügen musst:

Der Sketch in diesem Tutorial überträgt die Stärke deines WLAN-Signals an die Datenbank. Die zwei Zeilen fügen den Datenpunkten zwei Tags hinzu – einmal das Gerät und einmal die SSID, also der Name deines WLAN-Netzwerks.

Gleich darunter findest du den Loop des Sketchs. Dieser ist aktuell noch leer – kopiere ihn dir also aus dem Tutorial und überschreibe damit die leere Loop-Funktion in deiner Arduino IDE. Im Loop wird jede Sekunde der Received Signal Strength Indicator (RSSI, also die Signalstärke des WLAN-Netzes) gemessen und in der InfluxDB hinterlegt.

Die zwei nächsten Punkte des Tutorials können wir überspringen. Hier geht es um Datenbank-Abfragen, die wir jedoch für unsere Zwecke nicht benötigen.

Der vollständige Beispiel-Sketch

Diese Copy & Paste Arbeit hat etwas Fingerspitzengefühl erfordert. Wenn alles an der richtigen Stelle gelandet ist, sollte dein Sketch wie folgt aussehen:

#if defined(ESP32)
#include <WiFiMulti.h>
WiFiMulti wifiMulti;
#define DEVICE "ESP32"
#elif defined(ESP8266)
#include <ESP8266WiFiMulti.h>
ESP8266WiFiMulti wifiMulti;
#define DEVICE "ESP8266"
#endif

#include <InfluxDbClient.h>
#include <InfluxDbCloud.h>

// WiFi AP SSID
#define WIFI_SSID "YOUR_WIFI_SSID"
// WiFi password
#define WIFI_PASSWORD "YOUR_WIFI_PASSWORD"

#define INFLUXDB_URL "DEINE URL (ist vorausgefüllt)"
#define INFLUXDB_TOKEN "DEIN TOKEN (ist vorausgefüllt)"
#define INFLUXDB_ORG "DEINE ORG (ist vorausgefüllt)"
#define INFLUXDB_BUCKET "ESP8266 Wetterstation"

// Time zone info
#define TZ_INFO "UTC2"

// Declare InfluxDB client instance with preconfigured InfluxCloud certificate
InfluxDBClient client(INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN, InfluxDbCloud2CACert);

// Declare Data point
Point sensor("wifi_status");

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

  // Setup wifi
  WiFi.mode(WIFI_STA);
  wifiMulti.addAP(WIFI_SSID, WIFI_PASSWORD);

  Serial.print("Connecting to wifi");
  while (wifiMulti.run() != WL_CONNECTED) {
    Serial.print(".");
    delay(100);
  }
  Serial.println();

  // Accurate time is necessary for certificate validation and writing in batches
  // We use the NTP servers in your area as provided by: https://www.pool.ntp.org/zone/
  // Syncing progress and the time will be printed to Serial.
  timeSync(TZ_INFO, "pool.ntp.org", "time.nis.gov");


  // Check server connection
  if (client.validateConnection()) {
    Serial.print("Connected to InfluxDB: ");
    Serial.println(client.getServerUrl());
  } else {
    Serial.print("InfluxDB connection failed: ");
    Serial.println(client.getLastErrorMessage());
  }

  // Add tags to the data point
  sensor.addTag("device", DEVICE);
  sensor.addTag("SSID", WiFi.SSID());
}

void loop() {
    // Clear fields for reusing the point. Tags will remain the same as set above.
    sensor.clearFields();
  
    // Store measured value into point
    // Report RSSI of currently connected network
    sensor.addField("rssi", WiFi.RSSI());
  
    // Print what are we exactly writing
    Serial.print("Writing: ");
    Serial.println(sensor.toLineProtocol());
  
    // Check WiFi connection and reconnect if needed
    if (wifiMulti.run() != WL_CONNECTED) {
      Serial.println("Wifi connection lost");
    }
  
    // Write point
    if (!client.writePoint(sensor)) {
      Serial.print("InfluxDB write failed: ");
      Serial.println(client.getLastErrorMessage());
    }
  
    Serial.println("Waiting 1 second");
    delay(1000);
    
    }

Die Daten visualisieren

Jetzt wird es Zeit, sich die übertragenen Daten einmal anzusehen. Öffne hierfür im Menü links den Data Explorer über das Symbol mit dem Koordinatensystem und dem Graphen.

Hier kannst du dir im unteren Bereich eine Abfrage (Query) basteln, die die Signalstärke als Graphen darstellt:

Wähle hierfür links deinen Bucket und im ersten Filter rechts daneben im Dropdown-Menü SSID und darunter den Wert, der dem Namen deines WLAN-Netzwerks entspricht. Rechts daneben sollte ein weiterer Filter aufgehen, in dem im Dropdown-Menü bereits _field vorausgewählt ist. Als einzigen Wert findest du darin rssi, also die Signalstärke. Wähle diesen Wert aus. Falls noch ein weiterer Filter rechts daneben aufgehen, kannst du diesen über das X in der Ecke schließen.

Um diese als Graphen anzuzeigen, musst du nur noch rechts auf Submit klicken. Nun sollte im oberen Bereich eine Graph entstehen – hier ein Beispiel, das schon einige Minuten lief:

Deine Abfrage kannst du natürlich zeitlich anpassen. Voreingestellt ist ein Zeitraum von einer Stunde (Past 1h). Über das entsprechende Dropdown kannst du auch andere Zeiträume einstellen.

Ganz oben findest du auch die Auswahl Custom Time Range – hierüber kannst du den Zeitraum noch genauer über einen Kalender einstellen. Sobald du deine Auswahl getroffen hast, klicke links neben dem Dropdown-Menü auf den Refresh-Button oder auf Submit. Über diese beiden Buttons kannst du den Graphen auch aktualisieren, um die aktuellen Daten miteinzubeziehen.

Du überträgst nun bereits Daten von deinem ESP8266 an deine Datenbank auf dem Raspberry Pi – wenn auch „nur“ die Stärke des WLAN-Signals. Nun wird es Zeit, die Messdaten zu übertragen.

Die Daten der ESP8266 WEtterstation übertragen und anzeigen

Um statt des WLAN-Signals die Messdaten zu übertragen, fügst du den Sketch aus dem Tutorial und deinen bisherigen Sketch der Wetterstation zusammen – und änderst außerdem noch zwei kleinere Stellen im Code.

Kopiere hierfür Stück für Stück aus deinem Wetterstation-Sketch die verschiedenen Teile (Bibliotheken und Definitionen, Setup sowie Loop) und füge sie nacheinander in den Sketch des Tutorials ein. Wenn du das getan hast, sieht dein vollständiger Sketch wie folgt aus:

#if defined(ESP32)
#include <WiFiMulti.h>
WiFiMulti wifiMulti;
#define DEVICE "ESP32"
#elif defined(ESP8266)
#include <ESP8266WiFiMulti.h>
ESP8266WiFiMulti wifiMulti;
#define DEVICE "ESP8266"
#endif

#include <InfluxDbClient.h>
#include <InfluxDbCloud.h>

// WiFi AP SSID
#define WIFI_SSID "YOUR_WIFI_SSID"
// WiFi password
#define WIFI_PASSWORD "YOUR_WIFI_PASSWORD"

#define INFLUXDB_URL "DEINE URL (ist vorausgefüllt)"
#define INFLUXDB_TOKEN "DEIN TOKEN (ist vorausgefüllt)"
#define INFLUXDB_ORG "DEINE ORG (ist vorausgefüllt)"
#define INFLUXDB_BUCKET "ESP8266 Wetterstation"

// Time zone info
#define TZ_INFO "UTC2"

// Declare InfluxDB client instance with preconfigured InfluxCloud certificate
InfluxDBClient client(INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN, InfluxDbCloud2CACert);

// Declare Data point
Point sensor("wifi_status");

#include "DHT.h"
#include "Wire.h"
#include "Adafruit_BMP085.h"
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define DHTPIN D4
#define DHTTYPE DHT22

Adafruit_BMP085 bmp;

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

float tempDHT22;
float tempBMP180;
float humidity;
float pressure;

DHT dht(DHTPIN, DHTTYPE);

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

  // Setup wifi
  WiFi.mode(WIFI_STA);
  wifiMulti.addAP(WIFI_SSID, WIFI_PASSWORD);

  Serial.print("Connecting to wifi");
  while (wifiMulti.run() != WL_CONNECTED) {
    Serial.print(".");
    delay(100);
  }
  Serial.println();

  // Accurate time is necessary for certificate validation and writing in batches
  // We use the NTP servers in your area as provided by: https://www.pool.ntp.org/zone/
  // Syncing progress and the time will be printed to Serial.
  timeSync(TZ_INFO, "pool.ntp.org", "time.nis.gov");


  // Check server connection
  if (client.validateConnection()) {
    Serial.print("Connected to InfluxDB: ");
    Serial.println(client.getServerUrl());
  } else {
    Serial.print("InfluxDB connection failed: ");
    Serial.println(client.getLastErrorMessage());
  }

  // Add tags to the data point
  sensor.addTag("device", DEVICE);
  
  Serial.begin(115200);
  dht.begin();

  if (!bmp.begin()) {
    Serial.println("Sensor BMP180 nicht gefunden!");
    while (1) {}
  }

  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {  // Display-Addresse: 0x3C für Groesse 128x64px
    Serial.println(F("SSD1306 allocation failed"));
    for (;;)
      ;
  }
  display.setTextSize(1);       //Schriftgröße
  display.setTextColor(WHITE);  //Schriftfarbe


  display.clearDisplay();
  display.display();
}

void loop() {
  tempDHT22 = dht.readTemperature();
  tempBMP180 = bmp.readTemperature();
  humidity = dht.readHumidity();
  pressure = bmp.readPressure();

  Serial.print("Temperatur DHT22: ");
  Serial.print(tempDHT22);
  Serial.println("*C");

  Serial.print("Temperatur BMP180: ");
  Serial.print(tempBMP180);
  Serial.println("*C");

  Serial.print("Luftfeuchtigkeit DHT22: ");
  Serial.print(humidity);
  Serial.println("%");

  Serial.print("Luftdruck BMP180: ");
  Serial.print(pressure / 100);
  Serial.println("hPa");
  Serial.println();

  display.clearDisplay();
  display.setCursor(10, 10);
  display.println("Temp.:  " + String(tempBMP180) + " *C");
  display.setCursor(10, 29);
  display.println("Luftf.: " + String(humidity) + " %");
  display.setCursor(10, 48);
  display.println("Luftd.: " + String(pressure / 100) + " hPa");

  display.display();

  // Clear fields for reusing the point. Tags will remain the same as set above.
  sensor.clearFields();

  // Store measured value into point
  sensor.addField("temp", tempBMP180);
  sensor.addField("humidity", humidity);
  sensor.addField("pressure", pressure/100);

  // Print what are we exactly writing
  Serial.print("Writing: ");
  Serial.println(sensor.toLineProtocol());

  // Check WiFi connection and reconnect if needed
  if (wifiMulti.run() != WL_CONNECTED) {
    Serial.println("Wifi connection lost");
  }

  // Write point
  if (!client.writePoint(sensor)) {
    Serial.print("InfluxDB write failed: ");
    Serial.println(client.getLastErrorMessage());
  }



  Serial.println("Waiting 1 second");
  delay(1000);
}

Im Code oben findest du zwei Änderungen, die wir uns jetzt näher anschauen. Zunächst streichst du den Tag SSID in der Setup-Funktion, da wir das nicht mehr brauchen. Übrig bleibt das device (also ESP8266) – hierüber können wir später im Data Explorer die Messdaten leichter finden.

// Add tags to the data point
sensor.addTag("device", DEVICE);

Außerdem müssen statt der Signalstärke unsere Messdaten für Temperatur, Luftfeuchtigkeit und -druck übertragen werden. Das geschieht im Loop mit folgendem Code:

  // Store measured value into point
  sensor.addField("temp", tempBMP180);
  sensor.addField("humidity", humidity);
  sensor.addField("pressure", pressure/100);

Hiermit werden die drei Felder (fields) mit den Namen temp, humidity und pressure in der Datenbank angelegt und mit den aktuellen Daten der Sensoren versorgt. Diese stammen aus unseren Variablen tempBMP180, humidity und pressure. Wie du siehst teilst du den Wert in der Variablen pressure noch durch 100, um statt Pascal (Pa) Hektopascal (hPa) zu erhalten.

Und das war auch schon alles. Achte darauf, dass deine korrekten WLAN-Zugangsdaten im Sketch eingetragen sind und lade ihn auf deinen ESP8266.

Die Daten im Data Explorer anzeigen

Als nächstes schaust du im Data Explorer nach, ob die Daten auch wie gewünscht deine Datenbank erreichen. Klicke hierfür links auf den entsprechenden Menüpunkt und erstelle deine Abfrage wie folgt:

Hier pickst du dir testweise die Temperatur über die Variable temp heraus. Wenn du deine Abfrage erstellt hast, klicke auf den Button Submit. Im oberen Bereich der Seite sollte nun ein Graph zu sehen sein – hier wieder ein Beispiel, das schon einige Zeit lief:

Wie du hier siehst begann die Aufzeichnung kurz nach 12 Uhr am 7. August 2023 mit Werten um die 24 °C. Gegen 12:20 Uhr brach die Verbindung zur Datenbank ab (die Wetterstation war aus) und begann wieder gegen 12:30 Uhr. Diese Zwischenzeit wird im Graphen als Verbindungslinie zwischen dem letzten erhaltenen und ersten wieder verfügbaren Datenpunkt dargestellt. Anschließend sank die Temperatur kontinuierlich von 25 °C auf circa 24,7 °C.

Hinweis: Auf der rechten Seite findest du den Punk Aggregate Function – hier kannst du einstellen, ob du von den Temperaturwerten den Durchschnitt (mean), den Median oder immer den letzten aktuellen Wert (last) sehen möchtest. Die letzte Option zeigt dir immer den aktuellen, tatsächlichen Wert an. Die beiden anderen glätten hingegen den Graphen, sodass Temperaturausschläge weniger ins Gewicht fallen.

Solange deine ESP8266 Wetterstation also läuft, werden Daten gesammelt. Auf diese kannst du entweder über das Dropdown (z.B. Past 1h) oder spezifisch über den Punkt Custom Time Range zugreifen. Es gibt allerdings noch eine weitere spannende Funktion: ein Dashboard.

Die Messdaten auf einem Dashboard anzeigen

Um die aktuelle Abfrage auf einem Dashboard zu speichern, das dir einen schnellen Überblick verschafft, klicke zunächst im Menü links auf den entsprechenden Button:

Klicke anschließend rechts auf den Button Create Dashboard und anschließend auf New Dashboard. Auf der folgenden Seite kannst du oben einen Namen für das Dashboard vergeben, zum Beispiel ESP8266 Wetterstation:

Anschließend kannst du über den Button Add Cell eine Kachel hinzufügen – zum Beispiel den gerade gesehenen Temperaturverlauf. Du landest nach deinem Klick wieder in der Ansicht des Data Explorers, nur das du diesmal hier die Abfrage für die neue Temperatur-Kachel erstellst.

Vergib zunächst wieder ganz oben einen Namen, zum Beispiel Temperaturverlauf. Anschließend wählst du im unteren Bereich wieder die Variable temp aus.

Alles, was jetzt noch fehlt ist ein Klick auf das Häkchen oben rechts – und schon landest du wieder auf deinem Dashboard, das nun den Temperaturverlauf enthält.

Im oberen Bereich findest du den Button Set Auto Refresh – hierüber kannst du einstellen, in welchem Intervall das Dashboard und damit auch der Graph aktualisiert werden soll.

Apropos Graph, du kannst auf dem Dashboard auch die aktuelle Temperatur anzeigen lassen. Klicke hierfür wieder auf Add Cell und erstelle die bekannte Abfrage erneut. Sobald du auf Submit geklickt hast, erscheint wieder dein Temperaturverlauf. Oben links findest du ein Dropdown-Menü, in dem du die Anzeige anpassen kannst. Wähle hier den Eintrag Gauge.

Nun siehst du nicht mehr den Verlauf, sondern die aktuelle Temperatur:

Vergib dieser Kachel wieder einen Namen und klicke abschließend auf das Häkchen rechts oben. Nun landest du wieder in deinem Dashboard mit den zwei Kacheln zur Temperatur.

Mit deinen beiden anderen Messwerten zu Luftdruck und -feuchtigkeit kannst du genauso verfahren. Noch ein letzter Hinweis zu den Kacheln: Über den Button Customize kannst du die Anzeige noch anpassen, was besonders beim Luftdruck sinnvoll ist. Stelle hier unter Thresholds einen Bereich von 900 bis 1100 ein, damit der Zeiger auch etwas zum Anzeigen hat. Ebenso kannst du dem Wert noch ein Suffix vergeben – hier also hPa.

Warnungen per Nachricht senden

Möchtest du Nachrichten an dich oder jemand anderes senden, wenn ein bestimmter Messwert über- oder unterschritten wird? Im folgenden Tutorial erfährst du, wie du mit dem ESP8266 oder ESP32 Nachrichten per Telegram, WhatsApp und E-Mail verschickst.

]]>
Countdown zum nächsten SpaceX Raketenstart https://polluxlabs.net/esp8266-projekte/countdown-zum-naechsten-spacex-raketenstart/ Sat, 20 Mar 2021 16:18:28 +0000 https://polluxlabs.net/?p=4485 Wichtiger Hinweis: Leider funktioniert dieses Projekt nicht mehr, da die hierfür benötigte API leider eingestellt wurde. Es ist trotzdem weiterhin online, denn vielleicht bringt es dich beim Durchlesen auf eine andere Idee. Falls du eine andere Möglichkeit kennst, den nächsten Start einer Rakete zu ermitteln, schreibe gerne an info@polluxlabs.net

Wenn du an einen Raketenstart denkst, kommt dir bestimmt vieles in den Sinn. Sicherlich auch der obligatorische Countdown. Wie wäre es, wenn du daheim die Sekunden zum nächsten Launch einer Rakete von SpaceX herunterzählst?

Elon Musks Weltraumunternehmen betreibt eine API, von der du unter anderem die Daten des nächsten Starts beziehen kannst. Hierfür benötigst du nur einen ESP8266. Den Countdown kannst du stilecht auf einem 7-Segment Display darstellen. Und für die Beleuchtung beim Lift-off eignet sich ein NeoPixel LED-Ring.

Fortgeschrittene

1 – 2 Stunden

ca. 15 €

Für dieses Projekt benötigst du (Mengen s. Beschreibung):

Noch ein Hinweis vorab: Genau auf die Sekunde wirst den Lift-off der Rakete leider nicht treffen. Das liegt weniger an deinem Projekt, sondern daran, dass die meisten Raketen nicht ganz pünktlich sind. 😉 Zumindest haben wir bei unseren Tests beobachtet, dass der tatsächliche Start von der Angabe in der API immer um ein paar Sekunden abweicht.

Grundlegende Tutorials

In diesem Projekt kommt einiges zum Einsatz, für das du auf Pollux Labs passende Tutorials findest:

Schaue zunächst in die oben genannten Tutorials, wenn du ein paar der Themen noch nicht kennst oder erfahren möchtest, wie du ein 7-Segment Display oder den NeoPixel LED-Ring anschließt.

Im Folgenden konzentrieren wir uns auf den Start der nächsten Rakete von SpaceX – bzw. auf die Daten, die du hierfür benötigst.

Die SpaceX API

SpaceX betreibt eine sehr große Datenbank, aus der du dich kostenlos bedienen darfst. Hier findest du beispielsweise Daten zu Raketen, Modulen und Startrampen – aber natürlich auch zu vergangenen und kommenden Raketenstarts. Du findest sogar die aktuelle Position des Tesla Roadsters, der in den Orbit geschossen wurde.

SpaceX Roadster im Orbit
Foto: SpaceX

All diese Daten kannst du über eine API abrufen und weiterverarbeiten – und zwar auch mit deinem ESP8266. Für dieses Projekt benötigst du also die Daten zum nächsten Raketenstart. Diese findest du unter folgender Adresse:

https://api.spacexdata.com/v4/launches/next

Wenn du diese URL in deinem Browser aufrufst, findest eine ganze Menge Daten im JSON-Format. Unter anderem auch den Zeitpunkt des nächsten SpaceX-Starts als Unixzeit. In diesem Fall der 24.3.2021 um 9:58 MEZ.

"date_unix":1616576280

Diese URL verwendest du in deinem Sketch, um mit Hilfe der aktuellen Uhrzeit die Dauer bis zum nächsten Start – und somit den Countdown – zu berechnen.

Die Daten abrufen

Du findest den gesamten Sketch am Ende des Projekts. An dieser Stelle steigen wir direkt beim Abruf der Daten von der SpaceX API ein.

Hierfür hinterlegst du zunächst zu Beginn des Sketchs die URL der API und den Port, der für die sichere Übertragung via HTTPS zuständig ist: 443.

const char* host = "https://api.spacexdata.com/v4/launches/next";
const int httpsPort = 443;

Als nächstes kommt der Loop. Hier erstellst du zunächst die Instanz http von HTTPClient und verbindest dich mit der API:

HTTPClient http;
client.connect(host, httpsPort);
http.begin(client, host);

Danach prüfst du, ob die Verbindung steht und speicherst die JSON-Daten, die du von der API erhältst, in der Variablen payload. Anschließend dekodierst du sie und „ziehst“ dir die oben genannte Zeitangabe als Unixtime. Diese Zeitangabe speicherst du in der Variablen launchTime.

if (http.GET() == HTTP_CODE_OK) {
  String payload = http.getString();
  DynamicJsonDocument doc(3072);
  deserializeJson(doc, payload);

  launchTime = doc["date_unix"]; //Startzeit speichern

Als nächstes berechnest du den Countdown. Hierfür benötigst du natürlich auch die aktuelle Uhrzeit. Diese hast du weiter oben im Sketch schon ermittelt und in der Variablen currentTime gespeichert (Schaue hierfür in den vollständigen Sketch).

countdown = launchTime - currentTime;

Den SpaceX Countdown anzeigen

Jetzt lässt du deinen Countdown laufen, bis die Startzeit erreicht wurde:

while (countdown != 0) {

Du zeigst zunächst die Sekunden, die es noch bis zum Start dauert, auf dem Display an. Hier kommt eine Funktion ins Spiel, die du im vollständigen Sketch findest:

drawDigits(countdown);

Danach ziehst du vom Wert in der Variablen countdown eine Sekunde ab und wartest mit deinem delay auch genau diese eine Sekunde, bevor du die nächste Zahl anzeigst.

countdown -= 1;
delay(1000);

Wenn der While-Loop ausgelaufen ist, die Variable countdown also eine Null enthält, löschst du das Display, startest dafür den LED-Ring und lässt ihn z.B. eine halbe Stunde angeschaltet.

lc.clearDisplay(0);

for (int i = 0; i < 12; i++) {
  pixels.setPixelColor(i, pixels.Color(255, 0, 0));
  pixels.show();
  delay(1);
}

delay(1800000); //30 Minuten

In diesem Beispiel leuchten alles LEDs auf dem Ring in einem satten Rot auf. Du kannst dir aber natürlich auch etwas raffinierteres überlegen!

Spacex Countdown ESP8266
Ein möglicher Aufbau für das Projekt

Und das war es auch schon! Zuletzt noch ein Hinweis: Wenn dein Delay am Ende des Sketchs ausgelaufen ist, folgt die nächste Abfrage der SpaceX API. Möglicherweise liegen dann aber noch keine neuen Daten vor. Bis das soweit ist, kann es mitunter einige Stunden oder auch Tage dauern – du könntest deinen ESP8266 also auch erst einmal gar keine neue Abfrage stellen lassen.

Sketch als .txt anschauen

#include <ESP8266WiFi.h>
#include <ArduinoJson.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecure.h>

WiFiClientSecure client;

//NTP
#include <NTPClient.h>
#include <WiFiUdp.h>
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP);

//SpaceX API URL und Port
const char* host = "https://api.spacexdata.com/v4/launches/next";
const int httpsPort = 443;

//WLAN Zugangsdaten
const char* ssid = "NETWORK";
const char* password =  "PASSWORD";

//Variablen
long launchTime;
long currentTime;
long countdown;

//7-Segment-Display
#include <LedControl.h>
LedControl lc = LedControl(12, 15, 13, 1); //12=D6=DIN, 15=D8=CLK, 13=D7=CS
const int NUM_DIGITS = 8;

//NeoPixel LED-Ring
#include <Adafruit_NeoPixel.h>

int leds = 12; //Anzahl der LEDs
int ledPin = 14; //14=D5, Pin, an dem der NeoPixel angeschlossen ist

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(leds, ledPin, NEO_GRB + NEO_KHZ800);

void getCurrentTime() {

  timeClient.update();
  currentTime = timeClient.getEpochTime();
  
  Serial.print("Current Time= ");
  Serial.println(currentTime);
}

void drawDigits(int num) {
  for (int i = 0; i < NUM_DIGITS ; i++) {
    lc.setDigit(0, i, num % 10, false);
    num /= 10;
    if (num == 0)
      return;
  }
}

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

  //Einstellungen des 7-Segment-Displays
  lc.shutdown(0, false);
  lc.setIntensity(0, 8);
  lc.clearDisplay(0);

  //Einstellungen des NeoPixels
  pinMode (ledPin, OUTPUT);
  pixels.begin();
  pixels.setBrightness(255); //Helligkeit: 0 (aus) - 255

  for (int i = 0; i < 12; i++) {
    pixels.setPixelColor(i, pixels.Color(0, 0, 0));
    pixels.show();
    delay(1);
  }

  WiFi.begin(ssid, password); //Mit dem WLAN verbinden

  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
  delay(1000);
  Serial.println("Hello, world!");
}

void loop() {
  if ((WiFi.status() == WL_CONNECTED)) {

    getCurrentTime();

    HTTPClient http;
    client.connect(host, httpsPort);
    http.begin(client, host);

    if (http.GET() == HTTP_CODE_OK) {
      String payload = http.getString();
      DynamicJsonDocument doc(3072);
      deserializeJson(doc, payload);

      launchTime = doc["date_unix"]; //Startzeit speichern
      Serial.print("Launch Time: ");
      Serial.println(launchTime);

      countdown = launchTime - currentTime; //Countdown = Startzeit - aktuelle Zeit
    } else {
      Serial.print("Error on HTTP request (SpaceX): ");
      Serial.println(http.GET()); //Error Code
    }
  }

  while (countdown != 0) { //Solange die Startzeit nicht erreicht wurde
    Serial.print("Time Until Launch: ");
    Serial.println(countdown);
    drawDigits(countdown);
    countdown -= 1;
    delay(1000);
  }

  Serial.println("LAUNCH!"); // Startzeit = aktuelle Zeit
  lc.clearDisplay(0);

  for (int i = 0; i < 12; i++) {
    pixels.setPixelColor(i, pixels.Color(255, 255, 0));
    pixels.show();
    delay(1);
  }

  delay(1800000);
}
]]>
Ein Babyphone mit künstlicher Intelligenz und einem Arduino Nano 33 BLE Sense https://polluxlabs.net/arduino-projekte/ein-babyphone-mit-kuenstlicher-intelligenz-und-einem-arduino-nano-33-ble-sense/ Mon, 18 Jan 2021 22:36:23 +0000 https://polluxlabs.net/?p=4884

Ein Babyphone ist eine praktische Sache, sollte das eigene Kleinkind mal außer Hörweite sein. Im Prinzip ist solch ein Gerät nichts anderes als ein Sender mit Mikrofon, der per Funk Geräusche an einen Empfänger überträgt. Weint oder schreit ein Baby aus dem Lautsprecher, erkennt jeder Mensch sofort, was hier los ist.

Aber kann eine künstliche Intelligenz auch lernen, Weinen und Schreien von anderen Geräuschen zu unterscheiden? Klar! Hier stellen wir ein Projekt vor, das mithilfe von Machine Learning und TinyML genau das macht. Alles was du dafür brauchst, ist ein Arduino Nano 33 BLE Sense und einen kostenlosen Account bei Edge Impulse.

Fortgeschrittene

3 – 4 Stunden

ca. 40€

Für dieses Projekt benötigst du:

Arduino® Board Nano 33 BLE Sense with headers
Arduino® Board Nano 33 BLE Sense with headers
Arduino Board Nano 33 BLE Sense with headers; Inhalt: 1 Stück

Das vorgestellte Projekt stammt vom Maker Ish Ot Jr. und wurde im Arduino Project Hub veröffentlicht. Du findest die Beschreibung (Englisch) hier. Im Folgenden beschäftigen wir uns mit den Voraussetzungen für das Babyphone mit KI, gehen auf wichtige Aspekte genauer ein und geben einen Ausblick auf weitere Anwendungen und Möglichkeiten.

Grundlagen

Das Babyphone mit dem Arduino Nano 33 BLE Sense funktioniert im Kern so: Zunächst sammelst du Geräusche von (d)einem Baby, die die Künstliche Intelligenz später erkennen soll. Damit sie diese von allen möglichen anderen Geräuschen unterscheiden kann, benötigst du auch Nebengeräusche, die im Kinderzimmer auftreten können – und zwar so viele Samples wie möglich, auch von der Stille eines träumenden Babys. 😉

Die Audiodaten kannst du direkt mit deinem Arduino sammeln. Wenn du noch nicht weißt, wie das geht, lese zunächst diese beiden Tutorials:

Wie du siehst, steht der Service von Edge Impulse im Mittelpunkt. Diesen kannst du kostenlos nutzen, um Daten zu sammeln, zu KI-Modellen zu verarbeiten und auf einem Microcontroller zu deployen.

Audio sammeln und ein Modell entwickeln

Wenn du nun weißt, wie du grundsätzlich Daten mit deinem Arduino Nano sammelst und in Edge Impulse speicherst, kannst du damit loslegen. Im Projekt im Arduino Project Hub legt der Autor zwei Labels an: Weinen (crying) und Geräusche (noise).

Wenn du für jedes deiner Labels mehrere Minuten Audio gesammelt hast, kann es weitergehen. In seinem Tutorial im Arduino Project Hub erläutert der Autor, welche Einstellungen er für sein Impulse Design verwendet. Damit erhält er eine Genauigkeit seines Modells von gut 86%.

Investiere in diesen Schritt des Projekts ruhig etwas Zeit und spiele mit den Einstellungen herum. Es ist wichtig, dass die einzelnen Labels (bzw. Features) gut von einander unterscheidbar sind, wie hier zu sehen. Diese Daten stammen aus einem anderem KI-Projekt, bei dem es um das Erkennen von Gesten geht.

KI-Modell in Edge Impulse
Beispiel für gut getrennte Features

Wenn du auf keinen grünen Zweig kommst, könnte eine Möglichkeit sein, einfach noch mehr Daten zu sammeln. Je mehr Material deine KI zum Lernen hat, desto besser. Achte auch darauf, dass du in Edge Impulse auch genügend Testdaten hast – diese dürfen sich nicht mit den Trainingsdaten überschneiden, um zuverlässige Ergebnisse zu erhalten.

So bringst du die KI auf deinen Arduino

Wenn du zufrieden bist mit deinem KI-Modell, wähle unter dem Menüpunkt Deployment die Kachel Arduino Library und lade dir die ZIP-Datei herunter. Hier enthalten sind eine Bibliothek und ein Sketch, den du im Folgenden etwas modifizierst.

Du könntest unter Deployment auch eine Firmware für deinen Arduino Nano 33 BLE Sense erstellen lassen. Dann könntest du die Auswertungen der KI jedoch nur im Seriellen Monitor verfolgen – etwas unpraktisch für ein Babyphone. Deshalb kommt der Sketch ins Spiel.

Im Tutorial im Project Hub geht der Autor sehr genau auf seine Anpassungen ein und erklärt, wie er die direkt am Arduino verbaute RGB LED verwendet, um darzustellen, ob ein Baby gerade weint oder nicht. Grün steht hier für „Alles in Ordnung“, also dafür, dass die Künstliche Intelligenz nur Noise erkennt. Rot hingegen für Weinen.

Wie geht es weiter?

Das vorgestellte Projekt zeigt sehr anschaulich, wie man mit einem einigermaßen günstigen Microcontroller und Edge Impulse relativ einfach ein KI-Projekt umsetzen kann. Du könntest das Modell noch verfeinern und nicht alles was nicht Weinen ist, einfach unter Noise zusammenfassen. So könnten weitere Labels die Ergebnisse noch verbessern.

Für diese detailliertere Auswertung ist die interne RGB LED vermutlich nicht ausreichend. Auf Pollux Labs findest du viele Tutorials zu Displays und auch zu einem NeoPixel LED Ring.

Wie sieht es mit anderen Geräuschen aus? Duscht gerade jemand? Oder schleudert deine Waschmaschine unruhig und könnte bald kaputtgehen? Das könnten alles Anwendungsfälle für dich sein. 🙂

]]>
Erhalte eine Nachricht aufs Smartphone, wenn die Post da ist https://polluxlabs.net/esp8266-projekte/erhalte-eine-nachricht-aufs-smartphone-wenn-die-post-da-ist/ Wed, 06 Jan 2021 12:20:49 +0000 https://polluxlabs.net/?p=4657 Gehst du ab und an vergeblich zum Briefkasten? Das ist jetzt vorbei! Dieses Projekt sendet dir eine Telegram-Nachricht aufs Smartphone, sobald der Briefträger die Post gebracht hat.

Hierfür verwenden wir einen Wemos D1 Mini und einen PIR-Bewegungsmelder. Du kannst aber natürlich auch einen anderen ESP8266 verwenden. Ebenso kannst du zum Beispiel ein Radarmodul verwenden, um die Bewegung im Briefkasten festzustellen.

Da wir im Briefkasten keine Steckdose haben, verwenden wir eine Powerbank für die Stromversorgung und versetzen den Microcontroller die meiste Zeit in Tiefschlaf. Eine Bedingung gibt es allerdings: Der D1 Mini bzw. ESP8266 muss sich mit deinem WLAN verbinden können – dieses sollte also bis zu deinem Briefkasten reichen.

Fortgeschrittene

1 – 2 Stunden

ca. 15€

Für dieses Projekt benötigst du (Mengen s. Beschreibung):

Alternativ kannst du auch einen NodeMCU ESP8266 und/oder ein Radarmodul verwenden:

Vorbereitungen

Ein paar Dinge musst du eventuell vorab erledigen – wie, erfährst du in diesen Tutorials: Richte dir zunächst einen Telegram-Bot ein, um Nachrichten vom D1 Mini auf deinem Smartphone erhalten zu können. Außerdem musst du deinen D1 Mini bzw. ESP8266 in deiner Arduino IDE verfügbar machen, um ihn von dort aus programmieren zu können.

Das Projekt aufbauen

Montiere zunächst alle Teile wie folgt auf deinem Breadboard.

Aufbau auf dem Breadboard
Aufbau auf dem Breadboard

Schauen wir zunächst auf die Verbindung der Pins D0 und RST: Hiermit kannst du deinen Microcontroller aufwecken, wenn von D0 ein Signal an RST gesendet wird. In diesem Projekt soll dieses Signal jedoch vom Bewegungssensor kommen. Damit es also nicht fälschlicherweise doch vom Pin D0 gesendet wird, verwenden wir eine Diode, die Strom von diesem Pin blockiert (achte beim Einbau auf die Richtung des weißen Rings). Falls du keine Diode zur Hand hast, sollte das Projekt behelfsweise auch mit einer einfachen Drahtbrücke funktionieren.

Noch ein Hinweis: Solange die beiden Pins miteinander verbunden sind, kannst du keinen Sketch hochladen. Hierfür musst du die Verbindung zunächst trennen.

Das Wecksignal für den D1 Mini

Du weckst deinen Microcontroller also über ein HIGH am Reset-Pin auf. Hierfür benötigst du allerdings einen eindeutigen Wechsel von LOW auf HIGH. Wenn du deinen PIR-Sensor direkt an den Reset-Pin anschließt, erhältst du kein korrektes Signal, was zu mehreren Resets hintereinander führen kann – oder einfach nicht funktioniert, wie in unseren Tests.

In Anlehnung an dieses Tutorial verbauen wir einen NPN-Transistor (BC547), der uns dabei hilft, ein eindeutiges Signal vom PIR-Sensor an den Reset-Pin des D1 Mini zu übertragen. Sollte das bei dir trotzdem zu ungewollten Neustarts (also Aufwachen) führen, wird im verlinkten Tutorial auch noch ein Aufbau mit einem Kondensator beschrieben, der dir weiterhelfen könnte.

Mehr über NPN-Transistoren erfährst du in diesem Artikel. In diesem Tutorial erfährst du mehr über den Bewegungssensor HC-SR501.

Der Sketch

Kommen wir also zum Code. Zunächst benötigst du ein paar Bibliotheken, die du zu Beginn des Sketchs einbindest. Falls noch nicht geschehen, installiere im Bibliotheksmanager die jeweils neueste Version.

#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>

Anschließend folgen einige Definitionen und Konstanten für deine Zugangsdaten zum WLAN und zu deinem Telegram Bot. Zur Erinnerung: Wie du die benötigten Daten für Telegram erhältst, erfährst du im oben genannten Tutorial.

//Deine WLAN-Zugangsdaten
const char* ssid = "NETWORK";
const char* password = "PASSWORD";

//Dein Bot-Token
#define botToken "BOT-TOKEN"  // den Bot-Token bekommst du vom Botfather)

//Deine User ID
#define userID "USER-ID"

Im letzten Schritt vor der Setup-Funktion erstellst du eine Instanz von WiFiClientSecure und initialisierst mit dieser die Verbindung zum Telegram-Bot.

WiFiClientSecure client;
UniversalTelegramBot bot(botToken, client);

Die Setup-Funktion

Im Sketch für dieses Projekt benötigst du nur die Setup-Funktion, die nach dem Start des Wemos D1 Mini abläuft. Hier startest du die Internet-Verbindung und sendest die Nachricht, dass die Post da ist, an dein Smartphone (also an deinen Telegram-Bot). Zuletzt legst du deinen Microcontroller wieder schlafen.

Sobald er ein neues Signal von deinem Bewegungsmelder erhält, wacht er wieder auf und durchläuft das Setup erneut. Wie du siehst, benötigst du also keinen Loop.

Zuerst konfigurierst du den client, den du oben erzeugt hast, startest den Seriellen Monitor und gibst dort die Nachricht aus, dass dein D1 Mini aufgeweckt wurde:

client.setInsecure();
Serial.begin(115200);
Serial.println("Awake");

Anschließend stellst du die Verbindung zu deinem WLAN-Netzwerk her:

Serial.print("Connecting to WiFi: ");
Serial.println(ssid);

WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {
  Serial.print(".");
  delay(500);
}

Serial.println("");
Serial.println("Connected!");

Zuletzt sendest du eine Nachricht an deinen Telegram-Bot mit der Funktion bot.sendMessage() – wartest 2 Sekunden und legst den D1 Mini wieder schlafen:

bot.sendMessage(userID, "Your mail has arrived!", "");

delay(2000);
Serial.println("Going to sleep");
ESP.deepSleep(0);

Für den Tiefschlaf verwendest du die Funktion ESP.deepSleep() mit dem Parameter 0. Diese Null sorgt dafür, dass dein Microcontroller ohne zeitliches Limit in den Schlaf versetzt wird – das heißt er wird erst wieder durch ein Signal vom Bewegungssensor aufgeweckt.

Und das war es auch schon – eigentlich ganz einfach. Hier nun der gesamte Sketch zum Rauskopieren.

//Libraries
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>

//WiFi Credentials
const char* ssid = "NETWORK";
const char* password = "PASSWORD";

//Telegram Bot Credentials
#define botToken "BOT-TOKEN"
#define userID "USER-ID"

WiFiClientSecure client;
UniversalTelegramBot bot(botToken, client);

void setup() {
  client.setInsecure();
  Serial.begin(115200);
  Serial.println("Awake");

  //Connecting to WiFi
  Serial.print("Connecting to WiFi: ");
  Serial.println(ssid);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }

  Serial.println("");
  Serial.println("Connected!");

  bot.sendMessage(userID, "Your mail has arrived!", "");

  delay(2000);
  Serial.println("Going to sleep");
  ESP.deepSleep(0);
}

void loop() {
}

Wie geht es weiter?

Du hast nun einen Post-Melder mit dem Bewegungssensor HC-SR501. Je nachdem, wie dein Briefkasten beschaffen ist, kann ein Radar-Modul die bessere Wahl sein.

Vielleicht fallen dir aber auch noch ganz andere Lösungen ein, um zu erkennen, dass dein Briefkasten geöffnet wurde. Nerdiger und ambitionierter wäre zum Beispiel eine Variante, die KI nutzt. Auf Pollux Labs kannst du die Grundlagen von Künstlicher Intelligenz auf Microcontrollern lernen.

]]>
Arduino Wettervorhersage https://polluxlabs.net/arduino-projekte/arduino-wettervorhersage/ Wed, 02 Dec 2020 10:34:52 +0000 https://polluxlabs.net/?p=4363 Eine Wetterstation mit aktuellen Werten ist eine Sache – aber zu wissen, wie das Wetter in einigen Stunden sein wird, eine ganz andere. In diesem Projekt baust du dir so eine einfache Wettervorhersage mit dem Luftdrucksensor BMP180 und einem Servo.

Zugegeben, mit den Meteorologen in den Nachrichten kann dieses Projekt nicht mithalten, aber möglicherweise kann es dich vor den ganz großen Wetterumschwüngen warnen.

Anfänger

1 – 2 Stunden

ca. 10 € + Arduino

Für dieses Projekt benötigst du (Mengen s. Beschreibung):

So funktioniert die Wettervorhersage

Wie eingangs erwähnt, ist eine Wettervorhersage eine komplexe Angelegenheit, an der Spezialisten mit Großcomputern arbeiten. Es gibt jedoch einen Zusammenhang, den wir uns in diesem Projekt zunutze machen: Steigt der Luftdruck, bessert sich das Wetter – fällt er, wird das Wetter schlecht.

Auch das ist eine starke Vereinfachung der Realität sein, aber dieser Zusammenhang soll uns hier einmal genügen. Immerhin wird dieses Prinzip schon seit Jahrhunderten angewandt. Sicherlich kennst du alte Barometer wie dieses hier:

Dosen-Barometer, Quelle: Wikipedia/Langspeed

Mehr über Barometer und die möglichen Anwendungen erfährst du auf Wikipedia. In unserer Arduino Wettervorhersage messen wir in regelmäßigen Abständen den Luftdruck und vergleichen ihn mit dem zuletzt gemessenen Wert. Fällt der Luftdruck immer weiter, dreht ein Servo-Motor einen Zeiger nach links in Richtung schlechtes Wetter. Wenn der Luftdruck kontinuierlich steigt, bewegt sich der Zeig nach rechts – es wird also gutes Wetter geben. Übrigens: Mit unserer ESP8266 Wetterstation speicherst du Daten zum Luftdruck lokal in einer Datenbank.

Luftdruck messen mit dem BMP180

Für die Messung des Luftdrucks verwenden wir den Sensor BMP180. Du kannst auch den genaueren BMP280* oder einen BME280* verwenden. Letzterer misst auch die Luftfeuchte, die wir in diesem Projekt jedoch nicht brauchen.

Diese Sensoren lassen sich leicht per I²C anschließen und mit einer passenden Bibliothek komfortabel verwenden. In diesem Tutorial erfährst du mehr darüber, wie du den BMP180 anschließt und verwendest.

Das Wetter mit einem Servo anzeigen

Es gibt viele denkbare Möglichkeiten, um die Veränderungen des Luftdrucks anzuzeigen: Verschiedene Displays oder auch ein NeoPixel LED-Ring. Hier verwenden wir jedoch einen Servo-Motor und eine passende Schablone. Diese können z.B. Kinder farbig gestalten und sie hat ebenso den gewissen Retro-Charme. 🙂

Schablone für den Servo der Wettervorhersage
Schablone für den Servo

Was ein Servo-Motor ist, wie funktioniert und was es alles zu beachten gibt, erfährst in diesem Tutorial zu Servos.

Der Aufbau der Arduino Wettervorhersage

Es dauert nur wenige Minuten, die Wettervorhersage auf deinem Breadboard aufzubauen. Orientiere dich hierbei an diesem Schema:

Aufbau der Arduino Wettervorhersage

Achte darauf, dass du den Servo mit 5V und den BMP180 mit 3,3V versorgst. Die Beschriftung der Pins des BMP180 befindet sich auf seiner Unterseite – vergewissere dich, dass du sie richtig am Arduino angeschlossen hast, bevor du ihn mit Strom versorgst.

Der Sketch

Es sind nicht viele Zeilen Code nötig, um die Wettervorhersage zum Laufen zu bringen. Starte wie so oft, indem du die nötigen Bibliotheken einbindest. Wie du sie installierst, erfährst du in den oben genannten Tutorials.

#include <Wire.h>
#include <Adafruit_BMP085.h>
#include <Servo.h>

Anschließend erstellst du zwei Objekte – eines für den Luftdruck-Sensor und eines für den Servo:

Adafruit_BMP085 bmp;
Servo myServo;

Dazu benötigst du noch ein paar Variablen, um die Werte des Sensors und die gewünschte Position des Servos zu speichern:

int servoPosition;
long currentPressure;
long oldPressure;
int delta;

Die Setup-Funktion

Hier startest du den Seriellen Monitor und vergewisserst dich, dass der BMP180 richtig angeschlossen und funktionstüchtig ist. Anschließend weist du dem Servo den Anschlusspin 8 zu und drehst den Zeiger nach oben – auf 90°.

void setup() {
  Serial.begin(115200);
  if (!bmp.begin()) {
    Serial.println("Sensor not found!");
    while (1) {}
  }

  myServo.attach(8);
  myServo.write(90);
}

Übrigens: Wenn du nicht weißt, in welcher Position sich dein Servo gerade befindet und in welche Position der Zeiger bei der Montage schauen soll, dann bringe ihn erst nach dem Start an. Zu Beginn des Sketchs steht der Servo auf 90° – sodass der Zeiger nach oben gerichtet ist.

Der Loop der Arduino Wettervorhersage

Hier misst du als erstes den aktuellen Luftdruck und gibst ihn im Seriellen Monitor aus:

  currentPressure = bmp.readPressure();
  Serial.print("Current Pressure = ");
  Serial.print(currentPressure);
  Serial.println(" Pa");

Wie du siehst, geht das ganz einfach mit der Funktion bmp.readPressure(). Gleichzeitig speicherst du diesen Wert – der in Pascal ausgegeben wird – in der Variablen currentPressure.

Danach fragst du ab, ob sich der aktuelle Luftdruck gegenüber der Messung davor (gespeichert in der Variablen oldPressure) verändert hat. Falls ja, speicherst du diese Veränderung in der Variablen delta.

 if (oldPressure && currentPressure != oldPressure) {
   delta = currentPressure - oldPressure;

Im If-Statement siehst du die Bedingung oldPressure && – diese befindet sich hier, da es im ersten Messdurchgang noch keine alte Messung gibt. Erst wenn in dieser Variablen eine Zahl hinterlegt ist – sie also nicht mehr auf 0 bzw. false steht – wird diese Bedingung wahr. Alternativ kannst du auch folgendes schreiben:

 if (oldPressure == true && currentPressure != oldPressure) {

Den Servo steuern

Kommen wir zum Servo und der Anzeige der Wetterlage. Um die Veränderung des Luftdrucks in der Variablen delta anzuzeigen, musst du dem Servo mitteilen, wo er sich hindrehen soll. Ein Servo kann eine Position zwischen 0° und 180° einnehmen – zu Beginn des Sketchs steht er in der Mitte, also auf 90°.

Wir nehmen mal an, dass die maximale Veränderung in unserem Messzeitraum (mehr dazu gleich) bei +- 100 Pa liegt. Bei -100 Pa soll der Servo auf 0° fahren, bei +100 Pa entsprechend auf 180°. Hinweis: Möglicherweise liegen wir mit dieser Einschätzung falsch – hier sind also deine eigenen Experimente gefragt. Verfolge im Seriellen Monitor die Messwerte und kalibriere deine Wetterstation entsprechend.

Jedenfalls musst du die beiden Wertebereiche +-100 Pa und 0-180° unter einen Hut bringen. Hierfür bietet sich die Funktion map() an:

servoPosition = map(delta, -100, 100, 0, 180);

Hier nimmst du die aktuelle Veränderung delta, ihren möglichen Wertebereich +-100 und „mappst“ diesen Wert auf die möglichen Winkel des Servos: 0° bis 180°. Heraus kommt der Winkel, der der Veränderung des Luftdrucks entspricht. Diesen speicherst du in der Variablen servoPosition.

Anschließend steuerst du deinen Servo auf diese Position:

myServo.write(servoPosition);

Danach machst du die aktuelle Messung zur alten Messung oldPressure, mit der du die nächste vergleichst. Und als letztes wartest du eine gewisse Zeit bis zur Messung, in diesem Fall 5 Minuten bzw. 300.000 Millisekunden.

oldPressure = currentPressure;
delay(300000);

Hier nun der gesamte Sketch der Arduino Wettervorhersage zum Rauskopieren und Hochladen. Viel Spaß! 🙂

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

#include <Servo.h>

Adafruit_BMP085 bmp;
Servo myServo;

int servoPosition;
long currentPressure;
long oldPressure;
int delta;

void setup() {
  Serial.begin(115200);
  if (!bmp.begin()) {
    Serial.println("Sensor not found!");
    while (1) {}
  }

  myServo.attach(8);
  myServo.write(90);
}

void loop() {
  currentPressure = bmp.readPressure();
  Serial.print("Current Pressure = ");
  Serial.print(currentPressure);
  Serial.println(" Pa");

  if (oldPressure && currentPressure != oldPressure) {
    delta = currentPressure - oldPressure;
    Serial.print("Change: ");
    Serial.print(delta);
    Serial.println();
  }

  servoPosition = map(delta, -100, 100, 0, 180);
  myServo.write(servoPosition);
  
  oldPressure = currentPressure;

  delay(300000);
}

Fehlen dir noch Bauteile? Dann wirf einen Blick in unsere Übersicht der besten Arduino Starter Kits.

]]>
Gute oder schlechte Nachrichten? Mit einer Sentimentanalyse findest du es heraus. https://polluxlabs.net/esp8266-projekte/pruefe-die-nachrichten-mit-einer-sentimentanalyse/ Mon, 23 Nov 2020 17:12:21 +0000 https://polluxlabs.net/?p=4243 In diesem Projekt lädst du dir mit einem ESP32 aktuellen Schlagzeilen aus dem Internet und findest mit einer Sentimentanalyse heraus, ob sie positiv oder negativ sind. Ein NeoPixel LED-Ring zeigt dir dann an, ob die Nachrichtenlage gerade gut (grün), schlecht (rot) oder etwas dazwischen ist.

Fortgeschrittene

1 – 2 Stunden

ca. 12 €

Für dieses Projekt benötigst du (Mengen s. Beschreibung):

Die Basics

Vieles, was in diesem Projekt zum Einsatz kommt, haben wir bereits in Tutorials beschreiben. Wenn du etwas davon noch nicht kennst, wirf zunächst dort einen Blick hinein:

Im Folgenden konzentrieren wir uns darauf, mit einer Sentimentsanalyse herauszufinden, ob eine Nachricht gut oder schlecht ist – und das dann mit dem NeoPixel anzuzeigen.

Was ist eine Sentimentanalyse?

Für einen Menschen ist es normalerweise kein Problem zu beurteilen, ob die Stimmung (Sentiment) einer Aussage positiv, negativ oder irgendetwas dazwischen ist. Für einen Computer ist das etwas schwieriger, aber trotzdem möglich – mit den richtigen Algorithmen und Wörterbüchern.

Hierfür liest der Computer einen Text ein und beurteilt die Wörter und Wortzusammenhänge, die er dort vorfindet. Im Hintergrund steckt immer ein Wörterbuch, in dem Begriffe von Menschen bewertet wurden: Pech und Trauer sind negative Begriffe, Glück und Positiv hingegen – positive.

Aber so einfach ist es leider nicht: Oft steckt die Bedeutung einer Aussage nicht in einem einzelnen Wort, sondern in einer Wendung, die aus mehreren Wörtern besteht. Positiv mag zwar in den meisten Fällen etwas Gutes sein, wenn jedoch jemand auf ein Virus positiv getestet wurde, ist genau das Gegenteil der Fall.

Mehr über dieses spannende Thema findest du auf Wikipedia.

Der Aufbau des Projekts

Du musst im Prinzip nur deinen NeoPixel mit deinem ESP32 wie folgt verbinden:

NeoPixelESP32
VCC3v3
GNDGND
DI14

Natürlich kannst du hierfür einfach ein Breadboard verwenden. Etwas mehr Stimmung verleihst du mit einer Kugel und einem Sockel aus dem 3D-Drucker. Passende Plastikkugeln* findest du für wenig Geld im Bastelladen oder auf Amazon.

Die Schlagzeilen laden

Für dieses Projekt lädst du dir zunächst die aktuellen Schlagzeilen von einer kostenlosen API. Hierfür bietet sich der Service newsapi.org an. Erstelle dir dort zunächst einen kostenlosen Account, um deinen API Key zu erhalten, den du für die Abfrage der Nachrichten benötigst.

Danach kannst du z.B. länderspezifische Top Headlines abfragen, die dir als JSON zur Verfügung gestellt werden. Du kannst die Nachrichten jedoch auch nach Ländern, Suchbegriffen und Quellen filtern. Da die Sentimentanalyse, die wir später verwenden, kein Deutsch versteht, verwenden wir hier die Top Headlines der USA.

Die URL für den API Call lautet dann wie folgt – wobei du <<API-KEY>> durch deinen eigenen API Key ersetzen musst.

http://newsapi.org/v2/top-headlines?country=us&apiKey=<<API-KEY>>

Mit dieser Abfrage erhält dein ESP32 Daten im JSON-Format, die er – wie im oben genannten Tutorial beschrieben – mit der Bibliothek ArduinoJson verarbeiten und der Sentimentanalyse weitergeben kann.

Übrigens: Wenn du die aktuellen Nachrichten zum Corona-Virus analysieren möchtest, verwende die folgende URL (Covid ist in englischen Medien gängiger als Corona).

http://newsapi.org/v2/everything?q=covid&apiKey=<<API-KEY>>

Die Stimmung der Schlagzeilen bestimmen

Hier kommt eine weitere kostenlose API ins Spiel: meaningcloud.com – auch hier benötigst du einen kostenlosen Developer Account, der dir eine begrenzte (aber hier ausreichende) Anzahl von Abfragen zur Verfügung stellt. Für diesen Service benötigst du ebenfalls einen API Key, den du in der Abfrage-URL hinterlegen musst.

Diese URL sieht folgendermaßen aus:

http://api.meaningcloud.com/sentiment-2.1?key=<<API-KEY>>&of=json&txt=<<TEXT>>&lang=en

Wie du siehst, übergibst du die Schlagzeilen von newsapi.org in der URL an die API. Diese steckt in der Variable article, allerdings mit Leerzeichen zwischen den Wörtern, was in einer URL natürlich nicht funktioniert. Deshalb ersetzt du diese Leerzeichen wie folgt durch ein %20:

article.replace(" ", "%20");

Nun kannst du die URL für die Sentimentanalyse flexibel zusammenbauen:

http://api.meaningcloud.com/sentiment-2.1?key=<<API-KEY>>&of=json&txt=" + article + "&lang=en

Im Sketch passiert das in einem For-Loop insgesamt 15 Mal – für die 15 neuesten Schlagzeilen.

Die Ergebnisse der Sentimentanalyse

Jetzt wird es spannend: Die API spielt eine ganze Menge Daten im JSON-Format zurück, in denen uns hier aber nur die Stimmung der Schlagzeile interessiert. Hierfür gibt es fünf Werte:

Sehr positivP+
PositivP
NeutralNEU
NegativN
Sehr negativN+

Darüberhinaus gibt es noch den Wert NONE – falls keine Stimmung ermittelt werden konnte. Jedem dieser Werte ordnest du im Sketch eine Zahl zu: von 2 für P+ bis zu -2 für N+. Hier das Beispiel für eine sehr positive Schlagzeile:

if (score_tag == "P+") {
  sentiment += 2;
}

Diese Zahlen addierst du miteinander und erhältst dadurch einen Wert – den aktuellen Zustand der Welt. 😉

Den Gesamtwert als Farbe ausgeben

Theoretisch könnte sich der Gesamtwert der 15 Schlagzeilen zwischen -30 und +30 bewegen. In den meisten Fällen reicht jedoch eine Skala von -5 bis +5.

Diesen 11 Werten innerhalb der Skala ordnest du Farben zu, die du als RGB-Werte anlegst – und lässt deinen NeoPixel LED-Ring dann in der Farbe des Gesamtwerts leuchten. Wir haben uns für ein Farbspektrum von Rot zu Grün entschieden – mit Gelb in der Mitte für den neutralen Wert Null:

int minus5[] = {255, 0, 0};
int minus4[] = {255, 51, 0};
int minus3[] = {255, 102, 0};
int minus2[] = {255, 153, 0};
int minus1[] = {255, 204, 0};
int neutral[] = {255, 255, 0};
int plus1[] = {204, 255, 0};
int plus2[] = {153, 255, 0};
int plus3[] = {102, 255, 0};
int plus4[] = {51, 255, 0};
int plus5[] = {0, 255, 0};

Wenn du selbst die Farben bestimmten möchtest, eignet sich hierfür der Color Picker von Google.

Im Sketch fehlt nun nur noch etwas Code, um entsprechend des Gesamtwerts der Sentimentanalyse eine Farbe auszugeben. Für den Wert -4 zum Beispiel:

switch (sentiment) {
  case -4:
    for (int i = 0; i < 12; i++) {
      pixels.setPixelColor(i, minus4[0], minus4[1], minus4[2]);
      pixels.show();
    }
    break;

Alle Werte von -5 und darunter (sowie +5 und darüber) fängst du davor schon mit einer If-Abfrage ab:

if (sentiment <= -5) {
  for (int i = 0; i < 12; i++) {
    pixels.setPixelColor(i, minus5[0], minus5[1], minus5[2]);
    pixels.show();
  }

Ganz am Ende des Sketchs fügst du noch einen Delay ein, der bestimmt, wann die nächste Abfrage der Schlagzeilen und die Sentimentanalyse erfolgen soll – z.B. alle 30 Minuten:

delay(1800000);

Hier nun der gesamte Sketch:

Sketch als .txt anschauen

//Libraries
#include <ArduinoJson.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <Adafruit_NeoPixel.h>

//Pin for the NeoPixel
int ledPin = 14;

//Initiate NeoPixel
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(12, ledPin, NEO_GRB + NEO_KHZ800);

//Colors
int minus5[] = {255, 0, 0};
int minus4[] = {255, 51, 0};
int minus3[] = {255, 102, 0};
int minus2[] = {255, 153, 0};
int minus1[] = {255, 204, 0};
int neutral[] = {255, 255, 0};
int plus1[] = {204, 255, 0};
int plus2[] = {153, 255, 0};
int plus3[] = {102, 255, 0};
int plus4[] = {51, 255, 0};
int plus5[] = {0, 255, 0};


// WiFi Credentials
const char* ssid = "WiFi SSID";
const char* password =  "PASSWORD";

String article;
int sentiment = 0;

void getSentiment() {

  HTTPClient http;

  http.begin("http://api.meaningcloud.com/sentiment-2.1?key=<<API-KEY>>&of=json&txt=" + article + "&lang=en");

  int httpCode = http.GET();

  if (httpCode == 200) {

    String payload = http.getString();

    const size_t capacity = 2*JSON_ARRAY_SIZE(0) + 7*JSON_ARRAY_SIZE(1) + 5*JSON_ARRAY_SIZE(2) + 2*JSON_ARRAY_SIZE(3) + JSON_ARRAY_SIZE(4) + 2*JSON_ARRAY_SIZE(5) + 2*JSON_ARRAY_SIZE(6) + JSON_ARRAY_SIZE(7) + 25*JSON_OBJECT_SIZE(4) + 4*JSON_OBJECT_SIZE(6) + 22*JSON_OBJECT_SIZE(7) + JSON_OBJECT_SIZE(8) + 2*JSON_OBJECT_SIZE(9) + 3*JSON_OBJECT_SIZE(10) + JSON_OBJECT_SIZE(11) + 3500;

    DynamicJsonDocument doc(capacity);

    DeserializationError error = deserializeJson(doc, payload, DeserializationOption::NestingLimit(11));

    if (error) {
      Serial.print(F("deserializeJson() Error: "));
      Serial.println(error.c_str());
      return;
    }

    JsonObject status = doc["status"];
    String score_tag = doc["score_tag"];

    Serial.println(score_tag);

    if (score_tag == "P+") {
      sentiment += 2;
    }
    else if (score_tag == "P") {
      sentiment += 1;
    }
    else if (score_tag == "N") {
      sentiment -= 1;
    }
    else if (score_tag == "N+") {
      sentiment -= 2;
    }
  }

  else {
    Serial.println("Bad HTTP Request");
  }
  http.end();
}

void setup() {
  pixels.begin();
  pixels.setBrightness(175);

  pinMode (14, OUTPUT); //LED Pin


  Serial.begin(115200);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting...");
  }
  Serial.println("Connected!");
}

void () {

  //reset sentiment count
  sentiment = 0;

  if ((WiFi.status() == WL_CONNECTED)) {

    HTTPClient http;

    http.begin("http://newsapi.org/v2/top-headlines?country=us&apiKey=<<API-KEY>>");

    int httpCode = http.GET();

    if (httpCode == 200) {

      String payload = http.getString();

      const size_t capacity = JSON_ARRAY_SIZE(20) + 20 * JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(3) + 20 * JSON_OBJECT_SIZE(8) + 16600;
      DynamicJsonDocument doc(capacity);

      DeserializationError error = deserializeJson(doc, payload);

      if (error) {
        Serial.print(F("deserializeJson() Error: "));
        Serial.println(error.c_str());
        return;
      }

      //Save number of headlines
      int noHeadlines = doc["totalResults"];

      //Loop through headlines & get sentiment
      for (int i = 0; i < 15; i++) {

        //Get Headlines from Newsapi
        JsonArray articles = doc["articles"];

        JsonObject articles_number = articles[i];

        const char* articles_number_title = articles_number["title"];

        article = String(articles_number_title);
        Serial.println(article);
        
        article.replace(" ", "%20");
        Serial.println(article);

        getSentiment();
      }
      
      Serial.println("Loop done.");
      Serial.println(sentiment);

      //Show state on NeoPixel
      if (sentiment <= -5) {
        for (int i = 0; i < 12; i++) {
          pixels.setPixelColor(i, minus5[0], minus5[1], minus5[2]);
          pixels.show();
        }
      } else if (sentiment >= 5) {
        for (int i = 0; i < 12; i++) {
          pixels.setPixelColor(i, plus5[0], plus5[1], plus5[2]);
          pixels.show();
        }
      }

      switch (sentiment) {
        case -4:
          for (int i = 0; i < 12; i++) {
            pixels.setPixelColor(i, minus4[0], minus4[1], minus4[2]);
            pixels.show();
          }
          break;
        case -3:
          for (int i = 0; i < 12; i++) {
            pixels.setPixelColor(i, minus3[0], minus3[1], minus3[2]);
            pixels.show();
          }
          break;
        case -2:
          for (int i = 0; i < 12; i++) {
            pixels.setPixelColor(i, minus2[0], minus2[1], minus2[2]);
            pixels.show();
          }
          break;
        case -1:
          for (int i = 0; i < 12; i++) {
            pixels.setPixelColor(i, minus1[0], minus1[1], minus1[2]);
            pixels.show();
          }
          break;
        case 0:
          for (int i = 0; i < 12; i++) {
            pixels.setPixelColor(i, neutral[0], neutral[1], neutral[2]);
            pixels.show();
          }
          break;
        case 1:
          for (int i = 0; i < 12; i++) {
            pixels.setPixelColor(i, plus1[0], plus1[1], plus1[2]);
            pixels.show();
          }
          break;
        case 2:
          for (int i = 0; i < 12; i++) {
            pixels.setPixelColor(i, plus2[0], plus2[1], plus2[2]);
            pixels.show();
          }
          break;
        case 3:
          for (int i = 0; i < 12; i++) {
            pixels.setPixelColor(i, plus3[0], plus3[1], plus3[2]);
            pixels.show();
          }
          break;
        case 4:
          for (int i = 0; i < 12; i++) {
            pixels.setPixelColor(i, plus4[0], plus4[1], plus4[2]);
            pixels.show();
          }
          break;
      }
    }

    else {
      Serial.println("Bad HTTP Request");
    }

    http.end();
    Serial.println("Connection closed.");
  }
  //Wait for 30 minutes until next call
  delay(1800000);
}

Wie geht es weiter?

Hast du Lust auf mehr Daten und mehr Beleuchtung? In diesem Projekt baust du einen Würfel, der beginnt zu leuchten, wenn die International Space Station über ihm fliegt.

]]>
Beleuchte deine LEGO Mondlandefähre, wenn der Mond am Himmel steht https://polluxlabs.net/esp8266-projekte/beleuchte-deine-lego-mondlandefaehre-wenn-der-mond-am-himmel-steht/ Wed, 18 Nov 2020 14:13:52 +0000 https://polluxlabs.net/?p=4188

Du hast eine LEGO Mondlandefähre (Apollo 11 Lunar Lander), aber dir fehlt noch die richtige Beleuchtung? In diesem Projekt verbaust du unter der Landefähre einen LED-Ring, den du mit einem ESP8266 anschaltest, sobald der Mond aufgeht – und wieder ausschaltest, nachdem der Mond untergegangen ist.

Anfänger

1 – 2 Stunden

ca. 15 € + LEGO Mondlandefähre

Für dieses Projekt benötigst du (Mengen s. Beschreibung):

In diesem Projekt lernst du, wie du einen NeoPixel LED-Ring verwendest, die aktuelle Uhrzeit per WLAN abrufst und, am wichtigsten: herausfindest, ob der Mond bereits aufgegangen ist oder nicht.

lego lunar lander cover

Der Aufbau

Du benötigst nur wenige Bauteile, die du schnell miteinander verbunden hast. Die NeoPixel LED-Ringe werden meistens unverlötet geliefert, d.h. du musst zunächst drei Kabel an die Pins GND, VCC und DI löten.

Lege nun den Ring in die runde Aussparung (Krater?) der Bodenplatte. Diese eignet sich perfekt, um den NeoPixel unterzubringen. Wenn du vom Rand der Aussparung ein Teil entfernst, kannst du die drei Kabel hier unauffällig durchführen:

Verbinde deinen NeoPixel anschließend mit deinem ESP8266 wie folgt:

NeoPixelESP8266
GNDGND
VCC3v3
DID5

Der Sketch für die Beleuchtung der Mondlandefähre

Viele der Themen, die im folgenden Sketch wichtig sind, haben wir bereits in anderen Tutorials behandelt:

Falls du hiermit noch nicht vertraut bist, wirf dort zunächst einen Blick rein.

Steht der Mond am Himmel?

Schauen wir uns das Kernstück des Sketchs an: Um herauszufinden, ob der Mond bereits aufgegangen ist, verwendest du die Bibliothek MoonRise.h. Diese findest du wie du es von anderen Bibliotheken gewohnt bist, im Bibliotheksmanager.

Der große Vorteil dieser Bibliothek ist es, dass sie anhand deiner Koordinaten und des Zeitunterschieds zur Koordinierten Weltzeit (UTC) die Monddaten auf deinem ESP8266 berechnet. Du benötigst hier also keine API-Abfragen oder ähnliches.

Binde also zunächst die Bibliothek MoonRise.h am Anfang deines Sketchs ein und hinterlege deine Koordinaten sowie den Zeitunterschied zur UTC in Stunden. Hier das Beispiel für Karlsruhe in der Winterzeit:

#include <MoonRise.h>
const float latitude = 49.00;
const float longitude = 8.40;
const int offset = 1;

Anschließend benötigst du zwei Variablen: Eine für die aktuelle Uhrzeit und eine für den Zustand Mond sichtbar/nicht sichtbar.

long currentTime;
bool moonVisible;

Danach erstellst du das Objekt mr der Bibliothek MoonRise:

MoonRise mr;

Das war es auch schon mit den Vorbereitungen. Fehlen nur noch zwei Zeilen Code, um herauszufinden, ob der Mond am Himmel steht.

 mr.calculate(latitude, longitude, currentTime + offset);
 moonVisible = mr.isVisible;

Mit der Funktion mr.calculate berechnest du anhand deiner Koordinaten, der aktuellen Uhrzeit und des Zeitunterschieds den Stand des Monds. Ob der Mond von deinem Standort aus sichtbar ist, erfährst du mit der Funktion mr.isVisible. Das Ergebnis – entweder 1 oder 0 – speicherst du in der Variablen moonVisible.

Übrigens: Die Bibliothek hat noch weitere Funktionen. Mehr darüber erfährst du auf Github.

Schalte den NeoPixel ein

Solange in der Variablen moonVisible eine 0 gespeichert ist – der Mond also noch nicht sichtbar ist – bleibt der LED-Ring ausgeschaltet. Sobald dort jedoch eine 1 seht, beginnt er, deine LEGO Mondlandefähre zu beleuchten. Das machst du mit folgendem Code:

if (moonVisible) {
  for (int j = 0; j < 12; j++) {
    pixels.setPixelColor(j, pixels.Color(237, 235, 28));
    pixels.show();
  }
} else {
  for (int j = 0; j < 12; j++) {
    pixels.setPixelColor(j, LOW);
    pixels.show();
  }
}

Wie du siehst, schaltest du in einem For-Loop alle 12 LEDs hintereinander ein bzw. aus. In der Funktion pixels.Color() kannst du die Farbe des Lichts bestimmen – in unserem Fall ein warmes Weiß. Du kannst alle möglichen anderen Farben wählen, indem du z.B. den Color Picker von Google verwendest.

Zuletzt legst du noch einen Delay fest, der bestimmt, in welchen Abständen du berechnest, ob der Mond am Himmel sichtbar ist. Im folgendem Fall alle 100 Sekunden:

delay(100000);

Hier nun der vollständige Sketch:

Sketch als .txt anschauen

/*********
  Frederik Kumbartzki
  Complete project details at https://polluxlabs.net
*********/

//WI-FI Credentials
const char* ssid = "YOUR WI-FI NETWORK";
const char* password =  "YOUR PASSWORD";

//Coordinates
const float latitude = 00.00; //Your Latitude
const float longitude = 00.00; //Your Longitude
const int offset = 0; //Your offset to UTC in hours

#include <MoonRise.h> //MoonRise Library
#include <ESP8266WiFi.h> //Wi-Fi

//Get time
#include <NTPClient.h>
#include <WiFiUdp.h>

WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org");

#include <Adafruit_NeoPixel.h> //NeoPixel

//Times and Durations
long currentTime;
bool moonVisible;

//Moonrise Library
MoonRise mr;

//Initiate NeoPixel
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(12, 14, NEO_GRB + NEO_KHZ800); //14 = Pin D5 on the board

void setup() {

  pixels.begin();
  pixels.setBrightness(250); //NeoPixel Brightness: 0-255

  Serial.begin(115200); //Start Serial Monitor
  WiFi.begin(ssid, password); //Go online

  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting...");
  }

  delay(1000);
  Serial.println("Hello, world!");

  timeClient.begin();
}

void loop() {

  timeClient.update();

  currentTime = timeClient.getEpochTime();

  Serial.print(currentTime);
  Serial.print(" - ");

  Serial.print("moon visible: ");
  Serial.println(moonVisible);

  mr.calculate(latitude, longitude, currentTime + offset); //Calculate Moonrise
  moonVisible = mr.isVisible; //1 if Moon is visible, 0 if not

  if (moonVisible) {
    for (int j = 0; j < 12; j++) { //Light up all 12 LEDs on NeoPixel
      pixels.setPixelColor(j, pixels.Color(237, 235, 28)); //Set NeoPixel color in RGB
      pixels.show();
    }
  } else {
    //Turn off NeoPixel if Moon isn't visible
    for (int j = 0; j < 12; j++) {
      pixels.setPixelColor(j, LOW);
      pixels.show();
    }
  }
  delay(100000); //Wait until next query
}

Wie geht es weiter?

LEGO beleuchten ist das Richtige für dich? Dann probiere etwas ähnliches mit der LEGO ISS, die anfängt zu leuchten, wenn die echte ISS über ihr fliegt.

]]>
Das Licht ein- und ausschalten mit Telegram https://polluxlabs.net/esp8266-projekte/das-licht-ein-und-ausschalten-mit-telegram/ Wed, 11 Nov 2020 07:11:02 +0000 https://polluxlabs.net/?p=4091 In diesem Projekt lernst du, wie du von deinem Smartphone aus eine Lichtquelle ein- und ausschalten kannst. Außerdem kannst du von dort aus abfragen, ob das Licht gerade an oder aus ist.

Für einen ersten Test verwendest du eine LED, die du an deinem ESP8266 anschließt. Wenn du später möchtest – und vorausgesetzt, du fühlst dich damit sicher – kannst du dich einer „richtigen“ Lampe zuwenden. Hierfür benötigst du dann ein Relais, das du per Telegram steuert und das wiederum den Stromkreis der Lampe öffnet und schließt.

Dieses Projekt ist der vierte Teil einer Serie und baut auf die Vorgängerprojekte auf. In diesem Artikel findest du alles zum Aufbau und den passenden Sketch; wir besprechen jedoch nicht alle Teile des Codes. Wenn du mehr Details erfahren möchtest, wirf bitte einen Blick in die folgenden Projekte:

  1. Ein stiller Alarm mit Telegram und einem ESP8266
  2. Überwache die Temperatur mit Telegram und einem ESP8266
  3. Die aktuelle Temperatur per Telegram abfragen

Falls du noch kein Projekt mit Telegram gebaut hast, lerne zunächst, wie du einen Telegram-Bot erstellst.

Anfänger

1 – 2 Stunden

ca. 10 €

Für dieses Projekt benötigst du (Mengen s. Beschreibung):

Der Aufbau des Projekts

Du benötigst für dieses Projekt deinen ESP8266, eine LED samt Widerstand (z.B. 220 Ohm), ein Breadboard und Kabel. Orientiere dich an folgendem Schema:

Aufbau LED steuern

Wie du oben siehst, verbindest du die Anode (langes Bein) der LED mit dem Pin D5 an deinem ESP8266. Die Kathode (kurzes Bein) verbindest du über einen Widerstand mit GND.

Der Sketch

Kopiere den folgenden Sketch in deine Arduino IDE, ergänze deine Daten und lade ihn auf deinen ESP8266.

Sketch als .txt anschauen

/*
   Das Licht ein- und ausschalten mit Telegram - polluxlabs.net
*/

//Bibliotheken
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>

// Deine WLAN-Zugangsdaten
const char* ssid = "NETZWERK";
const char* password = "PASSWORT";

// Den Telegram-Bot initialisieren
#define botToken "TOKEN"  // den Bot-Token bekommst du vom Botfather)

//Deine UserID
#define userID "USERID"

WiFiClientSecure client;
UniversalTelegramBot bot(botToken, client);

//Variable für das Licht
const int lightPin = 14; //Am ESP8266 Pin D5
bool lightState = LOW;

//Variable für die Anzahl der Anfragen
int numNewRequests;

//Variable für den Text der Anfrage, die du sendest
String text = "";

//UserID des Absenders
String chat_id = "";

//Name des Absenders
String from_name = "";

//Variable für die Willkommensnachricht
String welcome = "";

//Funktion fürs Verarbeiten neuer Anfragen
void handleNewRequests(int numNewRequests) {

  for (int i = 0; i < numNewRequests; i++) { //loopt durch die neuen Anfragen

    //Checkt, ob du die Anfrage gesendet hast oder jemand anderes
    chat_id = String(bot.messages[i].chat_id);
    if (chat_id != userID) {
      bot.sendMessage(chat_id, "Du bist nicht autorisiert!", "");
      continue;
    }

    // Anfragetext speichern
    text = bot.messages[i].text;
    Serial.println(text);

    from_name = bot.messages[i].from_name;

    if (text == "/start") {
      welcome = "Willkommen, " + from_name + ".\n";
      welcome += "Folgende Befehle kannst du verwenden: \n\n";
      welcome += "/lichtEin \n";
      welcome += "/lichtAus \n";
      welcome += "/status \n";
      bot.sendMessage(chat_id, "http://gph.is/1Rc70ke", "");
      bot.sendMessage(chat_id, welcome, "");
    }

    if (text == "/lichtEin") {
      lightState = HIGH;
      digitalWrite(14, lightState);
      bot.sendMessage(chat_id, "Das Licht ist an.", "");
    }

    if (text == "/lichtAus") {
      lightState = LOW;
      digitalWrite(14, lightState);
      bot.sendMessage(chat_id, "Das Licht ist aus.", "");
    }

    if (text == "/status") {
      if (digitalRead(lightPin)) {
      bot.sendMessage(chat_id, "Das Licht ist an.", "");
      }
      else {
        bot.sendMessage(chat_id, "Das Licht ist aus.", "");
      }
    }
  }
}

void setup() {

  Serial.begin(115200);
  client.setInsecure();

  pinMode(lightPin, OUTPUT);
  digitalWrite(lightPin, lightState);

  //Verbindung zum WLAN
  Serial.print("Verbinde mich mit: ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(300);
  }
  Serial.println("");
  Serial.println("Verbunden!");
}

void loop() {

  //checkt, ob eine neue Anfrage reinkam
  int numNewRequests = bot.getUpdates(bot.last_message_received + 1);

  while (numNewRequests) { //wird ausgeführt, wenn numNewRequests == 1
    Serial.println("Anfrage erhalten");
    handleNewRequests(numNewRequests);
    numNewRequests = bot.getUpdates(bot.last_message_received + 1);
  }
  delay(1000);
}

Was ist neu in diesem Sketch?

Viel musst du in deinem Code eigentlich nicht verändern, um statt der Temperaturabfrage das Licht ein- und auszuschalten. Werfen wir einen Blick auf die Details.

Zunächst benötigst du eine Konstante für den Pin, an dem die LED angeschlossen ist und eine Variable für den Zustand der LED – also ob sie an oder aus ist.

const int lightPin = 14; //Am ESP8266 Pin D5
bool lightState = LOW;

In der Setup-Funktion legst du den pinMode für den LED-Pin fest und weist der LED den Zustand zu, der in der Variablen lightState steht – also zu Beginn LOW – aus.

pinMode(lightPin, OUTPUT);
digitalWrite(lightPin, lightState);

Die Funktion handleNewRequests()

Diese Funktion kennst du ja bereits aus dem vorangegangenen Projekt. Hier werden deine Anfragen verarbeitet, die du von deinem Smartphone aus sendest.

Licht einschalten mit Telegram
Du kannst auch GIFs nutzen, sende dazu einfach die entsprechende URL. Im Sketch oben befindet sich schon eine.

In diesem Projekt gibt es drei Anfragen (oder Befehle), die du senden kannst: /lichtEin, /lichtAus und /status. Mit der letzten Anfrage fragst du ab, ob die LED gerade ein- oder ausgeschaltet ist und erhältst die entsprechende Antwort zurück.

Wenn du also deinem ESP8266 den Text /lichtEin sendest, setzt dieser die Variable lightState auf HIGH und schaltet die LED mit digitalWrite() ein. Zuletzt sendet er dir mit der bekannten Funktion bot.sendMessage() Feedback.

if (text == "/lichtEin") {
  lightState = HIGH;
  digitalWrite(14, lightState);
  bot.sendMessage(chat_id, "Das Licht ist an.", "");
}

Der Befehl /lichtAus funktioniert so ähnlich, nur dass er die LED natürlich ausschaltet. Wenn du /status sendest, prüft dein ESP8266, ob die LED ein- oder ausgeschaltet ist:

if (text == "/status") {
  if (digitalRead(lightPin)) {
  bot.sendMessage(chat_id, "Das Licht ist an.", "");
  }
  else {
    bot.sendMessage(chat_id, "Das Licht ist aus.", "");
  }
}

Wie geht es weiter?

Wie eingangs erwähnt, kannst du statt der LED ein Relais steuern. Je nachdem, welches Signal du diesem von deinem Smartphone aus sendest, öffnet und schließt das dann den Stromkreis eines anderen Geräts – z.B. einer Lampe oder einer Kaffeemaschine.

]]>
Video-Livestream mit der ESP32-CAM https://polluxlabs.net/arduino-tutorials/video-livestream-mit-der-esp32-cam/ Fri, 23 Oct 2020 06:23:00 +0000 https://polluxlabs.net/?p=3211 Mit der ESP32-CAM kannst du einfach und günstig deine Kameraprojekte umsetzen, wie z.B. unsere Fotofalle. Aber es müssen ja nicht immer nur Fotos sein – ein Video-Livestream geht auch!

In diesem Tutorial lernst du, wie du mit deiner ESP32-CAM einen Livestream aufsetzt, den du in deinem Browser aufrufen kannst. Hierfür benötigst du:

Vorbereitungen

Wenn du deine ESP32-CAM noch nie mit der Arduino IDE programmiert hast, musst du sie dort erst verfügbar machen. Da dieses Board keinen eigenen USB-Anschluss hat, benötigst du außerdem eine FTDI-Programmer, um deinen Sketch hochzuladen.

Wie beides funktioniert lernst du in diesem Tutorial hier bei uns. 🙂

Sobald die Verbindung von deinem Computer zur ESP32-CAM steht, kann es weitergehen.

Den Sketch hochladen

Die Konfiguration und das Hochladen des passenden Sketchs fallen in diesem Tutorial ziemlich angenehm aus: er befindet sich nämlich schon in deiner Arduino IDE. Wähle im Menü Datei -> Beispiele -> ESP32 -> Camera -> CameraWebServer. Daraufhin öffnet sich der Sketch, den du benötigst.

Zwei Kleinigkeiten musst du jedoch noch anpassen: Trage zunächst den Namen und das Passwort deines WLAN-Netzwerks ein.

Netzwerkdaten für den Videostream

Außerdem musst du noch das richtige Board auswählen. In unserem Fall (und in den meisten anderen Fällen auch) ist das AI Thinker. Lösche hierfür den Kommentar vor dem Board, sodass nur deines aktiv ist.

Board auswählen für den Video-Livestream

Lade nun den Sketch auf die ESP32-CAM. Vergiss nicht für den Upload die Pins IO0 und GND zu verbinden. Sobald der Code auf dem Board ist, trenne diese Verbindung wieder, starte deinen Seriellen Monitor und drücke zuletzt den RESET-Button auf dem Board.

Den Video-Livestream starten

Nun solltest du nach ein paar Sekunden (zwischenzeitliche Fehlermeldungen machen nichts) folgendes in deinem Seriellen Monitor sehen. Die IP-Adresse kann variieren.

Ausgabe im Seriellen Monitor

Klicke nun auf den Link oder kopiere dir die Adresse heraus und setze sie in deinem Browser ein. Du solltest nun auf der linken Seite eine ganze Reihe von Einstellungsmöglichkeiten sehen. Ganz unten befindet sich der Button Start Stream – ein Klick darauf startet den Stream.

Der Video-Livestream

Und das war es auch schon. Du kannst nun mit den Einstellungen experimentieren, um die bestmögliche Leistung zu erhalten. Außerdem hast du ganz unten auch die Option, eine Gesichtserkennung zu aktivieren, die wir hier jedoch nicht weiter behandeln.

Zwei Hinweise noch: Du kannst den Livestream nur aus deinem eigenen WLAN-Netz aus aufrufen. Dieses Tutorial ist zum Ausprobieren gedacht – wenn du die Kamera produktiv einsetzen möchtest, mache dir vorab Gedanken zur IT-Sicherheit.

]]>
Die aktuelle Temperatur per Telegram abfragen https://polluxlabs.net/esp8266-projekte/die-aktuelle-temperatur-per-telegram-abfragen/ Wed, 21 Oct 2020 19:01:15 +0000 https://polluxlabs.net/?p=3163 In diesem Projekt verwendest du Telegram, um bei deinem ESP8266 nachzufragen, wie hoch die aktuelle Temperatur ist. Sobald dein ESP8266 deine Anfrage erhalten hat, fragt er die aktuelle Temperatur beim Sensor BMP180 ab und sendet sie dir auf dein Smartphone.

Dieses Projekt ist der dritte Teil einer Serie und baut auf die Vorgängerprojekte auf. In diesem Artikel findest du alles zum Aufbau und den passenden Sketch; wir besprechen jedoch nicht alle Teile des Codes. Wenn du mehr Details erfahren möchtest, wirf bitte einen Blick in die folgenden Projekte:

  1. Ein stiller Alarm mit Telegram und einem ESP8266
  2. Überwache die Temperatur mit Telegram und einem ESP8266

Falls du noch kein Projekt mit Telegram gebaut hast, lerne zunächst, wie du einen Telegram-Bot erstellst.

Anfänger

1 – 2 Stunden

ca. 12 €

Für dieses Projekt benötigst du (Mengen s. Beschreibung):

Der Aufbau des Projekts

Wenn du das Vorgängerprojekt aufgebaut hast, dann musst du auf Seiten der Hardware nichts weiter tun – du kannst deinen Aufbau hier einfach weiterverwenden. 🙂

Aufbau des Temperatursensors

Im Sketch hat sich dafür einiges verändert:

Der Sketch

Kopiere den folgenden Sketch in deine Arduino IDE, ergänze deine Daten und lade ihn auf deinen ESP8266.

Sketch als .txt anschauen

/*
   Die Temperatur abfragen mit Telegram - polluxlabs.net
*/

//Bibliotheken
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>

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

// Deine WLAN-Zugangsdaten
const char* ssid = "NETZWERKNAME";
const char* password = "PASSWORT";

// Den Telegram-Bot initialisieren
#define botToken "TOKEN"  // den Bot-Token bekommst du vom Botfather)

//Deine UserID
#define userID "USERID"

WiFiClientSecure client;
UniversalTelegramBot bot(botToken, client);

Adafruit_BMP085 bmp;

//Variable für die Temperatur
float temp;

//Variable für die Anzahl der Anfragen
int numNewRequests;

//Variable für den Text der Anfrage, die du sendest
String text = "";

//UserID des Absenders
String chat_id = "";

//Name des Absenders
String from_name = "";

//Variable für die Willkommensnachricht
String welcome = "";

//Funktion fürs Verarbeiten neuer Anfragen
void handleNewRequests(int numNewRequests) {

  for (int i = 0; i < numNewRequests; i++) { //loopt durch die neuen Anfragen

    //Checkt, ob du die Anfrage gesendet hast oder jemand anderes
    chat_id = String(bot.messages[i].chat_id);
    if (chat_id != userID) {
      bot.sendMessage(chat_id, "Du bist nicht autorisiert!", "");
      continue;
    }

    // Anfragetext speichern
    text = bot.messages[i].text;
    Serial.println(text);

    from_name = bot.messages[i].from_name;

    if (text == "/start") {
      welcome = "Willkommen, " + from_name + ".\n";
      welcome += "Mit folgendem Befehl fragst du die aktuelle Temperatur ab: \n\n";
      welcome += "/messen \n";
      bot.sendMessage(chat_id, welcome, "");
      bot.sendMessage(chat_id, "http://gph.is/2aLXZ8H", "");
    }

    if (text == "/messen") {
      temp = bmp.readTemperature();
      bot.sendMessage(chat_id, "Temperatur: " + String(temp) + " ºC", "");
    }
  }
}

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

  //Verbindung zum WLAN
  Serial.print("Verbinde mich mit: ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(300);
  }
  Serial.println("");
  Serial.println("Verbunden!");

  if (!bmp.begin()) {
    Serial.println("Kein Sensor gefunden! Checke die Verbindung.");
    while (1) {}
  }
}

void loop() {

  //checkt, ob eine neue Anfrage reinkam
  int numNewRequests = bot.getUpdates(bot.last_message_received + 1);

  while (numNewRequests) { //wird ausgeführt, wenn numNewRequests == 1
    Serial.println("Anfrage erhalten");
    handleNewRequests(numNewRequests);
    numNewRequests = bot.getUpdates(bot.last_message_received + 1);
  }
  delay(1000);
}

Was ist neu in diesem Sketch?

In diesem Sketch läuft einiges anders: In den zwei vorangegangenen Projekten hat dein ESP8266 nur Nachrichten gesendet, in diesem Projekt wartet er jedoch auf eine Nachricht von dir – die er dann selbst mit der aktuellen Temperatur beantwortet.

Dafür muss dein ESP8266 – im Gegensatz zu den vorherigen Projekten – die ganze Zeit mit deinem WLAN verbunden sein. Deshalb wandert der Code, mit dem du die Verbindung herstellst, direkt in die Setup-Funktion:

  Serial.print("Verbinde mich mit: ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(300);
  }
  Serial.println("");
  Serial.println("Verbunden!");

Schauen wir jetzt direkt in den Loop. Dort prüft dein ESP8266 alle 1000 Millisekunden, ob eine neue Anfrage vorliegt:

int numNewRequests = bot.getUpdates(bot.last_message_received + 1);

Wenn das der Fall ist, wird in der Variablen numNewRequests mithilfe der Funktion bot.getUpdates() die Anzahl der vorliegenden Anfragen gespeichert (im Normalfall eine 1, aber es können theoretisch ja auch mehrere vorliegen).

Das wiederum setzt den folgenden While-Loop in Gang, der die Funktion handleNewRequests() so oft aufruft wie Anfragen „abzuarbeiten“ sind. In diesem Loop wird auch die Variable numNewRequests aktualisiert – sobald hier wieder eine 0 enthalten ist, endet der Loop.

while (numNewRequests) {
  handleNewRequests(numNewRequests);
  numNewRequests = bot.getUpdates(bot.last_message_received + 1);
  }

Die Funktion handleNewRequests()

Kommen wir zum Kernstück des Sketchs. Diese Funktion handleNewRequests() erwartet bei Ihrem Aufruf ein Argument – nämlich die Anzahl der vorliegenden Anfragen, die in der Variablen numNewRequests steckt. Diese hast du beim Aufruf der Funktion im While-Lopp bereits mitgegeben:

handleNewRequests(numNewRequests);

Bei der Deklaration der Funktion taucht diese Variable wieder auf, um dann gleich weiterverarbeitet zu werden. Diesmal iterierst du mit einem For-Loop über die Anzahl der Anfragen, um sie eine nach der anderen abzuarbeiten:

void handleNewRequests(int numNewRequests) {
  for (int i = 0; i < numNewRequests; i++) { 

Innerhalb des Loops findet zuerst eine sehr wichtige Prüfung statt – nämlich ob die Anfrage überhaupt von einem autorisierten User (also von dir) stammt. Hierfür speicherst du die chat_id, die mit jeder Anfrage übergeben wird, in der gleichnamigen Variablen.

Anschließend prüfst du, ob diese mit deiner eigenen UserID übereinstimmt. Ist das nicht der Fall, erhält der Absender eine entsprechende Nachricht und der For-Loop wird mit continue abgebrochen:

chat_id = String(bot.messages[i].chat_id);
if (chat_id != userID) {
  bot.sendMessage(chat_id, "Du bist nicht autorisiert!", "");
  continue;
}

Wenn die Anfrage jedoch von dir stammt, speicherst du ihren Textinhalt und den Namen des Absenders (also wiederum deinen) ab:

text = bot.messages[i].text;
from_name = bot.messages[i].from_name;

Fehlen nur noch zwei If-Statements. Wenn du deinen Telegram-Bot zum ersten Mal startest, machst du das mit dem Text /start (den du natürlich auch senden kannst, wenn der Bot schon läuft). In diesem Fall erscheint eine kleine Begrüßungsnachricht mit einer Erklärung: Um die aktuelle Temperatur abzufragen, musst du deinem Bot (und damit deinem ESP8266) die Nachricht /messen schicken.

Begrüßung im Chat
Du kannst auch GIFs senden

Hier werden wieder Strings miteinander verbunden, diesmal mit dem Operator +=, der einfach die Variable welcome um weiteren Text erweitert. Auch beachtenswert: Mit \n kannst du einen Zeilenumbruch einfügen.

Am Ende des Statements findest du wieder die Funktion bot.sendMessage(), die du bereits kennengelernt hast. Diese taucht hier gleich zweimal auf: Einmal, um den String welcome zu senden und dann noch einmal, um ein GIF hinterherzuschicken. Wenn du spaßeshalber auch GIFs senden möchtest, reicht dafür einfach die entsprechende URL der Datei.

if (text == "/start") {
  welcome = "Willkommen, " + from_name + ".\n";
  welcome += "Mit folgendem Befehl fragst du die aktuelle Temperatur ab: \n\n";
  welcome += "/messen \n";
  bot.sendMessage(chat_id, welcome, "");
  bot.sendMessage(chat_id, "http://gph.is/2aLXZ8H", "");
}

Die zweite Abfrage kümmert sich um die Anfrage /messen, die mit der aktuellen Temperatur beantwortet werden soll. Auch das kennst du bereits aus den vorangegangenen Projekten:

if (text == "/messen") {
  temp = bmp.readTemperature();
  bot.sendMessage(chat_id, "Temperatur: " + String(temp) + " ºC", "");
}

Und das war es auch schon. Du hast in dieser Lektion gelernt, wie du nicht nur Nachrichten von deinem ESP8266 an deinen Telegram-Bot senden kannst, sondern auch von dort aus Sensordaten abrufen kannst.

Wie geht es weiter?

Du kannst natürlich nicht nur die Temperatur abfragen. Der Sensor BMP180 kann auch den Luftdruck ermitteln. Der Nachfolger BMP280 misst sogar die Luftfeuchtigkeit. Baue z.B. eine Wetterstation, bei der du die aktuellen Daten aus der Ferne abfragst.

Eine weitere Möglichkeit ist ein Feuchtigkeitssensor, den du neben eine Pflanze in die Erde steckst. Wenn du denkst, die Erde sei zu trocken, kannst du eine automatische „Gießkanne“ von deinem Smartphone aus steuern.

]]>
Überwache die Temperatur mit Telegram und einem ESP8266 https://polluxlabs.net/esp8266-projekte/ueberwache-die-temperatur-mit-telegram-und-einem-esp8266/ Mon, 19 Oct 2020 14:42:24 +0000 https://polluxlabs.net/?p=3147 In diesem Projekt überwachst du die Temperatur mit einem Sensor und deinem ESP8266. Sobald der Temperatursensor einen von dir festgelegten Wert ermittelt, sendet dein Microcontroller eine Nachricht an deinen Telegram-Bot.

Dieses Projekt ist der zweite Teil einer Serie: Inhaltlich baut es auf unserem Stillen Alarm mit Telegram auf. Schaue dort hinein, um mehr über die grundlegenden Funktionen des Sketchs zu erfahren. Auch in diesem Projekt wartet dein ESP8266 darauf, dass ein bestimmtes Ereignis eintritt. Allerdings wird er hier nicht durch einen Interrupt getriggert, sondern fragt selbst die Daten eines Temperatursensors ab.

Anfänger

1 – 2 Stunden

ca. 12 €

Für dieses Projekt benötigst du (Mengen s. Beschreibung):

Der passende Temperatursensor

In diesem Projekt verwenden wir den Sensor BMP180. Du kannst aber natürlich auch jeden anderen Temperatursensor verwenden – z.B. einen einfachen TMP36, einen DHT22 oder einen GY-906. Denke in diesem Fall daran, deinen Sketch entsprechend anzupassen.

In diesen Tutorials lernst du, wie du einen TMP36 und einen GY-906 anschließt und verwendest.

Der Aufbau des Projekts

Du benötigst nur deinen ESP8266, den Temperatursensor (in unserem Fall einen BMP180), ein Breadboard und Kabel. Orientiere dich beim Aufbau an diesem Schema:

Aufbau Temperatursensor am ESP8266

Der Sensor BMP180 wird per I²C angeschlossen. Am ESP8266 musst du deshalb zwingend die beiden Pins D1 und D2 verwenden. Schließe den Sensor wie folgt an:

BMP180ESP8266
VIN3v3
GNDGND
SDAD2
SCLD1

Der Sketch

___STEADY_PAYWALL___

Kopiere den folgenden Sketch in deine Arduino IDE, ergänze deine Daten und lade ihn auf deinen ESP8266.

Sketch als .txt anschauen

/*
   Die Temperatur überwachen mit Telegram - polluxlabs.net
*/

//Bibliotheken
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>

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

// Deine WLAN-Zugangsdaten
const char* ssid = "NETZWERKNAME";
const char* password = "PASSWORT";

// Den Telegram-Bot initialisieren
#define botToken "DEIN TOKEN"  // den Bot-Token bekommst du vom Botfather)

//Deine UserID
#define userID "DEINE USERID"

WiFiClientSecure client;
UniversalTelegramBot bot(botToken, client);

Adafruit_BMP085 bmp;

//Variablen für die Temperatur
float temp;
float threshold = 27.00;

//Verbindung zum WLAN
void connectToWiFi() {
  Serial.print("Verbinde mich mit: ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(300);
  }
  Serial.println("");
  Serial.println("Verbunden!");
}

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

  if (!bmp.begin()) {
    Serial.println("Kein Sensor gefunden! Checke die Verbindung.");
    while (1) {}
  }
}

void loop() {
  temp = bmp.readTemperature();
  Serial.print("Temperatur = ");
  Serial.print(temp);
  Serial.println(" °C");

  if (temp > threshold) { //Die Temperatur, die überschritten werden muss
    connectToWiFi();
    bot.sendMessage(userID, "Temperatur zu hoch!  " + String(temp) + " °C", "");
    Serial.println("Temperatur zu hoch!");
    WiFi.disconnect();
    delay(300000);
  }
  delay(500);
}

Was ist neu in diesem Sketch?

Vieles im Code haben wir schon im Stillen Alarm verwendet. Ein paar Teile sind jedoch neu und diese schauen wir uns nun genauer an.

Zunächst benötigst du zwei weitere Bibliotheken. Wire.h ist für die Kommunikation per I²C zuständig. Diese Bibliothek ist standardmäßig bereits vorinstalliert. Damit du den Sensor BMP180 so einfach wie möglich verwenden kannst, existiert ebenfalls eine passende Bibliothek: Arduino_BMP085.h. Lass dich nicht vom Namen irritieren. Dort steht zwar BMP085 (das ist der Vorläufer des BMP180), sie funktioniert jedoch auch mit dem BMP180 problemlos.

Am Anfang deines Sketchs bindest du also zusätzlich diese beiden Bibliotheken ein:

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

Ebenfalls zu Beginn des Sketchs erstellst du für den Sensor das Objekt bmp sowie zwei Variablen: eine für die gemessene Temperatur (temp) und eine für den Schwellenwert (threshold), bei dessen Überschreiten dein ESP8266 eine Nachricht an dich sendet. In unserem Beispiel setzen wir diesen Schwellenwert auf 27,00 °C. Da der BMP180 die Temperatur als Kommazahl ausgibt, benötigst du für diese Variablen den Dateityp float.

Adafruit_BMP085 bmp;
float temp;
float threshold = 27.00;

In der Setup-Funktion prüfst du, ob der Temperatursensor von deinem ESP8266 gefunden wurde und verwendet werden kann. Ist das nicht der Fall, wirst du im Seriellen Monitor darüber informiert und der Sketch begibt sich mit while(1) in eine Endlosschleife – friert also ein.

  if (!bmp.begin()) {
    Serial.println("Kein Sensor gefunden! Checke die Verbindung.");
    while (1) {}
  }

Solltest den obigen Text also in deinem Seriellen Monitor zu Gesicht bekommen, trenne deinen ESP8266 vom Strom und überprüfe deine Verkabelung. In den meisten Fällen sollte hier der Fehler liegen.

Der Loop

Im Loop fragst du die Temperatur im Halbsekundentakt ab und prüfst mit einem Schwellenwert, ob sie diesen überschritten hat.

temp = bmp.readTemperature();

Wenn das der Fall ist, verbindet sich dein ESP8266 mit dem Internet und sendet eine entsprechende Nachricht an dein Smartphone.

if (temp > threshold) { //Wenn die Temperatur über dem Schwellenwert liegt
    connectToWiFi();
    bot.sendMessage(userID, "Temperatur zu hoch!  " + String(temp) + " °C", "");

Die Funktion bot.sendMessage() besteht aus drei Teilen. Der mittlere ist die Nachricht selbst, die du hier jedoch aus zwei Strings und der Variablen temp „zusammenbaust“. Hierfür verbindest du die einzelnen Teilstrings einfach mit einem Pluszeichen – doch Vorsicht: In der Mitte befindet sich die Variable temp als float. Um den Wert in dieser Variablen senden zu können, musst du diese erst mit der Funktion String(temp) in einen String umwandeln.

Auf deinem Smartphone erscheint dann der Text: „Temperatur zu hoch! x °C“ – wobei das x für die gemessene Temperatur steht.

Zuletzt trennt dein ESP8266 die Verbindung zu deinem WLAN wieder und setzt sich für 5 Minuten (300.000 Millisekunden) zur Ruhe. Erst dann beginnt er wieder damit, alle 500 Millisekunden die Temperatur vom BMP180 abzufragen.

    WiFi.disconnect();
    delay(300000);
  }
  delay(500);
}

Sollte danach die Temperatur immer noch über dem Schwellenwert liegen, erhältst du eine weitere Nachricht.

Falls noch nicht geschehen, lade den Sketch auf deinen ESP8266 und probiere ihn gleich aus.

Das passende Gehäuse

Möchtest du das Projekt in einem kleinen Gehäuse unterbringen? Hier findest du passende 3D-Druck-Dateien, die du herunterladen und ausdrucken kannst.

Wie geht es weiter?

Im nächsten Teil der Serie wartest du nicht darauf, bis dein ESP8266 sich bei dir meldet, sondern fragst die Temperatur selbst von deinem Smartphone aus ab.

Statt eines Temperatursensors kannst du auch andere Sensoren einsetzen: So kannst du dich warnen lassen, wenn die Luft zu schlecht oder das Licht zu hell ist. Es gibt auch Sensoren, die Flammen erkennen können – solltest du diesen einsetzen, hoffen wir, dass du niemals eine entsprechende Nachricht erhältst!

]]>
Beleuchte deine LEGO ISS im Rhythmus der echten Raumstation https://polluxlabs.net/esp8266-projekte/beleuchte-deine-lego-iss-im-rhythmus-der-echten-raumstation/ Tue, 13 Oct 2020 18:32:13 +0000 https://polluxlabs.net/?p=2036 Hinweis: Leider ist dieses Projekt bis auf Weiteres nicht mehr umsetzbar, da die benötigte API zur Ermittlung der nächsten Überflüge nicht mehr online ist. Du kannst stattdessen mit Python den nächsten Überflug der ISS berechnen oder die aktuelle Position der ISS ermitteln und auf einer Weltkarte anzeigen.

Wie wäre es, wenn deine LEGO ISS aufleuchten würde, sobald die echte International Space Station über ihr fliegt? In diesem Projekt lässt du genau das Wirklichkeit werden. Du lernst, wie du den Zeitpunkt des nächsten Überflugs herausfindest, damit einen Countdown programmierst und dann im richtigen Moment einen NeoPixel LED-Ring aufleuchten lässt. Los geht’s! 🙂

Für dieses Projekt benötigst du – abgesehen von der LEGO ISS – nur wenige Bauteile. Damit das Projekt gut aussieht, verwenden wir zwei Boxen aus dem 3D-Drucker – eine für den ESP8266 und den NeoPixel, die andere für das 7-Segment-Display.

Fortgeschrittene

1 – 2 Stunden – ohne Aufbau der LEGO ISS 😉

ca. 20 € plus ggfs. Kosten für den LEGO-Bausatz und 3D-Druck

Für dieses Projekt benötigst du (Mengen s. Beschreibung):

Hinweis: Meistens werden die NeoPixel LED-Ringe unverlötet verkauft – du benötigst also noch einen Lötkolben plus Zubehör. Wenn du unsere Vorlagen für die Boxen verwenden möchtest, benötigst du auch einen 3D-Drucker und Filament.

Der Aufbau unter der LEGO ISS

Da die LEGO ISS auf einer Art Sockel steht, unter dem noch viel Platz ist, eignet sich dieser Ort besonders, um sie von unten zu beleuchten. Hier platzierst du also deinen ESP8266 und den NeoPixel-Ring. Das 7-Segment-Display für den Countdown bis zum nächsten Überflug liegt etwas abseits.

Aufbau ESP8266, Neopixel, Display unter der LEGO ISS

Auf dem Breadboard

Die Bauteile miteinander zu verbinden, dauert nur wenige Minuten. Verwende für den Aufbau die folgende Tabelle:

ESP8266 NeoPixel
3v3 VCC
GND GND
D5 IN
ESP8266 7-Segment-Display
3v3 VCC
GND GND
D6 DIN
D7 CS
D8 CLK

Noch zwei Hinweise: Wenn du unsere Vorlage für die Boxen aus dem 3D-Drucker verwendest, achte darauf, deinen ESP8266 an den glatten Rand des Mini-Breadboards zu setzen – die Seite, die keine Feder hat. Dann kannst du das USB-Kabel bequem durch ein Loch in der Box mit dem ESP8266 verbinden.

Unsere Box hat einen Innendurchmesser von 39 mm. Achte darauf, einen NeoPixel-Ring mit 37 mm Durchmesser zu verwenden. Diese werden jedoch teilweise fälschlicherweise mit einem Durchmesser von 39 mm verkauft. Egal wie, beide passen in die Box. 😉

So ungefähr sollte es aussehen, wenn du alles miteinander verbunden hast.

Die passenden Boxen

Natürlich kannst du dein Projekt auch „nackt“ unter deine LEGO ISS stellen. Allerdings sieht es aufgeräumter aus, wenn du deine Bauteile in Boxen unterbringst. Wenn du möchtest, kannst du unsere Vorlagen verwenden und sie mit einem 3D-Drucker ausdrucken. Hier findest du je zwei Boxen mit Deckel – eine für den ESP8266 samt NeoPixel und eine für das 7-Segment-Display.

Hinweis: Du kannst die Boxen natürlich in deiner Lieblingsfarbe ausdrucken – der Deckel der Box für den ESP8266 plus NeoPixel sollte jedoch transparent sein, damit das Licht durchscheinen und deine LEGO ISS von unten beleuchten kann.

Du findest unsere Vorlagen hier bei uns als Download.

Und so sehen die Bauteile in den Boxen aus. Wie du siehst führt ein Kabelstrang aus der Box für den ESP8266 zum 7-Segment-Display in der anderen Box. Wir haben hierfür 10 cm lange Kabel verwendet, aber die Wahl liegt natürlich bei dir.

Alle Bauteile in den passenden Boxen

Und das war es auch schon mit der Hardware. Weiter geht es mit dem Sketch.

Der passende Sketch

Schauen wir uns die wichtigsten Teile des Codes genauer an.

Die benötigten Bibliotheken

Für dieses Projekt benötigst du einige Bibliotheken, um z.B. das 7-Segment-Display, den NeoPixel-Ring zu steuern und die aktuelle Uhrzeit abzufragen. Installiere die folgenden Bibliotheken in deinem Bibliotheksmanager, falls sie noch nicht in deiner Arduino IDE verfügbar sind:

ArduinoJson.h
ESP8266HTTPClient.h
WiFiClientSecure.h
Adafruit_NeoPixel.h
LedControl.h
NTPClient.h
WiFiUdp.h

Den ESP8266 mit dem Internet verbinden

Für dieses Projekt muss dein ESP8266 mit dem Internet verbunden sein. Zu diesem Thema haben wir ein eigenes Tutorial, in dem du lernst, wie das geht. Übrigens: Solltest du noch nie einen ESP8266 mit der Arduino IDE programmiert haben, lies hier nach, wie du die beiden verbindest.

Den nächsten Überflug der ISS ermitteln

Damit die Beleuchtung deiner LEGO ISS im Rhythmus der echten funktioniert, benötigst du zuerst die Überflugsdaten. Hier kommt die API von open-notify.org ins Spiel. Mit ihr kannst du kostenlos den nächsten Transit für deinen Standort mit deinem ESP8266 abfragen.

Trage deshalb zu Beginn des Sketchs deine Koordinaten ein. Falls du diese nicht kennst, kannst du sie z.B bei geoplaner.de ermitteln. Deine Höhe ist optional – wenn du sie weißt, wird das Ergebnis ein klein wenig genauer, wenn nicht, rechnet die API einfach mit einer Höhe von 100 Metern. Allerdings muss die Höhenangabe größer als Null sein – sonst funktioniert die API-Abfrage nicht.

Hier sind die Daten für Karlsruhe eingetragen:

const float latitude = 49.00; //Dein Breitengrad
const float longitude = 8.40; //Dein Längengrad
const float altitude = 115.00; //Deine Höhe über Normalnull

Im Sketch befindet sich die Funktion apiCall() – hier werden die Überflugsdaten der ISS für deinen Standort bei der API von open-notify.org abgerufen und weiterverarbeitet.

Zunächst die Abfrage: Mit der Funktion http.begin() rufst du die Daten von der API-Adresse ab. Hierfür benötigst den Breiten- und Längengrad sowie optional die Höhe, die du bereits oben in den jeweiligen Konstanten hinterlegt hast. Die URL innerhalb der Funktion erweiterst du deshalb ganz einfach mit diesen hinterlegten Daten:

http.begin("http://api.open-notify.org/iss-pass.json?lat=" + String(latitude) + "&lon=" + String(longitude) + "&alt=" + String(altitude) + "&n=5");

Die fertige URL für das Beispiel Karlsruhe lautet dann wie folgt. Der Parameter n=5 bestimmt die Anzahl der nächsten Überflüge, die berechnet werden soll – hier also 5.

http://api.open-notify.org/iss-pass.json?lat=49&lon=8.4&alt=115&n=5

Die Antwort der API auswerten

Wenn du diese Adresse testweise in deinem Browser öffnest, erhältst du ungefähr folgende Antwort:

Antwort der API

Hierbei handelt es sich um Daten im JSON-Format, mit denen du noch nichts anfangen kannst. Was du benötigst, sind die Daten, die im Key response stecken: duration (die Dauer des Überflugs) und risetime (der Zeitpunkt, wann die ISS über den Horizont steigt und von deinem Standort aus theoretisch sichtbar wird).

Das Angaben duration und risetime sind insgesamt fünf Mal in der response enthalten, da du die Abfrage mit n=5 entsprechend gestartet hast. Alle fünf zusammen befinden sich paarweise in einem Array. Das erste Paar ist hierbei der nächste Überflug, das zweite der übernächste und so weiter. Später werden wir das benötigte Paar aus dem Array picken.

Zunächst speicherst du jedoch diese JSON-Daten als Ganzes in der Variablen payload ab:

String payload = http.getString();

Anschließend kommt die Bibliothek ArduinoJson ins Spiel. Auch hierfür haben wir ein eigenes Tutorial, weshalb wir das Parsen der JSON-Daten hier überspringen.

Vielmehr springen wir gleich zum Teil, in dem du die Dauer und den Zeitpunkt des nächsten Überflugs abspeicherst. Wie du im Code gleich hier unten siehst, picken wir den ersten Eintrag des oben genannten Arrays mit [0].

JsonArray response = doc["response"];
duration = response[0]["duration"];
riseTime = response[0]["risetime"];

Wie lange dauert es noch, bis die ISS kommt?

Du hast jetzt den Zeitpunkt des nächsten Überflugs, aber dein ESP8266 weiß noch nicht, wie lange es bis dahin noch dauert. Hierfür befindet sich im Sketch eine andere Funktion: getCurrentTime()

Hier verwendest du das Network Time Protocol (NTP). Auch hierfür gibt es das passende Tutorial bei uns, in dem du weitere Details erfährst. Du ermittelst in dieser Funktion also die aktuelle Uhrzeit – und speicherst diese in der Variablen currentTime ab.

Den Zeitpunkt bis zum Erscheinen der ISS berechnest du dann wie folgt:

timeUntilRise = riseTime - currentTime;

In der Variablen timeUntilRise befindet sich nun also die Dauer bis zum nächsten Überflug – in Sekunden. Noch ein Hinweis: Sowohl die aktuelle Uhrzeit currentTime als auch den Zeitpunkt des Überflugs riseTime rufst du als Unixzeit ab. Hierbei handelt es sich um die Sekunden, die seit dem 1. Januar 1970 vergangen sind. Das hat den Vorteil, dass du von Zeitverschiebung, Sommer- und Winterzeit unabhängig bist: In die Variable timeUntilRise kommen einfach die Sekunden bis zum nächsten Transit der ISS.

Was aber, wenn der Zeitpunkt riseTime schon überschritten wurde, die API aber noch diesen Zeitpunkt als nächsten Überflug ausgibt? Hier liegt der Wert in riseTime unterhalb der aktuellen Uhrzeit currentTime. In diesem Fall nimmst du den zweiten Eintrag der API-Antwort, den du entsprechend mit [1] auswählst.

if (timeUntilRise < 0) { 
  duration = response[1]["duration"];
  riseTime = response[1]["risetime"];

Der Countdown

Apropos nächster Transit: Auf dem 7-Segment-Display soll ja ein Countdown bis zum nächsten Überflug erscheinen. Auch diesen Teil sparen wir hier aus, da du in diesem Tutorial nachlesen kannst, wie du ein 7-Segment-Display im Sketch ansteuerst. In diesem Projekt erfährst du alles zum Thema Countdown mit diesem Display.

Die LEGO ISS beleuchten

Nun wird es Zeit für etwas Licht. Wenn also die echte ISS über den Horizont steigt, soll die LEGO ISS beleuchtet werden.

Die beleuchtete LEGO ISS

Hier kommt der NeoPixel-Ring mit seinen LEDs ins Spiel. Du wirst es erraten haben, auch hierfür haben wir bereits ein Tutorial, in dem du nachlesen kannst, wie du einen NeoPixel-Ring verwendest.

Allerdings gehst du in diesem Projekt einen Schritt weiter: Der LED-Ring soll nicht nur aufleuchten, sondern auch noch für die Dauer des Überflugs seine Farbe verändern. Hierfür benötigst du wieder die Variable duration, die du oben mit der Überflugsdauer befüllt hast.

Diese Variable kommt nun in der Funktion map() zum Einsatz. Hier berechnest du jede Sekunde einen neuen Farbwert, den alle 12 LEDs auf dem Ring annehmen sollen – von Blau zu Beginn des Überflugs bis zu einem tiefen Rot, kurz bevor die ISS wieder hinter dem Horizont verschwindet.

for (int i = duration; i >= 0; i--) {
  int colorRed = map(i, 0, duration, 200, 0);
  int colorBlue = map(i, 0, duration, 0, 200);
  //Die neuen Farben auf dem NeoPixel ausgeben
  for (int j = 0; j < 12; j++) {
    pixels.setPixelColor(j, pixels.Color(colorRed, 0, colorBlue));
    pixels.show();
  }
  delay(1000);
}

Die Funktion map() ist auf den ersten Blick nicht ganz leicht zu verstehen, aber die offizielle Arduino-Dokumention erhellt dieses Thema ziemlich gut.

Experimentiere mit den RGB-Werten für den NeoPixel etwas herum, um den Farbverlauf nach deinen Wünschen anzupassen. Oder lösche diese Funktion ganz, um die ISS in nur einer Farbe zu beleuchten.

Und das war es. 🙂

]]>
Eine Fotofalle mit der ESP32-CAM und Telegram https://polluxlabs.net/esp8266-projekte/eine-fotofalle-mit-der-esp32-cam-und-telegram/ Sun, 04 Oct 2020 09:43:39 +0000 https://polluxlabs.net/?p=2870 ESP32- und ESP8266-Boards sind zusammen mit Telegram eine tolle Kombination. Du kannst Daten blitzschnell auf dein Smartphone senden und von dort aus deinen Microcontroller steuern.

In diesem Projekt baust du eine Fotofalle mit einem speziellen ESP32-Board – der ESP32-CAM. Diese Falle schnappt zu, sobald sich jemand vor der Kamera bewegt. Dann nimmt sie ein Foto auf und sendet es umgehend an dein Smartphone.

Fotofalle mit ESP32-CAM und Telegram

Anfänger

1 Stunde

ca. 25 €

Für dieses Projekt benötigst du (Mengen s. Beschreibung):

Bevor du loslegen kannst, musst du noch ein paar Minuten in Vorbereitungen stecken.

Telegram vorbereiten

Zunächst benötigst du einen Account bei Telegram – und die dazugehörige App für dein Smartphone oder den Computer. Im Folgenden verwenden wir ein Smartphone. Telegram ist kostenlos, werbefrei und funktioniert so ähnlich wie WhatsApp. Allerdings hast du hier die Möglichkeit, Bots zu erstellen, mit denen du interagieren kannst.

In diesem Tutorial auf Pollux Labs lernst du, wie du deinen eigenen Telegram-Bot erstellst.

Die Fotofalle aufbauen

Die ESP32-CAM hat leider keinen USB-Port, weswegen du auf einen FTDI-Adapter zurückgreifen musst, um Sketches hochladen zu können. Wenn du hier noch keine Erfahrung sammeln konntest, informiere dich in diesem Tutorial darüber, wie du die ESP32-CAM programmieren kannst.

___STEADY_PAYWALL___

Wenn du den USB/Serial-Adapter verbunden hast, fehlt nur noch der Bewegungssensor. In diesem Projekt verwenden wir den PIR-Sensor HC-SR501, für den du nur einen Daten-Pin benötigst.

Orientiere dich beim Aufbau an diesem Schema:

Aufbau Fotofalle ESP32-CAM und PIR-Sensor

Hier noch einmal die Verbindungen übersichtlich dargestellt:

FTDI-AdapterESP32-CAM
GNDGND
VCC5V
TXDU0R
RXDU0T
HC-SR501ESP32-CAM
VCC3v3
GNDGND
OUTIO13

Hinweis: Im Schema oben findest du eine Brücke in Grau – denke immer daran, dass du diese benötigst, um einen Sketch hochzuladen. Sobald der Code auf deiner ESP32-CAM ist, trenne die Brücke und drücke den RESET-Button auf dem Board. Erst dann startet dein Sketch.

Das passende Gehäuse

Möchtest du die Fotofalle diskret aufbauen? Oder passt ein Breadboard nicht in das Design des Raums drumherum? Dann sind unsere Gehäuse die richtige Wahl:

Du kannst die .STL Dateien für die Gehäuse hier bei uns herunterladen.

Der Sketch

Für dieses Projekt haben wir einen Sketch von Random Nerd Tutorials als Ausgangspunkt genommen und diesen erweitert. Rui Santos verwendet hier die ESP32-CAM, um bei einer Bewegung ein Foto auf eine SD-Karte zu speichern.

Wir haben den Aufbau und den Code so modifiziert, dass dein Board das Foto an deinen Telegram-Bot, also an dein Smartphone sendet, sobald eine Bewegung erkannt wurde.

Du findest den gesamten Sketch auf Github als .txt und am Ende dieses Artikels.

Wichtige Anpassungen im Code der Fotofalle

Damit der Sketch bei dir funktioniert, musst du ein paar Kleinigkeiten anpassen. Trage zunächst die Zugangsdaten deines WLAN-Netzwerks ein, damit sich die ESP32-CAM damit verbinden kann.

Anschließend benötigst du deinen Token und die UserID, die du beim Erstellen deines Telegram-Bots erhalten hast.

const char* ssid = "NETWORK";
const char* password = "PASSWORD";
String BOTtoken = "TOKEN"; 
String CHAT_ID = "USERID";

Eine Besonderheit, die du nach deinen Wünschen anpassen kannst, ist die Zeit zwischen den Fotos. Im Loop findest du einen delay() von 20 Sekunden. Das ist die Zeitspanne, in der der Bewegungssensor nach einem gesendeten Foto keine weitere Bewegung registriert.

Je kürzer du diese Zeitspanne einstellst, desto mehr Fotos erhältst du – sofern sich weiterhin jemand vor der Kamera bewegt.

Ebenso kannst du die Empfindlichkeit deines HC-SR501 einstellen. An ihm findest du zwei Potis: Wenn du die Platine nach oben drehst, kannst du am linken von ihnen die Empfindlichkeit einstellen. Experimentiere hier ein wenig herum, um die passende Einstellung für dich zu finden.

Die Bibliothek UniversalTelegramBot

Die Bibliothek UniversalTelegramBot.h übernimmt die Kommunikation mit deinem Telegram-Bot. Du findest sie im Bibliotheksverwalter der Arduino IDE – diese kann jedoch veraltet sein. Deshalb empfehlen wir dir, die Bibliothek hier bei uns herunterzuladen.

Anschließend musst du diese Bibliothek in deinen Sketch einbinden, indem du im Menü der Arduino IDE Sketch -> Bibliothek einbinden -> .ZIP-Bibliothek hinzufügen wählst und die gerade heruntergeladene ZIP-Datei auswählst.

Noch ein Hinweis: Dieses Projekt dient dem Ausprobieren. Wenn du die Fotofalle produktiv einsetzen möchtest, mache dir vorab Gedanken über die IT-Sicherheit und achte darauf, keine Persönlichkeitsrechte zu verletzen!

Hier nun der gesamt Sketch zum Rauskopieren:

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/telegram-esp32-cam-photo-arduino/

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.

  Adapted by Pollux Labs – https://polluxlabs.net
*/

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include "esp_camera.h"
#include <UniversalTelegramBot.h>

const char* ssid = "NETWORK";
const char* password = "PASSWORD";

// Initialize Telegram BOT
String BOTtoken = "TOKEN";  // dein Bot-Token vom Botfather)

// Trage hier deine User-ID ein
String CHAT_ID = "CHAT-ID";

bool sendPhoto = false;

WiFiClientSecure clientTCP;
UniversalTelegramBot bot(BOTtoken, clientTCP);

//Pin of the motion sensor
#define PIR_PIN 13

//CAMERA_MODEL_AI_THINKER
#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27

#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22


void configInitCamera() {
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;

  //init with high specs to pre-allocate larger buffers
  if (psramFound()) {
    config.frame_size = FRAMESIZE_UXGA;
    config.jpeg_quality = 10;  //0-63 lower number means higher quality
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 12;  //0-63 lower number means higher quality
    config.fb_count = 1;
  }

  // camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    delay(1000);
    ESP.restart();
  }

  // Drop down frame size for higher initial frame rate
  sensor_t * s = esp_camera_sensor_get();
  s->set_framesize(s, FRAMESIZE_CIF);  // UXGA|SXGA|XGA|SVGA|VGA|CIF|QVGA|HQVGA|QQVGA
}

String sendPhotoTelegram() {
  const char* myDomain = "api.telegram.org";
  String getAll = "";
  String getBody = "";

  camera_fb_t * fb = NULL;
  fb = esp_camera_fb_get();  
  if(!fb) {
    Serial.println("Camera capture failed");
    delay(1000);
    ESP.restart();
    return "Camera capture failed";
  }  
  
  Serial.println("Connect to " + String(myDomain));


  if (clientTCP.connect(myDomain, 443)) {
    Serial.println("Connection successful");
    
    String head = "--RandomNerdTutorials\r\nContent-Disposition: form-data; name=\"chat_id\"; \r\n\r\n" + CHAT_ID + "\r\n--RandomNerdTutorials\r\nContent-Disposition: form-data; name=\"photo\"; filename=\"esp32-cam.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n";
    String tail = "\r\n--RandomNerdTutorials--\r\n";

    uint16_t imageLen = fb->len;
    uint16_t extraLen = head.length() + tail.length();
    uint16_t totalLen = imageLen + extraLen;
  
    clientTCP.println("POST /bot"+BOTtoken+"/sendPhoto HTTP/1.1");
    clientTCP.println("Host: " + String(myDomain));
    clientTCP.println("Content-Length: " + String(totalLen));
    clientTCP.println("Content-Type: multipart/form-data; boundary=RandomNerdTutorials");
    clientTCP.println();
    clientTCP.print(head);
  
    uint8_t *fbBuf = fb->buf;
    size_t fbLen = fb->len;
    for (size_t n=0;n<fbLen;n=n+1024) {
      if (n+1024<fbLen) {
        clientTCP.write(fbBuf, 1024);
        fbBuf += 1024;
      }
      else if (fbLen%1024>0) {
        size_t remainder = fbLen%1024;
        clientTCP.write(fbBuf, remainder);
      }
    }  
    
    clientTCP.print(tail);
    
    esp_camera_fb_return(fb);
    
    int waitTime = 10000;   // timeout 10 seconds
    long startTimer = millis();
    boolean state = false;
    
    while ((startTimer + waitTime) > millis()){
      Serial.print(".");
      delay(100);      
      while (clientTCP.available()) {
        char c = clientTCP.read();
        if (state==true) getBody += String(c);        
        if (c == '\n') {
          if (getAll.length()==0) state=true; 
          getAll = "";
        } 
        else if (c != '\r')
          getAll += String(c);
        startTimer = millis();
      }
      if (getBody.length()>0) break;
    }
    clientTCP.stop();
    Serial.println(getBody);
  }
  else {
    getBody="Connected to api.telegram.org failed.";
    Serial.println("Connected to api.telegram.org failed.");
  }
  return getBody;
}

void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
  // Init Serial Monitor
  Serial.begin(115200);

  //Set PIR
  pinMode(PIR_PIN, INPUT);

  // Config and init the camera
  configInitCamera();

  // Connect to Wi-Fi
  WiFi.mode(WIFI_STA);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  clientTCP.setCACert(TELEGRAM_CERTIFICATE_ROOT); // Add root certificate for api.telegram.org
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
}

void loop() {
  Serial.println(digitalRead(PIR_PIN));
  if (digitalRead(PIR_PIN)) {
    Serial.println("Preparing photo");
    sendPhotoTelegram();
    delay(20000);
  }
}

Lade den obigen Sketch nun auf deine ESP32-CAM hoch. Achte darauf, dass du die Brücke für den Upload schließt, sie danach wieder öffnest und das Board neustartest mit dem Reset-Button. Im Seriellen Monitor solltest du nun sehen, dass sich das Board mit deinem WLAN verbindet und anschließend auf eine Bewegung wartet – hier sollten in schneller Folge Nullen durch den Seriellen Monitor jagen, bis dein PIR-Sensor eine Bewegung erkannt hat. Daraufhin sendet dir die ESP32-CAM ein Foto der aktuellen Situation an deinen Telegram-Bot.

Mögliche Fehler

Solltest du eine Fehlermeldung im Zusammenhang mit TELEGRAM_CERTIFICATE_ROOT bekommen, lade die aktuelle Version der Bibltiothek UniversalTelegramBot herunter. Möglicherweise ist die Version auf unserem Server bereits veraltet und noch nicht rechtzeitig von uns aktualisiert worden. Auf GitHub findest du die neueste Version – klicke dort rechts oben auf Code und anschließend auf Download ZIP.

]]>
Eine ESP32-CAM (AI Thinker) mit der Arduino IDE programmieren https://polluxlabs.net/arduino-tutorials/eine-esp32-cam-ai-thinker-mit-der-arduino-ide-programmieren/ Fri, 02 Oct 2020 09:08:19 +0000 https://polluxlabs.net/?p=2802 Du möchtest Fotos aufnehmen und sie auf einer SD-Karte speichern, übers Internet verschicken oder gleich einen Video-Stream einrichten? Dann ist die ESP32-CAM eine tolle Sache! Es gibt da nur einen Umstand: Das Board hat keinen USB-Port. Also was tun?

In diesem Tutorial lernst du, wie du deine ESP32-CAM über einen USB/Serial-Adapter direkt in der Arduino IDE programmierst. Du benötigst hierfür:

AZDelivery Kompatibel mit FT232RL USB zu TTL Serial Adapter für 3,3V und 5V kompatibel mit Arduino inklusive E-Book!
AZDelivery Kompatibel mit FT232RL USB zu TTL Serial Adapter für 3,3V und 5V kompatibel mit Arduino inklusive E-Book!
✅ AZDelivery Adapter kompatibel mit FT232RL USB zu TTL Serial für 3,3V und 5V.
6,99 €

Vorbereitungen in der Arduino IDE

Wenn du die ESP32-CAM noch nicht mit deiner Arduino IDE verbunden hast, hole das schnell nach. In diesem Tutorial erfährst du, wie du ESP32-Boards verfügbar machst.

Wenn du das erledigt hast, findest du im Menü Werkzeuge > Board den Eintrag AI Thinker ESP32-CAM – den brauchst du später.

Programmieren über einen Adapter

Da du die ESP32-CAM nicht direkt an deinen Computer anschließen kannst, benötigst du einen Adapter, der die serielle Kommunikation per USB ermöglicht. Diese werden auch FTDI-Adapter genannt – nach dem Unternehmen FTDI, das sich auf diese Anwendungen spezialisiert hat. Ein bisschen wie Tempo also. 🙂

Diese Adapter gibt es in vielen Ausführungen von vielen Herstellern. Oben haben wir dir einen verlinkt, den du zwischen 3,3V und 5V umschalten kannst – was ganz praktisch ist.

Um nun also einen Sketch hochzuladen, verbinde deine ESP32-CAM und den FTDI-Adapter wie folgt:

ESP32-CAM mit FTDI-Adapter programmieren

Hier noch einmal die Verbindungen übersichtlich dargestellt:

FTDI-AdapterESP32-CAM
GNDGND
VCC5V
TXDU0R
RXDU0T

Wichtig: Wenn du einen Sketch hochlädst, musst du an deiner ESP32-CAM den Pin IO0 mit GND verbinden – im Schema oben hellblau dargestellt. Um den Sketch später auszuführen, trenne diese Verbindung wieder und drücke den Reset-Button.

Wenn du den Adapter und die ESP32-CAM wie oben beschrieben miteinander verkabelt hast, verbinde den Adapter mit deinem Computer. Wähle dann in der Arduino IDE das Board AI Thinker ESP32-CAM und den richtigen Port aus und lade deinen Sketch hoch.

Funktioniert nicht?

Der häufigste Fehler ist die fehlende Verbindung zwischen IO0 und GND, die du beim Upload herstellen musst. Wenn du hierfür ein Kabel verwendest, prüfe es auf seine Funktion.

Eine weitere Fehlerquelle kann auch das USB-Kabel sein: Kannst du damit Daten übertragen oder liefert es nur Strom? Manchmal kommt es auch zu Problemen, wenn das Kabel zu lang ist – probiere einfach mal ein anderes.

Wenn der Fehler woanders liegen muss, findest du möglicherweise in der recht umfangreichen Dokumentation bei Random Nerd Tutorials (Englisch) eine Lösung.

]]>
Ein stiller Alarm mit Telegram und einem ESP8266 https://polluxlabs.net/esp8266-projekte/ein-stiller-alarm-mit-telegram-und-einem-esp8266/ Tue, 22 Sep 2020 21:04:52 +0000 https://polluxlabs.net/?p=2494 In diesem Projekt baust du dir einen Bewegungsmelder, der dir eine Nachricht schickt, wenn sich vor ihm jemand bewegt. Im Prinzip handelt es sich hierbei um einen stillen Alarm – also eine Alarmanlage, die keinen Lärm macht, sondern ganz diskret mitteilt, das etwas im Gang ist.

Zum Einsatz kommt hierbei der Sensor HC-SR501. Diese Art von Sensoren kennst du von den handelsüblichen Bewegungsmeldern, die das Licht anschalten, sobald jemand an ihm vorbeikommt.

Das ist ein sogenannter PIR-Sensor – PIR steht für Passive Infrared. Dieser Sensor erkennt also Infrarotstrahlung bzw. Wärme, die von Körpern abgestrahlt wird. Darüber hinaus springt er nur an, wenn sich dieser Körper bewegt – denn sonst würde er schließlich auch bei einer warmen Heizung Alarm schlagen.

Heißt also konkret: Sobald eine Person (oder auch eine Katze oder ein Hund) in die Reichweite des Bewegungsmelders gerät, registriert dieser die abgestrahlte Wärme sowie die Bewegung und gibt dir Bescheid.

Anfänger

1 – 2 Stunden

ca. 10 €

Für dieses Projekt benötigst du (Mengen s. Beschreibung):

Telegram vorbereiten

Zunächst benötigst du einen Account bei Telegram – und die dazugehörige App für dein Smartphone oder den Computer. Im Folgenden verwenden wir ein Smartphone. Telegram ist kostenlos, werbefrei und funktioniert so ähnlich wie WhatsApp. Allerdings hast du hier die Möglichkeit, Bots zu erstellen, mit denen du interagieren kannst.

Das machst du dir in diesem Projekt zunutze, indem du deinen ESP8266 mit deinem Telegram-Bot „sprechen“ lässt. Der Bot wiederum sendet dir daraufhin umgehend eine Benachrichtigung.

In diesem Tutorial auf Pollux Labs lernst du, wie du deinen eigenen Telegram-Bot erstellst.

Das Projekt aufbauen

Der Aufbau auf dem Breadboard geht ganz fix: Du musst nur den Bewegungssensor mit deinem ESP8266 verbinden:

Die Beschriftung der Pins am Sensor siehst du, wenn du die weiße Kappe abhebst. Verbinde den Sensor und deinen ESP8266 wie folgt:

HC-SR501ESP8266
GNDGND
OUTD5
VCC3v3

Das war es auch schon mit der Hardware. Werfen wir einen Blick auf den Code.

Der Sketch für den Bewegungsmelder

Nun wird es Zeit für etwas Code. Falls du noch nie einen ESP8266 mit der Arduino IDE verwendet hast: In diesem Tutorial lernst du, wie du deinen ESP8266 in der Arduino IDE verfügbar machen und programmieren kannst.

Die benötigten Bibliotheken

#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>

Du benötigst insgesamt drei Bibliotheken. Zwei davon sollten bei dir schon vorinstalliert sein: ESP8266WiFi.h und WiFiClientSecure.h – du benötigst sie für die Verbindung zu deinem WLAN-Netzwerk und zum Senden der Daten.

Die Bibliothek UniversalTelegramBot.h übernimmt die Kommunikation mit deinem Telegram-Bot. Du findest sie im Bibliotheksverwalter der Arduino IDE –diese kann jedoch veraltet sein. Deshalb empfehlen wir dir, die Bibliothek hier bei uns herunterzuladen.

Anschließend musst du diese Bibliothek in deinen Sketch einbinden, indem du im Menü der Arduino IDE Sketch -> Bibliothek einbinden -> .ZIP-Bibliothek hinzufügen wählst und die gerade heruntergeladene ZIP-Datei auswählst.

Kopiere dir nun den folgenden Sketch und lade ihn auf deinen ESP8266.

Hinweis: Bevor dein Telegram-Bot Nachrichten empfangen kann, musst du ihn erst aufrufen und auf Start tappen. Das musst du allerdings nur einmal zu Beginn tun.

Sketch als .txt anschauen

/*
   Stiller Alarm mit Telegram - polluxlabs.net
*/

//Bibliotheken
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>

// Deine WLAN-Zugangsdaten
const char* ssid = "NETZWERKNAME";
const char* password = "PASSWORT";

// Den Telegram-Bot initialisieren
#define botToken "DEIN TOKEN"  // den Bot-Token bekommst du vom Botfather)

//Deine User ID
#define userID "DEINE USERID"

WiFiClientSecure client;
UniversalTelegramBot bot(botToken, client);

const int sensor = 14; // Pin des Bewegungssensors, auf dem ESP Pin D5
bool motion = false;

// Indicates when motion is detected
void IRAM_ATTR detectingMovement() {
  motion = true;
}

//Verbindung zum WLAN
void connectToWiFi() {
  Serial.print("Verbinde mich mit: ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(300);
  }
  Serial.println("");
  Serial.println("Verbunden!");
}

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

  // Verwendeter Pin
  pinMode(sensor, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(sensor), detectingMovement, RISING);
}

void loop() {

  if (motion == true) {
    connectToWiFi();
    bot.sendMessage(userID, "Hier bewegt sich etwas!", "");
    Serial.println("Bewegung erkannt");
    motion = false;
    WiFi.disconnect();
  }
}

Der Sketch Schritt für Schritt

Schauen wir uns nun ein paar wichtige Teile des Sketchs für deinen Bewegungsmelder genauer an.

Zunächst gibt es einige Daten, die du durch deine eigenen ersetzen musst: Deine WLAN-Zugangsdaten sowie dein Token und deine User ID von Telgram. Trage diese Daten hier ein:

const char* ssid = "DEIN WLAN-NETZWERK";
const char* password = "DEIN PASSWORT";
#define botToken "DEIN TOKEN"
#define userID "DEINE USER ID"

Anschließend erstellst du eine Instanz von WiFiClientSecure names client und ebenso einen bot mit deinem oben definierten botToken und dem client.

WiFiClientSecure client;
UniversalTelegramBot bot(botToken, client);

Jetzt fehlen noch eine Konstante und eine Variable. In ersteren legst du du den Pin fest, an dem der Sensor angeschlossen ist. Hinweis: Die Zahlen auf dem ESP8266 und im Sketch unterscheiden sich. Wenn du den Sensor am Pin D5 angeschlossen hast, entspricht das im Sketch der 14.

Die Variable motion setzt du hier zu Beginn des Sketchs auf false. Diese Variable kann zwei Zustände haben: false, wenn keine Bewegung erkannt wird und true, wenn genau das der Fall ist. Du benötigst also den Datentyp bool, der nur diese zwei Werte annehmen kann.

const int sensor = 14; // Pin des Sensors, am ESP8266 Pin D5
bool motion = false; // Variable für eine erkannte Bewegung

Die Funktionen im Sketch

Jetzt kommt die erste Funktion ins Spiel. Diese Callback-Funktion wird aufgerufen, sobald der Sensor eine Bewegung erkennt und eine 1 (HIGH) an deinen ESP8266 sendet.

Die Funktion macht nichts anderes als die gerade definierte Variable motion auf true zu setzen. Das wiederum setzt die Nachricht an deinen Telegram-Bot in Gang – was wir uns gleich genauer anschauen werden.

void IRAM_ATTR detectingMovement() {
  motion = true;
}

Zunächst steht im Sketch jedoch noch eine weitere Funktion, die deinen ESP8266 mit dem Internet verbindet, sobald eine Bewegung erkannt wurde.

void connectToWiFi() {
  Serial.print("Verbinde mich mit: ");
  Serial.println(ssid);

  WiFi.begin(ssid, password); //hast du zu Beginn hinterlegt
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(300);
  }
  Serial.println("");
  Serial.println("Verbunden!");
}

Die Setup-Funktion

Auch hier musst du noch ein paar grundlegende Dinge erledigen: Den seriellen Monitor starten, den client konfigurieren und den pinMode für den angeschlossenen Sensor festlegen. 

Mit der Funktion attachInterrupt definierst du die Bewegungsmeldung. Im ersten Parameter steht der Pin, an dem der Sensor angeschlossen ist. Im zweiten, was passieren soll, wenn etwas erkannt wurde: die Funktion detectingMovement() ausführen. Der dritte Parameter definiert, wann das passieren soll – nämlich wenn das Signal am Pin von LOW auf HIGH wechselt.


Genauere Erläuterungen dieser Funktion findest du in der Arduino-Referenz.

void setup() {
  Serial.begin(115200);
  client.setInsecure();
  pinMode(sensor, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(sensor), detectingMovement, RISING);
}

Der Loop

Hier kommt also der entscheidende Teil des Sketches. Der Loop läuft die meiste Zeit, ohne dass er irgendetwas ausführt. Erst wenn der Sensor ein Signal sendet und der Interrupt die Variable motion auf true setzt, wird die Bedinung im If-Statement erfüllt und einiges in Gang setzt.
Zunächst wird die Funktion connectToWiFi() aufgerufen und dein ESP8266 verbindet sich mit dem Internet.

Die Nachricht senden

Anschließend sendet er mit bot.sendMessage() eine Nachricht an deinen Telegram-Bot. 

Das ist sozusagen das Herzstück deines Sketchs. Die Funktion sendMessage() benötigt drei Argumente: 

  1. deine userID, die du oben im Sketch eingetragen hast
  2. eine Nachricht deiner Wahl als String
  3. den Parse Mode – Optional, lassen wir in diesem Projekte leer

Wenn also dein ESP8266 mit dem Internet verbunden ist, sollte nach wenigen Sekunden die Nachricht auf deinem Smartphone erscheinen.

Zuletzt wird die Variable motion wieder auf false gesetzt und die Verbindung zum Internet gekappt.

void loop() {

  if (motion == true) {
    connectToWiFi();
    bot.sendMessage(userID, "Hier bewegt sich etwas!", "");
    Serial.println("Bewegung erkannt");
    motion = false;
    WiFi.disconnect();
  }
}

Danach befindet sich dein Projekt wieder im Ausgangszustand und wartet auf die nächste Bewegung.
Falls noch nicht geschehen, lade den Sketch auf deinen ESP8266 und probiere ihn gleich aus. Und vergiss nicht: Vor dem ersten Test musst du deinen Bot zunächst einmalig starten, indem du auf start tappst.

Feinjustierung des Sensors

Auf der Unterseite des Sensors findest du zwei Potentiometer. Wenn du Sensor umdrehst, sodass die Platine nach oben zeigt, kannst du am linken Potentiometer die Empfindlichkeit einstellen. Experimentiere hiermit etwas, um die optimale Reichweite des Bewegungsmelders einzustellen.

Am rechten Potentiometer lässt sich einstellen, wie lange der Sensor ein HIGH-Signal sendet, bevor er wieder zurückgesetzt wird und neue Bewegungen erkennen kann. Diese Funktion nutzt du in diesem Projekt allerdings nicht, da der Sensor hier einen Interrupt triggert. Wie lange der Sensor das HIGH-Signal sendet, spielt hier keine Rolle.

Wie geht es weiter?

Du hast nun einen Bewegungsmelder, den du als stillen Alarm einsetzen kannst und der dir Nachrichten an deinen Telegram-Bot sendet. Im zweiten Teil dieser Projektserie überwachst du die Temperatur: Sobald ein von dir bestimmter Wert überschritten wurde, erhältst du eine Nachricht aufs Smartphone.

Auf pollux labs haben wir viele Tutorials und Projekte, die sich mit Temperatur- und anderen Sensoren beschäftigen.

Viel Spaß beim Ausprobieren! 🙂

]]>
Mehr Möglichkeiten mit IFTTT PRO https://polluxlabs.net/news/mehr-moeglichkeiten-mit-ifttt-pro/ Wed, 16 Sep 2020 11:15:45 +0000 https://polluxlabs.net/?p=2450 Der Service IFTTT (If This Then That) bietet seit kurzem ein PRO-Upgrade an. Wer sich dafür entscheidet, erhält die Möglichkeit, komplexere Anwendungen zu bauen. Bisher gab es nur die Möglichkeit, eine einzige Bedingung und eine daraus resultierende Aktion zu definieren.

Auch hier bei pollux labs haben wir eine Alarmanlage, die IFTTT nutzt: Ein Radar-Sensor registriert eine Bewegung, der angeschlossene ESP8266 triggert IFTTT und du erhältst eine Benachrichtigung.

Laut IFTTT können User mit PRO-Account also künftig sogenannte Applets bauen, die

  • mehrere Schritte ausführen
  • selbstständig Abfragen ausführen, um weitere Daten einzuholen
  • wiederum andere Applets triggern und
  • schneller ausgeführt werden

Leider gibt es auch eine Schattenseite: Der Gratis-Account mit Einführung von IFTTT PRO auf drei eigene Applets beschränkt. Vorgefertigte Applets von Unternehmen und anderen Usern kannst du jedoch weiterhin unbegrenzt nutzen. Auf der Webseite des Services findest du weitere Informationen.

Interessant ist die Preisgestaltung zur Einführung: Regulär wird der PRO-Account 9,99$ kosten. Momentan kannst du dir den Preis dafür jedoch selbst aussuchen – solange er über 1,99$ liegt. Dieses Angebot gilt bis zum 7. Oktober 2020. Einen Gratis-Testphase gibt es leider nicht.

Wenn du also eine Projektidee im Kopf hast, für die du IFTTT verwenden möchtest, die jedoch zu komplex für den Gratis-Account ist, könnte sich ein Test für dich lohnen.

]]>