Introduction: Fronius Gen24 Inverter Info Display
I wanted to have a simple display in the living room to show all family members, without an app, how much electricity the photovoltaic panels are currently producing, how much electricity we are using and whether it is wise to switch on large consumers at the moment or not. I have a Fronius Gen24 inverter with a Smart Meter which offers an API that can be read over the IP-network in the house.
It should integrate in the design of my switches and all the electronics should be placed in a wall socket. I use Busch Jaeger Reflex SI system switches, so my friend Walter 3D-printed me a perfectly fitting cover / frame for the display. He shared the 3D-file for everyone here: https://www.thingiverse.com/thing:5729702.
I got inspired by:
- https://www.simonlaw.com.au/electronics/raspberry-pi-solar-pv-usage-display/
Simon uses a really cool method to show everyone the consumption situation right away: The background colour is changed depending on whether or how much surplus there is.
- https://onderka.com/hausautomation-technik-und-elektronik/nodemcu-json-daten-von-fronius-wechselrichter-einlesen
Most of the code for this project came from Stefan, who shared it with everyone on his blog. I have only adapted the code to my needs. Most comments in the code and displayed indications are in German, so use deepl.com to get it translated into your preferred language.
Supplies
1x NodeMCU Lolin V3 Module ESP8266 ESP-12F WiFi
1x 1,3 Zoll OLED Display I2C SSH1106 Chip 128 x 64 Pixel I2C
1x 220V to 5V mini power supply compatible with Arduino
1x the suitable frame for the display
Some cables to connect everything (I used an old Cat5 network cable), some hot glue
(optional one or two LEDs to signal good or bad timing to switch on large consumers)
Step 1: Get the Display in the Switch Cover
I had no hole in the wall behind the display, so I simply extended the existing three switches and used a frame for four switches instead of the existing frame for the three swiches.
I hot-glued the 3D-printed cover into a Busch-Jaeger frame. After soldering the necessary wires onto the display I then glued the display into the cover with hot glue. I then simply ran the wires for the display behind the next switch and pulled the wire to the a distribution box, which was just nearby. The microcontroller and the power supply unit are also located there.
Step 2: Program the NodeMCU (ESP8266)
As said, I used the code from Stefan and adapted it to my needs. I am no programmer, so please show leniency in reviewing the code. The whole thing is far from perfect, but it works (at least for me).
I added three things:
- The display shuts off during nighttime.
- I added a green LED into the frame which illuminates when there is production surplus of more than 1500 Watts. (First, I planned to have a red LED too, but I skipped that in the end.)
- The controller reboots every 24h (because I noticed that sometimes it looses WIFI and does not reconnect).
Use Arduino IDE to import the code and adapt it to your needs. In the first lines you see the needed libaries (#includes...).
And I used a standard Windows Font (i.e. Arial) instead of the ones included in the display library. Please see https://rop.nl/truetype2gfx/ to convert your own font preference.
#include <ArduinoJson.h> // Arduino JSON (5.x stable, not 6.x)
#include <ESP8266WiFi.h> // ESP8266 WiFi
#include <ESP8266WiFiMulti.h> // ESP8266 Wifi-Client
#include <ESP8266HTTPClient.h> // ESP8266 HTTP-Client
#include <Wire.h> // Wire / I2C
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>
#include <Fonts/arial7pt7b.h>
#include <time.h> // time() ctime()
// WiFi SSID und PSK
ESP8266WiFiMulti WiFiMulti;
WiFiClient wifiClient;
#ifndef STASSID
#define STASSID "YOUR WIFI SSID HERE"
#define STAPSK "YOU WIFI PASSWORD HERE"
#endif
const char* ssid = STASSID;
const char* password = STAPSK;
// Wechselrichter Hostname oder IP-Adresse
const String inverterHostname = "YOUR INVERTERS IP ADDRESS HERE";
// Fronius Solar API URLs "Meter Data" und "Flow Data"
String urlMeter = "http://YOUR INVERTERS IP ADDRESS HERE/solar_api/v1/GetMeterRealtimeData.cgi?Scope=Device&DeviceId=0";
String urlFlow = "http://YOUR INVERTERS IP ADDRESS HERE/solar_api/v1/GetPowerFlowRealtimeData.fcgi";
// Display-Setup
#define i2c_Address 0x3c //initialize with the I2C addr 0x3C Typically eBay OLED's
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1 // QT-PY / XIAO
Adafruit_SH1106G oled = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Status-Leds definieren
int LEDG = D6;
//Zeit
#define MY_NTP_SERVER "at.pool.ntp.org"
#define MY_TZ "CET-1CEST,M3.5.0/02,M10.5.0/03"
time_t now; // this is the epoch
tm tm; // the structure tm holds time information in a more convient way
// setup()
void setup() {
// 1s warten
delay(1000);
pinMode(LEDG, OUTPUT); // Pin LEDG ist ein Ausgang.
Wire.begin();
Wire.setClock(400000L);
oled.begin(i2c_Address, true); // Address 0x3C default
oled.display();
delay(500);
// Clear the buffer.
oled.clearDisplay();
delay(2000);
// Serielle Schnittstelle an, 115200/8/N/1
Serial.begin(115200);
//Zeit an
configTime(MY_TZ, MY_NTP_SERVER);
//OLED an
oled.setFont(&arial7pt7b);
oled.setTextSize(1);
oled.setTextColor(SH110X_WHITE);
oled.setCursor(0, 0);
oled.println(">");
oled.println(" PV Anzeige");
oled.println(" startet ...");
oled.display();
// We start by connecting to a WiFi network
WiFi.mode(WIFI_STA);
WiFiMulti.addAP(ssid, password);
Serial.println();
Serial.println();
Serial.print("Wait for WiFi... ");
while (WiFiMulti.run() != WL_CONNECTED) {
Serial.print(".");
delay(500);
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
oled.display();
oled.clearDisplay();
oled.setFont();
oled.setTextSize(1);
oled.setTextColor(SH110X_WHITE);
oled.setCursor(0, 0);
oled.println("WiFi connected");
oled.println(WiFi.localIP());
oled.display();
delay(3000);
WiFi.setAutoReconnect(true);
WiFi.persistent(true);
}
void(* resetFunc) (void) = 0; //declare reset function at address 0 - MUST BE ABOVE LOOP
// loop()
void loop() {
// HTTP-Clients initialisieren
HTTPClient http;
const size_t capacityMeter = JSON_OBJECT_SIZE(3) + JSON_ARRAY_SIZE(2) + 60;
const size_t capacityFlow = JSON_OBJECT_SIZE(3) + JSON_ARRAY_SIZE(2) + 60;
// JSON-Puffer initialisieren
DynamicJsonBuffer jsonBufferMeter(capacityMeter);
DynamicJsonBuffer jsonBufferFlow(capacityFlow);
//Zeit aktualisieren
time(&now); // read the current time
localtime_r(&now, &tm); // update the structure tm with the current time
Serial.print("Hour: "); // check whether time/hour is correct
Serial.println(tm.tm_hour);
// URL #1 - Meter Data
http.begin(wifiClient, urlMeter);
int httpCodeMeter = http.GET();
// JSON-Antwort
String httpResponseMeter = http.getString();
// HTTP-Client beenden
http.end();
Serial.print("URL: ");
Serial.println(urlMeter);
Serial.print("HTTP Status: ");
Serial.println(httpCodeMeter);
JsonObject& jsonMeter = jsonBufferMeter.parseObject(httpResponseMeter);
if (!jsonMeter.success()) {
// JSON nicht erfolgreich geparst
Serial.println("JSON-Parser: Fehler");
} else {
Serial.println("JSON-Parser: OK");
}
// URL #2 - Flow Data
http.begin(wifiClient, urlFlow);
int httpCodeFlow = http.GET();
// JSON-Antwort
String httpResponseFlow = http.getString();
// HTTP-Client beenden
http.end();
Serial.print("URL: ");
Serial.println(urlFlow);
Serial.print("HTTP Status: ");
Serial.println(httpCodeFlow);
JsonObject& jsonFlow = jsonBufferFlow.parseObject(httpResponseFlow);
if (!jsonFlow.success()) {
// JSON nicht erfolgreich geparst
Serial.println("JSON-Parser: Fehler");
} else {
Serial.println("JSON-Parser: OK");
}
Serial.println();
// Timestamp Daten Power Meter
String ts_meter = (jsonMeter["Head"]["Timestamp"] | "Leer");
// Momentanverbrauch Phase1/2/3
float p_phase1 = (jsonMeter["Body"]["Data"]["PowerReal_P_Phase_1"] | 0);
float p_phase2 = (jsonMeter["Body"]["Data"]["PowerReal_P_Phase_2"] | 0);
float p_phase3 = (jsonMeter["Body"]["Data"]["PowerReal_P_Phase_3"] | 0);
// Spannung Phase1/2/3
float u_phase1 = (jsonMeter["Body"]["Data"]["Voltage_AC_Phase_1"] | 0);
float u_phase2 = (jsonMeter["Body"]["Data"]["Voltage_AC_Phase_2"] | 0);
float u_phase3 = (jsonMeter["Body"]["Data"]["Voltage_AC_Phase_3"] | 0);
// Strom Phase1/2/3
float i_phase1 = jsonMeter["Body"]["Data"]["Current_AC_Phase_1"].as<float>();
float i_phase2 = jsonMeter["Body"]["Data"]["Current_AC_Phase_2"].as<float>();
float i_phase3 = jsonMeter["Body"]["Data"]["Current_AC_Phase_3"].as<float>();
// Momentane Leistung Summe
float p_summe = (jsonMeter["Body"]["Data"]["PowerReal_P_Sum"] | 0);
// Momentane Blindleistung Summe
float p_sum_ap = (jsonMeter["Body"]["Data"]["PowerApparent_S_Sum"] | 0);
// Durchschnitt Netzfrequenz
float net_freq = jsonMeter["Body"]["Data"]["Frequency_Phase_Average"].as<float>();
// Timestamp Daten Power Flow
String ts_flow = (jsonFlow["Head"]["Timestamp"] | "Leer");
// Energie Tag
float p_day = (jsonFlow["Body"]["Data"]["Inverters"]["1"]["E_Day"] | 0);
// Energie Jahr
float p_year = (jsonFlow["Body"]["Data"]["Inverters"]["1"]["E_Year"] | 0);
// Energie Gesamt
float p_total = (jsonFlow["Body"]["Data"]["Inverters"]["1"]["E_Total"] | 0);
// Einspeisung / Bezug: Negativ Einspeisung, positiv Bezug
float in_out = (jsonFlow["Body"]["Data"]["Site"]["P_Grid"] | 0);
// Verbrauch momentan
float cons = (jsonFlow["Body"]["Data"]["Site"]["P_Load"] | 0);
// Produktion momentan
float prod = (jsonFlow["Body"]["Data"]["Site"]["P_PV"] | 0);
// Autonomie (% Produktion an Verbrauch)
float akku = (jsonFlow["Body"]["Data"]["Inverters"]["1"]["SOC"] | 0);
// Autonomie (% Produktion an Verbrauch)
float autonomy = (jsonFlow["Body"]["Data"]["Site"]["rel_Autonomy"] | 0);
// Selbstverbrauch (% Eigenverbrauch an Produktion)
float selfcons = (jsonFlow["Body"]["Data"]["Site"]["rel_SelfConsumption"] | 0);
// Ausgabe seriell
Serial.print("Timestamp Meter: ");
Serial.println(ts_meter);
Serial.print("Timestamp Flow: ");
Serial.println(ts_flow);
Serial.print("Spannung Phase 1: ");
Serial.print(u_phase1);
Serial.println(" V");
Serial.print("Spannung Phase 2: ");
Serial.print(u_phase2);
Serial.println(" V");
Serial.print("Spannung Phase 3: ");
Serial.print(u_phase3);
Serial.println(" V");
Serial.print("Strom Phase 1: ");
Serial.print(i_phase1);
Serial.println(" A");
Serial.print("Strom Phase 2: ");
Serial.print(i_phase2);
Serial.println(" A");
Serial.print("Strom Phase 3: ");
Serial.print(i_phase3);
Serial.println(" A");
Serial.print("Leistung Phase 1: ");
Serial.print(p_phase1 * -1);
Serial.println(" W");
Serial.print("Leistung Phase 2: ");
Serial.print(p_phase2 * -1);
Serial.println(" W");
Serial.print("Leistung Phase 3: ");
Serial.print(p_phase3 * -1);
Serial.println(" W");
Serial.print("Leistung Summe: ");
Serial.print(p_summe * -1);
Serial.println(" W");
Serial.print("Scheinleistung: ");
Serial.print(p_sum_ap);
Serial.println(" W");
Serial.print("Netzfrequenz: ");
Serial.print(net_freq);
Serial.println(" Hz");
Serial.print("Produktion Tag: ");
Serial.print(p_day / 1000);
Serial.println(" kWh");
Serial.print("Produktion Jahr: ");
Serial.print(p_year / 1000);
Serial.println(" kWh");
Serial.print("Produktion Gesamt: ");
Serial.print(p_total / 1000);
Serial.println(" kWh");
Serial.println();
// Negativ Einspeisung, positiv Bezug
if (in_out >= 0) {
// Bezug
Serial.print("Strom Bezug: ");
Serial.print(in_out);
Serial.println(" W");
Serial.println("Strom Einspeisung: 0.00 W");
} else {
// Einspeisung
Serial.println("Strom Bezug: 0.00 W");
Serial.print("Strom Einspeisung: ");
Serial.print(in_out * -1);
Serial.println(" W");
}
Serial.print("Verbrauch Haus: ");
Serial.print(cons * -1);
Serial.println(" W");
Serial.print("Leistung PV: ");
Serial.print(prod);
Serial.println(" W");
Serial.print("Autonomie: ");
Serial.print(autonomy);
Serial.println(" %");
Serial.print("Eigenverbrauch: ");
Serial.print(selfcons);
Serial.println(" %");
Serial.println();
// OLED
oled.clearDisplay();
if (tm.tm_hour < 6) {
oled.clearDisplay();
oled.display();
}
else if (tm.tm_hour > 22) {
oled.clearDisplay();
oled.display();
}
else {
oled.setFont(&arial7pt7b);
oled.setTextColor(SH110X_WHITE);
oled.setCursor(0, -7);
oled.println();
oled.print("Use: ");
oled.print(cons * -1, 0);
oled.print(" W");
oled.setCursor(0, 26);
oled.print("PV: ");
oled.setCursor(55, 26);
oled.print(prod, 0);
oled.print(" W");
oled.setCursor(0, 43);
oled.print("Akku: ");
oled.setCursor(55, 43);
oled.print(akku, 0);
oled.print(" %");
// Negativ Einspeisung, positiv Bezug
if (in_out >= 0) {
// Bezug
oled.setCursor(0, 60);
oled.print("Bezug: ");
oled.setCursor(55, 60);
oled.print(in_out, 0);
oled.print(" W");
} else {
// Einspeisung
oled.setCursor(0, 60);
oled.print("Einsp: ");
oled.setCursor(55, 60);
oled.print(in_out * -1, 0);
oled.print(" W");
}
oled.display();
}
// zu wenig Einspeisung, positiv Bezug
if (in_out >= -1500) {
// Bezug
// digitalWrite(LEDR, HIGH); // Schalte die LED rot an.
digitalWrite(LEDG, LOW); // Schalte die LED gruen aus.
} else {
// ausreichend Einspeisung
digitalWrite(LEDG, HIGH); // Schalte die LED rot an.
// digitalWrite(LEDR, LOW); // Schalte die LED gruen aus.
}
// 30s warten
delay(20000);
if ( millis() >= 86400000) resetFunc(); //call reset every 24 hours (1 Day)
// Ende loop()
}
Step 3: Wire Everything
Wire everything correctly and ...voila!