An alle die sich einen übertriebenen Wohnungsputz-Weihnachtsstress machen.
Es kommt das Christkind - nicht das Gesundheitsamt.
Trotzdem eine frohe Weihnachtszeit und einen guten Rutsch in das neue Jahr.
An alle die sich einen übertriebenen Wohnungsputz-Weihnachtsstress machen.
Es kommt das Christkind - nicht das Gesundheitsamt.
Trotzdem eine frohe Weihnachtszeit und einen guten Rutsch in das neue Jahr.
@mcm57
Displays:
www.tindie.com/products/electronics-by-nic/5-pcs-29-epaper-tags-for-openepaperlink
AP:
www.tindie.com/products/electronics-by-nic/openepaperlink-mini-ap-v3-zigbee-wifi-gateway/
@doggie sagte in Wasserzähler - Version 2 - all-in-device:
Mir scheint das ganze AI on the edge Projekt ist nicht annähernd ausgereift. Sehr, sehr schade. Und enttäuschend.
Was sagen andere dazu?
Gibt es eigentlich Systeme die fehlerfrei laufen?
Als Entwickler des Systems würde mich so ein Fehler nicht schlafen lassen.
Ein klein wenig Demut wäre nicht schlecht. Hier macht jemand ein Projekt in seiner Freizeit. Alles kostenlos. Letztendlich ist es "Bastelei". Was nicht bedeutet das es sehr, sehr gute Projekte sind.
Wenn ein 100% zuverlässiges System gesucht wird, sollte man sich event. im Fachhandel umschauen. Ob dort die entsprechenden System und Programme auch kostenlos angeboten werden, kann ich so nicht sagen. Ich glaube aber eher nicht.
Grüße
Manfred
Vielen Dank.
Ich liebe dieses Forum und die Leute darin.
@bananajoe sagte in EPS E-INK Display Ansteuerung -> Statusdisplay für 2€:
@beowolf hast du die selbst geflasht? In der Bucht sind die gerade zu kaufen und ich starre gerade schon eine ganze weile auf das Pinout wo ich denn ran müsste ... (ich bin Tasmota-verwöhnt, da wird immer ein Bild hinterlegt welche Pins man nehmen muss)
Eines vorab. Das ist nicht auf meinem Mist gewachsen. Ich habe das auch nur mit der Hilfe von Aaron Christophel hin bekommen.
Vielen, vielen Dank dafür.
Hier z.B. ein Video von ihm.
https://www.youtube.com/watch?v=8oQdo9bJ7Rk
Es geht um diesen TAG
https://github.com/jjwbruijn/OpenEPaperLink/wiki/2.9″-EL029H3WRA#pinout
Für den Anschluss und Positionierung der POGO-Pins geht dieses z.B.
https://github.com/jjwbruijn/OpenEPaperLink/blob/master/Hardware/M3 Newton Jigs by Jelmer/M3-2.9.stl
Ich habe dieses
https://github.com/jjwbruijn/OpenEPaperLink/tree/master/Tag_Flasher
herunter geladen.
Nun in das Verzeichnis ESP32_Flasher wechseln und dann mit der Hilfe von Visual Studio Code den Flasher auf einen ESP32 S2 aufgespielen.
Den ESP dann so wie hier mit dem TAG verbinden.
Nun ein Verzeichnis höher gehen. Also in den Tag_Flasher Ordner gehen.
Dann diese Datei herunterladen und in das Verzeichnis ablegen.
https://github.com/jjwbruijn/OpenEPaperLink/blob/master/binaries/Tag/SOL_M3_Uni_full_26.bin
Ich habe es mit diesem Befehl gemacht.
python3 OEPL-Flasher.py -p COM11 -e -n write SOL_M3_Uni_full_26.bin --flash --pt
COM11 natürlich anpassen.
Für das Flashen muß auf dem Rechner Python installiert sein.
Hier die passende Version laden.
Bei dem Versuch kamen bei mir ein paar Meldungen das diese oder jenes nicht da ist.
Ich musste z.b. dieses nachinstallieren: "pip install pyserial". Sollten da noch Meldungen kommen. einfach kurz bei google suchen.
Danach sollte der Tag mit OPenEPaper geflasht sein.
Ich hoffe ich habe nichts vergessen.
@bananajoe sagte in EPS E-INK Display Ansteuerung -> Statusdisplay für 2€:
@beowolf sagte in EPS E-INK Display Ansteuerung -> Statusdisplay für 2€:
Ich bin ein wenig Ratlos und weiß nicht wo ich anfangen soll.
Hier mal grob in Schritten zusammengefasst:
Schritt 1: Eigene VIS für die ePaper Tags
.........
Für die schwarzen TAGs muß die Auflösung geändert werden. Sonst gibt es nur Schrott in der Anzeige.
Also auf 384 x 168 Pixel
Grüße
Noch ein kleiner Tipp. Ich habe in meinen Blocklys noch eine "Pausezeit" für die Displays eingefügt.
So werden sie in der Zeit von 23 Uhr bis 8 Uhr nicht mehr aktualisiert. Ist eh keiner wach um auf die TAGs zu schauen.
So kann Energie gespart werden und die Batterien halten etwas länger.
Frag doch einfach den "atc" wie oben in meinem Beitrag nach einem yellow AP. Das Ding läuft. Der hat ein Display.
@m-a-x sagte in E-INK Display OpenEPaperLink - Displayanzeige mit Batterie:
Edit: an der von dir verlinkten Lösung gefällt mir nicht: zusätzlicher Router (Strom Verbrauch und mehr Hardware), die displays sehen sehr oft / immer billig aus (kein Gehäuse oder nur ein seitlich offenes).
Nichts für ungut, aber ob ein ESP32 jetzt "den" Stromverbrauch hat? Nun gut.
Displays billig? Schau dir diese an
Anzeige steht gerade auf dem Kopf.
Weder billig noch offen.
Was möchtest du denn noch mehr?
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
Ablauf im loop()
Die Endlosschleife (loop()) kümmert sich um:
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
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.
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
}
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.
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.
Hier mal ein Bild:
Bei mir hat es sich gezeigt, das der "WAF" sehr hoch ist.
@simatec sagte in Test Adapter shuttercontrol v2.0.x:
val === 'up' ? 100 : val === 'shade' ? 50 : 0
Ich verstehe nicht wo das eingegeben werden muß?
@simatec
Hattest du schon Zeit um es etwas genauer zu beschreiben?
shade kennt er noch.
"Dem Alias kannst du so erstellen, dass up = 100 und down = 0 ist."
Kannst du mir zeigen wo das gemacht wird?
Hab da mal ne Frage.
Ich habe TDEF-Motoren von Jarolift. Die verstehen UP, DOWN und STOP per mqtt.
Kann ich die in den Adapter auch anbinden und steuern?
Könntest du die kleinen Grafiken hier anzeigen?
Also window_open48.jpg usw.
Grüße
Ist das richtig, das das skript hier hin kommt?
/opt/iobroker/node_modules/iobroker.mihome-vacuum/lib/
Wenn ja, der Inhalt der Datei die dort schon liegt ist identisch mit der auf github.
Habe das Skript mal gerade getestet. Scheitert bei mir mit der gleichen Fehlermeldung. Man kann sich nicht anmelden.
Ich glaube, das hier etwas durcheinander läuft.
Welches Skript meinst du? Event. noch einmal hier einstellen.