ESP32 Projekte – Pollux Labs https://polluxlabs.net Arduino, ESP32 & ESP8266 | Projekte & Tutorials Mon, 11 Nov 2024 08:59:45 +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 ESP32 Projekte – Pollux Labs https://polluxlabs.net 32 32 OTA Updates für den ESP32 – Aktualisierungen aus der Ferne https://polluxlabs.net/arduino-tutorials/ota-updates-fuer-den-esp32/ Mon, 11 Nov 2024 08:30:57 +0000 https://polluxlabs.net/?p=17845 OTA Updates für den ESP32 – Aktualisierungen aus der Ferne Weiterlesen »

]]>
Der ESP32 unterstützt OTA (Over-the-Air), mit dem du den Sketches drahtlos aktualisieren kannst. OTA ist besonders hilfreich, wenn der Mikrocontroller schwer zugänglich ist oder du Änderungen ohne physische Verbindung übertragen möchtest. In diesem Tutorial erfährst du, wie du OTA einrichtest und lernst Schritt für Schritt ein Beispielprojekt kennen: Eine blinkende LED, deren Blinkfrequenz per OTA-Update verändert wird.

Die Bibliothek ArduinoOTA

Falls du die ESP32-Boards über den Boardverwalter der Arduino-IDE hinzugefügt hast, ist die Bibliothek ArduinoOTA normalerweise schon dabei. Die Bibliothek ermöglicht dir eine unkomplizierte Integration der OTA-Funktionalität.

Sollte die Bibliothek dennoch fehlen, kannst du sie über den Bibliotheksverwalter installieren. Gehe dazu in der Arduino-IDE zu Sketch > Bibliothek einbinden > Bibliotheken verwalten und suche nach “ArduinoOTA”. Stelle sicher, dass die neueste Version installiert ist, damit du alle aktuellen Features und Sicherheitsverbesserungen nutzen kannst.

Die Bibliothek ArduinoOTA installieren

Schritt 1: Der erste Sketch für eine blinkende LED

Du startest mit einem einfachen Sketch, der eine LED am ESP32 im Sekundentakt blinken lässt. Die LED ist dabei über einen passenden Vorwiderstand an den GPIO-Pin 2 des ESP32 angeschlossen. Dieser Sketch dient als Grundlage für das OTA-Update.

#include <WiFi.h>
#include <ArduinoOTA.h>

const char* ssid = "Dein_WLAN-NETZWERK";          // Ersetze durch deinen WLAN-Namen
const char* password = "Dein_WLAN_Passwort";  // Ersetze durch dein WLAN-Passwort

#define LED_PIN 2 // GPIO-Pin der LED

unsigned long previousMillis = 0;
const long interval = 1000; // Blinkintervall in Millisekunden (1 Sekunde)

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);

  // WLAN-Verbindung herstellen
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nVerbunden mit WiFi");

  // OTA-Setup
  ArduinoOTA.setHostname("esp32_led_ota");
  ArduinoOTA.setPassword("Dein_Sicheres_Passwort"); // Setze hier ein starkes Passwort für OTA-Updates, um unbefugten Zugriff zu verhindern
  ArduinoOTA.begin();
}

void loop() {
  ArduinoOTA.handle(); // Nach OTA-Updates suchen

  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    digitalWrite(LED_PIN, !digitalRead(LED_PIN)); // Zustand der LED umschalten
  }
}

So funktioniert der Sketch

  1. Bibliotheken einbinden: Der Sketch beginnt mit dem Einbinden der notwendigen Bibliotheken WiFi.h und ArduinoOTA.h. Erstere ermöglicht die Verbindung des ESP32 mit einem WLAN-Netzwerk, während ArduinoOTA.h die OTA-Funktionalität bereitstellt. Diese Bibliotheken sind notwendig, um die gewünschten Netzwerk- und Update-Funktionen auf dem ESP32 zu implementieren.
  2. Netzwerk-Konfiguration: Die Variablen ssid und password speichern die Zugangsdaten für dein WLAN. Diese werden verwendet, um den ESP32 mit deinem Netzwerk zu verbinden.
  3. GPIO-Pin-Definition: #define LED_PIN 2 definiert den Pin, an den die LED angeschlossen ist. In diesem Fall nutzt du den GPIO-Pin 2 des ESP32.
  4. WLAN-Verbindung herstellen: Im setup()-Teil des Codes wird die Verbindung zum WLAN hergestellt. Mit WiFi.begin(ssid, password) verbindet sich der ESP32 mit dem Netzwerk. Die Schleife while (WiFi.status() != WL_CONNECTED) sorgt dafür, dass das Programm wartet, bis die Verbindung hergestellt ist.
  5. OTA-Setup: Im setup()-Teil wird auch die OTA-Funktionalität initialisiert. Mit ArduinoOTA.setHostname("esp32_led_ota") wird der Name des ESP32 im Netzwerk festgelegt. Dieser Name erleichtert es, das Gerät im Netzwerk zu identifizieren, besonders wenn du mehrere ESP32-Geräte verwendest. Ein (möglichst sicheres) Passwort hinterlegst du mit Hilfe der Funktion ArduinoOTA.setPassword("Mein_OTA_Passwort"), damit nur du Updates durchführen kannst. ArduinoOTA.begin() startet den OTA-Service, damit der ESP32 auf eingehende Updates wartet.
  6. LED-Blinken: Die Funktion loop() enthält den Code, der die LED im Sekundentakt blinken lässt. Mit der Funktion millis() wird überprüft, ob der festgelegte Intervall (1000 Millisekunden) vergangen ist. Wenn dies der Fall ist, wird der Zustand der LED umgeschaltet mit digitalWrite(LED_PIN, !digitalRead(LED_PIN)).
  7. OTA-Handler: In der loop()-Funktion wird ArduinoOTA.handle() aufgerufen, um kontinuierlich nach OTA-Updates zu suchen. Dies ermöglicht es, jederzeit ein Update zu empfangen, während das Hauptprogramm weiterläuft.

Schritt 2: OTA-Update zur Änderung des intervalls

Nun nimmst du eine Änderung am Code vor, um die Blinkfrequenz der LED auf 500 ms zu reduzieren. Diese Änderung überträgst du drahtlos per OTA auf den ESP32.

Ändere im obigen Sketch den Wert des Intervalls von 1000 auf 500:

const long interval = 500; // Blinkintervall in Millisekunden (500 ms)

Update via OTA durchführen

Nun folgt der Upload des aktualisierten Sketchs. Sofern dein ESP32 mit deinem WLAN-Netzwerk verbunden ist, sollte er in der Arduino-IDE als Netzwerkport sichtbar sein. Gehe hierfür zu Werkzeuge > Port und wähle den ESP32 (esp32_led_ota) aus.

Der ESP32 im Netzwerk finden für das Update per OTA

OTA-Update hochladen: Lade den neuen Sketch (mit 500 ms Blinkintervall) über den Netzwerkport hoch. Klicke hierfür einfach wie gewohnt auf den Upload-Button – so wie du es auch machst, wenn dein ESP32 über ein USB-Kabel verbunden ist. Achte darauf, dass in deinem Update wieder die WLAN-Zugangsdaten und dein Passwort hinterlegt sind.

Die Arduino-IDE fordert dich auf, das OTA-Passwort einzugeben. Gib das definierte Passwort (“Mein_OTA_Passwort”) ein und das Update wird drahtlos übertragen.

Hinweis: Solltest du Updates mit verschiedenen Passwörtern machen, kann es sein, dass die Arduino IDE ein falsches Passwort automatisch verwenden möchte. Schließe in diesem Fall die IDE und öffne sie erneut – dann wirst du wieder nach dem Passwort für deinen ESP32 gefragt.

Nachdem das Update drahtlos auf deinen ESP32 übertragen wurde, sollte die LED nun im neuen Halbsekundentakt aufleuchten. Und das war es auch schon – du kennst nun eine Möglichkeit, Programme zu aktualisieren, ohne den Microcontroller irgendwo ausbauen und an deinen Computer anschließen zu müssen. Weitere Informationen zum Thema findest du bei Espressif.

]]>
Snake spielen auf dem Arduino (und dem ESP32) https://polluxlabs.net/arduino-projekte/snake-spielen-auf-dem-arduino-und-dem-esp32/ Sun, 27 Oct 2024 20:51:07 +0000 https://polluxlabs.net/?p=17469 Snake spielen auf dem Arduino (und dem ESP32) Weiterlesen »

]]>
Der Spieleklassiker Snake begleitet uns schon lange und auf ganz unterschiedlichen Geräten – auf dem PC, auf Taschenrechnern von Texas Instruments oder auch auf dem ehrwürdigen Nokia 3210. Dank des einfachen Prinzips ist das Spiel fast überall dort umzusetzen, wo etwas Prozessorleistung vorhanden ist. Also warum nicht auch auf einem Arduino?

In diesem Projekt baust du dir zunächst eine Snake-Version auf einem Arduino UNO R4. Als Display verwendest du hierbei ein OLED-Display mit 128×64 Pixeln. Steuern wirst du das Spiel mit einem kleinen Joystick. Da das Spiel zwar auf dem Arduino UNO läuft, für geschickte Spieler jedoch vielleicht etwas zu langsam ist, kommt später noch ein ESP32 zum Zug. Dieser verfügt über mehr Leistung und sorgt für ein flüssigeres Spielvergnügen.

Für dieses Projekt benötigst du:

Snake auf dem Arduino UNO

Zunächst also die Version auf dem Arduino UNO. Hier eignet sich der “neue” UNO R4, da dieser deutlich mehr Leistung als sein Vorgänger R3 besitzt. Ob du die WiFi- oder die Minima-Version verwendest, ist für dieses Projekt egal. Falls du jedoch noch überlegst, dir einen R4 zuzulegen, empfehle ich dir auf jeden Fall erstere, da der R4 WiFi nur ein paar Euro mehr kostet und du damit mit deinen Projekten ins Internet kannst.

Nun aber zum Anschluss der Bauteile: Verbinde das OLED-Display und den den Joystick folgendermaßen mit dem Arduino UNO:

Anschluss von OLED-Display und Joystick am Arduino UNO für Snake

Je nachdem, welchen Joystick du verwendest, kann die Beschriftung der Pins variieren. So kann zum Beispiel statt VERT auch VRy zu lesen sein. Wenn du alles verkabelt hast, kann es direkt mit dem Sketch weitergehen.

Die benötigten Bibliotheken

Damit du am Arduino dein OLED-Display verwenden kannst, benötigst du zwei Bibliotheken. Öffne in der Arduino IDE den Bibliotheksverwalter, suche und installiere dort diese beiden Bibliotheken von Adafruit:

Adafruit SSD1306
Adafruit GFX

Im Sketch wirst du gleich noch eine dritte Bibliothek sehen, Wire.h – diese ist jedoch bereits vorinstalliert, sodass du dich um diese nicht kümmern musst.

Der gesamte Sketch

Hier nun der vollständige Sketch für Snake. Kopiere dir den folgenden Code, erstelle einen neuen Sketch und lade ihn auf deinen Arduino hoch.

// Snake spielen auf dem Arduino
// Pollux Labs

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

// OLED Display Konfiguration
#define SCREEN_WIDTH 128  // Breite des OLED-Displays
#define SCREEN_HEIGHT 64  // Höhe des OLED-Displays
#define OLED_RESET    -1  // OLED-Reset-Pin (nicht verwendet)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Joystick Konfiguration
#define VRX_PIN A0  // Joystick X-Achsen-Pin (analoger Pin A0 des Arduino Uno)
#define VRY_PIN A1  // Joystick Y-Achsen-Pin (analoger Pin A1 des Arduino Uno)
#define SW_PIN  7   // Joystick Taster-Pin (digitaler Pin 7 des Arduino Uno)

// Eigenschaften der Schlange
#define SNAKE_SIZE 4  // Größe jedes Segments der Schlange
#define MAX_SNAKE_LENGTH 50  // Maximale Länge der Schlange (reduziert für Arduino Uno, um Speicher zu sparen)
int snakeX[MAX_SNAKE_LENGTH], snakeY[MAX_SNAKE_LENGTH];  // Arrays zur Speicherung der Segmentpositionen der Schlange
int snakeLength = 5;  // Anfangslänge der Schlange
int directionX = 1, directionY = 0;  // Anfangsrichtung der Bewegung (nach rechts)

// Eigenschaften des Futters
int foodX = random(2, (SCREEN_WIDTH - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;  // X-Koordinate des Futters (nicht direkt am Rand)
int foodY = random(12 / SNAKE_SIZE, (SCREEN_HEIGHT - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;  // Y-Koordinate des Futters (Spielbereich unterhalb des Rahmens, nicht direkt am Rand)

// Score
int score = 0;

void setup() {
  Serial.begin(9600);  // Initialisiere serielle Kommunikation mit niedrigerer Baudrate für Arduino Uno

  // Initialisiere Display
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {  // Initialisiere das OLED-Display mit der I2C-Adresse 0x3C
    Serial.println(F("SSD1306 allocation failed"));
    for (;;);  // Stoppe, falls die Display-Initialisierung fehlschlägt
  }
  display.clearDisplay();  // Leere den Display-Puffer
  display.display();  // Zeige den geleerten Puffer an

  // Initialisiere Joystick
  pinMode(VRX_PIN, INPUT);  // Setze Joystick X-Achsen-Pin als Eingang
  pinMode(VRY_PIN, INPUT);  // Setze Joystick Y-Achsen-Pin als Eingang
  pinMode(SW_PIN, INPUT_PULLUP);  // Setze Joystick-Taster-Pin als Eingang mit internem Pull-up-Widerstand

  // Initialisiere Position der Schlange
  for (int i = 0; i < snakeLength; i++) {
    snakeX[i] = SCREEN_WIDTH / 2 - (i * SNAKE_SIZE);  // Setze anfängliche X-Koordinaten der Schlangensegmente
    snakeY[i] = SCREEN_HEIGHT / 2;  // Setze anfängliche Y-Koordinate der Schlangensegmente
  }
  Serial.println("Setup abgeschlossen");
}

void loop() {
  // Lese Joystick-Werte
  int xValue = analogRead(VRX_PIN);  // Lese X-Achsen-Wert vom Joystick
  int yValue = analogRead(VRY_PIN);  // Lese Y-Achsen-Wert vom Joystick
  Serial.print("Joystick X: ");
  Serial.print(xValue);
  Serial.print(" Y: ");
  Serial.println(yValue);

  // Setze Richtung basierend auf Joystick-Eingaben, verhindere diagonale Bewegung
  if (xValue < 300 && directionX == 0) {  // Bewegung nach links, wenn derzeit nicht horizontal bewegt wird
    directionX = -1;
    directionY = 0;
    Serial.println("Richtung: Links");
  } else if (xValue > 700 && directionX == 0) {  // Bewegung nach rechts, wenn derzeit nicht horizontal bewegt wird
    directionX = 1;
    directionY = 0;
    Serial.println("Richtung: Rechts");
  } else if (yValue < 300 && directionY == 0) {  // Bewegung nach oben, wenn derzeit nicht vertikal bewegt wird
    directionX = 0;
    directionY = -1;
    Serial.println("Richtung: Oben");
  } else if (yValue > 700 && directionY == 0) {  // Bewegung nach unten, wenn derzeit nicht vertikal bewegt wird
    directionX = 0;
    directionY = 1;
    Serial.println("Richtung: Unten");
  }

  // Aktualisiere Position der Schlange
  for (int i = snakeLength - 1; i > 0; i--) {  // Bewege jedes Segment zur Position des vorherigen Segments
    snakeX[i] = snakeX[i - 1];
    snakeY[i] = snakeY[i - 1];
  }
  snakeX[0] += directionX * SNAKE_SIZE;  // Aktualisiere Kopfposition in X-Richtung
  snakeY[0] += directionY * SNAKE_SIZE;  // Aktualisiere Kopfposition in Y-Richtung
  Serial.print("Schlangenkopf X: ");
  Serial.print(snakeX[0]);
  Serial.print(" Y: ");
  Serial.println(snakeY[0]);

  // Prüfe auf Kollision mit dem Futter
  if (snakeX[0] == foodX && snakeY[0] == foodY) {  // Wenn der Schlangenkopf das Futter erreicht
    if (snakeLength < MAX_SNAKE_LENGTH) {
      snakeLength++;  // Erhöhe die Schlangenlänge
      score++;  // Erhöhe den Score
      Serial.println("Futter gegessen, Schlangenlänge: " + String(snakeLength));
    }
    // Generiere neue Futterposition (nicht direkt am Rand)
    foodX = random(2, (SCREEN_WIDTH - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;
    foodY = random(12 / SNAKE_SIZE, (SCREEN_HEIGHT - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;
    Serial.print("Neues Futter bei X: ");
    Serial.print(foodX);
    Serial.print(" Y: ");
    Serial.println(foodY);
  }

  // Prüfe auf Kollision mit den Wänden
  if (snakeX[0] < SNAKE_SIZE || snakeX[0] >= SCREEN_WIDTH - SNAKE_SIZE || snakeY[0] < 10 || snakeY[0] >= SCREEN_HEIGHT - SNAKE_SIZE) {
    Serial.println("Kollision mit der Wand, Spiel wird zurückgesetzt");
    resetGame();  // Setze das Spiel zurück, wenn die Schlange die Wand trifft
  }

  // Prüfe auf Kollision mit sich selbst
  for (int i = 1; i < snakeLength; i++) {
    if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) {  // Wenn der Schlangenkopf mit ihrem eigenen Körper kollidiert
      Serial.println("Kollision mit sich selbst, Spiel wird zurückgesetzt");
      resetGame();  // Setze das Spiel zurück
    }
  }

  // Zeichne alles
  display.clearDisplay();  // Leere den Display-Puffer

  // Zeichne den Score
  display.setTextSize(1);  // Setze Textgröße
  display.setTextColor(SSD1306_WHITE);  // Setze Textfarbe
  display.setCursor(0, 0);  // Setze Cursor für Score
  display.print("Score: ");
  display.print(score);  // Zeige aktuellen Score an

  // Zeichne den Rand um das Spielfeld
  display.drawRect(0, 10, SCREEN_WIDTH, SCREEN_HEIGHT - 10, SSD1306_WHITE);  // Zeichne einen weißen Rahmen um das Spielfeld

  // Zeichne die Schlange
  for (int i = 0; i < snakeLength; i++) {
    display.fillRect(snakeX[i], snakeY[i], SNAKE_SIZE, SNAKE_SIZE, SSD1306_WHITE);  // Zeichne jedes Segment der Schlange
  }

  // Zeichne das Futter
  display.fillRect(foodX, foodY, SNAKE_SIZE, SNAKE_SIZE, SSD1306_WHITE);  // Zeichne das Futter

  display.display();  // Zeige den aktualisierten Puffer an

  delay(150);  // Verzögerung zur Steuerung der Geschwindigkeit der Schlange
}

void resetGame() {
  // Setze Eigenschaften der Schlange zurück
  snakeLength = 5;  // Setze die Schlangenlänge zurück
  directionX = 1;  // Setze die Richtung nach rechts zurück
  directionY = 0;
  score = 0;  // Setze den Score zurück
  for (int i = 0; i < snakeLength; i++) {
    snakeX[i] = SCREEN_WIDTH / 2 - (i * SNAKE_SIZE);  // Setze die Position der Schlange zurück auf die Mitte
    snakeY[i] = SCREEN_HEIGHT / 2;
  }
  // Generiere neue Futterposition (nicht direkt am Rand)
  foodX = random(2, (SCREEN_WIDTH - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;
  foodY = random(12 / SNAKE_SIZE, (SCREEN_HEIGHT - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;
  Serial.println("Spiel zurückgesetzt");
  Serial.print("Neues Futter bei X: ");
  Serial.print(foodX);
  Serial.print(" Y: ");
  Serial.println(foodY);
}

Nach dem Upload sollte es auf dem Display direkt losgehen: In der Mitte erscheint, die zunächst 5 Pixel lange Schlange und bewegt sich. Ein Stück Futter erscheint zufällig platziert auf dem Spielfeld. Wie du sicherlich weißt, ist dein Ziel, so viel zu fressen wie möglich – ohne mit dem Spielfeldrand oder dir selbst zu kollidieren.

Oben links findest du deine Punktzahl. Für jede Verlängerung deiner Schlange erhöht sich dein Score um 1.

So funktioniert der Sketch

Lass uns ein paar wichtige Stellen des Sketchs anschauen.

Setup

Die setup()-Funktion führt alle notwendigen Initialisierungen durch:

  • Serielle Kommunikation wird gestartet, um Debugging-Informationen auszugeben.
  • Das OLED-Display wird initialisiert. Falls die Initialisierung fehlschlägt, wird eine Fehlermeldung ausgegeben und das Programm angehalten.
void setup() {
  Serial.begin(9600);
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;);
  }
  display.clearDisplay();
  display.display();
}
  • Der Joystick wird initialisiert, indem seine Pins als Eingänge konfiguriert werden.
  • Die Position der Schlange wird initial in der Mitte des Displays gesetzt.

Loop

Die loop()-Funktion läuft kontinuierlich und behandelt alle Aspekte des Spiels, wie die Steuerung, Bewegung, Kollisionserkennung und die Ausgabe auf dem Display.

Joystick-Steuerung

Der Joystick wird verwendet, um die Bewegungsrichtung der Schlange zu ändern. Die analogen Eingänge des Joysticks liefern Werte, die bestimmen, in welche Richtung sich die Schlange bewegt:

  • Wenn der Joystick nach links bewegt wird, wird die Richtung auf links gesetzt, solange die Schlange sich nicht bereits nach rechts bewegt.
  • Gleiches gilt für die anderen Richtungen.
if (xValue < 300 && directionX == 0) {
  directionX = -1;
  directionY = 0;
  Serial.println("Richtung: Links");
}
Bewegung der Schlange

Die Position der Schlange wird durch eine Schleife aktualisiert, in der jedes Segment der Schlange zur Position des vorherigen Segments bewegt wird. Der Kopf der Schlange wird in die gewählte Richtung verschoben.

for (int i = snakeLength - 1; i > 0; i--) {
  snakeX[i] = snakeX[i - 1];
  snakeY[i] = snakeY[i - 1];
}
snakeX[0] += directionX * SNAKE_SIZE;
snakeY[0] += directionY * SNAKE_SIZE;
Fressen des Futters

Wenn die Schlange das Futter erreicht, wird ihre Länge erhöht, und ein neuer Punktestand wird berechnet. Danach wird das Futter an einer neuen Position generiert, die nicht direkt am Rand liegt, um das Spiel einfacher zu machen.

if (snakeX[0] == foodX && snakeY[0] == foodY) {
  if (snakeLength < MAX_SNAKE_LENGTH) {
    snakeLength++;
    score++;
  }
  foodX = random(2, (SCREEN_WIDTH - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;
  foodY = random(12 / SNAKE_SIZE, (SCREEN_HEIGHT - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;
}
Kollisionserkennung

Die loop()-Funktion prüft auch, ob die Schlange mit den Rändern des Spielfeldes oder mit sich selbst kollidiert:

  • Wenn die Schlange mit einer Wand kollidiert, wird das Spiel zurückgesetzt.
  • Wenn die Schlange ihren eigenen Körper berührt, wird ebenfalls das Spiel zurückgesetzt.
if (snakeX[0] < SNAKE_SIZE || snakeX[0] >= SCREEN_WIDTH - SNAKE_SIZE || snakeY[0] < 10 || snakeY[0] >= SCREEN_HEIGHT - SNAKE_SIZE) {
  resetGame();
}
Anzeige auf dem OLED-Display

Das OLED-Display wird in jeder Schleife aktualisiert:

  • Der Punktestand wird oben links angezeigt.
  • Ein weißer Rahmen wird um das Spielfeld gezeichnet, um die Grenzen des Spielfeldes anzuzeigen.
  • Die Schlange und das Futter werden auf dem Display gezeichnet.
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.print("Score: ");
display.print(score);

display.drawRect(0, 10, SCREEN_WIDTH, SCREEN_HEIGHT - 10, SSD1306_WHITE);

for (int i = 0; i < snakeLength; i++) {
  display.fillRect(snakeX[i], snakeY[i], SNAKE_SIZE, SNAKE_SIZE, SSD1306_WHITE);
}
display.fillRect(foodX, foodY, SNAKE_SIZE, SNAKE_SIZE, SSD1306_WHITE);
display.display();

Das Spiel zurücksetzen

Die resetGame()-Funktion setzt die Schlange zurück auf ihre Ausgangsposition und -länge, wenn eine Kollision erkannt wird. Auch der Punktestand wird zurückgesetzt, und eine neue Position für das Futter wird generiert.

void resetGame() {
  snakeLength = 5;
  directionX = 1;
  directionY = 0;
  score = 0;
  for (int i = 0; i < snakeLength; i++) {
    snakeX[i] = SCREEN_WIDTH / 2 - (i * SNAKE_SIZE);
    snakeY[i] = SCREEN_HEIGHT / 2;
  }
}

Snake auf dem ESP32 spielen

Wenn du an dieser Stelle bereits Snake auf dem Arduino UNO gespielt hast, wirst du sicherlich bemerkt haben, dass das Spiel sehr gemächlich – um nicht zu sagen “ruckelig” – abläuft. Das liegt an der eher begrenzten Leistung, die auch beim Modell R4 nicht ausreichend ist, alle Berechnungen in der Loop-Funktion schnell genug durchzuführen.

Falls du jedoch auch einen ESP32 dein Eigen nennst, kannst du das Spiel (fast) ganz einfach umziehen und ihm so mehr Geschwindigkeit einhauchen. Schließe das OLED-Display und den Joystick folgendermaßen am ESP32 an:

Anschluss von OLED-Display und Joystick am ESP32

Der Sketch

Damit Snake auf dem ESP32 läuft, sind nur ein paar kleinere Adaptionen nötig. So hinterlegst du im Sketch zunächst natürlich andere Pins. Aber auch die maximale Länge der Schlange kannst du hier nach oben setzen – von 50 für den Arduino auf 100 für den ESP32:

#define MAX_SNAKE_LENGTH 100  // Maximale Länge der Schlange

Auch die Schwellenwerte für den Joystick musst du beim ESP32 ändern, weil der dieser eine höhere Auflösung für die analogen Eingänge hat als der Arduino UNO. Der Arduino Uno verwendet eine 10-Bit-Auflösung, was bedeutet, dass die analogen Eingänge Werte von 0 bis 1023 zurückgeben. Der ESP32 hingegen verwendet eine 12-Bit-Auflösung, was einen Bereich von 0 bis 4095 ergibt.

Für eine Bewegung nach links ist das zum Beispiel ein Wert von 1000. Im Sketch für den Arduino UNO lag dieser Wert bei 300:

if (xValue < 1000 && directionX == 0) { //Schwellenwert für den ESP32

Mit einem Delay steuerst du die Geschwindigkeit der Schlange. Da dir auf dem ESP32 mehr Leistung zur Verfügung steht, setzt du diesen Wert hier in Zeile 147 des Sketchs auf 100 (statt 150 für den Arduino UNO).

Hier nun der gesamte Sketch für den ESP32:

// Snake spielen auf dem ESP32
// Pollux Labs

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

// OLED Display Konfiguration
#define SCREEN_WIDTH 128  // Breite des OLED-Displays
#define SCREEN_HEIGHT 64  // Höhe des OLED-Displays
#define OLED_RESET    -1  // OLED-Reset-Pin (nicht verwendet)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Joystick Konfiguration
#define VRX_PIN 34  // Joystick X-Achsen-Pin
#define VRY_PIN 35  // Joystick Y-Achsen-Pin
#define SW_PIN  32  // Joystick Taster-Pin

// Eigenschaften der Schlange
#define SNAKE_SIZE 4  // Größe jedes Segments der Schlange
#define MAX_SNAKE_LENGTH 100  // Maximale Länge der Schlange
int snakeX[MAX_SNAKE_LENGTH], snakeY[MAX_SNAKE_LENGTH];  // Arrays zur Speicherung der Segmentpositionen der Schlange
int snakeLength = 5;  // Anfangslänge der Schlange
int directionX = 1, directionY = 0;  // Anfangsrichtung der Bewegung (nach rechts)

// Eigenschaften des Futters
int foodX = random(2, (SCREEN_WIDTH - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;  // X-Koordinate des Futters (nicht direkt am Rand)
int foodY = random(12 / SNAKE_SIZE, (SCREEN_HEIGHT - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;  // Y-Koordinate des Futters (Spielbereich unterhalb des Rahmens, nicht direkt am Rand)

// Score
int score = 0;

void setup() {
  Serial.begin(115200);  // Initialisiere serielle Kommunikation

  // Initialisiere Display
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {  // Initialisiere das OLED-Display mit der I2C-Adresse 0x3C
    Serial.println(F("SSD1306 allocation failed"));
    for (;;);  // Stoppe, falls die Display-Initialisierung fehlschlägt
  }
  display.clearDisplay();  // Leere den Display-Puffer
  display.display();  // Zeige den geleerten Puffer an

  // Initialisiere Joystick
  pinMode(VRX_PIN, INPUT);  // Setze Joystick X-Achsen-Pin als Eingang
  pinMode(VRY_PIN, INPUT);  // Setze Joystick Y-Achsen-Pin als Eingang
  pinMode(SW_PIN, INPUT_PULLUP);  // Setze Joystick-Taster-Pin als Eingang mit internem Pull-up-Widerstand

  // Initialisiere Position der Schlange
  for (int i = 0; i < snakeLength; i++) {
    snakeX[i] = SCREEN_WIDTH / 2 - (i * SNAKE_SIZE);  // Setze anfängliche X-Koordinaten der Schlangensegmente
    snakeY[i] = SCREEN_HEIGHT / 2;  // Setze anfängliche Y-Koordinate der Schlangensegmente
  }
  Serial.println("Setup abgeschlossen");
}

void loop() {
  // Lese Joystick-Werte
  int xValue = analogRead(VRX_PIN);  // Lese X-Achsen-Wert vom Joystick
  int yValue = analogRead(VRY_PIN);  // Lese Y-Achsen-Wert vom Joystick
  Serial.print("Joystick X: ");
  Serial.print(xValue);
  Serial.print(" Y: ");
  Serial.println(yValue);

  // Setze Richtung basierend auf Joystick-Eingaben, verhindere diagonale Bewegung
  if (xValue < 1000 && directionX == 0) {  // Bewegung nach links, wenn derzeit nicht horizontal bewegt wird
    directionX = -1;
    directionY = 0;
    Serial.println("Richtung: Links");
  } else if (xValue > 3000 && directionX == 0) {  // Bewegung nach rechts, wenn derzeit nicht horizontal bewegt wird
    directionX = 1;
    directionY = 0;
    Serial.println("Richtung: Rechts");
  } else if (yValue < 1000 && directionY == 0) {  // Bewegung nach oben, wenn derzeit nicht vertikal bewegt wird
    directionX = 0;
    directionY = -1;
    Serial.println("Richtung: Oben");
  } else if (yValue > 3000 && directionY == 0) {  // Bewegung nach unten, wenn derzeit nicht vertikal bewegt wird
    directionX = 0;
    directionY = 1;
    Serial.println("Richtung: Unten");
  }

  // Aktualisiere Position der Schlange
  for (int i = snakeLength - 1; i > 0; i--) {  // Bewege jedes Segment zur Position des vorherigen Segments
    snakeX[i] = snakeX[i - 1];
    snakeY[i] = snakeY[i - 1];
  }
  snakeX[0] += directionX * SNAKE_SIZE;  // Aktualisiere Kopfposition in X-Richtung
  snakeY[0] += directionY * SNAKE_SIZE;  // Aktualisiere Kopfposition in Y-Richtung
  Serial.print("Schlangenkopf X: ");
  Serial.print(snakeX[0]);
  Serial.print(" Y: ");
  Serial.println(snakeY[0]);

  // Prüfe auf Kollision mit dem Futter
  if (snakeX[0] == foodX && snakeY[0] == foodY) {  // Wenn der Schlangenkopf das Futter erreicht
    if (snakeLength < MAX_SNAKE_LENGTH) {
      snakeLength++;  // Erhöhe die Schlangenlänge
      score++;  // Erhöhe den Score
      Serial.println("Futter gegessen, Schlangenlänge: " + String(snakeLength));
    }
    // Generiere neue Futterposition (nicht direkt am Rand)
    foodX = random(2, (SCREEN_WIDTH - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;
    foodY = random(12 / SNAKE_SIZE, (SCREEN_HEIGHT - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;
    Serial.print("Neues Futter bei X: ");
    Serial.print(foodX);
    Serial.print(" Y: ");
    Serial.println(foodY);
  }

  // Prüfe auf Kollision mit den Wänden
  if (snakeX[0] < SNAKE_SIZE || snakeX[0] >= SCREEN_WIDTH - SNAKE_SIZE || snakeY[0] < 10 || snakeY[0] >= SCREEN_HEIGHT - SNAKE_SIZE) {
    Serial.println("Kollision mit der Wand, Spiel wird zurückgesetzt");
    resetGame();  // Setze das Spiel zurück, wenn die Schlange die Wand trifft
  }

  // Prüfe auf Kollision mit sich selbst
  for (int i = 1; i < snakeLength; i++) {
    if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) {  // Wenn der Schlangenkopf mit ihrem eigenen Körper kollidiert
      Serial.println("Kollision mit sich selbst, Spiel wird zurückgesetzt");
      resetGame();  // Setze das Spiel zurück
    }
  }

  // Zeichne alles
  display.clearDisplay();  // Leere den Display-Puffer

  // Zeichne den Score
  display.setTextSize(1);  // Setze Textgröße
  display.setTextColor(SSD1306_WHITE);  // Setze Textfarbe
  display.setCursor(0, 0);  // Setze Cursor für Score
  display.print("Score: ");
  display.print(score);  // Zeige aktuellen Score an

  // Zeichne den Rand um das Spielfeld
  display.drawRect(0, 10, SCREEN_WIDTH, SCREEN_HEIGHT - 10, SSD1306_WHITE);  // Zeichne einen weißen Rahmen um das Spielfeld

  // Zeichne die Schlange
  for (int i = 0; i < snakeLength; i++) {
    display.fillRect(snakeX[i], snakeY[i], SNAKE_SIZE, SNAKE_SIZE, SSD1306_WHITE);  // Zeichne jedes Segment der Schlange
  }

  // Zeichne das Futter
  display.fillRect(foodX, foodY, SNAKE_SIZE, SNAKE_SIZE, SSD1306_WHITE);  // Zeichne das Futter

  display.display();  // Zeige den aktualisierten Puffer an

  delay(100);  // Verzögerung zur Steuerung der Geschwindigkeit der Schlange
}

void resetGame() {
  // Setze Eigenschaften der Schlange zurück
  snakeLength = 5;  // Setze die Schlangenlänge zurück
  directionX = 1;  // Setze die Richtung nach rechts zurück
  directionY = 0;
  score = 0;  // Setze den Score zurück
  for (int i = 0; i < snakeLength; i++) {
    snakeX[i] = SCREEN_WIDTH / 2 - (i * SNAKE_SIZE);  // Setze die Position der Schlange zurück auf die Mitte
    snakeY[i] = SCREEN_HEIGHT / 2;
  }
  // Generiere neue Futterposition (nicht direkt am Rand)
  foodX = random(2, (SCREEN_WIDTH - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;
  foodY = random(12 / SNAKE_SIZE, (SCREEN_HEIGHT - 2 * SNAKE_SIZE) / SNAKE_SIZE) * SNAKE_SIZE;
  Serial.println("Spiel zurückgesetzt");
  Serial.print("Neues Futter bei X: ");
  Serial.print(foodX);
  Serial.print(" Y: ");
  Serial.println(foodY);
}

Wie geht es weiter?

Du hast nun eine spielbare Version von Snake samt einer Anzeige deiner Punkte. Wie könntest du das Spiel noch erweitern oder verbessern? Eine Idee könnte zum Beispiel ein Highscore sein. Der aktuelle Halter dieses Highscores könnte mit dem Joystick seine Initialen eintragen, die neben der Punktezahl im Dateisystem des ESP32 gespeichert werden und auf dem Display angezeigt werden – ein guter Ansporn für eine weitere Partie Snake!

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

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

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

]]>
Video-Livestream mit der ESP32-CAM https://polluxlabs.net/arduino-tutorials/video-livestream-mit-der-esp32-cam/ Fri, 23 Oct 2020 06:23:00 +0000 https://polluxlabs.net/?p=3211 Video-Livestream mit der ESP32-CAM Weiterlesen »

]]>
Mit der ESP32-CAM kannst du einfach und günstig deine Kameraprojekte umsetzen, wie z.B. unsere Fotofalle. Aber es müssen ja nicht immer nur Fotos sein – ein Video-Livestream geht auch!

In diesem Tutorial lernst du, wie du mit deiner ESP32-CAM einen Livestream aufsetzt, den du in deinem Browser aufrufen kannst. Hierfür benötigst du:

Vorbereitungen

Wenn du deine ESP32-CAM noch nie mit der Arduino IDE programmiert hast, musst du sie dort erst verfügbar machen. Da dieses Board keinen eigenen USB-Anschluss hat, benötigst du außerdem eine FTDI-Programmer, um deinen Sketch hochzuladen.

Wie beides funktioniert lernst du in diesem Tutorial hier bei uns. 🙂

Sobald die Verbindung von deinem Computer zur ESP32-CAM steht, kann es weitergehen.

Den Sketch hochladen

Die Konfiguration und das Hochladen des passenden Sketchs fallen in diesem Tutorial ziemlich angenehm aus: er befindet sich nämlich schon in deiner Arduino IDE. Wähle im Menü Datei -> Beispiele -> ESP32 -> Camera -> CameraWebServer. Daraufhin öffnet sich der Sketch, den du benötigst.

Zwei Kleinigkeiten musst du jedoch noch anpassen: Trage zunächst den Namen und das Passwort deines WLAN-Netzwerks ein.

Netzwerkdaten für den Videostream

Außerdem musst du noch das richtige Board auswählen. In unserem Fall (und in den meisten anderen Fällen auch) ist das AI Thinker. Lösche hierfür den Kommentar vor dem Board, sodass nur deines aktiv ist.

Board auswählen für den Video-Livestream

Lade nun den Sketch auf die ESP32-CAM. Vergiss nicht für den Upload die Pins IO0 und GND zu verbinden. Sobald der Code auf dem Board ist, trenne diese Verbindung wieder, starte deinen Seriellen Monitor und drücke zuletzt den RESET-Button auf dem Board.

Den Video-Livestream starten

Nun solltest du nach ein paar Sekunden (zwischenzeitliche Fehlermeldungen machen nichts) folgendes in deinem Seriellen Monitor sehen. Die IP-Adresse kann variieren.

Ausgabe im Seriellen Monitor

Klicke nun auf den Link oder kopiere dir die Adresse heraus und setze sie in deinem Browser ein. Du solltest nun auf der linken Seite eine ganze Reihe von Einstellungsmöglichkeiten sehen. Ganz unten befindet sich der Button Start Stream – ein Klick darauf startet den Stream.

Der Video-Livestream

Und das war es auch schon. Du kannst nun mit den Einstellungen experimentieren, um die bestmögliche Leistung zu erhalten. Außerdem hast du ganz unten auch die Option, eine Gesichtserkennung zu aktivieren, die wir hier jedoch nicht weiter behandeln.

Zwei Hinweise noch: Du kannst den Livestream nur aus deinem eigenen WLAN-Netz aus aufrufen. Dieses Tutorial ist zum Ausprobieren gedacht – wenn du die Kamera produktiv einsetzen möchtest, mache dir vorab Gedanken zur IT-Sicherheit.

]]>
Eine Fotofalle mit der ESP32-CAM und Telegram https://polluxlabs.net/esp8266-projekte/eine-fotofalle-mit-der-esp32-cam-und-telegram/ Sun, 04 Oct 2020 09:43:39 +0000 https://polluxlabs.net/?p=2870 Eine Fotofalle mit der ESP32-CAM und Telegram Weiterlesen »

]]>
ESP32- und ESP8266-Boards sind zusammen mit Telegram eine tolle Kombination. Du kannst Daten blitzschnell auf dein Smartphone senden und von dort aus deinen Microcontroller steuern.

In diesem Projekt baust du eine Fotofalle mit einem speziellen ESP32-Board – der ESP32-CAM. Diese Falle schnappt zu, sobald sich jemand vor der Kamera bewegt. Dann nimmt sie ein Foto auf und sendet es umgehend an dein Smartphone.

Fotofalle mit ESP32-CAM und Telegram

Anfänger

1 Stunde

ca. 25 €

Für dieses Projekt benötigst du (Mengen s. Beschreibung):

Bevor du loslegen kannst, musst du noch ein paar Minuten in Vorbereitungen stecken.

Telegram vorbereiten

Zunächst benötigst du einen Account bei Telegram – und die dazugehörige App für dein Smartphone oder den Computer. Im Folgenden verwenden wir ein Smartphone. Telegram ist kostenlos, werbefrei und funktioniert so ähnlich wie WhatsApp. Allerdings hast du hier die Möglichkeit, Bots zu erstellen, mit denen du interagieren kannst.

In diesem Tutorial auf Pollux Labs lernst du, wie du deinen eigenen Telegram-Bot erstellst.

Die Fotofalle aufbauen

Die ESP32-CAM hat leider keinen USB-Port, weswegen du auf einen FTDI-Adapter zurückgreifen musst, um Sketches hochladen zu können. Wenn du hier noch keine Erfahrung sammeln konntest, informiere dich in diesem Tutorial darüber, wie du die ESP32-CAM programmieren kannst.

___STEADY_PAYWALL___

Wenn du den USB/Serial-Adapter verbunden hast, fehlt nur noch der Bewegungssensor. In diesem Projekt verwenden wir den PIR-Sensor HC-SR501, für den du nur einen Daten-Pin benötigst.

Orientiere dich beim Aufbau an diesem Schema:

Aufbau Fotofalle ESP32-CAM und PIR-Sensor

Hier noch einmal die Verbindungen übersichtlich dargestellt:

FTDI-AdapterESP32-CAM
GNDGND
VCC5V
TXDU0R
RXDU0T
HC-SR501ESP32-CAM
VCC3v3
GNDGND
OUTIO13

Hinweis: Im Schema oben findest du eine Brücke in Grau – denke immer daran, dass du diese benötigst, um einen Sketch hochzuladen. Sobald der Code auf deiner ESP32-CAM ist, trenne die Brücke und drücke den RESET-Button auf dem Board. Erst dann startet dein Sketch.

Das passende Gehäuse

Möchtest du die Fotofalle diskret aufbauen? Oder passt ein Breadboard nicht in das Design des Raums drumherum? Dann sind unsere Gehäuse die richtige Wahl:

Du kannst die .STL Dateien für die Gehäuse hier bei uns herunterladen.

Der Sketch

Für dieses Projekt haben wir einen Sketch von Random Nerd Tutorials als Ausgangspunkt genommen und diesen erweitert. Rui Santos verwendet hier die ESP32-CAM, um bei einer Bewegung ein Foto auf eine SD-Karte zu speichern.

Wir haben den Aufbau und den Code so modifiziert, dass dein Board das Foto an deinen Telegram-Bot, also an dein Smartphone sendet, sobald eine Bewegung erkannt wurde.

Du findest den gesamten Sketch auf Github als .txt und am Ende dieses Artikels.

Wichtige Anpassungen im Code der Fotofalle

Damit der Sketch bei dir funktioniert, musst du ein paar Kleinigkeiten anpassen. Trage zunächst die Zugangsdaten deines WLAN-Netzwerks ein, damit sich die ESP32-CAM damit verbinden kann.

Anschließend benötigst du deinen Token und die UserID, die du beim Erstellen deines Telegram-Bots erhalten hast.

const char* ssid = "NETWORK";
const char* password = "PASSWORD";
String BOTtoken = "TOKEN"; 
String CHAT_ID = "USERID";

Eine Besonderheit, die du nach deinen Wünschen anpassen kannst, ist die Zeit zwischen den Fotos. Im Loop findest du einen delay() von 20 Sekunden. Das ist die Zeitspanne, in der der Bewegungssensor nach einem gesendeten Foto keine weitere Bewegung registriert.

Je kürzer du diese Zeitspanne einstellst, desto mehr Fotos erhältst du – sofern sich weiterhin jemand vor der Kamera bewegt.

Ebenso kannst du die Empfindlichkeit deines HC-SR501 einstellen. An ihm findest du zwei Potis: Wenn du die Platine nach oben drehst, kannst du am linken von ihnen die Empfindlichkeit einstellen. Experimentiere hier ein wenig herum, um die passende Einstellung für dich zu finden.

Die Bibliothek UniversalTelegramBot

Die Bibliothek UniversalTelegramBot.h übernimmt die Kommunikation mit deinem Telegram-Bot. Du findest sie im Bibliotheksverwalter der Arduino IDE – diese kann jedoch veraltet sein. Deshalb empfehlen wir dir, die Bibliothek hier bei uns herunterzuladen.

Anschließend musst du diese Bibliothek in deinen Sketch einbinden, indem du im Menü der Arduino IDE Sketch -> Bibliothek einbinden -> .ZIP-Bibliothek hinzufügen wählst und die gerade heruntergeladene ZIP-Datei auswählst.

Noch ein Hinweis: Dieses Projekt dient dem Ausprobieren. Wenn du die Fotofalle produktiv einsetzen möchtest, mache dir vorab Gedanken über die IT-Sicherheit und achte darauf, keine Persönlichkeitsrechte zu verletzen!

Hier nun der gesamt Sketch zum Rauskopieren:

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/telegram-esp32-cam-photo-arduino/

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.

  Adapted by Pollux Labs – https://polluxlabs.net
*/

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include "esp_camera.h"
#include <UniversalTelegramBot.h>

const char* ssid = "NETWORK";
const char* password = "PASSWORD";

// Initialize Telegram BOT
String BOTtoken = "TOKEN";  // dein Bot-Token vom Botfather)

// Trage hier deine User-ID ein
String CHAT_ID = "CHAT-ID";

bool sendPhoto = false;

WiFiClientSecure clientTCP;
UniversalTelegramBot bot(BOTtoken, clientTCP);

//Pin of the motion sensor
#define PIR_PIN 13

//CAMERA_MODEL_AI_THINKER
#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27

#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22


void configInitCamera() {
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;

  //init with high specs to pre-allocate larger buffers
  if (psramFound()) {
    config.frame_size = FRAMESIZE_UXGA;
    config.jpeg_quality = 10;  //0-63 lower number means higher quality
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 12;  //0-63 lower number means higher quality
    config.fb_count = 1;
  }

  // camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    delay(1000);
    ESP.restart();
  }

  // Drop down frame size for higher initial frame rate
  sensor_t * s = esp_camera_sensor_get();
  s->set_framesize(s, FRAMESIZE_CIF);  // UXGA|SXGA|XGA|SVGA|VGA|CIF|QVGA|HQVGA|QQVGA
}

String sendPhotoTelegram() {
  const char* myDomain = "api.telegram.org";
  String getAll = "";
  String getBody = "";

  camera_fb_t * fb = NULL;
  fb = esp_camera_fb_get();  
  if(!fb) {
    Serial.println("Camera capture failed");
    delay(1000);
    ESP.restart();
    return "Camera capture failed";
  }  
  
  Serial.println("Connect to " + String(myDomain));


  if (clientTCP.connect(myDomain, 443)) {
    Serial.println("Connection successful");
    
    String head = "--RandomNerdTutorials\r\nContent-Disposition: form-data; name=\"chat_id\"; \r\n\r\n" + CHAT_ID + "\r\n--RandomNerdTutorials\r\nContent-Disposition: form-data; name=\"photo\"; filename=\"esp32-cam.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n";
    String tail = "\r\n--RandomNerdTutorials--\r\n";

    uint16_t imageLen = fb->len;
    uint16_t extraLen = head.length() + tail.length();
    uint16_t totalLen = imageLen + extraLen;
  
    clientTCP.println("POST /bot"+BOTtoken+"/sendPhoto HTTP/1.1");
    clientTCP.println("Host: " + String(myDomain));
    clientTCP.println("Content-Length: " + String(totalLen));
    clientTCP.println("Content-Type: multipart/form-data; boundary=RandomNerdTutorials");
    clientTCP.println();
    clientTCP.print(head);
  
    uint8_t *fbBuf = fb->buf;
    size_t fbLen = fb->len;
    for (size_t n=0;n<fbLen;n=n+1024) {
      if (n+1024<fbLen) {
        clientTCP.write(fbBuf, 1024);
        fbBuf += 1024;
      }
      else if (fbLen%1024>0) {
        size_t remainder = fbLen%1024;
        clientTCP.write(fbBuf, remainder);
      }
    }  
    
    clientTCP.print(tail);
    
    esp_camera_fb_return(fb);
    
    int waitTime = 10000;   // timeout 10 seconds
    long startTimer = millis();
    boolean state = false;
    
    while ((startTimer + waitTime) > millis()){
      Serial.print(".");
      delay(100);      
      while (clientTCP.available()) {
        char c = clientTCP.read();
        if (state==true) getBody += String(c);        
        if (c == '\n') {
          if (getAll.length()==0) state=true; 
          getAll = "";
        } 
        else if (c != '\r')
          getAll += String(c);
        startTimer = millis();
      }
      if (getBody.length()>0) break;
    }
    clientTCP.stop();
    Serial.println(getBody);
  }
  else {
    getBody="Connected to api.telegram.org failed.";
    Serial.println("Connected to api.telegram.org failed.");
  }
  return getBody;
}

void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
  // Init Serial Monitor
  Serial.begin(115200);

  //Set PIR
  pinMode(PIR_PIN, INPUT);

  // Config and init the camera
  configInitCamera();

  // Connect to Wi-Fi
  WiFi.mode(WIFI_STA);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  clientTCP.setCACert(TELEGRAM_CERTIFICATE_ROOT); // Add root certificate for api.telegram.org
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
}

void loop() {
  Serial.println(digitalRead(PIR_PIN));
  if (digitalRead(PIR_PIN)) {
    Serial.println("Preparing photo");
    sendPhotoTelegram();
    delay(20000);
  }
}

Lade den obigen Sketch nun auf deine ESP32-CAM hoch. Achte darauf, dass du die Brücke für den Upload schließt, sie danach wieder öffnest und das Board neustartest mit dem Reset-Button. Im Seriellen Monitor solltest du nun sehen, dass sich das Board mit deinem WLAN verbindet und anschließend auf eine Bewegung wartet – hier sollten in schneller Folge Nullen durch den Seriellen Monitor jagen, bis dein PIR-Sensor eine Bewegung erkannt hat. Daraufhin sendet dir die ESP32-CAM ein Foto der aktuellen Situation an deinen Telegram-Bot.

Mögliche Fehler

Solltest du eine Fehlermeldung im Zusammenhang mit TELEGRAM_CERTIFICATE_ROOT bekommen, lade die aktuelle Version der Bibltiothek UniversalTelegramBot herunter. Möglicherweise ist die Version auf unserem Server bereits veraltet und noch nicht rechtzeitig von uns aktualisiert worden. Auf GitHub findest du die neueste Version – klicke dort rechts oben auf Code und anschließend auf Download ZIP.

]]>
Eine ESP32-CAM (AI Thinker) mit der Arduino IDE programmieren https://polluxlabs.net/arduino-tutorials/eine-esp32-cam-ai-thinker-mit-der-arduino-ide-programmieren/ Fri, 02 Oct 2020 09:08:19 +0000 https://polluxlabs.net/?p=2802 Eine ESP32-CAM (AI Thinker) mit der Arduino IDE programmieren Weiterlesen »

]]>
Du möchtest Fotos aufnehmen und sie auf einer SD-Karte speichern, übers Internet verschicken oder gleich einen Video-Stream einrichten? Dann ist die ESP32-CAM eine tolle Sache! Es gibt da nur einen Umstand: Das Board hat keinen USB-Port. Also was tun?

In diesem Tutorial lernst du, wie du deine ESP32-CAM über einen USB/Serial-Adapter direkt in der Arduino IDE programmierst. Du benötigst hierfür:

Vorbereitungen in der Arduino IDE

Wenn du die ESP32-CAM noch nicht mit deiner Arduino IDE verbunden hast, hole das schnell nach. In diesem Tutorial erfährst du, wie du ESP32-Boards verfügbar machst.

Wenn du das erledigt hast, findest du im Menü Werkzeuge > Board den Eintrag AI Thinker ESP32-CAM – den brauchst du später.

Programmieren über einen Adapter

Da du die ESP32-CAM nicht direkt an deinen Computer anschließen kannst, benötigst du einen Adapter, der die serielle Kommunikation per USB ermöglicht. Diese werden auch FTDI-Adapter genannt – nach dem Unternehmen FTDI, das sich auf diese Anwendungen spezialisiert hat. Ein bisschen wie Tempo also. 🙂

Diese Adapter gibt es in vielen Ausführungen von vielen Herstellern. Oben haben wir dir einen verlinkt, den du zwischen 3,3V und 5V umschalten kannst – was ganz praktisch ist.

Um nun also einen Sketch hochzuladen, verbinde deine ESP32-CAM und den FTDI-Adapter wie folgt:

ESP32-CAM mit FTDI-Adapter programmieren

Hier noch einmal die Verbindungen übersichtlich dargestellt:

FTDI-AdapterESP32-CAM
GNDGND
VCC5V
TXDU0R
RXDU0T

Wichtig: Wenn du einen Sketch hochlädst, musst du an deiner ESP32-CAM den Pin IO0 mit GND verbinden – im Schema oben hellblau dargestellt. Um den Sketch später auszuführen, trenne diese Verbindung wieder und drücke den Reset-Button.

Wenn du den Adapter und die ESP32-CAM wie oben beschrieben miteinander verkabelt hast, verbinde den Adapter mit deinem Computer. Wähle dann in der Arduino IDE das Board AI Thinker ESP32-CAM und den richtigen Port aus und lade deinen Sketch hoch.

Funktioniert nicht?

Der häufigste Fehler ist die fehlende Verbindung zwischen IO0 und GND, die du beim Upload herstellen musst. Wenn du hierfür ein Kabel verwendest, prüfe es auf seine Funktion.

Eine weitere Fehlerquelle kann auch das USB-Kabel sein: Kannst du damit Daten übertragen oder liefert es nur Strom? Manchmal kommt es auch zu Problemen, wenn das Kabel zu lang ist – probiere einfach mal ein anderes.

Wenn der Fehler woanders liegen muss, findest du möglicherweise in der recht umfangreichen Dokumentation bei Random Nerd Tutorials (Englisch) eine Lösung.

]]>
Deinen ESP32 mit der Arduino IDE programmieren https://polluxlabs.net/arduino-tutorials/deinen-esp32-mit-der-arduino-ide-programmieren/ Thu, 01 Oct 2020 13:52:35 +0000 https://polluxlabs.net/?p=2806 Deinen ESP32 mit der Arduino IDE programmieren Weiterlesen »

]]>
Der ESP32 ist so etwas wie der große Bruder des ESP8266. Mit ihm verfügst du nicht nur über WiFi, sondern kannst auch Bluetooth nutzen. Und das beste: Du kannst es ganz einfach mit deiner Arduino IDE programmieren – genauso wie deine Arduino-Boards. Ein paar Vorbereitungen musst du hierfür allerdings treffen.

Lerne hier, wie du in 5 Minuten deinen ESP32 in deiner Arduino IDE installierst und anschließend mit dem Programmieren loslegen kannst.

Dein Board in der Arduino IDE installieren

Öffne zuerst die Einstellungen deiner Arduino IDE. Dort findest du das Feld Zusätzliche Boardverwalter-URLs. Trage hier die folgende Adresse ein:

https://dl.espressif.com/dl/package_esp32_index.json
ESP32 URL in der Arduino IDE eintragen

Tipp: Wenn du dort schon die URL deines ESP8266 eingetragen hast, schreibe die des ESP32 einfach mit einem Komma getrennt dahinter. Dann verfügst du in der Arduino IDE über beide.

Schließe nun das Fenster mit einem Klick auf OK. Öffne als nächstes das Menü Werkzeuge und wähle dort den Menüpunkt Boards und anschließend Boardverwalter.

Suche in dem Fenster, das sich jetzt öffnet, nach ESP32. Vermutlich wirst du dort nur einen Eintrag finden. Noch ein Klick auf Installieren, kurz warten und das sollte es gewesen sein. 🙂

ESP32 im Boardverwalter
Das Paket für deinen ESP32 installieren

Einen Sketch auf den ESP32 hochladen

Schließe nun deinen ESP32 an und wähle im Menü unter Werkzeuge > Board dein Modell und den richtigen Port aus.

Unter Datei > Beispiele findest du unter WiFi den Sketch WiFiScan. Dieser eignet sich für einen ersten Test, denn er macht nichts anderes als die verfügbaren WLAN-Netzwerke in der Umgebung anzuzeigen.

Öffne also diesen Sketch und lade ihn auf deinen ESP32. Nach dem Upload findest du in deinem Seriellen Monitor (115200 Baud) die Netzwerke in der Umgebung.

Funktioniert nicht?

Solltest du beim Upload Probleme haben, probiere folgendes:

Halte den Button BOOT auf deinem ESP32 gedrückt und starte den Upload. Sobald in der Arduino IDE der Status “Connecting…” erscheint, kannst du den Button wieder loslassen. Der Upload sollte jetzt durchlaufen.

]]>
So verwendest du Bluetooth am ESP32 https://polluxlabs.net/arduino-tutorials/so-verwendest-du-bluetooth-am-esp32/ Thu, 30 Jul 2020 07:27:43 +0000 https://polluxlabs.net/?p=1845 So verwendest du Bluetooth am ESP32 Weiterlesen »

]]>
Bluetooth ist allgegenwärtig und auch dein ESP32 kann damit umgehen. Lerne in diesem Tutorial, wie du Bluetooth Classic verwendest und Daten zwischen einem Smartphone und deinem ESP32 austauschst.

Für dieses Tutorial benötigst du nur einen ESP32 und ein Android-Smartphone, auf dem du die kostenfreie App Serial Bluetooth Terminal installieren kannst.

So machst du deinen ESP32 in der Arduino IDE verfügbar

Falls du deinen ESP32 bisher noch nicht mit der Arduino IDE programmiert hast, führe bitte erst die folgenden Schritte durch:

Öffne die Einstellungen der IDE und trage in des Feld Zusätzliche Boardverwalter-URLs folgenden Link ein.

https://dl.espressif.com/dl/package_esp32_index.json

Öffne anschließend den Boardverwalter unter Werkzeuge/Board. Suche dort nach ESP32 und installiere dir die neueste Version des gleichnamigen Pakets von espressif Systems. Und das war es auch schon – du solltest deinen ESP32 nun wie jedes andere Board in der Arduino IDE auswählen und verbinden können.

Der Sketch für deinen ESP32

Für ein grundlegendes Experiment mit Bluetooth benötigst du nur wenig Code:

#include "BluetoothSerial.h"

BluetoothSerial SerialBT;

void setup() {
  Serial.begin(115200);
  SerialBT.begin("ESP32test"); //Name des ESP32
  Serial.println("Der ESP32 ist bereit. Verbinde dich nun über Bluetooth.");
}

void loop() {
  if (Serial.available()) {
    SerialBT.write(Serial.read());
  }
  if (SerialBT.available()) {
    Serial.write(SerialBT.read());
  }
  delay(25);
}

Schauen wir uns die wichtigsten Teile genauer an: Zunächst bindest du die Bibliothek BluetoothSerial.h ein, die bereits in deiner Arduino IDE verfügbar ist und nicht extra installiert werden muss. Anschließend erstellst du eine Instanz namens SerialBT.

Im Setup startest du die serielle Kommunikation mit einer Baudrate von 115200 – achte darauf, dass dein Serieller Monitor auch auf diese Rate eingestellt ist. Anschließend initialisierst du Bluetooth und gibst deinem ESP32 den Namen ESP32test.

Im Loop befinden sich zwei Abfragen: Die erste prüft, ob du etwas in den Seriellen Monitor eingetragen und abgeschickt hast.

Wenn das der Fall ist, schickst du die Daten an den ESP32:

SerialBT.write(Serial.read());

In der zweiten Abfrage wird geprüft, ob der ESP32 etwas über Bluetooth empfangen hat. Diese Daten werden dann im Seriellen Monitor ausgegeben.

Ein erster Test

Gehen wir also zur Praxis über. Für diesen Test verbindest du ein Android-Smartphone mit deinem ESP32 und schickst Nachrichten von einem Gerät aufs andere.

Lade zunächst den obigen Sketch wie gewohnt auf deinen ESP32 und starte den Seriellen Monitor mit der Baudrate 115200. Sollte sich hier nichts tun, drücke den Reset- oder Enable-Button am ESP32. Anschließend sollte nach einigen Hardware-Informationen folgender Satz aus deinem Sketch angezeigt werden: Der ESP32 ist bereit. Verbinde dich nun über Bluetooth.

In den Bluetooth-Einstellungen deines Smartphone solltest du nun deinen ESP32 mit dem Namen ESP32test sehen und eine Verbindung herstellen können.

ESP32 mit dem Smartphone pairen

Falls noch nicht geschehen, lade dir die App Serial Bluetooth Terminal aus dem Play Store und öffne sie. Unter dem Menüpunkt Devices/Bluetooth Classic erscheint nun ebenfalls dein ESP32, mit dem du dich mit einem Tap verbinden kannst.

ESP32 im Serial Bluetooth Terminal auswählen

Zeit für etwas Konversation! Schreibe deinem ESP32 in der App eine Nachricht und sende sie ab. Dein Text wird auf der Gegenseite empfangen und im Seriellen Monitor ausgegeben.

Nachricht an einen ESP32 senden

In die andere Richtung funktioniert das genauso. Schreibe etwas in das Eingabefeld oben in deinem Seriellen Monitor und klicke auf Senden. Diese Nachricht wird dann von deinem ESP32 aus an dein Smartphone gesendet und dort angezeigt:

Nachricht vom ESP32 empfangen

Und das war auch schon dein erster Test. Wenn du Nachrichten hin und herschicken kannst, geht das natürlich auch mit allen anderen Daten.

Als nächstes kannst du einen Sensor an den ESP32 anschließen und dessen Daten in regelmäßigen Abständen an dein Smartphone senden. Oder andersherum mit deinem Smartphone z.B. eine LED an deinem Microcontroller an- und ausschalten. Wie du vom Smartphone aus Geräte steuert, lernst du auch in unserem Kurs Dein eigener ESP8266 Webserver.

]]>