Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Fronius 3 phase inverter and Smart Meter 63A-3 #40

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions examples/Fronius/Fronius3p.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
Copyright (C) AC SOFTWARE SP. Z O.O.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include <SuplaDevice.h>
#include <supla/pv/fronius3p.h>
#include <supla/pv/fronius3pmeter.h>

// Choose proper network interface for your card:
#ifdef ARDUINO_ARCH_AVR
// Arduino Mega with EthernetShield W5100:
#include <supla/network/ethernet_shield.h>
// Ethernet MAC address
uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05};
Supla::EthernetShield ethernet(mac);

// Arduino Mega with ENC28J60:
// #include <supla/network/ENC28J60.h>
// Supla::ENC28J60 ethernet(mac);
#elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
// ESP8266 and ESP32 based board:
#include <supla/network/esp_wifi.h>
Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password");
#endif

void setup() {

Serial.begin(115200);

// Replace the falowing GUID with value that you can retrieve from https://www.supla.org/arduino/get-guid
char GUID[SUPLA_GUID_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};

// Replace the following AUTHKEY with value that you can retrieve from: https://www.supla.org/arduino/get-authkey
char AUTHKEY[SUPLA_AUTHKEY_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};

/*
* Having your device already registered at cloud.supla.org,
* you want to change CHANNEL sequence or remove any of them,
* then you must also remove the device itself from cloud.supla.org.
* Otherwise you will get "Channel conflict!" error.
*/

// CHANNEL0
// Put IP address of your Fronius 3 phase inverter, then port (deafult is 80)
new Supla::PV::Fronius3p(IPAddress(192, 168, 0, 59));
// CHANNEL1
// Put IP address of your Fronius inventer connected to 3 phase Smart Meter
new Supla::PV::Fronius3pmeter(IPAddress(192, 168, 0, 59));

// You can use one of those channels or both simultaneously.
// You have to delete device on SUPLA server every time you decide to use/unuse channel.

/*
* Server address is available at https://cloud.supla.org
* If you do not have an account, you can create it at https://cloud.supla.org/account/create
* SUPLA and SUPLA CLOUD are free of charge
*/

SuplaDevice.begin(GUID, // Global Unique Identifier
"svr1.supla.org", // SUPLA server address
"email@address", // Email address used to login to Supla Cloud
AUTHKEY); // Authorization key
}

void loop() {
SuplaDevice.iterate();
}

289 changes: 289 additions & 0 deletions src/supla/pv/fronius3p.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
/*
Copyright (C) AC SOFTWARE SP. Z O.O.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include "fronius3p.h"

using namespace Supla;
using namespace PV;

enum ParametersToRead { NONE, TOTAL_ENERGY, FAC, IAC, PAC, UAC,
IAC_L1, IAC_L2, IAC_L3, UAC_L1, UAC_L2, UAC_L3 };

Fronius3p::Fronius3p(IPAddress ip, int port, int deviceId)
: ip(ip),
port(port),
buf(),
totalGeneratedEnergy(0),
currentPower(0),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

currentActivePower{},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

currentCurrent{},
currentFreq(0),
currentVoltage{},
bytesCounter(0),
retryCounter(0),
valueToFetch(NONE),
deviceId(deviceId),
startCharFound(false),
dataIsReady(false),
dataFetchInProgress(false),
connectionTimeoutMs(0),
fetch3p(false) {
refreshRateSec = 15;
}

void Fronius3p::iterateAlways() {
if (dataFetchInProgress) {
if (millis() - connectionTimeoutMs > 30000) {
Serial.println(F("Fronius: connection timeout. Remote host is not responding"));
pvClient.stop();
dataFetchInProgress = false;
dataIsReady = false;
return;
}
if (!pvClient.connected()) {
Serial.println(F("Fronius fetch completed"));
dataFetchInProgress = false;
dataIsReady = true;
}
if (pvClient.available()) {
Serial.print(F("Reading data from Fronius: "));
Serial.println(pvClient.available());
}
while (pvClient.available()) {
char c;
c = pvClient.read();
if (c == '\n') {
if (startCharFound) {
if (bytesCounter > 79) bytesCounter = 79;
buf[bytesCounter] = '\0';
char varName[80];
char varValue[80];
sscanf(buf, " %s : %s", varName, varValue);
if (valueToFetch != NONE && strncmp(varName, "Value", strlen("Value")) == 0) {
switch (valueToFetch) {
case TOTAL_ENERGY: {
float totalProd = atof(varValue);
Serial.print(F("Total production: "));
Serial.print(totalProd);
Serial.println(F(" Wh"));
totalGeneratedEnergy = totalProd * 100;

break;
}
case PAC: {
float curPower = atof(varValue);
Serial.print(F("Current power: "));
Serial.println(curPower);
currentPower = curPower * 100000;

break;
}
Comment on lines +86 to +93
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

case IAC: {
float curCurrent = atof(varValue);
Serial.print(F("Current: "));
Serial.println(curCurrent);
//currentCurrent[0] = curCurrent * 1000;

break;
}
Comment on lines +94 to +101
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

case FAC: {
float curFreq = atof(varValue);
Serial.print(F("Frequency: "));
Serial.println(curFreq);
currentFreq = curFreq * 100;

break;
}
case UAC: {
float curVoltage = atof(varValue);
Serial.print(F("Voltage: "));
Serial.println(curVoltage);
//currentVoltage[0] = curVoltage * 100;

break;
Comment on lines +110 to +116
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

}
case IAC_L1: {
float curCurrent = atof(varValue);
Serial.print(F("Current Phase 1: "));
Serial.println(curCurrent);
currentCurrent[0] = curCurrent * 1000;

break;
}
case IAC_L2: {
float curCurrent = atof(varValue);
Serial.print(F("Current Phase 2: "));
Serial.println(curCurrent);
currentCurrent[1] = curCurrent * 1000;

break;
}
case IAC_L3: {
float curCurrent = atof(varValue);
Serial.print(F("Current Phase 3: "));
Serial.println(curCurrent);
currentCurrent[2] = curCurrent * 1000;

break;
}
case UAC_L1: {
float curVoltage = atof(varValue);
Serial.print(F("Voltage Phase 1: "));
Serial.println(curVoltage);
currentVoltage[0] = curVoltage * 100;

break;
}
case UAC_L2: {
float curVoltage = atof(varValue);
Serial.print(F("Voltage Phase 2: "));
Serial.println(curVoltage);
currentVoltage[1] = curVoltage * 100;

break;
}
case UAC_L3: {
float curVoltage = atof(varValue);
Serial.print(F("Voltage Phase 3: "));
Serial.println(curVoltage);
currentVoltage[2] = curVoltage * 100;

break;
}
}
valueToFetch = NONE;
} else if (strncmp(varName, "IAC_L1", strlen("IAC_L1")) == 0) {
valueToFetch = IAC_L1;
} else if (strncmp(varName, "IAC_L2", strlen("IAC_L2")) == 0) {
valueToFetch = IAC_L2;
} else if (strncmp(varName, "IAC_L3", strlen("IAC_L3")) == 0) {
valueToFetch = IAC_L3;
} else if (strncmp(varName, "UAC_L1", strlen("UAC_L1")) == 0) {
valueToFetch = UAC_L1;
} else if (strncmp(varName, "UAC_L2", strlen("UAC_L2")) == 0) {
valueToFetch = UAC_L2;
} else if (strncmp(varName, "UAC_L3", strlen("UAC_L3")) == 0) {
valueToFetch = UAC_L3;
} else if (strncmp(varName, "TOTAL_ENERGY\"", strlen("TOTAL_ENERGY")) == 0) {
valueToFetch = TOTAL_ENERGY;
} else if (strncmp(varName, "FAC", strlen("FAC")) == 0) {
valueToFetch = FAC;
} else if (strncmp(varName, "UAC", strlen("UAC")) == 0) {
valueToFetch = UAC;
} else if (strncmp(varName, "IAC", strlen("IAC")) == 0) {
valueToFetch = IAC;
} else if (strncmp(varName, "PAC", strlen("PAC")) == 0) {
valueToFetch = PAC;
}
}
bytesCounter = 0;
startCharFound = false;
} else if (c == '"' || startCharFound) {
startCharFound = true;
if (c == '"') {
c = ' ';
}
if (bytesCounter < 80) {
buf[bytesCounter] = c;
}
bytesCounter++;
}
}
if (!pvClient.connected()) {
pvClient.stop();
}
}
if (dataIsReady) {
dataIsReady = false;
// reverse logic
if (fetch3p) {
setFwdActEnergy(0, totalGeneratedEnergy);
setFreq(currentFreq);
} else {
setCurrent(0, currentCurrent[0]);
setVoltage(0, currentVoltage[0]);
setPowerActive(0, currentCurrent[0] * currentVoltage[0]);
setCurrent(1, currentCurrent[1]);
setVoltage(1, currentVoltage[1]);
setPowerActive(1, currentCurrent[1] * currentVoltage[1]);
setCurrent(2, currentCurrent[2]);
setVoltage(2, currentVoltage[2]);
setPowerActive(2, currentCurrent[2] * currentVoltage[2]);
//setPowerActive(0, currentPower);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

}
updateChannelValues();
}
}

bool Fronius3p::iterateConnected(void *srpc) {
if (!dataFetchInProgress) {
if (lastReadTime == 0 || millis() - lastReadTime > refreshRateSec*1000) {
lastReadTime = millis();
Serial.print(F("Fronius connecting "));
Serial.println(deviceId);
if (pvClient.connect(ip, port)) {
retryCounter = 0;
dataFetchInProgress = true;
connectionTimeoutMs = lastReadTime;
Serial.println(F("Succesful connect"));

char buf[100];
char idBuf[20];
sprintf(idBuf, "%d", deviceId);
if (fetch3p) {
fetch3p = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can toggle this bool outside of "if" like this:
fetch3p = !fetch3p;

strcpy(buf, "GET /solar_api/v1/GetInverterRealtimeData.cgi?Scope=Device&DeviceID=");
strcat(buf, idBuf);
Comment on lines +248 to +249
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can move it above "if" and remove it from second if section

strcat(buf, "&DataCollection=3PInverterData HTTP/1.1");
pvClient.println(buf);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line can be moved below both "ifs"

} else {
fetch3p = true;
strcpy(buf, "GET /solar_api/v1/GetInverterRealtimeData.cgi?Scope=Device&DeviceID=");
strcat(buf, idBuf);
strcat(buf, "&DataCollection=CommonInverterData HTTP/1.1");
pvClient.println(buf);
}
pvClient.println("Host: localhost");
pvClient.println("Connection: close");
pvClient.println();

} else { // if connection wasn't successful, try few times. If it fails,
// then assume that inverter is off during the night
Serial.print(F("Failed to connect to Fronius at: "));
Serial.print(ip);
Serial.print(F(":"));
Serial.println(port);
retryCounter++;
if (retryCounter > 3) {
//currentPower = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

currentFreq = 0;
currentCurrent[0] = 0;
currentVoltage[0] = 0;
currentCurrent[1] = 0;
currentVoltage[1] = 0;
currentCurrent[2] = 0;
currentVoltage[2] = 0;
dataIsReady = true;
}
}
}
}
return Element::iterateConnected(srpc);
}

void Fronius3p::readValuesFromDevice() {
}

Loading