Inhalt
- Erste Schritte
- Anzapfen des Shellys
- Mehr PV, Akkus und E-Zigaretten
- Anzeige des Ladestands und Einspeisestatus-LED
- Tiefschlaf ist angesagt
- Schaltbild und vollständiger Code
Ziele und Vorbereitungen
Im letzten Blogeintrag habe ich beschrieben, wie ich mir eine miniPV-Anlage aufs Dach gebaut habe. Das ganze Projekt ist jetzt schon wieder fast 5 Monate her, jetzt, in den Osterferien, habe ich endlich wieder Zeit gefunden, um mich dem nächsten Projekt zu widmen: Ich möchte die aktuelle PV-Produktion, die aktuelle Gesamtleistung und die aktuelle dem Netz entnommene oder eingespeiste Leistung irgendwie sichtbar machen.
Dafür habe ich mir schon Mitte November einen ESP8266 mit und ohne OLED-Display gekauft.
In diesem Blogeintrag möchte ich nun beschreiben, wie ich es geschafft habe den ESP entsprechend zu programmieren und dazu so zu beschalten, dass er über einen kleinen LiPo-Akku und einer PV-Zelle autark läuft. Ich möchte meine Einzelschritte und Fehler auch mit dokumentieren, bemühe mich aber das Ganze nicht allzu lang und unterhaltsam zu halten. Wer nur die fertige Beschaltung und den fertigen Code möchte, muss bis ganz zum Ende scrollen.
Neben dem Nutzen und der Neugierde mal mit einem neuen Microcontroller zu arbeiten, stand der positive Nebeneffekt, dass wir in der Schule auch überlegen uns ein paar ESP anzuschaffen und im Lernfeld „Cyber-physische Systeme“ ein bisschen damit programmieren. Da kann man sich daheim auch schon mal damit beschäftigen 😉
1. Erste Schritte
Als erstes wollte ich einfach nur das Display zum Leben erwecken und gucken, wie es funktioniert dort unterschiedliche Texte darzustellen und zu positionieren. Den Einstieg dazu tat die Anleitung direkt im Ebay-Artikel. Jetzt wieder mein etwas eingerostetes Arduino / C++ Code-Wissen reaktivieren und schon nach kurzer Zeit hatte ich Text auf dem Display.
Sowas in diese Richtung habe ich auch schon mit den Arduino (ein anderer Microcontroller) gemacht. Der ESP hat allerdings WLAN, also war das der nächste Schritt: Eine WLAN-Verbindung mit meinem Gäste-WLAN „Internetschnorrer“ herstellen, wo auch alle weiteren SmartHome-Geräte drin hängen. Das ist schnell geglückt und ich habe nach erfolgreicher Verbindung einfach einen Zähler hinter dem „Loading“ hochlaufen lassen. Auch das ist gar nicht soo einfach, da die Funktion, um etwas auf dem Display darzustellen den Datentyp „char“ benötigt, Zahlen, die man auch hochzählen kann, aber zum Datentyp „int“ gehören. Eine Umwandlung über ein Array musste also her.
Ich machte mir auch gleich Gedanken, was ich denn gerne wo auf dem Display dargestellt haben möchte und entwarf einen „Muster-Screen“ mit fiktiven Werten.

Checklist:
- ✅ Display ansteuern
- ✅ WLAN-Verbindung aufbauen
- ➡️ Next up: Wert des Shelly auf dem Display darstellen
2. Anzapfen des Shellys
2.1 Der Plan
Ich möchte die Daten (aktueller Energieverbrauch) von meinen Shellys über WLAN abfragen.
Das will ich genauer wissen. Weiterlesen…. (<– clickme)
Die Shellys sind auch dafür bekannt, dass man sie mit vielen Protokollen steuern bzw. abfragen kann. Ich hatte mir erst überlegt, ob ich das vielleicht auch über MQTT (ein weit verbreitetes IoT Protokoll für genau solche Zwecke) mache. Allerdings braucht das MQTT-Protokoll immer einen Broker, also sozusagen eine Zentrale. Die habe ich nicht und will sie auch nicht irgendwo aufsetzen, nur wegen zwei Shellys von denen ich Daten benötige. Vielleicht kommt das irgendwann mal, wenn ich mein SmartHome nicht mehr über die Cloud steuern lasse, sondern mir selbst den Heimserver aufsetze, aber dafür brauche ich erstmal wieder einen Zeitraum mit viiel Zeit…
2.2 ChatGPT und der UDP Fail
Ich startete mit einer Anfrage an den Microsoft Copilot (also GPT-4). Die Antwort funktionierte, allerdings war das nicht so das gelbe vom Ei…
Das will ich genauer wissen. Weiterlesen…
Ich dachte mir ich frage einfach mal den Copilot, also GPT-4, wie das die Abfrage umsetzen würde. Ich erhielt einen Code, der mit ein paar kleinen Anpassungen tatsächlich funktioniert hat. Grundsätzlich bemühe ich mich die Anzahl der Anfragen an KI möglichst gering zu halten, gegenüber dem normalen Googlen sieht die Energiebilanz von KI doch sehr schlecht aus.
//Funktion zur Abfrage von Werten aus einem Shelly
double getShellyData(String rpccmd, const char* shellyIP, String JSONValue){
//RPC Befehl an das Shelly senden
double power;
udp.beginPacket(shellyIP, shellyPort);
udp.print(rpccmd);
udp.endPacket();
//auf Antwort warten
delay(500);
if (udp.parsePacket()){
char reply[1024];
int len = udp.read(reply,1023);
if (len>0){
reply[len]=0;
//JSON-Daten parsen
StaticJsonDocument<1024> doc; //Größe je nach erwarteter Antwortgröße
DeserializationError error = deserializeJson(doc,reply);
if(!error){
power = doc["result"][JSONValue];
} else {
power = 999; //Fehler beim parsen der JSON Datei
}
}
}
return power;
}
Der Code zeigt die Funktion einer Abfrage an ein Shelly. Ein Aufruf könnte folgendermaßen aussehen:
PVpwr = getShellyData("{\"id\": 1, \"method\": \"PM1.GetStatus\", \"params\": {\"id\":0}",PVshellyIP, "apower");
Der Code zeigt die Funktion einer Abfrage an ein Shelly. Ein Aufruf könnte folgendermaßen aussehen:
PVpwr = getShellyData("{\"id\": 1, \"method\": \"PM1.GetStatus\", \"params\": {\"id\":0}",PVshellyIP, "apower");
Bild auf dem nur Wert bei „Erzeugung“
So super funktioniert hat das aber nicht, es gab folgende Probleme:
- Manchmal kommt als Wert „0“ oder etwas ungültiges (siehe Code „Fehler beim parsen der JSON Datei“)
Ich schaffte es nicht den Befehl auch für das zweite Shelly gangbar zu machen

2.3 Ein neuer Ansatz muss her
Bei der Suche nach Lösungen bin ich auf die Idee gekommen, dass ich auch bei einer Standard-HTTP-Anfrage, wie sie ein Browser macht, eine Antwort bekomme. Diese Antwort muss ich eigentlich nur noch auswerten.

Das will ich genauer wissen. Weiterlesen…
Bei meiner Suche nach den Befehlen und Namen der Datenfelder hatte ich mittlerweile auch die Shelly-API-Dokumentation immer weiter studiert.
Dabei bin ich draufgekommen, dass die RPC Commands einfache HTTP GET Befehle sind und dann eine JSON als Antwort kommt.
Also gabs eine neue Anfrage an den Copilot, diesmal sehr präzise formuliert
ich möchte mit einem ESP8266 einen http get request an ein Shelly gerät senden. Die Antwort auf den Befehl […] sieht folgendermaßen aus: [JSON Antwort wie im Screenshot].
Anfrage an den Copilot
Und siehe da, ich bekomme eine Antwort, die – natürlich nach ein paar Anpassungen – funktioniert.
2.4 Finale Veränderungen
Ich kopiere das Ganze für mein zweites Shelly, passe den Code entsprechend an und schon habe ich beide Werte.
- Wert 1: Aktuell erzeugte Leistung durch die PV – gemessen vom Shelly PM mini in der Einspeisesteckdose („apower“ im JSON, immer negativ, weil Einspeisung)
- Wert 2: Aktuell dem Netz entnommene Leistung (Wert positiv) oder aktuelle Einspeisung ins Stromnetz (Wert negativ) – gemessen durch das Shelly Pro 3EM im Sicherungskasten („total_act_power“ im JSON)


Wenn ich beide Werte verrechne, kann ich die aktuell benötigte Gesamtleistung in der Wohnung ausrechnen. Dargestellt sieht das dann so aus:

Auch das Umschalten zwischen „Netzbezug“ und „Einspeisung“ in der letzten Zeile funktioniert:
Checkliste:
- ✅ Display ansteuern
- ✅ WLAN-Verbindung aufbauen
- ✅ Werte per selbstgebautem UDP-Paket abfragen
- ✅ Fehler bekommen – Lust verlieren – nach Alternativen suchen
- ✅ Werte per http Request abfragen
- ✅ Codeoptimierungen
- ➡️Next up: Externe Spannungsversorgung des ESP
3. Mehr PV, Akkus aus E-Zigaretten
3.1 Der Plan
Da so ein ESP recht sparsam ist, möchte ich ihn mit einer kleinen PV-Zelle und einem Akku als Speicher betreiben.
Das will ich genauer wissen. Weiterlesen…
Der ESP ist auch dafür bekannt, dass er einen sehr geringen Energiebedarf hat, daher dachte ich mir sollte es ja möglich sein ihn auch über eine kleine PV-Zelle mit einem Akku als Pufferspeicher dauerhaft zu betreiben. Nach ersten Tests, bei denen ich den ESP einfach mal über Nacht an einer Powerbank hängen lies, zeigten mir, dass die Stromaufnahme wohl doch höher als gedacht ist. Eine kleine 4000mAh Powerbank war nach ca. 10 Std leer. Gut, dass der ESP auch eine deep-Sleep Funktion hat… aber da kümmere ich mich später drum.

Ich fand eine Webseite, die genau das beschrieben hat, was ich vorhatte, und ich bestellte mir die fehlenden Teile.
3.2 Ausschlachten der E-Zigaretten
Kommen wir nun zu einer der bescheuertsten Umweltsünden, die die Konsumgesellschaft in den letzten Jahren so hervorgebracht hat: Wegwerf-E-Zigaretten.

Die Dinger gibt es auf diversen Festivals und anderen Events zu kaufen. Drin steht ein geladener Akku, stinkendes Aroma zeug und ein Verdampfer. Das Problem: wenn das Aroma verdampft ist, ist das Teil für den Abfalleimer, bzw. eigentlich den Elektroschrott, bestimmt. Wie es mit Wegwerf-Produkten aber so ist, landen sie oft einfach auf dem Boden. Traurig.

Aus den Akkus aus E-Zigaretten betreibe ich mittlerweile auch drei Temperatursensoren und meine Türklingel, statt Knopfzellen bzw. zwei AAA-Batterien.
Zur Verteidigung der dampfenden Mitmenschen muss man sagen, dass es die Dinger auch in Wiederauffüll- und -aufladbar gibt. Die landen dann im Normalfall nicht auf dem Boden.
Mehr Infos…


3.3 Energieversorgung ESP
Ich nehme also einen Akku aus der E-Zigarette und bastle mir aus Laderegler und PV-Zelle die Energieversorgung.
Das will ich genauer wissen. Weiterlesen…
Ich bereitete also einen Akku aus einer E-Zigarette vor indem ich eine kleine Platine mit Über-/Unterspannungsschutz anlötete (hätte ich mal genauer gucken sollen, was ich bestellt habe, denn der Laderegler hat die Schutzfunktion schon eingebaut…).

Nungut, jetzt noch die PV-Panels verbinden, da kam mir die Idee das mit Schaschlik Spießen und Heißkleber zu realisieren. Dann den Spannungsregler mit seinen zwei (bei mir drei) Kondensatoren an die Platine löten.


PV-Modul an den Laderegler anschließen, Akku und ESP an den Laderegler anschließen: Funktioniert. Bei Sonnenlicht leuchtet auch die Lade-LED, der Akku scheint also zu laden, wenn Sonne scheint.

Checkliste:
- ✅ Display ansteuern
- ✅ WLAN-Verbindung aufbauen
- ✅ Werte per selbstgebautem UDP-Paket abfragen
- ✅ Fehler bekommen – Lust verlieren – nach Alternativen suchen
- ✅ Werte per http Request abfragen
- ✅ Codeoptimierungen
- ✅ Akku aus E-Zigarette ausbauen
- ✅ Spannungsversorgung löten
- ➡️ Next up: Batterieanzeige auf Display und Einspeisestatus per LED
4. Anzeige des Ladestands und Einspeisestatus-LED
4.1 Lötarbeiten
Im Artikel der Random Nerd Tutorials war auch gleich die Möglichkeit beschrieben, wie man die Akkuspannung messen kann. Warum also nicht den Akkustand auch auf dem Display anzeigen? Dafür musste erstmal was gelötet werden. Dazu sollte noch eine LED und ein Button…
Das will ich genauer wissen. Weiterlesen…
Daher habe ich den Spannungsteiler direkt mit an meine Platine gelötet. Da ich nicht ganz die passenden Widerstände hatte, habe ich mir schnell einen neuen Spannungsteiler berechnet, der sich mit den mir zur Verfügung stehenden Widerständen realisieren lässt.
Infobox Analogeingang und Spannungsteiler
Der Analogeingang des ESP wandelt eine anliegende anliegende Spannung in einen digitalen Wert (10bit, also zwischen 0 und 1023) um. Es gilt zu wissen, welche Spannung dem HIGH-Wert (1023) entspricht. Für meinen Fall war das 3,3V (leider nur zu 50% richtig, wie sich später herausstellt). Bedeutet also, dass am Analogeingang maximal 3,3 V anliegen dürfen.
Ein LiPo-Akku hat allerdings, wenn er voll ist eine Spannung von 4,2V und als leer gilt er, wenn nur noch 3,2V übrig sind. Betrachten wir die maximale Spannung von 4,2V. Bei dieser Spannung wollen wir, dass 3,3V am Analogpin anliegen. Also müssen wir dafür sorgen, dass davor bereits 0,9V an einem „Vorwiderstand“ abfallen. Es wird also die Gesamtspannung von 4,2V in zwei Spannungen aufgeteilt: 0,9V und 3,3V. Dies erreicht man mit zwei Widerständen in Reihe, welche man mit einer Formel berechnen kann:


Da ich schon dabei war, wollte ich auch gleich eine LED mit anlöten. Jetzt hatte ich allerdings schon den Energiespargedanken im Kopf und ich wollte erstmal testen welchen Vorwiderstand ich benötige, damit die Helligkeit der LED gerade ausreichend ist (oft kann man LEDs mit nur halben Strom betreiben und sie leuchten fast genauso hell, wie mit doppeltem Strom).

Einen Knopf habe ich auch gleich mit angelötet. Mit dem will ich später den ESP aus dem Schlaf jederzeit aufwecken können.


4.2 Programmieren der Ladestandsanzeige
Das war schwieriger als gedacht, da meine zwei ESP ein bisschen anders beschalten sind. Naja, nachdem ich dann die Analogwerte den richtigen Akkuspannungen zuordnen konnte, hat es auch funktioniert.
Das will ich genauer wissen. Weiterlesen…
Der Ausgang des Spannungsteilers ist am Analogeingang des ESP angeschlossen. Den Wert des Analogeingangs kann man im Code auslesen. Er besitzt eine Auflösung von 10 bit, das bedeutet liegt die maximale Spannung (laut Random Nerd Tutorials 3,3V) an, wird der Wert 1023 ausgegeben, liegen 0V an, gibt die Funktion analogRead den Wert 0 zurück. Jetzt musste ich also nur noch die Werte für „Akku voll“ und „Akku leer“ bestimmen. Dazu nahm ich mein zweites ESP8266-Board und baute auf dem Entwicklerboard schnell den Spannungsteiler auf. Als Akkuspannung nahm ich mein regelbares Netzteil und lies mir die aktuellen Werte der analogRead-Funktion direkt auf dem Laptop ausgeben.

Werte ermittelt und in den Code eingebracht. 117% Akkustand bei einem Akku der nur etwa 70% voll ist. Da stimmt was nicht. Spannungen nachgemessen, passt alles. Am Analogpin liegen ca, 2,8V, was auch mitten im festgelegen Bereich (0-3,3V) liegt. Nochmal am anderen ESP probiert, da funktioniert es. Verzweiflung. Bin ich doof? Googlen hilft: Der eigentliche ESP8266-Chip arbeitet mit max. 1V am Analogeingang. Bei den meisten Boards auf denen er installiert ist, ist ein weiterer Spannungswandler aufgelötet um 3,3V als Maximalwert zu akzeptieren. Mein Board ohne das Display hat diesen Spannungswandler, das mit dem Display nicht. Also meinen eigenen Spannungswandler so berechnen, dass bei maximaler Akkuspannung von 4,2V nur 1V am Analogpin ankommt.

Äquivalente Analogwerte neu bestimmt und siehe da, schon geht’s. Danke für den Stolperstein…

4.3 Einspeisestatus-LED
Das ist schnell erklärt: Die kleine grüne LED neben dem ESP soll 1x blinken, wenn die Einspeisung unter 100W, 2x blinken, wenn unter 200W, 3x unter 300W, usw. Da es mehrere Bereiche gab, dachte ich mir, ich probieren mal die Switchcase-Struktur im Code aus.
Hat auch direkt funktioniert.
Checkliste:
- ✅ Display ansteuern
- ✅ WLAN-Verbindung aufbauen
- ✅ Werte per selbstgebautem UDP-Paket abfragen
- ✅ Fehler bekommen – Lust verlieren – nach Alternativen suchen
- ✅ Werte per http Request abfragen
- ✅ Codeoptimierungen
- ✅ Akku aus E-Zigarette ausbauen
- ✅ Spannungsversorgung löten
- ✅ Ladestand anzeigen
- ✅ Einspeisestatus-LED umsetzen
- ➡️ Finale: Energieverbrauch optimieren
5. Tiefschlaf ist angesagt
Der ESP besitzt einen Tiefschlaf-Modus, bei dem er nur etwa 20µA benötigt. Den wollte ich mir nun zu Nutze machen, da der kleine 600mAh-Akku aus der E-Zigarette sonst niemals die Nacht durchhalten würde. Zudem weiß ich ja, dass, wenn es dunkel ist, auch die Einspeisung Null ist.
Wieder gab es ein Top-Tutorial auf Random Nerd Tutorials.
5.1 Reset-Schwierigkeiten
Ich wollte mit dem angelöteten Button den ESP aufwecken. Ich hatte anfangs gedacht, dass es im Code gelöst wird, aber nein, zum Neustart wird der ESP zurückgesetzt. Wie der Resetbutton am PC, daher nochmal umlöten.
Das will ich genauer wissen. Weiterlesen…
Als erstes stellte ich fest, dass ich den Button falsch gelötet hatte. Man kann nicht einfach irgendeinen Digitaleingang zum Reset (also zum Neustart) des ESP nehmen, sondern es muss der Reset-Pin sein. Ja, da hätte man sich denken können, aber ich dachte man kann im Code sicher auch einen Pin festlegen – im Nachhinein ein dämlicher Gedanke. Also habe ich den Button umgelötet. Weitere Unaufmerksamkeit meinerseits: Damit das automatische Aufwachen nach einer bestimmten Zeit funktioniert, muss sich der ESP selbst zurücksetzen können, dafür muss der D0-Pin mit dem Reset-Pin verbunden werden. Das habe ich natürlich gekonnt ignoriert und mich gewundert, warum nix funktioniert… Naja, es war spät am Abend.
Am nächsten Tag habe ich den Fehler schnell gefunden, die Brücke gelötet und schon gings.

5.2 Was soll auf dem Display stehen? – NTP Zeitsynchronisierung
Ich stellte fest, dass der Display eingeschaltet bleibt, wenn der ESP in den Schlafmodus geht. Den Vorteil wollte ich nutzen und dort zumindest den letzten Verbrauch und den Akkustand angezeigt lassen.
Dann wäre es natürlich cool, wenn auch dran steht, von welcher Uhrzeit die Daten sind. Naja, wenn der ESP schon WLAN und somit auch Internet hat, wird es nicht so schwer sein die aktuelle Uhrzeit abzufragen. Wieder half – wer hätte es gedacht – Random Nerd Tutorials.

5.3 PV-Optimierungen
Trotz des Schlafmodus reichte der Akku nicht. Also beschloss ich mich, mal nachzumessen, wo welche Ströme fließen. Ich stellte fest, dass die zwei PV-Panels, welche ich für die Energieversorgung nutze, hinter der Fensterscheibe (inkl. Fliegengitter) hochgradig ineffizient sind. Bei starker Bewölkung kommen gerade mal 3mA rum. Na das reicht natürlich nicht, wenn der ESP alleine schon ca. 75mA benötigt. Am Fenster ohne Fliegengitter waren es schon 8 mA. Draußen dann ca. 80mA. Aha! Geht doch. Also habe ich die Panels wasserfest gemacht und jetzt außen vor das Fenster gelegt.
5.4 Finale Energieoptimierung
Zudem geht der ESP jetzt in den Tiefschlaf, sobald die PV-Produktion für 30 Sekunden auf unter 100W fällt (starke Bewölkung) oder der Akkustand unter 50% liegt. Nach einer Stunde wacht er automatisch wieder auf und evtl. beginnen die 30 Sekunden von vorne. Somit steht auf dem Display immer ein max. eine Stunde alter Wert des Energiebedarfs und Akkustands.
Im Tiefschlaf fließen mit eingeschaltetem Display noch etwa 6,4mA. Daher habe ich beschlossen doch nachts (zwischen 0 und 5 Uhr) das Display ganz auszuschalten, schließlich bin ich von den 20µA noch um den Faktor 320 weg. Das brachte mäßigen Erfolg. Statt den 6,4mA sind es bei ausgeschaltetem Display 2,4mA. Klar nochmal mehr als halbiert, aber immer noch über das 100-fache vom erhofften Wert.
Eine kurze Recherche lieferte das Ergebnis: Der ESP (auf den Bildern der kleine silberne Chip mit der WLAN-Antenne links) benötigt wirklich nur die 20µA, der ganze Rest geht für den USB zu Seriell-Adapter drauf, der ja auch noch auf dem Board ist…. Auf den könnte ich verzichten, indem ich mir einen „nackten“ ESP-Chip kaufe und diesen mit externem USBtoSerial-Adapter programmiere. Das würde das Projekt maximal energieeffizent machen, aber jetzt gucke ich mir erstmal an, wie sich der Akku in Verbindung mit dem recht „aggressiven“ Energiesparmodus so schlägt.
Fazit nach ca. 2 Wochen: Auch am Ende eines bewölkten Tages hat der Akku noch gut 70%, reicht also locker aus 🙂
Checkliste:
- ✅ Display ansteuern
- ✅ WLAN-Verbindung aufbauen
- ✅ Werte per selbstgebautem UDP-Paket abfragen
- ✅ Fehler bekommen – Lust verlieren – nach Alternativen suchen
- ✅ Werte per http Request abfragen
- ✅ Codeoptimierungen
- ✅ Akku aus E-Zigarette ausbauen
- ✅ Spannungsversorgung löten
- ✅ Ladestand anzeigen
- ✅ Einspeisestatus-LED umsetzen
- ✅ Tiefschlaf umsetzen
- ✅ Energieoptimierungen
- ➡️ Gehäuse designen und 3D-drucken
- ➡️ Zweite Displayseite mit Tagesgesamtverbrauch und -erzeugung
6. Schaltbild und vollständiger Code
6.1 Schaltbild
Naja, ich habe jetzt keine Lust mehr ein fertiges Schaltbild zu zeichnen, im Prinzip gibt’s die alle in den verlinkten Random Nerd Tutorials. Aber hier gibt’s ein beschriftetes Foto meiner Bauteile:

6.2 Vollständiger Code
Obacht: ich bin kein Softwareentwickler und das ist definitiv das Aufwändigste, was ich bisher so gemacht habe. Es gibt also sicherlich einiges noch an Optimierungsvarianten.
/*
Code by mrneveroff - neveroff.de
u8g2 I2C Display Dokumentation: https://github.com/olikraus/u8g2/wiki/u8g2reference
Arduino C++ Code Dokumentation: https://www.arduino.cc/reference/en/
Shelly Doku (Link für EnergyMeter, z.b. das Pro 3EM): https://shelly-api-docs.shelly.cloud/gen2/ComponentsAndServices/EM/
PV-Selbstversorgung des ESP: https://randomnerdtutorials.com/power-esp32-esp8266-solar-panels-battery-level-monitoring/
Deep Sleep Beschaltung und Infos: https://randomnerdtutorials.com/esp8266-deep-sleep-with-arduino-ide/
GitHub der NTPClients: https://github.com/arduino-libraries/NTPClient
NTP Zeitsynchro: https://randomnerdtutorials.com/esp8266-nodemcu-date-time-ntp-client-server-arduino/
*/
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <U8g2lib.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
//Taster und LED Initialisierung
int LED = D1; //LED die leutet, wenn eingespeist wird
//WiFi Konfig
const char* ssid = "Internetschnorrer";
const char* password = "EinBloedesPasswort";
IPAddress local_IP(192, 168, 189, 200);
IPAddress gateway(192, 168, 189, 1);
IPAddress subnet(255, 255, 255, 0);
IPAddress primaryDNS(192, 168, 189, 1); // optional
//NTP Konfig
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org");
//Variableninitialisierung
bool testmode = false; //Um einzelne Funktionen im Code zu testen
String totalActPower; //Gesamtwert des Shelly Pro 3EM (Leistung)
double totpwr; //Gesamtwert als Double
String apower; //Aktueller Wert des Shelly PM mini in der Einspeisedose des BKW (Leistung)
double PVpwr; //Erzeugungswert (apower) als Double
double totusage; //Summe des Gesamtverbrauchs der Wohnung
int batteryLevel; //Akkustand in Prozent
int lowpowercounter; //Zähler, der hochzählt, wenn nur geringe Einspeisung und niedriger Akkustand um rechtzeitig in den deepsleep zu schalten
//OLED-Display Initialisierung
U8G2_SSD1306_128X64_NONAME_F_SW_I2C
u8g2(U8G2_R0,/*clock=*/14,/*data=*/12,U8X8_PIN_NONE); //U8G2_R0 gibt Displayorientierung an; GPIO14 für I2C Clock und GPIO12 für I2C Data
//Funktion die die LED mit übergebener Anzahl blinken lässt
void blink(int count){
for(int i=0;i<count;i++){
digitalWrite(LED,HIGH);
delay(100);
digitalWrite(LED,LOW);
//Serial.println(i);
delay(300);
}
}
//Funktion die Double-Wert umwandelt in Char und dann an gegebener Stelle in den Display Buffer schreibt
void writeDisplayBuffer(double wert,int x, int y){
char powerStr[10];
dtostrf(wert,5,1,powerStr); //double in char* umwandeln, max. 5 stellen, 1 nachkommastelle
u8g2.drawStr(x,y,powerStr);
}
//wie oben, nur ohne Nachkommastellen
void writeDisplayBufferInt(double wert,int x, int y){
char powerStr[10];
dtostrf(wert,5,0,powerStr); //double in char* umwandeln, max. 5 stellen, 0 nachkommastellen
u8g2.drawStr(x,y,powerStr);
}
void setup() {
//Serial.begin(921600);
//delay(10);
//Serial.print("test");
pinMode(LED,OUTPUT);
pinMode(A0, INPUT);
blink(1);
u8g2.begin();
u8g2.setFont(u8g2_font_mercutio_basic_nbp_tf);
u8g2.drawStr(0,10,"Initialisierung");
u8g2.drawStr(0,26,"Verbinde mit WLAN:");
u8g2.drawStr(0,40,ssid);
u8g2.sendBuffer();
//Initialisierung, damit hochgezählt und später die IP angezeigt werden kann
int i=0;
char b[16];
String str;
//Wifi Config setzen und Verbindung aufbauen
WiFi.config(local_IP, gateway, subnet, primaryDNS);
WiFi.begin(ssid,password);
while(WiFi.status() != WL_CONNECTED){
u8g2.drawStr(0,54,"warten...");
str=String(i);
str.toCharArray(b,50);
u8g2.drawStr(70,54, b);
u8g2.sendBuffer();
i++;
delay(1000);
}
timeClient.begin();
timeClient.setTimeOffset(7200); //UTC+2 = CEST = Sommerzeit
//timeClient.setTimeOffset(3600); //UTC+1 = CET = Winterzeit
//IP-Adresse auf dem Display anzeigen
u8g2.clearBuffer();
u8g2.drawStr(0,10,"Verbunden mit SSID:");
u8g2.drawStr(0,26,ssid);
//u8g2.drawStr(0,38,"___________________");
u8g2.drawStr(0,42,"IP-Adresse:");
str=WiFi.localIP().toString();
str.toCharArray(b, 50);
u8g2.drawStr(0,56,b);
u8g2.sendBuffer();
blink(2);
delay(1360);
}
void loop() {
batteryLevel = map(analogRead(0), 327, 435, 0, 100); //Aktuellen Akkustand auslesen; 327 = 3,3V; 435 = 4,2V Akkuspannung über Spannungsteiler; max. 1V am A0 Pin
//batteryLevel = analogRead(0); //Um Analogwert direkt auszugeben; hierfür Zeile davor und Zeile mit "%" auskommentieren
u8g2.clearBuffer();
u8g2.drawStr(0,10,"Status:");
u8g2.drawStr(78,10,"Bat:");
writeDisplayBufferInt(batteryLevel,98,10); //INT funktion macht keine Nachkommastellen
u8g2.drawStr(120,10,"%");
u8g2.drawStr(0,26,"Erzeugung:");
u8g2.drawStr(116,26,"W");
u8g2.drawStr(0,40,"Verbrauch:");
u8g2.drawStr(116,40,"W");
u8g2.drawStr(0,47,"__________________");
u8g2.drawStr(116,61,"W");
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
WiFiClient client;
// Anfrage an das Shelly PRO 3EM im Sicherungskasten
http.begin(client,"http://192.168.189.89/rpc/EM.GetStatus?id=0");
int httpCode = http.GET();
if (httpCode > 0) {
String payload = http.getString();
//Serial.println(payload);
int startIndex = payload.indexOf("total_act_power") + 17;
int endIndex = payload.indexOf(",", startIndex);
totalActPower = payload.substring(startIndex, endIndex);
//Serial.print("Gesamt:");
//Serial.println(totalActPower);
}
http.end();
// Anfrage an das Shelly PM mini in der Einspeisedose
http.begin(client,"http://192.168.189.5/rpc/PM1.GetStatus?id=0");
httpCode = http.GET();
if (httpCode > 0) {
String payload = http.getString();
int startIndex = payload.indexOf("apower") + 9;
int endIndex = payload.indexOf(",", startIndex);
apower = payload.substring(startIndex, endIndex);
//Serial.print("Erzeugung:");
//Serial.println(apower);
}
http.end();
}
//u8g2.drawStr(87,26,apower.c_str()); //Darstellen aktuelle Erzeugung
PVpwr = apower.toDouble(); //umwandlen des Strings zu einem Double-Wert,
writeDisplayBuffer(PVpwr,81,26); //wäre nicht nötig, siehe auskommentierter u8g2.drawStr oben, allerdings sind so alle drei Werte untereinander zentriert
totpwr = totalActPower.toDouble(); //same here
if(totpwr>0){
digitalWrite(LED,LOW); //Sicherstellen, dass Einspeise-LED aus
u8g2.drawStr(0,61,"Netzbezug:");
writeDisplayBuffer(totpwr,81,61);
totusage = PVpwr + totpwr; //wenn Energie aus dem Netz genommen wird, ergibt sich die Gesamtleistung aus der Summe der aktuellen Erzeugung und dem Netzbezug
writeDisplayBuffer(totusage,81,40);
}else{
u8g2.drawStr(0,61,"Einspeisung:");
totpwr = totpwr * -1; //Vorzeichen umdrehen, weil Einspeisung als negativer Verbrauch angezeigt wird
writeDisplayBuffer(totpwr,81,61);
totusage = PVpwr - totpwr; //wenn ins Netz eingespeist wird, dann ist die Differenz aus erzeugter Energie und der eingespeisten Energie, die aktuelle Leistung der Wohnung
writeDisplayBuffer(totusage,81,40);
int totpwrint = floor(totpwr); //Nachkommastellen von totpwr ignorieren und in ein int speichern
switch(totpwrint){
case 0 ... 99:
blink(1);
break;
case 100 ... 199:
blink(2);
break;
case 200 ... 299:
blink(3);
break;
case 300 ... 399:
blink(4);
break;
case 400 ... 499:
blink(5);
break;
case 500 ... 599:
blink(6);
break;
case 600 ... 900:
digitalWrite(LED,HIGH);
break;
}
}
u8g2.sendBuffer();
delay(1000);
if (testmode==true){
PVpwr=999;
batteryLevel=69; //verhindert den Sleepmodus
}
//Abfrage ob geringe PV-Produktion und niedriger Akkustand, wenn ja, dann gute Nacht (Normalbetrieb ca, 74mA)
if (PVpwr<100 || batteryLevel<50){
lowpowercounter++;
if (lowpowercounter>30){
u8g2.clearBuffer();
timeClient.update();
int hour = timeClient.getHours();
if(hour>=0 && hour<=5){ //Nachts soll der Display ganz aus sein (ca. 2,4mA)
//u8g2.drawStr(0,10,".");
//u8g2.sendBuffer();
//delay(2000);
u8g2.setPowerSave(1); //Display ganz ausschalten
delay(100);
ESP.deepSleep(7200e6); //deepsleep für 5 Stunden
}else{
u8g2.drawStr(19,12,"SLEEPING");
u8g2.drawStr(78,12,"Bat:");
writeDisplayBufferInt(batteryLevel,98,12); //INT funktion macht keine Nachkommastellen
u8g2.drawStr(120,12,"%");
u8g2.drawStr(0,28,"Verbrauch:");
writeDisplayBuffer(totpwr,81,28);
u8g2.drawStr(116,28,"W");
u8g2.drawStr(0,43,"Stand:");
String formattedTime = timeClient.getFormattedTime();
u8g2.drawStr(37,43,formattedTime.c_str());
u8g2.drawStr(80,43,"Uhr");
u8g2.drawStr(0,61,"push button to wake up");
u8g2.setFont(u8g2_font_unifont_t_weather);
u8g2.drawGlyph(0, 12, 0x0029); //Mond https://github.com/olikraus/u8g2/wiki/fnticons
u8g2.sendBuffer();
delay(100);
ESP.deepSleep(3600e6); //deepsleep für 1 Stunde, benötigt ca. 6,4mA
}
}
}else{
lowpowercounter = 0;
}
}