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());
}