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:

  1. 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.

  1. 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:

  1. The display shuts off during nighttime.
  2. 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.)
  3. 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!