Eurorack Multiple STL – passives Signal-Multitool einfach selbst bauen

Eingebautes passives 4×4 Eurorack Multiple mit türkisfarbener Frontplatte neben einem Behringer Envelope Generator Modul im Eurorack-Gehäuse.

Ob Audio, CV oder Gate – ein Eurorack Multiple gehört zur absoluten Grundausstattung in jedem modularen Synthesizer-System. In diesem Beitrag zeige ich dir, wie du dir ganz einfach ein passives Multiple selbst bauen kannst, inklusive kostenloser STL-Dateien zum Selberdrucken.

Was ist ein Eurorack Multiple?

Ein Multiple (auch „Mult“ genannt) verteilt ein eingehendes Signal auf mehrere Ausgänge. Gerade bei Modulationen wie LFOs oder Hüllkurven ist das extrem nützlich, wenn man einen Signalverlauf an mehreren Stellen im Patch verwenden möchte.

Passive Multiples benötigen keine Stromversorgung und lassen sich super simpel aufbauen – perfekt für DIY-Fans und Sparfüchse.

CAD-Ansicht eines 3D-modellierten 2×4 Eurorack Multiples mit zwei Gruppen à vier Buchsen in FreeCAD.

Zwei Designs, kostenlos als STL

Ich habe zwei Varianten gestaltet, die du kostenlos auf Cults3D herunterladen kannst:

  • 2×4 Multiple – zwei unabhängige Gruppen mit je vier Buchsen und 4×4 Multiple – vier Gruppen mit je vier Buchsen für maximale Flexibilität
    👉 STL auf Cults3D herunterladen

Die Frontplatten sind so designt, dass handelsübliche 3,5-mm-Klinkenbuchsen einfach eingesteckt und mit Muttern verschraubt werden können.


Günstige Buchsen clever nutzen

Ich verwende in meinen Builds besonders günstige Klinkenbuchsen, die eigentlich für Video-Signale gedacht sind. Diese vierpoligen Buchsen funktionieren problemlos für Audio- und CV-Signale, solange du nur die äußeren Pins (Tip, Ground).

👉 Diese günstigen 3,5-mm-Buchsen bekommst du z. B. bei Amazon: RUNCCI-YUN 3,5-mm-Mini-Klinkenbuchse* (bezahlter Link)

Wichtig: Achte darauf, beim Einbau nur die drei relevanten Kontakte zu verlöten. Der vierte kann ignoriert werden oder wird sauber isoliert, damit es keine Störungen gibt.

CAD-Ansicht eines 3D-modellierten 4×4 Eurorack Multiples mit vier Gruppen à vier Buchsen in FreeCAD.

Aufbau: So einfach geht’s

  1. STL herunterladen und drucken – beide Varianten sind auf 3D-Druck mit Standard-Filament optimiert (z. B. PLA)
  2. Buchsen einbauen – in die vorgesehenen Bohrungen stecken und mit Muttern fixieren
  3. Verdrahten – bei passiven Multiples wird einfach jede Buchse einer Gruppe miteinander verbunden (Tip and Tip, GND and GND)
  4. Einbauen und patchen! – fertig ist dein eigenes Eurorack Multiple
Verkabelung eines selbstgebauten 4×4 Eurorack Multiples mit 3,5 mm Klinkenbuchsen auf einer weißen 3D-gedruckten Frontplatte.

Noch mehr DIY fürs Rack

Wenn du dein Eurorack komplett selbst drucken möchtest, schau dir auch meinen Beitrag über das selbst designte, modular erweiterbare Case an:

👉 Mein Eurorack zum Selberdrucken: Modulares 42TE-Case als kostenlose STL-Datei


Fazit

Ein passives Eurorack Multiple ist schnell gebaut, kostet kaum etwas und spart dir im Patch-Alltag viele Kabel und Nerven. Mit den kostenlosen STL-Dateien und den clever genutzten günstigen Buchsen kannst du direkt loslegen.

Eurorack Multiple STL – günstiger, einfacher und nerdiger geht’s kaum.

Mein Eurorack zum Selberdrucken: Modulares 42TE-Case als kostenlose STL-Datei

Eurorack-Synthesizer mit mehreren Modulen und einem selbst gedruckten, modularen 3D-Case in Betrieb

Wenn das Eurorack voll ist, wird’s eng. Und teuer. Also warum nicht selbst Hand anlegen? Genau das habe ich gemacht – und herausgekommen ist ein modulares 3D-gedrucktes Case mit 42TE pro Segment. Die passenden Eurorack STL-Dateien bekommst du kostenlos. Aber der Reihe nach:


Warum überhaupt modular?

Ganz einfach: Mein Drucker kann maximal 24 cm Breite. Klassische Eurorack-Cases sind meist deutlich breiter, z. B. 84 oder 104 TE. Also musste ein Plan her, der sich beliebig erweitern lässt. Die Lösung: Ein Basis-Segment mit 42 TE und andockbaren Seitenwangen, erweiterbar, nerdig.


Aufbau des Case-Systems

Das Case besteht aus drei Grundkomponenten:

  1. Modularer Grundkörper mit 42TE Innenbreite (ca. 213,4 mm nutzbar)
  2. Linke und rechte Seitenwange – enthalten vorbereitete Löcher zur Montage in einem vertikalen Rack
  3. Standardisierte Schraubverbindungen – kompatibel über beliebig viele Segmente hinweg

Die Module lassen sich durch M4 Schrauben mit Muttern oder Schmelzeinsätze verbinden.

Teilweise montiertes 3D-gedrucktes Eurorack-Case mit 42TE, sichtbare M3-Schmelzeinsätze und abgenommene Seitenwange

Montage mit Schmelzeinsätzen

Ich habe mich bewusst gegen eingedruckte Gewinde entschieden – zu ungenau. Stattdessen nutze ich sogenannte Schmelzeinsätze, die nach dem Druck mit einem Lötkolben sauber eingesetzt werden können. Damit wird das Case stabil, schraubbar und mehrfach montierbar.

🔗 Diese M3-Schmelzeinsätze nutze ich selbst* (bezahlter Link)

🔗 Und das sind die passenden M3-Schrauben* (bezahlter Link)


Erweiterbarkeit in alle Richtungen

Die 42TE-Einheiten lassen sich beliebig oft nebeneinander kombinieren. Du willst 84 TE? Zwei Module. 126 TE? Drei. Und so weiter.

Zudem kannst du die vorbereiteten Löcher in den Wangen nutzen, um das Case später in einem selbstgebauten Vertical Stand zu befestigen. Hierfür gibt es bereits fertige 3D Druck Dateien, z.B. der 3-Tier Behringer-style Synth Stand.


STL-Dateien: Kostenlos auf Cults3D

Ich stelle dir die Druckdateien komplett kostenlos zur Verfügung:

👉 Hier geht’s zu den Eurorack STL-Dateien auf Cults3D

Druckvorbereitung in Bambu Studio für ein modulares Eurorack-Case mit 42TE Grundkörper und zwei Seitenwangen

Fazit: Wenig Druckfläche? Kein Problem.

Ein modulares Eurorack-Gehäuse muss weder teuer noch aus Metall sein. Mit einem einfachen 3D-Drucker und ein paar Schmelzeinsätzen baust du dir dein Setup nach Maß. Und das Beste: Du kannst jederzeit erweitern, ohne bei Null anzufangen.


Noch Fragen?
Dann rein damit in die Kommentare

WaterMeV2 – Feuchtigkeitssensor mit ESP8266 und OLED-Display

Einleitung

Der WaterMeV2 – Feuchtigkeitssensor mit ESP8266 und OLED-Display kann die Bodenfeuchtigkeit in Echtzeit überwachen und die Daten direkt per MQTT an dein Smart Home System senden. Dieses Upgrade unseres vorherigen WaterMe Sensors bietet Verbesserungen in der Hardware und Software, eine optimierte Reset-Funktion sowie ein ansprechendes Gehäuse, das du kostenlos auf meinem Cults3D-Account herunterladen kannst (hier klicken).

WaterMeV2 Gehäuse

Vorteile von WaterMev2

  • Echtzeitüberwachung der Bodenfeuchtigkeit
  • Anbindung an MQTT für Smart Home Integration
  • WiFi-Manager für einfache WLAN-Konfiguration
  • OLED-Display für lokale Anzeige
  • 5-Sekunden-Reset-Taster für eine einfache Neukonfiguration
  • Kostenloses 3D-gedrucktes Gehäuse zum Schutz der Hardware

Benötigte Komponenten

Für dieses Projekt benötigst du folgende Komponenten (mit Affiliate-Links):

Aufbau der Hardware

Der Aufbau ist einfach und erfordert nur wenige Kabelverbindungen:

  1. ESP8266 mit dem OLED-Display verbinden:
    • SDA → D2 (GPIO4)
    • SCL → D1 (GPIO5)
    • VCC → 3.3V
    • GND → GND
  2. Feuchtigkeitssensor anschließen:
    • Signal → A0 (Analog-Pin)
    • VCC → 3.3V
    • GND → GND
  3. Taster für den Reset:
    • Ein Pin an GPIO0 (D3)
    • Der andere Pin an GND

Software – Der Code von WaterMev2

Der folgende Code enthält alle wichtigen Funktionen für den Sensor:

1. Bibliotheken und Variablen

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <WiFiManager.h>
#include <ESP8266WebServer.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <SSD1306Wire.h>
#include <LittleFS.h>
#include <ArduinoJson.h>

#define SENSOR_PIN A0
#define RESET_PIN 0
#define RESET_TIME 5000

Hier laden wir alle wichtigen Bibliotheken und definieren die Pins für den Sensor und den Reset-Taster.

2. Reset-Funktion mit 5-Sekunden-Haltezeit

void checkResetButton() {
    if (digitalRead(RESET_PIN) == LOW) {
        if (!buttonPressed) {
            buttonPressStart = millis();
            buttonPressed = true;
        }
        if (millis() - buttonPressStart >= RESET_TIME) {
            Serial.println("Reset-Taster 5 Sekunden gehalten! Setze WiFiManager-Einstellungen zurück...");
            WiFiManager wifiManager;
            wifiManager.resetSettings();
            delay(1000);
            WiFi.disconnect(true);
            delay(1000);
            ESP.restart();
        }
    } else {
        buttonPressed = false;
    }
}

Diese Funktion sorgt dafür, dass der WiFiManager nur zurückgesetzt wird, wenn der Taster mindestens 5 Sekunden gedrückt wird. Dadurch wird ein versehentliches Zurücksetzen verhindert.

3. Verbindung zum WLAN mit WiFiManager

void setup_wifi() {
    WiFiManager wifiManager;
    wifiManager.setTimeout(180);
    if (!wifiManager.autoConnect("WaterMev2")) {
        ESP.restart();
    }
}

Der WiFiManager ermöglicht eine einfache Einrichtung des WLANs über einen Access Point.

4. Sensor-Daten auslesen und per MQTT senden

void updateSensorData() {
    int sensorValue = analogRead(SENSOR_PIN);
    float moisture = map(sensorValue, 1024, 0, 0, 100);
    client.publish("WaterMev2/feuchtigkeit", String(moisture).c_str());
}

Hier wird die Feuchtigkeit gemessen und an den MQTT-Server gesendet.

5. Loop-Funktion mit Reset-Check

void loop() {
    server.handleClient();
    checkResetButton();
    updateSensorData();
    delay(30000);
}

Die loop()-Funktion überprüft den Taster und aktualisiert die Sensordaten alle 30 Sekunden.

6. Vollständiger Code

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <WiFiManager.h>  // WiFiManager Library für AP-Konfiguration
#include <ESP8266WebServer.h>
#include <time.h>        // Für configTime()
#include <SSD1306Wire.h>        // legacy: #include "SSD1306.h"
#include <LittleFS.h>
#include <ArduinoJson.h>
 
#define SENSOR_PIN A0  // Sensor an A0 angeschlossen
#define RESET_PIN 0  // GPIO0 (D3 beim ESP8266)
#define RESET_TIME 5000  // Zeit in Millisekunden (5 Sekunden)

unsigned long buttonPressStart = 0;
bool buttonPressed = false;

unsigned long previousMillis = 0;  // Speichert den Zeitpunkt des letzten MQTT-Sendevorgangs
const long interval = 30000;       // Zeitintervall für das Senden der MQTT-Daten
 
bool resetActive = false; // verhindert, dass andere Funktionen das Display während Reset stören

char mqtt_server[40] = "";
char mqtt_port[6] = "";
char mqtt_user[40] = "";
char mqtt_pass[40] = "";
char mqtt_topic_prefix[40] = "WaterMe"; // Benutzerdefiniertes Präfix
char ntp_server[40] = "pool.ntp.org";  // Standard NTP Server
 
WiFiClient espClient;
PubSubClient client(espClient);
ESP8266WebServer server(80);
SSD1306Wire display(0x3c, SDA, SCL);
 
void saveConfig() {
    Serial.println("Speichere Konfiguration in LittleFS...");
    File configFile = LittleFS.open("/config.json", "w");
    if (!configFile) {
        Serial.println("Fehler beim Speichern der Konfiguration!");
        return;
    }

    DynamicJsonDocument doc(128);
    doc["mqtt_server"] = mqtt_server;
    doc["mqtt_port"] = mqtt_port;
    doc["mqtt_user"] = mqtt_user;
    doc["mqtt_pass"] = mqtt_pass;
    doc["mqtt_topic_prefix"] = mqtt_topic_prefix;
    doc["ntp_server"] = ntp_server;

    serializeJson(doc, configFile);
    configFile.close();
    Serial.println("Konfiguration gespeichert.");
}

void loadConfig() {
    Serial.println("Lade Konfiguration aus LittleFS...");

    File configFile = LittleFS.open("/config.json", "r");
    if (!configFile) {
        Serial.println("Keine gespeicherte Konfiguration gefunden.");
        return;
    }

    DynamicJsonDocument doc(128);
    DeserializationError error = deserializeJson(doc, configFile);
    if (error) {
        Serial.println("Fehler beim Lesen der Konfigurationsdatei!");
        return;
    }

    strlcpy(mqtt_server, doc["mqtt_server"] | "", sizeof(mqtt_server));
    strlcpy(mqtt_port, doc["mqtt_port"] | "", sizeof(mqtt_port));
    strlcpy(mqtt_user, doc["mqtt_user"] | "", sizeof(mqtt_user));
    strlcpy(mqtt_pass, doc["mqtt_pass"] | "", sizeof(mqtt_pass));
    strlcpy(mqtt_topic_prefix, doc["mqtt_topic_prefix"] | "", sizeof(mqtt_topic_prefix));
    strlcpy(ntp_server, doc["ntp_server"] | "", sizeof(ntp_server));

    Serial.println("Konfiguration geladen.");
}
 
void setup_wifi() {
    Serial.println("Starte WiFiManager...");

    uint8_t mac[6];
    WiFi.macAddress(mac);

    char hostname[32];
    snprintf(hostname, sizeof(hostname), "WaterMe_%02X%02X%02X", mac[3], mac[4], mac[5]);

    WiFiManager wifiManager;
    wifiManager.setTimeout(180); // 3 Minuten warten statt unendlich

    //wifiManager.resetSettings();

    WiFiManagerParameter custom_mqtt_server("server", "MQTT Server", mqtt_server, 40);
    WiFiManagerParameter custom_mqtt_port("port", "MQTT Port", mqtt_port, 6);
    WiFiManagerParameter custom_mqtt_topic("topic", "MQTT Topic Prefix", mqtt_topic_prefix, 40);
    WiFiManagerParameter custom_ntp_server("ntp_server", "NTP Server", ntp_server, 40);
 
    wifiManager.addParameter(&custom_mqtt_server);
    wifiManager.addParameter(&custom_mqtt_port);
    wifiManager.addParameter(&custom_mqtt_topic);
    wifiManager.addParameter(&custom_ntp_server);

    if (!wifiManager.autoConnect(hostname)) {
        Serial.println("Fehlgeschlagen! Neustart in 5 Sekunden...");
        delay(5000);
        ESP.restart();
    }

    WiFi.hostname(hostname);

    strncpy(mqtt_server, custom_mqtt_server.getValue(), sizeof(mqtt_server) - 1);
    strncpy(mqtt_port, custom_mqtt_port.getValue(), sizeof(mqtt_port) - 1);
    strncpy(mqtt_topic_prefix, custom_mqtt_topic.getValue(), sizeof(mqtt_topic_prefix) - 1);
    strncpy(ntp_server, custom_ntp_server.getValue(), sizeof(ntp_server) - 1);
    saveConfig();
    Serial.println("WLAN verbunden!");
    Serial.print("IP-Adresse: ");
    Serial.println(WiFi.localIP());
}
 
void handleRoot() {
    Serial.println("Web-Oberfläche aufgerufen");
    String htmlContent = "<!DOCTYPE html><html><head><meta charset='UTF-8'><title>WaterMe Einstellungen</title>"
                         "<style>"
                         "body { font-family: Arial, sans-serif; margin: 40px; background-color: #333; color: #fff; }"
                         "h1 { color: #fff; }"
                         "form { background-color: #222; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px 0 rgba(0,0,0,0.5); }"
                         "label { display: block; margin-top: 20px; margin-bottom: 5px; color: #ccc; }"
                         "input[type='text'], input[type='submit'] { width: 100%; padding: 8px; margin-top: 5px; border: 1px solid #555; border-radius: 4px; }"
                         "input[type='text'] { background-color: #555; color: #ddd; }"
                         "input[type='submit'] { background-color: #008CBA; color: white; cursor: pointer; }"
                         "input[type='submit']:hover { background-color: #005f7a; }"
                         "</style>"
                         "</head><body>"
                         "<h1>WaterMe Einstellungen</h1>"
                         "<form action='/save' method='POST'>"
                         "<label for='server'>MQTT Server:</label> <input type='text' id='server' name='server' value='" + String(mqtt_server) + "'><br>"
                         "<label for='port'>MQTT Port:</label> <input type='text' id='port' name='port' value='" + String(mqtt_port) + "'><br>"
                         "<label for='user'>MQTT Benutzer:</label> <input type='text' id='user' name='user' value='" + String(mqtt_user) + "'><br>"
                         "<label for='pass'>MQTT Passwort:</label> <input type='password' id='pass' name='pass' value='" + String(mqtt_pass) + "'><br>"
                         "<label for='topic'>MQTT Topic Prefix:</label> <input type='text' id='topic' name='topic' value='" + String(mqtt_topic_prefix) + "'><br>"
                         "<label for='ntp_server'>NTP Server:</label> <input type='text' id='ntp_server' name='ntp_server' value='" + String(ntp_server) + "'><br>"
                         "<input type='submit' value='Speichern'>"
                         "</form></body></html>";
    server.send(200, "text/html", htmlContent);
}
 
void handleSave() {
    Serial.println("Speichere neue Einstellungen...");
    if (server.hasArg("server")) strncpy(mqtt_server, server.arg("server").c_str(), sizeof(mqtt_server) - 1);
    if (server.hasArg("port")) strncpy(mqtt_port, server.arg("port").c_str(), sizeof(mqtt_port) - 1);
    if (server.hasArg("user")) strlcpy(mqtt_user, server.arg("user").c_str(), sizeof(mqtt_user));
    if (server.hasArg("pass")) strlcpy(mqtt_pass, server.arg("pass").c_str(), sizeof(mqtt_pass));
    if (server.hasArg("topic")) strncpy(mqtt_topic_prefix, server.arg("topic").c_str(), sizeof(mqtt_topic_prefix) - 1);
    if (server.hasArg("ntp_server")) strncpy(ntp_server, server.arg("ntp_server").c_str(), sizeof(ntp_server) - 1);
    saveConfig();
    server.send(200, "text/html", "<html><body><h1>Gespeichert! Neustart...</h1></body></html>");
    delay(3000);
    ESP.restart();
}
 
void checkResetButton() {
    static int lastSecondsLeft = -1;

    if (digitalRead(RESET_PIN) == LOW) {
        if (!buttonPressed) {
            buttonPressStart = millis();
            buttonPressed = true;
            resetActive = true;
            lastSecondsLeft = -1;
        }

        unsigned long heldTime = millis() - buttonPressStart;
        int secondsLeft = (RESET_TIME - heldTime) / 1000;

        if (secondsLeft != lastSecondsLeft && heldTime < RESET_TIME) {
            lastSecondsLeft = secondsLeft;

            display.clear();
            display.setFont(ArialMT_Plain_16);
            display.setTextAlignment(TEXT_ALIGN_CENTER_BOTH);
            display.drawString(display.getWidth() / 2, 20, "Reset in");
            display.setFont(ArialMT_Plain_24);
            display.drawString(display.getWidth() / 2, 44, String(secondsLeft));
            display.display();
        }

        if (heldTime >= RESET_TIME) {
            display.clear();
            display.setFont(ArialMT_Plain_16);
            display.setTextAlignment(TEXT_ALIGN_CENTER_BOTH);
            display.drawString(display.getWidth() / 2, 28, "RESET!");
            display.display();
            delay(1000);

            Serial.println("Reset-Taster 5 Sekunden gehalten! Setze WiFiManager-Einstellungen zurück...");
            WiFiManager wifiManager;
            wifiManager.resetSettings();
            delay(500);
            WiFi.disconnect(true);
            delay(500);
            ESP.restart();
        }

    } else {
        if (buttonPressed) {
            buttonPressed = false;
            resetActive = false;

            // <<< Hier neu ergänzen:
            previousMillis = millis() - interval;  
            // <<< Dadurch wird beim nächsten loop() direkt aktualisiert!
        }
    }
}

void setup() {
    pinMode(RESET_PIN, INPUT_PULLUP);
    Serial.begin(115200);
    Serial.println("WaterMe startet...");

    if (!LittleFS.begin()) {
        Serial.println("LittleFS-Fehler");
    } else {
        loadConfig();
    }

    // *** Display direkt am Anfang initialisieren und "AP" anzeigen ***
    display.init();
    display.flipScreenVertically();
    display.clear();
    display.setFont(ArialMT_Plain_24);
    display.setTextAlignment(TEXT_ALIGN_CENTER_BOTH);
    display.drawString(display.getWidth() / 2, 28, "AP");
    display.display();
    delay(500);  // Kurze Verzögerung (optional), damit man es sicher sieht

    setup_wifi();
 
    if (strlen(mqtt_server) > 0 && strlen(mqtt_port) > 0) {
        client.setServer(mqtt_server, atoi(mqtt_port));
        Serial.println("MQTT-Konfiguration geladen.");
    } else {
        Serial.println("MQTT-Daten fehlen! Webinterface zur Konfiguration nutzen.");
    }
 
    server.on("/", handleRoot);
    server.on("/save", HTTP_POST, handleSave);
    server.begin();
    Serial.println("Webserver gestartet!");
    Serial.print("Rufe auf: http://");
    Serial.println(WiFi.localIP());

    // Sofortiges erstes Update erzwingen
    previousMillis = millis() - interval;
    handleMQTTAndDisplayUpdate(millis());

    // Sommerzeitregel für Mitteleuropa: letzte So im März + Okt
    configTime("CET-1CEST,M3.5.0/2,M10.5.0/3", ntp_server);

    Serial.print("Warte auf Zeit...");
    time_t now = time(nullptr);
    while (now < 100000) {
        delay(100);
        Serial.print(".");
        now = time(nullptr);
    }
    Serial.println(" Zeit empfangen.");
}
 
void reconnect() {
    static unsigned long lastReconnectAttempt = 0;
    uint8_t mac[6];
    WiFi.macAddress(mac);
    char clientId[32];
    snprintf(clientId, sizeof(clientId), "WaterMe_%02X%02X%02X", mac[3], mac[4], mac[5]);

    if (!client.connected() && millis() - lastReconnectAttempt > 5000) {
        lastReconnectAttempt = millis();
        Serial.print("Verbindungsversuch mit MQTT-Broker als ");
        Serial.println(clientId);

        if (client.connect(clientId, mqtt_user, mqtt_pass)) {
            Serial.println("MQTT-Verbindung hergestellt!");
        } else {
            Serial.print("MQTT-Verbindung fehlgeschlagen, Fehlercode: ");
            Serial.print(client.state());
            Serial.println(" - Neuer Versuch in 5 Sekunden...");
        }
    }
}

void loop() {
    checkResetButton();

    if (resetActive) {
        return;
    }

    server.handleClient();

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

    handleMQTTAndDisplayUpdate(millis());
}

void handleMQTTAndDisplayUpdate(unsigned long currentMillis) {
    if (resetActive) return;

    if (currentMillis - previousMillis >= interval) {
        previousMillis = currentMillis;
        if (!client.connected()) {
            reconnect();
        }
        updateSensorData();
    }
}

void updateSensorData() {
    if (resetActive) return;
    int sensorValue = analogRead(SENSOR_PIN);
    float moisture = map(sensorValue, 1024, 0, 0, 100);

    time_t now = time(nullptr);
    struct tm *timeinfo = localtime(&now);
    char buffer[20];
    strftime(buffer, sizeof(buffer), "%H:%M:%S", timeinfo);
    String timestamp = String(buffer);

    updateDisplay(moisture, timestamp);
    sendMQTTData(moisture, timestamp);
}

void updateDisplay(float moisture, String timestamp) {
    Serial.println("Aktualisiere Display...");
    Serial.print("Feuchtigkeit: "); Serial.println(moisture);
    Serial.print("Timestamp: "); Serial.println(timestamp);

    String text = String(moisture, 0) + "%";
    display.clear();
    display.setFont(ArialMT_Plain_24);
    display.setTextAlignment(TEXT_ALIGN_CENTER_BOTH);
    display.drawString(display.getWidth() / 2, 28, text);
    display.drawLine(0, 42, 128, 42);
    display.setFont(ArialMT_Plain_16);
    display.drawString(display.getWidth() / 2, 54, timestamp);
    display.display();
}

void sendMQTTData(float moisture, const String& timestamp) {
    char topic[50], message[50];

    snprintf(topic, sizeof(topic), "%s/feuchtigkeit", mqtt_topic_prefix);
    snprintf(message, sizeof(message), "%.2f", moisture);
    client.publish(topic, message);

    snprintf(topic, sizeof(topic), "%s/timestamp", mqtt_topic_prefix);
    client.publish(topic, timestamp.c_str());
}

Gehäuse zum 3D-Drucken

Damit die Elektronik geschützt ist, kannst du dir ein passendes Gehäuse für WaterMev2 kostenlos auf meinem Cults3D-Account herunterladen: Hier geht’s zum Gehäuse.

WaterMeV2 Gehäuse mit ESP8266 und OLED Display

Fazit

Mit dem WaterMeV2 – Feuchtigkeitssensor mit ESP8266 und OLED-Display kannst du ganz einfach Werte überwachen und in dein Smart Home System integrieren. Dank der MQTT-Integration kannst du die Daten bequem weiterverarbeiten und mit dem OLED-Display behältst du stets den Überblick. Baue dein eigenes WaterMeV2 jetzt nach und lade dir das passende Gehäuse herunter!


Hast du Fragen oder Verbesserungsvorschläge? Schreib sie in die Kommentare!

Der Topfi: Praktischer Küchenhelfer mit einem kleinen Haken

In jeder Küche, in der regelmäßig gekocht wird, stellt sich oft die Frage: Wohin mit dem heißen und nassen Topfdeckel, wenn man gerade umrührt oder Zutaten hinzufügt? Genau dafür gibt es den Topfi* (bezahlter Link) – einen praktischen Halter, der den Deckel sicher aufnimmt und verhindert, dass Wasser direkt auf die Arbeitsplatte oder die Herdplatte tropft.

Ein nützlicher Helfer mit einem kleinen Nachteil

Der Topfi erfüllt seinen Zweck gut und sorgt für mehr Ordnung und Sauberkeit in der Küche. Allerdings gibt es einen kleinen, aber nicht unwesentlichen Nachteil: Irgendwann muss man den Topfi selbst wieder beiseite räumen.

Gerade wenn der Topfi* (bezahlter Link) noch heiß und nass ist, kann das unpraktisch sein. Hier fehlt eine durchdachte Ablage, die das Restwasser auffängt und ein einfaches Entleeren ermöglicht.

Meine Lösung: Eine 3D-gedruckte Ablage für den Topfi

Um diesen Nachteil zu umgehen, habe ich eine maßgeschneiderte Ablage für den Topfi entworfen, die sich einfach 3D-drucken lässt. Die Ablage wurde so konstruiert, dass:

  • Wasser sich in einer Auffangmulde sammeln kann, sodass nichts auf die Arbeitsfläche tropft.
  • Eine speziell geformte Ecke das einfache Abschütten des Wassers ermöglicht, ohne dass es danebenläuft.
  • Der Topfi sicher aufbewahrt ist.

Diese Lösung macht den ohnehin schon praktischen Topfi noch alltagstauglicher und verhindert unnötige Wasserspuren oder unschöne Flecken auf der Arbeitsplatte.

Download der 3D-Druckdatei

Für alle, die den Topfi* (bezahlter Link) nutzen und sich über das Tropfwasser-Problem ärgern, gibt es meine 3D-Druck-Datei als kostenlosen Download auf Cults3D: 👉 Hier geht’s zur Druckdatei.

Falls du also ebenfalls regelmäßig mit heißem, nassem Topfdeckel hantierst, probiere die Ablage aus und bringe noch mehr Ordnung und Komfort in deine Küche!

WaterMe – DIY Bewässerungssystem mit ESP8266 und Gehäuse aus dem 3D-Drucker

Für alle, die gleichzeitig Technikfreaks und Hobbygärtner sind, habe ich hier etwas Spannendes: das WaterMe DIY Bewässerungssystem. Ein DIY-Bewässerungssystem, das auf dem cleveren ESP8266 Mikrocontroller basiert, um deine Pflanzen optimal und smart zu versorgen.

Die Bauteile des WaterMe DIY Bewässerungssystem

1. Der Bodenfeuchtesensor: Kernstück des Systems ist der kapazitive Bodenfeuchtesensor* (bezahlter Link), der dank seiner Technologie dauerhaft und zuverlässig die Feuchtigkeit im Boden misst.

2. Der D1 Mini ESP8266 Entwicklungsboard: Der D1 Mini* (bezahlter Link) ist ein kleines Kraftpaket mit WLAN-Fähigkeit, ideal für alle IoT-Projekte.

3. Das 3D-gedruckte Gehäuse: Mein selbst entworfenes Gehäuse ist auf Cults3D kostenlos erhältlich und nutzt Gewindeeinsätze* (bezahlter Link). Hier findet Ihr passende Mikro-Schrauben* (bezahlter Link).

WaterMe – DIY Bewässerungssystem mit 3D gedrucktem Gehäuse

Erweiterte Funktionen und technische Details

Das „WaterMe“ System nutzt einen AP für die WLAN-Verbindung, MQTT und NTP, um die Funktionalität über das reine Messen der Bodenfeuchtigkeit hinaus zu erweitern.

MQTT (Message Queuing Telemetry Transport): Dieses leichte und effiziente Protokoll ermöglicht es dem System, Messdaten über das Internet zu versenden. Im Code werden MQTT-Einstellungen konfiguriert, um Sensorwerte an einen Server zu senden, der diese dann für Monitoring oder automatische Bewässerungsaktionen verwenden kann.

NTP (Network Time Protocol): Die Integration von NTP hilft dabei, die exakte Zeit für das Logging der Sensorwerte zu erhalten. Dies ist besonders nützlich, um zu bestimmen, wann die Pflanzen zuletzt gegossen wurden und wann sie wieder Wasser benötigen.

Anschluss des ESP

Der Anschluss des ESP ist sehr einfach. Es muss lediglich die rote Plus-Leitung an 5V, die schwarze Minus-Leitung an GND und die gelbe Signalleitung an A0 des ESP angeschlossen werden.

WaterMe – DIY Bewässerungssystem mit ESP8266

Arduino Code Erklärung

Der untenstehende Arduino-Sketch ist das Herzstück von des WaterMe DIY Bewässerungssystem. Er verbindet den D1 Mini mit deinem WLAN, misst die Bodenfeuchtigkeit und sendet diese Daten über MQTT. Den kompletten Code findest du nachfolgend:

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <WiFiManager.h>  // WiFiManager Library für AP-Konfiguration
#include <ESP8266WebServer.h>
#include <NTPClient.h>  // Bibliothek für NTP-Client
#include <WiFiUdp.h>          // Only needed for Arduino 1.6.5 and earlier
#include <LittleFS.h>
#include <ArduinoJson.h>
 
#define SENSOR_PIN A0  // Sensor an A0 angeschlossen

unsigned long previousMillis = 0;  // Speichert den Zeitpunkt des letzten MQTT-Sendevorgangs
const long interval = 30000;       // Zeitintervall für das Senden der MQTT-Daten
 
char mqtt_server[40] = "";
char mqtt_port[6] = "";
char mqtt_topic_prefix[40] = "WaterMe"; // Benutzerdefiniertes Präfix
char ntp_server[40] = "pool.ntp.org";  // Standard NTP Server
 
WiFiClient espClient;
PubSubClient client(espClient);
ESP8266WebServer server(80);
WiFiUDP udp;
NTPClient timeClient(udp, ntp_server, 3600, 60000);  // Zeitzone +1 für Deutschland
 
void saveConfig() {
    Serial.println("Speichere Konfiguration in LittleFS...");
    File configFile = LittleFS.open("/config.json", "w");
    if (!configFile) {
        Serial.println("Fehler beim Speichern der Konfiguration!");
        return;
    }

    DynamicJsonDocument doc(128);
    doc["mqtt_server"] = mqtt_server;
    doc["mqtt_port"] = mqtt_port;
    doc["mqtt_topic_prefix"] = mqtt_topic_prefix;
    doc["ntp_server"] = ntp_server;

    serializeJson(doc, configFile);
    configFile.close();
    Serial.println("Konfiguration gespeichert.");
}

void loadConfig() {
    Serial.println("Lade Konfiguration aus LittleFS...");

    File configFile = LittleFS.open("/config.json", "r");
    if (!configFile) {
        Serial.println("Keine gespeicherte Konfiguration gefunden.");
        return;
    }

    DynamicJsonDocument doc(128);
    DeserializationError error = deserializeJson(doc, configFile);
    if (error) {
        Serial.println("Fehler beim Lesen der Konfigurationsdatei!");
        return;
    }

    strlcpy(mqtt_server, doc["mqtt_server"] | "", sizeof(mqtt_server));
    strlcpy(mqtt_port, doc["mqtt_port"] | "", sizeof(mqtt_port));
    strlcpy(mqtt_topic_prefix, doc["mqtt_topic_prefix"] | "", sizeof(mqtt_topic_prefix));
    strlcpy(ntp_server, doc["ntp_server"] | "", sizeof(ntp_server));

    Serial.println("Konfiguration geladen.");
}
 
void setup_wifi() {
    Serial.println("Starte WiFiManager...");

    uint8_t mac[6];
    WiFi.macAddress(mac);

    char hostname[32];
    snprintf(hostname, sizeof(hostname), "WaterMe_%02X%02X%02X", mac[3], mac[4], mac[5]);

    WiFiManager wifiManager;
    wifiManager.setTimeout(180); // 3 Minuten warten statt unendlich

    //wifiManager.resetSettings();

    WiFiManagerParameter custom_mqtt_server("server", "MQTT Server", mqtt_server, 40);
    WiFiManagerParameter custom_mqtt_port("port", "MQTT Port", mqtt_port, 6);
    WiFiManagerParameter custom_mqtt_topic("topic", "MQTT Topic Prefix", mqtt_topic_prefix, 40);
    WiFiManagerParameter custom_ntp_server("ntp_server", "NTP Server", ntp_server, 40);
 
    wifiManager.addParameter(&custom_mqtt_server);
    wifiManager.addParameter(&custom_mqtt_port);
    wifiManager.addParameter(&custom_mqtt_topic);
    wifiManager.addParameter(&custom_ntp_server);

    if (!wifiManager.autoConnect(hostname)) {
        Serial.println("Fehlgeschlagen! Neustart in 5 Sekunden...");
        delay(5000);
        ESP.restart();
    }

    WiFi.hostname(hostname);

    strncpy(mqtt_server, custom_mqtt_server.getValue(), sizeof(mqtt_server) - 1);
    strncpy(mqtt_port, custom_mqtt_port.getValue(), sizeof(mqtt_port) - 1);
    strncpy(mqtt_topic_prefix, custom_mqtt_topic.getValue(), sizeof(mqtt_topic_prefix) - 1);
    strncpy(ntp_server, custom_ntp_server.getValue(), sizeof(ntp_server) - 1);
    saveConfig();
    Serial.println("WLAN verbunden!");
    Serial.print("IP-Adresse: ");
    Serial.println(WiFi.localIP());
}
 
void handleRoot() {
    Serial.println("Web-Oberfläche aufgerufen");
    String htmlContent = "<!DOCTYPE html><html><head><meta charset='UTF-8'><title>WaterMe Einstellungen</title>"
                         "<style>"
                         "body { font-family: Arial, sans-serif; margin: 40px; background-color: #333; color: #fff; }"
                         "h1 { color: #fff; }"
                         "form { background-color: #222; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px 0 rgba(0,0,0,0.5); }"
                         "label { display: block; margin-top: 20px; margin-bottom: 5px; color: #ccc; }"
                         "input[type='text'], input[type='submit'] { width: 100%; padding: 8px; margin-top: 5px; border: 1px solid #555; border-radius: 4px; }"
                         "input[type='text'] { background-color: #555; color: #ddd; }"
                         "input[type='submit'] { background-color: #008CBA; color: white; cursor: pointer; }"
                         "input[type='submit']:hover { background-color: #005f7a; }"
                         "</style>"
                         "</head><body>"
                         "<h1>WaterMe Einstellungen</h1>"
                         "<form action='/save' method='POST'>"
                         "<label for='server'>MQTT Server:</label> <input type='text' id='server' name='server' value='" + String(mqtt_server) + "'><br>"
                         "<label for='port'>MQTT Port:</label> <input type='text' id='port' name='port' value='" + String(mqtt_port) + "'><br>"
                         "<label for='topic'>MQTT Topic Prefix:</label> <input type='text' id='topic' name='topic' value='" + String(mqtt_topic_prefix) + "'><br>"
                         "<label for='ntp_server'>NTP Server:</label> <input type='text' id='ntp_server' name='ntp_server' value='" + String(ntp_server) + "'><br>"
                         "<input type='submit' value='Speichern'>"
                         "</form></body></html>";
    server.send(200, "text/html", htmlContent);
}
 
void handleSave() {
    Serial.println("Speichere neue Einstellungen...");
    if (server.hasArg("server")) strncpy(mqtt_server, server.arg("server").c_str(), sizeof(mqtt_server) - 1);
    if (server.hasArg("port")) strncpy(mqtt_port, server.arg("port").c_str(), sizeof(mqtt_port) - 1);
    if (server.hasArg("topic")) strncpy(mqtt_topic_prefix, server.arg("topic").c_str(), sizeof(mqtt_topic_prefix) - 1);
    if (server.hasArg("ntp_server")) strncpy(ntp_server, server.arg("ntp_server").c_str(), sizeof(ntp_server) - 1);
    saveConfig();
    server.send(200, "text/html", "<html><body><h1>Gespeichert! Neustart...</h1></body></html>");
    delay(3000);
    ESP.restart();
}
 
void setup() {
    Serial.begin(115200);
    Serial.println("WaterMe startet...");

    if (!LittleFS.begin()) {
        Serial.println("LittleFS-Fehler");
    } else {
        loadConfig();
    }

    setup_wifi();
    timeClient.begin();
 
    if (strlen(mqtt_server) > 0 && strlen(mqtt_port) > 0) {
        client.setServer(mqtt_server, atoi(mqtt_port));
        Serial.println("MQTT-Konfiguration geladen.");
    } else {
        Serial.println("MQTT-Daten fehlen! Webinterface zur Konfiguration nutzen.");
    }
 
    server.on("/", handleRoot);
    server.on("/save", HTTP_POST, handleSave);
    server.begin();
    Serial.println("Webserver gestartet!");
    Serial.print("Rufe auf: http://");
    Serial.println(WiFi.localIP());

    // Sofortiges erstes Update erzwingen
    previousMillis = millis() - interval;  // Simuliert ein vergangenes Intervall
    handleMQTTUpdate(millis());
}
 
void reconnect() {
    static unsigned long lastReconnectAttempt = 0;
    uint8_t mac[6];
    WiFi.macAddress(mac);
    char clientId[32];
    snprintf(clientId, sizeof(clientId), "WaterMe_%02X%02X%02X", mac[3], mac[4], mac[5]);

    if (!client.connected() && millis() - lastReconnectAttempt > 5000) {
        lastReconnectAttempt = millis();
        Serial.print("Verbindungsversuch mit MQTT-Broker als ");
        Serial.println(clientId);

        if (client.connect(clientId)) {
            Serial.println("MQTT-Verbindung hergestellt!");

            // Falls du Topics abonnieren willst, füge sie hier hinzu:
            // client.subscribe("WaterMe/steuerung");  
            // client.subscribe("WaterMe/config");

        } else {
            Serial.print("MQTT-Verbindung fehlgeschlagen, Fehlercode: ");
            Serial.print(client.state());  // Gibt den Fehlercode aus
            Serial.println(" - Neuer Versuch in 5 Sekunden...");
        }
    }
}

void loop() {
    server.handleClient();
    timeClient.update();

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

    handleMQTTUpdate(millis());
}

void handleMQTTUpdate(unsigned long currentMillis) {
    if (currentMillis - previousMillis >= interval) {
        previousMillis = currentMillis;
        if (!client.connected()) {
            reconnect();
        }
        updateSensorData();
    }
}

void updateSensorData() {
    int sensorValue = analogRead(SENSOR_PIN);  // Nur einmal aufrufen
    float moisture = map(sensorValue, 1024, 0, 0, 100);
    String timestamp = timeClient.getFormattedTime();

    sendMQTTData(moisture, timestamp);
}

void sendMQTTData(float moisture, const String& timestamp) {
    char topic[50], message[50];

    snprintf(topic, sizeof(topic), "%s/feuchtigkeit", mqtt_topic_prefix);
    snprintf(message, sizeof(message), "%.2f", moisture);
    client.publish(topic, message);

    snprintf(topic, sizeof(topic), "%s/timestamp", mqtt_topic_prefix);
    client.publish(topic, timestamp.c_str());
}