Atem tally – kod źródłowy

/*
    Copyright (C) 2020 Aron N. Het Lam, aronhetlam@gmail.com

    This program makes an ESP8266 into a wireless tally light system for ATEM switchers,
    by using Kasper Skårhøj's (<https://skaarhoj.com>) ATEM clinet libraries for Arduino.

    Polish translation and modifications: Waldorf-Studio 2021 (https://waldorf-studio.pl)

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

//Include libraries:
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <EEPROM.h>
#include <ATEMmin.h>
#include <TallyServer.h>

//============== WERSJA STANDARD =============================================
//Define LED1 color pins
#define PIN_RED1    D1
#define PIN_GREEN1  D2
#define PIN_BLUE1   D4

//Define LED2 color pins
#define PIN_RED2    D0
#define PIN_GREEN2  D5
#define PIN_BLUE2   D6

//Define LED colors
#define LED_OFF     0
#define LED_RED     1
#define LED_GREEN   2
#define LED_BLUE    3
#define LED_YELLOW  4
#define LED_PINK    5
#define LED_WHITE   6

//Define states
#define STATE_STARTING                  0
#define STATE_CONNECTING_TO_WIFI        1
#define STATE_CONNECTING_TO_SWITCHER    2
#define STATE_RUNNING                   3

//Define modes of operation
#define MODE_NORMAL                     1
#define MODE_PREVIEW_STAY_ON            2
#define MODE_PROGRAM_ONLY               3

//Initialize global variables
ESP8266WebServer server(80);

ATEMmin atemSwitcher;

TallyServer tallyServer;

uint8_t state = STATE_STARTING;

//Define sturct for holding tally settings (mostly to simplify EEPROM read and write, in order to persist settings)
struct Settings {
    char tallyName[32] = "";
    uint8_t tallyNo;
    uint8_t tallyModeLED1;
    uint8_t tallyModeLED2;
    bool staticIP;
    IPAddress tallyIP;
    IPAddress tallySubnetMask;
    IPAddress tallyGateway;
    IPAddress switcherIP;
};

Settings settings;

bool firstRun = true;

//Perform initial setup on power on
void setup() {
    //Init pins for LED
    pinMode(PIN_RED1, OUTPUT);
    pinMode(PIN_GREEN1, OUTPUT);
    pinMode(PIN_BLUE1, OUTPUT);

    pinMode(PIN_RED2, OUTPUT);
    pinMode(PIN_GREEN2, OUTPUT);
    pinMode(PIN_BLUE2, OUTPUT);

    setBothLEDs(LED_BLUE);

    //Start Serial
    Serial.begin(115200);
    Serial.println("########################");
    Serial.println("Serial started");

    //save flash memory from being written too without need.
    WiFi.persistent(false);

    //Read settings from EEPROM. Settings struct takes 68 bytes total (according to sizeof()). WIFI settings are stored seperately by the ESP
    EEPROM.begin(68); //Needed on ESP8266 module, as EEPROM lib works a bit differently than on a regular arduino
    EEPROM.get(0, settings);

    Serial.println(settings.tallyName);
    //Serial.println(sizeof(settings)); //Check size of settings struct
    if (settings.staticIP) {
        WiFi.config(settings.tallyIP, settings.tallyGateway, settings.tallySubnetMask);
    }

    //Put WiFi into station mode and make it connect to saved network
    WiFi.mode(WIFI_STA);
    WiFi.begin();
    WiFi.hostname(settings.tallyName);
    WiFi.setAutoReconnect(true);

    Serial.println("------------------------");
    Serial.println("Connecting to WiFi...");
    Serial.println("Network name (SSID): " + WiFi.SSID());

    // Initialize and begin HTTP server for handeling the web interface
    server.on("/", handleRoot);
    server.on("/save", handleSave);
    server.onNotFound(handleNotFound);
    server.begin();

    tallyServer.begin();

    //Wait for result from first attempt to connect - This makes sure it only activates the softAP if it was unable to connect,
    //and not just because it hasn't had the time to do so yet. It's blocking, so don't use it inside loop()
    WiFi.waitForConnectResult();

    //Set state to connecting before entering loop
    changeState(STATE_CONNECTING_TO_WIFI);
}

void loop() {
    switch (state) {
        case STATE_CONNECTING_TO_WIFI: {
                if (WiFi.status() == WL_CONNECTED) {
                    WiFi.mode(WIFI_STA); // Disable softAP if connection is successful
                    Serial.println("------------------------");
                    Serial.println("Connected to WiFi:   " + WiFi.SSID());
                    Serial.println("IP:                  " + WiFi.localIP().toString());
                    Serial.println("Subnet Mask:         " + WiFi.subnetMask().toString());
                    Serial.println("Gateway IP:          " + WiFi.gatewayIP().toString());
                    changeState(STATE_CONNECTING_TO_SWITCHER);
                } else if (firstRun) {
                    firstRun = false;
                    WiFi.mode(WIFI_AP_STA); // Enable softAP to access web interface in case of no WiFi
                    WiFi.softAP("Tally Light AP");
                    if (WiFi.SSID().length() < 1) {
                      setBothLEDs(LED_WHITE);
                      delay(500);
                      setBothLEDs(LED_RED);
                      delay(500);
                      setBothLEDs(LED_GREEN);
                      delay(500);
                      setBothLEDs(LED_BLUE);
                      delay(500);
                      setBothLEDs(LED_PINK);
                      delay(500);
                      setBothLEDs(LED_YELLOW);
                    } else {
                      setBothLEDs(LED_WHITE);
                    }
                }
            }
            break;

        case STATE_CONNECTING_TO_SWITCHER:
            // Initialize a connection to the switcher:
            if (firstRun) {
                atemSwitcher.begin(settings.switcherIP);
                //atemSwitcher.serialOutput(0x80); //Makes Atem library print debug info
                Serial.println("------------------------");
                Serial.println("Connecting to switcher...");
                Serial.println((String)"Switcher IP:         " + settings.switcherIP[0] + "." + settings.switcherIP[1] + "." + settings.switcherIP[2] + "." + settings.switcherIP[3]);
                firstRun = false;
            }
            atemSwitcher.runLoop();
            if (atemSwitcher.hasInitialized()) {
                changeState(STATE_RUNNING);
                Serial.println("Connected to switcher");
            }
            break;

        case STATE_RUNNING:
            //Handle data exchange and connection to swithcher
            atemSwitcher.runLoop();

            int tallySources = atemSwitcher.getTallyByIndexSources();
            tallyServer.setTallySources(tallySources);
            for (int i = 0; i < tallySources; i++) {
                tallyServer.setTallyFlag(i, atemSwitcher.getTallyByIndexTallyFlags(i));
            }

            //Handle Tally Server
            tallyServer.runLoop();

            //Set tally light accordingly
            if (atemSwitcher.getTallyByIndexTallyFlags(settings.tallyNo) & 0x01) {              //if tally live
                setLED1(LED_RED);
            } else if ((!(settings.tallyModeLED1 == MODE_PROGRAM_ONLY))                             //if not program only
                       && ((atemSwitcher.getTallyByIndexTallyFlags(settings.tallyNo) & 0x02)    //and tally preview
                           || settings.tallyModeLED1 == MODE_PREVIEW_STAY_ON)) {                    //or preview stay on
                setLED1(LED_GREEN);
            } else {                                                                            // If tally is neither
                setLED1(LED_OFF);
            }

            //Set tally light LED 2 accordingly
            if (atemSwitcher.getTallyByIndexTallyFlags(settings.tallyNo) & 0x01) {              //if tally live
                setLED2(LED_RED);
            } else if ((!(settings.tallyModeLED2 == MODE_PROGRAM_ONLY))                             //if not program only
                       && ((atemSwitcher.getTallyByIndexTallyFlags(settings.tallyNo) & 0x02)    //and tally preview
                           || settings.tallyModeLED2 == MODE_PREVIEW_STAY_ON)) {                    //or preview stay on
                setLED2(LED_GREEN);
            } else {                                                                            // If tally is neither
                setLED2(LED_OFF);
            }

            //Switch state if connection is lost, dependant on which connection is lost.
            if (WiFi.status() != WL_CONNECTED) {
                Serial.println("------------------------");
                Serial.println("WiFi connection lost...");
                changeState(STATE_CONNECTING_TO_WIFI);

                //Force atem library to reset connection, in order for status to read correctly on website.
                atemSwitcher.begin(settings.switcherIP);

                //Reset tally server's tally flags, They won't get the message, but it'll be reset for when the connectoin is back.
                tallyServer.resetTallyFlags();

            } else if (!atemSwitcher.hasInitialized()) { // will return false if the connection was lost
                Serial.println("------------------------");
                Serial.println("Connection to Switcher lost...");
                changeState(STATE_CONNECTING_TO_SWITCHER);

                //Reset tally server's tally flags, so clients turn off their lights.
                tallyServer.resetTallyFlags();
            }
            break;
    }

    //Handle web interface
    server.handleClient();
}

//Handle the change of states in the program
void changeState(uint8_t stateToChangeTo) {
    firstRun = true;
    switch (stateToChangeTo) {
        case STATE_CONNECTING_TO_WIFI:
            state = STATE_CONNECTING_TO_WIFI;
            setBothLEDs(LED_BLUE);
            break;
        case STATE_CONNECTING_TO_SWITCHER:
            state = STATE_CONNECTING_TO_SWITCHER;
            setBothLEDs(LED_PINK);
            break;
        case STATE_RUNNING:
            state = STATE_RUNNING;
            setBothLEDs(LED_GREEN);
            break;
    }
}

//Set the color of both LEDs
void setBothLEDs(uint8_t color) {
    setLED(color, PIN_RED1, PIN_GREEN1, PIN_BLUE1);
    setLED(color, PIN_RED2, PIN_GREEN2, PIN_BLUE2);
}

//Set the color of the 1st LED
void setLED1(uint8_t color) {
    setLED(color, PIN_RED1, PIN_GREEN1, PIN_BLUE1);
}

//Set the color of the 2nd LED
void setLED2(uint8_t color) {
    setLED(color, PIN_RED2, PIN_GREEN2, PIN_BLUE2);
}

//Set the color of a LED using the given pins
void setLED(uint8_t color, int pinRed, int pinGreen, int pinBlue) {
    switch (color) {
        case LED_OFF:
            digitalWrite(pinRed, 0);
            digitalWrite(pinGreen, 0);
            digitalWrite(pinBlue, 0);
            break;
        case LED_RED:
            digitalWrite(pinRed, 1);
            digitalWrite(pinGreen, 0);
            digitalWrite(pinBlue, 0);
            break;
        case LED_GREEN:
            digitalWrite(pinRed, 0);
            digitalWrite(pinGreen, 1);
            digitalWrite(pinBlue, 0);
            break;
        case LED_BLUE:
            digitalWrite(pinRed, 0);
            digitalWrite(pinGreen, 0);
            digitalWrite(pinBlue, 1);
            break;
        case LED_YELLOW:
            digitalWrite(pinRed, 1);
            digitalWrite(pinGreen, 1);
            digitalWrite(pinBlue, 0);
            break;
        case LED_PINK:
            digitalWrite(pinRed, 1);
            digitalWrite(pinGreen, 0);
            analogWrite(pinBlue, 0xff);
            break;
        case LED_WHITE:
            digitalWrite(pinRed, 1);
            digitalWrite(pinGreen, 1);
            digitalWrite(pinBlue, 1);
            break;
    }
}

//Serve setup web page to client, by sending HTML with the correct variables
void handleRoot() {
    String html = "<!DOCTYPE html> <html> <head> <meta charset=\"UTF-8\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"> <title>ATEM Tally ustawienia</title> </head> <script> function switchIpField(e) { console.log(\"switch\"); console.log(e); var target = e.srcElement || e.target; var maxLength = parseInt(target.attributes[\"maxlength\"].value, 10); var myLength = target.value.length; if (myLength >= maxLength) { var next = target.nextElementSibling; if (next != null) { if (next.className.includes(\"IP\")) { next.focus(); } } } else if (myLength == 0) { var previous = target.previousElementSibling; if (previous != null) { if (previous.className.includes(\"IP\")) { previous.focus(); } } } } function ipFieldFocus(e) { console.log(\"focus\"); console.log(e); var target = e.srcElement || e.target; target.select(); } function load() { var containers = document.getElementsByClassName(\"IP\"); for (var i = 0; i < containers.length; i++) { var container = containers[i]; container.oninput = switchIpField; container.onfocus = ipFieldFocus; } containers = document.getElementsByClassName(\"tIP\"); for (var i = 0; i < containers.length; i++) { var container = containers[i]; container.oninput = switchIpField; container.onfocus = ipFieldFocus; } toggleStaticIPFields(); } function toggleStaticIPFields() { var enabled = document.getElementById(\"staticIP\").checked; document.getElementById(\"staticIPHidden\").disabled = enabled; var staticIpFields = document.getElementsByClassName('tIP'); for (var i = 0; i < staticIpFields.length; i++) { staticIpFields[i].disabled = !enabled; } } </script> <body style=\"font-family:Verdana; white-space:nowrap;\" onload=\"load()\"> <table cellpadding=\"2\" style=\"width:100%\"> <tr bgcolor=\"#0066CC\" style=\"color:#ffffff;font-size:12px;\"> <td colspan=\"3\"> <h1>&nbsp;<a style='color:#ffffff;text-decoration:none'; href='https://waldorf-studio.pl' target='_blank' rel='noopener'>WALDORF STUDIO</a> - ATEM Tally V3</h1> <hr> <h2>&nbsp;Status urządzenia:</h2> </td> </tr> <tr> <td><br></td> <td></td> <td style=\"width: 100%;\"></td> </tr> <tr> <td>Status połączenia:</td> <td colspan=\"2\">";
    switch (WiFi.status()) {
        case WL_CONNECTED:
            html += "Podłączony do sieci";
            break;
        case WL_NO_SSID_AVAIL:
            html += "Sieć nie znaleziona";
            break;
        case WL_CONNECT_FAILED:
            html += "Niewłaściwe hasło";
            break;
        case WL_IDLE_STATUS:
            html += "Sprawdzam stan...";
            break;
        case WL_DISCONNECTED:
            html += "Praca w trybie softAP";
            break;
        case -1:
            html += "Przekroczono czas";
            break;
    }

    html += "</td> </tr> <tr> <td>Nazwa sieci (SSID):</td> <td colspan=\"2\">";
    html += WiFi.SSID();
    html += "</td> </tr> <tr> <td><br></td> </tr> <tr> <td>Siła sygnału:</td> <td colspan=\"2\">";
    html += WiFi.RSSI();
    html += " dBm</td> </tr> <tr> <td>Statyczne IP:</td> <td colspan=\"2\">";
    html += settings.staticIP == true ? "Tak" : "Nie";
    html += "</td> </tr> <tr> <td>ATEM Tally - IP:</td> <td colspan=\"2\">";
    html += WiFi.localIP().toString();
    html += "</td> </tr> <tr> <td>Maska podsieci: </td> <td colspan=\"2\">";
    html += WiFi.subnetMask().toString();
    html += "</td> </tr> <tr> <td>Brama: </td> <td colspan=\"2\">";
    html += WiFi.gatewayIP().toString();
    html += "</td> </tr> <tr> <td><br></td> </tr> <tr> <td>ATEM - stan:</td> <td colspan=\"2\">";
    if (atemSwitcher.hasInitialized())
        html += "Połączono";
    else if (atemSwitcher.isConnected())
        html += "Połączono - Czekam na inicjalizację - połączenie mogło zostać odrzucone";
    else if (WiFi.status() == WL_CONNECTED)
        html += "Brak połączenia - ATEM nie odpowiada";
    else
        html += "Odłączono - Czekam na WiFi";
    html += "</td> </tr> <tr> <td>ATEM - IP:</td> <td colspan=\"2\">";
    html += (String)settings.switcherIP[0] + '.' + settings.switcherIP[1] + '.' + settings.switcherIP[2] + '.' + settings.switcherIP[3];
    html += "</td> </tr> <tr> <td><br></td> </tr> <tr bgcolor=\"#0066CC\" style=\"color:#ffffff;font-size:12px;\"> <td colspan=\"3\"> <h1>&nbsp;Ustawienia ogólne:<hr></h1> </td> </tr> <tr> <td><br></td> </tr> <form action=\"/save\" method=\"post\"> <tr> <td>ATEM Tally - nazwa: </td> <td> <input type=\"text\" size=\"30\" maxlength=\"30\" name=\"tName\" value=\"";
    html += WiFi.hostname();
    html += "\" required /> </td> </tr> <tr> <td><br></td> </tr> <tr> <td>ATEM Tally - numer: </td> <td> <input type=\"number\" size=\"5\" min=\"1\" max=\"41\" name=\"tNo\" value=\"";
    html += (settings.tallyNo + 1);
    html += "\" required /> </td> </tr> <tr> <td>Przednia Lampa Tally - tryb (LED 1):&nbsp;</td> <td> <select name=\"tModeLED1\"> <option value=\"";
    html += (String) MODE_NORMAL + "\" ";
    if (settings.tallyModeLED1 == MODE_NORMAL)
        html += "selected";
    html += ">Normalny</option> <option value=\"";
    html += (String) MODE_PREVIEW_STAY_ON + "\" ";
    if (settings.tallyModeLED1 == MODE_PREVIEW_STAY_ON)
        html += "selected";
    html += ">Zostaw włączony podgląd</option> <option value=\"";
    html += (String) MODE_PROGRAM_ONLY + "\" ";
    if (settings.tallyModeLED1 == MODE_PROGRAM_ONLY)
        html += "selected";
    html += ">Tylko program</option> </select> </td> </tr> <tr> <td>Tylna Lampa Tally - tryb (LED 2):</td> <td> <select name=\"tModeLED2\"> <option value=\"";
    html += (String) MODE_NORMAL + "\" ";
    if (settings.tallyModeLED2 == MODE_NORMAL)
        html += "selected";
    html += ">Normalny</option> <option value=\"";
    html += (String) MODE_PREVIEW_STAY_ON + "\" ";
    if (settings.tallyModeLED2 == MODE_PREVIEW_STAY_ON)
        html += "selected";
    html += ">Zostaw włączony podgląd</option> <option value=\"";
    html += (String) MODE_PROGRAM_ONLY + "\" ";
    if (settings.tallyModeLED2 == MODE_PROGRAM_ONLY)
        html += "selected";
    html += ">Tylko Program</option> </select> </td> </tr> <tr> <td><br></td> </tr> <tr> <td>Nazwa sieci (SSID): </td> <td> <input type=\"text\" size=\"30\" maxlength=\"30\" name=\"ssid\" value=\"";
    html += WiFi.SSID();
    html += "\" required /> </td> </tr> <tr> <td>Hasło sieci WiFi: </td> <td> <input type=\"password\" size=\"30\" maxlength=\"30\" name=\"pwd\" pattern=\"^$|.{8,32}\" value=\"";
    if (WiFi.isConnected()) //As a minimum security meassure, to only send the wifi password if it's currently connected to the given network.
        html += WiFi.psk();
    html += "\" /> </td> </tr> <tr> <td><br></td> </tr> <tr bgcolor=\"#0066CC\" style=\"color:#ffffff;font-size:12px;\"> <td colspan=\"3\"> <h1>&nbsp;Ustawienia sieci:<hr></h1> <h3>&nbsp;UWAGA! Wprowadź uważnie parametry IP.</h3> <h3>&nbsp;Gdy pozostawiamy statyczne IP nie wolno zapisać domyślnych ustawień 255.255.255.255</h3> <h3>&nbsp;W przypadku podania niewłaściwych parametrów i utracenia połączenia z ATEM Tally</h3> <h3>&nbsp;wprowadź go w stan softAP poprzez wyjście z zasięgu ustawionej sieci WiFi - patrz instrukcja</h3><hr> </td> </tr> <tr> <td><br></td> </tr> <tr> <td>Użyj statycznego IP: </td> <td> <input type=\"hidden\" id=\"staticIPHidden\" name=\"staticIP\" value=\"false\" /> <input id=\"staticIP\" type=\"checkbox\" name=\"staticIP\" value=\"true\" onchange=\"toggleStaticIPFields()\" ";
    if (settings.staticIP)
        html += "checked";
    html += "/> </td> </tr> <tr> <td>ATEM Tally - IP: </td> <td> <input class=\"tIP\" type=\"text\" size=\"3\" maxlength=\"3\" name=\"tIP1\" pattern=\"\\d{0,3}\" value=\"";
    html += settings.tallyIP[0];
    html += "\" required />. <input class=\"tIP\" type=\"text\" size=\"3\" maxlength=\"3\" name=\"tIP2\" pattern=\"\\d{0,3}\" value=\"";
    html += settings.tallyIP[1];
    html += "\" required />. <input class=\"tIP\" type=\"text\" size=\"3\" maxlength=\"3\" name=\"tIP3\" pattern=\"\\d{0,3}\" value=\"";
    html += settings.tallyIP[2];
    html += "\" required />. <input class=\"tIP\" type=\"text\" size=\"3\" maxlength=\"3\" name=\"tIP4\" pattern=\"\\d{0,3}\" value=\"";
    html += settings.tallyIP[3];
    html += "\" required /> </td> </tr> <tr> <td>Maska podsieci: </td> <td> <input class=\"tIP\" type=\"text\" size=\"3\" maxlength=\"3\" name=\"mask1\" pattern=\"\\d{0,3}\" value=\"";
    html += settings.tallySubnetMask[0];
    html += "\" required />. <input class=\"tIP\" type=\"text\" size=\"3\" maxlength=\"3\" name=\"mask2\" pattern=\"\\d{0,3}\" value=\"";
    html += settings.tallySubnetMask[1];
    html += "\" required />. <input class=\"tIP\" type=\"text\" size=\"3\" maxlength=\"3\" name=\"mask3\" pattern=\"\\d{0,3}\" value=\"";
    html += settings.tallySubnetMask[2];
    html += "\" required />. <input class=\"tIP\" type=\"text\" size=\"3\" maxlength=\"3\" name=\"mask4\" pattern=\"\\d{0,3}\" value=\"";
    html += settings.tallySubnetMask[3];
    html += "\" required /> </td> </tr> <tr> <td>Brama: </td> <td> <input class=\"tIP\" type=\"text\" size=\"3\" maxlength=\"3\" name=\"gate1\" pattern=\"\\d{0,3}\" value=\"";
    html += settings.tallyGateway[0];
    html += "\" required />. <input class=\"tIP\" type=\"text\" size=\"3\" maxlength=\"3\" name=\"gate2\" pattern=\"\\d{0,3}\" value=\"";
    html += settings.tallyGateway[1];
    html += "\" required />. <input class=\"tIP\" type=\"text\" size=\"3\" maxlength=\"3\" name=\"gate3\" pattern=\"\\d{0,3}\" value=\"";
    html += settings.tallyGateway[2];
    html += "\" required />. <input class=\"tIP\" type=\"text\" size=\"3\" maxlength=\"3\" name=\"gate4\" pattern=\"\\d{0,3}\" value=\"";
    html += settings.tallyGateway[3];
    html += "\" required /> </td> </tr> <tr> <td><br></td> </tr> <tr> <td>ATEM - IP: </td> <td> <input class=\"IP\" type=\"text\" size=\"3\" maxlength=\"3\" name=\"aIP1\" pattern=\"\\d{0,3}\" value=\"";
    html += settings.switcherIP[0];
    html += "\" required />. <input class=\"IP\" type=\"text\" size=\"3\" maxlength=\"3\" name=\"aIP2\" pattern=\"\\d{0,3}\" value=\"";
    html += settings.switcherIP[1];
    html += "\" required />. <input class=\"IP\" type=\"text\" size=\"3\" maxlength=\"3\" name=\"aIP3\" pattern=\"\\d{0,3}\" value=\"";
    html += settings.switcherIP[2];
    html += "\" required />. <input class=\"IP\" type=\"text\" size=\"3\" maxlength=\"3\" name=\"aIP4\" pattern=\"\\d{0,3}\" value=\"";
    html += settings.switcherIP[3];
    html += "\" required /> </tr> <tr> <td><br></td> </tr> <tr> <td /> <td style=\"float: right;\"> <input type=\"submit\" value=\"Zapisz zmiany\" /> </td> </tr> </form> </table> </body> </html>";

    server.send(200, "text/html", html);
}

//Save new settings from client in EEPROM and restart the ESP8266 module
void handleSave() {
    if (server.method() != HTTP_POST) {
        server.send(405, "text/html", "<!DOCTYPE html> <html> <head> <meta charset=\"UTF-8\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"> <title>ATEM Tally ustawienia</title> </head> <body style=\"font-family:Verdana;\"> <table bgcolor=\"#0066CC\" border=\"0\" width=\"100%\" cellpadding=\"1\" style=\"color:#ffffff;font-size:12px;\"> <tr> <td> <h1>&nbsp;ATEM Tally ustawienia</h1> </td> </tr> </table><br>Request without posting settings not allowed</body></html>");
    } else {
        String ssid;
        String pwd;
        bool change = false;
        for (uint8_t i = 0; i < server.args(); i++) {
            change = true;
            String var = server.argName(i);
            String val = server.arg(i);

            if (var ==  "tName") {
                val.toCharArray(settings.tallyName, (uint8_t)32);
            } else if (var ==  "tModeLED1") {
                settings.tallyModeLED1 = val.toInt();
            } else if (var ==  "tModeLED2") {
                settings.tallyModeLED2 = val.toInt();
            } else if (var ==  "tNo") {
                settings.tallyNo = val.toInt() - 1;
            } else if (var ==  "ssid") {
                ssid = String(val);
            } else if (var ==  "pwd") {
                pwd = String(val);
            } else if (var ==  "staticIP") {
                settings.staticIP = (val == "true");
            } else if (var ==  "tIP1") {
                settings.tallyIP[0] = val.toInt();
            } else if (var ==  "tIP2") {
                settings.tallyIP[1] = val.toInt();
            } else if (var ==  "tIP3") {
                settings.tallyIP[2] = val.toInt();
            } else if (var ==  "tIP4") {
                settings.tallyIP[3] = val.toInt();
            } else if (var ==  "mask1") {
                settings.tallySubnetMask[0] = val.toInt();
            } else if (var ==  "mask2") {
                settings.tallySubnetMask[1] = val.toInt();
            } else if (var ==  "mask3") {
                settings.tallySubnetMask[2] = val.toInt();
            } else if (var ==  "mask4") {
                settings.tallySubnetMask[3] = val.toInt();
            } else if (var ==  "gate1") {
                settings.tallyGateway[0] = val.toInt();
            } else if (var ==  "gate2") {
                settings.tallyGateway[1] = val.toInt();
            } else if (var ==  "gate3") {
                settings.tallyGateway[2] = val.toInt();
            } else if (var ==  "gate4") {
                settings.tallyGateway[3] = val.toInt();
            } else if (var ==  "aIP1") {
                settings.switcherIP[0] = val.toInt();
            } else if (var ==  "aIP2") {
                settings.switcherIP[1] = val.toInt();
            } else if (var ==  "aIP3") {
                settings.switcherIP[2] = val.toInt();
            } else if (var ==  "aIP4") {
                settings.switcherIP[3] = val.toInt();
            }
        }

        if (change) {
            EEPROM.put(0, settings);
            EEPROM.commit();

            server.send(200, "text/html", (String)"<!DOCTYPE html> <html> <head> <meta charset=\"ASCII\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"> <title>ATEM Tally ustawienia</title> </head> <body> <table bgcolor=\"#0066CC\" border=\"0\" width=\"100%\" cellpadding=\"1\" style=\"font-family:Verdana;color:#ffffff;font-size:12px;\"> <tr> <td> <h1>&nbsp;ATEM Tally ustawienia</h1> </td> </tr> </table><br>Ustawienia zapisane poprawnie.</body></html>");

            //Delay to let data be saved, and the responce to be sent properly to the client
            delay(5000);

            if (ssid && pwd && (ssid != WiFi.SSID() || pwd != WiFi.psk())) {
                WiFi.persistent(true);
                WiFi.begin(ssid, pwd);
                WiFi.persistent(false);
            }

            ESP.restart();
        }
    }
}

//Send 404 to client in case of invalid webpage being requested.
void handleNotFound() {
    server.send(404, "text/html", "<!DOCTYPE html> <html> <head> <meta charset=\"ASCII\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"> <title>ATEM Tally ustawienia</title> </head> <body style=\"font-family:Verdana;\"> <table bgcolor=\"#0066CC\" border=\"0\" width=\"100%\" cellpadding=\"1\" style=\"color:#ffffff;font-size:12px;\"> <tr> <td> <h1>&nbsp ATEM Tally ustawienia</h1> </td> </tr> </table><br>404 - Page not found</body></html>");
}