SolarBridge: a Wemos Base bridge between GroWatt and Toon

Forum about Toon hardware, both versions 1 and 2 of Toon

Moderators: marcelr, TheHogNL, Toonz

marcelr
Global Moderator
Global Moderator
Posts: 1153
Joined: Thu May 10, 2012 10:58 pm
Location: Ehv

Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon

Post by marcelr »

At the end of the day you want to get the total energy correct. That means that you should keep track of the amount of energy produced rather than the acute power. Maybe it's an idea to extract cumulative energy data, subtract the last number from the previous, calculate the average power in that period and then emit enough pulses at the right frequency to mimic that amount of energy and power (keeping track of the remainder, since you will hardly ever get an amount of energy that is an integer multiple of the energy per pulse). Then you get the power right AND the total energy. The toon software averages power over 5 minutes, so that should give a smooth enough number. Energy is stored on a per-hour basis only, so that should not be an issue either.

Just make sure you get the total number of pulses correct.
marcelr
Global Moderator
Global Moderator
Posts: 1153
Joined: Thu May 10, 2012 10:58 pm
Location: Ehv

Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon

Post by marcelr »

My previous post was a bit of a lecture (that's what I do for a living ;-) ). Got bored with all the skype/zoom/teams/bigblueblob stuff from today, so I took the liberty to add a bit of (pseudo) code to your arduino code.

Each added section is commented with something along these lines: // add: .... // end addition
Oh by the way, do declarations need to be global on an arduino (clone)?

The main bits are inside the routine getdata(), plus some declarations up front, hope it's useful.
My indentation is FUBAR, sorry about that.

Code: Select all

/******************************************************************************************
 * SolarBridge by Oepi-Loepi
 * All rights reserved
 * Do not use for commercial purposes
 * 
 * Solar Bridge for GroWatt solar converters
 * This bridge will get the data from the GroWatt web interface (API) and create S0 pulses and led flashes
 * Each pulse/flash will suggest 1 Watt
 * 
 * On GPIO 4 a resistor (330 ohm and red LED are connected in series 
 * PIN D2 ----  Resistor 33Ohm) ---- (Long LED lead ---- LED ---- Short LED Lead) ----- GND
 * 
 * On GPIO 5 a PC817 optocoupler is connected
 * PIN D1 ----  PC817 (anode, pin 1, spot)
 * GND -------  PC817 (cathode, pin 2)
 * 
 * Pin 3 and 4 of the PC817 will be a potential free contact
 * 
 * After uploading the sketch to the Wemos D1 mini, connect to the AutoConnectAP wifi. 
 * Goto 192.168.4.1 in a webbrowser and fill in all data including GroWatt credentials.
 * 
*/



#include <FS.h>                   //this needs to be first, or it all crashes and burns...

#include <ESP8266WiFi.h>          //https://github.com/esp8266/Arduino
//needed for library
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>          //https://github.com/tzapu/WiFiManager
#include "ESP8266HTTPClient.h"
#include <ArduinoJson.h>          //https://github.com/bblanchon/ArduinoJson

//define your default values here, if there are different values in config.json, they are overwritten.
char GroWattPass[40] = "password";
char GroWattName[40] = "login";

char static_ip[18] = "192.168.10.2";
char static_gw[18] = "192.168.10.1";
char static_sn[18] = "255.255.255.0";
char static_dn[18] = "8.8.8.8";
bool dhcp = false;

bool ConnectionPossible=false;

char* wifiHostname = "SolarBridge";

String JSESSIONID;
String SERVERID;

bool cookiestep= false;

bool reset1 = false;
bool reset2 = false;

int led = 4;
int meteradapter = 5;

// Add:  Need to do floating point arithmetic, integer is not accurate enough.

double today_value      = 0.0;
double last_value       = 0.0;
double remainder        = 0.0;
double freq_out         = 0.0;
double timestep         = 0.0;    //needs to be your data acquisition time interval, in seconds 
double energy_per_pulse = 3600.0; // in Joule (assuming 1000 pulses/kWh)

//end addition.

int CurrentValue = 0;
String todayval = "Not Connected Yet";
String monthval= "Not Connected Yet";

unsigned long startMillis;  //some global variables available anywhere in the program
unsigned long currentMillis;
unsigned long period = 2000;  //time between pulses, initial 2000 ms
unsigned long startMillis2;  //some global variables available anywhere in the program
unsigned long currentMillis2;
unsigned long InitialGetdatafrequency = 20000; //only first time to get data from Growatt, then time will be set to Getdatafrequency)
unsigned long Getdatafrequency = 60000; //time between data transfers from GroWatt
unsigned long Getdatafrequencyset = 0; //time between data transfers from GroWatt
unsigned long timebetweenpulses = 2000; //time between pulses calculated (initial)
bool shouldSaveConfig = false;


WiFiServer server(80);
String header_web = "";


//callback notifying us of the need to save config
void saveConfigCallback () {
  Serial.println("Should save config");
  shouldSaveConfig = true;
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);

  pinMode(led, OUTPUT);
  digitalWrite(led, HIGH);
  pinMode(meteradapter, OUTPUT);
  digitalWrite(meteradapter, HIGH);
  startMillis = millis();  //initial start time
  startMillis2 = millis();  //initial start time
  Getdatafrequencyset = InitialGetdatafrequency;

  //read configuration from FS json
  Serial.println("mounting FS...");

  if (SPIFFS.begin()) {
    Serial.println("mounted file system");
    if (SPIFFS.exists("/config.json")) {
      //file exists, reading and loading
      Serial.println("reading config file");
      File configFile = SPIFFS.open("/config.json", "r");
      if (configFile) {
        Serial.println("opened config file");
        size_t size = configFile.size();
        // Allocate a buffer to store contents of the file.
        std::unique_ptr<char[]> buf(new char[size]);

        configFile.readBytes(buf.get(), size);
        StaticJsonBuffer<200> jsonBuffer;
        JsonObject& json = jsonBuffer.parseObject(buf.get());
        json.printTo(Serial);
        if (json.success()) {
          Serial.println("\nparsed json");

          strcpy(GroWattName, json["GroWattName"]);
          strcpy(GroWattPass, json["GroWattPass"]);

          if(json["dhcp"]) {
            Serial.println("Setting up wifi from dhcp config");
            dhcp=true;
          } else{
            Serial.println("Setting up wifi from Static IP config");
          }        

          if(json["ip"]) {
            Serial.println("Last known ip from config");
            strcpy(static_ip, json["ip"]);
            strcpy(static_gw, json["gateway"]);
            strcpy(static_sn, json["subnet"]);
            Serial.println(static_ip);
          } else {
            Serial.println("no custom ip in config");
          }
        } else {
          Serial.println("failed to load json config");
        }
      }
    }
  } else {
    Serial.println("failed to mount FS");
  }
  //end read

  
  WiFiManagerParameter custom_GroWattName("GroWattName", "GroWattName", GroWattName, 40);
  WiFiManagerParameter custom_GroWattPass("GroWattPass", "GroWattPass", GroWattPass, 40);
  WiFiManagerParameter custom_text("<p>Select Checkbox for DHCP  ");
  WiFiManagerParameter custom_text2("</p><p>DHCP will be effective after reset (power off/on)</p>");
  WiFiManagerParameter custom_text3("</p><p>So first wait for a minute after saving and then reboot by removing power.</p>");
  WiFiManagerParameter custom_dhcp("dhcp", "dhcp on", "T", 2, "type=\"checkbox\" ");

  WiFiManager wifiManager;
  WiFi.hostname(wifiHostname);

  wifiManager.setSaveConfigCallback(saveConfigCallback);

  if (!dhcp){
    IPAddress _ip,_gw,_sn;
    _ip.fromString(static_ip);
    _gw.fromString(static_gw);
    _sn.fromString(static_sn);
  
    wifiManager.setSTAStaticIPConfig(_ip, _gw, _sn);
  }else{
    wifiManager.autoConnect("AutoConnectAP");
  }
  
  wifiManager.addParameter(&custom_GroWattName);
  wifiManager.addParameter(&custom_GroWattPass);
  wifiManager.addParameter(&custom_text);
  wifiManager.addParameter(&custom_dhcp);
  wifiManager.addParameter(&custom_text2);
  wifiManager.addParameter(&custom_text3);

  wifiManager.setMinimumSignalQuality();
  
  if (!wifiManager.autoConnect("AutoConnectAP")) {
    Serial.println("failed to connect and hit timeout");
    delay(3000);
    //reset and try again, or maybe put it to deep sleep
    ESP.reset();
    delay(5000);
  }

  Serial.println("connected...yeey :)");

  if (!dhcp){
    IPAddress _dn;
    _dn.fromString(static_dn);
    WiFi.hostname(wifiHostname);
    WiFi.mode(WIFI_STA);
    WiFi.config(WiFi.localIP(), WiFi.gatewayIP(), WiFi.subnetMask(), _dn);
    WiFi.begin();
  }

  delay(2000);

  Serial.println("local ip");
  Serial.println(WiFi.localIP());
  Serial.println(WiFi.gatewayIP());
  Serial.println(WiFi.subnetMask());
  Serial.println(WiFi.dnsIP());
  
  //Serial.print("custom_dhcp.getValue(): ");
  //Serial.println(custom_dhcp.getValue());
  dhcp = (strncmp(custom_dhcp.getValue(), "T", 1) == 0);

  //Serial.print("cdhcp: ");
  //Serial.println(dhcp);
  
  strcpy(GroWattName, custom_GroWattName.getValue());
  strcpy(GroWattPass, custom_GroWattPass.getValue());
  

  //save the custom parameters to FS
  if (shouldSaveConfig) {
    Serial.println("saving config");
    StaticJsonBuffer<200> jsonBuffer;
    JsonObject& json = jsonBuffer.createObject();
    json["GroWattName"] = GroWattName;
    json["GroWattPass"] = GroWattPass;
    json["dhcp"] = dhcp;

    json["ip"] = WiFi.localIP().toString();
    json["gateway"] = WiFi.gatewayIP().toString();
    json["subnet"] = WiFi.subnetMask().toString();
    
    File configFile = SPIFFS.open("/config.json", "w");
    if (!configFile) {
      Serial.println("failed to open config file for writing");
    }

    json.prettyPrintTo(Serial);
    json.printTo(configFile);
    configFile.close();
    //end save
  }

  delay(1000);
  WiFi.begin();
  server.begin();
}


void webserver(){
    // Set web server port number to 80
  WiFiClient client = server.available();   // Listen for incoming clients

  if (client) {                             // If a new client connects,
    Serial.println("New Client.");          // print a message out in the serial port
    String currentLine = "";                // make a String to hold incoming data from the client
    while (client.connected()) {            // loop while the client's connected
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        header_web += c;
        if (c == '\n') {                    // if the byte is a newline character
          if (currentLine.length() == 0) {
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println();
            
            client.println("<!DOCTYPE html><html>");
            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<meta http-equiv=\"refresh\" content=\"300\">");
            client.println("<title>SolarBridge</title>");
            client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}");
            client.println(".button { background-color: #195B6A; border: none; color: white; padding: 16px 40px;");
            client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}</style>");
            client.println("</head>");
            
            client.println("<body><h1>SolarBridge</h1>");
            client.println("<hr>");
            client.println("<p></p>");
             
            if ((header_web.indexOf("GET /") >= 0) && (header_web.indexOf("GET /reset/") <0)) {

              reset1 = false;
              reset2 = false;

              client.println("<p>IP: " +WiFi.localIP().toString() + "<br>");
              client.println("Gateway: " +WiFi.gatewayIP().toString()+ "<br>");
              client.println("Subnet: " +WiFi.subnetMask().toString() + "</p>");
  
              client.println("<p> </p>");
  
              client.println("<p>User: " + String(GroWattName) + "<br>");
              client.println("PW: " + String(GroWattPass) + "</p>");
              String YesNo = "No";
              if (ConnectionPossible){
                  YesNo = "Yes";
              }else{
                  YesNo = "No";
              };
              client.println("Connection Posssible: " + YesNo + "</p>");
  
              client.println("<p> </p>");
              client.println("<p>1000 imp/kW</p>");
  
              String curval = String(CurrentValue);
              client.println("<hr>");
              client.println("<h1>Current: " + curval + " Watt<br>");
              client.println("Today: " + todayval + "<br>");
              client.println("Month: " + monthval + "</h1>"); 
              client.println("<hr>");    
              client.println("<p> </p>");
              client.println("<p> </p>");
              client.println("<p> </p>");
              client.println("<p> </p>");
              
              client.println("<p><a href=\"/reset/req\"><button class=\"button\">Reset</button></a></p>");
              client.println("<p>When reset is pressed, all settings are deleted and AutoConnect is restarted<br>");
              client.println("Please connect to wifi network AutoConnectAP and after connect go to: 192.168.4.1 for configuration page </p>");
              client.println("</body></html>");
            }
            
            if (header_web.indexOf("GET /reset/req") >= 0) {
              reset1 = true;
              reset2 = false;
              Serial.print("Reset1 :");
              Serial.print(reset1);
              Serial.print(", Reset2 :");
              Serial.println(reset2);
              client.println("<p>Weet u zeker ?</p>");
              client.println("<p> </p>");
              client.println("<p><a href=\"/reset/ok\"><button class=\"button\">Yes</button></a><a href=\"/../..\"><button class=\"button\">No</button></a></p>");
              client.println("</body></html>");
            }

            if (header_web.indexOf("GET /reset/ok") >= 0) {
              reset2 = true;
              Serial.print("Reset1 :");
              Serial.print(reset1);
              Serial.print(", Reset2 :");
              Serial.println(reset2);
              client.println("<p>Reset carried out</p>");
              client.println("<p>Please connect to wifi network AutoConnectAP and goto 192.168.4.1 for configuration</p>");
              client.println("</body></html>");
            }
            
            // Break out of the while loop
            break;
          } else { // if you got a newline, then clear currentLine
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }
      }
    }
    // Clear the header variable
    header_web = "";
    // Close the connection
    client.stop();
    Serial.println("Client disconnected.");
    Serial.println("");
  }  
}


void getdata(){
          MD5Builder md5;
          md5.begin();
          md5.add(GroWattPass);  // md5 of the user:realm:user
          md5.calculate();
          String password = md5.toString();
          //Serial.print("password : ");
          //Serial.println(password);
        
          
          HTTPClient http;
          const char * headerkeys[] = {"User-Agent","Set-Cookie","Cookie","Date","Content-Type","Connection"} ;
          size_t headerkeyssize = sizeof(headerkeys)/sizeof(char*);
        
          http.begin("http://server.growatt.com/LoginAPI.do");
          http.setReuse(true);
          http.setUserAgent("Dalvik/2.1.0 (Linux; U; Android 9; ONEPLUS A6003 Build/PKQ1.180716.001");
          http.addHeader("Content-type", "application/x-www-form-urlencoded");

          http.collectHeaders(headerkeys,headerkeyssize);
        
          int code = http.POST("password=" + password + "&userName="+GroWattName); //Send the request
          if ((code=200) || (code = 301) || (code = 302)){
                        JSESSIONID = "";
                        SERVERID = "";
                        
                        //Serial.printf("[HTTP] POST... code: %d\r\n", code);
                        String res = http.getString();
                        //Serial.println(res); 
                      
                        //Serial.printf("Header count: %d\r\n", http.headers());
                        for (int i=0; i < http.headers(); i++) {
                          //Serial.printf("%s = %s\r\n", http.headerName(i).c_str(), http.header(i).c_str());
                        }
                        //Serial.printf("Cookie: %s\r\n", http.header("Cookie").c_str());
                        //Serial.printf("Set-Cookie: %s\r\n", http.header("Set-Cookie").c_str());
                        String totCookie = http.header("Set-Cookie").c_str();
                        //Serial.println(totCookie); 
                        int isteken = totCookie.indexOf("JSESSIONID=") +  11;
                        int puntkommateken = totCookie.indexOf(";",isteken);
                        JSESSIONID = totCookie.substring(isteken, puntkommateken);
                        //Serial.println(JSESSIONID); 
                        isteken = totCookie.indexOf("SERVERID=", puntkommateken ) + 9;
                        puntkommateken = totCookie.indexOf(";",isteken);
                        SERVERID = totCookie.substring(isteken, puntkommateken);
                        //Serial.println(SERVERID);
                        if ((JSESSIONID != "")&&(SERVERID != "")){
                            cookiestep= true;
                            ConnectionPossible = true;
                        }
          }

          http.begin("http://server-api.growatt.com/newPlantAPI.do?action=getUserCenterEnertyData");
          //http.begin("http://server.growatt.com/PlantDetailAPI.do?type=4&plantId=DJE2A02071");
          http.addHeader("Cookie", "JSESSIONID="  + JSESSIONID + ";" + "SERVERID="  + SERVERID);
          //http.addHeader("Set-Cookie", "JSESSIONID="  + JSESSIONID + ";" + "SERVERID="  + SERVERID);
          http.setReuse(true);
          http.setUserAgent("Dalvik/2.1.0 (Linux; U; Android 9; ONEPLUS A6003 Build/PKQ1.180716.001");
          http.addHeader("Content-type", "application/x-www-form-urlencoded");
          http.addHeader("Referer", "http://server.growatt.com/LoginAPI.do");

          http.collectHeaders(headerkeys,headerkeyssize);
        
          code = http.POST("language=1"); //Send the request
          //code = http.GET();
          //Serial.printf("[HTTP] POST... code: %d\r\n", code);
          if ((code=200) || (code = 301) || (code = 302)){

                     
                        String payload = http.getString();; //Get the request response payload
                        Serial.println(payload); //Print the response payload
                        
                        const size_t capacity = JSON_OBJECT_SIZE(3) + JSON_ARRAY_SIZE(2) + 60;
                        DynamicJsonBuffer jsonBuffer(capacity);
                        
                        //Serial.println("JSON covert..");
                        JsonObject& root = jsonBuffer.parseObject(payload);
                        if (!root.success()) {
                            Serial.println("parseObject() failed");
                        }
                        else {
                            String subroot  = root["powerValue"];
                            String DayValue  = root["todayStr"];
                            String MonthValue  = root["monthStr"];

// Need to add something like this: 
   	       		    last_value = today_value;   // keep track of the previous datum
// then continue in floating point,

                            today_value = (double)DayValue;  // Need to do a conversion of DayValue first. 
 			    		  		     // Not sure how to do that, don't have the json data.
// and add:

		            increment = today_value - last_value + remainder;        // not sure about the units, need to check that. 
                                                                                     // Need to add the last bit of energy from the last pulse, 
                                                                                     // since that has not been accounted for yet.
                            number_of_pulses = floor( increment / energy_per_pulse );  // do we have a complete math library including floor()?
                            remainder = increment % number_of_pulses;                // does arduino support the modulo operator?
			    power = increment / 60 ;                                 // From energy in the last timestep to power. 
			    	    	      	   				     // (assuming a 60s interval, and energy in Joule).
			    freq_out = number_of_pulses / timestep;                  // in Hz
// end addition
                            monthval = MonthValue;
                            
                            subroot.trim();
                            CurrentValue = subroot.toInt();

// replace                            
                            //if (CurrentValue > 1) {
                                //timebetweenpulses = 3600000/CurrentValue;
// with: 
                            if ( number_of_pulses > 0 )	
                                timebetweenpulses = (int)( freq_out * 60000 ); // assuming 60k millisecond time intervals 
// end replacement.
                            }
                            else{
                                timebetweenpulses = 0;
                            }
                            Getdatafrequencyset = Getdatafrequency;
                        }
          }                
        http.end();  //Close connection
}


void blinkled() {
  digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
  digitalWrite(meteradapter, HIGH);
  delay(100);               // wait for some time
  digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
  digitalWrite(meteradapter, LOW);
}



void loop() {

  //all reset pages have been acknowledged and reset parameters have been set
  webserver();
  
  if ((reset1) && (reset2)) {
      delay(2000);
      Serial.println("Reset required");
      Serial.println("saving config");
      SPIFFS.remove("/config.json");
      WiFiManager wifiManager;
      wifiManager.resetSettings();
      delay(500);
      ESP.reset();
  }
  currentMillis = millis();  //get the current "time" (actually the number of milliseconds since the program started)
  //if (currentMillis - startMillis >= 2000) { //test at 2 s interval (=1800 Watt/hr)
  if (currentMillis - startMillis >= period) { //test whether the period has elapsed 
 
    Serial.print("Current Value :");
    Serial.print(CurrentValue);
    Serial.print(", Pulse at time :");
    Serial.println(timebetweenpulses);

    blinkled();
    period = timebetweenpulses;
    startMillis = currentMillis;  //IMPORTANT to save the start time of the current LED state.
  }

  currentMillis2 = millis();  //get the current "time" (actually the number of milliseconds since the program started)
  //if (currentMillis - startMillis >= 2000) { //test at 2 s interval (=1800 Watt/hr)
  if (currentMillis2 - startMillis2 >= Getdatafrequencyset) { //test whether the period has elapsed 
    //get new data from server
    if (WiFi.status() == WL_CONNECTED) { //Check WiFi connection status
          getdata();
    }
     startMillis2 = currentMillis2;  //IMPORTANT to save the start time of the current LED state.
  }
}
oepi-loepi
Advanced Member
Advanced Member
Posts: 628
Joined: Sat Feb 09, 2019 7:18 pm

Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon

Post by oepi-loepi »

Please find an example of the JSON from this evening. The powervalue is in W , the todayvalue in kWh.

Code: Select all

{"monthProfitStr":"€11.6","todayProfitStr":"€1.7","plantNumber":1,"treeValue":"8.1",
"treeStr":"8","nominalPowerStr":"1.9kW",
"eventMessBeanList:[],"yearValue":"0.0","formulaCo2Vlue":"0.0",
"formulaCo2Str":"0kg","todayValue":"7.4","totalStr":"148kWh",
"powerValue":"67.0","totalValue":"148.0","nominalPowerValue":"1890.0",
"powerValueStr":"0kW","monthValue":"50.5","todayStr":"7.4kWh",
"monthStr":"50.5kWh","formulaCoalStr":"0kg","alarmValue":0,
"totalProfitStr":"€34","yearStr":"0kWh","formulaCoalValue":"0.0"}
Last edited by oepi-loepi on Mon Apr 06, 2020 11:08 pm, edited 4 times in total.
oepi-loepi
Advanced Member
Advanced Member
Posts: 628
Joined: Sat Feb 09, 2019 7:18 pm

Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon

Post by oepi-loepi »

I also found a little programming mistake (when the powervalue was 0, the time betweenpulses was 0. it should be:

if (CurrentValue > 1) {
timebetweenpulses = 3600000/CurrentValue;
}
else{
timebetweenpulses = 3*3600*1000;
}

then time will be 3 hours between the pulses when powervalue is 0.
oepi-loepi
Advanced Member
Advanced Member
Posts: 628
Joined: Sat Feb 09, 2019 7:18 pm

Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon

Post by oepi-loepi »

marcelr wrote:My previous post was a bit of a lecture (that's what I do for a living ;-) ). Got bored with all the skype/zoom/teams/bigblueblob stuff from today, so I took the liberty to add a bit of (pseudo) code to your arduino code.

Each added section is commented with something along these lines: // add: .... // end addition
Oh by the way, do declarations need to be global on an arduino (clone)?

The main bits are inside the routine getdata(), plus some declarations up front, hope it's useful.
My indentation is FUBAR, sorry about that.

Code: Select all

                  increment = today_value - last_value + remainder;        // not sure about the units, need to check that.
                                                                                     // Need to add the last bit of energy from the last pulse,
                                                                                     // since that has not been accounted for yet.
                            number_of_pulses = floor( increment / energy_per_pulse );  // do we have a complete math library including floor()?
                            remainder = increment % number_of_pulses;                // does arduino support the modulo operator?
             power = increment / 60 ;                                 // From energy in the last timestep to power.
                                                    // (assuming a 60s interval, and energy in Joule).
             freq_out = number_of_pulses / timestep;                  // in Hz
// end addition
                            monthval = MonthValue;
                           
                            subroot.trim();
                            CurrentValue = subroot.toInt();

// replace                           
                            //if (CurrentValue > 1) {
                                //timebetweenpulses = 3600000/CurrentValue;
// with:
                            if ( number_of_pulses > 0 )   
                                timebetweenpulses = (int)( freq_out * 60000 ); // assuming 60k millisecond time intervals
// end replacement.
                            }
                            else{
                                timebetweenpulses = 0;
                            }
                            Getdatafrequencyset = Getdatafrequency;
                        }
          }  
I will try to do something like this in the next weekend. Because the day total is in 100 Watt resolution i must find a way to have some smooth pulse interval even if solarpower is low. I understand what you mean exactly (despite the Fubar idents) and i will try. Also timebetweenpulses here should be a big number when solarpower is 0. :)
marcelr
Global Moderator
Global Moderator
Posts: 1153
Joined: Thu May 10, 2012 10:58 pm
Location: Ehv

Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon

Post by marcelr »

Hi oepi-loepi,

It's just a suggestion. The reason I posted this is because I'm just now using a PV S0 pulse simulator to test some meter adapters (and simpler wiring to connect PV inverters using S0 potential-free contacts to toon). After that, I need to couple my inverter to toon, with an extra hardware step in between. My inverter has a very funny way of counting energy production (but not really :-( ).
In my simulator I have an exact solution for the total energy produced, and while simulating I found that good and precise bookkeeping of the energy production is essential to get accurate measurement results. Power emulation alone is not good enough, it will diverge slowly from the total energy production.

The algorithm I suggested will automatically take care of low power signals. If the energy produced in any given time interval is less than the energy corresponding to 1 pulse, there will be no pulse emission in that interval. The total energy produced will be added to the remainder, until that has become large enough for a single pulse. Then, everything starts again for the next pulse or pulses. This indicates that the time interval for low or no power should be just a little bit larger than each time interval in which the inverter data are read.

In my simulations, I have a slow cosine (8hour period) from 0 to 4kW of PV power. kWh meter resolution is set to 10,000 pulses/kWh. The first pulse in that simulation appears after 283 seconds. Signals are smoothed within toon with a moving average filter with a 5 minute filtering window. The data in the graphs therefore have a lag of 150 s with respect to the actual power signal. Power indication in the "Zon nu" tile has a maximum delay of 10s, but only when pulse frequency exceeds the update rate of that tile's data. When the data are fed into, and read from an eneco meter adapter, they will be off by a bit more than 1%. Will post a full report on my findings shortly.

If the energy resolution is 0.1 kWh, and power resolution is much better, maybe there's a way to use power as primary source for the pulses as you do now. 100wH resolution is too coarse for proper pulse timing. Just store the power in the previous time interval, use a linear interpolation between previous power and current power, and emit pulses accordingly. In the meantime you can keep track of the total energy by integrating that power signal (just count the pulses you emit and multiply by energy_per_pulse) and then you can correct for possible divergence as a new energy reading pops up.
Should be feasible, I guess. I'll be happy to run that algorithm through my simulator, to test for any errors in long-term recordings. I will just need to add a json interpreter/writer to the code, and of course the pulse frequency computation.

The biggest issues will occur on a sunny day, with "nice weather clouds", the ones that make a PV power signal look like a poorly mown lawn.

Anyway, keep up the good work, when this is ready, many people can use it (also for other inverters, I guess).
TheHogNL
Forum Moderator
Forum Moderator
Posts: 2125
Joined: Sun Aug 20, 2017 8:53 pm

Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon

Post by TheHogNL »

marcelr wrote: The biggest issues will occur on a sunny day, with "nice weather clouds", the ones that make a PV power signal look like a poorly mown lawn.
That is what I indeed mentioned earlier :) Hard to generate (and read) pulses from that pattern.
Member of the Toon Software Collective
oepi-loepi
Advanced Member
Advanced Member
Posts: 628
Joined: Sat Feb 09, 2019 7:18 pm

Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon

Post by oepi-loepi »

Indeed i will adapt the program so that it will correct the pulses allready send when a new energy reading pops up. I will also have to keep track of the time between the new readings so i can spread the correction linear in time.

Indeed it will not give the exact "actual actuals", especialy not on a "nice weather cloudy day". However at the end it will be corrected each time (5 mins interval) to total energy. So the graph will look less poorly mown......

There is also the P1 port (variable 2.7.0) from (smart meter) which is read by the Toon. This is the only exact value to the grid.
marcelr
Global Moderator
Global Moderator
Posts: 1153
Joined: Thu May 10, 2012 10:58 pm
Location: Ehv

Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon

Post by marcelr »

Could you post a time series of your Growatt data (power and total energy), for let's say, 1 day, with 30s intervals? .csv or excel format will do nicely.
Just to test some ideas I have.
oepi-loepi
Advanced Member
Advanced Member
Posts: 628
Joined: Sat Feb 09, 2019 7:18 pm

Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon

Post by oepi-loepi »

Yes, i will adapt the software tonight and i will have the data aquisition tomorrow.

Update: i have send some data
Last edited by oepi-loepi on Tue Apr 07, 2020 12:32 pm, edited 3 times in total.
oepi-loepi
Advanced Member
Advanced Member
Posts: 628
Joined: Sat Feb 09, 2019 7:18 pm

Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon

Post by oepi-loepi »

marcelr wrote:Could you post a time series of your Growatt data (power and total energy), for let's say, 1 day, with 30s intervals? .csv or excel format will do nicely.
Just to test some ideas I have.
I have downloaded the historical data (6-4) from the website. It has a 5 minute interval.
Attachments
History data - 2020-04-05_2020-04-06 - kopie.rar
(28.51 KiB) Downloaded 382 times
marcelr
Global Moderator
Global Moderator
Posts: 1153
Joined: Thu May 10, 2012 10:58 pm
Location: Ehv

Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon

Post by marcelr »

Thanks, probably too coarse, but good enough to start working on.
oepi-loepi
Advanced Member
Advanced Member
Posts: 628
Joined: Sat Feb 09, 2019 7:18 pm

Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon

Post by oepi-loepi »

marcelr wrote:Thanks, probably too coarse, but good enough to start working on.
I have been puzzling a bit and i think i found some proper solutions to correct the pulsetime when a new daytotal has been received. It will only start after the daytotal has been 0 kWh so a final check will be tomorrow or this Eastern. Please find the updated software as working copy.

THIS COPY IS FOR INFORMATION ONLY (deleted....do not use). i am testing a new version during Eastern weerkend....
oepi-loepi
Advanced Member
Advanced Member
Posts: 628
Joined: Sat Feb 09, 2019 7:18 pm

Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon

Post by oepi-loepi »

A new version has been tested successfully.

This new version also takes the daily production and if it has been changed and if there is a difference between the pulses and the daily production, a (slow) correction will be added to the pulses. At the end of the day, the daily production pulsed for will be the same as the daily production on the GroWatt website.

Image
Attachments
SolarBridge_v2.1.rar
SolarBridge 2.1
(6.57 KiB) Downloaded 403 times
jozg
Member
Member
Posts: 56
Joined: Wed Nov 15, 2017 1:13 pm

Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon

Post by jozg »

Hello Oepi-loepi,

Before i build the optocoupler etc. i flashed my wemos with the sketch.
I managed to get the wemos working, and can fill in the wifi and growatt details.

For some reason it shows nothing:
Actual: 0.00 Watt
Today: Not Connected Yet
Month: Not Connected Yet

It also shows
Connection Posssible: Yes
1000 imp/kW

Is it possible that i have an other growatt the your program is expecting?
I used the same credentials i use in https://server.growatt.com

Maybe you can help me in the right direction.
Thanks.

Regards,
Post Reply

Return to “Toon Hardware”