Stromkosten live auf LED-Matrixdisplay anzeigen

In Anbetracht rasant steigender Strompreise sollte es idealerweise einen gewissen erzieherischen Effekt in der Familie haben, die tatsächlichen Bezugskosten des laufenden Tages tagtäglich am Esstisch unter die Nase gerieben zu bekommen. Die Erkenntnis soll sein, die Stromkosten durch das eigene Verhalten beeinflussen zu können, anstelle diese als unveränderbar hinzunehmen.

Die Live-Anzeige der Stromkosten ist die ideale Ergänzung zu meinem früheren Projekt "Grünes Licht für den Eigenverbrauch". Artikel muss noch geschrieben werden

Die Umsetzung der Verbrauchsanzeige geschah innerhalb von 3 Tagen mit folgenden Mitteln:

Hardware:

ArtikelPreisBezugsquelleFunktion
Wemos D1 Mini6,99 €AZ-DeliveryMikrocontroller
LED Matrix9,49 €AZ-DeliveryAnzeigemodul
USB-Ladegerät0,00 €SchubladeSpannungsversorgung

Software:

ArtikelPreisBezugsquelleFunktion
Arduino IDE0,00 €DownloadEntwicklungsumgebung
ioBroker0,00 €HomepageGebäudeautomatisierung

Jetzt geht der Spass im ioBroker los 😀

Das folgende Script berechnet im Sekundentakt, diesen Takt gibt das SMA-EnergyMeter vor, die unterhalb des Codes abgebildeten Datenpunkte. Die für die Anzeige interessanten Datenpunkte werden gleichzeitig in das entsprechende MQTT-Topic geschrieben.

// Sektion Bezug

// Bezugszählerstand jeweils um Mitternacht
// in Datenpunkt pregardcounter24h festhalten
schedule('{"time":{"exactTime":true,"start":"00:00"},"period":{"days":1}}', async function () {
  setState("0_userdata.0.Datenpunkte.Berechnungen.sma-em.pregardcounter24h"/*Datenpunkte.Berechnungen.sma-em.pregardcounter24h*/, getState("sma-em.0.1900235801.pregardcounter").val, true);
});

// Bezugskosten des aktuellen Tages jeweils um 23:59 Uhr
// im Datenpunkt BezugskostenGestern festhalten. DP wird
// bei Änderung in history.0 für 2 Jahre gespeichert.
schedule('{"time":{"exactTime":true,"start":"23:59"},"period":{"days":1}}', async function () {
  setState("0_userdata.0.Datenpunkte.Berechnungen.sma-em.BezugskostenGestern"/*Datenpunkte.Berechnungen.sma-em.BezugskostenGestern*/, getState("0_userdata.0.Datenpunkte.Berechnungen.sma-em.BezugskostenSeit24Uhr").val, true);
});

// Bezugskosten pro Tag seit Mitternacht in Euro in
// Datenpunkt BezugskostenSeit24Uhr schreiben / Aktueller
// Preis pro kWh 32,12 Cent (Maingau Januar 2022)
//
// binde: Wert automatisch in MQTT-Topic "MatrixDisplay1.text"
// kopieren (Anzeige in ESP8266-Matrix-Display im Wohnzimmer)
on({id: 'sma-em.0.1900235801.pregardcounter', change: "ne"}, async function (obj) {
  var value = obj.state.val;
  var oldValue = obj.oldState.val;
  setState("0_userdata.0.Datenpunkte.Berechnungen.sma-em.BezugskostenSeit24Uhr"/*Datenpunkte.Berechnungen.sma-em.BezugskostenSeit24Uhr*/, Math.round((parseFloat((parseFloat(getState("sma-em.0.1900235801.pregardcounter").val) - getState("0_userdata.0.Datenpunkte.Berechnungen.sma-em.pregardcounter24h").val)) * 0.3212)*100)/100, true);
});
on({id: '0_userdata.0.Datenpunkte.Berechnungen.sma-em.BezugskostenSeit24Uhr', change: "ne"}, function (obj) {
  setState('mqtt.0.MatrixDisplay1.text', obj.state.val);
});


// Sektion Erzeugung

// Einspeisezählerstand jeweils um Mitternacht
// in Datenpunkt psurpluscounter24h festhalten
schedule('{"time":{"exactTime":true,"start":"00:00"},"period":{"days":1}}', async function () {
  setState("0_userdata.0.Datenpunkte.Berechnungen.sma-em.psurpluscounter24h"/*Datenpunkte.Berechnungen.sma-em.psurpluscounter24h*/, getState("sma-em.0.1900235801.psurpluscounter").val, true);
});

// Einspeiseverguetung des aktuellen Tages jeweils um 23:59
// Uhr im Datenpunkt EinspeiseverguetungGestern festhalten.
// DP wird bei Änderung in history.0 für 2 Jahre gespeichert.
schedule('{"time":{"exactTime":true,"start":"23:59"},"period":{"days":1}}', async function () {
  setState("0_userdata.0.Datenpunkte.Berechnungen.sma-em.EinspeiseverguetungGestern"/*Datenpunkte.Berechnungen.sma-em.EinspeiseverguetungGestern*/, getState("0_userdata.0.Datenpunkte.Berechnungen.sma-em.EinspeiseverguetungSeit24Uhr").val, true);
});

// Einspeiseverguetung pro Tag seit Mitternacht in Euro
// in Datenpunkt EinspeiseverguetungSeit24Uhr schreiben /
// Einspeisevergütung pro kWh 12,00 Cent (Inbetriebnahme 2016)
//
// binde: Wert automatisch in MQTT-Topic "MatrixDisplay1.text2"
// kopieren (Anzeige in ESP8266-Matrix-Display im Wohnzimmer)
on({id: 'sma-em.0.1900235801.psurpluscounter', change: "any"}, async function (obj) {
  var value = obj.state.val;
  var oldValue = obj.oldState.val;
  setState("0_userdata.0.Datenpunkte.Berechnungen.sma-em.EinspeiseverguetungSeit24Uhr"/*Datenpunkte.Berechnungen.sma-em.EinspeiseverguetungSeit24Uhr*/, Math.round((parseFloat((parseFloat(getState("sma-em.0.1900235801.psurpluscounter").val) - getState("0_userdata.0.Datenpunkte.Berechnungen.sma-em.psurpluscounter24h").val)) * 0.12)*100)/100, true);
});
on({id: '0_userdata.0.Datenpunkte.Berechnungen.sma-em.EinspeiseverguetungSeit24Uhr', change: "ne"}, function (obj) {
  setState('mqtt.0.MatrixDisplay1.text2', obj.state.val);
});

Ergebnis des obigen Salates sind die folgenden Datenpunkte:

Berechnete Datenpunkte im ioBroker

Sowie die zugehörigen MQTT-Topics:

Aus Datenpunkten befüllte MQTT-Topics

Nun geht die Sause in der Arduino-IDE weiter 😃

Die folgende Programmierung verleitet den ESP8266-Mikrocontroller auf der angeschlossenen LED-Matrix zur Darstellung der ganz unten im Video zu sehenden Teufelei:

#include <ESP8266WebServer.h>
#include <ESP8266WiFi.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Max72xxPanel.h>
#include <time.h>
#include <PubSubClient.h>

WiFiClient espClient; 
PubSubClient client(espClient); 

bool starte = false;
char tmp[50];
String Text = ""; // Variable Text wird angelegt.
char ssid[] = "Hier die SSID des heimischen WLANs eintragen";  //  SSID (name)
char pass[] = "Hier den Key des heimischen WLANs eintragen";       // password
const char* mqtt_server = "Hier die IP-Adresse des heimischen ioBrokers eintragen"; //Broker IP Address
const char* mqttUser = "Optional: MQTT-Benutzer"; // Broker Name
const char* mqttPassword = "Optional: MQTT-Passwort"; // Broker Passwort

void connect_to_MQTT() {
 client.setServer(mqtt_server, 1883);//MQTT Server, - Port
 client.setCallback(callback); // aktiviert das zuhören 
 // Solltet Ihr kein Benutzer/Passwort haben so müssen folgende Zeilen entfernt werden: 
 // (client.connect("MatrixDisplay1" , mqttUser, mqttPassword))
 // und mit folgender getauscht werden: 
 // if (client.connect("MatrixDisplay1");
  if (client.connect("MatrixDisplay1")) {
  Serial.println("Verbinde zum MQTT Server");
  } else {
  Serial.println("zu MQTT Server nicht verbunden ");
  if (!client.connect("MatrixDisplay1")) {
      Serial.print("Fehlgeschlagen, state=");
      Serial.print(client.state());
      Serial.println(" Versuch in 5 Sekunden nochmal");
      delay(5000);
    }
  }
  client.subscribe("MatrixDisplay1.text");
  client.subscribe("MatrixDisplay1.text2");
  client.subscribe("MatrixDisplay1.kWhLadenAktuell");
  // Es könne weitere Topics hinzugefügt werden! 
  // Alternativ steht folgende Alternative zur Verfügung: 
  //client.subscribe("topic/#"); --> es werden dann alle topics abonniert ( Beispiel: MatrixDisplay1/# ) Es werden alle Nachrichten aus Wohnzimmer empfangen! 
  
  //client.subscribe("MatrixDisplay1/woche");
  //client.subscribe("MatrixDisplay1/verbraucht");
  //client.subscribe("MatrixDisplay1/internet");
  //client.subscribe("MatrixDisplay1/zimmertemperatur");
}
    
int pinCS = D8; // CS PIN
int numberOfHorizontalDisplays = 4; //Display Anzahl
int numberOfVerticalDisplays   = 1; // Diplay höhen Anzahl
char time_value[20];
// LED Matrix Pin -> ESP8266 Pin
// Vcc            -> 3v / 5v
// Gnd            -> Gnd 
// DIN            -> D7 
// CS             -> D8  
// CLK            -> D5
Max72xxPanel matrix = Max72xxPanel(pinCS, numberOfHorizontalDisplays, numberOfVerticalDisplays);
int wait = 95; // In millis -> Scrollgeschwindigkeit! 
int spacer = 1;
int width  = 5 + spacer; // Die Panels haben 5 Pixel + Leerzeichen

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, pass);
  while (WiFi.status() != WL_CONNECTED) {
  delay(500);
  Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  
  connect_to_MQTT(); 
  
  configTime(1 * 3600, 0, "192.168.10.1", "pool.ntp.org"); // Zeitserver aus dem LAN und ein alternativer Zeitserver im Internet mit Festlegung der Zeitzone
  setenv("TZ", "CET-1CEST,M3.5.0/01,M10.5.0/02",1);
  matrix.setIntensity(5); // Lichtstärke einstellen! Bis 8 kann man sicherlich über den Wemos D1 mini ansteuern, alles darüber sollte das Netzteil aufgeteilt werden und direkt mit Strom versorgt werden!! 
  matrix.setRotation(0, 1);    
  matrix.setRotation(1, 1);    
  matrix.setRotation(2, 1);    
  matrix.setRotation(3, 1);    
}

void loop() {

  matrix.fillScreen(LOW);
  time_t now = time(nullptr);
  String time = String(ctime(&now));
  time.trim();
  Serial.println(time);
  time.substring(11,19).toCharArray(time_value, 10); 
  matrix.drawChar(2,0, time_value[0], HIGH,LOW,1); // H
  matrix.drawChar(8,0, time_value[1], HIGH,LOW,1); // HH
  matrix.drawChar(14,0,time_value[2], HIGH,LOW,1); // HH:
  matrix.drawChar(20,0,time_value[3], HIGH,LOW,1); // HH:M
  matrix.drawChar(26,0,time_value[4], HIGH,LOW,1); // HH:MM
  matrix.write(); // Bitmap an das Display senden.
  
  delay(2000);
  if (!client.connected()) {
  Serial.println("Keine Verbindung zum MQTT Server");
  connect_to_MQTT();
  }
  client.loop();
}
void display_message(String message){
   for ( int i = 0 ; i < width * message.length() + matrix.width() - spacer; i++ ) {
  //matrix.fillScreen(LOW);
  int letter = i / width;
  int x = (matrix.width() - 1) - i % width;
  int y = (matrix.height() - 8) / 2; // Zentriert die Ausgabe.
  while ( x + width - spacer >= 0 && letter >= 0 ) {
    if ( letter < message.length() ) {
    matrix.drawChar(x, y, message[letter], HIGH, LOW, 1); // HIGH LOW 
    }
    letter--;
    x -= width;
  }
  matrix.write(); // Bitmap an das Display senden.
  delay(wait/2);
  }
}
void callback(char* topic, byte* payload, unsigned int length) {
 // Zähler
 int i = 0;
 // Hilfsvariablen für die Convertierung der Nachricht in ein String
 char message_buff[100];
 
 Serial.println("Message arrived: topic: " + String(topic));
 Serial.println("Length: " + String(length,DEC));
 
 // Kopieren der Nachricht und erstellen eines Bytes mit abschließender \0
 for(i=0; i<length; i++) {
 message_buff[i] = payload[i];
 }
 message_buff[i] = '\0';
 
 // Konvertierung der Nachricht in ein String
 String msgString = String(message_buff);
 Serial.println("Payload: " + msgString);
 if (strcmp(topic,"MatrixDisplay1.text")==0){
  Text = "Stromkosten heute: " + msgString + " EUR";
  display_message(Text);
  }

  if (strcmp(topic,"MatrixDisplay1.text")==0){
  Text = "-" + msgString + " EUR";
  display_message(Text);
  }

 if (strcmp(topic,"MatrixDisplay1.text2")==0){
  Text = "Stromerzeugung heute: " + msgString + " EUR";
  display_message(Text);
  }

  if (strcmp(topic,"MatrixDisplay1.text2")==0){
  Text = "+" + msgString + " EUR";
  display_message(Text);
  }
  
  if (strcmp(topic,"MatrixDisplay1.kWhLadenAktuell")==0){
  Text = "E-Ladevorgang: " + msgString + " kWh";
  display_message(Text);
  }

  /*
  Für die Zukunft:
  Zum freimachen, das "/*" und am ende "* /" entfernen!
  
  ################################################ 
  ## Topic filtern und etwas bestimmtes machen! ##
  ################################################
  
  if (strcmp(topic,"display1.woche")==0){
  Text = "Woche Gesamt: " + msgString + "Std.";
  display_message(Text);
  display_message(Text);
  }
  if (strcmp(topic,"display1.zimmertemperatur")==0){
  Text = msgString + " Grad";
  display_message(Text);
  display_message(Text);
  long rssi = WiFi.RSSI();
  itoa(rssi,tmp,10);
  client.publish("display1/rssi",tmp);
  //display_message(Text);
  }
   
*/
 

Nun geht der Spass mit der Hardware und dem Anschliessen weiter :😍

Der Wemos D1 Mini, oder jeder andere ESP8266-Controller, wird nun anhand der im Arduino-Code festgelegten Pin-Belegung mit der LED-Matrix verbunden und per USB-Netzteil mit Spannung versorgt.

Ich bitte nun wirklich jede und jeden inständig, den im Bild zu sehenden Aufbau unter !!!KEINEN UMSTÄNDEN!!! nachzumachen. Es dient lediglich der Illustration wie man es !!!auf gar keinen Fall!!! machen darf! Danke!

Niemals nie nachmachen!

Zusammen mit dem alten Voltmeter aus dem Physikunterricht ergibt das eine zumindest interessante Kombination.

Nicht schön aber selten

Sozusagen als Abfallprodukt wird die Uhrzeit sowie die, bei aktivem Ladevorgang, an der Wallbox ins E-Auto geladene Energiemenge in kWh angezeigt.

Das Ergbnis des gesamten Reigens ist nun dies Video 😀

PS: Wenn die Anzeige im Winter auch schmerzt, so freue ich mich auf den Frühling und Sommer, wenn die Bezugskosten nahe 0 € bleiben dürften und die Familie live sieht, warum die PV-Anlage unbedingt sein musste.