#include <SPI.h>
#include <SD.h>
//#include <SdFat.h>
//#include <sd_defines.h>
//#include <sd_diskio.h>
#include <Wire.h>
#include "RTClib.h"
#include "FS.h"
#include "SD.h"
#include "SPI.h"
#include <EEPROM.h>
#include <MechaQMC5883.h>
#include <math.h>
#include "MS5837.h"
 
#include <Arduino.h>
#include <U8g2lib.h>
#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif
 
 
#include <WiFi.h>             
#include <WebServer.h>
#include "SPIFFS.h"
#include "CSS.h"
 
#define ServerVersion "1.0"
String  webpage = "";
bool    SPIFFS_present = false;
 
const char ssid[]     = "******";
const char password[] = "******";
 
WebServer server(80);
 
MechaQMC5883 qmc;
RTC_DS1307 rtc;
MS5837 depthSensor;
 
 
 
//Input pin definition
//int const TMP36Pin = 4;  //the analog pin the TMP36's Vout (sense) pin is connected to the resolution is 10 mV/degree centigrade with a 500 mV offset to allow for negative temperatures
int const buttonPin = 27; //logger's press button attached to this pin
 
 
 
// LCD connection definition
U8G2_ST7920_128X64_F_SW_SPI u8g2(U8G2_R0,14,13,12); //128x64 st7920
//U8G2_ST7920_128X64_F_SW_SPI u8g2(U8G2_R0, /* clock=*/ 18, /* data=*/ 23, /* CS=*/ 15, /* reset=8*/ );
 
/* 
// Let's compare an average of 100
const byte averageCount = 100;  //max. 255, byte variable is used
 
// Variables for the Modified Moving Average
  double movingAverage = 0;
  double movingAverageSum = 0;
*/
 
// Timer variables
  unsigned long previousAutoLogMillis = 0;
  unsigned long currentAutoLogMillis = millis();
            int printInterval = 1000;  // print out measured results every 1000 msec
  unsigned long lastDebounceTime = 0;  // the last time the logger input button was toggled
            int debounceDelay = 10;    // the debounce time; increase if the output flickers
           
//Drunstate monitoring
  int DrunState = 0;
  unsigned int DrunTime = 0;
  byte DrunHour = 0;
  byte DrunMinutes = 0;
  byte DrunSecound = 0;
  unsigned int lastDrunTime = 0;
  unsigned int startTime = 0;
  //int thresholdTemp = 25;
  int thresholdDepth = 1;
  unsigned long previousdepthAreaMillis = 0;
  String formDrunTime;
  String formDate;
 
 
//surface area of the depth profile and avarege depth over time
double depthArea = 0;
double avgDepth = 0;
 
 
//magnetic sensor variables
  int x = 0;
  int y = 0;
  int z = 0;
  int azimuth = 0;
 
//magnetic sensor array for avarage calculation, stores 10 values
  const byte arraySize = 10;
  double myX[arraySize];
  double myY[arraySize];
  double myZ[arraySize];
  double myAzimuth[arraySize];
  double avgX = 0;
  double avgY = 0;
  double avgZ = 0;
  double avgAzimuth = 0;
 
 
//the current address in the EEPROM where you write the number of starts of the device
int addr = 0;
 
//number of device starts
byte starts = 0;
 
//chip pin for the datalogger shield, generally 4,10 or SS
  const byte chip = SS;
 
// logger button state definition
int buttonState = LOW;        // the current reading  the input button
int lastButtonState = LOW;   // the previous reading  the input button
 
 
void setup(void) {
 delay(1000);
 
// Open serial communications and wait for port to open:
  Serial.begin(115200);
  while (!Serial) ; // wait for serial port to connect. Needed for native USB port only
  Serial.println(F("Serial port connected"));
 
 
  WiFi.begin(ssid,password);
  while (WiFi.status() != WL_CONNECTED) { // Wait for the Wi-Fi to connect
    delay(250); Serial.print('.');
  }
  Serial.println("\nConnected to "+WiFi.SSID()+" Use IP address: "+WiFi.localIP().toString()); // Report which SSID and IP is in use
 
  if (!SPIFFS.begin(true)) {
    Serial.println("SPIFFS initialisation failed...");
    SPIFFS_present = false;
  }
  else
  {
    Serial.println(F("SPIFFS initialised... file access enabled..."));
    SPIFFS_present = true;
  }
 
 
// Join LCD display
  u8g2.begin();
  u8g2.setBusClock(500000);
 
//define pin modes
 // pinMode(TMP36Pin, INPUT);
  pinMode(buttonPin, INPUT);
 
//Join I2C bus
   Wire.begin();
   Serial.println(F("I2C bus connected"));
 
 
//QMC 5853 magnetic sensor initialization
    qmc.init();
  //qmc.setMode(Mode_Continuous,ODR_200Hz,RNG_2G,OSR_256);
    Serial.println(F("Magnetic sensor initialization..."));
 
//5837 pressure and temperature sensor initialization
    while (!depthSensor.init()) {
    depthSensor.setModel(MS5837::MS5837_30BA);
    depthSensor.setFluidDensity(997); // kg/m^3 (freshwater, 1029 for seawater)
     }
 
 
// read a byte  the current address of the EEPROM
  EEPROM.begin(512);
  starts = EEPROM.read(addr);
  starts = starts +1;
  EEPROM.write(addr, starts);
  EEPROM.commit();
  Serial.println(F("EEPROM data updated"));
  Serial.println(starts);
 
 
//SD card initialization
    Serial.println("SD card initialization...");
 if(!SD.begin()){
        Serial.println("Card Mount Failed");
        return;
    }
    uint8_t cardType = SD.cardType();
 
    if(cardType == CARD_NONE){
        Serial.println("No SD card attached");
        return;
    }
 
    Serial.print("SD Card Type: ");
    if(cardType == CARD_MMC){
        Serial.println("MMC");
    } else if(cardType == CARD_SD){
        Serial.println("SDSC");
    } else if(cardType == CARD_SDHC){
        Serial.println("SDHC");
    } else {
        Serial.println("UNKNOWN");
    }
 
    uint64_t cardSize = SD.cardSize() / (1024 * 1024);
    Serial.printf("SD Card Size: %lluMB\n", cardSize);
   
 
 
//create log files on SD card
    File dataFile = SD.open("/Log_" + String(starts) + ".csv", FILE_WRITE);
    dataFile.println("Date,Time,Depth,Temperature,X,Y,Z,Azimuth,AVG");
    Serial.println("Log_" + String(starts) + ".csv is created");
    File customFile = SD.open("/Dlog_" + String(starts) + ".csv", FILE_WRITE);
    customFile.println("Date,Time,Depth,Temperature,X,Y,Z,Azimuth,AVG");
    Serial.println("Dlog_" + String(starts) + ".csv is created");
 
/* 
  // Pre-load MMA
  for (int x=0; x < averageCount; x++)
  movingAverageSum = movingAverageSum + analogRead(TMP36Pin);
  //Serial.print(movingAverageSum);
  //Serial.println(" movingAverageSum ");
 
// Calculate inital average
  movingAverage = movingAverageSum / averageCount;
  //Serial.print(movingAverage);
  //Serial.println(" movingAverage ");
  //Serial.println("MMA preloaded");
*/
 
// Check if RTC is present
  Serial.println("RTC initialization...");
  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    while (1);
  }
 
  if (!rtc.isrunning()) {
    Serial.println("RTC is NOT running!");
    // following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
    //rtc.adjust(DateTime(2019, 2, 4, 21, 26, 30));
  }
 
  //Serial.println (F("Date,Time,Temperature,X,Y,Z,Azimuth, AVG"));
 
 
          // transfer internal memory to the display
  //----------------------------------------------------------------------   
  ///////////////////////////// Server Commands
  server.on("/",         HomePage);
  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");
 
//  delay(1000);
}
 
void loop(void) {
  DateTime now = rtc.now();
  server.handleClient(); // Listen for client connections
   
  // make a string for assembling the data to log:
  double dataDepth = 0;
  double dataAVG = 0;
   
  int dataYear = 0;
  int dataMonth = 0;
  int dataDay = 0;
  int dataHour = 0;
  int dataMinute = 0;
  int dataSecond = 0;
  int dataTemp = 0;
 
  int dataX = 0;
  int dataY = 0;
  int dataZ = 0;
  int dataAzimuth = 0;
 
   
//getting the voltage reading  the temperature sensor and append to the string:
//  int TMP36_Read = analogRead(TMP36Pin);
 
// read 5837 sensor data
  depthSensor.read();
 
// read the state of the logger button into a local variable:
  int reading = digitalRead(buttonPin);
 
 
// If the switch changed, due to noise or pressing:
  if (reading != lastButtonState) {
    // reset the debouncing timer
    lastDebounceTime = millis();
  }
 
/*
//Modified Moving Avarege method used to smooth sensor data
 
// each interation of loop, update moving average
// Get a new sample
// unsigned int currentValue = analogRead(analogPin);
 
// Remove previous movingAverage  the sum
  movingAverageSum = movingAverageSum - movingAverage;
 
// Replace it with the current sample
  movingAverageSum = movingAverageSum + TMP36_Read;
 
// Recalculate movingAverage
  movingAverage = movingAverageSum / averageCount;
  //Serial.print(movingAverage);
  //Serial.println(" calculated movingAverage ");
 
// analogRead values go  0 to 1023, analogWrite values  0 to 255
double voltage = movingAverage / 1023; // Gets you mV
  //Serial.print(voltage);
  //Serial.println(" voltage ");
 
// calculate the temperature
double temperatureC = (voltage - 0.5) * 100 ;  //ing  10 mv per degree wit 500 mV offset to degrees ((voltage - 500mV) / 10)
*/
 
// check Drun state according to the current temperature
  if (depthSensor.depth() < thresholdDepth){
    DrunState = LOW;
    startTime = millis()/1000;
    }
    else{
      DrunState = HIGH;
    }
 
//calculate Drun time
    if (DrunState == HIGH) {
        DrunTime = lastDrunTime + millis()/1000 - startTime; 
    }else{
      DrunTime;
      lastDrunTime = DrunTime;
    }
 
    DrunHour = DrunTime/3600;
    DrunMinutes = (DrunTime - (DrunHour * 3600))/60;
    DrunSecound = DrunTime - DrunHour * 3600 - DrunMinutes * 60;
    formDrunTime = String(DrunHour) + ":" + String(DrunMinutes) + ":" + String(DrunSecound);
 
//calculate average temperature based on Druntime. tempArea is the are below the temperature  vs time function
  if (millis() - previousdepthAreaMillis > printInterval){
    if (depthSensor.depth() >= thresholdDepth){
      depthArea = depthArea + depthSensor.depth() * (1000/printInterval);
    }
    else{
      depthArea;
    }
 
  if(depthArea>0){
    avgDepth = depthArea/DrunTime;
  }
  else{
    avgDepth = 0;
  }
 
  /*Serial.print(DrunTime);
  Serial.print(",");
  Serial.print(temperatureC, 1);
  Serial.print(",");
  Serial.print(tempArea);
  Serial.print(",");
  Serial.println(avgTemp,1);*/
   
// reset the millis clock
 previousdepthAreaMillis = millis();
}
 
 
//azimuth calculation  //is supporting float too
  qmc.read(&x, &y, &z,&azimuth);  //azimuth = qmc.azimuth(&y,&x);//you can get custom azimuth
 
 
//append temperature an ddepth data to the string:
  dataDepth = depthSensor.depth(),1;
  dataAVG = avgDepth,1;
  dataTemp = depthSensor.temperature(),1;
 
//append date and time data to the string:
  dataYear = now.year();
  dataMonth = now.month();
  dataDay = now.day();
  dataHour = now.hour();
  dataMinute = now.minute();
  dataSecond = now.second();
  formDate = (String(now.year()) + "." + String(now.month()) + "." + String(now.day()) + ".") ;
   
//append compass data to the string:
  dataX = x;
  dataY = y;
  dataZ = z;
  dataAzimuth = azimuth;
 
  // open the file. note that only one file can be open at a time,
  // so you have to close this one before opening another.
 
//    SdFile::dateTimeCallback(dateTime);
 
 
    currentAutoLogMillis = millis();
 
if (currentAutoLogMillis-previousAutoLogMillis >= printInterval){
     // reset the timer
    previousAutoLogMillis = millis();
 
    //open the log file for writing
  //writeFile(SD, "/GenLog.csv", "Date,Time,X,Y,Z,Azimuth,Druntime,AVG\n");
 
    File dataFile = SD.open("/Log_" + String(starts) + ".csv", FILE_APPEND);
 
       
  // if the file is opened write data into it:
  if (dataFile){ 
   
    dataFile.print(dataYear);
    dataFile.print(".");
    dataFile.print(dataMonth);
    dataFile.print(".");   
    dataFile.print(dataDay);
    dataFile.print(",");
    dataFile.print(dataHour);
    dataFile.print(":");
    dataFile.print(dataMinute);
    dataFile.print(":");   
    dataFile.print(dataSecond);
    dataFile.print(",");   
    dataFile.print(dataDepth);
    dataFile.print(",");
    dataFile.print(dataTemp);
    dataFile.print(",");   
    dataFile.print(dataX);
    dataFile.print(",");   
    dataFile.print(dataY);
    dataFile.print(",");   
    dataFile.print(dataZ);
    dataFile.print(",");   
    dataFile.print(dataAzimuth);
    dataFile.print(",");
    dataFile.print(DrunTime);
    dataFile.print(",");
    dataFile.println(dataAVG);
 
// close log file
    dataFile.close();
   
// print to the serial port too:
   Serial.print("Date: ");
   Serial.print(dataYear);
   Serial.print(F("."));
//   print2digits(dataMonth);
   Serial.print(dataMonth);
   Serial.print(F("."));
//   print2digits(dataDay);
   Serial.print(dataDay);
   Serial.print(F("."));
   Serial.print(" Time: ");   
 //  print2digits(dataHour);
   Serial.print(dataHour);
   Serial.print(F(":"));
 //  print2digits(dataMinute);
   Serial.print(dataMinute);
   Serial.print(F(":"));   
  // print2digits(dataSecond);
   Serial.print(dataSecond);
   Serial.print(F(","));
   Serial.print(" Depth: ");
   Serial.print(dataDepth,1);
   Serial.print(F(","));
 
   Serial.print(" Temperature: ");
   Serial.print(dataTemp,1);
   Serial.print(F(","));
   
   Serial.print(" xyz: "); 
   Serial.print(dataX);
   Serial.print(F(","));   
   Serial.print(dataY);
   Serial.print(F(","));   
   Serial.print(dataZ);
   Serial.print(F(","));
   Serial.print(" Azimuth: ");     
   Serial.print(dataAzimuth);
   Serial.print(" Druntime: ");
  // print2digits(DrunHour);
   Serial.print(DrunHour);
   Serial.print("h ");
  // print2digits(DrunMinutes);
   Serial.print(DrunMinutes);
   Serial.print("m ");
  // print2digits(DrunSecound);
   Serial.print(DrunSecound); 
   Serial.print("sec " );
   Serial.print(" AVG: ");
   Serial.print(dataAVG,1);
   Serial.println(F(","));
   
  /* Serial.print("Pressure: ");
   Serial.print(depthSensor.pressure());
   Serial.println(" mbar");
   Serial.print("Temperature: ");
   Serial.print(depthSensor.temperature());
   Serial.println(" deg C");
   Serial.print("Depth: ");
   Serial.print(depthSensor.depth());
   Serial.println(" m");*/
  }
  // if the file isn't open, pop up an error:
  else {
    Serial.println(F("error opening General log file"));
  }
 
 
 
// whatever the reading is at, it's been there for longer than the debounce delay, so take it as the actual current state:
 if (millis() - lastDebounceTime > debounceDelay) {
 
  // if the button state has changed:
    if (reading != buttonState) {
      buttonState = reading;
           
    // only toggle the LED if the new button state is HIGH
      if (buttonState == HIGH) {
 
 
    Serial.println(F("Custom log data: "));
   
     for (int i=0; i <= arraySize-1; i++){
            myX[i] = x;
     Serial.print(myX[i]);
     Serial.print(F(" "));
      }
     Serial.println(F(""));
       
      for (int i=0; i <= arraySize-1; i++){
            myY[i] = y;
     Serial.print(myY[i]);
     Serial.print(F(" "));
      }
     Serial.println(F(""));
 
      for (int i=0; i <= arraySize-1; i++){
            myZ[i] = z;
     Serial.print(myZ[i]);
     Serial.print(F(" "));
      }
     Serial.println(F(""));
     
      for (int i=0; i <= arraySize-1; i++){
            myAzimuth[i] = azimuth;
     Serial.print(myAzimuth[i]);
     Serial.print(F(" "));
      }
     Serial.println(F(""));
 
       
        avgX = 0;
        avgY = 0;
        avgZ = 0;
        avgAzimuth = 0;
        for (int i=0; i <= arraySize-1; i++){
            avgX = avgX + myX[i];
            avgY = avgY + myY[i];
            avgZ = avgZ + myZ[i];
            avgAzimuth = avgAzimuth + myAzimuth[i];
      }
 
        Serial.println(F("Avarage values: "));
        avgX = avgX/arraySize;
        avgY = avgY/arraySize;
        avgZ = avgZ/arraySize;
        avgAzimuth = avgAzimuth/arraySize;
       
        Serial.print(F("Avarage X value: "));
        Serial.println(avgX);
        Serial.print(F("Avarage Y value: "));
        Serial.println(avgY);
        Serial.print(F("Avarage Z value: "));
        Serial.println(avgZ);
        Serial.print(F("Avarage Azimuth value: "));
        Serial.println(avgAzimuth);
       
    //open the log file for writing
        File customFile = SD.open("/Dlog_" + String(starts) + ".csv", FILE_APPEND);
       
     // if the file is opened write data into it:     
             
        customFile.print(dataYear);
        customFile.print(".");
        customFile.print(dataMonth);
        customFile.print(".");   
        customFile.print(dataDay);
        customFile.print(",");
        customFile.print(dataHour);
        customFile.print(":");
        customFile.print(dataMinute);
        customFile.print(":");   
        customFile.print(dataSecond);
        customFile.print(",");   
        customFile.print(dataDepth);
        customFile.print(",");
        customFile.print(dataTemp);
        customFile.print(",");   
        customFile.print(dataX);
        customFile.print(",");   
        customFile.print(dataY);
        customFile.print(",");   
        customFile.print(dataZ);
        customFile.print(",");   
        customFile.println(dataAzimuth);
        customFile.print(",");   
        customFile.println(DrunTime);
        customFile.print(",");   
        customFile.println(dataAVG);       
   
       
 // close log file   
        customFile.close();
        Serial.println(F("Custom log written"));
 
    }
  }
 }
 
// save the reading. Next time through the loop, it'll be the lastButtonState
 lastButtonState = reading;   
 
}
 
// Define UI
  u8g2.clearBuffer();         // clear the internal memory
 
  //Draw UI frames
  /*u8g2.drawRFrame(0,0,41,24,3);
  u8g2.drawRFrame(43,0,42,24,3);
  u8g2.drawRFrame(87,0,41,24,3);
  u8g2.drawRFrame(0,26,41,24,3);
  u8g2.drawRFrame(43,26,42,24,3);
  u8g2.drawRFrame(87,26,41,24,3);
  u8g2.drawRFrame(0,52,85,12,3); */
  u8g2.drawCircle(106, 42, 20, U8G2_DRAW_ALL);
  u8g2.drawLine(106, 42, (106+17*sin(2*PI-dataAzimuth*PI/180)), (42-17*cos(2*PI-dataAzimuth*PI/180)));
  /*Serial.print("x   ");
  Serial.println(15*sin(dataAzimuth*2*PI/360));
  Serial.print("y   ");
  Serial.println(15*cos(dataAzimuth*2*PI/360));*/
 
  //Set menu font style and data
  u8g2.setFont(u8g2_font_micro_tr); // choose a suitable font
  u8g2.drawStr(5,7,"Druntime");
  u8g2.drawStr(46,7,"Depth / m");
  u8g2.drawStr(94,7,"Azimuth");
  u8g2.drawStr(0,33,"Temperature");
  u8g2.drawStr(57,33,"AVG");
 
         
  u8g2.setFont( u8g2_font_5x7_tr); // choose a suitable font 
  u8g2.setCursor(5,18);
  u8g2.print(formDrunTime);
  u8g2.setCursor(56,18);
  u8g2.print(depthSensor.depth(),1);
  u8g2.setCursor(100,18);
  u8g2.print(dataAzimuth);
  u8g2.setCursor(15,44);
  u8g2.print(depthSensor.temperature(),0);
  u8g2.setCursor(54,44);   
  u8g2.print(dataAVG,1);
  u8g2.setCursor(2,62);
  u8g2.print(formDate);
  u8g2.setCursor(58,62);
  u8g2.print("Log");
  u8g2.setCursor(76,62);
  u8g2.print(starts);
  u8g2.sendBuffer();
 
}
 
 
 
/* Additional functions */
/**************************************/
 
void print2digits(int number) {
  if (number >= 0 && number < 10) {
    Serial.write('0');
  }
    Serial.print(number);
}
 
 
/*
void dateTime(uint16_t* date, uint16_t* time) {
    DateTime now = rtc.now();
  // return date using FAT_DATE macro to format fields
  *date = FAT_DATE(now.year(), now.month(), now.day());
 
  // return time using FAT_TIME macro to format fields
  *time = FAT_TIME(now.hour(), now.minute(), now.second());
}
*/
 
// All supporting functions  here...
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void HomePage(){
  SendHTML_Header();
  webpage += F("<a href='/download'><button>Download</button></a>");
  webpage += F("<a href='/upload'><button>Upload</button></a>");
  //webpage += F("<a href='/stream'><button>Stream</button></a>");
  webpage += F("<a href='/delete'><button>Delete</button></a>");
  webpage += F("<a href='/dir'><button>Directory</button></a>");
  append_page_footer();
  SendHTML_Content();
  SendHTML_Stop(); // Stop is needed because no content length was sent
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void File_Download(){ // This gets called twice, the first pass s 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 Input("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> 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/ESP8266Web...r/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");
    }
  }
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#ifdef ESP32
void SPIFFS_dir(){
  if (SPIFFS_present) {
    File root = SPIFFS.open("/");
    if (root) {
      root.rewindDirectory();
      SendHTML_Header();
      //webpage += F("<h3 class='rcorners_m'>SD Card 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>");
      webpage += F("<a href='/'>[Back]</a><br><br>");
      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();
}
#endif
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#ifdef ESP8266
void SPIFFS_dir(){
  String str;
  if (SPIFFS_present) {
    Dir dir = SPIFFS.openDir("/");
    SendHTML_Header();
    webpage += F("<h3 class='rcorners_m'>SPIFFS Card Contents</h3><br>");
    webpage += F("<table align='center'>");
    webpage += F("<tr><th>Name/Type</th><th style='width:40%'>File Size</th></tr>");
    while (dir.next()) {
      Serial.print(dir.fileName());
      webpage += "<tr><td>"+String(dir.fileName())+"</td>";
      str  = dir.fileName();
      str += " / ";
      if(dir.fileSize()) {
        File f = dir.openFile("r");
        Serial.println(f.size());
        int bytes = f.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>";
        f.close();
      }
      str += String(dir.fileSize());
      str += "\r\n";
      Serial.println(str);
    }
    webpage += F("</table>");
    SendHTML_Content();
    append_page_footer();
    SendHTML_Content();
    SendHTML_Stop();   // Stop is needed because no content length was sent
  } else ReportSPIFFSNotPresent();
}
#endif
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void File_Stream(){
  if (server.args() > 0 ) { // Arguments were received
    if (server.hasArg("stream")) SPIFFS_file_stream(server.arg(0));
  }
  else Input("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  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 Input(" 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  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 Input(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 ion 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;
}
