Navigation

    Logo
    • Register
    • Login
    • Search
    • Recent
    • Tags
    • Unread
    • Categories
    • Unreplied
    • Popular
    • GitHub
    • Docu
    • Hilfe
    1. Home
    2. Deutsch
    3. Praktische Anwendungen (Showcase)
    4. Einfache Solarleistungsanzeige

    NEWS

    • Monatsrückblick – September 2025

    • Neues Video "KI im Smart Home" - ioBroker plus n8n

    • Neues Video über Aliase, virtuelle Geräte und Kategorien

    Einfache Solarleistungsanzeige

    This topic has been deleted. Only users with topic management privileges can see it.
    • B
      Beowolf last edited by Beowolf

      Ich habe mal eine einfache Anzeige der derzeitigen Solarleistung gebastelt. Meine Frau hatte immer gefragt, ob jetzt ein guter Zeitpunkt sei um dieses oder jenes laufen zu lassen. Also Waschmaschine, Kochen usw..

      Die Werte holt sich das Skript von OpenDTU und einem Hichi Lesekopf.

      Programmübersicht
      Dieses Programm läuft auf einem ESP8266-Mikrocontroller (z. B. NodeMCU oder Wemos D1 mini) und dient als Energie-Monitoring- und Anzeige-System.
      Es verbindet sich mit einem WLAN, liest Energiedaten von zwei Quellen (OpenDTU und HiChi/Tasmota-Stromzähler), zeigt die Werte auf einem TFT-Display an und stellt zusätzlich ein Web-Dashboard mit Live-Daten zur Verfügung.
      Über eine Weboberfläche können Einstellungen zur Display-Hintergrundbeleuchtung (An/Aus/Dimmen) vorgenommen und im EEPROM dauerhaft gespeichert werden.
      Zusätzlich unterstützt das Programm Over-the-Air Updates (OTA) und mDNS, damit das Gerät leicht im Netzwerk auffindbar ist.


      Hauptfunktionen des Programms

      1. WLAN-Verbindung
        • Das Gerät verbindet sich mit den im Code hinterlegten WLAN-Zugangsdaten (ssid, password).
        • Nach erfolgreicher Verbindung ist es über die lokale IP-Adresse erreichbar.
        • Die IP wird beim Start für kurze Zeit auf dem TFT-Display angezeigt.

      1. Zeitverwaltung
        • Die lokale Zeit wird über NTP-Server synchronisiert (pool.ntp.org, time.nist.gov).
        • Die Funktion isDST() berücksichtigt Sommerzeit (DST) und stellt sicher, dass die Uhrzeit korrekt angezeigt und für Beleuchtungssteuerung verwendet wird.

      1. Hintergrundbeleuchtung des Displays
        • Die TFT-Hintergrundbeleuchtung wird je nach Uhrzeit automatisch gesteuert:
        o Voll an: tagsüber
        o Gedimmt: ab einer festgelegten Uhrzeit am Abend
        o Aus: nachts
        • Diese Zeiten sind in Variablen gespeichert (stunde_an, stunde_dimmen, stunde_aus) und können über die Weboberfläche geändert werden.
        • Werte werden im EEPROM gespeichert, damit sie auch nach einem Neustart erhalten bleiben.

      1. Anzeige auf dem TFT-Display
        • Das Display zeigt nach dem Start zunächst die IP-Adresse für 5 Sekunden.
        • Danach wird ein Hauptbildschirm angezeigt:
        o Solarleistung (Watt) von der OpenDTU
        o Netzleistung (Watt) vom Zähler
        o Einspeisung (kWh)
        o Bezug (kWh)
        • Unten wird ein farbiges Feld angezeigt:
        o Rot mit "Netzbezug", wenn Strom aus dem Netz gezogen wird.
        o Grün mit "Einspeisung", wenn Strom ins Netz eingespeist wird.

      1. Abruf von Energiedaten
        • OpenDTU-API (opendtu_url): liefert die aktuelle Solarleistung.
        • HiChi/Tasmota-Zähler (hichi_url): liefert Netzleistung, Einspeisung und Bezug.
        • Die Abfrage erfolgt regelmäßig alle 5 Sekunden (updateInterval).
        • Daten werden per HTTP GET abgerufen und mit ArduinoJson geparst.

      1. Webserver-Funktionen
        Das Programm startet einen lokalen Webserver (Port 80), erreichbar über die IP-Adresse oder per mDNS („esp.local“).
        • / (Root):
        Dashboard-Seite im Browser mit Anzeige von Solar, Netz, Einspeisung, Bezug und der aktuellen Uhrzeit.
        Die Daten werden per AJAX (fetch /data) alle 5 Sekunden aktualisiert.
        • /data:
        Liefert die aktuellen Messwerte als JSON (für die Live-Aktualisierung im Browser).
        • /set:
        Ermöglicht das Ändern der Zeiten für An, Dimmen, Aus und des Dimmwertes.
        Werte werden geprüft, gespeichert und ins EEPROM geschrieben.

      1. OTA-Updates
        • Mit ArduinoOTA kann das Programm im Netzwerk neu geflasht werden, ohne den ESP8266 über USB anschließen zu müssen.
        • Das Gerät ist durch Hostname und Passwort abgesichert.

      Ablauf im loop()
      Die Endlosschleife (loop()) kümmert sich um:

      1. Abwicklung von OTA-Updates (ArduinoOTA.handle()).
      2. Beantwortung von Webserver-Anfragen (server.handleClient()).
      3. Anzeige der IP-Adresse beim Start für 5 Sekunden.
      4. Regelmäßige Abfrage der Energiedaten (alle 5 Sekunden).
      5. Aktualisierung der TFT-Anzeige mit den neuen Werten.
      6. Steuerung der Hintergrundbeleuchtung in Abhängigkeit von Uhrzeit und gespeicherten Einstellungen.

      Zusammenfassung
      Das Programm realisiert ein komplettes Energie-Monitoring-System mit:
      • Automatischer Abfrage von Solar- und Netzleistungen.
      • Anzeige der Werte auf einem TFT-Farbdisplay.
      • Steuerung der Display-Beleuchtung nach Uhrzeit.
      • Bereitstellung einer Weboberfläche mit Live-Daten und Einstellmöglichkeiten.
      • Speicherung von Benutzer-Einstellungen im EEPROM.
      • OTA-Update-Funktion für einfache Wartung.

      Skript:

      /*
        Ausführlich kommentierte Version des bereitgestellten ESP8266-Skripts.
        Dieses Skript verbindet sich mit einem WLAN, holt LIVE-Daten von zwei HTTP-APIs,
        zeigt Werte auf einem TFT-Display an, bietet ein kleines Web-Dashboard zum Lesen
        und Einstellen von Hintergrundlicht-Zeiten und unterstützt OTA-Updates.
      */
      
      #include <ESP8266WiFi.h>        // WiFi-Funktionalität für den ESP8266
      #include <ESP8266HTTPClient.h>  // HTTP-Client, um GET/POST-Requests zu machen
      #include <ESP8266WebServer.h>   // Einfacher HTTP-Server auf dem Gerät
      #include <ESP8266mDNS.h>        // mDNS (Multicast DNS) zur Namensauflösung im LAN
      #include <TFT_eSPI.h>           // Treiber für das TFT-Display (ILI9341/ILI9488 etc.)
      #include <ArduinoOTA.h>         // Over-the-Air Firmware-Updates (OTA)
      #include <ArduinoJson.h>        // JSON-Parsing für Antworten der APIs
      #include <time.h>               // Zeitfunktionen (time_t, struct tm, localtime...)
      #include <EEPROM.h>             // EEPROM zum dauerhaften Speichern kleiner Einstellungen
      
      #define EEPROM_SIZE 64         // Größe des verwendeten EEPROM-Bereichs
      
      // ---------- Netzwerk- und Service-Konfiguration (statisch im Sketch) ----------
      const char* ssid = "xxxxx";                  // WLAN-SSID
      const char* password = "xxxxxxxxxxxxxxx"; // WLAN-Passwort
      
      // URLs und Zugangsdaten für die entfernten Dienste
      const char* opendtu_url = "http://192.168.xxx.xxx/api/livedata/status"; // API 1
      const char* opendtu_user = "xxxxxxxxxx";                                      // Basic-Auth Benutzer
      const char* opendtu_password = "xxxxxxxxxxxxx";                         // Basic-Auth Passwort
      
      const char* hichi_url = "http://192.168.xxx.xxx/cm?cmnd=status%208";     // API 2 (z.B. Tasmota-Gerät)
      
      // OTA (Over-The-Air) Einstellungen
      String otaHostname = "ESP-Solar-1"; // Hostname für OTA
      String otaPassword = "xxxxx";      // Passwort für OTA (einfacher Schutz)
      
      // Objekte für Netzwerk, HTTP, Display und Webserver
      WiFiClient client;                     // Client für HTTP-Verbindungen
      HTTPClient http;                       // HTTP-Client-Wrapper
      TFT_eSPI tft = TFT_eSPI();             // TFT-Display-Objekt (Bibliothek TFT_eSPI)
      ESP8266WebServer server(80);           // HTTP-Server lauscht auf Port 80
      
      // ---------- Anzuzeigende Werte (globale Variablen) ----------
      float solarPower = 0;  // Aktuelle Solarleistung (W)
      float gridPower = 0;   // Aktuelle Netzleistung (positiv: Bezug, negativ: Einspeisung) (W)
      float einspeisung = 0; // kWh Ausgehende Energie (Einspeisung)
      float bezug = 0;       // kWh Eingehende Energie (Bezug)
      
      // Timing / Update-Intervall
      unsigned long lastUpdate = 0;                  // letzer Zeitstempel der Aktualisierung (millis)
      const unsigned long updateInterval = 5000;    // Aktualisierungsintervall in ms (5s)
      
      // Hintergrundbeleuchtung / PWM
      const int backlightPin = 3;                    // Pin für Hintergrundbeleuchtung (hier RX/PWM)
      int brightness = 1023;                         // Voller PWM-Wert (für analoge Ausgabe auf ESP8266)
      int brightnessDim = 200;                       // Gedimmter PWM-Wert (kann per Webinterface gesetzt werden)
      
      // Zeitpunkte für Hintergrundbeleuchtung (jeweils hh/mm)
      int stunde_an = 7;    // Zeitpunkt: Hintergrundlicht an (Stunde)
      int minute_an = 0;    // Zeitpunkt: Hintergrundlicht an (Minute)
      int stunde_dimmen = 21; // Ab wann dimmen
      int minute_dimmen = 0;
      int stunde_aus = 23;  // Ab wann ganz aus
      int minute_aus = 0;
      
      // UI/IP Anzeige Steuerung
      bool showIP = true;                           // Flag: zeigt beim Start die IP-Adresse an
      unsigned long ipDisplayStart = 0;             // millis() Zeitpunkt des Starts der IP-Anzeige
      const unsigned long ipDisplayDuration = 5000; // Zeitdauer der IP-Anzeige in ms (5s)
      
      // ------------------ Hilfsfunktionen für Zeitvergleiche ------------------
      
      // Vergleicht zwei Zeiten (h1:m1) >= (h2:m2)
      // Rückgabe: true, wenn (h1:m1) gleich oder nach (h2:m2) liegt.
      bool isTimeAfter(int h1, int m1, int h2, int m2) {
        if (h1 > h2) return true;
        if (h1 == h2 && m1 >= m2) return true;
        return false;
      }
      
      // Vergleicht zwei Zeiten (h1:m1) < (h2:m2)
      // Rückgabe: true, wenn (h1:m1) strikt vor (h2:m2) liegt.
      bool isTimeBefore(int h1, int m1, int h2, int m2) {
        if (h1 < h2) return true;
        if (h1 == h2 && m1 < m2) return true;
        return false;
      }
      
      // DST (Daylight Saving Time) Bestimmung für Mitteleuropa (MEZ/CEST)
      // Die Funktion prüft, ob die gegebene lokale Zeit in der Sommerzeit (DST) liegt.
      // Implementierung orientiert sich an: DST ist von letztem Sonntag im März (02:00) bis
      // letztem Sonntag im Oktober (03:00) gültig. Hier wird aus tm-Struktur (lokale Zeit)
      // entschieden. (Die Berechnung verwendet tm_mon, tm_mday, tm_wday, tm_hour.)
      bool isDST(struct tm* timeinfo) {
        int month = timeinfo->tm_mon + 1; // tm_mon: 0-11, darum +1
        int day = timeinfo->tm_mday;
        int wday = timeinfo->tm_wday;     // 0=Sonntag..6=Samstag
        int hour = timeinfo->tm_hour;
      
        // Monate außerhalb April-Oktober sind sicher keine DST-Monate
        if (month < 3 || month > 10) return false; // Jan, Feb, Nov, Dez -> kein DST
      
        // Apr-Sep sind immer DST
        if (month > 3 && month < 10) return true;  // Apr bis Sep -> DST
      
        // März: DST beginnt am letzten Sonntag im März um 2:00 Uhr
        if (month == 3) {
          // Berechnung des letzten Sonntags im Monat
          int lastSunday = 31 - ((wday + (31 - day)) % 7);
          if ((day > lastSunday) || (day == lastSunday && hour >= 2)) return true;
          else return false;
        }
      
        // Oktober: DST endet am letzten Sonntag im Oktober um 3:00 Uhr
        if (month == 10) {
          int lastSunday = 31 - ((wday + (31 - day)) % 7);
          if ((day < lastSunday) || (day == lastSunday && hour < 3)) return true;
          else return false;
        }
      
        return false; // Fallback
      }
      
      // ------------------ Anzeige-Funktionen (TFT) ------------------
      
      // Zeichnet einen einfachen Rahmen / Layout-Linien auf dem Display
      void drawFrame() {
        tft.drawRect(0, 0, tft.width(), tft.height(), TFT_WHITE);
        tft.drawLine(0, 28, tft.width(), 28, TFT_WHITE);   // obere Trennlinie
        tft.drawLine(0, 160, tft.width(), 160, TFT_WHITE); // mittlere Trennlinie
      }
      
      // Setzt die Hintergrundbeleuchtung je nach eingestellten Zeiten
      // - während der "aus"-Periode wird auf 0 gesetzt (aus)
      // - während der "dimmen"-Periode wird brightnessDim gesetzt
      // - sonst volle Helligkeit (brightness)
      void updateBacklight() {
        time_t now = time(nullptr);
        struct tm* timeinfo = localtime(&now);
        int curr_h = timeinfo->tm_hour;
        int curr_m = timeinfo->tm_min;
      
        // Aus: wenn aktuelle Zeit >= stunde_aus oder < stunde_an
        if (isTimeAfter(curr_h, curr_m, stunde_aus, minute_aus) || isTimeBefore(curr_h, curr_m, stunde_an, minute_an)) {
          analogWrite(backlightPin, 0); // ganz aus
        }
        // Dimmen: wenn aktuelle Zeit >= stunde_dimmen
        else if (isTimeAfter(curr_h, curr_m, stunde_dimmen, minute_dimmen)) {
          analogWrite(backlightPin, brightnessDim); // gedimmt
        }
        // Voll an: sonst
        else {
          analogWrite(backlightPin, brightness); // volle Helligkeit
        }
      }
      
      // Zeigt bei Start die IP-Adresse mittig auf dem Bildschirm an
      void showIPOnDisplay() {
        tft.fillScreen(TFT_BLACK);
        tft.setTextColor(TFT_YELLOW);
        tft.setTextDatum(MC_DATUM); // zentriert
        tft.setTextSize(3);
        tft.drawString("IP-Adresse:", tft.width() / 2, tft.height() / 3);
        tft.drawString(WiFi.localIP().toString(), tft.width() / 2, tft.height() / 2);
      }
      
      // Initiale (leere) Hauptanzeige (wird später bei Updates gefüllt)
      void showMainDisplay() {
        tft.fillScreen(TFT_BLACK);
        drawFrame();
        tft.setTextSize(2);
        tft.setTextColor(TFT_YELLOW);
        tft.setTextDatum(MC_DATUM);
        tft.drawString("Energieanzeige", tft.width() / 2, 14);
        // Die numerischen Werte werden regelmäßig in loop() gezeichnet/aktualisiert
      }
      
      // ------------------ Webserver-Handler ------------------
      
      // Handler für die Root-Seite: baut ein HTML-Dashboard als String zusammen
      // - Verwendet ein Raw-String-Literal (R"rawliteral(... )rawliteral") für das HTML-Template
      // - Fügt dynamische Werte (Hostname, Zeit, Messwerte, Formulareingaben) ein
      void handleRoot() {
        time_t now = time(nullptr);
        struct tm* timeinfo = localtime(&now);
        char timeStr[30];
        strftime(timeStr, sizeof(timeStr), "%d.%m.%Y %H:%M:%S", timeinfo);
      
        // Der HTML-Block ist ein Raw-String-Literal, damit Anführungszeichen im HTML nicht
        // escaped werden müssen. Danach werden dynamische Teile (Hostname, Werte, Formulare)
        // als String-Konkatenationen angehängt.
        String html = R"rawliteral(
      <!DOCTYPE html>
      <html lang="de">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>ESP Energie Dashboard</title>
        <style>
          /* Einfache CSS-Stile für ein dunkel gehaltenes Dashboard */
          body { background-color: #0b1120; color: #e2e8f0; font-family: "Segoe UI", sans-serif; margin: 0; padding: 20px; box-sizing: border-box; }
          h1 { text-align: center; color: #facc15; font-size: 2em; margin-bottom: 5px; }
          .subtitle { text-align: center; font-size: 0.9em; color: #94a3b8; margin-bottom: 20px; }
          .card { background-color: #1e293b; padding: 20px; margin: 20px auto; border-radius: 16px; max-width: 500px; width: 100%; box-shadow: 0 0 10px rgba(255, 255, 255, 0.05); }
          .label { color: #94a3b8; font-size: 0.9em; margin-top: 10px; }
          .value { font-size: 1.3em; margin-bottom: 12px; color: #f1f5f9; }
          form { display: flex; flex-direction: column; }
          form input[type='text'], form input[type='number'] { width: 100%; padding: 8px; margin: 6px 0; border-radius: 4px; border: none; background-color: #334155; color: white; box-sizing: border-box; }
          form input[type='submit'] { background-color: #4ade80; padding: 10px; margin-top: 12px; cursor: pointer; font-weight: bold; border: none; border-radius: 6px; transition: background 0.2s; }
          form input[type='submit']:hover { background-color: #22c55e; }
          @media (max-width: 600px) { h1 { font-size: 1.5em; } .card { margin: 10px; padding: 15px; } .value { font-size: 1.1em; } .label { font-size: 0.8em; } }
        </style>
        <script>
          // JavaScript-Funktion, die /data periodisch abfragt und Felder im HTML aktualisiert
          function updateData() {
            fetch('/data')
              .then(response => response.json())
              .then(data => {
                document.getElementById('zeit').textContent = data.zeit;
                document.getElementById('solar').textContent = data.solar + " W";
                document.getElementById('netz').textContent = data.netz + " W";
                document.getElementById('einspeisung').textContent = data.einspeisung + " kWh";
                document.getElementById('bezug').textContent = data.bezug + " kWh";
              })
              .catch(err => console.error("Fehler beim Datenabruf:", err));
          }
          setInterval(updateData, 5000); // alle 5 Sekunden aktualisieren
          window.onload = updateData;
        </script>
      </head>
      <body>
        <h1>ESP Energie Dashboard</h1>
        <div class="subtitle">Gerätename: )rawliteral";
      
        // Hier wird der Gerätename (OTA-Hostname) eingefügt
        html += otaHostname;
        html += R"rawliteral(</div>
        <div class="card">
          <div class="label">Zeit:</div>
          <div class="value" id="zeit">)rawliteral";
      
        // Aktuelle Zeit (Server-seitig) als initialer Wert
        html += String(timeStr);
        html += R"rawliteral(</div>
          <div class="label">Solar:</div>
          <div class="value" id="solar">)rawliteral";
      
        // Aktuelle Solarleistung als initialer Wert
        html += String(solarPower);
        html += R"rawliteral( W</div>
          <div class="label">Netz:</div>
          <div class="value" id="netz">)rawliteral";
      
        // Aktuelle Netzleistung
        html += String(gridPower);
        html += R"rawliteral( W</div>
          <div class="label">Einspeisung:</div>
          <div class="value" id="einspeisung">)rawliteral";
      
        // Aktuelle Einspeisung (kWh)
        html += String(einspeisung);
        html += R"rawliteral( kWh</div>
          <div class="label">Bezug:</div>
          <div class="value" id="bezug">)rawliteral";
      
        // Aktueller Bezug (kWh)
        html += String(bezug);
        html += R"rawliteral( kWh</div>
        </div>
      
        <div class="card">
          <form method='POST' action='/set'>
            <div class="label">Hintergrundlicht an (hh:mm):</div>
        )rawliteral";
      
        // Formularfelder: Werte werden aus den gespeicherten Variablen eingefügt
        html += "<input name='an_h' value=\"" + String(stunde_an) + "\" type='number' min='0' max='23'> : ";
        html += "<input name='an_m' value=\"" + String(minute_an) + "\" type='number' min='0' max='59'><br>";
        html += "<div class='label'>Dimmen ab (hh:mm):</div>";
        html += "<input name='dimmen_h' value=\"" + String(stunde_dimmen) + "\" type='number' min='0' max='23'> : ";
        html += "<input name='dimmen_m' value=\"" + String(minute_dimmen) + "\" type='number' min='0' max='59'><br>";
        html += "<div class='label'>Aus ab (hh:mm):</div>";
        html += "<input name='aus_h' value=\"" + String(stunde_aus) + "\" type='number' min='0' max='23'> : ";
        html += "<input name='aus_m' value=\"" + String(minute_aus) + "\" type='number' min='0' max='59'><br>";
        html += "<div class='label'>Dimmwert (0–1023):</div>";
        html += "<input name='dimValue' value=\"" + String(brightnessDim) + "\" type='number' min='0' max='1023'><br>";
        html += "<input type='submit' value='Speichern'>";
        html += "</form></div></body></html>";
      
        // Antwort an den Browser
        server.send(200, "text/html", html);
      }
      
      // Handler für das Formular /set (POST)
      // Liest die Formularfelder, validiert sie grob und schreibt sie ins EEPROM
      void handleSet() {
        if (server.method() == HTTP_POST) {
          // Formulardaten einlesen
          int an_h = server.arg("an_h").toInt();
          int an_m = server.arg("an_m").toInt();
          int dimmen_h = server.arg("dimmen_h").toInt();
          int dimmen_m = server.arg("dimmen_m").toInt();
          int aus_h = server.arg("aus_h").toInt();
          int aus_m = server.arg("aus_m").toInt();
          int dimValue = server.arg("dimValue").toInt();
      
          // Einfache Validierungschecks (nur Bereichsprüfung)
          if (an_h >= 0 && an_h < 24) stunde_an = an_h;
          if (an_m >= 0 && an_m < 60) minute_an = an_m;
          if (dimmen_h >= 0 && dimmen_h < 24) stunde_dimmen = dimmen_h;
          if (dimmen_m >= 0 && dimmen_m < 60) minute_dimmen = dimmen_m;
          if (aus_h >= 0 && aus_h < 24) stunde_aus = aus_h;
          if (aus_m >= 0 && aus_m < 60) minute_aus = aus_m;
          if (dimValue >= 0 && dimValue <= 1023) brightnessDim = dimValue;
      
          // Persistente Speicherung: einzelne Bytes ins EEPROM schreiben
          EEPROM.write(0, stunde_an);
          EEPROM.write(1, minute_an);
          EEPROM.write(2, stunde_dimmen);
          EEPROM.write(3, minute_dimmen);
          EEPROM.write(4, stunde_aus);
          EEPROM.write(5, minute_aus);
          EEPROM.write(6, brightnessDim & 0xFF);          // LSB des 16-bit Werts
          EEPROM.write(7, (brightnessDim >> 8) & 0xFF);   // MSB
          EEPROM.commit(); // Wichtig: Änderungen wirklich speichern
      
          // Bestätigungsseite zurück an den Browser
          String html = "<html><body style='font-family:sans-serif; background:#0b1120; color:#e2e8f0; padding:20px;'>";
          html += "<h2>✅ Einstellungen gespeichert</h2>";
          html += "<p><a href=\"/\">Zurück zum Dashboard</a></p>";
          html += "</body></html>";
          server.send(200, "text/html", html);
        } else {
          // Nur POST ist erlaubt
          server.send(405, "text/plain", "Method Not Allowed");
        }
      }
      
      // ------------------ Setup-Funktion ------------------
      void setup() {
        Serial.begin(115200); // Serielle Ausgabe für Debug
      
        // PWM / Backlight Pin vorbereiten
        pinMode(backlightPin, OUTPUT);
        analogWriteFreq(500); // PWM-Frequenz setzen (ESP8266 spezifisch)
      
        // Mit WLAN verbinden
        WiFi.begin(ssid, password);
        while (WiFi.status() != WL_CONNECTED) delay(500); // Blockiert bis Verbindung steht
      
        // Zeitkonfiguration: Zeitzone und NTP-Server
        // configTzTime nutzt die POSIX-Zeitzonenbeschreibung. Hier CET/CEST wird gesetzt.
        configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "pool.ntp.org", "time.nist.gov");
      
        // OTA initialisieren
        ArduinoOTA.setHostname(otaHostname.c_str());
        ArduinoOTA.setPassword(otaPassword.c_str());
        ArduinoOTA.begin();
      
        // TFT initialisieren
        tft.init();
        tft.invertDisplay(1); // Manche Displays benötigen invertiert beim ersten Einschalten
        tft.setRotation(3);   // Ausrichtung des Displays
      
        // IP-Anzeige beim Start initialisieren
        showIP = true;
        ipDisplayStart = millis();
      
        // mDNS: Gerät im lokalen Netzwerk unter "esp.local" verfügbar machen (falls mdns funktioniert)
        if (MDNS.begin("esp")) MDNS.addService("http", "tcp", 80);
      
        // Webserver Routen registrieren
        server.on("/", handleRoot);
        server.on("/set", HTTP_POST, handleSet);
        server.on("/data", handleData);
        server.begin();
      
        // Background initial: Hintergrundbeleuchtung auf vollen Wert setzen
        pinMode(backlightPin, OUTPUT);
        analogWrite(backlightPin, brightness);
      
        // EEPROM initialisieren und gespeicherte Einstellungen laden
        EEPROM.begin(EEPROM_SIZE);
        stunde_an = EEPROM.read(0);
        minute_an = EEPROM.read(1);
        stunde_dimmen = EEPROM.read(2);
        minute_dimmen = EEPROM.read(3);
        stunde_aus = EEPROM.read(4);
        minute_aus = EEPROM.read(5);
        brightnessDim = EEPROM.read(6) + (EEPROM.read(7) << 8); // 16-bit Wert aus zwei Bytes zusammensetzen
      
        // Gültigkeit der geladenen Werte prüfen, ggf. Standardwerte setzen
        bool invalid = false;
        if (stunde_an > 23) { stunde_an = 7; invalid = true; }
        if (minute_an > 59) { minute_an = 0; invalid = true; }
        if (stunde_dimmen > 23) { stunde_dimmen = 21; invalid = true; }
        if (minute_dimmen > 59) { minute_dimmen = 0; invalid = true; }
        if (stunde_aus > 23) { stunde_aus = 23; invalid = true; }
        if (minute_aus > 59) { minute_aus = 0; invalid = true; }
        if (brightnessDim > 1023) { brightnessDim = 200; invalid = true; }
      
        // Wenn ungültige Werte gefunden wurden, die korrigierten Standardwerte zurückschreiben
        if (invalid) {
          EEPROM.write(0, stunde_an);
          EEPROM.write(1, minute_an);
          EEPROM.write(2, stunde_dimmen);
          EEPROM.write(3, minute_dimmen);
          EEPROM.write(4, stunde_aus);
          EEPROM.write(5, minute_aus);
          EEPROM.write(6, brightnessDim & 0xFF);
          EEPROM.write(7, (brightnessDim >> 8) & 0xFF);
          EEPROM.commit();
        }
      }
      
      // ------------------ /data Handler: liefert JSON mit den aktuellen Werten ------------------
      void handleData() {
        time_t now = time(nullptr);
        struct tm* timeinfo = localtime(&now);
        char timeStr[30];
        strftime(timeStr, sizeof(timeStr), "%d.%m.%Y %H:%M:%S", timeinfo);
      
        // Einfacher JSON-String (manuell zusammengesetzt) mit den aktuellen Werten
        String json = "{";
        json += "\"zeit\":\"" + String(timeStr) + "\",";
        json += "\"solar\":" + String(solarPower) + ",";
        json += "\"netz\":" + String(gridPower) + ",";
        json += "\"einspeisung\":" + String(einspeisung) + ",";
        json += "\"bezug\":" + String(bezug);
        json += "}";
      
        server.send(200, "application/json", json);
      }
      
      // ------------------ Hauptschleife ------------------
      void loop() {
        ArduinoOTA.handle();   // OTA-Ereignisse verarbeiten (falls ein Update hereinkommt)
        server.handleClient(); // HTTP-Server-Anfragen verarbeiten
      
        // Beim Start die IP ein paar Sekunden zeigen
        if (showIP) {
          showIPOnDisplay();
          delay(5000); // 5 Sekunden warten während die IP angezeigt wird (blockierend)
          if (millis() - ipDisplayStart > ipDisplayDuration) {
            showIP = false;
            showMainDisplay();
          }
        } else {
          // Hintergrundbeleuchtung anpassen (je nach aktuellem Zeitpunkt)
          updateBacklight();
      
          unsigned long now = millis();
          if (now - lastUpdate > updateInterval) {
            lastUpdate = now;
      
            // Nur wenn WLAN verbunden ist, Daten abfragen
            if (WiFi.status() == WL_CONNECTED) {
              // --- 1) Anfrage an opendtu_url ---
              http.begin(client, opendtu_url);
              http.setAuthorization(opendtu_user, opendtu_password); // Basic Auth
              int httpCode = http.GET();
              if (httpCode == 200) {
                // JSON-Antwort parsen
                StaticJsonDocument<2048> doc;
                DeserializationError err = deserializeJson(doc, http.getString());
                if (!err)
                  solarPower = doc["total"]["Power"]["v"] | 0.0; // Pfad gemäß API (falls vorhanden)
              }
              http.end(); // Verbindung schließen
      
              // --- 2) Anfrage an hichi_url (z.B. Tasmota Status) ---
              http.begin(client, hichi_url);
              httpCode = http.GET();
              if (httpCode == 200) {
                StaticJsonDocument<1024> doc;
                DeserializationError err = deserializeJson(doc, http.getString());
                if (!err) {
                  // Werte (Pfad entsprechend der Tasmota-Status-Antwort)
                  gridPower = doc["StatusSNS"]["Zaehler"]["Power"] | 0.0;
                  einspeisung = doc["StatusSNS"]["Zaehler"]["E_out"] | 0.0;
                  bezug = doc["StatusSNS"]["Zaehler"]["E_in"] | 0.0;
                }
              }
              http.end();
      
              // ------------------ TFT-Display aktualisieren ------------------
              tft.fillRect(0, 30, tft.width(), 130, TFT_BLACK); // Bereich löschen
      
              // Solarleistung: große Schrift
              tft.setTextSize(3);
              tft.setTextColor(TFT_GREEN);
              tft.setTextDatum(MC_DATUM);
              char solarText[30];
              snprintf(solarText, sizeof(solarText), "Solar: %.0f W", solarPower);
              tft.drawString(solarText, tft.width() / 2, 50);
      
              // Einspeisung / Ausgang (kWh)
              tft.setTextSize(2);
              tft.setTextColor(TFT_LIGHTGREY);
              char einspeisungText[30];
              snprintf(einspeisungText, sizeof(einspeisungText), "Ausgang: %.2f kWh", einspeisung);
              tft.drawString(einspeisungText, tft.width() / 2, 80);
      
              // Netzleistung: Farbe abhängig vom Vorzeichen
              tft.setTextSize(3);
              uint16_t netzfarbe = (gridPower >= 0) ? TFT_RED : TFT_GREEN; // positiv=rot (Bezug), negativ=grün (Einspeisung)
              tft.setTextColor(netzfarbe);
              char gridText[30];
              snprintf(gridText, sizeof(gridText), "Netz: %.0f W", gridPower);
              tft.drawString(gridText, tft.width() / 2, 110);
      
              // Bezug (kWh)
              tft.setTextSize(2);
              tft.setTextColor(TFT_LIGHTGREY);
              char bezugText[30];
              snprintf(bezugText, sizeof(bezugText), "Eingang: %.2f kWh", bezug);
              tft.drawString(bezugText, tft.width() / 2, 140);
      
              // Unterer Balken: färbt die Fläche nach Netzstatus und schreibt Text
              uint16_t bottomColor = gridPower >= 0 ? TFT_RED : TFT_GREEN;
              tft.fillRect(0, 161, tft.width(), tft.height() - 161, bottomColor);
              tft.setTextSize(3);
              tft.setTextColor(TFT_BLACK);
              const char* statusText = gridPower >= 0 ? "Netzbezug" : "Einspeisung";
              int centerY = 161 + (tft.height() - 161) / 2;
              tft.drawString(statusText, tft.width() / 2, centerY);
            }
          }
        }
      }
      

      Technische Dokumentation – ESP8266 Energieanzeige

      1. Einleitung
        Das Projekt implementiert ein Energie-Monitoring-System auf einem ESP8266-Mikrocontroller.
        Es verbindet sich mit dem WLAN, ruft Energiedaten von einer OpenDTU und einem Tasmota-basierten Stromzähler ab, zeigt die Werte auf einem TFT-Display an und stellt zusätzlich ein Web-Dashboard zur Verfügung.
        Die Benutzer können Einstellungen für die Display-Hintergrundbeleuchtung über eine Weboberfläche vornehmen und im EEPROM speichern.
        Zusätzlich unterstützt das System Over-the-Air Updates (OTA) und ist per mDNS im Netzwerk auffindbar.

      1. Systemübersicht
        Hardware
        • ESP8266 (z. B. NodeMCU / Wemos D1 mini)
        • TFT-Display (angesteuert über TFT_eSPI-Library)
        • Stromzähler (HiChi/Tasmota) – liefert Netzwerte per HTTP
        • OpenDTU – liefert Solarleistungswerte per HTTP
        • Hintergrundbeleuchtung des TFT über PWM (Pin D3/RX)
        Software
        • Arduino Core for ESP8266
        • Wichtige Bibliotheken:
        o ESP8266WiFi (WLAN-Verbindung)
        o ESP8266WebServer (Webserver)
        o ArduinoJson (Parsing von API-Daten)
        o ArduinoOTA (Over-the-Air Updates)
        o TFT_eSPI (Displaysteuerung)
        o EEPROM (Speichern von Benutzer-Einstellungen)
        o time.h (NTP-Synchronisation, Sommerzeit)

      1. Programmablauf
        Setup (setup())
      2. WLAN-Verbindung mit gespeicherten SSID/Passwort.
      3. Zeitkonfiguration via NTP mit Sommerzeitregelung.
      4. OTA-Konfiguration (Hostname, Passwort, Start).
      5. Display-Initialisierung (Rotation, invertieren, Startanzeige).
      6. mDNS starten (esp.local).
      7. Webserver einrichten:
        o / → Dashboard
        o /data → JSON-Daten
        o /set → Einstellungsformular
      8. EEPROM laden und Werte prüfen/korrigieren.

      Loop (loop())
      • OTA-Requests bearbeiten (ArduinoOTA.handle()).
      • Webserver-Anfragen bedienen (server.handleClient()).
      • Display-Anzeige:
      o Start: IP-Adresse 5 Sekunden lang.
      o Danach: Hauptanzeige mit Solar, Netz, Einspeisung, Bezug.
      • Hintergrundbeleuchtung nach Uhrzeit an/aus/dimmen.
      • Alle 5 Sekunden:
      o Daten von OpenDTU (Solarleistung) und HiChi-Zähler (Netz, Einspeisung, Bezug) abrufen.
      o Display mit neuen Werten aktualisieren.


      1. Webinterface
        Dashboard (/)
        • Anzeige von:
        o Solarleistung (Watt)
        o Netzleistung (Watt)
        o Einspeisung (kWh)
        o Bezug (kWh)
        o Aktuelle Uhrzeit
        • Automatische Aktualisierung alle 5 Sekunden per AJAX.
        Einstellungen (/set)
        • Einstellen von:
        o An-Zeit (hh:mm)
        o Dimm-Zeit (hh:mm)
        o Aus-Zeit (hh:mm)
        o Dimmwert (PWM, 0–1023)
        • Werte werden im EEPROM gespeichert und beim Neustart geladen.
        Daten-Endpunkt (/data)
        • JSON-Ausgabe mit aktuellen Messwerten, z. B.:
        {
        "zeit": "10.09.2025 12:34:56",
        "solar": 450.0,
        "netz": -120.0,
        "einspeisung": 5.32,
        "bezug": 2.45
        }

      2. EEPROM-Nutzung
        Die Benutzer-Einstellungen werden im EEPROM gespeichert:
        • Adresse 0 → Stunde An
        • Adresse 1 → Minute An
        • Adresse 2 → Stunde Dimmen
        • Adresse 3 → Minute Dimmen
        • Adresse 4 → Stunde Aus
        • Adresse 5 → Minute Aus
        • Adresse 6 → Dimmwert LSB
        • Adresse 7 → Dimmwert MSB
        Damit bleiben die Werte auch nach einem Neustart erhalten.


      1. Besondere Funktionen
        • Sommerzeitberechnung: Automatische Umstellung gemäß Mitteleuropäischer Zeit (MEZ/MEZ+1).
        • Farbanzeige im Display:
        o Rot: Netzbezug
        o Grün: Einspeisung
        • OTA-Update: Programm kann per WLAN aktualisiert werden, ohne USB.
        • mDNS: Zugriff über http://esp.local/.

      1. Einsatzszenario
        Dieses System eignet sich besonders für Heimanwendungen, bei denen ein Balkonkraftwerk (Solar-Inverter) und ein Haushaltszähler überwacht werden sollen.
        Es bietet:
        • Schnelle Übersicht am Display (lokal, ohne PC/Smartphone).
        • Browser-Dashboard für Remote-Zugriff im Netzwerk.
        • Benutzerfreundliche Steuerung der Anzeige (automatische Dimmung).

      1. Grenzen und Anforderungen
        • Läuft ausschließlich auf ESP8266-Boards.
        • Erfordert ein funktionierendes WLAN.
        • Abhängigkeit von OpenDTU- und Tasmota-Geräten für Messdaten.
        • EEPROM hat begrenzte Schreibzyklen → sollte nicht zu oft überschrieben werden.

      Als Gehäuse habe ich dieses genommen:

      https://www.thingiverse.com/thing:4570437

      Display (2,4" TFT LCD Display Modul ILI9341 240x320) ist solch eines:

      https://www.roboter-bausatz.de/p/2-4-tft-lcd-display-modul-ili9341-240x320
      Es scheint, das es zwei verschiedene Arten von diesem Display gibt. Im Skript ist dafür eine Einstellung vorgesehen um die Farben zu invertieren.

      und natürlich ein Wemos D1 mini.

      Für das Display gibt es auch eine direkt Adapterplatine um den Wemos D1 mini ohne Kabelgefummel anzuschliessen.

      sm_red_top.png

      KiCAD-master.zip

      Hier mal ein Bild:

      innen.jpg
      anzeige.jpg

      Bei mir hat es sich gezeigt, das der "WAF" sehr hoch ist. 😊

      1 Reply Last reply Reply Quote 1
      • First post
        Last post

      Support us

      ioBroker
      Community Adapters
      Donate

      502
      Online

      32.1k
      Users

      80.8k
      Topics

      1.3m
      Posts

      1
      1
      90
      Loading More Posts
      • Oldest to Newest
      • Newest to Oldest
      • Most Votes
      Reply
      • Reply as topic
      Log in to reply
      Community
      Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen
      The ioBroker Community 2014-2023
      logo