I would like to share the code I've written for anybody who wants to use it, feel free.
I works with SMA SUNNY TRIPOWER 8.0 and has an App Implementation with Blynk as well as some hysterese to make sure the charging station is not turned on and off every second.
A hint for anybody struggeling with SMA SUNNY TRIPOWER 8.0 Modbus implementation: there seems to be some kind of firewall in the inverter, I haven't found out when its triggered - but if it is no modbus connection is possible any more and that's when error codes are generated by Modbus::ResultCode. Once I finally figured that out (and that it was not my code that was wrong): just stop the TCP Modbus Server on the webinterface of SMA. Wait a few seconds and turn it back on. That did the trick.
I'll split it up in 2 posts because it's too long in a single post.
If you have any critical remarks on the code feel free. I know the reset is not ideal, but unfortuntley disconnect and connect again doesn't work... maybe somebody has an idea to get it more stable.
#include <ESP8266WiFi.h>
#include <Modbus.h>
#include <ModbusIP_ESP8266.h>
IPAddress remote(192, 168, 0, 21); // Address of Modbus Slave device SMA
ModbusIP ModbusIPobject; //ModbusIP object
#include <BlynkSimpleEsp8266.h>
#define BLYNK_PRINT Serial
WidgetLCD lcd(V3);
WidgetLED led4Sofortladenaktiv(V4);
WidgetLED led5Ueberschussladenaktiv(V5);
WidgetLED led9Winteraktiv(V9);
#include <Ticker.h> //Ticker Library
Ticker blinkerReset6h;
Ticker blinkerresetHystereseRelais;
Ticker blinkerdebug;
Ticker alle10secsModbusauslesen;
//Config Modbus
uint16_t offsetToReadPVfrom = 40199; // Active power (W), in W*10 (40201).
uint16_t offsetToReadOperationStatefrom = 40223; // Operating status (St): 1 = Off 2 = Wait for PV voltage 3 = Starting (Morgens wenn die Sonne aufgeht) 4 = MPP 5 = Regulated 6 = Shutting down 7 = Error 8 = Waiting for electric utility company
const int DeviceID = 126;
//config Programmverhalten
const int HystereseRelaisDauer = 600; //in Sekunden
const int DauerSofortladen = 21600; //in Sekunden
const int LadenErlaubenAbLeistung = 4000; // ab 4kW
//config allgemein
const char ssid[] = "";
const char pass[] = "";
const char auth[] = ""; //Blynk
const int PINOUT = 5; // Pin 5 am Arduino = Hardwarepin D1 für Schalten des Relais festlegen
// Laufzeitvariablen
static unsigned long measurement_timestamp = 10000000000;
uint16_t modbusValuePVLeistung = 0;
uint16_t modbusValuePVLeistungMaxCurrentRetry = 0;
uint16_t TimesPVLeistungMaxCurrentRetrySeenLagerThanLadenErlaubenAbLeistung = 0;
uint16_t modbusResult = 0;
uint16_t modbusResultPower = 0;
int AnzahlMessungen = 0;
// Programmzustände
int Ueberschussladenaktiv = 1;
int Sofortladenaktiv = 0;
int Winterladenaktiv = 0;
uint16_t StatusLadesauele = 0; // 0 = gesperrt, kein Laden möglich 1 = läuft
int HystereseRelais = 0;
bool cb(Modbus::ResultCode event, uint16_t transactionId, void* data) { // Modbus Transaction callback
//if (event != Modbus::EX_SUCCESS) // If transaction got an error
Serial.printf("Modbus result: %02X\n", event); // Display Modbus error code
Serial.printf("Modbus transactionId: %02X\n", transactionId); // Display Modbus error code
// Serial.printf("Modbus data: %02X\n", data); // Display Modbus data
if (event == Modbus::EX_TIMEOUT) { // If Transaction timeout took place
ModbusIPobject.disconnect(remote); // Close connection to slave and
ModbusIPobject.dropTransactions(); // Cancel all waiting transactions
}
return true;
}
BLYNK_READ(V6) // Aktuellle Leistung anzeigen
{
Blynk.virtualWrite(V6, modbusValuePVLeistung);
// Serial.println("Blynk.virtualWrite(V6, modbusValuePVLeistung)");
}
BLYNK_WRITE(V8) // Schalter um Überschussladen zu aktivieren
{
if (param.asInt() == 1) {
Ueberschussladenaktiv = 1;
Sofortladenaktiv = 0;
Winterladenaktiv = 0;
if (TimesPVLeistungMaxCurrentRetrySeenLagerThanLadenErlaubenAbLeistung == 0) {
digitalWrite(PINOUT, LOW); // keine Solarenergie derzeit also PV-Laden deaktivieren
StatusLadesauele = 0;
HystereseRelais = 1;
blinkerresetHystereseRelais.attach(HystereseRelaisDauer, resetHystereseRelais);
}
blinkerReset6h.detach();
}
}
BLYNK_WRITE(V7) // Schalter um Sofortladen zu aktivieren
{
if (param.asInt() == 1) {
Ueberschussladenaktiv = 0;
Sofortladenaktiv = 1;
Winterladenaktiv = 0;
if (StatusLadesauele == 0) {
digitalWrite(PINOUT, HIGH);
StatusLadesauele = 1;
}
blinkerReset6h.detach();
blinkerReset6h.attach(DauerSofortladen, resetSofortLaden);
}
}
BLYNK_WRITE(V10) // Schalter um Winterladen zu aktivieren
{
if (param.asInt() == 1) {
Ueberschussladenaktiv = 0;
Sofortladenaktiv = 0;
Winterladenaktiv = 1;
if (StatusLadesauele == 0) {
digitalWrite(PINOUT, HIGH);
StatusLadesauele = 1;
}
blinkerReset6h.detach();
}
}
void resetSofortLaden() {
Ueberschussladenaktiv = 1;
Sofortladenaktiv = 0;
Winterladenaktiv = 0;
if ( TimesPVLeistungMaxCurrentRetrySeenLagerThanLadenErlaubenAbLeistung == 0) {
digitalWrite(PINOUT, LOW);
StatusLadesauele = 0;
HystereseRelais = 1;
blinkerresetHystereseRelais.attach(HystereseRelaisDauer, resetHystereseRelais);
}
}
void resetHystereseRelais() {
HystereseRelais = 0;
blinkerresetHystereseRelais.detach();
}
/*
void printDebugInfos() {
Serial.print("StatusLadesauele: ");
Serial.println(StatusLadesauele);
Serial.print("Ueberschussladenaktiv: ");
Serial.println(Ueberschussladenaktiv);
Serial.print("Sofortladenaktiv: ");
Serial.println(Sofortladenaktiv);
Serial.print("HystereseRelais: ");
Serial.println(HystereseRelais);
Serial.print("modbusValuePVLeistung: ");
Serial.println(modbusValuePVLeistung);
Serial.print("modbusValuePVLeistungMaxCurrentRetry: ");
Serial.println(modbusValuePVLeistungMaxCurrentRetry);
Serial.print("TimesPVLeistungMaxCurrentRetrySeenLagerThanLadenErlaubenAbLeistung: ");
Serial.println(TimesPVLeistungMaxCurrentRetrySeenLagerThanLadenErlaubenAbLeistung);
Serial.println("_______________________________________________________________");
blinkerdebug.detach();
}*/
void(* resetFunc) (void) = 0;//declare reset function at address 0
void setup() {
Serial.begin(115200);
ModbusIPobject.client();
pinMode(PINOUT, OUTPUT); // Initialisieren des Ausgangs
digitalWrite(PINOUT, LOW); // Standardmäßig wird das Relais nicht geschalten = Schlüsselkontakt offen, Laden nicht möglich
HystereseRelais = 1;
blinkerresetHystereseRelais.attach(HystereseRelaisDauer, resetHystereseRelais);
lcd.clear();
// WiFi Connection
while (WiFi.status() != WL_CONNECTED) {
Serial.print("Starting WiFi connection"); WiFi.begin(ssid, pass); while (WiFi.status() != WL_CONNECTED) {
delay(100);
Serial.print(".");
} Serial.println("WiFi connected"); // Serial.print("IP address: "); // Serial.println(WiFi.localIP());
}
// Modbus Connection
while (!ModbusIPobject.isConnected(remote)) {
Serial.println("Starting Modbus connection...");
ModbusIPobject.connect(remote); // Try to connect if no connection
delay(5000);
if (!ModbusIPobject.isConnected(remote)) {
Serial.println("Modbus connection didn't work, resetting device");
resetFunc(); //call reset
}
}
Serial.println("Modbus connected");
ModbusIPobject.readHreg(remote, offsetToReadOperationStatefrom, &modbusResult, 1, nullptr, DeviceID); // nullptr
ModbusIPobject.readHreg(remote, offsetToReadPVfrom, &modbusResultPower, 1, nullptr, DeviceID);
ModbusIPobject.task();
delay(1000);
readPVValue();
Blynk.begin(auth, ssid, pass);
}