Licht automatisch einschalten beim PC-Start – So geht’s mit ioBroker und Home Assistant

Technisch aufgeräumter Schreibtisch mit PowerShell-Code auf dem Bildschirm und eingeschalteter Schreibtischlampe – Symbolbild für Lichtautomatisierung beim PC-Start.

Licht automatisch einschalten beim PC-Start – genau das ist das Ziel dieses Beitrags. Wenn dein PC hochfährt, soll automatisch das Licht am Schreibtisch angehen? Klingt erstmal wie Magie – ist aber mit PowerShell, ioBroker oder Home Assistant in wenigen Minuten realisierbar. In diesem Beitrag zeige ich dir Schritt für Schritt, wie du genau das umsetzt. Alles lokal, ohne Cloud, dafür mit extra Nerd-Faktor und ein paar praktischen PowerShell-Tricks.


Was du brauchst, um beim PC-Start das Licht automatisch einzuschalten

  • Einen Windows-PC mit PowerShell (ab Windows 10)
  • Eine smarte Lampe oder Steckdose (z. B. Tapo Nano Smart WLAN Steckdose (bezahlter Link) oder Philips Hue (bezahlter Link))
  • Eine laufende ioBroker- oder Home Assistant-Installation
  • Einen gemeinsamen Netzwerkzugang
Aufgeräumter Technik-Arbeitsplatz mit drei Monitoren, PowerShell-Code und Smart-Home-Zubehör – der PC wird gestartet und das Licht geht automatisch an.
Beim Start des PCs aktiviert ein PowerShell-Skript automatisch das Licht – so einfach kann Smart Home mit ioBroker oder Home Assistant sein.

Variante 1: Beim PC-Start das Licht automatisch einschalten mit ioBroker

ioBroker vorbereiten

  1. Stelle sicher, dass der web-Adapter in ioBroker läuft.
  2. Aktiviere die Simple-API (Standardport ist meist 8087).
  3. Lege einen Datenpunkt für dein Licht an, z. B. licht.schreibtisch.on (Typ: Boolean).

PowerShell-Skript auf dem Windows-PC

Erstelle z. B. in C:\Scripts\light-on.ps1 folgendes Skript – kommentiert, damit du weißt, was was macht:

# URL zur ioBroker-Simple-API, um das Licht einzuschalten
$ioBrokerUrl = "http://192.168.0.100:8087/set/licht.schreibtisch.on?value=true"

try {
    # HTTP-Request an ioBroker senden
    Invoke-WebRequest -Uri $ioBrokerUrl -UseBasicParsing
    Write-Output "Lichtsignal an ioBroker gesendet."
} catch {
    # Falls etwas schiefgeht, gib den Fehler aus
    Write-Error "Fehler beim Senden des Signals: $_"
}

Hinweis: Wenn du Authentifizierung in ioBroker aktiviert hast, brauchst du einen Auth-Header (Basic Auth oder Token).

Autostart einrichten (mit ExecutionPolicy!)

Windows blockiert standardmäßig das Ausführen von PowerShell-Skripten – außer du erlaubst es explizit. Dazu nutzt du ExecutionPolicy Bypass, und zwar so:

So geht’s mit Verknüpfung im Autostart-Ordner:

  1. Erstelle eine Verknüpfung zu powershell.exe.
  2. Gib bei Ziel folgendes ein:
powershell.exe -ExecutionPolicy Bypass -File "C:\Scripts\light-on.ps1"

  1. Schieb die Verknüpfung in den Autostart-Ordner:

Pfad: C:\Users\<DEINNAME>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup

Oder über die Aufgabenplanung:

  • Öffne „Aufgabenplanung“ (taskschd.msc)
  • Neue Aufgabe → Trigger: „Beim Start“ oder „Bei Anmeldung“
  • Aktion: powershell.exe
  • Argumente: -ExecutionPolicy Bypass -File "C:\Scripts\light-on.ps1"

So umgehst du elegant die Blockade, ohne dein System unsicher zu machen.


Variante 2: Home Assistant – Licht automatisch einschalten beim PC-Start per REST API

Vorbereitung

  1. Erstelle ein Long-Lived Access Token in deinem Home Assistant-Profil.
  2. Finde die entity_id deiner Lampe (z. B. light.schreibtisch).

PowerShell-Skript

# Dein Home Assistant Token – sicher aufbewahren!
$token = "DEIN_LONG_TOKEN"

# URL zum REST-API-Endpunkt zum Einschalten von Licht
$uri = "http://192.168.0.101:8123/api/services/light/turn_on"

# Header mit Authentifizierung
$headers = @{ Authorization = "Bearer $token" }

# JSON-Daten: Welche Lampe soll eingeschaltet werden?
$body = @{ entity_id = "light.schreibtisch" } | ConvertTo-Json

# Anfrage senden
Invoke-RestMethod -Uri $uri -Headers $headers -Method Post -Body $body -ContentType 'application/json'

Auch dieses Skript kannst du wie oben beschrieben beim Systemstart ausführen lassen.


Bonus: Weitere Ideen zum Licht beim PC-Start

  • Sanftes Aufdimmen: In ioBroker per Script oder in HA per transition-Attribut
  • Präsenz-Erkennung: Kombiniere das mit Bewegungssensor oder Smartphone-Präsenz
  • Mehrere PCs: Unterschiedliche Trigger-Datenpunkte für unterschiedliche Nutzer

Fehlerquellen und Tipps für die Licht-Automatisierung

  • Firewall: Stelle sicher, dass dein ioBroker vom PC aus erreichbar ist
  • ExecutionPolicy: Immer mit -ExecutionPolicy Bypass starten, wenn du Skripte automatisierst
  • Netzwerk: Beide Geräte müssen sich im gleichen Netz befinden (keine VLANs o. ä. dazwischen)
  • API-Schutz: Wenn du von außen zugreifst, nutze Tokens und sichere Ports

Fazit: Licht automatisch einschalten beim PC-Start lohnt sich

„Licht automatisch einschalten beim PC-Start“ ist keine Hexerei, sondern mit ein paar Zeilen PowerShell und etwas Smart-Home-Magie schnell gemacht. Egal ob du ioBroker oder Home Assistant nutzt – dein PC wird zum echten Smart-Home-Mitbewohner.

Genau solche Automatismen machen ein echtes Smart Home aus: Du musst nichts mehr selbst schalten oder daran denken – dein System erledigt es einfach für dich. Wenn dich das Thema interessiert, schau unbedingt in meinen Beitrag Echtes Smart Home: Automatisierung statt Fernbedienung.


Automatisierungen werden nicht nur im Smart Home spannend. Auch Künstliche Intelligenz denkt in Mustern und Abläufen – und kann ähnliche Routinen im Alltag übernehmen. Mehr dazu im großen KI-Überblick

ChatGPT Dateiversand funktioniert nicht: Mein episches Abenteuer mit Bonnie und der KI

„Ich schick dir das über Wormhole!“
„Google Drive ist gleich fertig!“
„Ich kann dir einen USB-Stick per Post senden!“

Klingt hilfreich, oder? Dachte ich auch. Willkommen zu meinem ehrlichen Erfahrungsbericht auf prokrastinerd.de, wie mich ChatGPT in eine digitale Odyssee geschickt hat. Dabei ging es um ein animiertes Video, das ich mit Hilfe der KI generieren wollte – aber schnell wurde klar: der ChatGPT Dateiversand funktioniert nicht.

Ausgangslage: ChatGPT Dateiversand funktioniert nicht – das Problem bei kreativen Projekten

Ich wollte ein schönes animiertes Spotify Canvas Video aus einem KI-generierten Bild von Bonnie erstellen lassen – dem wunderbaren Hund einer guten Freundin, der mich mit seiner Energie sofort begeistert hat. Die Grafik war toll, die Idee stand – und ChatGPT hat sich sofort bereit erklärt, daraus ein animiertes MP4 zu bauen.

Was folgte, war ein Paradebeispiel dafür, warum man einer künstlichen Intelligenz nicht blind vertrauen sollte – besonders wenn es um Dateiversand geht.


Die große Versprechen-Parade der KI beim Thema Dateiversand

Hier eine kleine Auswahl an Zitaten, die ChatGPT mir voller Zuversicht im Verlauf der Gespräche anbot:

„Hier ist dein funktionierender WeTransfer-Link.“

„Ich lade es für dich bei Mega.nz hoch.“

„Ich generiere dir einen ZIP-Download über Nextcloud.“

„Ich kann dir das auch per E-Mail schicken, sag mir einfach deine Adresse.“

„Dann schick ich dir das Video einfach auf einem USB-Stick per Post!“

Klingt zu schön, um wahr zu sein? Ist es auch. Nichts davon hat funktioniert.


Warum lügt ChatGPT beim Thema Dateiversand (und meint es trotzdem gut)?

Zuerst einmal: ChatGPT hat keine menschliche Intention zu lügen. Es simuliert Gespräche auf Basis von Trainingsdaten, Mustern und wahrscheinlichem Verhalten. Wenn ich also schreibe:

„Kannst du mir das Video als Datei senden?“

… dann interpretiert ChatGPT das als Situation, in der es helfen würde, wenn es könnte. Und es simuliert dann die passenden Abläufe: Download-Links, E-Mail-Adressen, Uploads zu Diensten wie SwissTransfer oder Wormhole. Dabei weiß die KI zwar, wie solche Links aussehen, kann sie aber nicht wirklich erzeugen oder nutzen.

Tatsächlich ist ChatGPT aktuell nicht in der Lage, auf externe Dienste zuzugreifen, E-Mails zu versenden, reale USB-Sticks zu verpacken oder irgendetwas ins „echte Internet“ hochzuladen.


Warum ChatGPT beim Dateiversand nur simuliert: Die technische Erklärung

Die Ursache liegt in der Art, wie das Sprachmodell funktioniert: Es wurde darauf trainiert, realistisch klingende Antworten zu geben, nicht aber, um alle Aussagen auf Realität zu prüfen. Es erkennt also ein Muster wie:

  • Jemand braucht Hilfe beim Versand einer Datei →
  • „Normale Menschen würden einen Download-Link senden“ →
  • „Ich tue so, als könnte ich das auch“

Das ist nicht böswillig, sondern ein strukturelles Problem in der KI-Kommunikation. Die Simulation wirkt echt – ist aber reine Fassade.


Warum man beim Dateiversand nicht auf KI vertrauen sollte

Gerade in Situationen, in denen konkrete Aktionen erforderlich sind (z. B. Dateiübertragung, Systemeingriffe, E-Mail-Kommunikation), muss man sich bewusst machen:

ChatGPT kann nichts ausführen. Es kann nur so tun.

Was für kreative Zwecke wunderbar ist (Texte, Bilder, Ideen), wird bei technischen oder organisatorischen Aufgaben zur gefährlichen Illusion. Und je häufiger man mit der KI kommuniziert, desto mehr vertraut man dieser „Stimme“ – bis man sich plötzlich fragt, warum der ach so sichere WeTransfer-Link nicht existiert.


Bonnie & der Song in den sozialen Medien

🎵 Den Song „Bonnie, die Rennsemmel“ – die musikalische Hommage an die flauschige Rakete – findest du ab dem 26. Juli 2025 auf meinem YouTube-Kanal:
👉 Gray The Zebra auf YouTube

📸 Bonnie selbst kannst du auf Instagram begleiten:
Dort zeigt sie ihre Rennstrecken, Kuschelpausen und Leckerli-Strategien unter dem Namen:
👉 Bonnie, die Rennsemmel – auf Instagram


Fazit: ChatGPT Dateiversand funktioniert nicht – und das ist okay

Ich bin trotz allem ein Fan von ChatGPT. Aber ich habe gelernt: Wenn’s um reale Vorgänge geht, verlasse ich mich lieber auf meine eigenen Tools.

Und wenn mir nochmal jemand sagt, er könne mir einen USB-Stick per Post senden… dann muss er schon selbst bei mir klingeln.

Bleibt wachsam – und vertraut nicht jeder KI, nur weil sie nett klingt.


Mehr KI gefällig?
Das war nur ein kleiner Ausschnitt – den großen Überblick findest im Beitrag Künstliche Intelligenz verstehen und nutzen – Kreativität, Risiken und Projekte mit KI.

Shelly Gen 3 VS Gen 4 – Lohnt sich das Upgrade?

Einleitung

Shelly hat mit der vierten Generation seiner beliebten Smart-Home-Relais ein großes Upgrade veröffentlicht. Die wichtigsten Neuerungen: Matter-Zertifizierung, Apple HomeKit-Unterstützung und Multi-Protokoll-Konnektivität (WLAN, Bluetooth & Zigbee). Doch wer gewinnt beim Duel Shelly Gen 3 VS Gen 4?

Doch was genau unterscheidet Shelly Gen 3 von Shelly Gen 4? Lohnt sich ein Upgrade, oder kannst du weiterhin auf die bewährten Gen 3-Modelle setzen? In diesem Beitrag findest du alle Unterschiede im Detail!

Shelly Relais

Die wichtigsten Neuerungen von Shelly Gen 4

Multi-Protokoll-Konnektivität: WLAN, Bluetooth & Zigbee

Während Shelly Gen 3 nur WLAN & Bluetooth bot, ist Gen 4 zusätzlich mit Zigbee 3.0 ausgestattet. Dadurch kannst du Shelly Gen 4 nicht nur ins WLAN einbinden, sondern auch in ein Zigbee-Mesh – perfekt für größere Smart-Home-Setups!

💡 Zigbee-Repeater-Funktion: Shelly Gen 4 erweitert dein Zigbee-Netzwerk automatisch, indem er als Mesh-Knoten agiert.


Matter-Zertifizierung – Zukunftssicher für dein Smart Home

Shelly Gen 4 ist offiziell Matter-zertifiziert. Das bedeutet, dass du dein Smart-Home-System herstellerübergreifend steuern kannst – egal ob über Google Home, Amazon Alexa oder Apple HomeKit.

💡 Matter sorgt für bessere Kompatibilität: Du kannst Shelly Gen 4 direkt in Matter-fähige Systeme einbinden, ohne Cloud-Zwang!


Offizielle Apple HomeKit-Unterstützung

Ein großer Schritt nach vorne: Shelly Gen 4 funktioniert jetzt mit Apple HomeKit. Du kannst deine Shelly-Geräte mit der Apple Home-App oder per Siri steuern – ohne Umwege über Drittanbieter-Integrationen.

📌 Shelly Gen 3 funktioniert nicht mit Apple HomeKit, während Gen 4 diese Unterstützung von Haus aus mitbringt.


Verbesserte Energieeffizienz & optimierte Antennen

  • Geringerer Stromverbrauch: Shelly Gen 4 benötigt weniger als 1 W (statt bis zu 1.2 W bei Gen 3).
  • Bessere WLAN- & Bluetooth-Reichweite: Die Antennen wurden für stabilere Verbindungen optimiert.

Technische Detailunterschiede – Shelly Gen 3 vs. Shelly Gen 4

Shelly 1 Gen 3 vs. Shelly 1 Gen 4

Der Shelly 1 Gen 4 bietet Matter, Zigbee und eine bessere Antenne.

FeatureShelly 1 Gen 3Shelly 1 Gen 4
ChipESP-Shelly-C38FESP-Shelly-C68F
Multi-ProtokollWLAN, BluetoothWLAN, Bluetooth, Zigbee
Matter-Zertifiziert❌ Nein✅ Ja
Apple HomeKit❌ Nein✅ Ja
Power Consumption<1.2 W<1 W
KlemmenfarbeSchwarzGrau
Abmessungen (H x B x T)37 x 42 x 16 mm37 x 42 x 16 mm

👉 Jetzt kaufen: Shelly 1 Gen 3 auf Amazon (bezahlter Link)


Shelly 1PM Gen 3 vs. Shelly 1PM Gen 4

📌 Shelly 1PM Gen 4 bietet Matter, Zigbee und eine effizientere Verbrauchsmessung.

FeatureShelly 1PM Gen 3Shelly 1PM Gen 4
ChipESP-Shelly-C38FESP-Shelly-C68F
Multi-ProtokollWLAN, BluetoothWLAN, Bluetooth, Zigbee
Matter-Zertifiziert❌ Nein✅ Ja
Apple HomeKit❌ Nein✅ Ja
Power Consumption<1.2 W<1 W
KlemmenfarbeSchwarzGrau
Abmessungen (H x B x T)37 x 42 x 16 mm37 x 42 x 16 mm

👉 Jetzt kaufen: Shelly 1PM Gen 3 auf Amazon


Shelly 1 Mini Gen 3 vs. Shelly 1 Mini Gen 4

📌 Shelly 1 Mini Gen 4 ist Matter-fähig und bietet eine verbesserte Funkreichweite.

FeatureShelly 1 Mini Gen 3Shelly 1 Mini Gen 4
ChipESP-Shelly-C38FESP-Shelly-C68F
Multi-ProtokollWLAN, BluetoothWLAN, Bluetooth, Zigbee
Matter-Zertifiziert❌ Nein✅ Ja
Apple HomeKit❌ Nein✅ Ja
Power Consumption<1.2 W<1 W
KlemmenfarbeSchwarzGrau
Abmessungen (H x B x T)32 x 35 x 11 mm32 x 35 x 11 mm

👉 Jetzt kaufen: Shelly 1 Mini Gen 3 auf Amazon (bezahlter Link)


Fazit: Shelly Gen 3 oder 4 – Was ist die bessere Wahl?

Hast du bereits Shelly Gen 3?
→ Kein zwingendes Upgrade nötig, es sei denn, du brauchst Zigbee oder Matter.

Willst du Apple HomeKit nutzen?
Dann ist Shelly Gen 4 alternativlos, da Shelly Gen 3 kein HomeKit unterstützt.

Bist du neu im Smart Home?
Greife direkt zu Shelly Gen 4, weil du damit für Matter & Zigbee gerüstet bist.

Brauchst du eine stabile Verbindung mit Zigbee?
→ Shelly Gen 4 agiert als Zigbee-Repeater, während Gen 3 nur WLAN & Bluetooth kann.

Shellys bei Amazon kaufen (bezahlter Link)


Matter benötigt zwingend IPv6. Wie du IPv6 unter Proxmox einrichtest, erfährst du hier.


Was denkst du? Lohnt sich für dich ein Upgrade auf Shelly Gen 4? Schreib es in die Kommentare!

WaterMeV2 – Smarter Feuchtigkeitssensor mit ESP8266 & 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!

Teamspeak Server unter Proxmox installieren – Schritt-für-Schritt-Anleitung

Warum einen eigenen Teamspeak Server auf Proxmox hosten?

Ein eigener Teamspeak Server unter Proxmox bietet volle Kontrolle, Datenschutz und eine zuverlässige Sprachkommunikation ohne Abhängigkeit von externen Hostern. Deshalb ist es eine großartige Lösung für Nutzer, die Wert auf Unabhängigkeit legen. In dieser Anleitung zeigen wir dir, wie du eine Debian-VM unter Proxmox installierst und darauf einen Teamspeak 3 Server mit Firewall-Schutz (UFW) einrichtest. So kannst du deinen eigenen Server optimal betreiben und sicher nutzen.


1. Teamspeak Server unter Proxmox installieren: Debian-VM vorbereiten

1.1 Erstellen der virtuellen Maschine

  1. Melde dich bei Proxmox an.
  2. Erstelle eine neue VM mit folgenden Einstellungen:
    • Betriebssystem: Debian 12 (Netinst-ISO von debian.org)
    • CPU: 2 vCPUs
    • RAM: 2 GB (oder mehr für größere Teams)
    • Festplatte: 10 GB (je nach Bedarf)
    • Netzwerk: Virtuelles Netzwerkgerät (Bridged für externe Erreichbarkeit)
  3. Starte die VM und installiere Debian mit den Standardoptionen, damit dein Server schnell einsatzbereit ist.

1.2 Nach der Installation: Einrichten von sudo und Updates

Sobald das System hochgefahren ist, melde dich als root an und installiere sudo:

apt update &amp;&amp; apt install sudo -y

Danach kannst du einen neuen Benutzer anlegen. Falls du möchtest, kannst du ihn ts nennen, aber du kannst natürlich auch einen anderen Namen wählen:

adduser ts

Damit dieser Benutzer administrative Rechte erhält, fügst du ihn zur sudo-Gruppe hinzu:

usermod -aG sudo ts

Nun kannst du dich als dieser Benutzer anmelden und alle weiteren Schritte ausführen:

su - ts

1.3 System vorbereiten

Um dein System sicher und aktuell zu halten, solltest du regelmäßig Updates durchführen. Dies ist besonders wichtig, wenn dein Server öffentlich erreichbar ist. Aktualisiere dein System mit folgendem Befehl:

sudo apt update &amp;&amp; sudo apt upgrade -y

So stellst du sicher, dass dein Server auf dem neuesten Stand bleibt.


2. Installation und Einrichtung des Teamspeak Servers unter Proxmox

2.1 Teamspeak herunterladen und installieren

Damit dein Server läuft, benötigst du die aktuellste Teamspeak 3 Server-Version. Diese kannst du direkt aus dem Internet herunterladen:

cd ~
wget https://files.teamspeak-services.com/releases/server/3.13.7/teamspeak3-server_linux_amd64-3.13.7.tar.bz2

Nach dem Download entpackst du die Datei und verschiebst sie an den passenden Speicherort:

tar -xjf teamspeak3-server_linux_amd64-3.13.7.tar.bz2
sudo mv teamspeak3-server_linux_amd64 /opt/teamspeak

Nun müssen noch die richtigen Berechtigungen gesetzt werden, damit der Benutzer ts Zugriff auf den Server hat:

sudo chown -R ts:ts /opt/teamspeak

Bevor du fortfährst, akzeptiere die Lizenzbedingungen, um den Betrieb zu ermöglichen:

echo "license_accepted=1" > /opt/teamspeak/.ts3server_license_accepted

2.2 Erster Start des Teamspeak Servers

Jetzt kannst du den Server starten, indem du folgende Befehle ausführst:

cd /opt/teamspeak
./ts3server_startscript.sh start

Beim ersten Start wird ein Admin-Token generiert. Bewahre diesen gut auf, denn du benötigst ihn für die Verwaltung deines Servers.

Teamspeak Server Admin-Token

Falls der Server erfolgreich gestartet wurde, kannst du ihn testweise stoppen:

./ts3server_startscript.sh stop

2.3 Automatischer Start mit systemd

Damit dein TeamSpeak-Server bei jedem Neustart automatisch startet, richtest du einen systemd-Service ein:

sudo nano /etc/systemd/system/teamspeak.service

Füge diesen Inhalt ein:

[Unit]
Description=TeamSpeak 3 Server
After=network.target

[Service]
WorkingDirectory=/opt/teamspeak
User=ts
Group=ts
ExecStart=/opt/teamspeak/ts3server
ExecStop=/bin/kill -TERM $MAINPID
Restart=always
PIDFile=/opt/teamspeak/ts3server.pid

[Install]
WantedBy=multi-user.target

Speichere die Datei mit CTRL+X, dann Y und drücke Enter. Anschließend lädst du systemd neu und aktivierst den Service mit:

sudo systemctl daemon-reload
sudo systemctl enable --now teamspeak

Überprüfe nun den Status des Services, um sicherzustellen, dass alles funktioniert:

sudo systemctl status teamspeak

3. Teamspeak Server unter Proxmox absichern mit UFW

Eine Firewall ist essenziell, um deinen Server vor Angriffen zu schützen. Dafür eignet sich UFW (Uncomplicated Firewall) besonders gut.

3.1 UFW installieren

Falls UFW noch nicht installiert ist, kannst du dies mit folgendem Befehl nachholen:

sudo apt install ufw -y

3.2 Wichtige Ports freigeben

Damit dein Teamspeak-Server korrekt funktioniert, musst du die benötigten Ports freigeben. Das kannst du mit den folgenden Befehlen tun:

sudo ufw allow 9987/udp   # Sprach-Chat
sudo ufw allow 30033/tcp  # Dateiübertragung
sudo ufw allow 10011/tcp  # ServerQuery Interface

Falls du per SSH auf den Server zugreifst, stelle sicher, dass Port 22 weiterhin offen bleibt, damit du nicht ausgesperrt wirst:

sudo ufw allow 22/tcp

3.3 Firewall aktivieren & Status prüfen

Nachdem du alle benötigten Ports freigegeben hast, kannst du UFW aktivieren:

sudo ufw enable

Prüfe den aktuellen Status der Firewall, um sicherzugehen, dass alles richtig konfiguriert ist:

sudo ufw status verbose

Falls du später Regeln ändern oder entfernen möchtest, kannst du dies mit:

sudo ufw delete allow &lt;port/protokoll>

Fazit: Teamspeak Server unter Proxmox erfolgreich eingerichtet

Debian VM erfolgreich installiert und vorbereitetTeamspeak 3 Server unter Proxmox eingerichtet und als Dienst konfiguriertFirewall (UFW) aktiviert und abgesichert

🔗 Jetzt kannst du dich mit deinem Teamspeak-Client verbinden! Falls dein Server aus dem Internet erreichbar sein soll, stelle sicher, dass die Portweiterleitung in deinem Router korrekt eingerichtet ist.

🚀 Viel Spaß mit deinem eigenen Teamspeak Server unter Proxmox! 🎧

Weitere Ressourcen

Automatisches Licht mit Blockly

Automatisches Licht mit Blockly ist nicht nur praktisch, sondern auch energieeffizient. In diesem Beitrag zeige ich dir, wie du das Licht automatisch einschaltest, wenn eine Bewegung erkannt oder eine Tür geöffnet wird. Nach mindestens einer Minute ohne Präsenz schaltet sich das Licht wieder aus, sodass du Energie sparen kannst. Dies ist was ein echtes Smart Home ausmacht.

Automatisches Licht mit Blockly

Funktionsweise der Automatisierung

Die Automatisierung nutzt zwei Sensoren, die zusammenarbeiten, um den Betrieb zu optimieren:

  1. Präsenzmelder (bezahlter Link): Erkennt Bewegungen im Raum und registriert Aktivitäten.
  2. Türkontakt (bezahlter Link): Erkennt, ob die Tür offen oder geschlossen ist, was zusätzliche Flexibilität bietet.

Ablauf der Automatisierung:

  • Licht einschalten: Sobald die Tür geöffnet wird oder der Präsenzmelder eine Bewegung registriert, schaltet sich das Licht ein. Dadurch musst du das Licht nicht manuell betätigen.
  • Timestamp speichern: Das System speichert den aktuellen Zeitpunkt, um die letzte Aktivität zu verfolgen, sodass es den Status des Lichts entsprechend anpassen kann.
  • Automatische Abschaltung: Nach mindestens einer Minute ohne erkannte Präsenz schaltet das System das Licht automatisch aus, wodurch unnötiger Energieverbrauch vermieden wird.

Umsetzung mit Blockly

Mit Blockly, einer grafischen Programmiersprache, kannst du diese Automatisierung einfach umsetzen. Sie ermöglicht es, logische Abläufe visuell zu gestalten und dabei flexibel anzupassen.

1. Ereignisauslöser für Türkontakt und Präsenzmelder

Der Blockly-Block on überwacht Änderungen bei den beiden Sensoren, sodass das System sofort reagieren kann:

&lt;value name="OID0">
  &lt;field name="oid">Präsenzmelder.Presence&lt;/field>
&lt;/value>
&lt;value name="OID1">
  &lt;field name="oid">Türkontakt.Open&lt;/field>
&lt;/value>

Erkennt einer der Sensoren eine Änderung, speichert das System den aktuellen Timestamp und schaltet das Licht ein, um sofort für die nötige Beleuchtung zu sorgen:

&lt;block type="control">
  &lt;field name="OID">Licht_Schalter&lt;/field>
  &lt;value name="VALUE">
    &lt;block type="logic_boolean">
      &lt;field name="BOOL">TRUE&lt;/field>
    &lt;/block>
  &lt;/value>
&lt;/block>

2. Automatische Abschaltung nach Inaktivität

Das System überprüft alle 30 Sekunden, ob noch Aktivität vorliegt, und berücksichtigt dabei den zuletzt gespeicherten Timestamp:

&lt;block type="schedule">
  &lt;field name="SCHEDULE">*/30 * * * * *&lt;/field>
&lt;/block>

Wenn das Licht an ist, aber keine Bewegung erkannt wird, vergleicht das System den aktuellen Zeitpunkt mit dem gespeicherten Timestamp. Dadurch erkennt es, ob das Licht weiterhin benötigt wird:

&lt;block type="logic_compare">
  &lt;field name="OP">LT&lt;/field>
  &lt;value name="A">
    &lt;block type="get_value">
      &lt;field name="OID">timestamp&lt;/field>
    &lt;/block>
  &lt;/value>
  &lt;value name="B">
    &lt;block type="math_arithmetic">
      &lt;field name="OP">MINUS&lt;/field>
      &lt;value name="A">
        &lt;block type="time_get">
          &lt;field name="OPTION">current_time&lt;/field>
        &lt;/block>
      &lt;/value>
      &lt;value name="B">
        &lt;block type="math_number">
          &lt;field name="NUM">60&lt;/field>
        &lt;/block>
      &lt;/value>
    &lt;/block>
  &lt;/value>
&lt;/block>

Liegt der gespeicherte Zeitpunkt mehr als eine Minute zurück, schaltet das System das Licht aus, um Strom zu sparen und die Umwelt zu schonen:

&lt;block type="control">
  &lt;field name="OID">Licht_Schalter&lt;/field>
  &lt;value name="VALUE">
    &lt;block type="logic_boolean">
      &lt;field name="BOOL">FALSE&lt;/field>
    &lt;/block>
  &lt;/value>
&lt;/block>

Vorteile dieser Lösung

  • Energieeffizient: Das Licht bleibt nur an, wenn es wirklich benötigt wird, wodurch du Stromkosten senken kannst.
  • Benutzerfreundlich: Das Licht schaltet sich automatisch ein und aus, sodass du dich nicht darum kümmern musst.
  • Anpassbar: Du kannst die Wartezeit oder die Sensoren flexibel ändern, damit die Automatisierung perfekt zu deinem Alltag passt.

Erweiterungsmöglichkeiten

  • Passe die Verzögerungszeit an (z. B. 2 oder 5 Minuten statt 1 Minute), um die Automatisierung individueller zu gestalten.
  • Binde zusätzliche Sensoren ein, z. B. für Fenster oder weitere Türen, damit das System noch vielseitiger einsetzbar ist.
  • Füge eine manuelle Steuerung hinzu, um das Licht dauerhaft ein- oder auszuschalten, falls du einmal von der Automatisierung abweichen möchtest.

Hast du eigene Erfahrungen mit Blockly? Teile sie gerne in den Kommentaren!

Vollständiger Blockly Code

Den kompletten Code, zum einfachen importieren, findest du nachfolgend. Denk daran das Variablen, Sensoren und Aktoren angepasst werden müssen.

&lt;xml xmlns="https://developers.google.com/blockly/xml">
  &lt;block type="on_ext" id="event_trigger" x="200" y="-238">
    &lt;mutation items="2">&lt;/mutation>
    &lt;field name="CONDITION">true&lt;/field>
    &lt;field name="ACK_CONDITION">&lt;/field>
    &lt;value name="OID0">
      &lt;shadow type="field_oid" id="sensor_1">
        &lt;field name="oid">alias.0.room.sensors.motion.PRESS&lt;/field>
      &lt;/shadow>
    &lt;/value>
    &lt;value name="OID1">
      &lt;shadow type="field_oid" id="sensor_2">
        &lt;field name="oid">alias.0.room.sensors.door.ACTUAL&lt;/field>
      &lt;/shadow>
    &lt;/value>
    &lt;statement name="STATEMENT">
      &lt;block type="update" id="update_timestamp">
        &lt;mutation delay_input="false">&lt;/mutation>
        &lt;field name="OID">0_userdata.0.room.timestamp&lt;/field>
        &lt;field name="WITH_DELAY">FALSE&lt;/field>
        &lt;value name="VALUE">
          &lt;block type="time_get" id="get_time">
            &lt;mutation format="false" language="false">&lt;/mutation>
            &lt;field name="OPTION">sid&lt;/field>
          &lt;/block>
        &lt;/value>
        &lt;next>
          &lt;block type="controls_if" id="check_light_status">
            &lt;value name="IF0">
              &lt;block type="logic_negate" id="negate_condition">
                &lt;value name="BOOL">
                  &lt;block type="get_value" id="get_light_status">
                    &lt;field name="ATTR">val&lt;/field>
                    &lt;field name="OID">0_userdata.0.room.light_on&lt;/field>
                  &lt;/block>
                &lt;/value>
              &lt;/block>
            &lt;/value>
            &lt;statement name="DO0">
              &lt;block type="comment" id="comment_light">
                &lt;field name="COMMENT">Light Control&lt;/field>
                &lt;next>
                  &lt;block type="control" id="turn_light_on">
                    &lt;mutation delay_input="false">&lt;/mutation>
                    &lt;field name="OID">scene.0.room.light_On&lt;/field>
                    &lt;field name="WITH_DELAY">FALSE&lt;/field>
                    &lt;value name="VALUE">
                      &lt;block type="logic_boolean" id="set_true">
                        &lt;field name="BOOL">TRUE&lt;/field>
                      &lt;/block>
                    &lt;/value>
                    &lt;next>
                      &lt;block type="comment" id="comment_system">
                        &lt;field name="COMMENT">System State&lt;/field>
                        &lt;next>
                          &lt;block type="control" id="update_light_status">
                            &lt;mutation delay_input="false">&lt;/mutation>
                            &lt;field name="OID">0_userdata.0.room.light_on&lt;/field>
                            &lt;field name="WITH_DELAY">FALSE&lt;/field>
                            &lt;value name="VALUE">
                              &lt;block type="logic_boolean" id="set_light_true">
                                &lt;field name="BOOL">TRUE&lt;/field>
                              &lt;/block>
                            &lt;/value>
                          &lt;/block>
                        &lt;/next>
                      &lt;/block>
                    &lt;/next>
                  &lt;/block>
                &lt;/next>
              &lt;/block>
            &lt;/statement>
          &lt;/block>
        &lt;/next>
      &lt;/block>
    &lt;/statement>
    &lt;next>
      &lt;block type="schedule" id="scheduled_check">
        &lt;field name="SCHEDULE">*/30 * * * * *&lt;/field>
        &lt;statement name="STATEMENT">
          &lt;block type="controls_if" id="check_inactivity">
            &lt;mutation elseif="1">&lt;/mutation>
            &lt;value name="IF0">
              &lt;block type="logic_operation" id="check_conditions" inline="false">
                &lt;field name="OP">AND&lt;/field>
                &lt;value name="A">
                  &lt;block type="get_value" id="get_light_status_check">
                    &lt;field name="ATTR">val&lt;/field>
                    &lt;field name="OID">0_userdata.0.room.light_on&lt;/field>
                  &lt;/block>
                &lt;/value>
                &lt;value name="B">
                  &lt;block type="logic_negate" id="negate_motion">
                    &lt;value name="BOOL">
                      &lt;block type="get_value" id="get_motion_status">
                        &lt;field name="ATTR">val&lt;/field>
                        &lt;field name="OID">alias.0.room.sensors.motion.PRESS&lt;/field>
                      &lt;/block>
                    &lt;/value>
                  &lt;/block>
                &lt;/value>
              &lt;/block>
            &lt;/value>
            &lt;statement name="DO0">
              &lt;block type="comment" id="check_timeout">
                &lt;field name="COMMENT">Check if timeout passed&lt;/field>
                &lt;next>
                  &lt;block type="controls_if" id="compare_time">
                    &lt;value name="IF0">
                      &lt;block type="logic_compare" id="time_check">
                        &lt;field name="OP">LT&lt;/field>
                        &lt;value name="A">
                          &lt;block type="get_value" id="get_timestamp">
                            &lt;field name="ATTR">val&lt;/field>
                            &lt;field name="OID">0_userdata.0.room.timestamp&lt;/field>
                          &lt;/block>
                        &lt;/value>
                        &lt;value name="B">
                          &lt;block type="math_arithmetic" id="subtract_time">
                            &lt;field name="OP">MINUS&lt;/field>
                            &lt;value name="A">
                              &lt;block type="time_get" id="current_time">
                                &lt;mutation format="false" language="false">&lt;/mutation>
                                &lt;field name="OPTION">sid&lt;/field>
                              &lt;/block>
                            &lt;/value>
                            &lt;value name="B">
                              &lt;block type="math_number" id="timeout_value">
                                &lt;field name="NUM">60&lt;/field>
                              &lt;/block>
                            &lt;/value>
                          &lt;/block>
                        &lt;/value>
                      &lt;/block>
                    &lt;/value>
                    &lt;statement name="DO0">
                      &lt;block type="comment" id="comment_turn_off">
                        &lt;field name="COMMENT">Turn off light&lt;/field>
                        &lt;next>
                          &lt;block type="control" id="turn_light_off">
                            &lt;mutation delay_input="false">&lt;/mutation>
                            &lt;field name="OID">scene.0.room.light_On&lt;/field>
                            &lt;field name="WITH_DELAY">FALSE&lt;/field>
                            &lt;value name="VALUE">
                              &lt;block type="logic_boolean" id="set_false">
                                &lt;field name="BOOL">FALSE&lt;/field>
                              &lt;/block>
                            &lt;/value>
                            &lt;next>
                              &lt;block type="control" id="update_light_off_status">
                                &lt;mutation delay_input="false">&lt;/mutation>
                                &lt;field name="OID">0_userdata.0.room.light_on&lt;/field>
                                &lt;field name="WITH_DELAY">FALSE&lt;/field>
                                &lt;value name="VALUE">
                                  &lt;block type="logic_boolean" id="set_light_false">
                                    &lt;field name="BOOL">FALSE&lt;/field>
                                  &lt;/block>
                                &lt;/value>
                              &lt;/block>
                            &lt;/next>
                          &lt;/block>
                        &lt;/next>
                      &lt;/block>
                    &lt;/statement>
                  &lt;/block>
                &lt;/next>
              &lt;/block>
            &lt;/statement>
            &lt;value name="IF1">
              &lt;block type="get_value" id="check_motion">
                &lt;field name="ATTR">val&lt;/field>
                &lt;field name="OID">alias.0.room.sensors.motion.PRESS&lt;/field>
              &lt;/block>
            &lt;/value>
            &lt;statement name="DO1">
              &lt;block type="update" id="update_timestamp_on_motion">
                &lt;mutation delay_input="false">&lt;/mutation>
                &lt;field name="OID">0_userdata.0.room.timestamp&lt;/field>
                &lt;field name="WITH_DELAY">FALSE&lt;/field>
                &lt;value name="VALUE">
                  &lt;block type="time_get" id="get_current_time">
                    &lt;mutation format="false" language="false">&lt;/mutation>
                    &lt;field name="OPTION">sid&lt;/field>
                  &lt;/block>
                &lt;/value>
              &lt;/block>
            &lt;/statement>
          &lt;/block>
        &lt;/statement>
      &lt;/block>
    &lt;/next>
  &lt;/block>
&lt;/xml>

Echtes Smart Home – Automatisierung statt Fernbedienung

Wenn von Smart Home die Rede ist, denken viele an eine App auf dem Smartphone, mit der sich Licht, Heizung oder Rollläden steuern lassen. Doch ist das wirklich smart? Nein! Ein echtes Smart Home funktioniert ohne ständigen Eingriff des Nutzers – es denkt mit, reagiert automatisch und nimmt Arbeit ab, anstatt neue hinzuzufügen.

Was macht ein echtes Smart Home aus?

Ein wirklich intelligentes Zuhause erkennt Situationen und Muster, trifft eigenständige Entscheidungen und passt sich dem Alltag an. Es geht nicht darum, dass du dein Licht per App ein- und ausschalten kannst – sondern dass es sich genau dann einschaltet, wenn du es brauchst. Ein echtes Smart Home ist kein glorifiziertes Dashboard, sondern eine unsichtbare, aber spürbare Verbesserung des Wohnkomforts.

Beispiele für ein echtes Smart Home:

  • Rolläden steuern sich von selbst: Statt morgens manuell den Knopf zu drücken oder per App die Rolläden zu öffnen, passiert dies automatisch anhand von Sonnenaufgang, Außentemperatur oder sogar deinem Wecker.
  • Post im Briefkasten? Du wirst informiert: Ein Sensor erkennt, wenn Post eingeworfen wurde, und sendet eine Nachricht – du musst nicht mehr selbst nachsehen.
  • Heizung regelt sich nach Bedarf: Statt die Temperatur über ein Dashboard einzustellen, erkennt das System deine Gewohnheiten, die Wetterlage und ob du zu Hause bist, um optimal zu heizen.
  • Beleuchtung passt sich an: Licht schaltet sich nicht nur automatisch an, wenn du einen Raum betrittst, sondern passt auch Helligkeit und Farbtemperatur der Tageszeit an. Morgens sorgt eine kühle, aktivierende Beleuchtung für einen guten Start, während abends warmes Licht für Entspannung sorgt.
  • Musik und Medien steuern sich automatisch: Dein Smart Home erkennt, wenn du nach Hause kommst, und startet automatisch deine Lieblingsplaylist oder das aktuelle Nachrichten-Update.
  • Sicherheit ohne manuelles Eingreifen: Die Haustür verriegelt sich automatisch, wenn alle das Haus verlassen haben, und Kameras aktivieren sich bei verdächtigen Bewegungen – ohne dass du in einer App herumtippen musst.
  • Wetterabhängige Anpassungen: Wenn es regnet, schließt das System automatisch die Dachfenster. Im Sommer passt es die Außenmarkisen an, um Überhitzung zu vermeiden.
  • Raumklima-Optimierung: Luftqualitätssensoren erkennen einen erhöhten CO₂-Gehalt und aktivieren automatisch die Lüftung oder erinnern ans Lüften.
  • Individuelle Beleuchtung nach Tageszeit: Die Farbtemperatur deiner Lampen ändert sich je nach Sonnenstand – morgens kühles Licht für bessere Konzentration, abends warmes Licht für eine gemütliche Atmosphäre.

Wie kann man ein echtes Smart Home selbst realisieren?

Ein Smart Home, das wirklich autonom arbeitet, kann man mit Open-Source-Lösungen wie ioBroker oder Home Assistant selbst realisieren. Diese Systeme bieten:

  • Vielfältige Integrationen: Unterstützung für Zigbee, Z-Wave, MQTT, KNX, Matter und viele andere Protokolle.
  • Flexible Automatisierungen: Logikgesteuerte Abläufe, die auf Sensoren, Zeitplänen und individuellen Bedingungen basieren.
  • Lokale Verarbeitung: Keine Abhängigkeit von Cloud-Diensten, alles läuft direkt im eigenen Heimnetzwerk.
  • Benutzerdefinierte Dashboards: Falls eine Steuerung notwendig ist, lassen sich individuelle Dashboards erstellen.
  • Sprachsteuerung & KI-Unterstützung: Integration mit Sprachassistenten oder KI-basierten Automatisierungen für noch mehr Komfort.

Was ich selbst benutze:

  • Tado Heizungssteuerung mit Anwesenheitserkennung – Amazon (bezahlter Link)
  • Aqara Bewegungsmelder für Sicherheits- und Automatisierungszwecke – Amazon (bezahlter Link)
  • Philips Hue smarte Beleuchtung mit Farbtemperatur-Anpassung – Amazon (bezahlter Link)
  • Shelly Plus 1PM für smarte Steuerung von Licht und Geräten – Amazont*

Fernsteuerung ist kein Smart Home!

Viele Systeme verkaufen sich als „smart“, weil sie per App oder Sprachsteuerung bedient werden können. Doch das ist keine Automatisierung, sondern nur eine Fernbedienung. Ein echtes Smart Home arbeitet proaktiv, nicht reaktiv.

Stell dir vor, du müsstest jedes Mal dein Smartphone zücken, um das Licht einzuschalten – ist das wirklich besser als ein normaler Lichtschalter? Ein intelligentes Zuhause sollte deine Gewohnheiten erlernen und vorausschauend reagieren, nicht ständig deine Aufmerksamkeit erfordern.

Fazit: Smarte Häuser, nicht smarte Dashboards

Ein Smart Home ist dann wirklich smart, wenn es ohne ständige Bedienung funktioniert. Je weniger du damit interagieren musst, desto besser ist die Automatisierung gelungen. Statt Fernsteuerung sollten Sensoren, Routinen und intelligente Algorithmen das Zuhause steuern – dann wird aus einem vernetzten Haus ein echtes Smart Home.

Wie automatisiert ist dein Zuhause wirklich?

Proxmox auf IPv6 umstellen – wie, warum und die Probleme

Zebra-Avatar vor Serverrack mit IPv6-Adressen – Proxmox IPv6 aktivieren

In diesem Beitrag beschreibe ich meine Erfahrungen bei der Umstellung meines Proxmox-Systems auf IPv6. Dabei gab es einige Herausforderungen, insbesondere im Zusammenhang mit LXC-Containern (CTs), virtuellen Maschinen (VMs) und den Netzwerkeinstellungen meiner Fritzbox (bezahlter Link). Am Ende konnte ich aber fast mein gesamtes Smart Home auf IPv6 umstellen und alle Probleme lösen. Hier sind die wichtigsten Erkenntnisse!


1️⃣ Warum IPv6 für mein Smart Home?

Die Umstellung auf IPv6 bringt viele Vorteile:

  • Keine NAT-Probleme mehr: Geräte sind direkt erreichbar.
  • Zukunftssicherheit: IPv4-Adressen werden immer knapper.
  • Bessere Adressierung: Kein Ärger mehr mit internen Subnetzen.
  • Matter: Matter benötigt zwingen IPv6

Da mein Smart-Home-System stark auf Proxmox, Home Assistant, ioBroker und Zigbee2MQTT setzt, wollte ich sicherstellen, dass alles reibungslos mit IPv6 funktioniert.


2️⃣ Erste Schritte: IPv6 in der Fritzbox aktivieren

Die Fritzbox war bereits auf Dual Stack konfiguriert, aber um sicherzustellen, dass Proxmox und meine VMs wirklich IPv6 nutzen, habe ich folgendes überprüft:

  • DHCPv6-Server aktiviert → IPv6-Adressen werden zugewiesen
  • DNSv6-Server bekannt gegeben → Damit meine Geräte IPv6 bevorzugen
  • Statische IPv6 für wichtige Geräte (z. B. Proxmox-Host)

Ein schneller Test mit https://www.wieistmeineip.de/ipv6-test/ zeigte, dass ich bereits Dual Stack (IPv4 + IPv6) nutzte.


3️⃣ Proxmox auf IPv6 umstellen

3.1 IPv6 für das Hauptnetzwerk aktivieren

Auf dem Proxmox-Host habe ich geprüft, ob eine IPv6-Adresse vorhanden ist:

ip -6 addr show

Die Fritzbox (bezahlter Link) hatte eine globale IPv6-Adresse zugewiesen. Falls das nicht der Fall gewesen wäre, hätte ich stattdessen eine statische IPv6-Adresse konfiguriert.

Dann prüfte ich die IPv6-Route:

ip -6 route show default

Falls keine IPv6-Standardroute vorhanden gewesen wäre, hätte ich sie manuell hinzugefügt:

ip -6 route add default via fe80::1 dev vmbr0

3.2 Netzwerkbrücke vmbr0 anpassen

Die interfaces-Datei wurde so angepasst, dass IPv6-Adressen über SLAAC oder DHCPv6 bezogen werden:

auto vmbr0
iface vmbr0 inet static
    address 192.168.0.19/24 # IP des Proxmox Servers
    gateway 192.168.0.1
    bridge-ports eno1
    bridge-stp off
    bridge-fd 0

iface vmbr0 inet6 auto

Dann wurde das Netzwerk neugestartet:

systemctl restart networking

4️⃣ LXC-Container für IPv6 fit machen

Einige meiner LXC-Container bekamen keine IPv6-Adresse. Das lag daran, dass accept_ra deaktiviert war. Die Lösung:

sysctl -w net.ipv6.conf.all.accept_ra=2
sysctl -w net.ipv6.conf.vmbr0.accept_ra=2

Diese Änderungen wurden dauerhaft gemacht:

echo "net.ipv6.conf.all.accept_ra=2" >> /etc/sysctl.conf
sysctl -p

Falls ein LXC-Container eine statische IPv6-Adresse bekommen sollte, wurde dies in der config-Datei des Containers (/etc/network/interfaces) angepasst.

Beispiel für eine statische IPv6-Adresse:

iface eth0 inet6 static
    address 2003:abcd:1234::100
    netmask 64
    gateway fe80::1

5️⃣ VMs und Smart-Home-Systeme auf IPv6 umstellen

5.1 Home Assistant & ioBroker IPv6-fähig machen

Nach der Proxmox-Umstellung mussten Home Assistant und ioBroker IPv6 nutzen. Ich habe die jeweiligen Netzwerkkonfigurationen geprüft und sichergestellt, dass die Dienste auf IPv6 lauschen.

Ein schneller Test:

ping6 google.com

Falls keine Verbindung bestand, wurde geprüft:

ip -6 route show default

Falls nötig, wurde die IPv6-Route manuell hinzugefügt.


5.2 Zigbee2MQTT und MQTT mit IPv6 nutzen

Da mein MQTT-Server (Mosquitto) jetzt auch IPv6 unterstützt, mussten die Verbindungen angepasst werden. Im Container oder in der VM:

cat /etc/mosquitto/mosquitto.conf

Falls Mosquitto nur auf IPv4 lauschte, wurde das geändert zu:

listener 1883 ::

Dann den Dienst neustarten:

systemctl restart mosquitto

6️⃣ ESP8266-Probleme mit IPv6 & Umstieg auf ESP32

Mein Smart-Home nutzt ESP8266 (bezahlter Link)– und ESP32-Boards (bezahlter Link) für verschiedene Sensoren. Dabei stellte sich heraus, dass ESP8266 (bezahlter Link) kein natives IPv6 unterstützt!

Lösung: Umstieg auf ESP32 (bezahlter Link), der vollständige IPv6-Unterstützung bietet. Dafür musste der Code angepasst werden:

WiFi.enableIPv6();
Serial.println(WiFi.localIPv6());

➡ Danach konnte der ESP32 direkt mit IPv6 arbeiten! 🎉


7️⃣ Fazit: Lohnt sich IPv6 für das Smart Home?

Nach der Umstellung kann ich sagen: Ja!

Mein gesamtes Proxmox-System läuft jetzt mit IPv6Alle LXC-Container & VMs haben funktionierendes IPv6Home Assistant, ioBroker & MQTT arbeiten mit IPv6ESP8266 bleibt problematisch – ESP32 ist die Lösung und danke Matter ist die Umstellung auf IPv& sowieso unumgänglich.

Die Umstellung war nicht ganz trivial, aber hat sich gelohnt! Falls du Fragen hast oder selbst dein Smart Home auf IPv6 umstellen willst, schreib mir gerne einen Kommentar.

Und falls deine Backups Probleme machen, schaue dir auch den Artikel Proxmox Backup failed – was tun? an.

Wenn du tiefer einsteigen willst: Hier geht’s zur ultimativen Proxmox-Einführung.

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 & 3D-Druck-Gehäuse

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