SolarBridge: a Wemos Base bridge between GroWatt and Toon
Moderators: marcelr, TheHogNL, Toonz
Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon
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.
Just make sure you get the total number of pulses correct.
Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon
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.

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.
}
}
-
- Advanced Member
- Posts: 681
- Joined: Sat Feb 09, 2019 7:18 pm
Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon
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.
-
- Advanced Member
- Posts: 681
- Joined: Sat Feb 09, 2019 7:18 pm
Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon
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.
if (CurrentValue > 1) {
timebetweenpulses = 3600000/CurrentValue;
}
else{
timebetweenpulses = 3*3600*1000;
}
then time will be 3 hours between the pulses when powervalue is 0.
-
- Advanced Member
- Posts: 681
- Joined: Sat Feb 09, 2019 7:18 pm
Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon
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;
}
}

Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon
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).
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).
Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon
That is what I indeed mentioned earliermarcelr 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.

Member of the Toon Software Collective
-
- Advanced Member
- Posts: 681
- Joined: Sat Feb 09, 2019 7:18 pm
Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon
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.
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.
Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon
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.
Just to test some ideas I have.
-
- Advanced Member
- Posts: 681
- Joined: Sat Feb 09, 2019 7:18 pm
Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon
Yes, i will adapt the software tonight and i will have the data aquisition tomorrow.
Update: i have send some data
Update: i have send some data
Last edited by oepi-loepi on Tue Apr 07, 2020 12:32 pm, edited 3 times in total.
-
- Advanced Member
- Posts: 681
- Joined: Sat Feb 09, 2019 7:18 pm
Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon
I have downloaded the historical data (6-4) from the website. It has a 5 minute interval.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.
- Attachments
-
- History data - 2020-04-05_2020-04-06 - kopie.rar
- (28.51 KiB) Downloaded 440 times
Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon
Thanks, probably too coarse, but good enough to start working on.
-
- Advanced Member
- Posts: 681
- Joined: Sat Feb 09, 2019 7:18 pm
Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon
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.marcelr wrote:Thanks, probably too coarse, but good enough to start working on.
THIS COPY IS FOR INFORMATION ONLY (deleted....do not use). i am testing a new version during Eastern weerkend....
-
- Advanced Member
- Posts: 681
- Joined: Sat Feb 09, 2019 7:18 pm
Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon
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.

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.

- Attachments
-
- SolarBridge_v2.1.rar
- SolarBridge 2.1
- (6.57 KiB) Downloaded 493 times
Re: SolarBridge: a Wemos Base bridge between GroWatt and Toon
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,
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,