Pollux Labs https://polluxlabs.net Arduino, ESP32 & ESP8266 | Projekte & Tutorials Tue, 01 Jul 2025 07:25:03 +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 Pollux Labs https://polluxlabs.net 32 32 RFID-Karten mit dem Raspberry Pi 5 und RC522 auslesen https://polluxlabs.net/raspberry-pi-projekte/rfid-karten-mit-dem-raspberry-pi-5-und-rc522-auslesen/ Tue, 01 Jul 2025 07:22:58 +0000 https://polluxlabs.net/?p=19458 RFID-Karten mit dem Raspberry Pi 5 und RC522 auslesen Weiterlesen »

]]>
Dieses Tutorial führt dich durch den Prozess, einen RC522 RFID-Reader über die SPI-Schnittstelle mit einem Raspberry Pi 5 zu verbinden und mit einer minimalistischen Python-Bibliothek die eindeutige ID (UID) von RFID-Karten auszulesen. Viele bestehende Anleitungen für den RC522 nutzen die RPi.GPIO-Bibliothek, die auf dem Raspberry Pi 5 nicht mehr ohne Weiteres funktioniert. Dieses Tutorial löst dieses Problem, indem es eine schlanke Python-Bibliothek nutzt, die auf der modernen und kompatiblen gpiod-Bibliothek aufbaut.

✨ Kernfunktionen der Bibliothek

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

Hardware: Voraussetzungen und Verkabelung

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

Benötigte Hardware

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

Verkabelungsplan (SPI)

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

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

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

Software: System einrichten

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

a) SPI-Schnittstelle aktivieren

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

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

b) Systemabhängigkeiten installieren

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

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

Bibliothek und Beispiel-Code

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

a) Die Bibliotheksdatei: rc522_spi_library.py

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

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

import time
import logging

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        self._initialized = False
        self.initialize()

    def __enter__(self):
        return self

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        if n & 0x01:
            status = StatusCodes.ERROR

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

            if fifo_size == 0:
                fifo_size = 1

            if fifo_size > 16:
                fifo_size = 16

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

        return status, back_data, back_len

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

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

b) Die Beispieldatei: example.py

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

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

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

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

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

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

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

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

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

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

if __name__ == '__main__':
    main()

Anwendung und Test

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

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

Beispiel-Ausgabe:

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

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

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

]]>
So baust du einen ESP32 Webserver https://polluxlabs.net/esp8266-projekte/so-baust-du-einen-esp32-webserver/ Tue, 24 Jun 2025 08:55:13 +0000 https://polluxlabs.net/?p=19423 So baust du einen ESP32 Webserver Weiterlesen »

]]>
In diesem Projekt baust du dir deinen eigenen ESP32 Webserver. Hierbei arbeitest du dich schrittweise voran: vom einfachen „Hello World“ über die Steuerung einer LED bis zur Abfrage der aktuellen Temperatur. All dies stellt dein Microcontroller auf einer Webseite zur Verfügung, die du von einem Gerät deiner Wahl aufrufen kannst. Los geht’s!

Warum ein Webserver auf dem ESP32?

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

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

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

Was du hierfür brauchst:

Der Sketch

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

//ESP32 Webserver
//polluxlabs.net

#include <WiFi.h>

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

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

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

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

  WiFi.begin(ssid, password);

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

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

  server.begin();
}

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

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

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

Was passiert hier?

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

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

Webseite mit Hallo Welt vom ESP32 Webserver

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

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

Was du zusätzlich brauchst:

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

Der Aufbau auf dem Breadboard

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

Aufbau des ESP32 Webservers mit LED

Der Sketch

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

___STEADY_PAYWALL___

//ESP32 Webserver mit LED
//polluxlabs.net

#include <WiFi.h>

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

WiFiServer server(80);

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

void setup() {
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);

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

  WiFi.begin(ssid, password);

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

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

  server.begin();
}

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

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

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

Was ist neu?

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

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

Webseite des ESP32 Webservs mit LED-Steuerung

Teil 3: Temperaturanzeige mit einem DHT11

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

Was du zusätzlich brauchst:

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

Der Aufbau auf dem Breadboard

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

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

Der Sketch

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

//ESP32 Webserver mit Temperaturmessung
//polluxlabs.net

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

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

WiFiServer server(80);

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

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

  WiFi.begin(ssid, password);

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

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

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

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

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

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

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

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

Das ist neu

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

Teil 4: LED-Steuerung und Temperatur zusammen

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

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

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

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

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

WiFiServer server(80);

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

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

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

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

  server.begin();
}

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

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

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

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

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

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

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

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

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

Was ist neu?

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

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

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

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

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

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

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

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

WiFiServer server(80);

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

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

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

  server.begin();
}

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

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

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

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

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

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

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

Das Ergebnis

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

Webseite des ESP32 Webservers

Was ist neu und warum?

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

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

Fazit

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

]]>
Arduino RFID: Der Einstieg mit dem RC522-Reader https://polluxlabs.net/arduino-tutorials/arduino-rfid-der-einstieg-mit-dem-rc522-reader/ Mon, 23 Jun 2025 08:57:01 +0000 https://polluxlabs.net/?p=19411 Arduino RFID: Der Einstieg mit dem RC522-Reader Weiterlesen »

]]>
In diesem Tutorial tauchen wir in eine faszinierende Technologie ein, die uns im Alltag ständig begegnet: RFID. Ob beim kontaktlosen Bezahlen, bei der Zeiterfassung oder in modernen Schließsystemen – die kleinen Chips sind überall. Aber hast du dich schon mal gefragt, wie das eigentlich funktioniert? Und noch besser: Wie du es selbst für deine eigenen Projekte nutzen kannst?

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

Was ist RFID eigentlich?

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

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

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

Für dieses Arduino RFID Tutorial brauchst du

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

Schritt 1: Die Verkabelung

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

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

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

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

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

Schritt 2: Die Bibliothek installieren

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

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

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

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

Schritt 3: Einen Tag auslesen

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

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

// Arduino RFID
//polluxlabs.net

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

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

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

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

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

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

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

Was macht dieser Code?

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

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

Wie geht es weiter?

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

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

Experimentiere mit dem Code und entwickle deine eigenen Ideen.

]]>
Sprachmodelle mit Ollama auf dem Raspberry Pi nutzen https://polluxlabs.net/raspberry-pi-projekte/sprachmodelle-mit-ollama-auf-dem-raspberry-pi-nutzen/ Fri, 07 Mar 2025 11:31:10 +0000 https://polluxlabs.net/?p=19301 Sprachmodelle mit Ollama auf dem Raspberry Pi nutzen Weiterlesen »

]]>
KI-Sprachmodelle wie ChatGPT, Claude, Gemini, Deepseek etc. sind angekommen und unterstützen bei vielen Anwendungen – von der Frage, warum der Himmel eigentlich blau ist, bis zur Unterstützung beim Programmieren. Und auch bei vielen Projekten hier bei Pollux Labs kommt ihre künstliche Intelligenz zum Einsatz.

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

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

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

Das passende Raspberry Pi Modell

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

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

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

sudo apt update
sudo apt upgrade

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

sudo apt install curl

Ollama auf dem Raspberry Pi installieren

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

___STEADY_PAYWALL___

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

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

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

ollama --version

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

Ollama Version im Terminal

Sprachmodelle laden und starten

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

Deepseek-r1

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

Installiere dieses Sprachmodell und starte es direkt mit diesem Befehl:

ollama run deepseek-r1:1.5b

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

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

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

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

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

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

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

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

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

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

TinyLlama

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

ollama run tinyllama

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

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

Phi-3

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

ollama run phi3

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

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

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

Gemma 3 1B

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

ollama run gemma3:1b 

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

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

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

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

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

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

Gemma 3 4B

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

ollama run gemma3:4b

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

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

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

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

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

Fazit

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

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

]]>
Update fürs ESP32 Internetradio: Songs in Spotify speichern https://polluxlabs.net/raspberry-pi-projekte/update-fuers-esp32-internetradio-songs-in-spotify-speichern/ Tue, 25 Feb 2025 11:07:00 +0000 https://polluxlabs.net/?p=19195 Update fürs ESP32 Internetradio: Songs in Spotify speichern Weiterlesen »

]]>
Hast du schon einmal einen Song im Radio gehört, den du dir merken wolltest, damit du ihn später auf Spotify hören kannst? Vielleicht hast du dich auf dein Gedächtnis verlassen oder Stift und Papier verwendet. Aber es geht auch eleganter:

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

Für dieses Update benötigst du:

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

Den Raspberry Pi vorbereiten

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

Das Betriebssystem installieren

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

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

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

Verbinde dich per SSH mit dem Raspberry Pi

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

sudo ssh pi@raspberrypi.local

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

Installiere die benötigten Pakete

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

sudo apt update
sudo apt upgrade

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

sudo apt install python3-pip

Eine virtuelle Umgebung einrichten

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

sudo apt install python3-venv
python3 -m venv RadioSpotify --system-site-packages

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

source RadioSpotify/bin/activate

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

pip3 install spotipy

Spotify vorbereiten

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

Eine neue App im Spotify Entwickler-Konto erstellen

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

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

Client ID und Client Secret im Spotify Entwicklerkonto

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

Das Python-Script für das Radio

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

___STEADY_PAYWALL___

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

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

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

SPOTIPY_CLIENT_ID = 'deine_client_id'
SPOTIPY_CLIENT_SECRET = 'dein_client_secret'

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

Im Verzeichnis angekommen führe folgenden Befehl aus:

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

Den Code ausführen

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

sudo ssh pi@raspberrypi.local

Anschließend kannst du das Script wie folgt starten:

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

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

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

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

curl "DEINE KOPIERTE URL"

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

Ein weiteres Kabel am ESP32

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

ESP32 Internetradio mit Spotify-Anbindung

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

Aktualisiere den Sketch auf dem ESP32

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

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

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

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

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

#define I2C_SDA 8
#define I2C_SCL 9

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

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

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

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

Audio audio;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

hostname -I

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

IP-Adresse des Raspberry Pi im Terminal

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

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

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

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

Einen Song an Spotifiy übertragen

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

Erfolgreiche Übertragung eines Songs an Spotify

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

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

Das Script automatisch starten

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

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

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

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

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

sudo systemctl status spotify-radio

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

Spotify-Service läuft im Terminal

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

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

]]>
Die Zimmer lüften mit dem Arduino https://polluxlabs.net/arduino-projekte/die-zimmer-lueften-mit-dem-arduino/ Thu, 05 Dec 2024 09:22:06 +0000 https://polluxlabs.net/?p=18059 Die Zimmer lüften mit dem Arduino Weiterlesen »

]]>
Gehörst du zu den Leuten, die gerne die Zimmer lüften, aber dann vergessen, dass das Fenster offen ist? Mir passiert das regelmäßig – bis jetzt. In diesem Projekt überwacht ein Arduino Nano die Temperatur, sobald du das Fenster zum Lüften öffnest. Fällt die Temperatur im Raum um 0,5°C schlägt er Alarm. So kannst du sicher sein, dass der Raum oder sogar die ganze Wohnung nicht unnötig auskühlt.

Diese Bauteile benötigst du:

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

So funktioniert die Temperaturüberwachung

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

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

Aufgebauter Arduino Temperaturwächter

So baust du das Projekt zusammen

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

Aufbau des Projekts mit dem Arduino Nano und weiteren Bauteilen

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

Der Sketch für das Projekt

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

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

Nun zum vollständigen Sketch:

___STEADY_PAYWALL___

#include <DHT.h>

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

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

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

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

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

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

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

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

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

So funktioniert der sketch

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

#include <DHT.h>

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

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

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

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

Setup-Funktion

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

void setup() {

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

Loop-Funktion

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

if (digitalRead(BUTTON_PIN) == LOW) {

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

}

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

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

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

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

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

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

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

Wie geht es weiter?

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

]]>
So verwendest du einen Arduino Joystick https://polluxlabs.net/arduino-tutorials/arduino-joystick-erste-schritte/ Wed, 27 Nov 2024 10:41:40 +0000 https://polluxlabs.net/?p=18031 So verwendest du einen Arduino Joystick Weiterlesen »

]]>
Das Joystick-Modul ist ein vielseitiges Eingabegerät, das häufig in Maker-Projekten verwendet wird. In diesem Artikel erfährst du, wie du einen Arduino Joystick an einen Arduino UNO anschließt, wie er funktioniert und wie du ihn in deinem nächsten Projekt einsetzen kannst.

So funktioniert der Joystick

Das Joystick-Modul verwendet Potentiometer für die x- und y-Achse. Wenn der Joystick bewegt wird, ändert sich der Widerstand der Potentiometer, was zu einer Änderung der Ausgangsspannung führt. Diese Spannung wird vom Arduino ausgelesen und in einen Zahlenwert zwischen 0 und 1023 umgewandelt.

Mit diesen Werte kannst du dann weiterarbeiten: Die Höhe des Werts zeigt dir an, in welche Richtung der Joystick bewegt wurde, also nach oben, unten, links oder rechts. Neben den Potis für die x- und y-Achse findest du meistens noch einen Button, den du betätigst, indem den Stick drückst. Auch das kannst du natürlich in deinem Sketch auslesen und bei Bedarf in deinem Projekt verwenden.

Und so schließt du ihn an

Ein typischer Arduino Joystick hat fünf Pins. Die Stromversorgung und die beiden Anschlüsse für die x- und y-Achse sind hierbei obligatorisch. Der Pin SW ist mit dem Button verbunden – wenn in deinem Projekt später keinen Button benötigst, brauchst du ihn nicht mit deinem Microcontroller verbinden. Im Folgenden verwenden wir jedoch alle fünf Anschlüsse:

Anschlussdiagramm des Arduino Joysticks am Arduino UNO R4

___STEADY_PAYWALL___

Beachte, dass die Beschriftung der Pins je nach Modell variieren kann: Meistens findest du jedoch VRx für die horizontale x-Achse (HOR) und VRy für die vertikale y-Achse (VER).

Ein erster Test mit dem ARduino Joystick

Nun, wo du den Joystick angeschlossen hast, wird es Zeit für einen ersten Test. Hierbei liest du einfach die Bewegungen des Sticks und den Button aus und zeigst deine Aktionen entsprechend im Seriellen Monitor an.

Kopiere den folgenden Sketch und lade ihn auf deinen Arduino:

const int joyPinX = A0; // Pin für die x-Achse des Joysticks
const int joyPinY = A1; // Pin für die y-Achse des Joysticks
const int joyPinSW = A2; // Pin für den Button des Joysticks

void setup() {
  pinMode(joyPinSW, INPUT_PULLUP); // Button-Pin als Eingang mit internem Pull-up-Widerstand
  Serial.begin(9600); // Serielle Kommunikation starten
}

void loop() {
  int xValue = analogRead(joyPinX); // x-Achse auslesen
  int yValue = analogRead(joyPinY); // y-Achse auslesen
  int swValue = analogRead(joyPinSW); // Button auslesen

  // Richtung ermitteln
  String direction = "Mitte";
  if (xValue > 600) {
    direction = "Rechts";
  } else if (xValue < 400) {
    direction = "Links";
  }
  if (yValue > 600) {
    direction = "Unten";
  } else if (yValue < 400) {
    direction = "Oben";
  }

  // Werte im seriellen Monitor anzeigen
  Serial.print("Richtung: ");
  Serial.print(direction);
  Serial.print(" | X: ");
  Serial.print(xValue);
  Serial.print(" | Y: ");
  Serial.print(yValue);
  Serial.print(" | Button: ");
  Serial.print(swValue);
  if (swValue < 20) {
    Serial.println(" | Gedrückt!");
  } else {
    Serial.println(" | ");
  }

  delay(100); // Kurze Pause
}

Teste nun deinen Joystick – siehst du im Seriellen Monitor die Richtungen und einen Hinweis, wenn du den Button drückst? Falls du dich wunderst, dass die ermittelten Richtungen nicht mit deinen Absichten übereinstimmen: Dann positioniere deinen Arduino Joystick bitte wie auf diesem Foto. 😉

Arduino Joystick richtig ausrichten

So funktioniert der Sketch

Zunächst legst du die Pins fest, an denen dein Joystick mit dem Arduino UNO verbunden ist:

const int joyPinX = A0; // Pin für die x-Achse des Joysticks
const int joyPinY = A1; // Pin für die y-Achse des Joysticks
const int joyPinSW = A2; // Pin für den Button des Joysticks

Im Setup legst du fest, dass der Button des Arduino Joysticks den internen Pullup-Widerstand nutzt, damit du seine Werte zuverlässig auslesen kannst. Außerdem startest du den Seriellen Monitor:

void setup() {
  pinMode(joyPinSW, INPUT_PULLUP); // Button-Pin als Eingang mit internem Pull-up-Widerstand
  Serial.begin(9600); // Serielle Kommunikation starten
}

Der Loop beginnt damit, dass dein Arduino die aktuellen Werte des Joysticks einliest und basierend darauf die gedrückte Richtung ermittelt:

void loop() {
  int xValue = analogRead(joyPinX); // x-Achse auslesen
  int yValue = analogRead(joyPinY); // y-Achse auslesen
  int swValue = analogRead(joyPinSW); // Button auslesen

  // Richtung ermitteln
  String direction = "Mitte";
  if (xValue > 600) {
    direction = "Rechts";
  } else if (xValue < 400) {
    direction = "Links";
  }
  if (yValue > 600) {
    direction = "Unten";
  } else if (yValue < 400) {
    direction = "Oben";
  }

Wie eingangs erwähnt sind im Arduino Joystick zwei Potentiometer verbaut, für jede Achse eines. Am Beispiel der x-Achse bedeutet das: Ein Drücken nach links bis zum Anschlag erzeugt den Wert 0. Diese Werte steigen an, bis sie in der Ruheposition des Sticks im Bereich von 500 liegen. Dein Drücken nach rechts lässt die Werte weiter steigen, bis sie am Anschlag den vollen Wert 1023 erreichen.

Fehlen noch zwei Dinge: Die Ausgabe der Richtungen im Seriellen Monitor und der Check, ob du den Button des Sticks gedrückt hast:

  // Werte im seriellen Monitor anzeigen
  Serial.print("Richtung: ");
  Serial.print(direction);
  Serial.print(" | X: ");
  Serial.print(xValue);
  Serial.print(" | Y: ");
  Serial.print(yValue);
  Serial.print(" | Button: ");
  Serial.print(swValue);
  if (swValue < 20) {
    Serial.println(" | Gedrückt!");
  } else {
    Serial.println(" | ");
  }

  delay(100); // Kurze Pause
}

Eine Besonderheit des Arduino Joysticks ist, dass du an einem Analog-Pin ermitteln musst, ob der Button gedrückt wurde. Der ungedrückte Button liefert an seinem Pin SW immer einen Wert von ungefähr 500. Erst wenn du ihn betätigst fällt dieser Wert auf Null. Diesen Moment fängst du im Code oben mit einer If-Abfrage ein und gibst entsprechend ein Gedrückt! im Seriellen Monitor aus.

Analog-Stick und diagonale Bewegungen

Wie du im Seriellen Monitor erkennst, ermittelt dein Arduino Werte, die von der Intensität deiner Bewegungen abhängen. Das heißt konkret, dass zum Beispiel der Wert bei einer Bewegung nach rechts immer weiter ansteigt, bis er ab einer Größe von 600 als „nach rechts gedrückt“ gilt. Damit kannst du aber auch feine Bewegungen erkennen, ähnlich wie bei einem Analog-Stick eines herkömmlichen Controllers für die Playstation, Xbox oder Switch.

Falls du also für dein Projekt sowohl schnelle als auch langsame Bewegungen erkennen möchtest, setze bei den Werten mehrere Schwellenwerte: Der Wert 600 könnte dann ein gemächliches Schleichen sein, der Maximalwert von 1023 (also der Anschlag des Joysticks nach rechts) dann die volle Geschwindigkeit.

Auch diagonale Möglichkeiten sind möglich, denn diese sind nichts anderes als eine Kombination von x- und y-Werten. Ersetze in deinem Sketch den Teil Richtung ermitteln durch folgenden Code:

  // Richtung ermitteln
  String direction = "Mitte";
  if (xValue > 600 && yValue > 600) {
    direction = "Rechts-Unten";
  } else if (xValue > 600 && yValue < 400) {
    direction = "Rechts-Oben";
  } else if (xValue < 400 && yValue > 600) {
    direction = "Links-Unten";
  } else if (xValue < 400 && yValue < 400) {
    direction = "Links-Oben";
  } else if (xValue > 600) {
    direction = "Rechts";
  } else if (xValue < 400) {
    direction = "Links";
  } else if (yValue > 600) {
    direction = "Unten";
  } else if (yValue < 400) {
    direction = "Oben";
  }

Hier wird zum Beispiel eine Bewegung nach links oben erkannt, wenn sowohl der x-Wert als auch der y-Wert unter 400 liegt. Entsprechend behandelst du die anderen drei diagonalen Richtungen. Das ermöglicht dir weitere Möglichkeiten für deine Steuerung – mit etwas Fleißarbeit sind sogar noch weitere Abstufungen möglich.

Wie geht es weiter?

Jetzt wo du weißt, wie du deinen Arduino Joystick anschließt und verwendest, möchtest du bestimmt ein passendes Projekt bauen. In diesem Tutorial lernst du, wie du den Spieleklassiker Snake auf einem Arduino und ESP32 umsetzt.

]]>
Geheime Botschaften: Verschlüsselte Nachrichten als Audio übertragen https://polluxlabs.net/python-tutorials-und-projekte/geheime-botschaften-verschluesselte-nachrichten-als-audio-uebertragen/ Sun, 17 Nov 2024 12:18:33 +0000 https://polluxlabs.net/?p=17906 Geheime Botschaften: Verschlüsselte Nachrichten als Audio übertragen Weiterlesen »

]]>
Nachrichten zu versenden, die Ende-zu-Ende-verschlüsselt sind, gehört mittlerweile zum Standard vieler Nachrichten-Apps wie Signal, Threema und WhatsApp. Aber wie wäre es mit etwas spielerischem Retro-Charme? In diesem Projekt wandelst du Textnachrichten in Töne um und versendest sie als Audio-Dateien im Anhang einer E-Mail. Der Empfänger kann diese Audio-Dateien dann mit Hilfe eines zuvor zwischen euch vereinbarten Schlüsselworts entschlüsseln und lesen. Nicht Eingeweihte hören nur eine Abfolge von Tönen, wie in diesem Beispiel:

Zum Einsatz kommen hierbei zwei Python-Scripte – je eines für den Sender und den Empfänger. Als E-Mail-Provider dient Gmail.

Gmail für den Versand der E-Mails einrichten

Zunächst benötigst du einen E-Mail-Provider, den du aus dem Python-Script des Senders ansteuern und für den Versand der E-Mails nutzen kannst. Hier bietet sich Googles Gmail an, da die Einrichtung unkompliziert ist. Wichtig: Falls du bereits eine Mail-Adresse bei Gmail besitzt, richte dir für dieses Projekt trotzdem eine neue ein. So stellst du sicher, dass zum Beispiel ein fehlerhaftes Versenden von vielen E-Mails hintereinander zu einer vorübergehenden Sperrung deines Kontos führt.

Wie du eine E-Mail-Adresse bei Gmail und sie für den Versand aus einem Python-Script einrichtest, erfährst du in diesem Tutorial.

Die benötigten Python-Bibliotheken

Im Folgenden verwendest du die Bibliotheken numpy und scipy. Die numpy-Bibliothek wird später verwendet, um numerische Operationen durchzuführen, die für die Analyse der Frequenzen in den Audiodaten benötigt werden. Die scipy-Bibliothek enthält die Funktion wavfile.read, die verwendet wird, um die erzeugten WAV-Datei einzulesen und die Audiodaten sowie die Abtastrate zu extrahieren. Diese Bibliotheken sind nicht standardmäßig in Python enthalten und müssen daher manuell installiert werden. Um sie zu installieren, verwenden Sie den folgenden Befehl:

pip install numpy scipy

Das Script für den Sender der verschlüsselten Nachrichten

Wenn du nun eine E-Mail-Adresse bei Gmail eingerichtet und die beiden benötigten Bibliotheken installiert hast, kann es direkt weitergehen mit dem Python-Script für den Sender. Hier der vollständige Code:

___STEADY_PAYWALL___

# Verschlüsselte Nachrichten - Script für den Sender
# Pollux Labs, polluxlabs.net

import numpy as np
from scipy.io.wavfile import write
import smtplib
from email.message import EmailMessage
import socket

# Parameter, die vom Benutzer bearbeitet werden müssen
user_parameters = {
    "sample_rate": 44100,  # Abtastrate (Hz)
    "bit_duration": 0.1,   # Dauer eines Bits (in Sekunden)
    "freq_0": 1000,        # Frequenz für "0" (Hz)
    "freq_1": 2000,        # Frequenz für "1" (Hz)
    "encryption_key": "geheimer_schluessel",  # Verschlüsselungsschlüssel (beide Parteien müssen denselben Schlüssel verwenden)
    "sender_email": "Absender-Adresse",  # Absender-E-Mail-Adresse
    "sender_password": "App-Passwort",        # App-Passwort für die Absender-E-Mail
    "receiver_email": "Empfänger-Adresse",      # Empfänger-E-Mail-Adresse
    "email_subject": "Betreff",   # Betreff der E-Mail
    "email_body": "Inhalt der E-Mail, z.B. Hier kommt eine verschlüsselte Nachricht für dich.",  # E-Mail-Inhalt
    "wav_filename": "message.wav"           # Name der zu speichernden WAV-Datei
}

# Funktion zur Erstellung eines Tons für ein Bit
def generate_tone(frequency, duration, sample_rate):
    # Erzeugt eine Zeitachse von 0 bis zur angegebenen Dauer mit der entsprechenden Anzahl an Samples
    t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
    # Berechnet den Sinuswert für die gegebene Frequenz über die Zeitachse
    return np.sin(2 * np.pi * frequency * t)

# Nachricht in Binärdaten umwandeln
def text_to_binary(text):
    # Wandelt jeden Buchstaben der Nachricht in eine 8-Bit Binärdarstellung um
    binary_data = ''.join(format(ord(char), '08b') for char in text)
    return binary_data

# Nachricht mit dem Schlüssel verschlüsseln
def encrypt_message(text, key):
    encrypted_message = ""
    for i in range(len(text)):
        encrypted_char = chr(ord(text[i]) ^ ord(key[i % len(key)]))
        encrypted_message += encrypted_char
    return encrypted_message

# Nachricht in modulierte Audiodaten umwandeln
def encode_to_audio(binary_data, bit_duration, sample_rate, freq_0, freq_1):
    # Initialisiert ein leeres Array für die Audiodaten
    audio = np.array([])
    # Iteriert durch jedes Bit der Binärdaten
    for bit in binary_data:
        # Erzeugt einen Ton für "0" oder "1" und fügt ihn an das Audioarray an
        if bit == '0':
            audio = np.append(audio, generate_tone(freq_0, bit_duration, sample_rate))
        else:
            audio = np.append(audio, generate_tone(freq_1, bit_duration, sample_rate))
    return audio

# Funktion zum Versenden der WAV-Datei per E-Mail
def send_email_with_attachment(receiver_email, subject, body, attachment_path):
    # Absender-E-Mail und Passwort
    sender_email = user_parameters["sender_email"]
    sender_password = user_parameters["sender_password"]

    # Erstellen der E-Mail-Nachricht
    msg = EmailMessage()
    msg['From'] = sender_email
    msg['To'] = receiver_email
    msg['Subject'] = subject
    msg.set_content(body)

    # Anhang hinzufügen (die WAV-Datei)
    with open(attachment_path, 'rb') as attachment:
        msg.add_attachment(attachment.read(), maintype='audio', subtype='wav', filename=attachment_path)

    try:
        # SMTP-Server einrichten und die E-Mail senden
        with smtplib.SMTP_SSL('smtp.gmail.com', 465) as smtp:
            smtp.login(sender_email, sender_password)  # Anmelden am SMTP-Server
            smtp.send_message(msg)  # E-Mail senden
    except socket.gaierror:
        print("Fehler: Der SMTP-Server konnte nicht erreicht werden. Bitte überprüfen Sie die Serveradresse.")
    except smtplib.SMTPAuthenticationError:
        print("Fehler: Authentifizierung fehlgeschlagen. Bitte überprüfen Sie Ihre E-Mail-Adresse und Ihr Passwort.")
    except Exception as e:
        print(f"Ein unerwarteter Fehler ist aufgetreten: {e}")

# Hauptprogramm
if __name__ == "__main__":
    # Nachricht erstellen
    email_message = "Das Pferd frisst keinen Gurkensalat."
    
    # Nachricht verschlüsseln
    encrypted_message = encrypt_message(email_message, user_parameters["encryption_key"])
    
    # Verschlüsselte Nachricht in Binärdaten umwandeln
    binary_data = text_to_binary(encrypted_message)
    print(f"Binärdaten der Nachricht: {binary_data[:64]}...")  # Zeigt die ersten 64 Bits der Nachricht (für Debugging)
    
    # Binärdaten in Audiosignale umwandeln
    audio_data = encode_to_audio(binary_data, user_parameters["bit_duration"], user_parameters["sample_rate"], user_parameters["freq_0"], user_parameters["freq_1"])
    
    # WAV-Datei speichern
    wav_filename = user_parameters["wav_filename"]
    # Speichert die Audiodaten als 16-Bit Integer in eine WAV-Datei
    write(wav_filename, user_parameters["sample_rate"], (audio_data * 32767).astype(np.int16))
    print(f"WAV-Datei '{wav_filename}' erfolgreich erstellt!")

    # WAV-Datei per E-Mail versenden
    send_email_with_attachment(user_parameters["receiver_email"], user_parameters["email_subject"], user_parameters["email_body"], wav_filename)
    print(f"WAV-Datei '{wav_filename}' erfolgreich per E-Mail versendet!")

So funktioniert das Script

Das Sender-Script verschlüsselt eine Nachricht, wandelt sie in Binärdaten um und erzeugt daraus eine Audiodatei, die dann per E-Mail verschickt wird:

  1. Variablen, die bearbeitet werden müssen: Zu Beginn und auch an einer Stelle weiter unten im Code gibt es Einstellungen, die du vorab unbedingt vornehmen musst. Dazu gehören der geheime Schlüssel (den der Empfänger in seinem Script auch hinterlegen muss), die E-Mail-Adressen, das Passwort aus Gmail und natürlich die zu verschlüsselnde Nachricht selbst.
"encryption_key": "geheimer_schluessel",  # Schlüssel (beide Parteien müssen denselben Schlüssel verwenden)
"sender_email": "Absender-Adresse",  # Absender-E-Mail-Adresse
"sender_password": "App-Passwort",        # App-Passwort für die Absender-E-Mail
"receiver_email": "Empfänger-Adresse",      # Empfänger-E-Mail-Adresse
"email_subject": "Betreff",   # Betreff der E-Mail
"email_body": "Inhalt der E-Mail, z.B. Hier kommt eine verschlüsselte Nachricht für dich.",  # E-Mail-Inhalt
"wav_filename": "message.wav"           # Name der zu speichernden WAV-Datei
 
email_message = "Das Pferd frisst keinen Gurkensalat."  # Die eigentliche Textnachricht, die verschlüsselt und versendet wird
  1. Nachricht mit dem Schlüssel verschlüsseln Die Funktion encrypt_message() verwendet eine einfache XOR-Verschlüsselung, um die Nachricht zu verschlüsseln. Dabei wird jedes Zeichen der Nachricht mit einem Zeichen des Schlüssels kombiniert:
   def encrypt_message(text, key):
       encrypted_message = ""
       for i in range(len(text)):
           encrypted_char = chr(ord(text[i]) ^ ord(key[i % len(key)]))
           encrypted_message += encrypted_char
       return encrypted_message

Diese Methode sorgt dafür, dass sowohl der Sender als auch der Empfänger denselben Schlüssel benötigen, um die Nachricht zu entschlüsseln.

  1. Nachricht in Binärdaten umwandeln Nachdem die Nachricht verschlüsselt wurde, wird sie in Binärdaten umgewandelt, um sie später als Audio darstellen zu können:
   def text_to_binary(text):
       binary_data = ''.join(format(ord(char), '08b') for char in text)
       return binary_data

Jedes Zeichen der Nachricht wird in seine 8-Bit-Binärdarstellung konvertiert, sodass die gesamte Nachricht als eine Folge von Nullen und Einsen dargestellt wird.

  1. Erstellen eines Tons für jedes Bit Die Funktion generate_tone() erstellt einen Sinuston für ein einzelnes Bit (entweder „0“ oder „1“). Diese Töne werden später aneinandergereiht, um die gesamte Nachricht in Audiodaten darzustellen:
   def generate_tone(frequency, duration, sample_rate):
       t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
       return np.sin(2 * np.pi * frequency * t)

Hierbei wird entweder eine Frequenz für „0“ oder eine andere für „1“ verwendet, um die Bits der Nachricht zu unterscheiden.

  1. Nachricht in Audiodaten kodieren Die Funktion encode_to_audio() wandelt die gesamte Binärnachricht in Audiodaten um, indem sie für jedes Bit den entsprechenden Ton erzeugt und diese aneinanderreiht:
   def encode_to_audio(binary_data, bit_duration, sample_rate, freq_0, freq_1):
       audio = np.array([])
       for bit in binary_data:
           if bit == '0':
               audio = np.append(audio, generate_tone(freq_0, bit_duration, sample_rate))
           else:
               audio = np.append(audio, generate_tone(freq_1, bit_duration, sample_rate))
       return audio

Das Ergebnis ist eine Audiodatei, die die verschlüsselte Nachricht repräsentiert.

  1. Erstellen und Versenden der Audiodatei Nachdem die Audiodaten erstellt wurden, wird die Nachricht als .wav-Datei gespeichert und per E-Mail versendet:
   write(wav_filename, sample_rate, (audio_data * 32767).astype(np.int16))

Diese Zeile speichert die Audiodaten als 16-Bit-Integer-Werte in einer WAV-Datei auf deinem Computer, die dann mit der Funktion send_email_with_attachment() per E-Mail versendet wird.

Ergänze das Script nun um deine Daten und hinterlege die Nachricht, die du versenden möchtest. Lass anschließend das Script einmal laufen – im Terminal solltest du die Nachricht lesen können, dass die E-Mail mit der Audio-Datei an deinen Empfänger gesendet wurde:

Und das war es für den Sender – nun zur anderen Seite, dem Empfänger deiner verschlüsselten Nachricht.

Das Script für den Empfänger

Der Empfänger der Nachricht benötigt also ein eigenes Script, das es ihm ermöglicht, die verschlüsselten Audiodaten wieder in Text umzuwandeln. Hier das vollständige Python-Script:

# Verschlüsselte Nachrichten - Script für den Sender
# Pollux Labs, polluxlabs.net

import numpy as np
from scipy.io.wavfile import read

# Parameter, die vom Benutzer bearbeitet werden müssen
user_parameters = {
    "sample_rate": 44100,  # Abtastrate (Hz)
    "bit_duration": 0.1,   # Dauer eines Bits (in Sekunden)
    "freq_0": 1000,        # Frequenz für "0" (Hz)
    "freq_1": 2000,        # Frequenz für "1" (Hz)
    "encryption_key": "geheimer_schluessel",  # Verschlüsselungsschlüssel (beide Parteien müssen denselben Schlüssel verwenden)
    "wav_filename": "message.wav"         # Name der zu lesenden WAV-Datei
}

# Funktion zum Dekodieren der Audiodaten in Binärdaten
def decode_audio_to_binary(audio_data, bit_duration, sample_rate, freq_0, freq_1):
    bit_length = int(sample_rate * bit_duration)
    binary_data = ""

    for i in range(0, len(audio_data), bit_length):
        segment = audio_data[i:i + bit_length]
        # Frequenz analysieren, um festzustellen, ob es sich um ein "0"- oder "1"-Bit handelt
        fft_result = np.fft.fft(segment)
        freqs = np.fft.fftfreq(len(segment), 1 / sample_rate)
        peak_freq = abs(freqs[np.argmax(np.abs(fft_result))])

        if abs(peak_freq - freq_0) < abs(peak_freq - freq_1):
            binary_data += "0"
        else:
            binary_data += "1"

    return binary_data

# Binärdaten in Text umwandeln
def binary_to_text(binary_data):
    text = ""
    for i in range(0, len(binary_data), 8):
        byte = binary_data[i:i + 8]
        if len(byte) == 8:
            text += chr(int(byte, 2))
    return text

# Nachricht entschlüsseln
def decrypt_message(encrypted_text, key):
    # Nachricht entschlüsseln, indem der Schlüssel mit den ursprünglichen Daten kombiniert wird (XOR)
    decrypted_message = ""
    for i in range(len(encrypted_text)):
        decrypted_char = chr(ord(encrypted_text[i]) ^ ord(key[i % len(key)]))
        decrypted_message += decrypted_char
    return decrypted_message

# Hauptprogramm
if __name__ == "__main__":
    # WAV-Datei lesen
    sample_rate, audio_data = read(user_parameters["wav_filename"])
    if audio_data.ndim > 1:
        audio_data = audio_data[:, 0]  # Falls Stereo, nur einen Kanal verwenden
    audio_data = audio_data / 32767.0  # Normalisieren auf den Bereich [-1, 1]

    # Audiodaten in Binärdaten dekodieren
    binary_data = decode_audio_to_binary(audio_data, user_parameters["bit_duration"], user_parameters["sample_rate"], user_parameters["freq_0"], user_parameters["freq_1"])
    print(f"Binärdaten der Nachricht: {binary_data[:64]}...")  # Zeigt die ersten 64 Bits der Nachricht (für Debugging)

    # Binärdaten in verschlüsselten Text umwandeln
    encrypted_message = binary_to_text(binary_data)

    # Nachricht entschlüsseln
    decrypted_message = decrypt_message(encrypted_message, user_parameters["encryption_key"])
    print(f"Entschlüsselte Nachricht: {decrypted_message}")

Auch hier gibt es Parameter, die eingestellt werden können – oder müssen:

user_parameters = {
    "sample_rate": 44100,  # Abtastrate (Hz)
    "bit_duration": 0.1,   # Dauer eines Bits (in Sekunden)
    "freq_0": 1000,        # Frequenz für "0" (Hz)
    "freq_1": 2000,        # Frequenz für "1" (Hz)
    "encryption_key": "geheimer_schluessel",  # Verschlüsselungsschlüssel (beide Parteien müssen denselben Schlüssel verwenden)
    "wav_filename": "message.wav"         # Name der zu lesenden WAV-Datei
}

Allen voran natürlich der geheime Schlüssel: Dieser muss unbedingt mit jenem übereinstimmen, den der Sender in seinem Script zum Verschlüsseln verwendet hat.

Aber auch die Parameter sample_rate, bit_duration und die Frequenzen müssen mit den Einstellungen des Senders übereinstimmen. Der Dateiname der wav_filename muss dem Namen der per E-Mail empfangenen Audio-Datei entsprechen – also vor im Script angepasst werden, bevor es ausgeführt wird.

So funktioniert das Script

Das Empfänger-Script liest also die Audiodatei, dekodiert die darin enthaltene Nachricht und entschlüsselt sie. Hier die einzelnen Schritte:

  1. WAV-Datei lesen Das Script beginnt mit dem Einlesen der Audiodatei mithilfe der Funktion scipy.io.wavfile.read(). Dabei wird die Abtastrate und die Audiodaten extrahiert:
   sample_rate, audio_data = read(user_parameters["wav_filename"])
   if audio_data.ndim > 1:
       audio_data = audio_data[:, 0]  # Falls Stereo, nur einen Kanal verwenden
   audio_data = audio_data / 32767.0  # Normalisieren auf den Bereich [-1, 1]

Diese Normalisierung ist notwendig, um die Audiodaten auf einen Bereich zwischen -1 und 1 zu skalieren.

  1. Audiodaten in Binärdaten dekodieren Die Funktion decode_audio_to_binary() analysiert die Audiodaten und konvertiert sie zurück in eine Binärfolge. Dabei wird die Fourier-Transformation verwendet, um die Frequenzen der einzelnen Segmente zu analysieren und zu entscheiden, ob es sich um ein Bit „0“ oder „1“ handelt:
   def decode_audio_to_binary(audio_data, bit_duration, sample_rate, freq_0, freq_1):
       bit_length = int(sample_rate * bit_duration)
       binary_data = ""

       for i in range(0, len(audio_data), bit_length):
           segment = audio_data[i:i + bit_length]
           fft_result = np.fft.fft(segment)
           freqs = np.fft.fftfreq(len(segment), 1 / sample_rate)
           peak_freq = abs(freqs[np.argmax(np.abs(fft_result))])

           if abs(peak_freq - freq_0) < abs(peak_freq - freq_1):
               binary_data += "0"
           else:
               binary_data += "1"

       return binary_data

Diese Funktion durchläuft die Audiodaten in Segmenten und bestimmt für jedes Segment, ob es sich um eine „0“ oder „1“ handelt.

  1. Binärdaten in Text umwandeln Nachdem die Audiodaten in Binärdaten umgewandelt wurden, werden diese in den ursprünglichen Text konvertiert. Hierbei wird jeder 8-Bit-Block in ein Zeichen umgewandelt:
   def binary_to_text(binary_data):
       text = ""
       for i in range(0, len(binary_data), 8):
           byte = binary_data[i:i + 8]
           if len(byte) == 8:
               text += chr(int(byte, 2))
       return text

So wird der verschlüsselte Text aus den Binärdaten wiederhergestellt.

  1. Nachricht entschlüsseln Die entschlüsselte Nachricht wird mit der Funktion decrypt_message() wieder in den Klartext umgewandelt. Dazu wird derselbe Schlüssel verwendet, der auch beim Verschlüsseln benutzt wurde:
   def decrypt_message(encrypted_text, key):
       decrypted_message = ""
       for i in range(len(encrypted_text)):
           decrypted_char = chr(ord(encrypted_text[i]) ^ ord(key[i % len(key)]))
           decrypted_message += decrypted_char
       return decrypted_message

Diese Methode führt eine XOR-Operation auf jedes Zeichen des verschlüsselten Textes durch, um die ursprüngliche Nachricht wiederherzustellen.

  1. Ergebnis anzeigen Schließlich wird die entschlüsselte Nachricht auf der Konsole ausgegeben:
   print(f"Entschlüsselte Nachricht: {decrypted_message}")

Damit erhält der Empfänger die ursprünglich gesendete Nachricht im Klartext in seinem Terminal:

Falls der Empfänger jedoch einen falschen Schlüssel in seinem Script verwendet, klappt es mit dem Entschlüsseln nicht. So sieht die gleiche Nachricht aus, wenn am Ende des Schlüssels zwei Zeichen fehlen:

Die Nachricht beginnt zwar korrekt, weil der Anfang des Schlüssels mit jenem des Senders übereinstimmt. Sie „zerfällt“ dann aber zu Kauderwelsch, da der Schlüssel des Empfängers, wie gesagt, zu kurz ist und also zu früh wieder die ersten Zeichen des Schlüssels verwendet werden. Wenn der Schlüssel überhaupt nicht jenem des Senders entspricht, bleibt die Textnachricht vollständig unlesbar.

Wie geht es weiter?

Du kannst nun also Nachrichten verschlüsselt als Audio versenden und dir einigermaßen sicher sein, dass sie nur jemand entschlüsseln kann, der ein geeignetes Script und vor allem den richtigen Schlüssel dafür besitzt. Wie könnten Verbesserungen aussehen? Du hast vielleicht schon bemerkt, dass die erzeugten WAV-Dateien recht groß sind – die relative kurze Nachricht aus dem Beispiel oben hat bereits 2,6 MB.

Hier könnte eine Konvertierung in das MP3-Format weiterhelfen, um sicherzugehen, dass deine Nachricht nicht zu groß für den Anhang einer E-Mail ist.

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

]]>
UX-Design für Maker: So baust du benutzerfreundliche Projekte https://polluxlabs.net/arduino-tutorials/ux-design-fur-maker/ Tue, 05 Nov 2024 19:15:12 +0000 https://polluxlabs.net/?p=17071 UX-Design für Maker: So baust du benutzerfreundliche Projekte Weiterlesen »

]]>
Als Maker sind wir oft so fasziniert von unseren Projekten, dass wir einen entscheidenden Aspekt übersehen: die Benutzerfreundlichkeit. Wir löten begeistert Bauteile zusammen, programmieren Mikrocontroller und konstruieren komplexe Mechanismen. Doch wenn unser Projekt fertig ist, stellen wir fest, dass nur wir selbst es richtig bedienen können. Freunde und Familie, denen wir unsere neueste Kreation vorführen wollen, stehen ratlos davor.

Hier kommt UX-Design ins Spiel. UX steht für „User Experience“, also Nutzererfahrung. Es geht darum, wie Menschen mit unserem Produkt interagieren und welche Erfahrungen sie dabei machen. Gutes UX-Design sorgt dafür, dass die Bedienung intuitiv, effizient und angenehm ist.

Don Norman, einer der Pioniere des UX-Designs, hat in seinem Buch „The Design of Everyday Things“ grundlegende Prinzipien formuliert, die auch für uns Maker Gold wert sind. In diesem Artikel werden wir diese Prinzipien auf die Welt der DIY-Elektronik und Maker-Projekte übertragen.

Warum ist UX-Design für Maker wichtig?

Zum einen macht es unsere Projekte zugänglicher für andere. Ein gut designtes Gerät kann von jedem genutzt werden, nicht nur vom Erfinder. Zum anderen verbessert es auch unsere eigene Nutzererfahrung. Wer hat sich nicht schon einmal über seine eigenen, unübersichtlich angeordneten Buttons und Regler geärgert?

Gutes UX-Design muss dabei nicht bedeuten, dass wir Kompromisse bei der Funktionalität eingehen. Im Gegenteil: Oft führt die Auseinandersetzung mit UX-Fragen zu cleveren Lösungen, die unser Projekt noch verbessern.

Grundprinzipien des UX-Designs nach Don Norman

In seinem wegweisenden Werk The Design of Everyday Things* hat Don Norman einige fundamentale Prinzipien des UX-Designs formuliert, die für uns Maker von großem Wert sind. In diesem Beitrag soll es um drei der wichtigsten Konzepte gehen:

  1. Gulf of Execution und Gulf of Evaluation:Der Gulf of Execution beschreibt die Kluft zwischen den Zielen des Nutzers und den Möglichkeiten des Systems. Der Gulf of Evaluation ist die Kluft zwischen dem, was das System tut, und dem Verständnis des Nutzers davon.
  2. Affordances und Signifiers: Affordances sind die möglichen Interaktionen zwischen einem Objekt und dem Nutzer. Ein Drehregler „bietet“ das Drehen an. Signifiers sind die sichtbaren Hinweise auf diese Möglichkeiten.
  3. Feedback:Jede Aktion des Nutzers sollte eine wahrnehmbare Reaktion hervorrufen.

Diese Prinzipien bilden das Fundament für benutzerfreundliches Design. Als Maker können wir sie nutzen, um Projekte zu bauen, die nicht nur funktionieren, sondern auch Freude bei der Benutzung bereiten. In den folgenden Abschnitten werden wir uns ansehen, wie wir diese Konzepte konkret in unseren DIY-Projekten umsetzen können.

Gulf of Execution und Evaluation

Zwei zentrale Konzepte in Don Normans Theorie, die für uns Maker besonders relevant sind, sind der „Gulf of Execution“ und der „Gulf of Evaluation“. Diese beschreiben die Herausforderungen, mit denen Nutzer bei der Interaktion mit unseren Projekten konfrontiert werden.

Gulf of Execution und Gulf of Evaluation im UX-Design

Der „Gulf of Execution“ ist die Kluft zwischen dem, was der Nutzer tun möchte, und den Möglichkeiten, die unser Gerät bietet. Stelle dir einen selbstgebauten Synthesizer vor: Der Nutzer möchte einen bestimmten Sound erzeugen, aber wie? Sind die Regler logisch angeordnet? Sind die Funktionen klar beschriftet? Je größer diese Kluft, desto frustrierender die Erfahrung.

Der „Gulf of Evaluation“ hingegen beschreibt die Schwierigkeit, den aktuellen Zustand des Systems zu verstehen. Hat unser Arduino die Eingabe verarbeitet? Wurde die Einstellung gespeichert? Läuft das Programm überhaupt? Ohne klares Feedback tappt der Nutzer im Dunkeln.

Als Maker ist es unsere Aufgabe, Brücken über diese Klüfte zu bauen:

  1. Überbrücken des Gulf of Execution:
    • Verwenden eindeutige Beschriftungen und Symbole
    • Gruppiere zusammengehörige Funktionen
    • Benutze vertraute Metaphern, zum Biespiel ein Zahnrad-Symbol für Einstellungen
  2. Überbrücken des Gulf of Evaluation:
    • Integriere Status-LEDs für wichtige Funktionen
    • Verwende Displays zur Anzeige komplexer Informationen
    • Gibt akustisches Feedback bei wichtigen Ereignissen

Ein Beispiel: Bei einem Smart-Home-Projekt könnte ein Touch-Display die aktuelle Raumtemperatur anzeigen (Evaluation) und gleichzeitig als Schieberegler zur Temperatureinstellung dienen (Execution). Eine kurze Vibration bestätigt die Eingabe, während eine animierte Kurve den Temperaturverlauf visualisiert.

Indem wir diese Konzepte berücksichtigen, machen wir unsere Projekte nicht nur funktional, sondern auch intuitiv bedienbar. Das Ziel ist es, dass der Nutzer mühelos von der Idee zur Ausführung und dann zum Verständnis des Ergebnisses gelangt.

Affordances und Signifiers

Affordances und Signifiers sind zentrale Konzepte, die uns helfen, die Interaktionen zwischen Nutzer und Objekt zu verbessern. Affordances beschreiben, welche Aktionen ein Objekt anbietet. Ein einfacher Drehregler an einem Verstärker bietet beispielsweise die Affordance, gedreht zu werden, um die Lautstärke zu verändern. Das Design eines Objekts sollte klar machen, welche Interaktionen möglich sind.

Signifiers sind die visuellen Hinweise, die dem Nutzer zeigen, wie er interagieren soll. Sie können physisch (z.B. eine Markierung auf einem Schalter) oder digital (z.B. ein Symbol auf einem Touchscreen) sein. Signifiers können oft auch dazu beitragen, Missverständnisse zu vermeiden. Wenn ein Knopf gedrückt werden soll, könnte er leicht hervorgehoben und beschriftet sein, um die Funktion deutlich zu machen.

Als Maker können wir Affordances und Signifiers gezielt einsetzen, um unsere Projekte benutzerfreundlicher zu gestalten. Zum Beispiel könnten wir:

  • Einen Drehregler mit einer sichtbaren Markierung versehen, um zu verdeutlichen, in welche Richtung gedreht werden kann.
  • Bei einem selbstgebauten Bedienfeld alle Tasten so gestalten, dass deren Funktionen klar erkennbar sind (z.B. durch eindeutige Symbole).
  • LED-Streifen als Signifiers nutzen, um den Nutzer zu bestimmten Bereichen des Geräts zu leiten, besonders bei komplexeren Projekten.

Ein weiteres Beispiel sind Touch-Interfaces: Wenn wir ein Touchscreen-Display nutzen, sollten wir darauf achten, dass die zu drückenden Bereiche klar markiert und ausreichend groß sind, um eine präzise Eingabe zu ermöglichen. Das Verwenden von Signifiers sorgt dafür, dass der Nutzer die beabsichtigte Interaktion schnell erkennt und umsetzen kann.

Indem wir Affordances und Signifiers bewusst einsetzen, machen wir die Benutzung unserer Projekte intuitiver und senken die Einstiegshürde für neue Nutzer.

Feedback

Feedback ist ein weiterer essenzieller Aspekt des UX-Designs. Jede Aktion, die ein Nutzer durchführt, sollte eine wahrnehmbare Reaktion hervorrufen. Ohne Feedback ist es für den Nutzer schwer zu erkennen, ob seine Interaktion erfolgreich war oder ob ein Fehler aufgetreten ist – siehe den erwähnten Gulf of Evaluation.

Als Maker können wir viele verschiedene Formen von Feedback einsetzen:

  • Visuelles Feedback: LED-Anzeigen, die aufleuchten, wenn eine Funktion aktiviert wird, oder ein Display, das den aktuellen Status zeigt. Zum Beispiel kann eine grüne LED anzeigen, dass ein System betriebsbereit ist, während eine rote LED einen Fehler signalisiert.
  • Akustisches Feedback: Ein Piepton kann darauf hinweisen, dass eine Eingabe erfolgt ist oder ein Fehler aufgetreten ist. Ein sanfter Signalton kann bestätigen, dass eine Einstellung erfolgreich vorgenommen wurde.
  • Haptisches Feedback: Vibrationen bei der Berührung eines Touchscreens können dem Nutzer zusätzliche Bestätigung bieten, dass eine Eingabe registriert wurde.

Ein Beispiel für gutes Feedback bei einem selbstgebauten Code-Schloss: Wenn der Nutzer den richtigen Code eingibt, leuchtet eine grüne LED auf. Bei einem falschen Code hingegen leuchtet eine rote LED.

Code-Schloss Arduino

Feedback hilft nicht nur dem Nutzer, die Funktionalität zu verstehen, sondern trägt auch dazu bei, Vertrauen in das System aufzubauen. Es reduziert Unsicherheit und sorgt dafür, dass der Nutzer immer weiß, was gerade passiert. Besonders bei komplexeren Projekten ist es wichtig, dass der Nutzer sofort Rückmeldung erhält, ob seine Eingaben erfolgreich waren oder nicht.

Indem wir in unseren Projekten stets Feedback einplanen, sorgen wir dafür, dass Nutzer mit unseren Kreationen besser interagieren können und die Bedienung angenehmer und effizienter wird.

Fazit

UX-Design ist ein unverzichtbarer Bestandteil des Entwicklungsprozesses, der unsere Projekte von rein technischen Spielereien zu benutzerfreundlichen und inspirierenden Kreationen macht. Indem wir die Prinzipien von Don Norman – wie den Gulf of Execution und Evaluation, Affordances und Signifiers sowie Feedback – in unsere DIY-Projekte integrieren, können wir die Benutzerfreundlichkeit erheblich steigern.

Unsere Projekte sollen nicht nur funktionieren, sondern auch von anderen Menschen genutzt und geschätzt werden können. Gutes UX-Design ermöglicht es uns, Barrieren abzubauen, die Bedienung zu vereinfachen und das Nutzererlebnis zu verbessern. Letztendlich schaffen wir damit Geräte, die nicht nur uns selbst stolz machen, sondern auch andere begeistern und inspirieren.

Also, denke bei deinen nächsten Projekten nicht nur an die Technik, sondern auch an die Menschen, die sie nutzen werden. Denn die beste Technologie ist die, die Freude bringt und von allen verstanden wird.

]]>