Siemens current monitoring output relay signal for EV-wallbox in Arduino project

Hi everyone,

I've been through basic tutorials and setups and reaching out to you on my 1st "own" project idea.

I have a PV on my roof and would like to make sure my EV-wallbox only charges the car if enough solar energy is beeing produced. I have a wallbox in my garage with an external-key contact that I plan to steer with a 5V relais modul connected to an arduino. It shall receive a signal from a second arduino via wifi whenever enough solar energy is produced.

To find out when that is the case I have a 3UG4622-1AW30 monitoring device from siemens measuring the current actually produced in the basement, far away from the garage but with wifi coverage.
It has 3 available output terminals, which open either circuit 12-11 or 11-14 starting at a defined current:
12 Output relay K1 CO contact NC contact
11 Output relay K1 CO contact root
14 Output relay K1 CO contact NO contact

Now I am thinking on how to get the signal from the siemens device into the transmitting arduino: would that be as simple as connecting siemens output terminal 11 to 3V-power-out of arduino and siemens output terminal 14 to an digital in of arduino, and then sketching with if (digitalRead(digitalPin7) == HIGH) ?

I am open to any remarks or recommendations on the project idea....as it is my first.

Yours
Michael

I could not find a relay contact diagram in the data sheet for that current threshold monitoring device but, in principle, you’d use the COM (root) and NO terminals to determine if the threshold has been exceeded.

You could connect the Arduino ground to COM and an Arduino digital pin to NO probably with a 1k pull-up resistor.

If you are using WiFi then you could use, for example, an ESP8266 to publish the state of the relay (say by running a web server on it , but there are alternatives ). In the garage, you’d have another ESP8266 which would periodically contact the web server to determine the relay state.

Hi,
Welcome to the forum.

Can you post a picture of your current relay please.
I hope it has on its side a diagram of how the terminals are configured.

I want searching for a proper data sheet, but it seems Siemens have let me down with nay connection examples.

Thanks.. Tom.. :slight_smile:

Please read the post at the start of any forum , entitled "How to use this Forum".
OR
http://forum.arduino.cc/index.php/topic,148850.0.html.

the website is quite overloaded, but here's the direct link to the manual https://support.industry.siemens.com/cs/attachments/54397927/manual_monitoring_relay_SIRIUS_monitoring_devices_en-US.pdf?download=true and attached the wiring diagramm.
the recommendation on ESP8266 looks perfectly fitting my needs and a bargain, thank you very much!

Is there a led or something on your inverter showing generation that you could sense as a different option ?

You might still have a issue that the PV is generating , but. It enough to power the charger so you still bet import powe

good point, I figured out the inverter has a tcp server to be activacted which is now serving the actually generated power via modbus. So I'm looking into ModbusIP_ESP8266.h - would also leave me with just one ESP8266 on the relais side and sparing me the one on sensor side.

Hi everyone,
I am using Modbus Master Simulator on my Windows PC to read the correct address and register from my inverter.
That works well
I could really use some help, after reading through everything I found on the topic I can't figure out why my sketch below won't return the right value, or any value (res is always 0).
Any help would be greatly appreciated. The magic should happen in the last few lines of code

/*
  Device ID 3
  Address 30775   +1  Holding registers
*/
#include <ESP8266WiFi.h>
#include <ModbusIP_ESP8266.h>
IPAddress remote(192, 168, 0, 21);  // Address of Modbus Slave device SMA
ModbusIP ModbusIPobject;  //ModbusIP object

uint16_t REG = 30775; 
const int DeviceID = 3;
const char ssid[] = "xx";
const char pass[] = "xy";

String inString = "";    // string to hold input

void setup() {
  Serial.begin(115200);
  Serial.println("");
  Serial.println("_______________________________________________________________");
  Serial.println("Serial connection available");
  ModbusIPobject.client();

}

uint16_t res = 0;

void loop() {

    int inChar = Serial.read();
    if (isDigit(inChar)) {
      // convert the incoming byte to a char and add it to the string:
      inString += (char)inChar;
    }
    // if you get a newline, print the string, then the string's value:
    if (inChar == '\n') {
      Serial.print("New Register:");
      Serial.println(inString.toInt());
      REG = inString.toInt();
      // clear the string for new input:
      inString = "";
    }
 

  



  // 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.print("WiFi connected, ");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
  }

  while (!ModbusIPobject.isConnected(remote)) {
    Serial.print("Starting Modbus connection...");
    ModbusIPobject.connect(remote);           // Try to connect if no connection
    while (!ModbusIPobject.isConnected(remote)) {
      delay(5);
      Serial.print(".");
    }
    Serial.println("Modbus connected");
  }

  ModbusIPobject.readHreg(remote, REG, &res, 1, nullptr, 3);
  

  ModbusIPobject.task();                      // Common local Modbus task  This method makes all magic, answering requests and changing the registers if necessary, it should be called only once, early in the loop.
  delay(2000);

  if (res > 0) {
    Serial.println("Adress__________");  Serial.println(REG);
    Serial.println("Result__________");  Serial.println(res);
  }
}

I figured it out :slight_smile: for any body looking for the same answer see the code below:

ModbusIPobject.readHreg(remote, 40223, &modbusResult, 1, nullptr, 126); // 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
  ModbusIPobject.readHreg(remote, 40199, &modbusResultPower, 1, nullptr, 126); // Power in watt currently generated, rounded and has to be multipled by 10
  ModbusIPobject.task();

/* The Unit ID in the SunSpec Modbus profile for SMA products derives from the preset Unit ID in the
  SMA Modbus profile + 123. The preset value for the Unit ID in the SunSpec Modbus profile is
  therefore 126.
  The Unit ID is a higher-level addressing type in the Modbus protocol. The SunSpec Modbus profile
  for SMA devices is set to the Unit ID = 126. For transmission of the SMA Grid Guard code to the
  inverters, you use additionally the Unit ID = 3 and the Modbus Register 43090.

  The SunSpec Modbus profile for the SMA products starts at the register number 40001

  Offset of the SunSpec register addresses
  For the reading and writing of Modbus registers, use the register addresses reduced by the
  offset 1 in each case.
  Example: Modbus register address = register address in the SunSpec Modbus profile – offset
  = 40001 - 1 = 40000.

  /*Data Transfer Interval via the Modbus protocol
    For system stability reasons, the time period between data transfers via the Modbus protocol must
    be at least ten seconds. No more than five parameters and measured values should be transmitted
    per SMA inverter.*/

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);
}

second (and last) code part and the pdf with the Modbus Specs from SMA - which really helped

void readPVValue()
{
  if (!ModbusIPobject.isConnected(remote)) {
    Serial.print("Modbus connection was broken, resetting device");
    resetFunc(); //call reset
  }
  /*Data Transfer Interval via the Modbus protocol
    For system stability reasons, the time period between data transfers via the Modbus protocol must
    be at least ten seconds. No more than five parameters and measured values should be transmitted
    per SMA inverter.*/
  measurement_timestamp = millis( );

 // modbusResult = 0;
  ModbusIPobject.readHreg(remote, offsetToReadOperationStatefrom, &modbusResult, 1, nullptr, DeviceID);
  ModbusIPobject.readHreg(remote, offsetToReadPVfrom, &modbusResultPower, 1, nullptr, DeviceID);

  ModbusIPobject.task();
  // Serial.println("");    Serial.print("Adress__________");  Serial.print(offsetToReadOperationStatefrom);    Serial.print("     Result__________");  Serial.println(modbusResult);

  if (modbusResult == 4 || modbusResult == 5) {
    Serial.println("");
    Serial.print("     State__________");  Serial.println(modbusResult);
    Serial.print("     Generated Power__________");  Serial.println(modbusResultPower * 10);

    modbusValuePVLeistung = modbusResultPower * 10;
    AnzahlMessungen++;
    if (modbusValuePVLeistung > modbusValuePVLeistungMaxCurrentRetry) {
      modbusValuePVLeistungMaxCurrentRetry = modbusValuePVLeistung;
       }
  }
  else {
    //  Serial.print("Gar keine PV-Erzeugung, Wert muss nicht ausgelesen werden");
    modbusValuePVLeistung = 0;
    modbusValuePVLeistungMaxCurrentRetry = 0;
  }
  alle10secsModbusauslesen.detach();
  alle10secsModbusauslesen.attach(10, readPVValue);
}

void setLEDState() {
  if (Sofortladenaktiv == 1) {
    led4Sofortladenaktiv.on();
  }
  else
    led4Sofortladenaktiv.off();
  if (Ueberschussladenaktiv == 1) {
    led5Ueberschussladenaktiv.on();
  }
  else
    led5Ueberschussladenaktiv.off();
  if (Winterladenaktiv == 1) {
    led9Winteraktiv.on();
  }
  else
    led9Winteraktiv.off();
}

void setLCDState() {
//  lcd.clear();
  if (StatusLadesauele == 1) {
    lcd.print(0, 0, "Laden bereit    ");
  }
  else
    lcd.print(0, 0, "Laden gesperrt  ");
  lcd.print(0, 1, AnzahlMessungen);
  lcd.print(1, 1, "Mes");
  lcd.print(5, 1, TimesPVLeistungMaxCurrentRetrySeenLagerThanLadenErlaubenAbLeistung);
  lcd.print(6, 1, ">Soll ");
  
  if (HystereseRelais == 1) {
    lcd.print(12, 1, "Hys");
  }
  else
    lcd.print(12, 1, "   ");
}

void loop() {

  setLEDState();
  setLCDState();
  Blynk.run();



  if (AnzahlMessungen == 3) {
    AnzahlMessungen = 0;

    if (Ueberschussladenaktiv == 1) {
      if (modbusValuePVLeistungMaxCurrentRetry >= LadenErlaubenAbLeistung) {
        switch (StatusLadesauele) {
          case 1:
            switch (TimesPVLeistungMaxCurrentRetrySeenLagerThanLadenErlaubenAbLeistung) {
              case 0:  // dürfte es nicht geben
                break;
              case 1: // wohl wechselhafter strom, aber jetzt wieder genug
                TimesPVLeistungMaxCurrentRetrySeenLagerThanLadenErlaubenAbLeistung++;
                break;
              case 2: // PV-Laden läuft schon, Relay wurde schon aktiviert, nichts machen, Fall tritt immer ein wenn konstante PV-Energie
                break;
            }
            break;
          case 0:
            switch (TimesPVLeistungMaxCurrentRetrySeenLagerThanLadenErlaubenAbLeistung) {
              case 0:  // erstmals Thresholde überschritten, Relay noch nicht schalten
                TimesPVLeistungMaxCurrentRetrySeenLagerThanLadenErlaubenAbLeistung++;
                break;
              case 1: // zweite Mal in Folge, Relay schalten
                if (HystereseRelais != 1) {
                  digitalWrite(PINOUT, HIGH);  // 2 Mal in Folge wurde der Thresholde überschritten also PV-Laden aktivieren

                  StatusLadesauele = 1;
                  TimesPVLeistungMaxCurrentRetrySeenLagerThanLadenErlaubenAbLeistung++;
                  HystereseRelais = 1;
                  blinkerresetHystereseRelais.attach(HystereseRelaisDauer, resetHystereseRelais);
                }

                break;
              case 2: // Fall tritt nie ein
                break;
            }
            break;
        }
      }
      else { // keine bzw zu wenig PV Energie
        switch (StatusLadesauele) {
          case 0: // PV-Laden eh schon aus
            switch (TimesPVLeistungMaxCurrentRetrySeenLagerThanLadenErlaubenAbLeistung) {
              case 0:  // dauerhaft kein strom
                break;
              case 1: // wohl wechselhafter strom
                TimesPVLeistungMaxCurrentRetrySeenLagerThanLadenErlaubenAbLeistung--;
                break;
              case 2: // dürfte es nicht geben
                break;
            }
            break;
          case 1: // Ladesäule aktuell noch an
            switch (TimesPVLeistungMaxCurrentRetrySeenLagerThanLadenErlaubenAbLeistung) {
              case 2:  // erstmals Threshold nicht überschritten, Relay nicht schalten, aber eins runter zählen
                TimesPVLeistungMaxCurrentRetrySeenLagerThanLadenErlaubenAbLeistung--;
                break;
              case 1: // zweite Mal in Folge, Relay schalten
                if (HystereseRelais != 1) {
                  digitalWrite(PINOUT, LOW);  // 2 Mal in Folge wurde der Thresholde überschritten also PV-Laden deaktivieren
                  StatusLadesauele = 0;
                  TimesPVLeistungMaxCurrentRetrySeenLagerThanLadenErlaubenAbLeistung--;
                  HystereseRelais = 1;
                  blinkerresetHystereseRelais.attach(HystereseRelaisDauer, resetHystereseRelais);
                }
                break;
              default:
              // if nothing else matches, do the default
              // default is optional
              // hier default hoch bzw runterzaehlen.
              case 0: // Fall tritt nie ein
                break;
            }
            break;
        }
      }

    }
    modbusValuePVLeistungMaxCurrentRetry = 0;
  }
}

SunSpec_Modbus-TI-en-15.pdf (1.41 MB)

Hi @micha_d, may I ask what modbus libraries did you use for this code?
I noticed your include lines were empty so I had to ask with the intention of running the code using ESP32.

hey @204maker I have no idea why in in the original posting the included libraries are gone, so here they are again:

#include <ESP8266WiFi.h>
#include <Modbus.h>
#include <ModbusIP_ESP8266.h>
#include <BlynkSimpleEsp8266.h>
#include <Ticker.h>

1 Like

Thank you @micha_d.
I donwloaded the ModbusIP_ESP8266.h after I saw it mentioned on the thread.

Here's what I tried to test a Modbus TCP connection:
ESP32 (Modbus server) <<<---- wifi ----->>> laptop (Modbus slave)
IP addr: 192.168.0.104 ----------------------------IP addr: 192.168.0.43, Port: 502
-------------------------------------------------------------Unit (slave) ID: 3
-------------------------------------------------------------Address: 30775

The ESP32 acts as the server while the laptop acts as the client running the Modbus slave software from modbus tools (https://www.modbustools.com/download.html)

I tried reading and writing (random values between 0 to 255 into 2 different address and it worked so far.

  • Read from Address: 30775
  • Write to Address: 30777

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.