Project showcase
Automatic Transfer Switch (ATS) with Web Server

Automatic Transfer Switch (ATS) with Web Server © GPL3+

Monitor power supply and start emergency power generator in case of power failure.

  • 1,766 views
  • 1 comment
  • 7 respects

Components and supplies

About this project

ATS - Automatic Transfer Switch

Overview

The Automatic Transfer Switch (ATS) is a self-designed by Jörg Mahn and was developed and built in the period from March to June 2019. The controller is based on an Arduino with Ethernet Shield attached. There are also other modules such as real-time clock, LCD display, three power meters, and two one-wire temperature sensors. Switching the power supply from the utility to the emergency generator is done by a modified dual-power automatic transfer switch. This contains a servo motor which is controlled accordingly by the Arduino. The mains and generator voltage are monitored by three phase monitors. Furthermore, a web server and an e-mail client are implemented via which information can be sent and the operating mode can be changed manually.

Arduino

The microprocessor is an Arduino Mega 2560 with Ethernet Shield 2 with SD card slot. The SD card stores the most important parameters, as well as the templates for the web server and e-mail client. The real-time clock is a DS3231 and communicates with the Arduino via I²C bus. The clock is synchronized via NTP over the Internet. This happens at the start of the program. Voltage, current and power are measured on all three phases of the line from the generator to the house. This is done by three PZEM-004T digital multifunction meters. Two one-wire DS18B20 temperature sensors measure the temperature at the motor and generator. The generator is started by two relays located in the "UV PV & Generator". The relays are controlled by a ULN2004A driver. The dual-power automatic transfer switch is controlled by a small relay board located on the (carrier) board with the Arduino.

Generator

The generator has a 4-cylinder diesel engine. According to the nameplate it is a CAMINO 30MIL and produces at 400V 20KW, year 01/2009. At the time of purchase, 450 hours were read off the hour meter.

As long as the generator is connected to the control cable, it can not be started via the built-in ignition lock. For automatic operation, the ignition key must be in position "I." When set to "0," the generator is off and will not start automatically in the event of a power failure.

The ignition lock can be put back into operation if a dongle (located in the "UV PV & Generator") is plugged in instead of the control cable.

Function

The ATS works completely automatically. Each time the status changes an e-mail will be sent. Current data is displayed via the LCD and the integrated web server.


Code

ats.inoArduino
Arduino Sketch
/*
  ATS - Automatic Transfer Switch
  (c) 03'2019 by Jrg R. Mahn
  For Arduino Mega 2560

  Erkennt einen Stromausfall, trennt das Versorgungsnetz,
  startet das Notstromaggreagt, schaltet die Stromversorung auf das Aggregat um.
  Nach dem das Versorgungnetz wieder verfgbar ist, wird das Notstromaggregat getrennt,
  die Stromversorgung wird wieder auf das Versorgungsnetz umgeschaltet,
  das Notstromaggregat wird gestoppt.

  Features:
  - 20x4 LCD
  - DS3231 Realtime Clock with Internet Sync
  - Webserver (Port 80) (Pages stored on SD-Card)
  - E-Mail Client to send status E-Mail
  - One Wire Temperature Measurement
  - 3 x PZEM004T Power Meter

  09.04.2019  Erstes Release mit allen Modulen
  04.05.2019  Settings von SD-Karte laden
  06.05.2019  IP Settings von SD Karte laden
  08.05.2019  Zeiten von SD-Karte laden
  17.05.2019  Umstellung auf INI-Datei
  24.05.2019  Verschiebung der Strings in den Flash-Speicher
  10.06.2019  ndern der Reihenfolge beim Rckschalten.
              Esrt Generator ausschalten, dann Netzwiederkehr
  12.06.2019  Optische Verbesserungen am LCD
  22.07.2019  nderung der Leistungsanzeige
  25.07.2019  Anzeige der Batteriespannung
  28.07.2019  Temperaturanzeige (Motor und Auen) erfolgreich. (Pullup Widerstand von 2,2K auf 1,5K gesenkt
  05.08.2019  LCD Status "Fehler INI-File" eingefhrt, Variable "Leistung" globalisiert.
  13.08.2019  PIN 5 (ldruck OK) als Indikator fr den Lauf des Motors hinzugefgt. Funktion Generator abgendert.
              Funktion CheckState (Abschnitt Netzausfall im Status "Netzbetrieb" oder "Netztrennung in...") auf
              berwachung des Motors und Phasen ergnzt. Tastenpiep hinzugefgt.
              minbatvoltage zur INI-Datei hinzugefgt
  19.08.2019  Weiterer Web-Parameter hinzugefgt: showconfig. Zeigt die Aktuelle Konfiguration aus der INI-Datei an.
  20.08.2019  INDEX.HTM in drei Dateien (a.htm, b.htm, c.htm) zerlegt.
  21.08.2019  Webseiten der config.ini hinzugefgt. Styles der a.htm hinzugefgt.
  22.08.2019  Funktion Alert fr die Sprachausgabe ber die Haussteuerung hinzugefgt. (=> V.2.7)
  23.08.2019  Bug in der Generatorfunktion behoben. (Zndung ging nicht wieder an beim >1 Startversuch.)
              Startzeit (ignitiontime) wird nach jedem erfolglosem Startversuch um 100ms verlngert
  24.08.2019  berwachung des Zndschlosses hinzugefgt. Analogeingang A1 (=> V.2.8)
  13.09.2019  Struckturen fr Generator und Temperaturdaten eingefhrt
  17.09.2019  Datei-Upload implementiert. (=> V.2.9)
  19.09.2019  ATS-Konfiguration komplett neu gestaltet. (=> V.3.0)
  20.09.2019  Sicherheitsabfrage beim Lschen hinzugefgt
  27.09.2019  JSON Abfrage eingefhrt (=> V.3.1)
  02.10.2019  Volkszhler Option eingefhrt
  04.10.2019  Bug beim Dateiupload entfernt, HC und VZ enable eingefhrt (=> V.3.2)
  07.10.2019  MQTT eingefhrt (=> V.3.3)
  08.10.2019  Verbesserung des Webservers (Zeilenweise auslieferung)
              HTML-Vorlagen auf eine Datei reduziert.
              JSON Abfrage entfernt.
  09.10.2019  Last will and testament und Version hinzugefgt.
*/

String version = "3.3 09.10.2019";

#include <PZEM004T.h>           // Digitale Multifunktions Meter PZEM-004T
#include <Time.h>               // Timekeeping functionality
#include <TimeLib.h>
#include <DS3231.h>             // DS3231 - Version: Latest 
#include <Wire.h>               // IC bus (Mega2560: 20 (SDA), 21 (SCL))
#include <LiquidCrystal.h>      // LC-Display
#include <OneWire.h>            // Access 1-wire Bus (Pin 22)
#include <DallasTemperature.h>  // OneWire Temperatursensor
#include <SPI.h>                // Serial Peripheral Interface
#include <Ethernet.h>           // Ethernet library
#include <EthernetUdp.h>        // UDP library (for Internet-Time-Sync)
#include <SD.h>                 // Access SD-Card
#include <IniFile.h>            // INI-File Acces Routines
#include <Dns.h>                // getHostByName
#include <avr/pgmspace.h>       // Um Strings in den Flash-Speicher zu verschieben
#include <MQTT.h>

//#define BUFSIZ 100
File sdFile;
const char *configFile = "/config.ini";

// The select pin used for the SD card
#define SD_SELECT 4
#define ETHERNET_SELECT 53

// Settings Konfiguration
const size_t bufferLen = 80;    // Pufferlnge fr INI-Datei = Maximale Zeilenlnge
char buffer[bufferLen];

byte lostmain;
byte backmain;
byte glowtime;
byte ignitiontime;
byte starttrying;
byte genstartdelay;
byte genstoppdelay;
byte switchdelay;
float minbatvoltage;              // Minimale Batteriespannung bevor Warnung
char domain[20];
char host[20];
byte ip[4];
byte dns[4];
byte gateway[4];
byte subnet[4];
char ntp[bufferLen] = "";         // Aus INI-Datei, kann IP oder Hostname sein
char m_server[20] = "";
String m_to;
String m_to_rn;
String m_from;
String m_from_rn;

bool VZenable = false;
char UUID[bufferLen] = "";        // Daten zum Volkserver puschen
char VZServer[bufferLen] = "";
char VZRequest[bufferLen] = "";
unsigned long VZTime;

char Mail_File[20] = {};          // HTML-Seiten fr das Webfrontend
char ATS_File[20] = {};
char Config_File[20] = {};
char CSS_File[20] = {};

bool HCenable = false;
char HCServer[bufferLen] = "";
char HCRequest[bufferLen] = "";
byte HCStart;
byte HCStop;
byte HCError;
byte HCLoss;
byte HCBack;

bool MQTTenable = false;
char MQTTServer[bufferLen] = "";
char MQTTTopic[bufferLen] = "";
char MQTTMessage[bufferLen] = "";
char MQTTUsername[bufferLen] = "";
char MQTTPassword[bufferLen] = "";

// Konfiguration Netzwerk und Webserver
EthernetServer server(80);
byte mac[] = { 0xA8, 0x61, 0x0A, 0xAE, 0x3E, 0x54 };
IPAddress timeServer; // TimeServer (IP-Adresse) fr NTP-Sync

// Konfiguration E-Mail-Server
EthernetClient mailclient;

// Konfiguration MQTT
EthernetClient MQTTnet;
MQTTClient MQTT;

// Konfiguration NTP-Server
EthernetUDP Udp;
unsigned int localPort = 8888;
const int NTP_PACKET_SIZE = 48;
byte packetBuffer[NTP_PACKET_SIZE];

// Konfiguration DS3231 RTC
DS3231 Clock;
bool Century = false;
bool h12;
bool PM;

// Konfiguration One-Wire
#define ONE_WIRE_BUS 22
OneWire oneWire(ONE_WIRE_BUS);

// 1-Wire (Dallas) Temperatursensoren
DallasTemperature sensors(&oneWire);

// Konfiguration LCD
#define LCD_RS 37
#define LCD_E 36
#define LCD_D4 35
#define LCD_D5 34
#define LCD_D6 33
#define LCD_D7 32
#define LCD_Rows 4
#define LCD_Cols 20
LiquidCrystal lcd(LCD_RS, LCD_E, LCD_D4, LCD_D5, LCD_D6, LCD_D7);
String lcdHead = "ATS (c) 2019 J. Mahn";

// Konfiguration PZEM004T Power-Meter
PZEM004T* pzem1;
PZEM004T* pzem2;
PZEM004T* pzem3;
IPAddress pip1(192, 168, 1, 1);
IPAddress pip2(192, 168, 1, 2);
IPAddress pip3(192, 168, 1, 3);

//LCD Sonderzeichen
#define dg 0xDF                 // 

// Ausgnge fr das Zndschloss
#define PIN_BUTTON 2            // Mentaste
#define PIN_BUZZER 3            // Pieper
#define PIN_OEL 5               // Eingang ldruck LED grn
#define PIN_PHASE_NETZ 24       // Phasenkontrolle Netzversorger
#define PIN_PHASE_GEN 25        // Phasenkontrolle Generator
#define PIN_Z_EIN 28            // Zndschloss "Ein"
#define PIN_Z_START 29          // Zndschloss "Start"
#define REL_NETZ 30             // Schtz Netzversorger
#define REL_GEN 31              // Schtz Generator
#define BAT_INPUT 0             // Analogeingang fr Generator-Batteriespannung
#define ZUENDSCHLOSS 1          // Analogeingang fr Zuendschloss (Off / Standby)

#define ON 2
#define OFF 1

byte page = 1;                  // Start-Menseite
bool batlow = false;            // Generatorbatteriespannung zu gering
const byte maxPage = 5;         // Max. Anzahl Menseiten
long time = 0;                  // used for debounce
long debounce = 50;             // how many ms to "debounce" between presses
bool etherStatus;               // Zustand der Ethernet-Verbindung
char weekDay[7][2] = {"So", "Mo", "Di", "Mi", "Do", "Fr", "Sa" };
byte Status = 10;               // Index fr Betriebsart (Initalisierung)
byte timeZone = 1;              // Zeitzone
float Leistung = 0;             // Gesammt-Leistung
byte byte_array[4];             // Puffer fr IP-Adressen aus der INI-Datei / Char2IP()
bool zuendung = false;          // Zndschloss (Off / Standby)
int ram;
char timestamp[30];
 
// Status-Strings (Max 20 Chars)
const char status_0[] PROGMEM = "Netzbetrieb";
const char status_1[] PROGMEM = "Generatorbetrieb";
const char status_2[] PROGMEM = "Manueller Betrieb";
const char status_3[] PROGMEM = "Strung Generator";
const char status_4[] PROGMEM = "Strung Manuell";
const char status_5[] PROGMEM = "Stromausfall";
const char status_6[] PROGMEM = "Generator Start";
const char status_7[] PROGMEM = "Kein Netzwerk";
const char status_8[] PROGMEM = "Netztrennung";
const char status_9[] PROGMEM = "Netzwiederkehr";
const char status_10[] PROGMEM = "Initialisierung";
const char status_11[] PROGMEM = "SD-Card Fehler";
const char status_12[] PROGMEM = "Umschaltung";
const char status_13[] PROGMEM = "Vorglhen";
const char status_14[] PROGMEM = "Zndung";
const char status_15[] PROGMEM = "Drehzal berprfen";
const char status_16[] PROGMEM = "Generator stoppen";
const char status_17[] PROGMEM = "Fehler INI-File";
const char status_18[] PROGMEM = "Generator luft";

byte MaxStatus = 18;

const char *const status_table[] PROGMEM = {
  status_0,
  status_1,
  status_2,
  status_3,
  status_4,
  status_5,
  status_6,
  status_7,
  status_8,
  status_9,
  status_10,
  status_11,
  status_12,
  status_13,
  status_14,
  status_15,
  status_16,
  status_17,
  status_18
};

// INI-Strings
const char ini_0[] PROGMEM = "Generator Einstellungen";
const char ini_1[] PROGMEM = "lostmain";
const char ini_2[] PROGMEM = "backmain";
const char ini_3[] PROGMEM = "glowtime";
const char ini_4[] PROGMEM = "ignitiontime";
const char ini_5[] PROGMEM = "starttrying";
const char ini_6[] PROGMEM = "genstartdelay";
const char ini_7[] PROGMEM = "genstoppdelay";
const char ini_8[] PROGMEM = "switchdelay";
const char ini_9[] PROGMEM = "minbatvoltage";

const char ini_10[] PROGMEM = "Network";
const char ini_11[] PROGMEM = "IP";
const char ini_12[] PROGMEM = "DNS";
const char ini_13[] PROGMEM = "GW";
const char ini_14[] PROGMEM = "SUB";
const char ini_15[] PROGMEM = "NTP";

const char ini_16[] PROGMEM = "Mail";
const char ini_17[] PROGMEM = "Server";
const char ini_18[] PROGMEM = "From";
const char ini_19[] PROGMEM = "To";
const char ini_20[] PROGMEM = "From_RN";
const char ini_21[] PROGMEM = "To_RN";

const char ini_22[] PROGMEM = "Webserver";
const char ini_23[] PROGMEM = "Files";

const char ini_24[] PROGMEM = "HC";
const char ini_25[] PROGMEM = "Enabled";
const char ini_26[] PROGMEM = "Server";
const char ini_27[] PROGMEM = "Request";
const char ini_28[] PROGMEM = "Start";
const char ini_29[] PROGMEM = "Stop";
const char ini_30[] PROGMEM = "Error";
const char ini_31[] PROGMEM = "Loss";
const char ini_32[] PROGMEM = "Back";

const char *const ini_table[] PROGMEM = {
  ini_0,
  ini_1,
  ini_2,
  ini_3,
  ini_4,
  ini_5,
  ini_6,
  ini_7,
  ini_8,
  ini_9,
  ini_10,
  ini_11,
  ini_12,
  ini_13,
  ini_14,
  ini_15,
  ini_16,
  ini_17,
  ini_18,
  ini_19,
  ini_20,
  ini_21,
  ini_22,
  ini_23,
  ini_24,
  ini_25,
  ini_26,
  ini_27,
  ini_28,
  ini_29,
  ini_30,
  ini_31,
  ini_32
};

// Mail-Strings (Max 80 Chars)
const char mail_0[] PROGMEM = "Anforderung zum Generatorbetrieb, Generator gestartet";
const char mail_1[] PROGMEM = "Anforderung zum Generatorbetrieb, Generator nicht gestartet";
const char mail_2[] PROGMEM = "Anforderung zum Netzbetrieb, Generator gestoppt";
const char mail_3[] PROGMEM = "Anforderung zum Netzbetrieb, Generator nicht gestoppt";
const char mail_4[] PROGMEM = "Anforderung zum Generatorstart, Generator gestartet";
const char mail_5[] PROGMEM = "Anforderung zum Generatorstart, Generator nicht gestartet";
const char mail_6[] PROGMEM = "Anforderung zum Generatorstopp, Generator gestoppt";
const char mail_7[] PROGMEM = "Anforderung zum Generatorstopp, Generator nicht gestoppt";
const char mail_8[] PROGMEM = "Netzausfall";
const char mail_9[] PROGMEM = "Netzwiederkehr";
const char mail_10[] PROGMEM = "Generator gestartet";
const char mail_11[] PROGMEM = "Generator gestoppt";
const char mail_12[] PROGMEM = "Strung Generator";
const char mail_13[] PROGMEM = "Neustart";
const char mail_14[] PROGMEM = "Status";
const char mail_15[] PROGMEM = "Spannung Starterbatterie zu gering";
const char mail_16[] PROGMEM = "Anforderung zum Reboot";
const char mail_17[] PROGMEM = "Generator ist Standby";
const char mail_18[] PROGMEM = "Generator ist aus";

const char *const mail_table[] PROGMEM = {
  mail_0,
  mail_1,
  mail_2,
  mail_3,
  mail_4,
  mail_5,
  mail_6,
  mail_7,
  mail_8,
  mail_9,
  mail_10,
  mail_11,
  mail_12,
  mail_13,
  mail_14,
  mail_15,
  mail_16,
  mail_17,
  mail_18
};

//MQTT-Strings
const char mqtt_0[25] PROGMEM = "/set/reset";
const char mqtt_1[25] PROGMEM = "/set/betrieb";
const char mqtt_2[25] PROGMEM = "/set/email";
const char mqtt_3[25] PROGMEM = "/set/generator";

const char mqtt_4[25] PROGMEM = "/leistung";
const char mqtt_5[25] PROGMEM = "/status";
const char mqtt_6[25] PROGMEM = "/ram";

const char mqtt_7[25] PROGMEM = "/phase1/leistung";
const char mqtt_8[25] PROGMEM = "/phase1/strom";
const char mqtt_9[25] PROGMEM = "/phase1/spannung";

const char mqtt_10[25] PROGMEM = "/phase2/leistung";
const char mqtt_11[25] PROGMEM = "/phase2/strom";
const char mqtt_12[25] PROGMEM = "/phase2/spannung";

const char mqtt_13[25] PROGMEM = "/phase3/leistung";
const char mqtt_14[25] PROGMEM = "/phase3/strom";
const char mqtt_15[25] PROGMEM = "/phase3/spannung";

const char mqtt_16[25] PROGMEM = "/temperatur/system";
const char mqtt_17[25] PROGMEM = "/temperatur/motor";
const char mqtt_18[25] PROGMEM = "/temperatur/aussen";

const char mqtt_19[25] PROGMEM = "/get/betrieb";
const char mqtt_20[25] PROGMEM = "/get/generator";
const char mqtt_21[25] PROGMEM = "/get/status";
const char mqtt_22[25] PROGMEM = "/get/version";
const char mqtt_23[25] PROGMEM = "/get/batterie";

const char *const mqtt_table[] PROGMEM = {
  mqtt_0,
  mqtt_1,
  mqtt_2,
  mqtt_3,
  mqtt_4,
  mqtt_5,  
  mqtt_6,
  mqtt_7,
  mqtt_8,
  mqtt_9,
  mqtt_10,
  mqtt_11,  
  mqtt_12,
  mqtt_13,  
  mqtt_14,
  mqtt_15,  
  mqtt_16,
  mqtt_17,
  mqtt_18,
  mqtt_19,
  mqtt_20,
  mqtt_21,
  mqtt_22,
  mqtt_23
};

byte letztertag = 0;            // Fr die tgliche Zeitsynchronosation

typedef struct _Temperaturen_ {
  String SysTemperatur;           // Temperatur auf dem RTC
  String MotorTemperatur;         // One-Wire Temperatursensor Motor
  String AussenTemperatur;        // One-Wire Temperatursensor Generator
} Temperaturen;
Temperaturen t_data;

String StrLeistung;             // Gesamtleistung

float genbat;
String GeneratorBatterie;
int timeout = 0;
// Deklarierung fr die Pausen
unsigned long previousMillis = 0;

typedef struct _Powermeter_ {
  float L1_Spannung = 0;
  float L2_Spannung = 0;
  float L3_Spannung = 0;
  float L1_Strom = 0;
  float L2_Strom = 0;
  float L3_Strom = 0;
  float L1_Leistung = 0;
  float L2_Leistung = 0;
  float L3_Leistung = 0;
} Powermeter;
Powermeter p_data;

void MQTTconnect() {
  Serial.print("Connecting to MQTT...");
  while (!MQTT.connect("ats", MQTTUsername, MQTTPassword)) {
    Serial.print(".");
    delay(1000);
  }
  Serial.println("\nConnected!");
  MQTT.subscribe(String(MQTTTopic)+"/set/#");
}

void LCD_Start() {
  LCD_Head();
  lcd.setCursor(0, 1);
  delay(2);
  lcd.print(showDate());
  lcd.print("  ");
  lcd.print(showTime());
  LCD_Status(Status);
  Link_Gen();
}

void LCD_Verbrauch() {
  String str;
  LCD_Head();

  lcd.setCursor(0, 1);
  delay(2);
  str = form(String(p_data.L1_Spannung, 0) + "V", 6);
  str += form(String(p_data.L2_Spannung, 0) + "V", 6);
  str += form(String(p_data.L3_Spannung, 0) + "V", 6);
  lcd.print(fillup(str));

  lcd.setCursor(0, 2);
  delay(2);
  str = form(String(p_data.L1_Strom, 1) + "A", 6);
  str += form(String(p_data.L2_Strom, 1) + "A", 6);
  str += form(String(p_data.L3_Strom, 1) + "A", 6);
  lcd.print(fillup(str));

  lcd.setCursor(0, 3);
  delay(2);
  if (p_data.L1_Leistung > 1000) {
    p_data.L1_Leistung = p_data.L1_Leistung / 1000;
    str = form(String(p_data.L1_Leistung, 1) + "KW", 6);
  } else {
    str = form(String(p_data.L1_Leistung, 0) + "W", 6);
  }
  if (p_data.L2_Leistung > 1000) {
    p_data.L2_Leistung = p_data.L2_Leistung / 1000;
    str += form(String(p_data.L2_Leistung, 1) + "KW", 6);
  } else {
    str += form(String(p_data.L2_Leistung, 0) + "W", 6);
  }
  if (p_data.L3_Leistung > 1000) {
    p_data.L3_Leistung = p_data.L3_Leistung / 1000;
    str += form(String(p_data.L3_Leistung, 1) + "KW", 6);
  } else {
    str += form(String(p_data.L3_Leistung, 0) + "W", 6);
  }
  lcd.print(fillup(str));
}

void LCD_Leistungen() {
  String str;
  Leistung = p_data.L1_Leistung + p_data.L2_Leistung + p_data.L3_Leistung;
  LCD_Head();

  lcd.setCursor(0, 1);
  delay(2);
  str = "Leistung:  ";
  str += form(String(Leistung, 0) + " W", 8);
  lcd.print(fillup(str));

  lcd.setCursor(0, 2);
  delay(2);
  str = "Batterie:  ";
  str += form(ConvSeperator(GeneratorBatterie) + " V", 8);
  lcd.print(fillup(str));

  lcd.setCursor(0, 3);
  delay(2);
  str = " ";
  lcd.print(fillup(str));
}

void LCD_Temperaturen() {
  String str;
  LCD_Head();
  lcd.setCursor(0, 1);
  delay(2);
  str = "Motor:     ";
  str += form(t_data.MotorTemperatur + " " + (char)dg + "C", 8);
  lcd.print(fillup(str));

  lcd.setCursor(0, 2);
  delay(2);
  str = "Aussen:    ";
  str += form(t_data.AussenTemperatur + " " + (char)dg + "C", 8);
  lcd.print(fillup(str));

  lcd.setCursor(0, 3);
  delay(2);
  str = "System:    ";
  str += form(t_data.SysTemperatur + " " + (char)dg + "C", 8);
  lcd.print(fillup(str));
}

void LCD_Infos() {
  LCD_Head();

  lcd.setCursor(0, 1);
  delay(2);
  lcd.print(fillup((String) "Ver. " + version));

  lcd.setCursor(0, 2);
  delay(2);
  testLink();
  if (etherStatus) {
    lcd.print(fillup("IP " + DisplayAddress(Ethernet.localIP()) + "*"));
  } else {
    lcd.print(fillup("IP " + DisplayAddress(Ethernet.localIP())));
  }

  lcd.setCursor(0, 3);
  delay(2);
  lcd.print(fillup((String) ram + " Bytes RAM free"));
}

void loop(void) {
  CheckState();
  
  if (MQTTenable) { 
    MQTT.loop();
    if (!MQTT.connected()) {
      MQTTconnect();
    }
  }

  Webserver();
  switch (page) {
    case 1:
      LCD_Start();
      break;
    case 2:
      LCD_Verbrauch();
      break;
    case 3:
      LCD_Leistungen();
      break;
    case 4:
      LCD_Temperaturen();
      break;
    case 5:
      LCD_Infos();
      break;
  }
}
B_setup.inoArduino
Setup Routines
void setup() {
  while (!Serial) { }       // wait for serial port to connect. Needed for native USB port only
  Serial.begin(9600);
  PgmPrintln("ATS Version ");
  Serial.println(version);

  // LCD Initalisieren
  lcd.begin(LCD_Cols, LCD_Rows);
  delay(2);
  LCD_Head();
  LCD_Status(Status);

  // Initalisieren der drei Power Meter
  while (!Serial1) { }
  pzem1 = new PZEM004T(&Serial1);
  pzem1->setAddress(pip1);
  while (!Serial2) { }
  pzem2 = new PZEM004T(&Serial2);
  pzem2->setAddress(pip2);
  while (!Serial3) { }
  pzem3 = new PZEM004T(&Serial3);
  pzem3->setAddress(pip3);

  // Digital-Eingnge
  pinMode(PIN_BUTTON, INPUT_PULLUP);    // Menbutton
  attachInterrupt(digitalPinToInterrupt(PIN_BUTTON), InterruptButton, LOW);
  pinMode(PIN_PHASE_NETZ, INPUT_PULLUP);// Phasenkontrolle Netzversorger
  pinMode(PIN_PHASE_GEN, INPUT_PULLUP); // Phasenkontrolle Generator
  pinMode(PIN_OEL, INPUT);              // Digital-Eingang Kontrolle ldruck LED grn

  // Analog-Eingnge
  pinMode(BAT_INPUT, INPUT);            // Batteriespannungsmessung (Spannungsteiler (10K - 100k)
  pinMode(ZUENDSCHLOSS, INPUT);         // Spannungsmessung Zndschloss (Off / Standby) (10K - 100k)

  // Digital Ausgnge
  pinMode(PIN_BUZZER, OUTPUT);          // Digitalausgang Pieper
  pinMode(SD_SELECT, OUTPUT);
  digitalWrite(SD_SELECT, HIGH);        // disable SD card
  pinMode(ETHERNET_SELECT, OUTPUT);
  digitalWrite(ETHERNET_SELECT, HIGH);  // disable Ethernet
  pinMode(10, OUTPUT);                  // set the SS pin as an output (necessary!)
  digitalWrite(10, HIGH);               // but turn off the W5100 chip!

  // Umschalt Relais sind LOW-Active!
  pinMode(REL_NETZ, OUTPUT);            // Relais Netzversorger
  digitalWrite(REL_NETZ, HIGH);
  pinMode(REL_GEN, OUTPUT);             // Relais Generator
  digitalWrite(REL_GEN, HIGH);

  // Generator Relais sind HIGH-Active!
  pinMode(PIN_Z_EIN, OUTPUT);           // Zndschloss "Ein"
  digitalWrite(PIN_Z_EIN, LOW);
  pinMode(PIN_Z_START, OUTPUT);         // Zndschloss "Start"
  digitalWrite(PIN_Z_START, LOW);

  SPI.begin();

  if (!SD.begin(SD_SELECT)) {
    while (1) {
      PgmPrintln("SD.begin() failed");
      LCD_Status(11); // "SD-Card Fehler"
    }
  }
  delay(1);

  // INI-File von SD-Card einlesen und Variabeln setzen.
  ReadIniFile();

  // Start the I2C interface
  Wire.begin();
  delay(1);

  // Start the Ethernet connection and the server:
  Ethernet.begin(mac, ip, dns, gateway, subnet);
  server.begin();
  delay(1000); // Ethernet Start abwarten.
  // Check for Ethernet hardware present
  PgmPrintln("Waiting for Network");

  if (Ethernet.hardwareStatus() == EthernetNoHardware) {
    PgmPrintln("Ethernet shield was not found.  Sorry, can't run without hardware. :(");
    while (true) {
      LCD_Status(7); // "Kein Netzwerk"
      delay(1); // do nothing, no point running without Ethernet hardware
    }
  }

  // Teste Ethernet Kabel Verbindung
  testLink();
  while (!etherStatus) {
    LCD_Status(7); // Kein Netzwerk
    testLink();
  }

  // verbinde zum MQTT Brooker
  if (MQTTenable) {
    MQTT.begin(MQTTServer, MQTTnet);
    MQTT.onMessage(MQTTmessageReceived);
    // Last will and testament
    strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[21])));   // /get/status
    char topic[bufferLen] = ""; strcat(topic, MQTTTopic); strcat(topic, buffer);
    MQTT.setWill(topic, "Offline", true, 0);
    MQTTconnect();
  }
  
  LCD_Status(10);  // LCD-Status: Initalisierung

  // Webserver starten
  PgmPrintln("Webserver starting at: ");
  Serial.print(Ethernet.localIP());
  PgmPrintln(":80");

  // NTP-Eintrag aus INI-Datei in IP-Adresse auflsen (Egal ob IP oder Hostname)
  DNSClient dns;
  dns.begin(Ethernet.dnsServerIP());
  dns.getHostByName(ntp, timeServer);

  // Zeit mit Internetzeit Synchronisieren
  Udp.begin(localPort);
  PgmPrintln("Waiting for Timesync...");
  delay(1000); // Ethernet Start abwarten.
  syncTime();
  Serial.println(showDate());
  Serial.println(showTime());
  
  // set date time callback function
  SdFile::dateTimeCallback(dateTime);
  
  // Freien RAM ermitteln
  // ram = (String) FreeRam();
  ram = FreeRam();
  Serial.print(ram);
  PgmPrintln(" Bytes RAM free");

  // Auf Netzbetrieb schalten
  digitalWrite(REL_GEN, HIGH);
  digitalWrite(REL_NETZ, LOW);
  delay(switchdelay * 1000);
  digitalWrite(REL_NETZ, HIGH);
  page = 1;

  // Zndung an?
  int value = 0;
  value = analogRead(ZUENDSCHLOSS);
  if (value > 0) {
    zuendung = true;
  } else {
    zuendung = false;
  }
  
  if (HCenable) { Serial.println(F("Home-Controll: aktive")); } else { Serial.println(F("Home-Controll: not aktive")); }
  if (VZenable) { Serial.println(F("Volkzaehler: aktive")); } else { Serial.println(F("Volkzaehler: not aktive")); }
  if (MQTTenable) { Serial.println(F("MQTT: aktive")); } else { Serial.println(F("MQTT: not aktive")); }
  
  CheckState();
  Webserver();
  sendEmail(13);
  piep();
  Status = 0;
  PgmPrintln("READY");
}
C_checkstate.inoArduino
Check States
void CheckState() {
  unsigned long currentMillis = millis();
  noInterrupts();

  // Freien Speicher berechnen
  ram = FreeRam();
  
  // Temepraturwerte holen
  GetTemp(&t_data);

  // Stromwerte holen
  GetCurrent(&p_data);
  Leistung = p_data.L1_Leistung + p_data.L2_Leistung + p_data.L3_Leistung;


  // Zum VZ puschen
  unsigned long interval = 1000;  // 1sec "delay"
  if (millis() - VZTime > interval )  {
    PushVZ(VZServer, VZRequest, UUID, Leistung);
    VZTime = millis(); 
  }

  if (Leistung > 999) {
    Leistung = Leistung / 1000;
    StrLeistung = form(String(Leistung, 2) + " KW", 6);
  } else {
    StrLeistung = form(String(Leistung, 0) + " W", 6);
  }
  
  // zum MQTT Broker pushen
  if (MQTTenable) {  
    // mqtt_table[0-3] = subsriber Topics
    
    strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[4])));
    MQTT.publish(String(MQTTTopic) + (String) buffer, StrLeistung);
    strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[5])));
    MQTT.publish(String(MQTTTopic) + (String) buffer, String(Status));
    strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[6])));
    MQTT.publish(String(MQTTTopic) + (String) buffer, String(ram));
    
    strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[7])));
    MQTT.publish(String(MQTTTopic) + (String) buffer, String(p_data.L1_Leistung));
    strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[8])));
    MQTT.publish(String(MQTTTopic) + (String) buffer, String(p_data.L1_Strom));
    strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[9])));
    MQTT.publish(String(MQTTTopic) + (String) buffer, String(p_data.L1_Spannung));

    strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[10])));
    MQTT.publish(String(MQTTTopic) + (String) buffer, String(p_data.L2_Leistung));
    strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[11])));
    MQTT.publish(String(MQTTTopic) + (String) buffer, String(p_data.L2_Strom));
    strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[12])));
    MQTT.publish(String(MQTTTopic) + (String) buffer, String(p_data.L2_Spannung));

    strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[13])));
    MQTT.publish(String(MQTTTopic) + (String) buffer, String(p_data.L3_Leistung));
    strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[14])));
    MQTT.publish(String(MQTTTopic) + (String) buffer, String(p_data.L3_Strom));
    strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[15])));
    MQTT.publish(String(MQTTTopic) + (String) buffer, String(p_data.L3_Spannung));
    
    strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[16])));
    MQTT.publish(String(MQTTTopic) + (String) buffer, String(t_data.SysTemperatur));
    strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[17])));
    MQTT.publish(String(MQTTTopic) + (String) buffer, String(t_data.MotorTemperatur));
    strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[18])));
    MQTT.publish(String(MQTTTopic) + (String) buffer, String(t_data.AussenTemperatur));

    
    // Netzbetrieb
    if (Status==0) { 
      strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[19])));   // /get/betrieb
      MQTT.publish(String(MQTTTopic) + (String) buffer, "netz");
    }
    
    // Generatorbetrieb
    if (Status==1) { 
      strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[19])));   // /get/betrieb
      MQTT.publish(String(MQTTTopic) + (String) buffer, "generator");
    }
    
    // Generator luft
    if (Status==1) { // Generatorbetrieb
      strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[20])));   // /get/generator
      MQTT.publish(String(MQTTTopic) + (String) buffer, F("start"));
    }
    if (Status==18) { // Generator luft
      strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[20])));   // /get/generator
      MQTT.publish(String(MQTTTopic) + (String) buffer, F("start"));
    }

    // Generator luft nicht
    if (Status==0) {  // Netzbetrieb
      strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[20])));   // /get/generator
      MQTT.publish(String(MQTTTopic) + (String) buffer, F("stop"));
    }
    if (Status==3) {  // Strung Generator
      strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[20])));   // /get/generator
      MQTT.publish(String(MQTTTopic) + (String) buffer, F("stop"));
    }
    if (Status==4) {  // Strung Manuell
      strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[20])));   // /get/generator
      MQTT.publish(String(MQTTTopic) + (String) buffer, F("stop"));
    }

    for (int i=0; i<=MaxStatus; i++) {
      if (i==Status) {
        strcpy_P(buffer, (char *)pgm_read_word(&(status_table[i])));   // Lesbaren Status pushen
        MQTT.publish(String(MQTTTopic) + F("/get/status"), (String) buffer);
      }
    }

    if (zuendung) {
      if (Status == 1 || Status == 18) {
        MQTT.publish(String(MQTTTopic) + F("/get/generator"), F("Ein"));
      } else {
        MQTT.publish(String(MQTTTopic) + F("/get/generator"), F("Standby"));
      }
    } else {
      MQTT.publish(String(MQTTTopic) + F("/get/generator"), F("Aus"));
    }

    strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[22])));   // /get/version
    MQTT.publish(String(MQTTTopic) + (String) buffer, version);

    strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[23])));   // /get/batterie
    MQTT.publish(String(MQTTTopic) + (String) buffer, ConvSeperator(GeneratorBatterie));

  }

  // Sende keine E-Mail whrend der Initalisierung
  // Zndung an?

  int value = 0;
  value = analogRead(ZUENDSCHLOSS);
  if (value > 0) {
    if (!zuendung) {
      zuendung = true;
      sendEmail(17);
    }
  } else {
    if (zuendung) {
      zuendung = false;
      sendEmail(18);
    }
  }

  //Batteriespannung holen
  genbat = GetBatteryVoltage();
  GeneratorBatterie = String(genbat, 1);
  if (genbat < minbatvoltage) {
    if (!batlow) {
      sendEmail(15);
    }
    batlow = true;
  } else {
    batlow = false;
  }

  // Alles LOW-Active!
  // HIGH = Strom ausgefallen

  // Netzwiederkehr bevor Timeout Netzausfall
  if (!digitalRead(PIN_PHASE_NETZ) && Status == 8) {
    piep();
    page = 1;
    Status = 0; //  "Netzbetrieb"
    timeout = 0;
    LCD_Status(Status);
    previousMillis = 0;
    sendEmail(9);
    PgmPrintln("Netzwiederkehr bevor Timeout Netzausfall");
  }

  // Netzausfall bevor Timeout Netzwiederkehr
  if (digitalRead(PIN_PHASE_NETZ) && Status == 9) {
    piep();
    page = 1;
    Status = 1; // "Generatorbetrieb"
    timeout = 0;
    LCD_Status(Status);
    previousMillis = 0;
    sendEmail(8);
    PgmPrintln("Netzausfall bevor Timeout Netzwiederkehr");
  }

  // Netzausfall im Status "Netzbetrieb" oder "Netztrennung in..."
  if (digitalRead(PIN_PHASE_NETZ) && (Status == 0 || Status == 8)) {
    if (previousMillis == 0) previousMillis = currentMillis;
    if (Status == 0) {
      page = 1;
      Status = 8; // "Netztrennung"
      sendEmail(Status);
      Alert(HCServer, HCRequest, HCLoss);
      PgmPrintln("Netzausfall im Status Netzbetrieb, Countdown gestartet.");
      piep();
    }
    timeout = lostmain * 60 - ((currentMillis - previousMillis) / 1000);
    LCD_Status(Status);

    if (timeout <= 0) {
      previousMillis = 0;
      piep();
      PgmPrintln("Netzausfall!");
      Alert(HCServer, HCRequest, HCStart);
      if (Generator(ON)) {      // Wenn Generator gestartet...
        if (!digitalRead(PIN_PHASE_GEN)) {  // ...und alle drei Phasen vorhanden...
          Status = 1; // "Generatorbetrieb"
          LCD_Status(Status);
          SwitchGeneratorbetrieb(); // ...dann Umschalten auf Generatorbetrieb.
          sendEmail(10);
          PgmPrintln("Generator gestartet, Umschalten auf Generatorbetrieb.");
        } else { // Generator liefert keine Spannungen
          Status = 3; // "Strung Generator"
          Alert(HCServer, HCRequest, HCError);
          LCD_Status(Status);
          Generator(OFF); // Generator ausschalten
          sendEmail(12);
          PgmPrintln("Generator gestartet, aber Phasen liefern keine Spannung!");
        }
      } else { // Generator konnte nicht gestartet werden
        Status = 3; //  "Strung Generator"
        Alert(HCServer, HCRequest, HCError);
        LCD_Status(Status);
        Generator(OFF);
        sendEmail(12);
        PgmPrintln("Generator konnte nicht gestartet werden!");
      }
    }
  }

  // Netzwiederkehr im Status "Generatorbetrieb" und "Netzwiederkehr in..."
  if (!digitalRead(PIN_PHASE_NETZ) && (Status == 1 || Status == 9)) {
    if (previousMillis == 0) previousMillis = currentMillis;
    if (Status == 1) {
      page = 1;
      Status = 9; // "Netzwiederkehr"
      Alert(HCServer, HCRequest, HCBack);
      PgmPrintln("Netzwiederkehr im Status Generatorbetrieb, Countdown gestartet.");
      sendEmail(Status);
      piep();
    }
    timeout = (backmain * 60) - ((currentMillis - previousMillis) / 1000);
    LCD_Status(Status);

    if (timeout <= 0) {
      previousMillis = 0;
      PgmPrintln("Netzwiederkehr im Status Generatorbetrieb");
      Status = 0; //  "Netzbetrieb"
      Alert(HCServer, HCRequest, HCStop);
      piep();
      Generator(OFF);
      LCD_Status(Status);
      SwitchNetzbetrieb(); // Umschalten auf Netzbetrieb
      sendEmail(11);
    }
  }

  // Netzwiederkehr im Status "Strung Generator"
  if (!digitalRead(PIN_PHASE_NETZ) && Status == 3) {
    PgmPrintln("Netzwiederkehr im Status Strung");
    page = 1;
    Status = 0;  // "Netzbetrieb";
    Alert(HCServer, HCRequest, HCBack);
    piep();
    Generator(OFF);
    LCD_Status(Status);
    SwitchNetzbetrieb();
    sendEmail(9);
  }

  // Generatorwiederkehr im Status "Strung Generator"
  if (!digitalRead(PIN_PHASE_GEN) && Status == 3) {
    PgmPrintln("Generatorwiederkehr im Status Strung Generator");
    page = 1;
    Status = 1; // "Generatorbetrieb"
    piep();
    //Generator(ON);
    LCD_Status(Status);
    SwitchGeneratorbetrieb(); // Umschalten auf Generatorbetrieb
    sendEmail(10);
  }

  // Generatorausfall im Status "Generatorbetrieb"
  if (digitalRead(PIN_PHASE_GEN) && Status == 1) {
    PgmPrintln("Generatorausfall im Status Generatorbetrieb");
    Generator(OFF);
    SwitchNetzbetrieb(); // Umschalten auf Netzbetrieb und hoffen, dass der Strom bald wieder kommt!
    page = 1;
    Status = 3; // "Strung Generator"
    Alert(HCServer, HCRequest, HCError);
    piep();
    LCD_Status(Status);
    sendEmail(12);
  }

  // Generatorausfall im Status "Manueller Betrieb"
  if (digitalRead(PIN_PHASE_GEN) && Status == 2) {
    PgmPrintln("Generatorausfall im Status Manuellerbetrieb");
    Generator(OFF);
    page = 1;
    Status = 4; // "Strung Manuell"
    Alert(HCServer, HCRequest, HCError);
    piep();
    LCD_Status(Status);
    sendEmail(12);
  }

  // Generatorwiederkehr im Status "Strung Manuell"
  if (!digitalRead(PIN_PHASE_GEN) && Status == 4) {
    PgmPrintln("Generatorwiederkehr im Status Strung Manuell");
    page = 1;
    Status = 2; // "Manueller Betrieb"
    piep();
    Generator(ON);
    LCD_Status(Status);
    // SwitchGeneratorbetrieb(); // Umschalten auf Generatorbetrieb
    sendEmail(10);
  }

  interrupts();
}
D_webserver.inoArduino
Webserver & Parser
void Webserver() {
  String readString = "";
  char c;
  char br[5] = "<br>";
  char dp[3] = ": ";

  EthernetClient client = server.available();
  if (client) {


    // an http request ends with a blank line
    boolean currentLineIsBlank = true;
    while (client.connected()) {

      if (client.available()) {
        c = client.read();

        readString += c;

        if (c == '\n' && currentLineIsBlank) {

          // Datei Upload
          if (readString.indexOf("multipart/form-data;") > 0) {

            readString = "";
            currentLineIsBlank = true;
            while (client.connected()) {

              if (client.available()) {
                char w = client.read();
                readString += w;

                if (w == '\n' && currentLineIsBlank) {
                  // Dateiname extrahieren, Datei anlegen
                  int a = readString.indexOf("filename=") + 10;
                  readString = readString.substring(a);
                  a = readString.indexOf("\"");
                  readString.remove(a);
                  if (SD.exists(readString)) {
                    Serial.print(F("Upload: ")); Serial.print(readString); Serial.println(F(" ist bereits vorhanden, wird berschrieben."));
                    SD.remove(readString);
                  }
                  sdFile = SD.open(readString, FILE_WRITE);
                  int i = 0;

                  // stay in this loop until the file has been received
                  while (client.connected()) {

                    if (client.available()) {
                      w = client.read();  // get file byte

                      if (w == 0x2D) { // 1.
                        w = client.read();
                        if (w == 0x2D) {  // 2.
                          w = client.read();
                          if (w == 0x2D) {  // 3.
                            w = client.read();
                            if (w == 0x2D) {  // 4.
                              w = client.read();
                              if (w == 0x2D) {  // 5.
                                w = client.read();
                                if (w == 0x2D) {  // 6.
                                  sdFile.close();
                                  if (SD.exists(readString)) {
                                    Serial.print(F("Upload: "));
                                    Serial.print(readString);
                                    Serial.println(F(" OK"));
                                  } else {
                                    Serial.print(F("Upload: "));
                                    Serial.print(readString);
                                    Serial.println(F(" Fehlgeschlagen"));
                                  }
                                  goto Configuration;
                                  //client.stop();
                                  //break;
                                } else {
                                  sdFile.print("-");
                                  sdFile.print("-");
                                  sdFile.print("-");
                                  sdFile.print("-");
                                  sdFile.print("-");
                                  sdFile.print(w);
                                }
                              } else {
                                sdFile.print("-");
                                sdFile.print("-");
                                sdFile.print("-");
                                sdFile.print("-");
                                sdFile.print(w);
                              }
                            } else {
                              sdFile.print("-");
                              sdFile.print("-");
                              sdFile.print("-");
                              sdFile.print(w);
                            }
                          } else {
                            sdFile.print("-");
                            sdFile.print("-");
                            sdFile.print(w);
                          }
                        } else {
                          sdFile.print("-");
                          sdFile.print(w);
                        }
                      } else {
                        sdFile.print(w);
                      }
                    }
                  }
                }
                // detect the end of the incoming HTTP header
                if (w == '\n') {
                  // starting a new line
                  currentLineIsBlank = true;
                }
                else if (w != '\r') {
                  // got a character on the current line
                  currentLineIsBlank = false;
                }
              }
            }
          }


          // Download von Dateien von der SD-Karte
          if (readString.indexOf("?download") > 0 ) {
            int a = readString.indexOf("?download") + 10;
            int b = readString.indexOf(" HTTP/");
            readString = readString.substring(a, b);
            client.println(F("HTTP/1.1 200 OK"));
            client.println(F("Content-Type: text/html"));
            client.println();
            client.println(F("<!DOCTYPE html>"));
            client.println(F("<html>"));
            client.println(F("<head>"));
            client.print(F("<title>ATS-Konfiguration (")); client.print(readString); client.println(F(")</title>"));
            client.println(F("<meta name=\"referrer\" content=\"no-referrer\" />"));
            client.println(F("</head><body><pre>"));
            readString.toUpperCase();
            sdFile = SD.open(readString);
            if (!sdFile) {
              client.println(F("<h1>File not Found!</h1>"));
              Serial.println(F("Download: ")); Serial.print(readString); Serial.println(F(" nicht gefunden!"));
              client.stop();
              break;
            } else {
              Serial.print(F("Download: ")); Serial.println(readString);
              while (sdFile.available())   {
                int x = sdFile.read();
                if (x == 60) client.print("&lt;");
                if (x == 62) client.print("&gt;");
                if (x != 60 && x != 62) client.print(char(x));
              }
              sdFile.close();
            }
            client.println(F("</pre></body></html>"));
            client.stop();
            break;
          }

          // Reboot Arduino
          if (readString.indexOf("?reset") > 0 ) {
            PgmPrintln("Anforderung per Webfrontend zum Reset.");
            sendEmail(16);
            client.println(F("HTTP/1.1 200 OK"));
            client.println(F("Content-Type: text/html"));
            client.println();
            client.print(F("<!DOCTYPE html><html><head><title>ATS</title><meta http-equiv=\"refresh\" content=\"10; URL=http://"));
            client.print(host); client.print(F(".")); client.print(domain);
            client.print(F("\"><meta name=\"referrer\" content=\"no-referrer\" /></head><body><h1>Rebooting...</h1></body></html>"));
            delay(1);
            client.stop();
            delay(2000);
            softReset();
            break;
          }

          // Datei von SD-Karte lschen
          if (readString.indexOf("?deletefile") > 0 ) {
            //Serial.print(readString);
            int a = readString.indexOf("?deletefile") + 12;
            int b = readString.indexOf("&");
            int c = readString.indexOf(" HTTP");
            String filename = readString.substring(a, b);

            if (readString.substring(b + 1, c) == "delete=OK") {
              filename.toUpperCase();
              if (filename != "CONFIG.INI") {
                if (SD.exists(filename)) {
                  SD.remove(filename);
                  if (!SD.exists(filename)) {
                    Serial.print(F("Delete: ")); Serial.print(filename); Serial.println(F(" OK"));
                  } else {
                    Serial.print(F("Delete: ")); Serial.print(filename); Serial.println(F(" Fehlgeschlagen"));
                  }
                } else {
                  Serial.println(F("Delete: ")); Serial.print(filename); Serial.println(F(" nicht gefunden!"));
                }
              } else {
                Serial.println(F("Delete: config.ini darf nicht gelscht werden!"));
              }
            }
            goto Configuration;
            break;
          }

          // Datei lschen / Sicherheitsabfrage
          if (readString.indexOf("?askdelete") > 0 ) {
            int a = readString.indexOf("?askdelete") + 11;
            int b = readString.indexOf(" HTTP/");
            readString = readString.substring(a, b);
            client.println(F("HTTP/1.1 200 OK"));
            client.println(F("Content-Type: text/html"));
            client.println();
            client.print(F("<!DOCTYPE html><html><head><title>ATS-Konfiguration</title>"));
            client.print(F("<meta name=\"referrer\" content=\"no-referrer\" /></head><style>"));
            client.print(F("table {background-color: #D8D8D8;border-collapse: collapse;border: none;}"));
            client.print(F("thead {background-color: #DF0101;}"));
            client.print(F("input[type=submit] { padding:15px 15px; background:#ccc; border:0 none; cursor:pointer; -webkit-border-radius: 5px; border-radius: 5px; }"));
            client.print(F("td, th {text-align: center;padding: 0.5em 1em;}</style><body>"));
            client.print(F("<form method=\"get\" name=\"ats\">"));
            client.print(F("<input type=\"hidden\" name=\"deletefile\" value=\""));
            client.print(readString);
            client.print(F("\">"));
            client.print(F("<table>"));
            client.print(F("<thead><tr><th colspan=\"2\"><h1><font face=\"verdana\" color=\"white\">Achtung!</font></h1></th><thead></tr>"));
            client.print(F("<tbody><tr><td colspan=\"2\"><h2><font face=\"verdana\">Soll ")); client.print(readString); client.print(F(" wirklich gel&ouml;scht werden?</font></h2></td></tr></tbody>"));
            client.print(F("<tbody><tr>"));
            client.print(F("<td><input type=\"submit\" name=\"delete\" value=\"OK\"></td>"));
            client.print(F("<td><input type=\"submit\" name=\"delete\" value=\"Cancel\"></td>"));
            client.print(F("</tr></tbody></table></form>"));
            client.print(F("</body></html>"));
            delay(1);
            client.stop();
            break;
          }

          // Konfiguration anzeigen
          if (readString.indexOf("?config") > 0 ) {
Configuration:
            client.println(F("HTTP/1.1 200 OK"));
            client.println(F("Content-Type: text/html"));
            client.println();
            client.println(F("<!DOCTYPE html>"));
            client.println(F("<style>"));
            DeliverContent(&client, CSS_File);
            client.println(F("</style>"));
            ParseContent(&client, Config_File);
            break;
          }

          // Notfall Formular um Dateien hoch zu laden
          if (readString.indexOf("?upload") > 0 ) {
            client.println(F("HTTP/1.1 200 OK"));
            client.println(F("Content-Type: text/html"));
            client.println();
            client.print(F("<!DOCTYPE html><html><head><title>ATS</title><meta name=\"referrer\" content=\"no-referrer\" /></head><body><h1>Datei auf SD-Karte speichern</h1></body></html>"));
            client.print(F("<form method=\"post\" enctype=\"multipart/form-data\">"));
            client.print(F("<input type=\"button\" value=\"Zur&uuml;ck\" onclick=\"window.location.href='http://"));
            client.print(host); client.print(F(".")); client.print(domain);
            client.print(F("'\" />&nbsp;&nbsp;"));
            client.print(F("<input type=\"file\" name=\"datei\" accept=\"text/*\"><input class=\"button\" type=\"submit\" value=\"Upload\">&nbsp;&nbsp;"));
            client.print(F("<input type=\"button\" value=\"Reboot\" onclick=\"window.location.href='http://"));
            client.print(host); client.print(F(".")); client.print(domain);
            client.print(F("/?reset'\" />&nbsp;&nbsp;"));
            client.print(F("</form></body></html>"));
            delay(1);
            client.stop();
            break;
          }

          if (readString.indexOf("?power") > 0 ) { // Abfrage der aktuellen Leistung
            client.println(F("HTTP/1.1 200 OK"));
            client.println(F("Content-Type: text/plain"));
            client.println();
            client.println(ConvSeperator(StrLeistung));
            delay(1);
            client.stop();
            break;
          }

          if (readString.indexOf("?mainbet") > 0 ) { // Netzbetrieb
            PgmPrintln("Anforderung per Webfrontend zum Netzbetrieb");
            Status = 0; // "Netzbetrieb"
            Alert(HCServer, HCRequest, HCStop);
            if (!Generator(OFF)) {
              SwitchNetzbetrieb();
              sendEmail(2);
            } else {
              Status = 4; // "Strung Manuell"
              sendEmail(3);
            }
            Print_Status[Status];
          }

          if (readString.indexOf("?genbet") > 0 ) { // Generatorbetrieb, Stromausfall simulieren
            PgmPrintln("Anforderung per Webfrontend zum Generatorbetrieb");
            Status = 2; // Manueller Betrieb
            Alert(HCServer, HCRequest, HCStart);
            if (Generator(ON)) {
              SwitchGeneratorbetrieb();
              sendEmail(0);
            } else {
              Status = 4; // "Strung Manuell"
              Alert(HCServer, HCRequest, HCError);
              sendEmail(1);
            }
            Print_Status[Status];
          }

          if (readString.indexOf("?genstart") > 0 ) { // Generator starten
            PgmPrintln("Anforderung per Webfrontend zum Generatorstart");
            Alert(HCServer, HCRequest, HCStart);
            if (Generator(ON)) {
              Status = 18; // "Generator luft"
              sendEmail(4);
            } else {
              Status = 4; // "Strung Manuell"
              Alert(HCServer, HCRequest, HCError);
              sendEmail(5);
            }
            Print_Status[Status];
          }

          if (readString.indexOf("?genstop") > 0 ) { // Generator stoppen
            PgmPrintln("Anforderung per Webfrontend zum Generatorstopp");
            Alert(HCServer, HCRequest, HCStop);
            if (!Generator(OFF)) {
              Status = 0; // "Netzbetrieb"
              sendEmail(6);
            } else {
              Status = 4; // Strung Manuell
              sendEmail(7);
            }
            Print_Status[Status];
          }

          if (readString.indexOf("?sendmail") > 0 ) { // Status-Seite als E-Mail versenden
            PgmPrintln("Anforderung per Webfrontend zum Status-Mail-Versand");
            sendEmail(14);
          }

          // HTTP-Request lschen um Speicher frei zu geben.
          readString = "";

          // Webseite an Client ausliefern
          client.println(F("HTTP/1.1 200 OK"));
          client.println(F("Content-Type: text/html"));
          client.println();
          client.println(F("<!DOCTYPE html>"));
          client.println(F("<style>"));
          DeliverContent(&client, CSS_File);
          client.println(F("</style>"));
          ParseContent(&client, ATS_File);

          break;
        }
        if (c == '\n') {
          // you're starting a new line
          currentLineIsBlank = true;
        } else if (c != '\r') {
          // you've gotten a character on the current line
          currentLineIsBlank = false;
        }
      }
    }
    delay(1);
    client.stop();
  }
}


void DeliverSDRoot(EthernetClient *client) {
  File root;
  root = SD.open("/");
  while (true) {
    File entry = root.openNextFile();
    if (! entry) { // no more files
      break;
    }
    if (!entry.isDirectory()) {
      client->print(F("<tr><td id=\"a\">"));
      String filename = String(entry.name());
      filename.toLowerCase();
      client->print(F("<a href=\"http://"));
      client->print(host); client->print(F(".")); client->print(domain);
      client->print(F("/?download="));
      client->print(filename);
      client->print(F("\"><font class=\"n\">"));
      client->print(filename);
      client->print(F("</font></a>"));
      client->print(F("</td><td id=\"a\"><font class=\"n\">"));
      client->print(entry.size(), DEC);
      client->print(F("</font></td><td id=\"a\">"));
      client->print(F("<a href=\"http://"));
      client->print(host); client->print(F(".")); client->print(domain);
      client->print(F("/?askdelete="));
      client->print(filename);
      if (filename == "config.ini") {
        client->print(F("\"><font class=\"n\">"));
      } else {
        client->print(F("\"><font class=\"n\">X"));
      }
      client->print(F("</font></a></td>"));
      client->println(F("</tr>"));
    }
    entry.close();
  }
  delay(1);
}

void DeliverContent(EthernetClient *client, char *filename) {
  char c;
  char temp[200] = {};
  int i = 0;

  sdFile = SD.open(filename);
  if (!sdFile) {
    Serial.println(F("DeliverContent"));
    Serial.print(filename); Serial.println(F(" nicht gefunden!"));
    return false;
  }
  while (sdFile.available()) {
    c = sdFile.read();
    if ((c == '\n'))  {        // Zeile fertig eingelesen
      int len = strlen(temp);
      temp[len] = '\0';
      client->print(temp);     // ... und an Webclient ausliefern
      i = 0;
      memset(temp, '\0', sizeof(temp));
    } else {
      temp[i] = c;
      i++;
    }
  }
  sdFile.close();
}

void ParseContent(EthernetClient *client, char *filename) {
  char c;
  char temp[200] = {};
  String strLine = "";
  int i = 0;

  // Ist die Datei berhaupt da?
  sdFile = SD.open(filename);
  if (!sdFile) {
    Serial.println(F("ParseWebContent"));
    Serial.print(filename); Serial.println(F(" nicht gefunden!"));
    return false;
  }

  while (sdFile.available())   {
    c = sdFile.read();
    if ((c == '\n'))  {        // Zeile fertig eingelesen
      int len = strlen(temp);
      temp[len] = '\0';
      strLine = String(temp);

      // SD-Karteninhalt ausgeben
      if (strLine.indexOf("<sdcontent>") > 0) {
        DeliverSDRoot(client);
      }

      strLine.replace("%ram%", String(ram));
      strLine.replace("%ver%", version);
      strLine.replace("%z%", showTime());
      strLine.replace("%d%", showDate());
      strLine.replace("%sta%", Web_Status(Status));
      if (zuendung) {
        if (Status == 1 || Status == 18) {
          strLine.replace("%gen%", "Ein");
        } else {
          strLine.replace("%gen%", "Standby");
        }
      } else {
        strLine.replace("%gen%", "Aus");
      }
      strLine.replace("%sys%", ConvSeperator(t_data.SysTemperatur));
      strLine.replace("%mot%", ConvSeperator(t_data.MotorTemperatur));
      strLine.replace("%aut%", ConvSeperator(t_data.AussenTemperatur));
      strLine.replace("%bat%", ConvSeperator(GeneratorBatterie));
      strLine.replace("%p%", ConvSeperator(StrLeistung));
      strLine.replace("%1v%", String(p_data.L1_Spannung, 0));
      strLine.replace("%2v%", String(p_data.L2_Spannung, 0));
      strLine.replace("%3v%", String(p_data.L3_Spannung, 0));
      strLine.replace("%1c%", ConvSeperator(String(p_data.L1_Strom, 1)));
      strLine.replace("%2c%", ConvSeperator(String(p_data.L2_Strom, 1)));
      strLine.replace("%3c%", ConvSeperator(String(p_data.L3_Strom, 1)));

      strLine.replace("%lm%", String(lostmain));
      strLine.replace("%bm%", String(backmain));
      strLine.replace("%gt%", String(glowtime));
      strLine.replace("%it%", String(ignitiontime * 100));
      strLine.replace("%st%", String(starttrying));
      strLine.replace("%startd%", String(genstartdelay));
      strLine.replace("%stopd%", String(genstoppdelay));
      strLine.replace("%sd%", String(switchdelay));
      strLine.replace("%mv%", ConvSeperator(String(minbatvoltage, 1)));

      strLine.replace("", "&ouml;");
      strLine.replace("", "&auml;");
      strLine.replace("", "&uuml;");
      strLine.trim();

      client->println(strLine);     // ... und an Webclient ausliefern

      i = 0;
      memset(temp, '\0', sizeof(temp));

    } else {
      temp[i] = c;
      i++;
    }
  }
  sdFile.close();
}
functions.inoArduino
Functions
///////////////////////////////////////////////////////////////////////////////
// functions.ino
///////////////////////////////////////////////////////////////////////////////

void GetCurrent(Powermeter *data) {
  float v = pzem1->voltage(pip1);
  if (v < 0.0) v = 0.0;
  float p = pzem1->power(pip1);
  if (p < 0.0) p = 0.0;
  float i = pzem1->current(pip1);
  if (i < 0.0) i = 0.0;
  data->L1_Spannung = v;
  data->L1_Strom = i;
  data->L1_Leistung = p;

  v = pzem2->voltage(pip2);
  if (v < 0.0) v = 0.0;
  p = pzem2->power(pip2);
  if (p < 0.0) p = 0.0;
  i = pzem2->current(pip2);
  if (i < 0.0) i = 0.0;
  data->L2_Spannung = v;
  data->L2_Strom = i;
  data->L2_Leistung = p;

  v = pzem3->voltage(pip3);
  if (v < 0.0) v = 0.0;
  p = pzem3->power(pip3);
  if (p < 0.0) p = 0.0;
  i = pzem3->current(pip3);
  if (i < 0.0) i = 0.0;
  data->L3_Spannung = v;
  data->L3_Strom = i;
  data->L3_Leistung = p;
}

void GetTemp(Temperaturen *data) {
  //Temepraturwerte holen
  sensors.requestTemperatures(); // Send the command to get temperature readings
  data->AussenTemperatur = String(sensors.getTempCByIndex(0), 1);
  data->MotorTemperatur = String(sensors.getTempCByIndex(1), 1);
  data->SysTemperatur = String(Clock.getTemperature(), 1); // Fragt die Temperatur auf dem RTC ab
}


void testLink() {
  if (Ethernet.linkStatus() == Unknown) {
    etherStatus = false;
  }
  else if (Ethernet.linkStatus() == LinkON) {
    etherStatus = true;
  }
  else if (Ethernet.linkStatus() == LinkOFF) {
    etherStatus = false;
  }
}

void Link_Gen() {
  #define on 0xFF
  #define off 0x20

  testLink();
  lcd.setCursor(0, 3);
  delay(2);

  lcd.print(" LAN");
  if (etherStatus) {
    lcd.write(on);
  } else {
    lcd.write(off);
  }
  lcd.print("  NETZ");
  if (!digitalRead(PIN_PHASE_NETZ)) {
    lcd.write(on);
  } else {
    lcd.write(off);
  }
  lcd.print("  GEN");
  if (!digitalRead(PIN_PHASE_GEN)) {
    lcd.write(on);
  } else {
    lcd.write(off);
  }
  lcd.print("  ");
}


String DisplayAddress(IPAddress address) {
  return String(address[0]) + "." +
         String(address[1]) + "." +
         String(address[2]) + "." +
         String(address[3]);
}

String fillup(String str) {
  str = str.substring(0, (LCD_Cols));
  while (str.length() < LCD_Cols) {
    str = str + " ";
  }
  return str;
}

void SwitchNetzbetrieb() {
  piep();
  LCD_Status(12);
  switchREL(2, HIGH);
  switchREL(1, LOW);
  delay(switchdelay * 1000);
  switchREL(1, HIGH);
}

void SwitchGeneratorbetrieb() {
  piep();
  LCD_Status(12); //"Umschaltung"
  switchREL(1, HIGH);
  switchREL(2, LOW);
  delay(switchdelay * 1000);
  switchREL(2, HIGH);
}

void switchREL(int i, bool status) {
  //  Releais sind LOW-Active!
  if (i == 1) { // Netz-Schtz
    if (digitalRead(REL_GEN)) {
      digitalWrite(REL_NETZ, status);
    }
  }
  if (i == 2) { // Generator-Schtz
    if (digitalRead(REL_NETZ)) {
      digitalWrite(REL_GEN, status);
    }
  }
}

void InterruptButton() {  // Menbutton
  if (millis() - time > debounce)  {
    piep();
    page++;
    if (page > maxPage) {
      page = 1;
    }
    if (page < 1) {
      page = 1;
    }
    //    lcd.clear();
  }
  time = millis();
  delay(10);
}

String zero(String str) {
  if (str.length() < 2) {
    str = "0" + str;
  }
  return str;
}

String showDate() {
  return zero(String(Clock.getDate())) + "." + zero(String(Clock.getMonth(Century))) + ".20" + String(Clock.getYear());
}

String showTime() {
  return zero(String(Clock.getHour(h12, PM))) + ":" + zero(String(Clock.getMinute())) + ":" + zero(String(Clock.getSecond()));
}

void syncTime() {
  Clock.setClockMode(false);  // set to 24h

  // Internetzeit holen und RTC syncen
  setSyncProvider(getNtpTime);
  while (timeStatus() == timeNotSet); // wait until the time is set by the sync provider

  Clock.setYear(year() - 2000);
  Clock.setMonth(month());
  Clock.setDate(day());
  Clock.setDoW(weekday());
  Clock.setHour(hour());
  Clock.setMinute(minute());
  Clock.setSecond(second());

  if (sommerzeit(year(), month(), day(), hour(), 1)) {
    PgmPrintln("Sommerzeit");
    Clock.setHour(hour() + 1);
    if (hour() >= 24) {
      Clock.setHour(0);
      Clock.setDate(day() + 1);
    }
  }
}

/*-------- NTP code ----------*/
unsigned long getNtpTime()
{
  //  IPAddress timeServer(arr_ntp);
  sendNTPpacket(timeServer); // send an NTP packet to a time server
  delay(1000);
  if ( Udp.parsePacket() ) {
    Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into buffer
    //the timestamp starts at byte 40, convert four bytes into a long integer
    unsigned long hi = word(packetBuffer[40], packetBuffer[41]);
    unsigned long low = word(packetBuffer[42], packetBuffer[43]);
    // this is NTP time (seconds since Jan 1 1900
    ////unsigned long secsSince1900 = hi << 16 | low;
    // Unix time starts on Jan 1 1970
    ////const unsigned long seventyYears = 2208988800UL;
    ////unsigned long epoch = secsSince1900 - seventyYears;  // subtract 70 years
    ////return epoch;

    unsigned long secsSince1900;
    secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
    secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
    secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
    secsSince1900 |= (unsigned long)packetBuffer[43];
    return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;

  }
  return 0; // return 0 if unable to get the time
}


// send an NTP request to the time server at the given address
unsigned long sendNTPpacket(IPAddress address)
{
  memset(packetBuffer, 0, NTP_PACKET_SIZE);  // set all bytes in the buffer to 0

  // Initialize values needed to form NTP request
  packetBuffer[0] = B11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum
  packetBuffer[2] = 6;     // Max Interval between messages in seconds
  packetBuffer[3] = 0xEC;  // Clock Precision
  // bytes 4 - 11 are for Root Delay and Dispersion and were set to 0 by memset
  packetBuffer[12]  = 49;  // four-byte reference ID identifying
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;

  // send the packet requesting a timestamp:
  Udp.beginPacket(address, 123); //NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}

byte sendEmail(int sub) {
  String subject;
  char buffer[81];
  
  //Serial.print(F("Mailserver: "));Serial.println(m_server);
  
  if (mailclient.connect(m_server, 25)) {
    PgmPrintln("Sending email");
  } else {
    PgmPrintln("Connection to mailserver failed");
    return 0;
  }

  
  strcpy_P(buffer, (char *)pgm_read_word(&(mail_table[sub])));
  subject = (String) buffer;
  subject.trim();
  subject.replace("", "\xF6");

  if (!eRcv()) return 0;
  mailclient.print(F("HELO "));
  mailclient.println(DisplayAddress(Ethernet.localIP()));
  if (!eRcv()) return 0;
  mailclient.print(F("MAIL From: <"));
  mailclient.print(m_from);
  mailclient.println(F(">"));
  if (!eRcv()) return 0;
  mailclient.print(F("RCPT To: <"));
  mailclient.print(m_to);
  mailclient.println(F(">"));
  if (!eRcv()) return 0;
  mailclient.println(F("DATA"));
  if (!eRcv()) return 0;
  mailclient.print(F("To: "));
  mailclient.print(m_to_rn);
  mailclient.print(F("<"));
  mailclient.print(m_to);
  mailclient.println(F(">"));
  mailclient.print(F("From: "));
  mailclient.print(m_from_rn);
  mailclient.print(F("<"));
  mailclient.print(m_from);
  mailclient.println(F(">"));
  mailclient.print(F("Subject: "));
  mailclient.println(subject);
  mailclient.println(F("MIME-Version: 1.0"));
  mailclient.println(F("Content-type: text/html"));
  ParseContent(&mailclient, Mail_File);
  mailclient.println(F("."));
  if (!eRcv()) return 0;
  mailclient.println(F("QUIT"));
  if (!eRcv()) return 0;
  delay(1);
  mailclient.stop();
  return 1;
}

byte eRcv() {
  byte respCode;
  byte thisByte;
  int loopCount = 0;
  while (!mailclient.available()) {
    delay(1);
    loopCount++;
    // if nothing received for 10 seconds, timeout
    if (loopCount > 10000) {
      mailclient.stop();
      PgmPrintln("\r\nTimeout");
      return 0;
    }
  }
  respCode = mailclient.peek();
  /*
    while (mailclient.available()) {
    thisByte = mailclient.read();
    Serial.write(thisByte);
    }
  */
  if (respCode >= '4') {
    efail();
    return 0;
  }
  return 1;
}

void efail() {
  byte thisByte = 0;
  int loopCount = 0;
  mailclient.println(F("QUIT"));
  while (!mailclient.available()) {
    delay(1);
    loopCount++;
    // if nothing received for 10 seconds, timeout
    if (loopCount > 10000) {
      mailclient.stop();
      PgmPrintln("\r\nTimeout");
      return;
    }
  }
  while (mailclient.available()) {
    thisByte = mailclient.read();
    Serial.write(thisByte);
  }
  mailclient.stop();
  PgmPrintln("disconnected");
}

boolean sommerzeit(int s_jahr, byte s_monat, byte s_tag, byte s_stunde, byte s_zeitzone) {
  if (s_monat < 3 || s_monat > 10) return false;
  if (s_monat > 3 && s_monat < 10) return true;
  if (s_monat == 3 && (s_stunde + 24 * s_tag) >= (1 + s_zeitzone + 24 * (31 - (5 * s_jahr / 4 + 4) % 7)) || s_monat == 10 && (s_stunde + 24 * s_tag) < (1 + s_zeitzone + 24 * (31 - (5 * s_jahr / 4 + 1) % 7))) {
    return true;
  } else {
    return false;
  }
}

/*
  void error_P(const char* str) {
  PgmPrint("error: ");
  SerialPrintln_P(str);
  if (card.errorCode()) {
    PgmPrint("SD error: ");
    Serial.print(card.errorCode(), HEX);
    PgmPrintln(",");
    Serial.println(card.errorData(), HEX);
  }
  while (1);
  }
*/

boolean Generator(byte start) {
  bool running = false;
  lcd.clear();
  LCD_Head();
  // Generator Relais sind HIGH-Active!
  switch (start) {
    case 1: // OFF - Ausschalten
      if (!digitalRead(PIN_OEL)) {
        PgmPrintln("Generator luft nicht.\r\nKein Aktion durchgefhrt.");
      } else {
        digitalWrite(PIN_Z_EIN, LOW);
        PgmPrintln("Generator stoppen");
        LCD_Status(16);
        delay(genstoppdelay * 1000);
        if (digitalRead(PIN_OEL)) {
          //      if (!digitalRead(PIN_PHASE_GEN)) {
          running = true;
        } else {
          PgmPrintln("Generator gestoppt");
        }
      }
      break;

    case 2: // ON - Einschalten
      if (digitalRead(PIN_OEL)) {
        //      if (!digitalRead(PIN_PHASE_GEN)) {
        running = true;
        PgmPrintln("Generator luft bereits.\r\nKein Aktion durchgefhrt.");
      } else {
        byte i = 1;
        while (i <= starttrying && !running) {
          PgmPrintln("Generator starten");
          LCD_Status(13); //  "Vorglhen"
          PgmPrintln("Vorglhen");
          digitalWrite(PIN_Z_EIN, HIGH);
          delay(glowtime * 1000);
          digitalWrite(PIN_Z_START, HIGH);
          LCD_Status(14); //  "Zndung"
          Serial.print(i);
          PgmPrintln(". Zndung");
          // Ab der zweiten Zndung wird um 100ms lnger gestartet
          delay((ignitiontime + i - 1) * 100); 
          digitalWrite(PIN_Z_START, LOW);
          LCD_Status(15); // "Drehzal berprfen"
          delay(genstartdelay * 1000); 
          //        if (!digitalRead(PIN_PHASE_GEN)) {
          if (digitalRead(PIN_OEL)) {
            running = true;
            PgmPrintln("Generator gestartet");
          } else {
            PgmPrintln("Generator Start fehlgeschlagen");
            digitalWrite(PIN_Z_EIN, LOW);
            delay(5000);
          }
          i++;
        }
      }
      break;
  }
  return running;
}

void Print_Status(byte stat) {
  char buffer[21];
  strcpy_P(buffer, (char *)pgm_read_word(&(status_table[stat])));
  Serial.println(buffer);
}

String Web_Status(byte stat) {
  String temp_str;
  char buffer[21];
  strcpy_P(buffer, (char *)pgm_read_word(&(status_table[stat])));
  temp_str = (String) buffer;
  while (temp_str.endsWith(" ")) {
    temp_str.remove(temp_str.length() - 1);
  }
  temp_str.replace("", "&ouml;");
  temp_str.replace("", "&uuml;");
  return temp_str;
}

void LCD_Status(byte stat) {
  String temp_str;
  char buffer[21];
  int offset;
  strcpy_P(buffer, (char *)pgm_read_word(&(status_table[stat])));
  temp_str = (String) buffer;

  while (temp_str.endsWith(" ")) {
    temp_str.remove(temp_str.length() - 1);
  }

  if (timeout > 0) {
    temp_str = temp_str + " in " + String (timeout);
  }

  temp_str.replace("", "\xEF");
  temp_str.replace("", "\xF5");
  temp_str.replace("", "\xE1");
  bool vorne = false;
  while (temp_str.length() < 20) {
    if (vorne) {
      temp_str = " " + temp_str;
    } else {
      temp_str = temp_str + " ";
    }
    vorne = !vorne;
  }

  lcd.setCursor(0, 2);
  delay(2);
  //lcd.print(fillup(temp_str));
  lcd.print(temp_str);
}

void LCD_Head() {
  lcd.setCursor(0, 0);
  delay(2);
  lcd.print(fillup(lcdHead));
}

void Char2IP(char* str) {
  char *ptr;
  char delimiter[] = ".";
  ptr = strtok(str, delimiter);
  byte_array[0] = byte(atoi(ptr));
  ptr = strtok(NULL, delimiter);
  byte_array[1] = byte(atoi(ptr));
  ptr = strtok(NULL, delimiter);
  byte_array[2] = byte(atoi(ptr));
  ptr = strtok(NULL, delimiter);
  byte_array[3] = byte(atoi(ptr));
}

void printErrorMessage(uint8_t e, bool eol = true) // INI-File
{
  switch (e) {
    case IniFile::errorNoError:
      PgmPrintln("no error");
      break;
    case IniFile::errorFileNotFound:
      PgmPrintln("file not found");
      break;
    case IniFile::errorFileNotOpen:
      PgmPrintln("file not open");
      break;
    case IniFile::errorBufferTooSmall:
      PgmPrintln("buffer too small");
      break;
    case IniFile::errorSeekError:
      PgmPrintln("seek error");
      break;
    case IniFile::errorSectionNotFound:
      PgmPrintln("section not found");
      break;
    case IniFile::errorKeyNotFound:
      PgmPrintln("key not found");
      break;
    case IniFile::errorEndOfFile:
      PgmPrintln("end of file");
      break;
    case IniFile::errorUnknownError:
      PgmPrintln("unknown error");
      break;
    default:
      PgmPrintln("unknown error value");
      break;
  }
  if (eol)
    PgmPrintln("");
}

void ReadIniFile() {
  
  IniFile ini(configFile);
  if (!ini.open()) {
    PgmPrintln("Ini file ");
    Serial.print(configFile);
    PgmPrintln(" does not exist");
    // Cannot do anything else
    LCD_Status(17);

    while (1)
      ;
  }
  PgmPrintln("Ini file found");

  // Check the file is valid. This can be used to warn if any lines
  // are longer than the buffer.
  if (!ini.validate(buffer, bufferLen)) {
    PgmPrintln("ini file ");
    Serial.print(ini.getFilename());
    PgmPrint(" not valid: ");
    printErrorMessage(ini.getError());
    // Cannot do anything else
    LCD_Status(17);
    while (1)
      ;
  }

  if (ini.getValue("Generator", "lostmain", buffer, bufferLen)) {
    lostmain = int(atoi(buffer));
  }
  if (ini.getValue("Generator", "backmain", buffer, bufferLen)) {
    backmain = int(atoi(buffer));
  }
  if (ini.getValue("Generator", "glowtime", buffer, bufferLen)) {
    glowtime = int(atoi(buffer));
  }
  if (ini.getValue("Generator", "ignitiontime", buffer, bufferLen)) {
    ignitiontime = int(atoi(buffer));
  }
  if (ini.getValue("Generator", "starttrying", buffer, bufferLen)) {
    starttrying = int(atoi(buffer));
  }
  if (ini.getValue("Generator", "genstartdelay", buffer, bufferLen)) {
    genstartdelay = int(atoi(buffer));
  }
  if (ini.getValue("Generator", "genstoppdelay", buffer, bufferLen)) {
    genstoppdelay = int(atoi(buffer));
  }
  if (ini.getValue("Generator", "switchdelay", buffer, bufferLen)) {
    switchdelay = int(atoi(buffer));
  }
  if (ini.getValue("Generator", "minbatvoltage", buffer, bufferLen)) {
    minbatvoltage = float(atof(buffer));
  }

  if (ini.getValue("Network", "Host", buffer, bufferLen)) {
    strcpy(host,buffer);
  }
  if (ini.getValue("Network", "Domain", buffer, bufferLen)) {
    strcpy(domain,buffer);
  }
  if (ini.getValue("Network", "IP", buffer, bufferLen)) {
    Char2IP(buffer);
    for (int i = 0; i < 4; i++) {
      ip[i] = byte_array[i];
    }
  }
  if (ini.getValue("Network", "DNS", buffer, bufferLen)) {
    Char2IP(buffer);
    for (int i = 0; i < 4; i++) {
      dns[i] = byte_array[i];
    }
  }
  if (ini.getValue("Network", "GW", buffer, bufferLen)) {
    Char2IP(buffer);
    for (int i = 0; i < 4; i++) {
      gateway[i] = byte_array[i];
    }
  }
  if (ini.getValue("Network", "SUB", buffer, bufferLen)) {
    Char2IP(buffer);
    for (int i = 0; i < 4; i++) {
      subnet[i] = byte_array[i];
    }
  }
  if (ini.getValue("Network", "NTP", buffer, bufferLen)) {
    strcpy(ntp,buffer);
  }
  
  if (ini.getValue("Mail", "Server", buffer, bufferLen)) {
    strcpy(m_server, buffer);
  }
  if (ini.getValue("Mail", "From", buffer, bufferLen)) {
    m_from = (String) buffer;
  }
  if (ini.getValue("Mail", "To", buffer, bufferLen)) {
    m_to = (String) buffer;
  }
  if (ini.getValue("Mail", "To_RN", buffer, bufferLen)) {
    m_to_rn = (String) buffer;
  }
  if (ini.getValue("Mail", "From_RN", buffer, bufferLen)) {
    m_from_rn = (String) buffer;
  }

  if (ini.getValue("Webserver", "Mail", buffer, bufferLen)) {
    strcpy(Mail_File, buffer);
  }
  if (ini.getValue("Webserver", "ATS", buffer, bufferLen)) {
    strcpy(ATS_File, buffer);
  }
  if (ini.getValue("Webserver", "Config", buffer, bufferLen)) {
    strcpy(Config_File, buffer);
  }
  if (ini.getValue("Webserver", "CSS", buffer, bufferLen)) {
    strcpy(CSS_File, buffer);
  }

  bool found = ini.getValue("HC", "enable", buffer, bufferLen, HCenable);
  
   if (ini.getValue("HC", "Server", buffer, bufferLen)) {
    strcpy(HCServer, buffer);
  }
  if (ini.getValue("HC", "Request", buffer, bufferLen)) {
    strcpy(HCRequest, buffer);
  }
  if (ini.getValue("HC", "Start", buffer, bufferLen)) {
    HCStart = int(atoi(buffer));
  }    
  if (ini.getValue("HC", "Stop", buffer, bufferLen)) {
    HCStop = int(atoi(buffer));
  }    
  if (ini.getValue("HC", "Error", buffer, bufferLen)) {
    HCError = int(atoi(buffer));
  }    
  if (ini.getValue("HC", "Loss", buffer, bufferLen)) {
    HCLoss = int(atoi(buffer));
  }    
  if (ini.getValue("HC", "Back", buffer, bufferLen)) {
    HCBack = int(atoi(buffer));
  }    

  found = ini.getValue("VZ", "enable", buffer, bufferLen, VZenable);
  
  if (ini.getValue("VZ", "Server", buffer, bufferLen)) {
    strcpy(VZServer, buffer);
  }
  if (ini.getValue("VZ", "Request", buffer, bufferLen)) {
    strcpy(VZRequest, buffer);
  }
  if (ini.getValue("VZ", "UUID", buffer, bufferLen)) {
    strcpy(UUID, buffer);
  }

  found = ini.getValue("MQTT", "enable", buffer, bufferLen, MQTTenable);
  
  if (ini.getValue("MQTT", "Server", buffer, bufferLen)) {
    strcpy(MQTTServer, buffer);
  }
  if (ini.getValue("MQTT", "Topic", buffer, bufferLen)) {
    strcpy(MQTTTopic, buffer);
  }
  if (ini.getValue("MQTT", "Username", buffer, bufferLen)) {
    strcpy(MQTTUsername, buffer);
  }
  if (ini.getValue("MQTT", "Password", buffer, bufferLen)) {
    strcpy(MQTTPassword, buffer);
  }

}

void piep() {
  digitalWrite(PIN_BUZZER, HIGH);
  delay(100);
  digitalWrite(PIN_BUZZER, LOW);
}

String form(String str, int c) {
  while (str.length() < c) {
    str = " " + str;
  }
  return str;
}

float GetBatteryVoltage() {
  float vout = 0.0;
  float vin = 0.0;
  float R1 =  99600.0;
  // float R1 = 100000.0; // resistance of R1 (100K)
  float R2 = 10015.0;
  //  float R2 = 10000.0;  // resistance of R2 (10K)
  int value = 0;
  value = analogRead(BAT_INPUT);
  vout = (value * 5.0) / 1024.0;
  vin = vout / (R2 / (R1 + R2));
  if (vin < 0.09) {
    vin = 0.0; //statement to quash undesired reading !
  }
  return vin;
}

String ConvSeperator(String str) {
  str.replace(".", ",");
  return str;
}

void softReset() {
  asm volatile ("  jmp 0");
}

void Alert(char* server, char* request, byte message) {
  if (HCenable) {
    unsigned long byteCount = 0;
    EthernetClient client;
    
    if (client.connect(server, 80)) {
      client.print(request);
      client.print(message);
      client.println(F(" HTTP/1.1"));
      client.print(F("Host: "));
      client.println(server);
      client.println(F("Connection: close"));
      client.println();
      delay(1);
      client.stop();
    } else {
      Serial.print(F("Connection to "));
      Serial.print(server);
      Serial.println(F(" failed!"));
    }
  }
}

// call back for file timestamps
void dateTime(uint16_t* date, uint16_t* time) {
 *date = FAT_DATE(Clock.getYear()+2000, Clock.getMonth(Century), Clock.getDate());
 *time = FAT_TIME(Clock.getHour(h12, PM), Clock.getMinute(), Clock.getSecond());
}

void PushVZ(char* server, char* request, char* uuid, float wert) {
  if (VZenable) {
    EthernetClient client;
    char w;
    if (client.connect(server, 80)) {
      client.print(request);
      client.print(uuid);
      client.print(F(".json?operation=add&value="));
      client.print((int) wert);
      client.println(F(" HTTP/1.1"));
      client.print(F("Host: "));
      client.println(server);
      client.println(F("Connection: close"));
      client.println();
      delay(10);
      client.stop();
    } else {
      Serial.print(F("Connection to "));
      Serial.print(server);
      Serial.println(F(" failed!"));
    }
  }
}

// MQTT Callback
void MQTTmessageReceived(String &topic, String &payload) {
  
  Serial.println("incoming: " + topic + " - " + payload);

  // Reset
  strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[0])));
  if (topic == MQTTTopic + (String) buffer && payload == "true") {
    PgmPrintln("Anforderung per MQTT zum Reset");
    sendEmail(16);
    softReset();
  }

  // Betrieb (netz)
  strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[1])));
  if (topic == MQTTTopic + (String) buffer && payload == "netz") {
    PgmPrintln("Anforderung per NQTT zum Netzbetrieb");
    Status = 0; // "Netzbetrieb"
    Alert(HCServer, HCRequest, HCStop);
    if (!Generator(OFF)) {
      SwitchNetzbetrieb();
      sendEmail(2);
    } else {
      Status = 4; // "Strung Manuell"
      sendEmail(3);
    }
    Print_Status[Status];
  }
  
  // Betrieb (generator)
  strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[1])));
  if (topic == MQTTTopic + (String) buffer && payload == "generator") {
    PgmPrintln("Anforderung per MQTT zum Generatorbetrieb");
    Status = 2; // "Generatorbetrieb"
    Alert(HCServer, HCRequest, HCStop);
    if (!Generator(OFF)) {
      SwitchNetzbetrieb();
      sendEmail(2);
    } else {
      Status = 4; // "Strung Manuell"
      sendEmail(3);
    }
    Print_Status[Status];
  }
 
  strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[2])));
  if (topic == MQTTTopic + (String) buffer && payload == "send") {
    PgmPrintln("Anforderung per MQTT zum Status-Mail-Versand");
    sendEmail(14);
  }
  
 
  // Generator start
  strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[3])));
  if (topic == MQTTTopic + (String) buffer && payload == "start") {
    PgmPrintln("Anforderung per MQTT zum Generatorstart");
    Alert(HCServer, HCRequest, HCStart);
    if (Generator(ON)) {
      Status = 18; // "Generator luft"
      sendEmail(4);
    } else {
      Status = 4; // "Strung Manuell"
      Alert(HCServer, HCRequest, HCError);
      sendEmail(5);
    }
    Print_Status[Status];
  }
  
  // Generator stop
  strcpy_P(buffer, (char *)pgm_read_word(&(mqtt_table[3])));
  if (topic == MQTTTopic + (String) buffer && payload == "stop") {
    PgmPrintln("Anforderung per MQTT zum Generatorstopp");
    Alert(HCServer, HCRequest, HCStop);
    if (!Generator(OFF)) {
      Status = 0; // "Netzbetrieb"
      sendEmail(6);
    } else {
      Status = 4; // Strung Manuell
      sendEmail(7);
    }
    Print_Status[Status];
  }
}
ats.cssCSS
Cascading Style Sheets
body {
	background-color: #151515;
	font-family: Helvetica, Arial, Geneva, sans-serif;
	color: yellow;
	font-size: 3vmin;
}
.hl {
	font-size: 6vmin;
	font-weight: bold;
}
a {
	text-decoration: none;
}
table#a, td#a {
	height: 5vmin;	
	width: 80%;
	table-layout:fixed;
	text-align: left;
	border-collapse: collapse;
	border: 1px solid white;
	padding: 5px;
}	
table#b, td#b {
	height: 5vmin;	
	width: 80%;
	table-layout:fixed;
	text-align: center;
	border-collapse: collapse;
	border: 1px solid white;
	padding: 5px;
}	
	table#c, td#c{
	width: 80%;
	table-layout:fixed;
	text-align: center;
	border: none;
	/*border: 1px solid white;*/
	padding: 10px;
}
.e {
	font-size: 5vmin;
	font-weight: bold;
}
.n {
	font-size: 4vmin;
	color: lightyellow;
}
.v {
	font-size: 5vmin;
	color: lightyellow;
}	
.gn {
	background-color: #58FA58;
	color: black;
}
.rt {
	background-color: #FA5858;
	color: black;
}
.gr {
	/*background-color: #BDBDBD;*/
	background-color: lightyellow;
	color: black;
}
.bl {
	background-color: #5882FA;
	color: black;
}
.ge {
	background-color: #F4FA58;
	color: black;
}	
input[type="submit"], input[type="button"]{
	width: 35vmin;
	font-size: 3vmin;
	border: none;
	padding: 20px;
	text-align: center;
	display: inline-block;
}
input[type="file"]{
	width: 68vmin;
	font-size: 3vmin;
	border: none;
	padding: 20px;
	text-align: center;
	display: inline-block;
}
ats.htmHTML
Main Web Document
<html><head>
	<title>ATS</title>
	<link rel="shortcut icon" type="image/x-icon" href="http://webserver.com/favicon.ico">
	<meta name="referrer" content="no-referrer" />
	<meta http-equiv="refresh" content="10; URL=http://ats.webserver.com">
</head><body><center>
	<div class="hl">Automatic-Transfer-Switch</div>
	<div class="h2">Version %ver% (c) 2019 by J&ouml;rg Mahn</div><br>
	<table id="a">
		<tr><td id="a"><font class="e">Status:</font></td><td id="a"><font class="v">%sta%</font></td></tr>
		<tr><td id="a"><font class="e">Generator:</font></td><td id="a"><font class="v">%gen%</font></td></tr>
		<tr><td id="a"><font class="e">Zeit:</font></td><td id="a"><font class="v">%d% %z%</font></td></tr>
		<tr><td id="a"><font class="e">RAM:</font></td><td id="a"><font class="v">%ram%  Bytes frei</font></td></tr>
		<tr><td id="a"><font class="e">ATS:</font></td><td id="a"><font class="v">%sys% &deg;C</font></td></tr>
		<tr><td id="a"><font class="e">Halle:</font></td><td id="a"><font class="v">%aut% &deg;C</font></td></tr>
		<tr><td id="a"><font class="e">Motor:</font></td><td id="a"><font class="v">%mot% &deg;C</font></td></tr>
		<tr><td id="a"><font class="e">Batterie:</font></td><td id="a"><font class="v">%bat% V</font></td></tr>
		<tr><td id="a"><font class="e">Leistung:</font></td><td id="a"><font class="v">%p%</font></td></tr>
	</table><br>
	<table id="b">
		<tr><td id="b"><font class="e">L1</font></td><td id="b"><font class="e">L2</font></td><td id="b"><font class="e">L3</font></td></tr>
		<tr><td id="b"><font class="v">%1v% V</font></td><td id="b"><font class="v">%2v% V</font></td><td id="b"><font class="v">%3v% V</font></td></tr>
		<tr><td id="b"><font class="v">%1c% A</font></td><td id="b"><font class="v">%2c% A</font></td><td id="b"><font class="v">%3c% A</font></td></tr>
	</table><br>
	<form method="get" name="ats">
	<table id="c">
		<tr><td id="c"><input type="submit" class="gr" name="refresh" value="Aktualisieren"></td>
		<td id="c"><input type="submit" class="gr" name="sendmail" value="EMail senden"></td></tr>
		<tr><td id="c"><input type="submit" class="gr" name="config" value="Konfiguration"></td>
		<td id="c"><input type="submit" class="ge" name="reset" value="System Reset"></td></tr>
		<tr><td id="c"><input type="submit" class="gr" name="genstop" value="Generator stoppen"></td>
		<td id="c"><input type="submit" class="bl" name="genstart" value="Generator starten"></td></tr>
		<tr><td id="c"><input type="submit" class="gn" name="mainbet" value="Netzbetrieb"></td>
		<td id="c"><input type="submit" class="rt" name="genbet" value="Generatorbetrieb"></td></tr>
	</table>
	</form>
</center></body></html>
config.htmHTML
Config Web Document
<html><head>
<title>ATS-Konfiguration</title>
<link rel="shortcut icon" type="image/x-icon" href="http://webserver.com/favicon.ico">
<meta name="referrer" content="no-referrer" />
</head><body><center>
	<div class="hl">Automatic-Transfer-Switch</div>
	<div class="h2">Version %ver% (c) 2019 by J&ouml;rg Mahn</div>
	<br><div class="h2">Generator</div>
	<table id="a">
		<tr><td id="a"><font class="n">Umschaltzeit nach Stromausfall</font></td><td id="a"><font class="n">%lm% Min.</font></td></tr>
		<tr><td id="a"><font class="n">Umschaltzeit nach Stromwiederkehr</font></td><td id="a"><font class="n">%bm% Min.</font></td></tr>
		<tr><td id="a"><font class="n">Vorgl&uuml;hdauer</font></td><td id="a"><font class="n">%gt% Sek.</font></td></tr>
		<tr><td id="a"><font class="n">Startdauer</font></td><td id="a"><font class="n">%it% Millisek.</font></td></tr>
		<tr><td id="a"><font class="n">Startversuche</font></td><td id="a"><font class="n">%st% Mal</font></td></tr>
		<tr><td id="a"><font class="n">Wartezeit nach Start</font></td><td id="a"><font class="n">%startd% Sek.</font></td></tr>
		<tr><td id="a"><font class="n">Wartezeit nach Stop</font></td><td id="a"><font class="n">%stopd% Sek.</font></td></tr>
		<tr><td id="a"><font class="n">Umschaltdauer</font></td><td id="a"><font class="n">%sd% Sek.</font></td></tr>
		<tr><td id="a"><font class="n">Warnschwelle Starterbatterie</font></td><td id="a"><font class="n">%mv% Volt</font></td></tr>
	</table>
	<br><div class="h2">SD-Card</div>
	<table id="a">
		<tr><td id="a"><font class="n">Name</font></td>
		<td id="a"><font class="n">Gr&ouml;&szlig;e</font></td>
		<td id="a"><font class="n">l&ouml;schen</font></td></tr>
		<sdcontent>Hier wird der Inhalt der SD-Card eingefuegt</sdcontent>
	</table>
	<br><div class="h2">Datei Speichern</div>
	<form method="post" enctype="multipart/form-data">
	<table id="c">
		<tr><td id="c" colspan="2"><input class="gr" type="file" name="datei" accept="text/*"></td></tr>
		<tr><td id="c"><input class="gr" type="submit" value="Speichern"></td>
		<td id="c"><input class="gr" type="button" value="Aktualisieren" onclick="window.location.href='http://ats.joergmahn.de/?config'" /></td></tr>
		<tr><td id="c"><input class="gr" type="button" value="Zur&uuml;ck" onclick="window.location.href='http://ats.joergmahn.de'" /></td>
		<td id="c"><input class="gr" type="button" value="Neu Starten" onclick="window.location.href='http://ats.joergmahn.de/?reset'" /></td></tr>
	</table>
	</form>
</center></body></html>
mail.htmHTML
Mail Web Document
<!DOCTYPE html>
<html>
<head>
  <title>ATS</title>
</head>
<body>
	<font size="5"><b>Automatic-Transfer-Switch</b></font><br>
	<font>(c) 2019 by J&ouml;rg Mahn</font><br><br>
	<table>
		<tr><td><b>Version:</b></td><td><a href="ats.webserver.com">%ver%</a></td></tr>
		<tr><td><b>Status:</b></td><td>%sta%</td></tr>
		<tr><td><b>Generator:</b></td><td>%gen%</td></tr>
		<tr><td><b>RAM:</b></td><td>%ram% Bytes frei</td></tr>
		<tr><td><b>Zeit:</b></td><td>%d% %z%</td></tr>
		<tr><td><b>ATS:</b></td><td>%sys% &deg;C</td></tr>
		<tr><td><b>Motor:</b></td><td>%mot% &deg;C</td></tr>
		<tr><td><b>Halle:</b></td><td>%aut% &deg;C</td></tr>
		<tr><td><b>Batterie:</b></td><td>%bat% V</td></tr>
		<tr><td><b>Leistung:</b></td><td>%p%</td></tr>
	</table>
	<br>
	<table>
		<tr><td><b>L1</b></td><td><b>L2</b></td><td><b>L3</b></td><td></tr>
		<tr><td>%1v% V</td><td>%2v% V</td><td>%3v% V</td><td></tr>
		<tr><td>%1c% A</td><td>%2c% A</td><td>%3c% A</td><td></tr>
	</table>
</body>
</html>
config.iniINI
INI-File
;	INI-Datei fuer den ATS (c) 2019 by Joerg Mahn
;
;	Wegen der Puffer-Einstellung, darf die maximale Zeilenlaenge 80 Zeichen
;	nicht ueberschreiten!
;

[Generator]
; Zeit in Minuten bis Stromausfall akzeptiert wird
lostmain = 5

; Zeit in Minuten bis nach Strom-Wiederkehr auf
; Normalbetrieb zurueck geschaltet wird
backmain = 2

; Vorglueh-Zeit in Sekunden (15)
glowtime = 15

; Startdauer (x 100ms) (8)
ignitiontime = 8

; Startversuche (3)
starttrying = 3

; Wartezeit nach dem Start in Sekunden 
genstartdelay = 8

; Wartezeit nach dem Stopp in Sekunden 
genstoppdelay = 5

; Dauer zwischen den Umschaltungen in Sekunden (2)
switchdelay = 2

; Minimale Batteriespannung bevor Warnung (20.0)
minbatvoltage = 20.0

[Network]
Host = ats
Domain = webserver.com
IP = 192.168.100.29
DNS = 192.168.100.1
GW = 192.168.100.251
SUB = 255.255.255.0
; ntp = Zeitserver, IP oder Hostname
NTP = ptbtime1.ptb.de

[Mail]
Server = mail.webserver.com
To = me@webserver.com
To_RN = Joerg Mahn
From = ats@webserver.com
From_RN = ATS

[Webserver]
; Webfrontend Vorlagen
Mail = mail.htm
ATS = ats.htm
Config = config.htm
CSS =  ats.css

[HC]
; Homecontrol
; Texte werden in der settings.ini (Haussteuerung - Sound) festgelegt.
enable = true
Server = home.webserver.com
Request = GET /sound/index.php?
Start = 7
Stop = 8
Error = 9
Loss = 10
Back = 11

[VZ]
; Volkazaehler
; Werte werden an die angegebene UUID gepusht.
enable = true
Server = vz.webserver.com
Request = GET /middleware/data/
UUID = f4fd4310-e4ee-11e9-9c45-a9ae0b49728e

[MQTT]
; Zugangsdaten zum Broker und Topic-Settings
; <Topic>/set zum Konfigurieren
; <Topic>/get zum Abfragen
enable = true
Server = mqtt.webserver.com
Topic = home/ats
Username = user
Password = *******

Schematics

Wirering Diagramm
2019 09 16 17 18 45 clipboard ncs4h1utsm

Comments

Similar projects you might like

Arduino Controlled Smart Hydroponic Modular System

Project in progress by Luis Antonio Martin Nuez

  • 19,579 views
  • 3 comments
  • 97 respects

Automatic Indoor Vegetable Garden

Project in progress by londonium2021

  • 15,567 views
  • 21 comments
  • 86 respects

Automatic Door and Counter Prototype

Project tutorial by Francisc Camillo

  • 11,689 views
  • 1 comment
  • 21 respects

Control Home Appliances Through Web Or Mobile

Project tutorial by Team Gadget Programmers

  • 9,989 views
  • 4 comments
  • 22 respects

Arduino - Send Temperature to Web via Serial

Project tutorial by IoT_hobbyist

  • 6,804 views
  • 7 comments
  • 32 respects

Twilight Switch With Levels

Project in progress by Rafa Salvador

  • 4,200 views
  • 1 comment
  • 12 respects
Add projectSign up / Login