ESP8266 Projekte – Pollux Labs https://polluxlabs.net Arduino, ESP32 & ESP8266 | Projekte & Tutorials Wed, 06 Nov 2024 11:05:34 +0000 de hourly 1 https://wordpress.org/?v=6.6.2 https://polluxlabs.net/wp-content/uploads/2020/05/cropped-pollux-labs-p-32x32.png ESP8266 Projekte – Pollux Labs https://polluxlabs.net 32 32 MQTT (Teil4): Sichere Kommunikation zwischen Geräten https://polluxlabs.net/esp8266-projekte/mqtt-teil4-sichere-kommunikation-zwischen-geraeten/ Thu, 10 Oct 2024 08:42:42 +0000 https://polluxlabs.net/?p=17423 MQTT (Teil4): Sichere Kommunikation zwischen Geräten Weiterlesen »

]]>
In den letzten Teilen der MQTT-Reihe hast du schrittweise die Kommunikation zwischen zwei ESP8266 eingerichtet und Sensordaten ausgetauscht. Ein wichtiger Baustein fehlt allerdings noch: die sichere Kommunikation. Wenn du nicht sicherstellst, dass der Austausch von Daten privat bleibt, könnte sich jemand in dein System einschleusen und einiges Chaos stiften.

In diesem Tutorial lernst du, wie du eine Authentifizierung integrierst, sodass deine ESP8266 einen Benutzernamen und ein Passwort benötigen, um mit dem MQTT-Broker kommunizieren zu können. Das bietet zwar immer noch keine 100% Sicherheit (falls es diese überhaupt gibt), aber wenn wir davon ausgehen, dass du keine kritischen Anwendungen mit MQTT aufbaust, sollte diese Authentifizierung schon ein großer Schritt in die richtige Richtung sein.

Konfiguration des MQTT-Brokers

Zunächst müssen wir den Mosquitto-Broker auf dem Raspberry Pi konfigurieren, um die Authentifizierung zu aktivieren.

Schritt 1: Erstellen der Passwortdatei

Hierfür verwendest du den Befehl mosquitto_passwd, um eine Passwortdatei zu erstellen und Benutzer hinzuzufügen. Gib im Terminal den folgenden Befehl ein – ersetze hierbei <username> mit einem Benutzernamen deiner Wahl:

sudo mosquitto_passwd -c /etc/mosquitto/passwd <username>

Anschließend wirst du aufgefordert, ein Passwort einzugeben. Dieses sollte natürlich möglichst sicher sein – Tipps für sichere Passwörter findest du z.B. auf der Webseite des BSI. Dieses Passwort wirst du später noch in den Sketches der ESP8266 benötigen.

Falls du noch einen weiteren Benutzer anlegen möchtest, ohne die Passwortdatei erneut zu erstellen, verwenden den folgenden Befehl, in dem du <username> und <password> entsprechend ersetzt:

sudo mosquitto_passwd -b /etc/mosquitto/passwd <username> <password>

Schritt 2: Konfiguration von Mosquitto

Nun widmest du dich der Konfigurationsdatei von Mosquitto, die du mit dem folgenden Befehl öffnest:

sudo nano /etc/mosquitto/mosquitto.conf

Füge am Ende der Datei die folgenden Zeilen hinzu:

allow_anonymous false
password_file /etc/mosquitto/passwd

Mit diesen beiden Zeilen deaktivierst du den anonymen Zugriff und gibst den Pfad zur Passwortdatei an.

Schritt 3: Neustart des Mosquitto-Dienstes

Sollte dein MQTT-Broker noch laufen, starte ihn mit dem folgenden Befehl neu, um deine Änderungen zu übernehmen:

sudo systemctl restart mosquitto

Konfiguration der ESP8266-Clients

Nun kommen die beiden ESP8266 dran. Hier änderst du deine Sketches, sodass sie sich mit Benutzernamen und Passwort beim Broker authentifizieren. Entscheiden sind die folgenden Änderungen. Zunächst die Integration des Benutzernamen und zugehörigen Passworts, dass du anfangs erstellt hast – hinterlege diese Informationen am Anfang des Sketchs (hinter den Infos zu WLAN und IP-Adresse des Raspberry Pis):

const char* mqtt_user = "Dein_MQTT_Benutzername";
const char* mqtt_password = "Dein_MQTT_Passwort";

Diese beiden Konstanten verwendest du anschließend, wenn der ESP8266 sich mit dem Broker verbinden möchte. Die Funktion reconnect() aus den vorherigen Teilen sieht dann folgendermaßen aus:

void reconnect() {
  while (!client.connected()) {
    Serial.print("Verbinde mit MQTT...");
    if (client.connect("ESP8266Client", mqtt_user, mqtt_password)) {
      Serial.println("verbunden");
      client.subscribe("esp8266/light");
    } else {
      Serial.print("Fehler, rc=");
      Serial.print(client.state());
      Serial.println(" Nächster Versuch in 5 Sekunden");
      delay(5000);
    }
  }
}

Passe deine Sketches für die ESP8266 nun an und teste die Verbindung. Funktioniert alles wie vorher? Dann hast du ein MQTT-System jetzt erfolgreich abgesichert!

]]>
MQTT (Teil 3): Daten senden und empfangen https://polluxlabs.net/esp8266-projekte/mqtt-teil-3-daten-senden-und-empfangen/ Wed, 09 Oct 2024 09:29:58 +0000 https://polluxlabs.net/?p=17331 MQTT (Teil 3): Daten senden und empfangen Weiterlesen »

]]>
Im letzten Teil hast du den MQTT-Broker kennengelernt und erfahren, wie du ihn mit Mosquitto auf einem Raspberry Pi einrichtest. Nun wird es Zeit für etwas Kommunikation. In diesem Tutorials lernst du, wie du Daten von einem ESP8266 an den Broker sendest. Ein zweiter ESP8266 hat diese Daten abonniert und reagiert entsprechend. Konkret: Du sendest die aktuelle Lichtstärke und wenn es dunkel wird, geht woanders das Licht an.

Für diese Lektion brauchst du folgende Teile:

  • 2x ESP8266-Modul
  • 1x Lichtsensor plus 10kΩ Widerstand
  • 1x LED plus passendem Vorwiderstand
  • Breadboards und Kabel

MQTT-Bibliotheken für Arduino

Um MQTT mit dem ESP8266 zu nutzen, verwendest du die Bibliothek PubSubClient von Nick O’Leary. Sie ist speziell für die Verwendung mit der Arduino-IDE und ESP8266 optimiert.

Installation der PubSubClient-Bibliothek:

  1. Öffne die Arduino-IDE.
  2. Gehe zu Sketch > Bibliothek einbinden > Bibliotheken verwalten (oder klicke auf den entsprechenden Button im Menü links)
  3. Suche nach “PubSubClient” und installiere die neueste Version.
PubSubClient in der Arduino IDE installieren

Daten an den MQTT-Broker senden

Lass uns einen ersten Sketch erstellen, der eine Verbindung zum MQTT-Broker herstellt und eine Nachricht published.

//Nachrichten an den MQTT-Broker senden

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

const char* ssid = "DEIN_WLAN_SSID";
const char* password = "DEIN_WLAN_PASSWORT";
const char* mqtt_server = "IP_ADRESSE_DES_BROKERS";

WiFiClient espClient;
PubSubClient client(espClient);

void setup() {
  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
}

void setup_wifi() {
  delay(10);
  Serial.println();
  Serial.print("Verbinde mit ");
  Serial.println(ssid);

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

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

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

void reconnect() {
  while (!client.connected()) {
    Serial.print("Verbinde mit MQTT...");
    if (client.connect("ESP8266Client")) {
      Serial.println("verbunden");
    } else {
      Serial.print("Fehler, rc=");
      Serial.print(client.state());
      Serial.println(" Nächster Versuch in 5 Sekunden");
      delay(5000);
    }
  }
}

void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  client.publish("esp8266/test", "Hallo vom ESP8266!");
  delay(5000);
}

Ersetze DEIN_WLAN_SSID, DEIN_WLAN_PASSWORT und IP_ADRESSE_DES_BROKERS durch deine eigenen Werte. Falls du die IP deines Raspberry Pis (also des laufenden Brokers) nicht kennst, gib im Terminal Folgendes ein:

hostname -I

Im Terminal siehst du dann die IP-Adresse, zum Beispiel 192.168.0.143

Das Sketch stellt eine Verbindung zum WLAN und dem MQTT-Broker her. In der loop()-Funktion wird alle 5 Sekunden eine Nachricht an das Topic esp8266/test gesendet.

Lade das Sketch auf deinen ESP8266 hoch und öffne den Seriellen Monitor. Du solltest sehen, wie der ESP8266 eine Verbindung herstellt und Nachrichten sendet.

Auf dem Raspberry Pi kannst du die Nachrichten mit mosquitto_sub empfangen. Gibt hierfür den folgenden Befehl in einem neuen Terminal-Fenster (während Mosquitto aktiv ist) ein:

mosquitto_sub -t esp8266/test

So weit, so gut. Als Nächstes testest du die andere Richtung und sendest du Nachrichten vom MQTT-Broker zum ESP8266

Daten vom MQTT-Broker empfangen

In diesem Test installierst du an deinem ESP8266 eine LED, die du vom Terminal (also vom Broker) aus an- und ausschaltest. Verbinde die LED wie folgt:

LED am ESP8266

Nun lade den folgenden Sketch auf deinen ESP8266 – hinterlege jedoch zunächst wieder dein Netzwerk, Passwort und die IP-Adresse des Brokers:

//Nachrichten vom MQTT-Broker empfangen

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

const char* ssid = "DEIN_WLAN_SSID";
const char* password = "DEIN_WLAN_PASSWORT";
const char* mqtt_server = "IP_ADRESSE_DES_BROKERS";

WiFiClient espClient;
PubSubClient client(espClient);

const int ledPin = D5;

void setup() {
  pinMode(ledPin, OUTPUT);
  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
}

void setup_wifi() {
  delay(10);
  Serial.println();
  Serial.print("Verbinde mit ");
  Serial.println(ssid);

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

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

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

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Nachricht empfangen [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();

  if ((char)payload[0] == '1') {
    digitalWrite(ledPin, HIGH);  // LED einschalten
  } else {
    digitalWrite(ledPin, LOW);  // LED ausschalten
  }
}

void reconnect() {
  while (!client.connected()) {
    Serial.print("Verbinde mit MQTT...");
    if (client.connect("ESP8266Client")) {
      Serial.println("verbunden");
      client.subscribe("esp8266/led");
    } else {
      Serial.print("Fehler, rc=");
      Serial.print(client.state());
      Serial.println(" Nächster Versuch in 5 Sekunden");
      delay(5000);
    }
  }
}

void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();
}

So funktioniert der Sketch

Interessant in diesem Sketch sind die folgenden zwei Funktionen:

  • Die callback()-Funktion wird aufgerufen, wenn eine Nachricht auf einem abonnierten Topic eintrifft. Wir überprüfen den Inhalt der Nachricht und schalten die LED entsprechend ein oder aus.
  • In der reconnect()-Funktion abonnieren wir das Topic “esp8266/led”, um Steuerbefehle für die LED zu empfangen.

Nachdem du den Sketch auf den ESP8266 geladen hast, kannst du die Funktion wieder im Terminal testen. Steuere dort die LED mit einem der folgenden Befehle. Ersetze IP_ADRESSE_DES_BROKERS hierbei mit der tatsächlichen IP-Adresse deines Raspberry Pi:

mosquitto_pub -h IP_ADRESSE_DES_BROKERS -t "esp8266/led" -m "1"  # LED einschalten
mosquitto_pub -h IP_ADRESSE_DES_BROKERS -t "esp8266/led" -m "0"  # LED ausschalten

Funktioniert? Dann wird es Zeit für den letzten Teil: Hier lässt du zwei ESP8266 miteinander sprechen.

Kommunikation zwischen zwei ESP8266

Bisher hast du das Terminal verwendet, um den MQTT-Broker zu bedienen, also Nachrichten an einen ESP8266 zu senden oder von dort zu empfangen. Aber eigentlich sollte der Broker seine Arbeit im Hintergrund erledigen und nur ein Postbote sein, von dem du nicht viel mitbekommst. Nun sollen also zwei ESP8266 miteinander kommunizieren: Am ersten ist ein Lichtsensor installiert, der die Lichtstärke misst. Fällt diese unter einen bestimmten Wert, sendet er eine entsprechende Nachricht an den Broker. Ein zweiter ESP8266 hat diese Nachrichten abonniert und schaltet bei Bedarf eine LED ein.

Die LED hast du ja bereits installiert. Am anderen ESP8266 verbindest du den Lichtsensor wie folgt:

Lichtsensor am ESP8266

Der Sketch für den Sender

Für den Sender der Nachrichten, also den ESP8266 mit dem angeschlossenen Lichtsensor benötigst du folgenden Sketch:

//Lichtstärke senden

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

const char* ssid = "DEIN_WLAN_SSID";
const char* password = "DEIN_WLAN_PASSWORT";
const char* mqtt_server = "IP_ADRESSE_DES_BROKERS";

WiFiClient espClient;
PubSubClient client(espClient);

const int lightSensorPin = A0;
const int lightThreshold = 500;

void setup() {
  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
}

void setup_wifi() {
  delay(10);
  Serial.println();
  Serial.print("Verbinde mit ");
  Serial.println(ssid);

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

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

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

void reconnect() {
  while (!client.connected()) {
    Serial.print("Verbinde mit MQTT...");
    if (client.connect("ESP8266Client_Publisher")) {
      Serial.println("verbunden");
    } else {
      Serial.print("Fehler, rc=");
      Serial.print(client.state());
      Serial.println(" Nächster Versuch in 5 Sekunden");
      delay(5000);
    }
  }
}

void loop() {
  if (client.connected()) {
    client.loop();

    int lightValue = analogRead(lightSensorPin);
    Serial.print("Lichtwert: ");
    Serial.println(lightValue);

    if (lightValue < lightThreshold) {
      client.publish("esp8266/light", "1");  // LED einschalten
      Serial.println("MQTT Message Sent: 1");
    } else {
      client.publish("esp8266/light", "0");  // LED ausschalten
      Serial.println("MQTT Message Sent: 0");
    }

    delay(5000);
  } else {
    reconnect();
  }
}

Oben im Sketch hast du (neben den obligatorischen WLAN- und Broker-Daten) die Möglichkeit, in der Konstanten lightThreshold den Schwellenwert für den Lichtsensor einzustellen. Nach dem Upload siehst du den aktuellen Wert im Seriellen Monitor. Nutze diese Informationen, um einen passenden Schwellenwert für deine Anwendung zu definieren.

Der Sender misst alle 5 Sekunden den Lichtwert und sendet dann die passende Nachricht per MQTT.

Der Sketch für den Empfänger

Nun soll dein zweiter ESP8266 mit der angeschlossenen LED auf diese Nachrichten reagieren. Lade hierfür den folgenden Sketch hoch:

//Lichtstärke empfangen

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

const char* ssid = "DEIN_WLAN_SSID";
const char* password = "DEIN_WLAN_PASSWORT";
const char* mqtt_server = "IP_ADRESSE_DES_BROKERS";

WiFiClient espClient;
PubSubClient client(espClient);

const int ledPin = D5;

void setup() {
  pinMode(ledPin, OUTPUT);
  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
}

void setup_wifi() {
  delay(10);
  Serial.println();
  Serial.print("Verbinde mit ");
  Serial.println(ssid);

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

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

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

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Nachricht empfangen [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();

  if ((char)payload[0] == '1') {
    digitalWrite(ledPin, HIGH);  // LED einschalten
  } else {
    digitalWrite(ledPin, LOW);  // LED ausschalten
  }
}

void reconnect() {
  while (!client.connected()) {
    Serial.print("Verbinde mit MQTT...");
    if (client.connect("ESP8266Client_Receiver")) {
      Serial.println("verbunden");
      client.subscribe("esp8266/light");
    } else {
      Serial.print("Fehler, rc=");
      Serial.print(client.state());
      Serial.println(" Nächster Versuch in 5 Sekunden");
      delay(5000);
    }
  }
}

void loop() {
  if (client.connected()) {
    client.loop();
  } else {
    reconnect();
  }
}

Dieser Sketch unterscheidet sich nur leicht von jenem, mit dem du die LED per Terminal gesteuert hast. Eine Sache ist jedoch wichtig, wenn du zwei ESP8266 miteinander über den MQTT-Broker kommunizieren lässt: Sie müssen unterschiedliche Namen haben.

Den Namen, mit dem sich dein ESP8266 beim Broker anmeldet, definierst du in der folgenden Funktion (recht weit unten im Sketch):

client.connect("ESP8266Client_Receiver")

Und das war es schon, mehr benötigst du für eine einfache Kommunikation zweier ESP8266 nicht. Teste nun alle drei Geräte (Broker, Sender und Empfänger) und schaue, ob die LED angeht, wenn das Licht ausgeht.

]]>
MQTT (Teil 1): Einführung https://polluxlabs.net/esp8266-projekte/teil-1-einfuehrung-in-mqtt/ Sat, 05 Oct 2024 10:42:42 +0000 https://polluxlabs.net/?p=17307 MQTT (Teil 1): Einführung Weiterlesen »

]]>
Diese Tutorial-Reihe dreht sich ganz um MQTT – dem Nachrichtenprotokoll, das sich hervorragend dafür eignet, Daten zwischen Geräten auszutauschen. Stell dir vor, du möchtest die Temperatur in deinem Gewächshaus überwachen. Dazu installierst du Temperatursensoren, die ihre Messwerte per MQTT an einen zentralen Broker senden. Der Broker verteilt die Daten dann an alle “interessierten” Empfänger, wie einen Mikrocontroller, der die Bewässerung steuert.

In dieser Einführung klären wir zunächst, worum es sich bei MQTT überhaupt handelt und wie es grundlegend funktioniert.

Was ist MQTT?

MQTT steht für “Message Queuing Telemetry Transport”. Es handelt sich um ein leichtgewichtiges, offenes Nachrichtenprotokoll, das sich ideal für die M2M-Kommunikation (Machine-to-Machine) eignet. MQTT ermöglicht es Geräten, auf einfache und effiziente Weise Daten auszutauschen.

Das Protokoll wurde 1999 von Andy Stanford-Clark (IBM) und Arlen Nipper (Cirrus Link) entwickelt. Heute hat sich MQTT zu einem Standard im IoT-Bereich entwickelt.

MQTT bietet einige Vorteile, die es für IoT-Anwendungen besonders attraktiv machen:

  • Leichtgewichtig: MQTT kommt mit minimaler Netzwerk-Bandbreite und geringen Hardwareanforderungen aus.
  • Publish/Subscribe-Modell: MQTT entkoppelt Sender (Publisher) und Empfänger (Subscriber) von Nachrichten. Geräte kommunizieren nicht direkt miteinander, sondern über einen Broker.
  • Skalierbarkeit: Das Protokoll ist hervorragend für große Netzwerke mit vielen Geräten geeignet.
  • Zuverlässigkeit: MQTT bietet drei Quality of Service (QoS) Levels, um unterschiedliche Anforderungen zu erfüllen.
  • Flexibilität: MQTT lässt sich leicht in bestehende Systeme integrieren und ist mit vielen Programmiersprachen und Plattformen kompatibel.

Wie funktioniert MQTT?

Um die Funktionsweise von MQTT zu verstehen, ist wie so oft ein Schaubild ganz hilfreich:

Schaubild: So funktioniert MQTT

Dieses Schaubild zeigt ein mögliches Smart-Home-Szenario. Lass uns die einzelnen Elemente durchgehen:

  1. Broker: Im Zentrum des Bildes siehst du einen grünen Kreis mit der Beschriftung “Broker”. Der Broker ist das Herzstück des MQTT-Systems und fungiert als zentraler Vermittler zwischen allen Geräten.
  2. Publisher (Sender): Auf der linken Seite sind drei blaue Kreise mit Thermometer-Symbolen zu sehen. Diese stellen die Publisher dar – in diesem Fall Temperatursensoren an verschiedenen Orten:
    • Ein Sensor im Haus (home/temperature) misst 19°C
    • Ein Sensor in der Garage (garage/temperature) misst 15°C
    • Ein Sensor im Garten (garden/temperature) misst -4°C
  3. Subscriber (Empfänger): Auf der rechten Seite befinden sich zwei rote Kreise mit Chip-Symbolen. Diese repräsentieren die Subscriber – Geräte, die Temperaturinformationen empfangen und verarbeiten:
    • Ein Gerät, das sich für die Haustemperatur interessiert und z.B. die Temperatur regelt.
    • Ein Gerät, das die Gartentemperatur überwacht und z.B. die Rollläden herunterlässt, um das Haus besser zu isolieren.
  4. Publish-Vorgang: Die schwarzen Pfeile von den Publishern zum Broker zeigen den Publish-Vorgang. Jeder Sensor sendet seine Messwerte mit einem spezifischen Topic (z.B. “home/temperature”) an den Broker.
  5. Subscribe-Vorgang: Die grauen Pfeile vom Broker zu den Subscribern zeigen den Subscribe-Vorgang. Die Subscriber teilen dem Broker mit, für welche Topics sie sich interessieren (z.B. “home/temperature” oder “garden/temperature”).
  6. Nachrichtenverteilung: Die schwarzen Pfeile vom Broker zu den Subscribern zeigen, wie der Broker die empfangenen Nachrichten an die interessierten Subscriber weiterleitet.

Das Besondere an MQTT ist die Entkopplung von Sendern und Empfängern. Die Temperatursensoren (Publisher) wissen nicht, wer ihre Daten empfängt. Die Empfangsgeräte (Subscriber) wissen nicht, woher die Daten genau kommen. Alles läuft über den zentralen Broker.

Dieses System ermöglicht eine flexible und effiziente Kommunikation in IoT-Netzwerken. Neue Geräte können einfach hinzugefügt werden, indem sie sich beim Broker für die relevanten Topics registrieren, ohne dass andere Geräte angepasst werden müssen.

Topics und Topic-Hierarchie

In MQTT werden Nachrichten immer zu einem bestimmten “Topic” (Thema) veröffentlicht. Topics sind Zeichenketten, die hierarchisch aufgebaut sein können, ähnlich wie Verzeichnispfade.

Ein paar Beispiele für Topics hast du bereits im Schaubild kennengelernt:

  • home/temperature
  • garage/temperature
  • garden/temperature

Topics werden mit Schrägstrichen (/) strukturiert. Das ermöglicht eine übersichtliche Ordnung und erleichtert das Abonnieren verwandter Themen.

Ein Subscriber kann nicht nur einzelne Topics, sondern auch ganze Topic-Zweige abonnieren. Dazu gibt es zwei Platzhalter:

  • Plus (+): Ersetzt eine einzelne Hierarchieebene. Beispiel: home/+/temperature.
  • Raute (#): Ersetzt beliebig viele Hierarchieebenen. Beispiel: home/#.

Quality of Service (QoS) Levels

Neben den Topics sind Quality of Service Levels ein wichtiger und interessanter Aspekt von MQTT. Unterschieden werden 3 Level, die die Zustellung und Verarbeitung von Nachrichten steuern:

QoS 0 (At most once):

  • Dies ist das einfachste und schnellste Level.
  • Der Sender schickt die Nachricht einmal an den Broker und vergisst sie dann.
  • Der Broker leitet die Nachricht an die Subscriber weiter, ohne eine Bestätigung zu erwarten.
  • Es gibt keine Garantie, dass die Nachricht ankommt.
  • Verwendung: Ideal für häufig gesendete, nicht kritische Daten wie regelmäßige Sensorwerte.
  • Beispiel: Ein Temperatursensor, der alle 5 Sekunden einen Wert sendet. Wenn mal ein Wert verloren geht, ist das nicht schlimm.

QoS 1 (At least once):

  • Der Sender speichert die Nachricht, bis er eine Bestätigung (PUBACK) vom Broker erhält.
  • Wenn keine Bestätigung kommt, sendet er die Nachricht erneut.
  • Der Broker leitet die Nachricht an Subscriber weiter und erwartet ebenfalls eine Bestätigung.
  • Die Nachricht kommt mindestens einmal an, kann aber auch mehrfach ankommen.
  • Verwendung: Gut für wichtige Nachrichten, bei denen Duplikate kein Problem sind.
  • Beispiel: Ein Schalter, der seinen Status meldet. Es ist wichtig, dass die Änderung ankommt, auch wenn sie vielleicht doppelt gemeldet wird.

QoS 2 (Exactly once):

  • Das zuverlässigste, aber auch komplexeste und langsamste Level.
  • Verwendet ein 4-Wege-Handshake zwischen Sender und Empfänger.
  • Garantiert, dass die Nachricht genau einmal ankommt, ohne Verlust oder Duplikate.
  • Verwendung: Für kritische Nachrichten, bei denen jede Nachricht wichtig ist und Duplikate Probleme verursachen würden.
  • Beispiel: Finanztransaktionen oder kritische Steuerungsbefehle in industriellen Anlagen.

Die Wahl des passenden QoS-Levels hängt also von den Anforderungen deiner Anwendung ab. In vielen Fällen reicht bereits der einfachste Level QoS 0 aus.

Retained Messages und Last Will and Testament

Zuletzt noch zwei weitere nützliche MQTT-Funktionen:

Retained Messages

Stell dir einen digitalen Notizzettel vor, der an einer Pinnwand (dem Broker) hängt. Jeder, der vorbeikommt (sich subscribt), kann sofort den letzten Stand sehen, ohne warten zu müssen, bis jemand eine neue Notiz macht.

Beispiel:

  • Du hast eine smarte Glühbirne im Wohnzimmer.
  • Der Lichtschalter sendet eine Retained Message “AN” oder “AUS” an das Topic “haus/wohnzimmer/licht”.
  • Wenn du eine neue App auf deinem Smartphone installierst und sie sich mit dem MQTT-Broker verbindet, weiß sie sofort, ob das Licht an oder aus ist, ohne auf die nächste Zustandsänderung warten zu müssen.

Last Will and Testament (LWT)

Stell dir vor, du gehst auf eine Party und sagst dem Gastgeber: “Wenn ich in der nächsten Stunde nicht wiederkomme, sag allen, ich musste plötzlich nach Hause.” Das ist im Prinzip das LWT.

Beispiel:

  • Du hast einen Temperatursensor im Gewächshaus.
  • Bei der Verbindung zum Broker richtet der Sensor ein LWT ein: “Gewächshaussensor offline”.
  • Wenn der Sensor unerwartet die Verbindung verliert (z.B. wegen eines Stromausfalls), sendet der Broker automatisch diese Nachricht.
  • So wissen alle interessierten Geräte, dass der Sensor nicht mehr aktiv ist, ohne auf ein Timeout warten zu müssen.

Das soll es zunächst mit Teil 1 der Reihe gewesen sein. Der nächste Teil beschäftigt sich mit dem Broker – dort erfährst du, wie du diesen auf einem Raspberry Pi einrichtest und wie das Senden und Abonnieren von Daten in der Praxis aussieht.

]]>
ESP32 Internetradio https://polluxlabs.net/esp8266-projekte/esp32-internetradio/ Wed, 07 Aug 2024 19:01:34 +0000 https://polluxlabs.net/?p=16902 ESP32 Internetradio Weiterlesen »

]]>
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:

Aufbau des einfachsten ESP32 Internetradios

Hier noch einmal als Tabelle:

VerstärkermodulESP32-S3
VIN3,3V
GNDGND
GAINGND
DINGPIO 2
BCLKGPIO 3
LRCGPIO 4

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 nicht im Bibliotheksmanager der Arduino IDE findest – dafür jedoch auf GitHub. Lade dir die Bibliothek als ZIP-Datei hier herunter. Erstelle nun in der Arduino IDE einen neuen Sketch und binde die sie im Menü über Sketch > Bibliothek einbinden > ZIP-Datei hinzufügen ein.

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.

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 Akkubetrieb

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.

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 betrieben mit einem 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 Potentiometer zur Lautstärkeregulierung

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 - Aufbau

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 6

#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 OLED-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:

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 6

#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! 🙂

]]>
Babyphone mit Geräusch- und Bewegungsmelder https://polluxlabs.net/esp8266-projekte/babyphone-mit-display-und-geraeusch-bewegungsmelder/ Sat, 06 Jul 2024 21:42:59 +0000 https://polluxlabs.net/?p=16774 Babyphone mit Geräusch- und Bewegungsmelder Weiterlesen »

]]>
Dieses Projekt dreht sich rund um den Schlaf von Babys und kleinen Kindern: Du baust ein Babyphone, das aus zwei ESP8266 besteht, die mit ESP-NOW miteinander verbunden sind. Am ersten Microcontroller schließt du einen PIR-Sensor, um Bewegungen zu erkennen sowie einen Geräuschsensor an, um die Lautstärke zu ermitteln.

Gesendet werden die Daten an einen zweiten ESP8266, an dem wiederum ein LED-Matrix-Display angeschlossen ist. Hierauf kannst du die aktuelle Lautstärke im Schlafzimmer ermitteln. Außerdem leuchtet das Display auf, wenn eine Bewegung erkannt wurde.

Den ESP8266 mit den Sensoren stellst du neben das Bett der oder des Kleinen und jenen mit dem Display dorthin, wo du dich selbst aufhältst. So hast du das Geschehen im Schlafzimmer immer im Blick.

Du lernst mit diesem Projekt, wie du

  • einen PIR- und einen Geräuschsensor am ESP8266 anschließt,
  • ein LED-Matrix-Display verwendest und
  • zwei ESP8266 per ESP-NOW miteinander verbindest.

Diese Bauteile benötigst du

Der Empfänger

Los geht es mit dem Teil des Babyphones, auf dessen Display du ablesen kannst, was sich im Schlaf- oder Kinderzimmer so tut. Hierfür verwendest du ein 8×8 LED-Matrix-Display. Auf der einen Seite des Displays erstellst du eine Art Equalizer, der dir die gemessene Lautstärke anzeigt. Die andere Seite leuchtet rot auf, sobald eine Bewegung erkannt und vom Sender weitergegeben wurde.

Verbinde das Matrix-Display wie folgt mit dem ESP8266:

DisplayESP8266
GNDGND
VCC3,3V
DIND5
CLDD6
CSD7

Als nächstes musst du die MAC-Adresse des ESP8266 herausfinden, um ESP-NOW für die Kommunikation zwischen Sender und Empfänger nutzen zu können. MAC steht für Media Access Control und ist eine eindeutige Adresse zur Identifikation eines Geräts in einem Netzwerk.

Um diese Adresse herauszufinden, kannst du folgenden kleinen Sketch auf den Empfänger hochladen und die MAC-Adresse im Seriellen Monitor ablesen:

#include <ESP8266WiFi.h>

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  Serial.println(WiFi.macAddress());
}

void loop() {}

Deine Adresse könnte zum Beispiel wie folgt aussehen: EC:FA:BC:6E:EE:F4

Diese Adresse wirst du gleich im Sketch des Senders benötigen, um die Messdaten auf dem Matrix-Display anzuzeigen. Zunächst jedoch der eigentliche Sketch, der die Lautstärke und Bewegungen entgegennimmt und visualisiert:

#include <ESP8266WiFi.h>
#include <espnow.h>
#include <LedControl.h>

// Pins des Displays
const int DIN_PIN = D5;
const int CLK_PIN = D6;
const int CS_PIN = D7;

// LED Matrix Setup
LedControl lc = LedControl(DIN_PIN, CLK_PIN, CS_PIN, 1);

// Struktur der zu empfangenden Daten
struct SensorData {
  bool motionDetected;
  int noiseLevel;
};

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

  // LED matrix initialisieren
  lc.shutdown(0, false);
  lc.setIntensity(0, 8);  // Helligkeit (0-15)
  lc.clearDisplay(0);

  // ESP-NOW initialisieren
  WiFi.mode(WIFI_STA);
  if (esp_now_init() != 0) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }

  // Funktion für die empfangenen Daten
  esp_now_register_recv_cb(onDataReceived);
}

void loop() {
  // Leer, da du die Funktion onDataReceived verwendest
}

void onDataReceived(uint8_t *mac, uint8_t *incomingData, uint8_t len) {
  SensorData data;
  memcpy(&data, incomingData, sizeof(data));

  // Update des Displays
  updateLEDMatrix(data.motionDetected, data.noiseLevel);

  // Empfangene Daten im Seriellen Monitor ausgeben
  Serial.print("Motion: ");
  Serial.print(data.motionDetected);
  Serial.print(" | Noise: ");
  Serial.println(data.noiseLevel);
}

void updateLEDMatrix(bool motionDetected, int noiseLevel) {
  lc.clearDisplay(0);

  // Bewegung anzeigen
  if (motionDetected) {
    for (int row = 0; row < 4; row++) {
      for (int col = 0; col < 8; col++) {
        lc.setLed(0, row, col, true);
      }
    }
  }

  // Lautstärke anzeigen
  int noiseLEDs = map(noiseLevel, 150, 300, 0, 8);
  for (int row = 4; row < 8; row++) {
    for (int col = 0; col < noiseLEDs; col++) {
      lc.setLed(0, row, col, true);
    }
  }
}

Erklärung des Sketchs

Lass uns einen genaueren Blick auf den Code in diesem Sketch werfen.

Bibliotheken und Definitionen

#include <ESP8266WiFi.h>
#include <espnow.h>
#include <LedControl.h>

const int DIN_PIN = D5;
const int CLK_PIN = D6;
const int CS_PIN = D7;

LedControl lc = LedControl(DIN_PIN, CLK_PIN, CS_PIN, 1);

struct SensorData {
  bool motionDetected;
  int noiseLevel;
};

Der Code verwendet die ESP8266WiFi- und espnow-Bibliotheken für die drahtlose Kommunikation und die LedControl-Bibliothek für die Steuerung der LED-Matrix. Letztere musst du wahrscheinlich noch über den Bibliotheksmanager installieren.

Die Pins für die LED-Matrix werden definiert (DIN, CLK, CS) und ein LedControl-Objekt erstellt, um die LED-Matrix zu steuern. Zuletzt erzeugst du eine Struktur, um die empfangenen Daten in Variablen zu speichern.

Setup-Funktion

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

  lc.shutdown(0, false);
  lc.setIntensity(0, 8);
  lc.clearDisplay(0);

  WiFi.mode(WIFI_STA);
  if (esp_now_init() != 0) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }

  esp_now_register_recv_cb(onDataReceived);
}
  • Die serielle Kommunikation wird initialisiert.
  • Die LED-Matrix wird eingeschaltet, die Helligkeit auf 8 (von 15) gesetzt und das Display gelöscht.
  • Der ESP8266 wird in den Station-Modus versetzt.
  • ESP-NOW wird initialisiert und ein Fehler wird gemeldet, falls die Initialisierung fehlschlägt.
  • Die Funktion onDataReceived wird als Callback für empfangene Daten registriert.

Hauptschleife

void loop() {
}

Der Loop des Sketchs belibt leer, da die Datenverarbeitung in der folgenden Funktion erfolgt.

Datenempfangs-Callback

void onDataReceived(uint8_t *mac, uint8_t *incomingData, uint8_t len) {
  SensorData data;
  memcpy(&data, incomingData, sizeof(data));

  updateLEDMatrix(data.motionDetected, data.noiseLevel);

  Serial.print("Motion: ");
  Serial.print(data.motionDetected);
  Serial.print(" | Noise: ");
  Serial.println(data.noiseLevel);
}
  • Diese Funktion wird aufgerufen, wenn Daten empfangen werden.
  • Die empfangenen Daten werden in die SensorData-Struktur kopiert.
  • Die LED-Matrix wird mit den empfangenen Daten aktualisiert.
  • Die empfangenen Daten werden im Seriellen Monitor ausgegeben.

LED-Matrix-Aktualisierung

void updateLEDMatrix(bool motionDetected, int noiseLevel) {
  lc.clearDisplay(0);

  if (motionDetected) {
    for (int row = 0; row < 4; row++) {
      for (int col = 0; col < 8; col++) {
        lc.setLed(0, row, col, true);
      }
    }
  }

  int noiseLEDs = map(noiseLevel, 150, 300, 0, 8);
  for (int row = 4; row < 8; row++) {
    for (int col = 0; col < noiseLEDs; col++) {
      lc.setLed(0, row, col, true);
    }
  }
}
  • Diese Funktion aktualisiert die LED-Matrix basierend auf den empfangenen Sensordaten.
  • Zuerst wird das Display gelöscht.
  • Wenn Bewegung erkannt wurde, werden die oberen 4 Reihen der Matrix vollständig beleuchtet.
  • Der Geräuschpegel wird auf die unteren 4 Reihen abgebildet. Die Anzahl der beleuchteten LEDs hängt vom Geräuschpegel ab, wobei Werte zwischen 150 und 300 auf 0 bis 8 LEDs abgebildet werden.

Dieser Code empfängt also kontinuierlich Daten von einem Sender, verarbeitet diese und stellt sie visuell auf einer LED-Matrix dar.

Wichtig ist hier noch die Map-Funktion, die im Code oben Werte zwischen 150 und 300 auf die acht Zeilen des Displays “mappt”. Das sind Werte, die du einerseits direkt am Geräuschsensor einstellst und andererseite experimentell ermitteln musst. Also wie weit nach oben geht der Wert des Sensors bei einer bestimmten Geräuschkulisse? Idealerweise werden alle 8 Zeilen erleuchtet, wenn eine maximal “übliche” Lautstärke zum Beispiel durch Schreien erreicht wird.

Der Sender des Babyphones

Kommen wir zum zweiten Teil des Babyphones – dem Sender samt des Geräusch- und Bewegungsmelders. Dieser ESP8266 ermittelt, ob sich das Kind bewegt oder gar weint oder schreit. Hierfür eignet sich ein PIR-Sensor, der Bewegungen im Dunkeln erkennt und dessen Empfindlichkeit du so einstellen kannst, dass nicht jedes Umdrehen im Bett gleich eine Meldung auslöst. Dazu kommt noch ein einfacher Geräuschsensor zum Einsatz, der die Lautstärke im Raum in eine Zahl zwischen 0 und 1023 darstellt.

Schließe zunächst beide Sensoren wie folgt am ESP8266 an:

Anschluss PIR-Sensor und KY-037 am ESP8266

Vom Geräuschsensor liest du den analogen Ausgang aus, was bedeutet, dass du den analogen Eingang A0 des ESP8266 verwenden musst. Bei der Wahl des Digitalpins hast du freie Wahl – im Folgenden ist das der Pin D1.

Kommen wir gleich zum Sketch:

#include <ESP8266WiFi.h>
#include <espnow.h>

const int PIR_PIN = D1;
const int NOISE_PIN = A0;

//Die MAC-Adresse des Empfängers
uint8_t receiverAddress[] = {0xEC, 0xFA, 0xBC, 0x6E, 0xEE, 0xF4};

struct SensorData {
  bool motionDetected;
  int noiseLevel;
};

void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) {
  Serial.print("Last Packet Send Status: ");
  if (sendStatus == 0){
    Serial.println("Delivery success");
  }
  else{
    Serial.println("Delivery fail");
  }
}

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

  WiFi.mode(WIFI_STA);
  WiFi.disconnect();

  if (esp_now_init() != 0) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }

  esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);
  esp_now_register_send_cb(OnDataSent);

  esp_now_add_peer(receiverAddress, ESP_NOW_ROLE_SLAVE, 1, NULL, 0);

  Serial.println("Sender initialized");
}

void loop() {
  SensorData data;
  
  data.motionDetected = digitalRead(PIR_PIN);
  data.noiseLevel = analogRead(NOISE_PIN);

  esp_now_send(receiverAddress, (uint8_t *) &data, sizeof(data));

  Serial.print("Sending - Motion: ");
  Serial.print(data.motionDetected);
  Serial.print(" | Noise: ");
  Serial.println(data.noiseLevel);

  delay(200);
}

Hast du noch die MAC-Adresse des Empfängers zu Hand? Diese trägst du im oberen Bereich des Sketchs ein – ersetze hierfür die obige Adresse durch deine eigene.

Vieles dürfte dir in diesem Sketch schon aus jenem des Empfängers bekannt sein. Im Loop erfasst du die Daten des Sensors aus und sendest sie per ESP-NOW an den ESP8266 des Empfängers. Ganz zum Schluss stellst du mit einem Delay ein, wie häufig die Daten gemessen werden – hier ist ein Wert von 200 Millisekunden praktikabel. Du kannst aber natürlich damit experimentieren.

Den Geräuschsensor einstellen

Eine Sache musst du jedoch an der Hardware des Babyphone noch erledigen und den Sketch des Empfängers entsprechend aktualisieren. Am Geräuschsensor findest du eine kleine Schraube, mit der einstellen kannst, welcher Wert Stille bedeuten soll. Lade den obigen Sketch auf deinen ESP8266 und beobachte im Seriellen Monitor die gemessenen Werte. Drehe nun an der Schraub, bis die Wert sich etwas unter 200 einpendeln.

Mach nun in etwas Entfernung ein lauteres Geräusch (zum Beispiel in der Lautstärke eines weinenden Kindes) und beobachte, wie sich die Messwerte verhalten. Dieser obige Wert wird der Wert, bei dem alle 8 Zeilen des Matrix-Displays leuchten sollen. Passe nun die Zahl 300 im Empfänger-Sketch entsprechend an:

int noiseLEDs = map(noiseLevel, 150, 300, 0, 8);

Und das war es schon. Versorge beide ESP8266 mit Strom, zum Beispiel mit Hilfe von Powerbanks. Anschließend sollten sie eine Verbindung miteinander aufbauen und das Display aufleuchten.

Falls nicht, überprüfe noch einmal die MAC-Adresse, die du eingetragen hast. Stimmt sie mit jener des Empfängers überein?

Weitere Ideen für dein Babyphone

Neben dem Display könntest du natürlich auch noch andere Methoden nutzen, um dich informieren zu lassen. Das könnte ein einfacher Piezo-Summer sein, aber auch eine WhatsApp-Nachricht auf dein Smartphone.

]]>
OpenAI auf dem ESP32 verwenden https://polluxlabs.net/esp8266-projekte/openai-auf-dem-esp32-verwenden/ Thu, 09 May 2024 08:29:15 +0000 https://polluxlabs.net/?p=16436 OpenAI auf dem ESP32 verwenden Weiterlesen »

]]>
Hier bei Pollux Labs gibt es mittlerweile eine Vielzahl von Projekten, die OpenAI bzw. ChatGPT verwenden. Dabei kommt oft der Raspberry Pi und immer die Sprache Python zum Einsatz. Aber es geht auch anders: Wenn du in deinem nächsten Projekt ChatGPT oder auch DALL-E verwenden möchtest, kannst du hierfür auch OpenAI auf dem ESP32 verwenden.

In diesem Tutorial lernst du, wie du deinen ESP32 mit der API von OpenAI kommunizieren lässt, um dir Fragen beantworten und Bilder erstellen zu lassen. Du kommunizierst hierbei über den Seriellen Monitor – deine Fragen bzw. Anweisungen sendest du dabei als Nachricht an deinen ESP32 und erhältst die Antwort wie gewohnt im Textfeld des Monitors.

Für die folgende Anleitung benötigst du einen Account bei OpenAI und einen API-Key. Wie du beides erstellst, erfährst du zu Beginn dieses Tutorials.

Die Bibliothek, um OpenAI zu nutzen

Wie auch für Python gibt es für C++ eine Bibliothek, die du verwenden kannst, um ganz einfach auf die API von OpenAI zugreifen zu können.

Diese findest du aktuell jedoch nicht im Bibliotheksverwalter der Arduino IDE, sondern musst sie manuell herunterladen und in deinem Sketch verfügbar machen. Du findest sie auf der GitHub-Seite des Entwicklers Me No Dev. Klicke dort oben auf den grünen Button Code und anschließend auf Download ZIP.

GitHub-Seite der Bibliothek, um OpenAI auf dem ESP32 nutzen zu können

In deiner Arduino IDE kannst du diese Bibliothek dann ganz einfach über das Menü hinzufügen. Klicke hierfür auf Sketch -> Bibliothek einbinden -> ZIP Bibliothek hinzufügen und wähle dann die Datei, die du von GitHub heruntergeladen hast.

so verwendest du ChatGPT auf dem ESP32

Über den Menüpunkt Datei -> Beispiele -> OpenAI-ESP32 kannst du die Beispiel-Sketches finden, die in der Bibliothek enthalten sind. Wähle den Sketch ChatCompletion, der sich nun in einem neuen Fenster öffnen sollte. Trage hier zunächst die Zugangsdaten zu deinem WLAN-Netzwerk sowie deinen API-Key von OpenAI ein:

const char* ssid = "your-SSID";
const char* password = "your-PASSWORD";
const char* api_key = "your-OPENAI_API_KEY";

Etwas weiter unten im Sketch findest du folgende Zeilen:

chat.setModel("gpt-3.5-turbo");   //Model to use for completion. Default is gpt-3.5-turbo
chat.setSystem("Code geek");      //Description of the required assistant
chat.setMaxTokens(1000);          //The maximum number of tokens to generate in the completion.

In der ersten Zeile kannst du das gewünschte Sprachmodell festlegen. Voreingestellt ist GPT-3.5-turbo, mit gpt-4 kannst du jedoch auch das aktuellere Modell verwenden. Darunter kannst du die Rolle von ChatGPT einstellen. Im Beispiel ist das zunächst ein Code geek, aber wie wäre es z.B. mit einem Gärtner oder einem Designer? Deine Einstellung verhindert übrigens keine Antworten, die in einen anderen Wissenbereich fallen. Auch ein Code geek kann dir sagen, warum der Himmel blau ist.

In der nächsten Zeile kannst du einstellen, wie viele Token höchstens für die Antwort verwendet werden dürfen. Damit stellst du sicher, dass die Antworten von ChatGPT nicht zu kostspielig werden. Bei OpenAI findest du die aktuelle Preisliste. Aktuell (Mai 2024) liegst du mit 1000 Token und GPT-4 bei ungefähr 6 Cent.

In den Zeilen darunter findest du weitere Einstellungsmöglichkeiten, die du jedoch hier einmal außer Acht lassen kannst. Sobald du alles eingetragen hast, lade den Sketch auf deinen ESP32 hoch und öffne nach dem erfolgreichen Upload den Seriellen Monitor und stelle, falls nötig, eine Baudrate von 115200 ein.

Nachdem sich dein ESP32 mit deinem WLAN verbunden hat, siehst du die folgende Eingabeaufforderung:

ChatGPT im seriellen Monitor des ESP32

Hier siehst du nun also ChatGPT, das auf deine Frage oder Anweisung wartet. Um mit ChatGPT zu kommunizieren, trage oben im Feld Nachricht deine Frage ein und sende sie mit Enter ab. Testweise habe ich gefragt, warum der Himmel eigentlich blau ist. Und hier die Antwort:

Antwort von ChatGPT im Seriellen Monitor

Deine Frage wird also an die API von OpenAI übermittelt, von ChatGPT beantwortet und die Antwort in deinem Seriellen Monitor ausgegeben. Über ihr findest du die Anzahl der Tokens, die die Antwort verbraucht hat.

Falls du die Antwort nicht nur im Seriellen Monitor ausgeben, sondern zum Beispiel auf einem Display anzeigen möchtest – du findest sie in der Variablen response, die an dieser Stelle im Code befüllt wird:

String response = result.getAt(i);

Bilder mit DALL-E auf dem ESP32 erzeugen

Neben dem Chat kannst du über die API von OpenAI auch DALL-E verwenden, um damit Bilder zeichnen zu lassen. Ein Tutorial, wie du mit Python und DALL-E Bilder erzeugst, findest du übrigens auch bei Pollux Labs.

Auch hierfür verwendest du einen Sketch, der schon als Beispiel mitgeliefert wird. Öffne hierfür wieder Datei -> Beispiele -> OpenAI-ESP32 und wähle dann ImageGeneration. Trage als erstes wieder oben im Sketch deine Zugangsdaten ein. Eine weitere wichtige Stelle befindest sich etwas weiter unten:

imageGeneration.setSize(OPENAI_IMAGE_SIZE_256x256); 

Dort kannst du die Größe des Bilds angeben. Voreingestellt sind 256×256 Pixel, du kannst jedoch auch 512×512 oder 1024×2024 wählen. Im Folgenden habe ich die größte Option gewählt. Laden nun diesen Sketch auf deinen ESP32 und öffne nach dem Upload (und einem evtl. notwendigen Reset des Microcontrollers) wieder deinen Seriellen Monitor. Du erhältst wieder die Aufforderung, einen Prompt zu senden.

Ich habe es einmal mit Zeichne ein Bild eines Mannes auf dem Mond, der auf die Erde schaut – im Ukiyo-e Stil versucht. Als Ergebnis erhältst du ein URL zurück, in der letzten Zeile:

Antwort von DALL-E im Seriellen Monitor

Kopiere die gesamte URL aus dem Seriellen Monitor heraus und öffne sie in deinem Browser. Hier mein Ergebnis:

Das Ergebnis ist vielleicht nicht ganz die große Welle vor Kanagawa, aber okay, es war ja nur ein Versuch. Wie du in diesem Tutorial gesehen hast, ist die API von OpenAI also nicht nur Python und leistungsstärkeren Computern vorbehalten – auch mit einem ESP32 kannst du hier schon einiges erreichen.

]]>
Pflanzenbewässerung mit einem ESP8266 Webserver https://polluxlabs.net/esp8266-projekte/pflanzenbewaesserung-mit-einem-esp8266-webserver/ Wed, 24 Jan 2024 14:11:59 +0000 https://polluxlabs.net/?p=15654 Pflanzenbewässerung mit einem ESP8266 Webserver Weiterlesen »

]]>
In einem früheren Projekt habe ich eine automatische Pflanzenbewässerung mit einem Arduino UNO vorgestellt. In diesem Projekt hier gehst du einen Schritt weiter: Du baust ein Bewässerungssystem mit einem ESP8266 Webserver. Das bedeutet zunächst, dass du den aktuellen Feuchtigkeitswert der Pflanzenerde auf einer Webseite sehen kannst. Zusätzlich startet der ESP2866 die Wasserpumpe für eine festgelegte Zeit, sobald ein bestimmter Wert über- bzw. unterschritten wurde. Kurze Zeit später siehst du den neuen Wert auf der Webseite.

Die Webseite sieht hierbei – je nachdem, wie du das Diagramm einstellst – folgendermaßen aus:

Eine Art Tacho-Diagramm zeigt dir den aktuellen Feuchtigkeitswert an – grün eingefärbt ist hierbei die Spanne, in der die Erde feucht genug ist. Sobald der Feuchtigkeitssensor einen Wert übermittelt, der darüber liegt, startet die Wasserpumpe. Anschließend siehst du die Nadel auf der Anzeige wieder fallen.

Diese Bauteile benötigst du (jeweils eins):

Der Aufbau der Pflanzenbewässerung

Damit du ein 5V-Relais am ESP8266 betreiben kannst, benötigst du einen Spannungswandler, um die ausgehenden 3,3V auf 5V zu konvertieren. Auch den Feuchtigkeitssensor betreibst du dann mit 5V. Die Wasserpumpe benötigt zusätzliche Batterien, die über das Relais zugeschaltet werden. Orientiere dich beim Aufbau an folgender Skizze:

Pflanzenbewässerung mit dem ESP8266

Ein Hinweis zum Relais: Die Belegung der Pins kann bei deinem Modell variieren – achte darauf den richtigen Pin für das Signal mit deinem Arduino zu verbinden. Bitte schließe auch keine Wasserpumpe an, die mit Netzspannung betrieben wird – das ist für dieses Projekt zu gefährlich!

Der Sketch und weitere Dateien

Wie jedes andere Arduino- oder ESP8266-Projekt hat auch dieses einen Sketch – aber nicht nur das. Da du im Prinzip einen Webserver baust, benötigst du auch etwas HTML und CSS für die Webseite. Außerdem kommt hier noch JavaScript für das Tacho-Diagramm zum Einsatz.

Der Code hierfür ist in entsprechenden Dateien gespeichert, die du mit Hilfe von LittleFS auf deine ESP8266 überträgst und dort speicherst. Falls das noch neu für dich ist, findest du auf Pollux Labs ein entsprechendes LittleFS-Tutorial.

Derzeit (Jan. 2024) funktioniert LittleFS noch nicht mit der neuen Arduino IDE 2.x – deshalb musst du auf die ältere Version 1.8.19 zurückgreifen. Diese kannst du nach wie vor auf der offiziellen Download-Seite im Bereich Legacy IDE herunterladen.

Noch ein Hinweis: Dieses Projekt baut auf der Vorarbeit von Random Nerd Tutorials auf. Die nötigen Anpassungen in den HTML-, CSS- und JS-Dateien sowie den Sketch kannst du dir hier herunterladen. Wie du mit dem in der ZIP-Datei enthaltenen Ordner data umgehst, lernst du ebenfalls im oben verlinkten Tutorial.

Die benötigten Bibliotheken

Für den Sketch benötigst du eine Reihe von Bibliotheken:

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include "LittleFS.h"
#include <Arduino_JSON.h>

Die ersten beiden sollten bei dir schon vorinstalliert sein. Die drei Bibliotheken ESPAsyncTCP, ESPAsyncWebServer und Arduino_JSON findest du wie gewohnt in Bibliotheksmanager. Achte bei letzterer darauf, die Version von Arduino zu laden. Die ähnlich geschriebene Bibliothek ArduinoJson von Benoit Blanchon wäre für dieses Projekt die falsche.

Die JavaScript-Datei

Mit Hilfe von JavaScript erstellst du das Diagramm, auf dem der Feuchtigkeitswert angezeigt wird. Zum Einsatz kommt hierbei eine vorgefertigte Bibliothek, die alles beinhaltet, was du für dieses Projekt benötigst.

Öffne die Datei in einem Texteditor deiner Wahl. Wie du siehst, kannst du darin allerhand einstellen und an deine Vorlieben anpassen. Nähere Informationen findest du in der Dokumentation der Bibliothek. Zwei Stellen sind allerdings besonders wichtig und hängen von deinem Feuchtigkeitssensor und der Pflanze, die du gießen möchtest, ab.

Dein Sensor überträgt Messwerte in einem Bereich, den du vorab kurz ermitteln musst. Der Maximalwert ist 1024, der ausgegeben wird, wenn der Sensor nicht in der Erde steckt. Das ist sozusagen also der Wert für absolute Trockenheit. Den Minimalwert kannst du ermitteln, wenn du ihn längere Zeit in die feuchte Erde deiner Pflanze oder auch in ein Glas Wasser steckst. Sobald du die Pflanzenbewässerung aufgebaut und die Dateien sowie den Sketch übertragen hast, gibt dein ESP8266 diesen Wert im Seriellen Monitor aus. Bei mir sinkt der Wert kontinuierlich bis auf den Wert 680.

In der Datei script.js findest du zunächst die folgende Stelle. Dort kannst du den Minimal- und Maximalwert eintragen:

  minValue: 680,
  maxValue: 1024,

Außerdem kannst du ein Stück weiter unten noch die Skalenwerte festlegen, die auf dem Diagramm sichtbar sein sollen:

  majorTicks: [
      "680",
      "800",
      "900",
      "1024"
  ],

Und noch eine dritte Stelle ist interessant. Dort kannst du die Feuchtigkeitswerte innerhalb bestimmter Bereiche farblich kennzeichnen:

highlights: [
     {
      "from": 680,
      "to": 700,
      "color": "#346eeb"
     },  
     {
          "from": 700,
          "to": 800,
          "color": "#03C0C1"
      },
      {
        "from": 800,
        "to": 900,
        "color": "#ebd534"
      },
      {
        "from": 900,
        "to": 1024,
        "color": "#eb5834"
      }
  ],

Im obigen Beispiel sind das die Wert von 700 – 800, die grün eingefärbt sind, um zu signalisieren, dass hier die Erde eine optimale Feuchtigkeit hat. Welche Werte das bei dir sind, kannst du folgendermaßen leicht herausfinden:

Sobald du die Pflanzenbewässerung aufgebaut und die Dateien sowie den Sketch übertragen hast, gibt dein ESP8266 den aktuell gemessenen Feuchtigkeitswert im Seriellen Monitor aus. Stecke nun den Sensor in Erde, die so trocken ist, dass du sie jetzt bewässern würdest. Im Seriellen Monitor siehst du nun ersten Wert.

Gieße jetzt die Pflanze, bis sie feucht genug ist – im Seriellen Monitor siehst du nun deinen zweiten Wert. Trage diese Wert im JavaScript-Code ein, dieser Bereich wird dann grün markiert. Apropos grün: über den Hexadezimelwert hinter color kannst du selbst eine Farbe auswählen.

So könnte dann das Diagramm beispielsweise aussehen:

Die HTML-Datei

Das HTML für deine Webseite ist recht überschaubar:

<!DOCTYPE html>
<html>
  <head>
    <title>ESP8266 Pflanzenbewässerung</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta charset="utf-8">
    <link rel="icon" type="image/png" href="favicon.png">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
    <link rel="stylesheet" type="text/css" href="style.css">
    <script src="http://cdn.rawgit.com/Mikhus/canvas-gauges/gh-pages/download/2.1.7/all/gauge.min.js"></script>
  </head>
  <body>
    <div class="topnav">
      <h1>ESP8266 Pflanzenbewässerung</h1>
    </div>
    <div class="content">
      <div class="card-grid">
        <div class="card">
          <p class="card-title">Feuchtigkeit</p>
          <canvas id="gauge-humidity"></canvas>
        </div>
      </div>
    </div>
    <script src="script.js"></script>
  </body>
</html>

Darin findest du den Seitentitle title sowie die Überschrift h1 und den card-title, die du nach Belieben benennen kannst. Außerdem werden noch via CDN (Content Delivery Network) ein externes CSS sowie ein Script für das Diagramm geladen. Wenn du nichts anpassen möchtest, kannst du diese Datei so lassen wie sie ist – das gilt auch für die CSS-Datei im Ordner data, auf die ich hier nicht eigens eingehe.

Wichtige Anpassungen im Sketch

Nun zum Kernstück des Projekts. Hier musst du ein paar wichtige Anpassungen vornehmen, damit deine Pflanzenbewässerung wie gewünscht funktioniert. Hinterlege zunächst deine WLAN-Daten:

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

Weiter geht es mit einer Variablen. Diese gibt den Intervall vor, in dem die Webseite (und damit die Messdaten des Feuchtigkeitssensors) aktualisiert werden sollen. Voreingestellt sind hier 30 Sekunden, du kannst diese Zeitspanne jedoch beliebig verlängern und verkürzen.

unsigned long timerDelay = 30000;

Zuletzt der Schwellenwert, bei dessen Erreichen die Wasserpumpe in Aktion treten soll. Du findest diese Angabe recht weit unten im Sketch im Loop:

if (humidity > 900) {
  Serial.println("Starte Pumpe.");
  digitalWrite(pumpPin, HIGH);
  delay(1000);
  digitalWrite(pumpPin, LOW);
}
else {
  digitalWrite(pumpPin, LOW);
}

Setzt dort für die voreingestellten 900 den Wert ein, den du in der trockenen Erde ermittelt hast. Wenn der Messwert darüber liegt, springt die Pumpe an. Ein Hinweis: In diesem Projekt gehe ich davon aus, dass die Werte mit der Trockenheit der Erde steigen. Falls das bei dir genau andersherum ist, passe die If-Abfrage mit dem Zeichen < und deinem Schwellenwert entsprechend an.

Falls die Erde nun also zu trocken ist, wird der Pin pumpPin auf HIGH gestellt und damit die Wasserpumpe eingeschaltet. Diese läuft dann für eine Sekunde (auch ein Wert, den du an deine Bedürfnisse anpassen solltest) und wird danach wieder ausgeschaltet.

Während des Pumpvorgangs läuft der Intervall von 30 Sekunden weiter. Das bedeutet, dass nach knapp 30 Sekunden wieder die Feuchtigkeit geprüft, auf der Webseite aktualisiert und bei Bedarf die Pumpe angeschaltet wird. Liegt bei einer Prüfung dann der Wert im zulässigen Bereich, bleibt die Pumpe aus.

Und das war es auch schon. Mit den richtigen Anpassungen sollte deine Pflanzenbewässerung nun automatisch deine Lieblingspflanze bewässern während du z.B. auf dem Smartphone die Feuchtigkeit der Erde überwachst. 🙂

Wie geht es weiter?

Natürlich gibt es noch einige Verbesserungen, die deiner Bewässerungsanlage hinzufügen könntest. Wie wäre z.B. mit einer WhatsApp-Nachricht, die dein ESP8266 schickt, wenn die Erde zu trocken ist? Eine andere Idee könnte auch ein Button auf der Webseite sein, der den Gießvorgang manuell startet.

Sicherlich fallen dir noch weitere Optimierungen ein – viel Erfolg dabei!

]]>
Dateien auf dem ESP8266 speichern mit LittleFS https://polluxlabs.net/esp8266-projekte/dateien-auf-dem-esp8266-speichern-mit-littlefs/ Wed, 17 Jan 2024 16:11:10 +0000 https://polluxlabs.net/?p=15632 Dateien auf dem ESP8266 speichern mit LittleFS Weiterlesen »

]]>
Für einige Projekte ist es praktisch, Dateien auf einem ESP8266 zu speichern – z.B. weil du Sensordaten sichern möchtest, oder auch die HTML-, CSS- und JavaScript-Dateien eines Webservers sauber ablegen möchtest. Hier kommt das Tool LittleFS ins Spiel, mit dem du das problemlos über die Arduino IDE erledigen kannst.

das Tool herunterladen und installieren

Ein Hinweis vorab: Aktuell (Jan. 2024) funktioniert LittleFS nicht mit der Arduino IDE ab Version 2.0 – für dieses Tutorial habe ich deshalb die Version 1.8.19 verwendet. Diese kannst du nach wie vor hier im Bereich Legacy IDE herunterladen.

Du findest die aktuelle Version von LittleFS auf GitHub. Aktuell ist das die Version ESP8266LittleFS-2.6.0.zip, die du mit einem Klick auf den Dateinamen herunterladen kannst.

Steuere nun den Ordner deines Sketchbooks an. Das ist der Ort, in dem all deine mit der Arduino IDE erstellten Sketches wiederum in eigenen Ordnern gespeichert werden. Falls du dir nicht sicher bist, findest du den Pfad zum Ordner in den Einstellungen der Arduino IDE:

Erstelle in deinem Sketchbook-Ordner einen neuen Ordner mit dem Namen tools und entpacke dort die LittleFS ZIP-Datei, die du gerade heruntergeladen hast. In deinem Ordner tools sollte nun der entpackte Ordner ESP8266LittleFS zu finden sein.

Wenn du fertig bist, starte die Arduino IDE neu. Öffne nun das Menü Werkzeuge – dort siehst du nun den Eintrag ESP8266 LittleFS Data Upload.

Dateien auf den ESP8266 hochladen

Jetzt, wo alles eingerichtet ist, kann es losgehen. Erstelle einen neuen Sketch und öffne den dazugehörigen Ordner – z.B. über den Menüpunkt Sketch > Sketch-Ordner anzeigen. Erstelle in diesem Ordner nun einen weiteren Ordner namens data.

Alle Dateien, die du später in diesem Ordner ablegst, werden mit dem gerade installierten Tool auf den ESP8266 geladen. Erstelle nun testweise eine einfache HTML-Datei mit folgendem Inhalt:

Wenn du das hier liest, funktioniert LittleFS. :)

Speichere diese Datei nun im Ordner data mit dem Namen test.txt ab. Später wirst du diese Datei in deinem Seriellen Monitor auslesen. Doch zunächst der Upload:

Wähle zunächst (falls noch nicht geschehen) im Menü Werkzeuge deinen ESP8266 aus. Anschließend kannst du im selben Menü unter Flash size die benötigte Größe für den Speicher einstellen. Für den folgenden Test musst du hier jedoch nichts umstellen.

Wähle anschließend den Menüpunkt ESP8266 LittleFS Data Upload. Nun wird die HTML-Datei im Ordner data übertragen – was nach wenigen Sekunden erfolgreich abgeschlossen sein sollte:

Die TXT-Datei anzeigen

Ob der Upload wirklich funktioniert hat, lässt sich natürlich am besten überprüfen, indem du nachschaust, ob du den Inhalt von test.txt auslesen kannst.

Füge hierfür den folgenden Code in deinen Sketch ein:

#include "LittleFS.h"
 
void setup() {
  Serial.begin(115200);
  
  if(!LittleFS.begin()){
    Serial.println("An Error has occurred while mounting LittleFS");
    return;
  }
  
  File file = LittleFS.open("/test.txt", "r");
  if(!file){
    Serial.println("Failed to open file for reading");
    return;
  }
  
  Serial.println("File Content:");
  while(file.available()){
    Serial.write(file.read());
  }
  file.close();
}
 
void loop() {

}

Lade nun den Sketch auf deinen ESP8266 und öffne den Seriellen Monitor. Um die Datei aufzurufen, drücke kurz den Reset-Button RST – nun sollte der Inhalt von test.txt in Seriellen Monitor erscheinen:

In diesem Tutorial hast du die Grundlagen von LittleFS gelernt. Du weißt nun, wo du die Dateien im Sketch-Ordner ablegen musst und wie du diese auf deinen ESP8266 hochlädst.

]]>
Nachrichten mit dem ESP8266 und ESP32 versenden https://polluxlabs.net/arduino-tutorials/nachrichten-mit-dem-esp8266-und-esp32-versenden/ Mon, 04 Sep 2023 14:15:55 +0000 https://polluxlabs.net/?p=14335 Nachrichten mit dem ESP8266 und ESP32 versenden Weiterlesen »

]]>
Möchtest du mit deinem ESP8266 oder ESP32 Nachrichten verschicken, um zum Beispiel eine Warnung von einem Bewegungsmelder oder Temperatursensor zu erhalten? In diesem Tutorial erfährst du gleich drei Möglichkeiten, wie du das umsetzen kannst – per Telegram, E-Mail und WhatsApp.

Inhalt diese Tutorials

Nachrichten per Telegram senden

Eine gängige Möglichkeit ist es, Nachrichten per Telegram zu senden – genauer gesagt an einen Telegram-Bot. Diese Nachrichten kannst du dann umgehend auf deinem Smartphone lesen. Auf Pollux Labs gibt es einige Projekte, die auf Telegram zurückgreifen, zum Beispiel einen Dash Button oder eine Fotofalle mit der ESP32-CAM. Für letzteres Projekt ist Telegram besonders interessant, denn es ist damit auch möglich, Bilder zu senden.

Zunächst benötigst du einen Telegram-Bot. Wie du diesen im Handumdrehen einrichtest, erfährst du in diesem Tutorial. Außerdem benötigst du die Bibliothek UniversalTelegramBot, die du im Bibliotheksverwalter der Arduino IDE findest. Achte darauf, die neueste Version zu installieren und die Bibliothek aktuell zu halten. Sollte die Version im Bibliotheksverwalter veraltet sein, findest du auf GitHub immer die neueste Version zum Download.

Hier der Beispiel-Sketch für deinen ESP8266. Dieser versendet nach dem Start des Boards eine Nachricht:

#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>

// Deine WLAN-Zugangsdaten
const char* ssid = "DEIN WLAN-NETZWERK";
const char* password = "DEIN PASSWORT";

// Dein Bot-Token
#define botToken "DEIN TOKEN"  // den Bot-Token bekommst du vom Botfather)

//Deine User ID
#define userID "DEINE USER ID"

WiFiClientSecure client;
UniversalTelegramBot bot(botToken, client);

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

  // Verbindung zum WLAN
  Serial.print("Verbinde mit mit: ");
  Serial.println(ssid);

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

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

  Serial.println("");
  Serial.println("Verbunden!");

  bot.sendMessage(userID, "Hier spricht dein ESP8266.", "");
}

void loop() {
}

Nach dem Upload sollte in deiner Telegram-App folgende Nachricht erscheinen:

Telegram-Nachricht vom ESP8266

So funktioniert der Sketch

Nachdem du die benötigten Bibliotheken eingebunden hast (die zwei ersten sind bereits vorinstalliert), musst du ein paar Daten ergänzen. Das sind deine WLAN-Zugangsdaten sowie dein Bot-Token und deine User-ID. Wenn du die beiden letzteren noch nicht hast, schau dir zunächst unser oben verlinktes Tutorial an.

Anschließend initialisierst du den WifiClient sowie den UniversalTelegramBot mit jeweiligen Instanzen:

WiFiClientSecure client;
UniversalTelegramBot bot(botToken, client);

Im Setup stellst du dann die Verbindung zu deinem WLAN her. Sobald die Verbindung steht, verwendest du die Funktion bot.sendMessage, um eine Nachricht an deinen Telegram-Bot zu senden:

bot.sendMessage(userID, "Hier spricht dein ESP8266.", "");

Der erste Parameter in dieser Funktion ist deine userID, die du zu Beginn des Sketchs hinterlegt hast – gefolgt von der Nachricht, die du senden möchtest. Und das war es im Prinzip auch schon! Du kannst dieses Muster nun für dein Projekt adaptieren, indem du die Nachricht zum Beispiel sendest, wenn eine bestimmte Temperatur überschritten wurde – als Inhalt der Nachricht könnte dann z.B. der aktuelle Messwert dienen.

Mit dem ESP32 eine Telegram-Nachricht senden

Natürlich kannst du statt des ESP8266 auch mit einem ESP32 oder einer ESP32-CAM Nachrichten per Telegram senden. Hierfür musst du nur eine Bibliothek austauschen. Statt ESP8266WiFI.h verwendest du WiFi.h.

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>

Und: Um eine korrekte Nachricht zu senden, kannst du noch den Inhalt anpassen. 😉

bot.sendMessage(userID, "Hier spricht dein ESP32.", "");

E-Mails mit dem ESP8266 oder ESP32 versenden

Nach Telegram schauen wir uns den Klassiker der elektronischen Nachrichten an, E-Mails. In diesem Tutorial verwenden wir den Service Gmail, um die E-Mails zu versenden. Auch wenn du schon eine Adresse bei Gmail haben solltest, empfehlen wir fürs Experimentieren auf jeden Fall eine brandneue Mail-Adresse anzulegen. Sollte etwas schief laufen, z.B. eine Kontosperrung, weil dein ESP8266 versehentlich zahllose Mails in kurzer Abfolge versendet, ist deine private Mail-Adresse hiervon nicht betroffen.

Um eine neue Adresse anzulegen, rufe zunächst hier Gmail auf und klicke auf Konto erstellen. Sobald dein Konto eingerichtet ist, benötigst du ein App-Passwort, das dein ESP8266 verwenden kann. Um ein App-Passwort anzulegen, öffne dein Google-Konto und klicke auf den Menüpunkt Sicherheit. Klicke anschließend auf den Menü-Punkt Bestätigung in zwei Schritten und folge den Anweisungen, um diese Zwei-Faktor-Authentifizierung zu aktivieren – sie ist nämlich die Voraussetzung für ein App-Passwort.

Sobald du mit der Einrichtung fertig bist, öffne wieder Sicherheit > Bestätigung in zwei Schritten und scrolle ganz nach unten zum Eintrag App-Passwörter. Erstelle nun ein Passwort für E-Mail und wähle als Gerät Andere (benutzerdefinierter Name) aus. Anschließend kannst du einen Namen vergeben, also z.B. ESP8266 oder ESP32.

Das war es zunächst mit den Vorbereitungen, weiter geht es mit einem Beispielsketch, der eine E-Mail an dich versendet.

Eine E-Mail senden

Zunächst benötigst du eine Bibliothek, die du über den Bibliotheksverwalter installieren kannst: ESP Mail Client

Nach der Installation kannst du dich gleich dem Sketch zuwenden. Wir verwenden in diesem Tutorial den Sketch des Blogs RandomNerdTutorials, der ein sehr guter Startpunkt für dieses Thema ist. Im folgenden Sketch musst du nur ein paar Anpassungen machen. Trage an den folgenden Stellen deine Daten ein:

#define WIFI_SSID "REPLACE_WITH_YOUR_SSID" //Dein WLAN-Name
#define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD" //Dein WLAN-Passwort
#define AUTHOR_EMAIL "YOUR_EMAIL@XXXX.com" //Deine Absendeadresse (die du bei Gmail angelegt hast)
#define AUTHOR_PASSWORD "YOUR_EMAIL_APP_PASS" //Dein App-Passwort
#define RECIPIENT_EMAIL "RECIPIENTE_EMAIL@XXXX.com" //Die Empfänger-Adresse

Der gesamte Sketch:

/*
  Rui Santos
  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.
  Example adapted from: https://github.com/mobizt/ESP-Mail-Client
*/

#include <Arduino.h>
#if defined(ESP32)
  #include <WiFi.h>
#elif defined(ESP8266)
  #include <ESP8266WiFi.h>
#endif
#include <ESP_Mail_Client.h>

#define WIFI_SSID "REPLACE_WITH_YOUR_SSID"
#define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD"

/** The smtp host name e.g. smtp.gmail.com for GMail or smtp.office365.com for Outlook or smtp.mail.yahoo.com */
#define SMTP_HOST "smtp.gmail.com"
#define SMTP_PORT 465

/* The sign in credentials */
#define AUTHOR_EMAIL "YOUR_EMAIL@XXXX.com"
#define AUTHOR_PASSWORD "YOUR_EMAIL_APP_PASS"

/* Recipient's email*/
#define RECIPIENT_EMAIL "RECIPIENTE_EMAIL@XXXX.com"

/* Declare the global used SMTPSession object for SMTP transport */
SMTPSession smtp;

/* Callback function to get the Email sending status */
void smtpCallback(SMTP_Status status);

void setup(){
  Serial.begin(115200);
  Serial.println();
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("Connecting to Wi-Fi");
  while (WiFi.status() != WL_CONNECTED){
    Serial.print(".");
    delay(300);
  }
  Serial.println();
  Serial.print("Connected with IP: ");
  Serial.println(WiFi.localIP());
  Serial.println();

  /*  Set the network reconnection option */
  MailClient.networkReconnect(true);

  /** Enable the debug via Serial port
   * 0 for no debugging
   * 1 for basic level debugging
   *
   * Debug port can be changed via ESP_MAIL_DEFAULT_DEBUG_PORT in ESP_Mail_FS.h
   */
  smtp.debug(1);

  /* Set the callback function to get the sending results */
  smtp.callback(smtpCallback);

  /* Declare the Session_Config for user defined session credentials */
  Session_Config config;

  /* Set the session config */
  config.server.host_name = SMTP_HOST;
  config.server.port = SMTP_PORT;
  config.login.email = AUTHOR_EMAIL;
  config.login.password = AUTHOR_PASSWORD;
  config.login.user_domain = "";

  /*
  Set the NTP config time
  For times east of the Prime Meridian use 0-12
  For times west of the Prime Meridian add 12 to the offset.
  Ex. American/Denver GMT would be -6. 6 + 12 = 18
  See https://en.wikipedia.org/wiki/Time_zone for a list of the GMT/UTC timezone offsets
  
  config.time.ntp_server = F("pool.ntp.org,time.nist.gov");
  config.time.gmt_offset = 1;
  config.time.day_light_offset = 0;
  */

  /* Declare the message class */
  SMTP_Message message;

  /* Set the message headers */
  message.sender.name = F("ESP");
  message.sender.email = AUTHOR_EMAIL;
  message.subject = F("ESP Test Email");
  message.addRecipient(F("Sara"), RECIPIENT_EMAIL);
    
  /*Send HTML message*/
  /*String htmlMsg = "<div style=\"color:#2f4468;\"><h1>Hello World!</h1><p>- Sent from ESP board</p></div>";
  message.html.content = htmlMsg.c_str();
  message.html.content = htmlMsg.c_str();
  message.text.charSet = "us-ascii";
  message.html.transfer_encoding = Content_Transfer_Encoding::enc_7bit;*/

   
  //Send raw text message
  String textMsg = "Hallo Mensch!";
  message.text.content = textMsg.c_str();
  message.text.charSet = "us-ascii";
  message.text.transfer_encoding = Content_Transfer_Encoding::enc_7bit;
  
  message.priority = esp_mail_smtp_priority::esp_mail_smtp_priority_low;
  message.response.notify = esp_mail_smtp_notify_success | esp_mail_smtp_notify_failure | esp_mail_smtp_notify_delay;


  /* Connect to the server */
  if (!smtp.connect(&config)){
    ESP_MAIL_PRINTF("Connection error, Status Code: %d, Error Code: %d, Reason: %s", smtp.statusCode(), smtp.errorCode(), smtp.errorReason().c_str());
    return;
  }

  if (!smtp.isLoggedIn()){
    Serial.println("\nNot yet logged in.");
  }
  else{
    if (smtp.isAuthenticated())
      Serial.println("\nSuccessfully logged in.");
    else
      Serial.println("\nConnected with no Auth.");
  }

  /* Start sending Email and close the session */
  if (!MailClient.sendMail(&smtp, &message))
    ESP_MAIL_PRINTF("Error, Status Code: %d, Error Code: %d, Reason: %s", smtp.statusCode(), smtp.errorCode(), smtp.errorReason().c_str());

}

void loop(){
}

/* Callback function to get the Email sending status */
void smtpCallback(SMTP_Status status){
  /* Print the current status */
  Serial.println(status.info());

  /* Print the sending result */
  if (status.success()){
    // ESP_MAIL_PRINTF used in the examples is for format printing via debug Serial port
    // that works for all supported Arduino platform SDKs e.g. AVR, SAMD, ESP32 and ESP8266.
    // In ESP8266 and ESP32, you can use Serial.printf directly.

    Serial.println("----------------");
    ESP_MAIL_PRINTF("Message sent success: %d\n", status.completedCount());
    ESP_MAIL_PRINTF("Message sent failed: %d\n", status.failedCount());
    Serial.println("----------------\n");

    for (size_t i = 0; i < smtp.sendingResult.size(); i++)
    {
      /* Get the result item */
      SMTP_Result result = smtp.sendingResult.getItem(i);

      // In case, ESP32, ESP8266 and SAMD device, the timestamp get from result.timestamp should be valid if
      // your device time was synched with NTP server.
      // Other devices may show invalid timestamp as the device time was not set i.e. it will show Jan 1, 1970.
      // You can call smtp.setSystemTime(xxx) to set device time manually. Where xxx is timestamp (seconds since Jan 1, 1970)
      
      ESP_MAIL_PRINTF("Message No: %d\n", i + 1);
      ESP_MAIL_PRINTF("Status: %s\n", result.completed ? "success" : "failed");
      ESP_MAIL_PRINTF("Date/Time: %s\n", MailClient.Time.getDateTimeString(result.timestamp, "%B %d, %Y %H:%M:%S").c_str());
      ESP_MAIL_PRINTF("Recipient: %s\n", result.recipients.c_str());
      ESP_MAIL_PRINTF("Subject: %s\n", result.subject.c_str());
    }
    Serial.println("----------------\n");

    // You need to clear sending result as the memory usage will grow up.
    smtp.sendingResult.clear();
  }
}

So funktioniert der sketch

Weiter oben hast du bereits die Stellen kennengelernt, die du anpassen musst, damit der Sketch funktioniert. Außerdem kannst du noch deine Zeitzone einstellen, um der E-Mail den richtigen Timestamp zu geben:

  config.time.ntp_server = F("pool.ntp.org,time.nist.gov");
  config.time.gmt_offset = 1;
  config.time.day_light_offset = 0;

Der gmt_offset für Deutschland (also der Abstand zur Greenwich Mean Time) wäre in diesem Beispiel 1 – in der Winterzeit. Im Sommer sind es hingegen 2 Stunden.

Den Betreff und den Absender- bzw. Empfängernamen trägst du hier ein:

  /* Set the message headers */
  message.sender.name = F("ABSENDERNAME");
  message.sender.email = AUTHOR_EMAIL;
  message.subject = F("BETREFF");
  message.addRecipient(F("EMPFÄNGERNAME"), RECIPIENT_EMAIL);

Die eigentliche Nachricht findest du hier:

  //Send raw text message
  String textMsg = "Hallo Mensch!";

Hier kannst du, je nachdem was in deinem eigenen Projekt sinnvoll ist, natürlich auch Variablen mitsenden. Lade den Sketch mit deinen Anpassungen auf deinen ESP8266 oder ESP32. Im Seriellen Monitor kannst du mitverfolgen, wie die E-Mail versendet wird und schließlich im Posteingang deines Empfängers landet.

Der obige Sketch funktioniert sowohl für den ESP8266 als auch für den ESP32. Anpassungen sind beim Wechsel der Boards nicht nötig.

Eine WhatsApp-Nachricht mit dem ESP8266 und ESP32 versenden

Kommen wir zu dritten Möglichkeit, deinen ESP8266 oder ESP32 eine Nachricht versenden zu lassen: WhatsApp. Voraussetzung ist hier ein externer Service. Im Folgenden verwenden wir CallMeBot.com – eine kostenlose API, mit deren Hilfe du WhatsApp-Textnachrichten auf dein Smartphone senden kannst.

Hierfür ist keine aufwändige Registrierung nötig, du musst nur kurz deine Mobilfunknummer bei der API anmelden, um einen API-Key zu erhalten. Über den Link oben kannst du die offizielle Anleitung aufrufen – oder einfach die folgenden Schritte ausführen:

  • Füge über WhatsApp die folgende Nummer deinen Kontakten hinzu (den Kontaktnamen kannst du frei wählen): +34 644 71 81 99
  • Sende anschließend die folgende Nachricht an diese Nummer: I allow callmebot to send me messages
  • Du erhältst daraufhin eine Antwort mit deinem API-Key – und das war’s auch schon

Die Bibliothek zum Codieren der Nachrichten

Auch um Nachrichten per WhatsApp senden zu können, benötigst du eine Bibliothek, die du ganz schnell über den Bibliotheksverwalter installieren kannst: UrlEncode

Diese brauchst du, da die Nachrichten über eine URL übertragen werden – z.B. so:

https://api.callmebot.com/whatsapp.php?phone=+49123123123&text=Dieser+Text+ist+codiert&apikey=123123

Die Bibliothek, die du gleich installierst, übernimmt die Aufgabe, deinen Nachrichten-String in das richtige Format zu bringen. Öffne also den Bibliotheksverwalter und suche nach UrlEncode. Installiere anschließend die neueste Version.

die WhatsApp-Nachricht senden

Hier kommt der vollständige Sketch, um eine WhatsApp-Nachricht mit deinem ESP8266 zu verschicken:

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClient.h>
#include <UrlEncode.h>

const char* ssid = "DEIN WLAN-NETZ";
const char* password = "DEIN WLAN-PASSWORT";

// Deutschland +49, Beispiel: +49176999888777
String phoneNumber = "DEINE MOBILFUNKNUMMER";
String apiKey = "DEIN API-KEY";

void sendMessage(String message) {

  // Daten, die gesendet werden
  String url = "http://api.callmebot.com/whatsapp.php?phone=" + phoneNumber + "&apikey=" + apiKey + "&text=" + urlEncode(message);
  WiFiClient client;
  HTTPClient http;
  http.begin(client, url);

  // Header
  http.addHeader("Content-Type", "application/x-www-form-urlencoded");

  // HTTP Post request senden
  int httpResponseCode = http.POST(url);
  if (httpResponseCode == 200) {
    Serial.print("Message sent successfully");
  } else {
    Serial.println("Error sending the message");
    Serial.print("HTTP response code: ");
    Serial.println(httpResponseCode);
  }

  http.end();
}

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

  WiFi.begin(ssid, password);
  Serial.println("Connecting");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to WiFi network with IP Address: ");
  Serial.println(WiFi.localIP());

  // WhatsApp-Nachricht
  sendMessage("Hier spricht dein ESP8266");
}

void loop() {
}

Zu Beginn des Sketchs hinterlegst du wieder deine WLAN-Zugangsdaten. Außerdem benötigst du deine eigene Mobilfunknummer, die du im internationalen Format (also z.B. für Deutschland mit einem +49 zu Beginn) hinterlegen musst. Zuletzt trägst du noch deinen API-Key ein, den du per WhatsApp-Nachricht empfangen hast:

const char* ssid = "DEIN WLAN-NETZ";
const char* password = "DEIN WLAN-PASSWORT";

// Deutschland +49, Beispiel: +49176999888777
String phoneNumber = "DEINE MOBILFUNKNUMMER";
String apiKey = "DEIN API-KEY";

Alles weitere im Sketch dient der Verbindung zu deinem WLAN und dem API-Request (in der Funktion sendMessage(String message)). Recht weit am Ende kannst du deine Textnachricht eingeben, die gesendet werden soll:

// WhatsApp-Nachricht
sendMessage("Hier spricht dein ESP8266");

Lade den Sketch auf deinen ESP8266. Nach dem Upload sollte innerhalb von wenigen Sekunden die entsprechende Nachricht auf deinem Smartphone erscheinen.

Die WhatsApp-Nachricht mit dem ESP32 senden

Wenn du statt eines ESP8266 einen ESP32 oder eine ESP32-CAM verwenden möchtest, musst du nur ein paar andere Bibliotheken zu Beginn des Sketchs einbinden:

#include <WiFi.h>
#include <HTTPClient.h>
#include <UrlEncode.h>

Den restlichen Sketch kannst du beibehalten ohne weitere Anpassungen. Beachte bitte, dass du mit CallMeBot nur reine Textnachrichten (die natürlich auch Variablen enthalten können) senden kannst. Bilder zu senden, funktioniert mit diesem Service leider nicht. Solltest du das vorhaben, verwende besser Telegram oder schaue dir einmal textmebot.com an – diese API ist allerdings kostenpflichtig.

Fazit

In diesem Tutorial hast du drei Methoden kennengelernt, mit denen du Nachrichten versenden kannst – per Telegram, E-Mail oder WhatsApp. Passende Projekte findest du auf Pollux Labs: Du könntest zum Beispiel die ESP8266 Wetterstation um Warnungen ergänzen, die du erhältst, sobald bestimmte Messwerte über- oder unterschritten werden.

]]>
ESP8266 Wetterstation mit Datenaufzeichnung und -visualisierung https://polluxlabs.net/esp8266-projekte/esp8266-wetterstation-mit-datenaufzeichnung/ https://polluxlabs.net/esp8266-projekte/esp8266-wetterstation-mit-datenaufzeichnung/#respond Mon, 07 Aug 2023 12:54:39 +0000 https://polluxlabs.net/?p=13977 ESP8266 Wetterstation mit Datenaufzeichnung und -visualisierung Weiterlesen »

]]>
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.

]]>
https://polluxlabs.net/esp8266-projekte/esp8266-wetterstation-mit-datenaufzeichnung/feed/ 0