Calculate on time for all active inputs

I have an esp32 code that check 4 input buttons and calculate on time for the first active button
I need to modify the code so that we can activate any buttons together at the same time and calculate time for any active buttons ( 1, 2, 3 even 4 are on same time )
please help

/*
  https://forum.arduino.cc/t/multi-button-programming/1094488/
  3 Outputs activated by button press
    keep track of runtime
  1 LED to indicate all OFF
  ESP32 Webserver

  Several examples on wokwi in this context:
  https://wokwi.com/projects/357646010902566913
  https://wokwi.com/projects/357648835646874625
  https://wokwi.com/projects/358001919796202497

  by noiasca
  2023-04-02 esp32_relay_timer
  2023-04-03 esp32_relay_timer2
  2023-04-10 esp32_relay_timer4 handle commands, handle javascript, make slider working, make day reset by button press
  2023-04-12 esp32_relay_timer5 proposal of reset method
  2023-04-15 esp32_relay_timer6 a separate output
  2023-04-18 esp32_relay_timer7 new class for the separate indicator
  2023-04-22 esp32_relay_timer7 corrected

*/
// write data to file each day and restore at startup  
// Read Time From NTP At Startup Then set internal RTC with Time From NTP
// Then Code Takes Its Time From RTC
// all button to run motor from pin 14


#include <WiFi.h>
#include <WebServer.h>
#include "time.h"
#include <string>
#include <iostream>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <time.h>
#include <SPIFFS.h>
#include "FS.h"
#include <SPI.h>



// #define ServerVersion "1.0"
String  webpage = "";
bool    SPIFFS_present = false;

#ifndef STASSID                        // either use an external .h file containing STASSID and STAPSK 
//                                     // or
#define STASSID "AMGAD60"
#define STAPSK  "Amgad#60"
#endif
#define test 14
const char* ssid = STASSID;
const char* password = STAPSK;



const char* ntpServer = "pool.ntp.org";
const long gmtOffsetInSeconds = 10800;
const int daylightOffsetInSeconds = 10800;

const char* fileNameFormat = "/%02d%04d.txt";

unsigned long previousMillis = 0;  // Variable to store the previous time
const unsigned long interval = 3600000;  // Interval of one hour in milliseconds


bool wifiConnected = false;
bool dataSaved = false;
bool restr = false;

String table1 = "<table><tr><th>Date</th><th>Runtime 1</th><th>Runtime 2</th><th>Runtime 3</th><th>Separate</th><th>Total 1</th><th>Total 2</th><th>Total 3</th><th>Total Separate</th></tr>";

int sum;
int sumd;


WebServer server(6060);

// this code works fine and calculate total time of each led on
const uint8_t b1 = 4;  // Button
const uint8_t b2 = 22;  
const uint8_t b3 = 15;
const uint8_t b4 = 16;
const uint8_t r1 = 21; // Output relay
const uint8_t r2 = 19;
const uint8_t r3 = 18;
const uint8_t r4 = 23;
const uint8_t commonStatePin = 5; // output (hence will be readed also in some cases)


// a class for one LED one button
class Indicator {
  protected:
    const uint8_t buttonPin;     // the input GPIO, active LOW
    const uint8_t relayPin;      // the output GPIO, active HIGH
    uint8_t state = 0;           // switched off or on
    bool active = true;          // output can be activated by buttonPin (true) or not (false)
    uint32_t runtimeMillis = 0;  // timestamp of last update of counterTotal
    uint32_t runtimeTotal = 0;   // current total running time
    uint32_t runtimeToday;   // todays running time

  public:
    void setRuntimeTotal(int total) {
    runtimeTotal = total;
    }

    static uint8_t lastActive;              // the last pressed button
    static constexpr uint8_t noPin = 255;   // dummy value for "no button is pressed"
    Indicator(uint8_t buttonPin, uint8_t relayPin) : buttonPin{buttonPin}, relayPin{relayPin}
    {}

    void begin() {
      pinMode(buttonPin, INPUT_PULLUP);
      pinMode(relayPin, OUTPUT);
    }

void setRuntimeToday(uint32_t value) {
    runtimeToday = value;
  }

 

    // if running, add the current open time to the runtimeTotal and runtimeToday
    void addTime() {
      uint32_t currentMillis = millis();
      if (state == 1) {
        runtimeTotal += (currentMillis - runtimeMillis) / 1000; // add passed time till now
        runtimeToday += (currentMillis - runtimeMillis) / 1000;
        runtimeMillis = currentMillis;
      }
    }

    uint8_t getState() {     // when you need the state - write a getter
      return state;
    }

    bool getActive() {
      return active;
    }

    uint32_t getRuntimeTotal() {
      addTime();
      return runtimeTotal;
    }

    uint32_t getRuntimeToday() {
      addTime();
      return runtimeToday;
    }

    //reset today's runtime - call once at midnight
    void resetToday() {
      addTime();
      runtimeToday = 0;
    }

    void resetTotal() {
      addTime();
      runtimeToday = 0;
      runtimeTotal = 0;
      }

    //activate reading of button pin (for example by the webserver)
    void activate() {
      active = true;
    }

    //deactivate reading of button pin (for example by the webserver)
    void deactivate() {
      active = false;
    }

    virtual uint8_t update(uint32_t currentMillis = millis()) {
      uint8_t buttonState = true;
      if (active) buttonState = digitalRead(buttonPin);  // only accept pressed button in active mode
      if (buttonState == LOW && lastActive == noPin) {
        state = 1;
        // delay(2000);
        digitalWrite(relayPin, HIGH);
        runtimeMillis = currentMillis;    // start the runtime counter
        lastActive = relayPin;            // changed to relayPin as preparation when the webserver can also activate the relay
      }
      else if (buttonState == HIGH && lastActive == relayPin) {
        state = 0;
        lastActive = noPin;
        addTime();                        // call before a switch off
        // delay(2000);
        digitalWrite(relayPin, LOW);
      }
      return lastActive;                  // returns the button GPIO if it is pressed
    }
};
uint8_t Indicator::lastActive = Indicator::noPin;  // initialize a static class member

//create 3 indicators (each with one button and one relay/LED)
Indicator indicator[] {
  {b1, r1},
  {b2, r2},
  {b3, r3},
};
constexpr size_t noOfIndicators = sizeof(indicator) / sizeof(indicator[0]);

class IndicatorVersionB : public Indicator {
  protected:
    const uint8_t commonStatePin;     // the input GPIO, active LOW
    uint8_t previousState = 0;        // needed to detect the state detection 
  public:
    void setRuntimeTotal(int total) {
    runtimeTotal = total;
    }
    IndicatorVersionB(uint8_t buttonPin, uint8_t relayPin, uint8_t commonStatePin) : Indicator(buttonPin, relayPin), commonStatePin{commonStatePin} {}

    // modified version for derrived class
    uint8_t update(uint32_t currentMillis = millis()) override {
      uint8_t buttonState = true;
      if (active) buttonState = digitalRead(buttonPin);  // only accept pressed button in active mode
      if (digitalRead(commonStatePin) == HIGH && (buttonState == LOW && previousState == 0)) {
        state = 1;
        // delay(2000);
        digitalWrite(relayPin, HIGH);
        runtimeMillis = currentMillis;    // start the runtime counter
      }
      else if (digitalRead(commonStatePin) == LOW || (buttonState == HIGH && previousState == 1)) { // switched off
        state = 0;
        addTime();                        // call before a switch off
        digitalWrite(relayPin, LOW);
      }
      previousState = state;
      
      return buttonState;                  // returns the button GPIO if it is pressed
    }
};

IndicatorVersionB separate{b4, r4, commonStatePin};

void connectToWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.print  ("IP address: ");
  Serial.println(WiFi.localIP());
  Serial.println("Connected to WiFi");
  wifiConnected = true;
}

void listDir(char* dir, String& recentFileName) {
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    Serial.println("Failed to obtain time from internal RTC");
    return;
  }
  int recentMonth = timeinfo.tm_mon + 1;  // Month is 0-based, so adding 1 to get the actual month
  int recentYear = timeinfo.tm_year + 1900;  // Year is years since 1900, so adding 1900 to get the actual year

  File root = SPIFFS.open(dir);
  File recentFile;

  File file = root.openNextFile();

  while (file) {
    String fileName = file.name();
    if (fileName.endsWith(".txt") && fileName.length() == 11) {
      if (recentFileName.isEmpty()) {
        recentFile = file;
        recentFileName = fileName;
        recentMonth = recentFileName.substring(1, 3).toInt();
        recentYear = recentFileName.substring(3, 7).toInt();
      } else {
        int fileMonth = fileName.substring(1, 3).toInt();
        int fileYear = fileName.substring(3, 7).toInt();

        if (fileYear > recentYear || (fileYear == recentYear && fileMonth > recentMonth)) {
          recentFile = file;
          recentFileName = fileName;
          recentMonth = fileMonth;
          recentYear = fileYear;
        }
      }
    }

    /* Serial.print("FILE: ");
    Serial.println(fileName);
*/
    file = root.openNextFile();
  }
  root.close();

}

void restart()
{
//delay(3000);
ESP.restart();
}

void saveDataToFile() {
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    Serial.println("Failed to obtain time from internal RTC");
    return;
  }
 
  char fileName[20];
  snprintf(fileName, sizeof(fileName), fileNameFormat, timeinfo.tm_mon + 1, timeinfo.tm_year + 1900);
  File file = SPIFFS.open(fileName, FILE_APPEND);
  if (!file) {
    Serial.println("Failed to open file for writing");
    return;
  }

  String dataLine = String(timeinfo.tm_mday) + "-" + String(timeinfo.tm_mon + 1) + "-" + String(timeinfo.tm_year + 1900) + ",";

  dataLine += String(indicator[0].getRuntimeToday()/60)+"," ;
  dataLine += String(indicator[1].getRuntimeToday()/60)+"," ;
  dataLine += String(indicator[2].getRuntimeToday()/60)+"," ;
  dataLine += String(separate.getRuntimeToday()/60)+"," ;
  dataLine += String(indicator[0].getRuntimeTotal()/60)+"," ;
  dataLine += String(indicator[1].getRuntimeTotal()/60)+"," ;
  dataLine += String(indicator[2].getRuntimeTotal()/60)+"," ;
  dataLine += String(separate.getRuntimeTotal()/60);
  if (file.println(dataLine)) {
    Serial.println("Data written successfully");
  } else {
    Serial.println("Failed to write data to file");
  }
  file.close();
  }

std::vector<String> lastcells() {
  String recentFileName;
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    Serial.println("Failed to obtain time from internal RTC");
    return std::vector<String>();
  }
  static bool dataLoaded = false;
  static std::vector<String> lines; // Store all lines from the file

  // Load data from file on startup
  if (!dataLoaded) {
    // Serial.print("Load data from file on startup ");
    listDir("/", recentFileName);
    Serial.print("Recent File Name: ");
    Serial.println(recentFileName);

    if (!recentFileName.isEmpty()) {
      Serial.print("Recent File: ");
      Serial.println(recentFileName);

      // Open the recent file for reading
      File recentFileHandle = SPIFFS.open(recentFileName, "r");
      if (recentFileHandle) {
        // Read all lines from the file
        while (recentFileHandle.available()) {
          String line = recentFileHandle.readStringUntil('\n');
          lines.push_back(line);
        }
        recentFileHandle.close();
        dataLoaded = true;
      } else {
        Serial.println("Failed to open recent file for reading.");
      }
    } else {
      Serial.println("No recent files found.");
    }
  }

  // Extract the last line and the last 4 values
  std::vector<String> lastValues;
  if (!lines.empty()) {
    String lastLine = lines.back();
//    Serial.print("Last Line: ");
//    Serial.println(lastLine);

    // Extract the values from the last line
    std::vector<String> values;
    int startIndex = 0;
    int endIndex;
    while ((endIndex = lastLine.indexOf(',', startIndex)) != -1) {
      values.push_back(lastLine.substring(startIndex, endIndex));
      startIndex = endIndex + 1;
    }
    values.push_back(lastLine.substring(startIndex));

    // Check if there are at least 9 values in the last line
    if (values.size() >= 9) {
      lastValues.push_back(values[5]); // inT0
      lastValues.push_back(values[6]); // inT1
      lastValues.push_back(values[7]); // inT2
      lastValues.push_back(values[8]); // sepT
    } else {
      Serial.println("Insufficient values in the last line.");
    }
  } else {
    Serial.println("No data available.");
  }

  return lastValues;
}





void handleRoot() {
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    Serial.println("Failed to obtain time from internal RTC");
    return;
  }
  
  String html = "<!doctype html>\n"
                "<html lang='en'>\n"
                "<head>\n"
                "<title>Relay Status</title>\n"
                "<meta name='viewport' content='width=device-width'>\n"
                "<meta http-equiv='refresh' content='2'>\n"
                "<link rel='icon' href='data:,'>\n"                      // avoid favicon request, might not work for Safari
                "<script src='j.js'></script>\n"
                "<style>\n"
                "body{margin:0;padding:20px;font-family:sans-serif}*{box-sizing:border-box}.table{width:100%;border-collapse:collapse}.table td,.table th{padding:12px 15px;border:1px solid #ddd;text-align:center;font-size:25px;font-weight:bold;color:black;}.table th{background-color:#46494d;color:#fff}.table1{width:100%;border-collapse:collapse}.table1 td,.table1 th{padding:12px 15px;border:1px solid #ddd;text-align:center;font-size:18px;font-weight:bold;color:black;}.table1 th{background-color:#970000;color:#fff}.switch{position:relative;display:inline-block;width:60px;height:34px}.switch input{opacity:0;width:0;height:0}.slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background-color:#ff0000;-webkit-transition:.4s;transition:.4s}.slider:before{position:absolute;content:"";height:26px;width:26px;left:4px;bottom:4px;background-color:#fff;-webkit-transition:.4s;transition:.4s}input:checked+.slider{background-color:#2196f3}input:focus+.slider{box-shadow:0 0 1px #2196f3}input:checked+.slider:before{-webkit-transform:translateX(26px);-ms-transform:translateX(26px);transform:translateX(26px)}.slider.round{border-radius:34px}.slider.round:before{border-radius:50%}"
                "</style>\n"
                "</head>\n"
                "<body>\n"
                "<table class='table'>\n"
                "<thead>\n"
                "<th>Floor</th>\n"
                "<th>Status</th>\n"
                "<th>Control</th>\n"
                "<th>Total H/M</th>\n"
                "<th>Total KW/M</th>\n"
                "<th>KW Today</th>\n"
                "</thead>\n"
                "<tbody>\n";

  // these are the "1 of 3 outputs"
  for (int i = 0; i < noOfIndicators; i++) {
    String color = indicator[i].getState() == 0 ? "red" : "green";
    html += "<tr class='colomn1'>";
    html += "<td data-label='Floor'>Floor (";
    html += i+1;
    html += ")</td>";
    html += "<td data-label='Status'>";
    html += "<div style='display:inline-block;margin:10px;width:200px;height:70px;border:1px solid black;background-color:" + color + ";'><p style='text-align:center;font-size:30px;font-weight:bold;color:black;'>" + String(indicator[i].getRuntimeTotal() / 60) + "M</p>\n";
    html += "</div></td>";
    html += "<td data-label='Control'>";
    html += "<label class='switch'>";     // The Switch
    html += "<input type='checkbox' ";
    if (indicator[i].getActive() == true)
      html += "value='checked' checked='checked' ";
    html += "onclick='firecb(this.id)' id='cb";
    html += i;
    html += "'>";
    html += "<span class='slider round'></span>";
    html += "</label>";
    html += "</td>";
    html += "<td data-label='Total H/M'>" + String(indicator[i].getRuntimeTotal() / 3600) + " H</td>";
    if (i == 0) {
      html += "<td data-label='Total KW/M'>" + String(indicator[i].getRuntimeTotal() * 0.888 / 3600.00) + " KW</td>";
      html += "<td data-label='KW Today'>" + String(indicator[i].getRuntimeToday() * 0.888 / 3600.00) + " KW/Day</td>";
    }
    else {
    html += "<td data-label='Total KW/M'>" + String(indicator[i].getRuntimeTotal() * 0.888 / 3600.00) + " KW</td>";
    html += "<td data-label='KW Today'>" + String(indicator[i].getRuntimeToday() * 0.888 / 3600.00) + " KW/Day</td>";
    }
  }

  //  this is the common state row  MAIN
  String color = digitalRead(commonStatePin) == LOW ? "red" : "green";
  html += "<tr class='colomn4'>";
  html += "<td data-label='Floor'>Main</td>";
  html += "<td data-label='Status'>";
  html += "<div style='display:inline-block;margin:10px;width:200px;height:70px;border:1px solid black;background-color:" + color + ";'><p style='text-align:center;font-size:30px;font-weight:bold;color:black;'>" + String(digitalRead(commonStatePin) == LOW ? "OFF" : "ON") + "</p>\n";
  html += "</div></td>";
  html += "<td data-label='Control'><a href = 'cmd?cmd=reset' target='i'>Reset M</a></td>";  // dirty call as href
  html += "<td data-label='Total H/M'><a href = 'cmdd?cmdd=resetd' target='i'>Reset D</a></td>";  // dirty call as href 
  html += "</td>";
  html += "<td data-label='Total KW/M'>" + String(timeinfo.tm_mday) + "/" + String(timeinfo.tm_mon + 1) + "/" + String(timeinfo.tm_year + 1900) + "</td>";
  html += "<td data-label='KW Today'> " + String(timeinfo.tm_hour) + ":" + String(timeinfo.tm_min) + ":" + String(timeinfo.tm_sec) + "</td>";

  // this is the separate output MOTOR
  color = separate.getState() == 0 ? "red" : "green";
  html += "<tr class='colomn4'>";
  html += "<td data-label='Floor'>Motor</td>";
  html += "<td data-label='Status'>";
  html += "<div style='display:inline-block;margin:10px;width:200px;height:70px;border:1px solid black;background-color:" + color + ";'><p style='text-align:center;font-size:30px;font-weight:bold;color:black;'>" + String(separate.getRuntimeTotal()/60 ) + "M</p>\n";  
  html += "</div></td>";
  html += "<td data-label='Control'>";
  html += "<label class='switch'>";     // The Switch
    html += "<input type='checkbox' ";
     if (separate.getActive() == true)
      html += "value='checked' checked='checked' ";
    html += "onclick='firecb(this.id)' id='resbutton";
    html += "'>";
    html += "<span class='slider round'></span>";
    html += "</label>";
    html += "</td>";

 
  
  html += "<td data-label='Total H/M'>" + String(separate.getRuntimeTotal() / 3600) + " H</td>";
  html += "<td data-label='Total KW/M'>" + String(separate.getRuntimeTotal() * 0.888 / 3600.00) + " KW</td>";
  html += "<td data-label='KW Today'>" + String(separate.getRuntimeToday() * 0.888 / 3600.00) + " KW/Day</td>";

  // a row with Totals
  color = digitalRead(b4) == HIGH ? "red" : "green";
  sum = indicator[0].getRuntimeTotal() + indicator[1].getRuntimeTotal() + indicator[2].getRuntimeTotal() + separate.getRuntimeTotal();
  sumd = indicator[0].getRuntimeToday() + indicator[1].getRuntimeToday() + indicator[2].getRuntimeToday() + separate.getRuntimeToday();
  html += "<tr class='colomn4'>";
  html += "<td data-label='Floor'>Totals</td>";
  html += "<td data-label='Status'>";
  html += sum / 60 ;
  html += " M</td>";
    
  html += "<td data-label='Control'>";
  html += "<div style='display:inline-block;margin:10px;width:200px;height:70px;border:1px solid black;background-color:" + color + ";'><p style='text-align:center;font-size:30px;font-weight:bold;color:black;'>" + "Motor "+ (digitalRead(b4) == LOW ? "Run" : "Stop") + "</p>\n";  
  html += "</div></td>";


  html += "<td data-label='Total H/M'>  ";
  html += sum / 3600 ;
  html += " H</td>";
  html += "<td data-label='Total KW/M'> ";
  html += sum * 0.888 / 3600.00;
  html += " KW</td>";
  html += "<td data-label='KW Today'> ";
  html += sumd * 0.888 / 3600.00;
  html += " KW/Day</td>";
  html += "</tr>";
  html += "</tbody>\n";
  html += "</table>\n";
  html += "<iframe name='i' style='display:none'></iframe>\n";               // dirty hack to keep
  html += "</div>\n";
  html += "<br><br>\n"; 
  for (int i = 0; i < 15; i++){
  html += "&nbsp;";  }
  html += "<button style='text-align:center;font-size:30px;font-weight:bold;color:black;' onclick = \"window.location.href = 'cmdr?cmdr=rest';\" >RESTART</button>";  // dirty call as href 
  html += "<br><br>\n";
  for (int i = 0; i < 30; i++){
  html += "&nbsp;";  }
  // html += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
  html += "<button style='text-align:center;font-size:30px;font-weight:bold;color:black;' onclick = \"window.location.href = 'cmdl?cmdl=moto';\" >RUN MOTOR</button>";  // dirty call as href 
  html += "<br><br>\n";
  for (int i = 0; i < 45; i++){
  html += "&nbsp;";  }
  html += "<button style='text-align:center;font-size:30px;font-weight:bold;color:black;' onclick = \"window.location.href = 'cmdw?cmdw=data';\" >Dialy Report</button>";  // dirty call as href 
  html += "<br><br>\n";
  for (int i = 0; i < 60; i++){
  html += "&nbsp;";  }
  html += "<button style='text-align:center;font-size:30px;font-weight:bold;color:black;' onclick = \"window.location.href = 'cmdf?cmdf=files';\" >FILES</button>";  // dirty call as href 
  html += "</body>\n</html>";
  server.send(200, "text/html", html);
}

void handleData()
{
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    Serial.println("Failed to obtain time from internal RTC");
    return;
  }
  
  String html = "<!doctype html>\n"
                "<html lang='en'>\n"
                "<head>\n"
                "<title>Dialy Report</title>\n"
                "<meta name='viewport' content='width=device-width'>\n"
                "<meta http-equiv='refresh' content='2'>\n"
                "<link rel='icon' href='data:,'>\n"                    // avoid favicon request, might not work for Safari
                "<script src='j.js'></script>\n"
                "<style>\n"
                "body{margin:0;padding:20px;font-family:sans-serif}*{box-sizing:border-box}.table{width:100%;border-collapse:collapse}.table td,.table th{padding:12px 15px;border:1px solid #ddd;text-align:center;font-size:25px;font-weight:bold;color:black;}.table th{background-color:#46494d;color:#fff}.table1{width:100%;border-collapse:collapse}.table1 td,.table1 th{padding:12px 15px;border:1px solid #ddd;text-align:center;font-size:18px;font-weight:bold;color:black;}.table1 th{background-color:#970000;color:#fff}.switch{position:relative;display:inline-block;width:60px;height:34px}.switch input{opacity:0;width:0;height:0}.slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background-color:#ff0000;-webkit-transition:.4s;transition:.4s}.slider:before{position:absolute;content:"";height:26px;width:26px;left:4px;bottom:4px;background-color:#fff;-webkit-transition:.4s;transition:.4s}input:checked+.slider{background-color:#2196f3}input:focus+.slider{box-shadow:0 0 1px #2196f3}input:checked+.slider:before{-webkit-transform:translateX(26px);-ms-transform:translateX(26px);transform:translateX(26px)}.slider.round{border-radius:34px}.slider.round:before{border-radius:50%}"
                "</style>\n"
                "</head>\n"
                "<body>\n"
                "<table class='table1'>\n"
                "<thead>\n"
                "<th>Date</th>\n"
                "<th>Floor1-H</th>\n"
                "<th>Floor2-H</th>\n"
                "<th>Floor3-H</th>\n"
                "<th>Motor-H</th>\n"
                "<th>Floor1-KW</th>\n"
                "<th>Floor2-KW</th>\n"
                "<th>Floor3-KW</th>\n"
                "<th>Motor-KW</th>\n"
                "<th>Total-KW</th>\n"
                "</thead>\n"
                "<tbody>\n";
                
  html += "<br><br>\n"; 
  html += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
  html += "<button style='text-align:center;font-size:30px;font-weight:bold;color:black;' onclick = \"window.location.href = '/';\" >HOME</button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";  // dirty call as href 
  html += "<button style='text-align:center;font-size:30px;font-weight:bold;color:black;' onclick = \"window.location.href = 'cmds?cmds=save';\" >SAVE DATA</button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";  // dirty call as href 
  html += "<button style='text-align:center;font-size:30px;font-weight:bold;color:black;' onclick = \"window.location.href = 'cmdf?cmdf=files';\" >FILES</button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";  // dirty call as href 
  html += "<br><br>\n";  

  String recentFileName;
  listDir("/", recentFileName);
  // Serial.print("Recent File Name: ");
  // Serial.println(recentFileName);
  if (!recentFileName.isEmpty()) {
    // Serial.print("Recent File: ");
    // Serial.println(recentFileName);
  static bool dataup = false;
  static bool dataLoaded = false;
  static std::vector<String> lines; // Store all lines from the file
  // Load data from file on startup
  if (!dataLoaded) {
    // Serial.print("Load data from file on startup ");
    listDir("/", recentFileName);
    // Serial.print("Recent File Name: ");
    // Serial.println(recentFileName);

    if (!recentFileName.isEmpty()) {
      // Serial.print("Recent File: ");
      // Serial.println(recentFileName);

      // Open the recent file for reading
      File recentFileHandle = SPIFFS.open(recentFileName, "r");
      if (recentFileHandle) {
        // Read all lines from the file
        while (recentFileHandle.available()) {
          String line = recentFileHandle.readStringUntil('\n');
          lines.push_back(line);
        }

        recentFileHandle.close();
        dataLoaded = true;
      } else {
        Serial.println("Failed to open recent file for reading.");
      }
    } else {
      Serial.println("No recent files found.");
    }
  }

  // Update the data from file daily at 23:57
  if (timeinfo.tm_hour == 23 && timeinfo.tm_min == 57 && !dataup ) {
    Serial.print("Updated now DATA ");
    listDir("/", recentFileName);
    // Serial.print("Recent File Name: ");
    // Serial.println(recentFileName);

    if (!recentFileName.isEmpty()) {
      // Serial.print("Recent File: ");
      // Serial.println(recentFileName);

      // Open the recent file for reading
      File recentFileHandle = SPIFFS.open(recentFileName, "r");
      if (recentFileHandle) {
        // Read all lines from the file
        lines.clear(); // Clear the previous lines
        while (recentFileHandle.available()) {
          String line = recentFileHandle.readStringUntil('\n');
          lines.push_back(line);
        }

        recentFileHandle.close();
        dataup = true;
      } else {
        Serial.println("Failed to open recent file for reading.");
      }
    } else {
      Serial.println("No recent files found.");
    }
  }

  // Display the data from the file on startup and throughout the day
  //Serial.println("OLD DATA");
  for (const auto& line : lines) {
    // Extract the values from the line
    std::vector<String> values;
    int startIndex = 0;
    int endIndex;
    while ((endIndex = line.indexOf(',', startIndex)) != -1) {
      values.push_back(line.substring(startIndex, endIndex));
      startIndex = endIndex + 1;
    }
    values.push_back(line.substring(startIndex));

          // Display the extracted values
                         html += "<tr class='colomn1'>";
                         html += "<td data-label='Date'>" + values[0] + " </td>";
                         html += "<td data-label='Floor1-H'>" + String(values[1].toInt()/60.00 ) + " </td>";                  
                         html += "<td data-label='Floor2-H'>" + String(values[2].toInt()/60.00 ) + " </td>";
                         html += "<td data-label='Floor3-H'>" + String(values[3].toInt()/60.00 ) + " </td>";
                         html += "<td data-label='Motor-H'>" + String(values[4].toInt()/60.00 ) + " </td>";
                         html += "<td data-label='Floor1-KW'>" + String(values[1].toInt() * 0.888/60 ) + " </td>";
                         html += "<td data-label='Floor2-KW'>" + String(values[2].toInt() * 0.888/60 ) + " </td>";
                         html += "<td data-label='Floor3-KW'>" + String(values[3].toInt() * 0.888/60 ) + " </td>";
                         html += "<td data-label='Motor-KW'>" + String(values[4].toInt() * 0.888/60 ) + " </td>";             
				         				 // sumd = inD0Str.toInt() + inD1Str.toInt() + inD2Str.toInt() + sepDStr.toInt() ;
                         sumd = values[1].toInt() * 0.888/60 + values[2].toInt() * 0.888/60 + values[3].toInt() * 0.888/60 + values[4].toInt() * 0.888/60 ;
                         // float multipliedSum = sumd ;
                         html += "<td data-label='Total-KW'>" + String(sumd) + " </td>";
  }
 }
  
  html += "</tr>";
  html += "</tbody>\n";
 html += "</table>\n";  

 html += "</body>\n</html>";
  

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



void handleCommand() {
  // receive command and handle accordingly
  // Serial.println("D310 handleCommand");
  for (uint8_t i = 0; i < server.args(); i++) {
    Serial.print(server.argName(i));
    Serial.print(": ");
    Serial.println(server.arg(i));
  }

  if (server.argName(0) == "cmd" && server.arg(0) == "reset") {            // the parameter which was sent to this server
    Serial.println("D318 reset");
    for (auto &i : indicator) i.resetToday(); // reset all today's runtime counter
    for (auto &i : indicator) i.resetTotal(); // reset Total runtime counter
    separate.resetTotal();    // reset separate Total runtime counter
  }
  else if (server.argName(0) == "cmdd" && server.arg(0) == "resetd") {            // the parameter which was sent to this server
    Serial.println("D275 reset");
    for (auto &i : indicator) i.resetToday(); // reset all today's runtime counter
    separate.resetToday();      // reset separate today's runtime counter
    }  

  else if (server.argName(0) == "cmdl" && server.arg(0) == "moto") {            // the parameter which was sent to this server
    Serial.println("D285 Motor On");
    digitalWrite(test, HIGH);      // run motor
    }  

  else if (server.argName(0) == "cmdr" && server.arg(0) == "rest") {            // the parameter which was sent to this server
    Serial.println("D295 Restart");
    restr = true;      // Restart
    }

  else if (server.argName(0) == "cmdf" && server.arg(0) == "files") {            // the parameter which was sent to this server
    Serial.println("D288 Files Menu");
    SPIFFS_dir();
    }

  else if (server.argName(0) == "cmdw" && server.arg(0) == "data") {            // the parameter which was sent to this server
    Serial.println("D289 Daily Report");
    handleData();
    } 

  else if (server.argName(0) == "cmds" && server.arg(0) == "save") {            // the parameter which was sent to this server
    Serial.println("D298 Daily Report");
    saveDataToFile();
    }

  else if (server.arg(0) == "resbutton" && server.argName(1) == "v") {            // a checkbox sends a value
    byte i = server.arg(0).substring(2).toInt();      // we get a "cb2"
    Serial.print("D334 cb i:"); Serial.println(i);
    for (uint8_t i = 0; i < noOfIndicators; i++){
      Serial.print("D344 FORLOOP i:"); Serial.println(i);
      if (server.arg(1) == "0") 
        {                      // we get a string not an integer
        indicator[i].deactivate();
        digitalWrite(test, LOW);
        separate.deactivate();
        pinMode(commonStatePin , INPUT_PULLUP);
        Serial.print("D355 separate.deactivate ");

        }
      else
        {
        indicator[i].activate();
        separate.activate();
        pinMode(commonStatePin, OUTPUT);
        Serial.print("D355 separate.activate(); ");
        }

    }
    
  }


  else if (server.argName(0) == "cb" && server.argName(1) == "v") {            // a checkbox sends a value
    byte i = server.arg(0).substring(2).toInt();      // we get a "cb2"
    Serial.print("D324 cb i:"); Serial.println(i);
    if (i < noOfIndicators) {
      if (server.arg(1) == "0")                       // we get a string not an integer
        indicator[i].deactivate();
      else
        indicator[i].activate();
    }}

  
  server.send(204);                      // this page doesn't send back content --> 204
}

void handleJs() {
  // Output: JavaScript

  String message;
  message += F("function firecb(c){\n"                                 // c caller (mostly the id/self)
               " var request=new XMLHttpRequest();\n"
               " var v=0;\n"                                           // if value is initialisied with 0 we get ...
               " if (document.getElementById(c).checked) v=1;\n"       // ... 1 or 0 if checkbox is checked (better than true or false)
               " request.open('GET','cmd?cb='+c+'&v='+v,true);\n"
               " request.open('GET','cmdd?cb='+c+'&v='+v,true);\n"
               " request.open('GET','cmdl?cb='+c+'&v='+v,true);\n"
               " request.open('GET','cmdr?cb='+c+'&v='+v,true);\n"
               " request.open('GET','cmdf?cb='+c+'&v='+v,true);\n" 
               " request.open('GET','cmdw?cb='+c+'&v='+v,true);\n" 
               " request.open('GET','cmds?cb='+c+'&v='+v,true);\n"
               " request.send(null);\n"
               "}\n");

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

void handle204() {
  server.send(204);                     // this page doesn't send back content --> 204
}

void handleNotFound() {
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
}

void checkRuntimeReset() {

struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    Serial.println("Failed to obtain time from internal RTC");
    return;
  }

  int currentDay = timeinfo.tm_mday;
  int currentMonth = timeinfo.tm_mon + 1;  // Month is 0-based, so adding 1 to get the actual month
  int currentYear = timeinfo.tm_year + 1900;  // Year is years since 1900, so adding 1900 to get the actual year

  static int previousDay = -1; // remember the last day of reset;
  if (timeinfo.tm_hour == 23 && timeinfo.tm_min == 59 && previousDay != timeinfo.tm_mday) {
    previousDay = timeinfo.tm_mday;
    Serial.println("D378 Daily reset of runtime");
    for (auto &i : indicator) i.resetToday();

    separate.resetToday(); // to be checked if needed
  }
}

//=============New Web Pages  ==========
void handleSPIFFSPage() {
 
#define ServerVersion "1.0"
String  webpage = "";
bool    SPIFFS_present = false;

#include "FS.h"
#include <SPI.h>

}

// All supporting functions from here...

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void File_Download(){ // This gets called twice, the first pass selects the input, the second pass then processes the command line arguments
  if (server.args() > 0 ) { // Arguments were received
    if (server.hasArg("download")) DownloadFile(server.arg(0));
  }
  else SelectInput("Enter filename to download","download","download");
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void DownloadFile(String filename){
  if (SPIFFS_present) { 
    File download = SPIFFS.open("/"+filename,  "r");
    if (download) {
      server.sendHeader("Content-Type", "text/text");
      server.sendHeader("Content-Disposition", "attachment; filename="+filename);
      server.sendHeader("Connection", "close");
      server.streamFile(download, "application/octet-stream");
      download.close();
    } else ReportFileNotPresent("download"); 
  } else ReportSPIFFSNotPresent();
}

void File_Upload(){
  append_page_header();
  webpage += F("<h3>Select File to Upload</h3>"); 
  webpage += F("<FORM action='/fupload' method='post' enctype='multipart/form-data'>");
  webpage += F("<input class='buttons' style='width:40%' type='file' name='fupload' id = 'fupload' value=''><br>");
  webpage += F("<br><button class='buttons' style='width:10%' type='submit'>Upload File</button><br>");
  webpage += F("<a href='/'>[Back]</a><br><br>");
  append_page_footer();
  server.send(200, "text/html",webpage);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
File UploadFile; 
void handleFileUpload(){ // upload a new file to the Filing system
  HTTPUpload& uploadfile = server.upload(); // See https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WebServer/srcv
                                            // For further information on 'status' structure, there are other reasons such as a failed transfer that could be used
  if(uploadfile.status == UPLOAD_FILE_START)
  {
    String filename = uploadfile.filename;
    if(!filename.startsWith("/")) filename = "/"+filename;
    Serial.print("Upload File Name: "); Serial.println(filename);
    SPIFFS.remove(filename);                  // Remove a previous version, otherwise data is appended the file again
    UploadFile = SPIFFS.open(filename, "w");  // Open the file for writing in SPIFFS (create it, if doesn't exist)
  }
  else if (uploadfile.status == UPLOAD_FILE_WRITE)
  {
    if(UploadFile) UploadFile.write(uploadfile.buf, uploadfile.currentSize); // Write the received bytes to the file
  } 
  else if (uploadfile.status == UPLOAD_FILE_END)
  {
    if(UploadFile)          // If the file was successfully created
    {                                    
      UploadFile.close();   // Close the file again
      Serial.print("Upload Size: "); Serial.println(uploadfile.totalSize);
      webpage = "";
      append_page_header();
      webpage += F("<h3>File was successfully uploaded</h3>"); 
      webpage += F("<h2>Uploaded File Name: "); webpage += uploadfile.filename+"</h2>";
      webpage += F("<h2>File Size: "); webpage += file_size(uploadfile.totalSize) + "</h2><br>"; 
      append_page_footer();
      server.send(200,"text/html",webpage);
    } 
    else
    {
      ReportCouldNotCreateFile("upload");
    }
  }
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void SPIFFS_dir(){ 
  if (SPIFFS_present) { 
    File root = SPIFFS.open("/");
    if (root) {
      root.rewindDirectory();
      SendHTML_Header();
      webpage += F("<h3 class='rcorners_m'>ESP32 Directory Contents</h3><br>");
      webpage += F("<table align='center'>");
      webpage += F("<tr><th>Name/Type</th><th style='width:20%'>Type File/Dir</th><th>File Size</th></tr>");
      printDirectory("/",0);
      webpage += F("</table>");
      SendHTML_Content();
      root.close();
    }
    else 
    {
      SendHTML_Header();
      webpage += F("<h3>No Files Found</h3>");
    }
    append_page_footer();
    SendHTML_Content();
    SendHTML_Stop();   // Stop is needed because no content length was sent
  } else ReportSPIFFSNotPresent();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void printDirectory(const char * dirname, uint8_t levels){
  File root = SPIFFS.open(dirname);
  if(!root){
    return;
  }
  if(!root.isDirectory()){
    return;
  }
  File file = root.openNextFile();
  while(file){
    if (webpage.length() > 1000) {
      SendHTML_Content();
    }
    if(file.isDirectory()){
      webpage += "<tr><td>"+String(file.isDirectory()?"Dir":"File")+"</td><td>"+String(file.name())+"</td><td></td></tr>";
      printDirectory(file.name(), levels-1);
    }
    else
    {
      webpage += "<tr><td>"+String(file.name())+"</td>";
      webpage += "<td>"+String(file.isDirectory()?"Dir":"File")+"</td>";
      int bytes = file.size();
      String fsize = "";
      if (bytes < 1024)                     fsize = String(bytes)+" B";
      else if(bytes < (1024 * 1024))        fsize = String(bytes/1024.0,3)+" KB";
      else if(bytes < (1024 * 1024 * 1024)) fsize = String(bytes/1024.0/1024.0,3)+" MB";
      else                                  fsize = String(bytes/1024.0/1024.0/1024.0,3)+" GB";
      webpage += "<td>"+fsize+"</td></tr>";
    }
    file = root.openNextFile();
  }
  file.close();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void File_Stream(){
  if (server.args() > 0 ) { // Arguments were received
    if (server.hasArg("stream")) SPIFFS_file_stream(server.arg(0));
  }
  else SelectInput("Enter a File to Stream","stream","stream");
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void SPIFFS_file_stream(String filename) { 
  if (SPIFFS_present) { 
    File dataFile = SPIFFS.open("/"+filename,  "r"); // Now read data from SPIFFS Card 
    if (dataFile) { 
      if (dataFile.available()) { // If data is available and present 
        String dataType = "application/octet-stream"; 
        if (server.streamFile(dataFile, dataType) != dataFile.size()) {Serial.print(F("Sent less data than expected!")); } 
      }
      dataFile.close(); // close the file: 
    } else ReportFileNotPresent("Cstream");
  } else ReportSPIFFSNotPresent(); 
}   
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void File_Delete(){
  if (server.args() > 0 ) { // Arguments were received
    if (server.hasArg("delete")) SPIFFS_file_delete(server.arg(0));
  }
  else SelectInput("Select a File to Delete","delete","delete");
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void SPIFFS_file_delete(String filename) { // Delete the file 
  if (SPIFFS_present) { 
    SendHTML_Header();
    File dataFile = SPIFFS.open("/"+filename, "r"); // Now read data from SPIFFS Card 
    if (dataFile)
    {
      if (SPIFFS.remove("/"+filename)) {
        Serial.println(F("File deleted successfully"));
        webpage += "<h3>File '"+filename+"' has been erased</h3>"; 
        webpage += F("<a href='/delete'>[Back]</a><br><br>");
      }
      else
      { 
        webpage += F("<h3>File was not deleted - error</h3>");
        webpage += F("<a href='delete'>[Back]</a><br><br>");
      }
    } else ReportFileNotPresent("delete");
    append_page_footer(); 
    SendHTML_Content();
    SendHTML_Stop();
  } else ReportSPIFFSNotPresent();
} 
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void SendHTML_Header(){
  server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); 
  server.sendHeader("Pragma", "no-cache"); 
  server.sendHeader("Expires", "-1"); 
  server.setContentLength(CONTENT_LENGTH_UNKNOWN); 
  server.send(200, "text/html", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves. 
  append_page_header();
  server.sendContent(webpage);
  webpage = "";
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void SendHTML_Content(){
  server.sendContent(webpage);
  webpage = "";
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void SendHTML_Stop(){
  server.sendContent("");
  server.client().stop(); // Stop is needed because no content length was sent
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void SelectInput(String heading1, String command, String arg_calling_name){
  SendHTML_Header();
  webpage += F("<h3>"); webpage += heading1 + "</h3>"; 
  webpage += F("<FORM action='/"); webpage += command + "' method='post'>"; // Must match the calling argument e.g. '/chart' calls '/chart' after selection but with arguments!
  webpage += F("<input type='text' name='"); webpage += arg_calling_name; webpage += F("' value=''><br>");
  webpage += F("<type='submit' name='"); webpage += arg_calling_name; webpage += F("' value=''><br><br>");
  webpage += F("<a href='/'>[Back]</a><br><br>");
  append_page_footer();
  SendHTML_Content();
  SendHTML_Stop();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void ReportSPIFFSNotPresent(){
  SendHTML_Header();
  webpage += F("<h3>No SPIFFS Card present</h3>"); 
  webpage += F("<a href='/'>[Back]</a><br><br>");
  append_page_footer();
  SendHTML_Content();
  SendHTML_Stop();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void ReportFileNotPresent(String target){
  SendHTML_Header();
  webpage += F("<h3>File does not exist</h3>"); 
  webpage += F("<a href='/"); webpage += target + "'>[Back]</a><br><br>";
  append_page_footer();
  SendHTML_Content();
  SendHTML_Stop();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void ReportCouldNotCreateFile(String target){
  SendHTML_Header();
  webpage += F("<h3>Could Not Create Uploaded File (write-protected?)</h3>"); 
  webpage += F("<a href='/"); webpage += target + "'>[Back]</a><br><br>";
  append_page_footer();
  SendHTML_Content();
  SendHTML_Stop();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
String file_size(int bytes){
  String fsize = "";
  if (bytes < 1024)                 fsize = String(bytes)+" B";
  else if(bytes < (1024*1024))      fsize = String(bytes/1024.0,3)+" KB";
  else if(bytes < (1024*1024*1024)) fsize = String(bytes/1024.0/1024.0,3)+" MB";
  else                              fsize = String(bytes/1024.0/1024.0/1024.0,3)+" GB";
  return fsize;
}

//===============End of new web pages


//======== Contents of CSS.h====================
void append_page_header() {
  webpage  = F("<!DOCTYPE html><html>");
  webpage += F("<head>");
  webpage += F("<title>File Server</title>"); // NOTE: 1em = 16px
  webpage += F("<meta name='viewport' content='user-scalable=yes,initial-scale=1.0,width=device-width'>");
  webpage += F("<style>");
  webpage += F("body{max-width:65%;margin:0 auto;font-family:arial;font-size:105%;text-align:center;color:blue;background-color:#F7F2Fd;}");
  webpage += F("ul{list-style-type:none;margin:0.1em;padding:0;border-radius:0.375em;overflow:hidden;background-color:#dcade6;font-size:1em;}");
  webpage += F("li{float:left;border-radius:0.375em;border-right:0.06em solid #bbb;}last-child {border-right:none;font-size:85%}");
  webpage += F("li a{display: block;border-radius:0.375em;padding:0.44em 0.44em;text-decoration:none;font-size:85%}");
  webpage += F("li a:hover{background-color:#EAE3EA;border-radius:0.375em;font-size:85%}");
  webpage += F("section {font-size:0.88em;}");
  webpage += F("h1{color:white;border-radius:0.5em;font-size:1em;padding:0.2em 0.2em;background:#558ED5;}");
  webpage += F("h2{color:orange;font-size:1.0em;}");
  webpage += F("h3{font-size:0.8em;}");
  webpage += F("table{font-family:arial,sans-serif;font-size:0.9em;border-collapse:collapse;width:85%;}"); 
  webpage += F("th,td {border:0.06em solid #dddddd;text-align:left;padding:0.3em;border-bottom:0.06em solid #dddddd;}"); 
  webpage += F("tr:nth-child(odd) {background-color:#eeeeee;}");
  webpage += F(".rcorners_n {border-radius:0.5em;background:#558ED5;padding:0.3em 0.3em;width:20%;color:white;font-size:75%;}");
  webpage += F(".rcorners_m {border-radius:0.5em;background:#558ED5;padding:0.3em 0.3em;width:50%;color:white;font-size:75%;}");
  webpage += F(".rcorners_w {border-radius:0.5em;background:#558ED5;padding:0.3em 0.3em;width:70%;color:white;font-size:75%;}");
  webpage += F(".column{float:left;width:50%;height:45%;}");
  webpage += F(".row:after{content:'';display:table;clear:both;}");
  webpage += F("*{box-sizing:border-box;}");
  webpage += F("footer{background-color:#eedfff; text-align:center;padding:0.3em 0.3em;border-radius:0.375em;font-size:60%;}");
  webpage += F("button{border-radius:0.5em;background:#558ED5;padding:0.3em 0.3em;width:20%;color:white;font-size:130%;}");
  webpage += F(".buttons {border-radius:0.5em;background:#558ED5;padding:0.3em 0.3em;width:15%;color:white;font-size:80%;}");
  webpage += F(".buttonsm{border-radius:0.5em;background:#558ED5;padding:0.3em 0.3em;width:9%; color:white;font-size:70%;}");
  webpage += F(".buttonm {border-radius:0.5em;background:#558ED5;padding:0.3em 0.3em;width:15%;color:white;font-size:70%;}");
  webpage += F(".buttonw {border-radius:0.5em;background:#558ED5;padding:0.3em 0.3em;width:40%;color:white;font-size:70%;}");
  webpage += F("a{font-size:75%;}");
  webpage += F("p{font-size:75%;}");
  webpage += F("</style></head><body><h1>File Server "); webpage += String(ServerVersion) + "</h1>";
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void append_page_footer(){ // Saves repeating many lines of code for HTML page footers
  webpage += F("<ul>");
  webpage += F("<li><a href='/'>Home</a></li>"); // Lower Menu bar command entries
  webpage += F("<li><a href='/download'>Download</a></li>"); 
  webpage += F("<li><a href='/upload'>Upload</a></li>"); 
  webpage += F("<li><a href='/stream'>Stream</a></li>"); 
  webpage += F("<li><a href='/delete'>Delete</a></li>"); 
  webpage += F("<li><a href='/dir'>Directory</a></li>");
  webpage += F("</ul>");
  webpage += "<footer>&copy;"+String(char(byte(0x40>>1)))+String(char(byte(0x88>>1)))+String(char(byte(0x5c>>1)))+String(char(byte(0x98>>1)))+String(char(byte(0x5c>>1)));
  webpage += String(char((0x84>>1)))+String(char(byte(0xd2>>1)))+String(char(0xe4>>1))+String(char(0xc8>>1))+String(char(byte(0x40>>1)));
  webpage += String(char(byte(0x64/2)))+String(char(byte(0x60>>1)))+String(char(byte(0x62>>1)))+String(char(0x70>>1))+"</footer>";
  webpage += F("</body></html>");
}
//======== End of CSS.h====================

//======== Contents of Network.h====================
// Adjust the following values to match your needs
// -----------------------------------------------
#define   servername "fileserver"  // Set your server's logical name here e.g. if 'myserver' then address is http://myserver.local/
IPAddress local_IP(192, 168, 0, 150); // Set your server's fixed IP address here
IPAddress gateway(192, 168, 0, 1);    // Set your network Gateway usually your Router base address
IPAddress subnet(255, 255, 255, 0);   // Set your network sub-network mask here
IPAddress dns(192,168,0,1);           // Set your network DNS usually your Router base address
const char ssid_1[]     = "your_SSID1";
const char password_1[] = "your_PASSWORD_for SSID1";

const char ssid_2[]     = "your_SSID2";
const char password_2[] = "your_PASSWORD_for SSID2";

const char ssid_3[]     = "your_SSID3";
const char password_3[] = "your_PASSWORD_for SSID3";

const char ssid_4[]     = "your_SSID4";
const char password_4[] = "your_PASSWORD_for SSID4";
//======== End of Network.h====================

//======== Contents of Sys_Variables.h====================

#ifdef ESP8266
#define SD_CS_pin           D8         // The pin on Wemos D1 Mini for SD Card Chip-Select
#else
#define SD_CS_pin           5        // Use pin 5 on MH-T Live ESP32 version of Wemos D1 Mini for SD Card Chip-Select
#endif                               // Use pin 13 on Wemos ESP32 Pro
//======== End of Sys_Variables.h====================





void setup() {
  int value = 0;
  pinMode(test, OUTPUT);
  digitalWrite(test,HIGH);
  Serial.begin(115200);
  // rtc.setTime(00, 00, 00, 8, 5, 2040);  // 1th Jan 2040 01:00:00
  while (!Serial);        // wait for the serial monitor to open

  
  connectToWiFi();
  
  WiFiUDP ntpUDP;
  NTPClient timeClient(ntpUDP, ntpServer, gmtOffsetInSeconds, daylightOffsetInSeconds);

  // Set ESP32 internal RTC from NTP
  timeClient.begin();
  timeClient.update();

  // Wait for NTP response
  while (!timeClient.update()) {
    Serial.println("Waiting for NTP response...");
    delay(1000);
  }

  // Get the epoch time from NTP
  unsigned long epochTime = timeClient.getEpochTime();

  // Set the ESP32's internal RTC
  struct timeval tv;
  tv.tv_sec = epochTime;
  tv.tv_usec = 0;
  settimeofday(&tv, NULL);

struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    Serial.println("Failed to obtain time from internal RTC");
    return;
  }

// Initialize SPIFFS
  if (!SPIFFS.begin()) {
    Serial.println("Failed to mount SPIFFS");
    return;
  }

 

  for (auto &i : indicator) i.begin();
  separate.begin();
  
   if (!SPIFFS.begin(true)) {
    Serial.println("SPIFFS initialisation failed...");
    SPIFFS_present = false; 
  }
  else
  {
    Serial.println(F("SPIFFS initialised... file access enabled..."));
    SPIFFS_present = true; 
  }
  
  
  // the webserver
  // Set up the server routes
  server.on("/", handleRoot);
  server.on("/favicon.ico", handle204);
  server.on("/j.js",  handleJs);                 // javscript
  server.on("/cmd", handleCommand);              // process commands
  server.on("/cmdd", handleCommand);              // process commands   
  server.on("/cmdl", handleCommand);              // process commands
  server.on("/cmdr", handleCommand);              // process commands
  server.on("/cmdf", handleCommand);              // process commands
  server.on("/cmdw", handleCommand);              // process commands
   server.on("/cmds", handleCommand);              // process commands
  server.onNotFound(handleNotFound);
  
  // server.on("/spiffs", HTTP_GET, [](){ server.sendHeader("Connection", "close"); handleSPIFFSPage(); });
  ///////////////////////////// Server Request commands  
  server.on("/download", File_Download);
  server.on("/upload",   File_Upload);
  server.on("/fupload",  HTTP_POST,[](){ server.send(200);}, handleFileUpload);
  server.on("/stream",   File_Stream);
  server.on("/delete",   File_Delete);
  server.on("/dir",      SPIFFS_dir);
    ///////////////////////////// End of Request commands   

  server.begin();
  
  Serial.println("HTTP server started");

  
  Serial.print("Current time From Internal RTC: ");
  Serial.print(timeinfo.tm_year + 1900);
  Serial.print("-");
  Serial.print(timeinfo.tm_mon + 1);
  Serial.print("-");
  Serial.print(timeinfo.tm_mday);
  Serial.print(" ");
  Serial.print(timeinfo.tm_hour);
  Serial.print(":");
  Serial.print(timeinfo.tm_min);
  Serial.print(":");
  Serial.print(timeinfo.tm_sec);
  Serial.println();
 
  if (!SPIFFS.begin()) {
    Serial.println("Failed to mount SPIFFS");
    return;
  }

  // Print available space
  Serial.println("Total space: " + String(SPIFFS.totalBytes()));
  Serial.println("Used space: " + String(SPIFFS.usedBytes()));


// Call lastcells() to get the last 4 values
  std::vector<String> lastValues = lastcells();

  if (!lastValues.empty()) {
    /*
    Serial.println("Last values from lastcells():");
    Serial.print("inT0: ");
    Serial.println(lastValues[0]);
    Serial.print("inT1: ");
    Serial.println(lastValues[1]);
    Serial.print("inT2: ");
    Serial.println(lastValues[2]);
    Serial.print("sepT: ");
    Serial.println(lastValues[3]); 
    */
    // Assign the values to the indicator objects and separate object
    indicator[0].setRuntimeTotal(lastValues[0].toInt() * 60);
    indicator[1].setRuntimeTotal(lastValues[1].toInt() * 60);
    indicator[2].setRuntimeTotal(lastValues[2].toInt() * 60);
    separate.setRuntimeTotal(lastValues[3].toInt() * 60);
  } else {
    Serial.println("No last values available.");
  }






  pinMode(commonStatePin, OUTPUT);
}


void loop() {
  static bool datasa = false; 
  bool commonState = HIGH;
  for (auto &i : indicator) {
    if (i.update() != Indicator::noPin) commonState = LOW; // if one button reports active, common Relay has to be switched off
  }
  digitalWrite(commonStatePin, commonState);

  if (!wifiConnected) {
    Serial.println("Lost connection to WiFi. Reconnecting...");
    connectToWiFi();
  }

  separate.update();

    
  server.handleClient();   // loop to handle incoming client requests

  checkRuntimeReset();

   struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    Serial.println("Failed to obtain time from internal RTC");
    return;
  }
  if (timeinfo.tm_hour == 23 && timeinfo.tm_min == 54 && !datasa) {
      saveDataToFile();
      datasa=true;
  }

if ( restr == true ) restart() ;

/*
  // Check if the specified interval has passed
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= 5000) {
    previousMillis = currentMillis;  // Update the previous time

    // Call your function here that needs to be executed every hour
    saveDataToFile();
    delay(1000);
  } 
  */
}

I need to modify it to e like this simulation

Explain what you want to do exactly, give an example of the expected output

Well, then use that code. The simulation code is right there. Copy it into your sketch.

Current code : when pressed a button it trigger corssponding led high and then caculate its on time while ignors other burtons state , and when current buttn depressed it chech for other pressed button to repeate same caculations. If no buttons pressed it trigger pin 5 high and calculate its on time, all led status and caculations updated every 2 sec. On web page.

Required modifications : allow pressing more than one button at same time and caculate on time serately for each led then update them on web page.

The simulation code is very simple while my code is comlicated and use different aproach for calculating time

wouldn't just recognizing that more that one button is being pressed (i.e. LOW) be sufficient?

or do you mean there needs to be a delay when checking if multiple buttons are pressed to allow time for all buttons to be pressed?

look this over

byte pinsBut [] = { A1, A2, A3 };
#define N_BUT   sizeof(pinsBut)

byte butState [N_BUT];

// -----------------------------------------------------------------------------
unsigned long msecPeriod;
unsigned long msecLst;

int
chkButtons ()
{
    unsigned long msec = millis ();
    if (msecPeriod && msec - msecLst > msecPeriod)  {
        msecPeriod = 0;

        int val = 0;
        for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
            val <<= 1;
            if (LOW == butState [n])
                val |= 1;
        }
        return val;
    }

    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        byte but = digitalRead (pinsBut [n]);

        if (butState [n] != but)  {
            butState [n] = but;

            if (LOW == but)  {
                msecPeriod = 500;
                msecLst    = msec;
                return 0;
            }
        }
    }
    return 0;
}

// -----------------------------------------------------------------------------
void
loop ()
{
    int val = chkButtons ();
    if (val)
        Serial.println (val);
}

// -----------------------------------------------------------------------------
void
setup ()
{
    Serial.begin (9600);

    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        pinMode (pinsBut [n], INPUT_PULLUP);
        butState [n] = digitalRead (pinsBut [n]);
    }
}

Get a perfect program working that calculates the on time for just one button.

Then go learn about array variables and for loops to exploit the same pattern. That will then handle N buttons. the only limit being the number of i/o pins you have. Five should be possible.

a7

1 Like

recognizing that more that one button is being pressed (i.e. LOW) be sufficient
Then calculate total time for each pressed button

recognizing that more that one button is being pressed (i.e. LOW) be sufficient
Then calculate total time for each pressed button

can't have it both ways. You're either running one sum, which increments when any button is pressed, or you're running four sums, one for each button, and displaying either the four numbers, the aggregate, or all five numbers. What is it?

looks like you want the duration that a button has currently been pressed. don't understand the how the LEDs are affected

looks this over

byte pinsBut [] = { A1, A2, A3 };
#define N_BUT   sizeof(pinsBut)

byte          butState [N_BUT];
unsigned long msecBut  [N_BUT];
unsigned long msec;

char s [80];

// -----------------------------------------------------------------------------
int
butScan ()
{
    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        byte but = digitalRead (pinsBut [n]);

        if (butState [n] != but)  {
            butState [n] = but;

            if (LOW == but)
                msecBut [n] = msec ? msec : 1;      // msec could be zero
            else
                msecBut [n] = 0;
        }
    }
    return 0;
}

// -----------------------------------------------------------------------------
unsigned long
butMsec (
    int n )
{
    if (! msecBut [n])
        return 0;
    return msec - msecBut [n];
}

// -----------------------------------------------------------------------------
void
loop ()
{
    msec = millis ();

    butScan ();
    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        unsigned long msecOn = butMsec (n);
        if (msecOn)  {
            sprintf (s, " but %u %8lu msec", n, msecOn);
            Serial.println (s);
        }
    }
}

// -----------------------------------------------------------------------------
void
setup ()
{
    Serial.begin (9600);

    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        pinMode (pinsBut [n], INPUT_PULLUP);
        butState [n] = digitalRead (pinsBut [n]);
    }
}

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