Alternate Software for Growatt ShineWifi-S to MQTT

I successfully flashed the Growatt ShineWiFi-S Stick with the software from https://github.com/otti/Growatt_ShineWiFi-S. It is connected to my network and MQTT is also sending data. So he is also registered in Hassio. It is also displayed in the MQTT Explorer. Unfortunately, I am still a beginner when it comes to MQTT. How can I now display the data in Hassio?! Thanks for Help!

Code in Arduino:

/*
Datei -> Voreinstellungen -> Zusaetzliche Boardverwalter-URLs -> "http://arduino.esp8266.com/stable/package_esp8266com_index.json"
Werkzeuge -> Board -> Boardverwalter -> ESP8266

Used Libs
----------

Download MQTT Client from
https://github.com/knolleary/pubsubclient
Install --> Sketch -> Bibliothek einbinden -> .Zib Bibliotherk hinzufuegen

Download ModbusMaster by Doc Walker

Board: Generic ESP8266 Module
Flash Mode: DIO
Cristal Freq:: 26 MHz
Flash Freq: 40 MHz
Upload Using: Serial
CPU Freq: 80 MHz
Flash Size: 4 MB (FS:none OTA~1019KB)
UploadSpeed: 115200

Thanks to Jethro Kairys
https://github.com/jkairys/growatt-esp8266

File -> "Show verbose output during:" "compilation".
This will show the path to the binary during compilation 
e.g. C:\Users\<username>\AppData\Local\Temp\arduino_build_533155


2019-10-16
*/

// ---------------------------------------------------------------
// User configuration area start
// ---------------------------------------------------------------

// Setting this define to 0 will disable the MQTT functionality
#define MQTT_SUPPORTED 1

// Setting this define to 1 will ping the default gateway periodically 
// if the ping is not successful, the wifi connection will be reestablished
#define PINGER_SUPPORTED 1

// Setting this define to 1 will enable the debug output via the serial port.
// The serial port is the same used for the communication to the inverter.
// Enabling this feature can cause problems with the inverter communication!
// For ShineWiFi-S everything seems to work perfect even though this flag is set
#define ENABLE_DEBUG_OUTPUT 1

// Setting this define to 1 will enable a web page (<ip>/debug) where debug messages can be displayed
#define ENABLE_WEB_DEBUG 1


// Setting this flag to 1 will simulate the inverter
// This could be helpful if it is night and the inverter is not working or
// during development where the stick is not connected to the inverter
#define SIMULATE_INVERTER 0

// Data of the Wifi access point
#define WIFI_SSID         "my_ssid"
#define WIFI_PASSWORD     "my_password"
#define HOSTNAME          "Growatt"

// Username and password for firmware update
#define UPDATE_USER       "my_user"
#define UPDATE_PASSWORD   "My_password"

#if MQTT_SUPPORTED == 1 
#define MQTT_SERVER       "192.168.2.150"
#define MQTT_TOPIC        "growatt_pv"
#endif

#if PINGER_SUPPORTED == 1
#define GATEWAY_IP IPAddress(192, 168, 2, 1)
#endif

#if ENABLE_WEB_DEBUG == 1
    char acWebDebug[1024] = "";
    uint16_t u16WebMsgNo = 0;
    #define WEB_DEBUG_PRINT(s) {if( (strlen(acWebDebug)+strlen(s)+50) < sizeof(acWebDebug) ) sprintf(acWebDebug, "%s#%i: %s\n", acWebDebug, u16WebMsgNo++, s);}
#else
 #define WEB_DEBUG_PRINT(s) ;
#endif



// ---------------------------------------------------------------
// User configuration area end
// ---------------------------------------------------------------


#include <ESP8266WiFi.h>

#if MQTT_SUPPORTED == 1
#include <PubSubClient.h>
#if MQTT_MAX_PACKET_SIZE < 512 
#error change MQTT_MAX_PACKET_SIZE to 512
// C:\Users\<user>\Documents\Arduino\libraries\pubsubclient-master\src\PubSubClient.h
#endif
#endif

#include "Growatt.h"
#include <ESP8266HTTPUpdateServer.h>

#include "index.h"

#if PINGER_SUPPORTED == 1
#include <Pinger.h>
#include <PingerResponse.h>
#endif

#include <stdio.h>
#include <stdlib.h>

#define LED_GN 0  // GPIO0
#define LED_RT 2  // GPIO2
#define LED_BL 16 // GPIO16

#define BUTTON 0 // GPIO0

#define NUM_OF_RETRIES 5
char u8RetryCounter = NUM_OF_RETRIES;

long lAccumulatedEnergy = 0;

const char* update_path = "/firmware";
uint16_t u16PacketCnt = 0;
#if PINGER_SUPPORTED == 1
Pinger pinger;
#endif

WiFiClient   espClient;
#if MQTT_SUPPORTED == 1 
PubSubClient MqttClient(espClient);
#endif
Growatt      Inverter;
ESP8266WebServer httpServer(80);
ESP8266HTTPUpdateServer httpUpdater;

char JsonString[MQTT_MAX_PACKET_SIZE] = "{\"Status\": \"Disconnected\" }";

// -------------------------------------------------------
// Check the WiFi status and reconnect if necessary
// -------------------------------------------------------
void WiFi_Reconnect()
{
  uint16_t cnt = 0;

  if( WiFi.status() != WL_CONNECTED )
  {
    digitalWrite(LED_GN, 0);
    
    WiFi.mode(WIFI_STA);
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

    while (WiFi.status() != WL_CONNECTED)
    {
      delay(200);
      #if ENABLE_DEBUG_OUTPUT == 1
      Serial.print("x");
      #endif
      digitalWrite(LED_RT, !digitalRead(LED_RT)); // toggle red led on WiFi (re)connect
    }

    #if ENABLE_DEBUG_OUTPUT == 1
    Serial.println("");
    WiFi.printDiag(Serial);
    Serial.print("local IP:");
    Serial.println(WiFi.localIP());
    Serial.print("Hostname: ");
    Serial.println(HOSTNAME);
    #endif

    WEB_DEBUG_PRINT("WiFi reconnected")

    #if MQTT_SUPPORTED == 1 
    MqttClient.setServer(MQTT_SERVER, 1883);
    #endif
    
    digitalWrite(LED_RT, 1);
  }
}

// Conection can fail after sunrise. The stick powers up before the inverter.
// So the detection of the inverter will fail. If no inverter is detected, we have to retry later (s. loop() )
// The detection without running inverter will take several seconds, because the ModBus-Lib has a timeout of 2s 
// for each read access (and we do several of them). The WiFi can crash during this function. Perhaps we can fix 
// this by using the callback function of the ModBus-Lib
void InverterReconnect(void)
{
  // Baudrate will be set here, depending on the version of the stick
  Inverter.begin(Serial);

  #if ENABLE_WEB_DEBUG == 1
  if( Inverter.GetWiFiStickType() == ShineWiFi_S )
    WEB_DEBUG_PRINT("ShineWiFi-S (Serial) found")
  else if( Inverter.GetWiFiStickType() == ShineWiFi_X )
    WEB_DEBUG_PRINT("ShineWiFi-X (USB) found")
  else
    WEB_DEBUG_PRINT("Error: Undef. Stick")
  #endif
}

// -------------------------------------------------------
// Check the Mqtt status and reconnect if necessary
// -------------------------------------------------------
#if MQTT_SUPPORTED == 1 
void MqttReconnect() 
{
  // Loop until we're reconnected
  while (!MqttClient.connected()) 
  {
    if( WiFi.status() != WL_CONNECTED )
      break;
    
    #if ENABLE_DEBUG_OUTPUT == 1
    Serial.print("Attempting MQTT connection...");
    #endif
    
    // Attempt to connect with last will
    // MQTT user and password config
    if (MqttClient.connect("Growatt", "my_mqtt_user", "my_mqtt_password", MQTT_TOPIC, 1, 1, "{\"Status\": \"Disconnected\" }"))
    {
      #if ENABLE_DEBUG_OUTPUT == 1
      Serial.println("connected");
      #endif
    } 
    else
    {
      #if ENABLE_DEBUG_OUTPUT == 1
      Serial.print("failed, rc=");
      Serial.print(MqttClient.state());
      Serial.println(" try again in 5 seconds");
      #endif
      WEB_DEBUG_PRINT("MQTT Connect failed")
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}
#endif

// -------------------------------------------------------
// Will be executed once after power on
// -------------------------------------------------------
void setup()
{
  pinMode(LED_GN, OUTPUT);
  pinMode(LED_RT, OUTPUT);
  pinMode(LED_BL, OUTPUT);

  WEB_DEBUG_PRINT("Setup()")
  
  WiFi.hostname(HOSTNAME);
  while (WiFi.status() != WL_CONNECTED)
    WiFi_Reconnect();

  httpServer.on("/status", SendJsonSite);
  httpServer.on("/postCommunicationModbus", SendPostSite);
  httpServer.on("/postCommunicationModbus_p", HTTP_POST, handlePostData);
  httpServer.on("/setAccumulatedEnergy", HTTP_POST, vSetAccumulatedEnergy);
  httpServer.on("/", MainPage);
  #if ENABLE_WEB_DEBUG == 1
  httpServer.on("/debug", SendDebug);
  #endif

  InverterReconnect();

  httpUpdater.setup(&httpServer, update_path, UPDATE_USER, UPDATE_PASSWORD);
  httpServer.begin();
}

void SendJsonSite(void)
{
  httpServer.send(200, "application/json", JsonString);
}

#if ENABLE_WEB_DEBUG == 1
void SendDebug(void)
{
  httpServer.send(200, "text/plain", acWebDebug);
}
#endif

void MainPage(void)
{
  httpServer.send(200, "text/html", MAIN_page);
}

void SendPostSite(void)
{
  httpServer.send(200, "text/html",
                  "<form action=\"/postCommunicationModbus_p\" method=\"POST\">"
                  "<input type=\"text\" name=\"reg\" placeholder=\"RegDec\"></br>"
                  "<input type=\"text\" name=\"val\" placeholder=\"ValueDec(16Bit)\"></br>"
                  "<input type=\"checkbox\" id=\"rd\" name=\"rd\" value=\"Rd\" checked>"
                  "<label for=\"rd\"> Read</label></br>"
                  "<input type=\"submit\" value=\"Go\">"
                  "</form>");
}

void vSetAccumulatedEnergy()
{
  if (httpServer.hasArg("AcE"))
  {
    // only react if AcE is transmitted
    char * msg;
    msg = JsonString;

    if (lAccumulatedEnergy <= 0)
    {
      lAccumulatedEnergy = httpServer.arg("AcE").toInt() * 3600;
      sprintf(msg, "Setting accumulated value to %d", httpServer.arg("AcE").toInt());
    }
    else
    {
      sprintf(msg, "Error: AccumulatedEnergy was not Zero or lower. Set to 0 first.");
    }

    if (httpServer.arg("AcE").toInt() == 0)
    {
      lAccumulatedEnergy = -1000 * 3600;
      sprintf(msg, "Prepared to set AcE. You can change it as long as it is negative.");
    }

    httpServer.send(200, "text/plain", msg);
  }
  else
  {
    httpServer.send(400, "text/plain", "400: Invalid Request"); // The request is invalid, so send HTTP status 400
  }
}

void handlePostData()
{
  char * msg;
  uint16_t u16Tmp;

  msg = JsonString;
  msg[0] = 0;

  if (!httpServer.hasArg("reg") || !httpServer.hasArg("val")) 
  {
    // If the POST request doesn't have data
    httpServer.send(400, "text/plain", "400: Invalid Request"); // The request is invalid, so send HTTP status 400
    return;
  }
  else
  {
    if (httpServer.arg("rd") == "Rd")
    {
      if (Inverter.ReadHoldingReg(httpServer.arg("reg").toInt(), & u16Tmp))
      {
        sprintf(msg, "Read register %d with value %d", httpServer.arg("reg").toInt(), u16Tmp);
      } 
      else 
      {
        sprintf(msg, "Read register %d impossible - not connected?", httpServer.arg("reg").toInt());
      }
    }
    else
    {
      if (Inverter.WriteHoldingReg(httpServer.arg("reg").toInt(), httpServer.arg("val").toInt()))
        sprintf(msg, "Wrote Register %d to a value of %d!", httpServer.arg("reg").toInt(), httpServer.arg("val").toInt());
      else
        sprintf(msg, "Did not write Register %d to a value of %d - fault!", httpServer.arg("reg").toInt(), httpServer.arg("val").toInt());
    }
    httpServer.send(200, "text/plain", msg);
    return;
  }
}

// -------------------------------------------------------
// Main loop
// -------------------------------------------------------
long Timer500ms = 0;
long Timer5s = 0;
long Timer2m = 0;

void loop()
{
  long now = millis();
  long lTemp;
  char readoutSucceeded;
  
  WiFi_Reconnect();
  
  #if MQTT_SUPPORTED == 1
  MqttReconnect();
  MqttClient.loop();
  #endif
  
  httpServer.handleClient();

  // Toggle green LED with 1 Hz (alive)
  // ------------------------------------------------------------
  if( (now - Timer500ms) > 500 )
  {   
    if( WiFi.status() == WL_CONNECTED )
      digitalWrite(LED_GN, !digitalRead(LED_GN));
    else
      digitalWrite(LED_GN, 0);
    
    Timer500ms = now;
  }

  // InverterReconnect() takes a long time --> wifi will crash
  // Do it only every two minutes
  if( (now - Timer2m) > (1000 * 60 * 2) )
  {
    if( Inverter.GetWiFiStickType() == Undef_stick )
      InverterReconnect();
    Timer2m = now;
  }

    // Read Inverter every 5 s
  // ------------------------------------------------------------
  if( (now - Timer5s) > 5000)
  {
    #if MQTT_SUPPORTED == 1
    if (MqttClient.connected() && (WiFi.status() == WL_CONNECTED) && (Inverter.GetWiFiStickType()) )
    #else
    if( 1 )
    #endif
    {
      readoutSucceeded = 0;
      while ((u8RetryCounter) && !(readoutSucceeded))
      {
        #if SIMULATE_INVERTER == 1
        if( 1 ) // do it always
        #else
        if( Inverter.UpdateData() ) // get new data from inverter
        #endif
        {
          WEB_DEBUG_PRINT("UpdateData() successful")
          u16PacketCnt++;
          u8RetryCounter = NUM_OF_RETRIES;
          CreateJson(JsonString);
    
          #if MQTT_SUPPORTED == 1 
          MqttClient.publish(MQTT_TOPIC, JsonString, true);
          #endif
      

          // if we got data, calculate the accumulated energy
          lTemp = (now - Timer5s) * Inverter.GetAcPower();      // we now get an increment in milliWattSeconds
          lTemp /= 1000;                                        // WattSeconds
          lAccumulatedEnergy += lTemp;                          // WattSeconds
          
          digitalWrite(LED_RT, 0); // clear red led if everything is ok
          // leave while-loop
          readoutSucceeded = 1;
        }
        else
        {
          WEB_DEBUG_PRINT("UpdateData() NOT successful")
          if(u8RetryCounter)
          {
            u8RetryCounter--;
          }
          else
          {
            WEB_DEBUG_PRINT("Retry counter\n")
            sprintf(JsonString, "{\"Status\": \"Disconnected\" }");
            #if MQTT_SUPPORTED == 1 
            MqttClient.publish(MQTT_TOPIC, JsonString, true);
            #endif
            digitalWrite(LED_RT, 1); // set red led in case of error
          }
        }
      }
      u8RetryCounter = NUM_OF_RETRIES;
    }

    #if MQTT_SUPPORTED == 1
    if (!MqttClient.connected())
      digitalWrite(LED_RT, 1);
    else
      digitalWrite(LED_RT, 0);
    #endif
    
    #if PINGER_SUPPORTED == 1
    //frequently check if gateway is reachable
    if (pinger.Ping(GATEWAY_IP) == false) 
      WiFi.disconnect();
    #endif

    Timer5s = now;
  }
}

void CreateJson(char *Buffer)
{
  Buffer[0] = 0; // Terminate first byte

#if SIMULATE_INVERTER != 1
  sprintf(Buffer, "{\r\n");
  switch( Inverter.GetStatus() )
  {
    case GwStatusWaiting:
      sprintf(Buffer, "%s  \"Status\": \"Waiting\",\r\n", Buffer);
      break;
    case GwStatusNormal: 
      sprintf(Buffer, "%s  \"Status\": \"Normal\",\r\n", Buffer);
      break;
    case GwStatusFault:
      sprintf(Buffer, "%s  \"Status\": \"Fault\",\r\n", Buffer);
      break;
    default:
      sprintf(Buffer, "%s  \"Status\": \"%d\",\r\n", Buffer, Inverter.GetStatus());
  }
  
  sprintf(Buffer, "%s  \"DcVoltage\": %.1f,\r\n",       Buffer, Inverter.GetDcVoltage());
  sprintf(Buffer, "%s  \"AcFreq\": %.3f,\r\n",          Buffer, Inverter.GetAcFrequency());
  sprintf(Buffer, "%s  \"AcVoltage\": %.1f,\r\n",       Buffer, Inverter.GetAcVoltage());
  sprintf(Buffer, "%s  \"AcPower\": %.1f,\r\n",         Buffer, Inverter.GetAcPower());
  sprintf(Buffer, "%s  \"EnergyToday\": %.1f,\r\n",     Buffer, Inverter.GetEnergyToday());
  sprintf(Buffer, "%s  \"EnergyTotal\": %.1f,\r\n",     Buffer, Inverter.GetEnergyTotal());
  sprintf(Buffer, "%s  \"OperatingTime\": %u,\r\n",     Buffer, Inverter.GetOperatingTime());
  sprintf(Buffer, "%s  \"Temperature\": %.1f,\r\n",     Buffer, Inverter.GetInverterTemperature());
  sprintf(Buffer, "%s  \"AccumulatedEnergy\": %d,\r\n", Buffer, lAccumulatedEnergy / 3600);
  sprintf(Buffer, "%s  \"Cnt\": %u\r\n",                Buffer, u16PacketCnt);
  sprintf(Buffer, "%s}\r\n", Buffer);
#else
  #warning simulating the inverter
  sprintf(Buffer, "{\r\n");
  sprintf(Buffer, "%s  \"Status\": \"Normal\",\r\n",     Buffer);
  sprintf(Buffer, "%s  \"DcVoltage\": 70.5,\r\n",        Buffer);
  sprintf(Buffer, "%s  \"AcFreq\": 50.00,\r\n",          Buffer);
  sprintf(Buffer, "%s  \"AcVoltage\": 230.0,\r\n",       Buffer);
  sprintf(Buffer, "%s  \"AcPower\": 0.00,\r\n",          Buffer);
  sprintf(Buffer, "%s  \"EnergyToday\": 0.3,\r\n",       Buffer);
  sprintf(Buffer, "%s  \"EnergyTotal\": 49.1,\r\n",      Buffer);
  sprintf(Buffer, "%s  \"OperatingTime\": 123456,\r\n",  Buffer);
  sprintf(Buffer, "%s  \"Temperature\": 21.12,\r\n",     Buffer);
  sprintf(Buffer, "%s  \"AccumulatedEnergy\": 320,\r\n", Buffer);
  sprintf(Buffer, "%s  \"Cnt\": %u\r\n",                 Buffer, u16PacketCnt);
  sprintf(Buffer, "%s}", Buffer);
#endif // SIMULATE_INVERTER
}
1 Like

If it was auto-discovered in Home Assistant, you should now have a number of sensors available for the device.

Look in Developer Tools on the States tab, you should find all the sensors there. Then just add them to a card for Lovelace using the UI.

They should also be visible in Settings, Devices and Services, MQTT and you can add them to a Dashboard from there.

If you can’t find the sensors in either spot (in other words it didn’t auto-discover the device) then you will manually need to add them via configuration.yaml:

Unfortunately, the sensors are not found automatically.
I have no plan how to add the sensors manually :worried:

I am going to assume that you have already configured MQTT correctly - given you have a heap of sensors there already.

Something like this in your configuration.yaml:

mqtt:
  sensor:
    - name: "Growatt Status"
      state_topic: "tele/so_licht_einfahrt/growatt_pv"
      value_template: "{{ value_json.Status }}"

    - name: "Growatt DC Voltage"
      unit_of_measurement: "V"
      state_topic: "tele/so_licht_einfahrt/growatt_pv"
      value_template: "{{ value_json.DcVoltage }}"

And so on for the rest of the sensors, then reload HA or just MQTT I think you can do in the latest HA version.

There are examples in the linked page above.

1 Like

Thank you for your help! It was almost perfect. Only the “state_topic:” has to be changed because it is not in any subgroup. Here is the changed code again. Maybe it’s useful to someone.

mqtt:
  sensor:
    - name: "Growatt Status"
      unique_id: "growatt_status"
      state_topic: "growatt_pv"
      value_template: "{{ value_json.Status }}"

    - name: "Growatt DC Voltage"
      unique_id: "growatt_dc_voltage"
      unit_of_measurement: "V"
      device_class: "voltage"
      state_topic: "growatt_pv"
      value_template: "{{ value_json.DcVoltage }}"
      
    - name: "Growatt AC Frequency"
      unique_id: "growatt_ac_frequency"
      unit_of_measurement: "Hz"
      state_class: "measurement"
      state_topic: "growatt_pv"
      device_class: "frequency"
      value_template: "{{ value_json.AcFreq }}"  

    - name: "Growatt AC Voltage"
      unique_id: "growatt_ac_voltage"
      unit_of_measurement: "V"
      state_class: "measurement"
      device_class: "voltage"
      state_topic: "growatt_pv"
      value_template: "{{ value_json.AcVoltage }}"        
  
    - name: "Growatt AC Power"
      unique_id: "growatt_ac_power"
      unit_of_measurement: "W"
      state_class: "measurement"
      device_class: "power"
      state_topic: "growatt_pv"
      value_template: "{{ value_json.AcPower }}"              
      
    - name: "Growatt Energy Today"
      unique_id: "growatt_energy_today"
      unit_of_measurement: "kWh"
      state_class: "total_increasing"
      device_class: "energy"
      state_topic: "growatt_pv"
      value_template: "{{ value_json.EnergyToday }}"  
      
    - name: "Growatt Energy Total"
      unique_id: "growatt_energy_total"
      unit_of_measurement: "kWh"
      state_class: "total_increasing"
      device_class: "energy"
      state_topic: "growatt_pv"
      value_template: "{{ value_json.EnergyTotal }}"       
      
    - name: "Growatt Temperature"
      unique_id: "growatt_temperature"
      unit_of_measurement: "°C"
      state_class: "measurement"
      device_class: "temperature"
      state_topic: "growatt_pv"
      value_template: "{{ value_json.Temperature }}"

Lol! Yeah I had my MQTT explorer open at the same time as your screenshot - got the paths for the topics muddled. All good.

Hey, incase it is useful, here is a list that you someone else can copy paste into their growat.ymal file.
Issue I faced is ; these data are lacking ‘‘Unique ID’’, should the unique id be added to this .ymal code? I looked into the documentation, it was not clear for me how to do it.


mqtt:

  sensor:

    - name: "Growatt"

      state_topic: "growatt/data"

      value_template: "{{ value_json.Status }}"

    - name: "Solarpower"

      unit_of_measurement: "W"

      state_topic: "growatt/data"

      value_template: "{{ value_json.solarpower }}"

    - name: "PV1voltage"

      unit_of_measurement: "V"

      state_topic: "growatt/data"

      value_template: "{{ value_json.pv1voltage }}"

    - name: "PV1current"

      unit_of_measurement: "A"

      state_topic: "growatt/data"

      value_template: "{{ value_json.pv1current }}"

    - name: "PV1power"

      unit_of_measurement: "W"

      state_topic: "growatt/data"

      value_template: "{{ value_json.pv1power }}"

    - name: "PV2voltage"

      unit_of_measurement: "V"

      state_topic: "growatt/data"

      value_template: "{{ value_json.pv2voltage }}"

    - name: "PV2current"

      unit_of_measurement: "A"

      state_topic: "growatt/data"

      value_template: "{{ value_json.pv2current }}"

    - name: "PV2power"

      unit_of_measurement: "W"

      state_topic: "growatt/data"

      value_template: "{{ value_json.pv2power }}"

    - name: "AC outputpower"

      unit_of_measurement: "W"

      state_topic: "growatt/data"

      value_template: "{{ value_json.outputpower }}"

    - name: "gridfrequency"

      unit_of_measurement: "Hz"

      state_topic: "growatt/data"

      value_template: "{{ value_json.gridfrequency }}"

    - name: "gridvoltage"

      unit_of_measurement: "V"

      state_topic: "growatt/data"

      value_template: "{{ value_json.gridvoltage }}"

    - name: "energytoday"

      unit_of_measurement: "kWh"

      state_topic: "growatt/data"

      value_template: "{{ value_json.energytoday }}"

    - name: "energytotal Ac"

      unit_of_measurement: "kWh"

      state_topic: "growatt/data"

      value_template: "{{ value_json.energytotal }}"

    - name: "energytotal PV1"

      unit_of_measurement: "kWh"

      state_topic: "growatt/data"

      value_template: "{{ value_json.pv1energytotal }}"

    - name: "totalworktime"

      unit_of_measurement: "Hours"

      state_topic: "growatt/data"

      value_template: "{{ value_json.totalworktime }}"

    - name: "inverter temperature"

      unit_of_measurement: "°C"

      state_topic: "growatt/data"

      value_template: "{{ value_json.tempinverter }}"

    - name: "inverter temperature2"

      unit_of_measurement: "°C"

      state_topic: "growatt/data"

      value_template: "{{ value_json.tempipm }}"

I have extended the code above.

how did you flash the shine-s ? how did you connect it to pc to flash it and with what software

here you will find all the information you need:
Link

I use the signal ''energytoday ‘’ in HA energy dashboard. In early mornings, just after invertor turns on, there is a jitter in the signal ( first it is set to zero then it jumps back to last know value before invertor waking up)

How can I make sure the signal ''energytoday ‘’ is rested automatically at certain time?

- name: "energytoday"

      unit_of_measurement: "kWh"

      state_topic: "growatt/data"

      value_template: "{{ value_json.energytoday }}"

      device_class: "energy"

      state_class: "total_increasing"