/*
SmartSwitch application
Based on ESP_AsyncFSBrowser
Temperature Control for heater with schedule
Main purpose - for winter outside car block heater or battery charger 
Wide browser compatibility, no other server-side needed.
HTTP server and WebSocket, single port  
Standalone, no JS dependencies for the browser from Internet (I hope)
Based on ESP_AsyncFSBrowser
Real Time (NTP) w/ Time Zones
Memorized settings to EEPROM
Multiple clients can be connected at same time, they see each other requests
Use latest ESP core lib (from Github)
*/

// Defaulut is SPIFFS, FatFS: only on ESP32
// Comment 2 lines below or uncomment only one of them

#define USE_LittleFS
//#define USE_FatFS // select partition scheme w/ ffat!


#define USE_WFM   // to use ESPAsyncWiFiManager
//#define DEL_WFM   // delete Wifi credentials stored 
                  //(use once then comment and flash again), also HTTP /erase-wifi can do the same live

// AUTH COOKIE uses only the password and unsigned long MY_SECRET_NUMBER

#define http_username "smart"
#define http_password "switch"

#define MY_SECRET_NUMBER  0xA217B02F

//See https://github.com/me-no-dev/ESPAsyncWebServer/pull/684
//SSWI or other 4 chars
#define USE_AUTH_COOKIE
#define MY_COOKIE_DEL "SSWI=;Max-Age=-1;Path=/;"
#define MY_COOKIE_PREF "SSWI="
#define MY_COOKIE_SUFF ";Max-Age=31536000;Path=/;"

#ifndef USE_AUTH_COOKIE             
  #define USE_AUTH_STAT   //Base Auth for stat, /commands and SPIFFSEditor
  //#define USE_AUTH_WS   //Base Auth also for WS, not very supported 
#endif

#include <ArduinoOTA.h>
#ifdef ESP32
 #include <FS.h>
 #ifdef USE_LittleFS
  #define HSTNM "ssw32-littlefs"
  #define MYFS LITTLEFS
  #include "LITTLEFS.h"
 #elif defined(USE_FatFS)
  #define HSTNM "ssw32-ffat"
  #define MYFS FFat
  #include "FFat.h"
 #else
  #define MYFS SPIFFS
  #include <SPIFFS.h>
  #define HSTNM "ssw32-spiffs"
 #endif
 #include <ESPmDNS.h>
 #include <WiFi.h>
 #include <AsyncTCP.h>
#elif defined(ESP8266)
 #ifdef USE_LittleFS
    #include <FS.h>
  #define HSTNM "ssw8266-littlefs"
    #define MYFS LittleFS
    #include <LittleFS.h> 
 #elif defined(USE_FatFS)
  #error "FatFS only on ESP32 for now!"
 #else
  #define HSTNM "ssw8266-spiffs"
  #define MYFS SPIFFS
 #endif
 #include <ESP8266WiFi.h>
 #include <ESPAsyncTCP.h>
 #include <ESP8266mDNS.h>
#endif

#include <ESPAsyncWebServer.h>
#ifdef USE_WFM 
 #include "ESPAsyncWiFiManager.h"
#endif 
#include <SPIFFSEditor.h>
#include <EEPROM.h>
#include <Ticker.h>
#include <DHT.h>

#ifdef USE_AUTH_COOKIE
 #include <stdint.h>
 #include "Xtea.h"
#endif


#define RTC_UTC_TEST 1577836800 // Some Date
#define MYTZ PSTR("EST5EDT,M3.2.0,M11.1.0")

#define EESC    100  // fixed eeprom address for sched choice
#define EECH    104  // fixed eeprom address to keep selected active channel, only for reference here 
#define EEBEGIN EECH + 1
#define EEMARK  0x5A
#define MEMMAX  2   // 0,1,2... last max index (only 3 channels)
#define EEALL   512

#define HYST 0.5 // C +/- hysteresis 

// DHT
#define DHTTYPE DHT22  // DHT 11 // DHT 22, AM2302, AM2321 // DHT 21, AM2301
#define DHTPIN 4       //D2

#define DHT_T_CORR -0.3 //Temperature offset compensation of the sensor (can be -)
#define DHT_H_CORR -2.2 //Humidity offset compensation of the sensor 

// SKETCH BEGIN MAIN DECLARATIONS

DHT dht(DHTPIN, DHTTYPE);

Ticker tim;
AsyncWebServer server(80); //single port - easy for forwarding 
AsyncWebSocket ws("/ws");

#ifdef USE_WFM 
 #ifdef USE_EADNS
  AsyncDNSServer dns;
 #else
  DNSServer dns;
 #endif

//Fallback timeout in seconds allowed to config or it creates an own AP, then serves 192.168.4.1 
 #define FBTO 120
 const char* fbssid = "FBSSW"; 
 const char* fbpassword = "FBpassword4";
 
#else
 const char* ssid = "MYROUTERSSD";
 const char* password = "MYROUTERPASSWD";
#endif 

const char* hostName = HSTNM;

// RTC
static timeval tv;
static time_t now;

// HW I/O
const int btnPin = 0; //D3
const int ledPin = 2; //D4

#ifdef ESP32
  #define LED_ON 0x1
  #define LED_OFF 0x0
#elif defined(ESP8266)
  #define LED_ON 0x0
  #define LED_OFF 0x1
#endif

int btnState = HIGH; 

// Globals
uint8_t count = 0;
uint8_t sched = 0; // automatic schedule
byte memch = 0;    // select memory "channel" to work with 
float t = 0;
float h = 0;
bool udht = false;
bool heat_enabled_prev = false;
bool ledState = LED_OFF;
bool ledOut = LED_OFF;

struct EE_bl { 
  byte memid;  //here goes the EEMARK stamp
  uint8_t hstart;
  uint8_t mstart;
  uint8_t hstop;
  uint8_t mstop;
  float tempe;
};

EE_bl ee = {0,0,0,0,0,0.1}; //populate as initial

// SUBS
void writeEE(){
  ee.memid = EEMARK;
  //EEPROM.put(EESC, sched); // only separately when needed with commit()
  //EEPROM.put(EECH, memch); // not need to store and retrieve memch
  EEPROM.put(EEBEGIN + memch*sizeof(ee), ee); 
  EEPROM.commit(); //needed for ESP8266?
}

void readEE(){
  byte ChkEE;
  if (memch > MEMMAX) memch = 0;
  EEPROM.get(EEBEGIN + memch*sizeof(ee), ChkEE);
  if (ChkEE == EEMARK){ //otherwise stays with defaults
    EEPROM.get(EEBEGIN  + memch*sizeof(ee), ee);
    EEPROM.get(EESC, sched);
    if (sched > MEMMAX + 1) sched = 0;
  }
}

void doOut(){
  if (ledOut != ledState){ // only if changed
    digitalWrite(ledPin, ledState); //consolidated here
    ledOut = ledState; //update
    if (ledState == LED_OFF){
        ws.textAll("led,ledoff");
        Serial.println(F("LED-OFF"));
    } else {
        ws.textAll("led,ledon");
        Serial.println(F("LED-ON"));
    }
  }
}

void showTime() 
{
  byte tmpch = 0;
  bool heat_enabled = false; 
  
  gettimeofday(&tv, nullptr);
  now = time(nullptr);
  const tm* tm = localtime(&now);
  ws.printfAll("Now,Clock,%02d:%02d,%d", tm->tm_hour, tm->tm_min, tm->tm_wday);
  if ((2==tm->tm_hour )&&(2==tm->tm_min)){
    configTzTime(MYTZ, "pool.ntp.org");
    Serial.print(F("Sync Clock at 02:02\n"));
  }
  Serial.printf("RTC: %02d:%02d\n", tm->tm_hour, tm->tm_min);

  if (sched == 0){ // automatic
      if ((tm->tm_wday > 0)&&(tm->tm_wday < 6)) tmpch = 0; //Mon - Fri
      else if (tm->tm_wday == 6) tmpch = 1; //Sat
      else if (tm->tm_wday == 0) tmpch = 2; //Sun
  } else { // manual
    tmpch = sched - 1; //and stays
  }

  if (tmpch != memch){ // update if different
    memch = tmpch;
    readEE();
    ws.printfAll("Now,Setting,%02d:%02d,%02d:%02d,%+2.1f", ee.hstart, ee.mstart, ee.hstop, ee.mstop, ee.tempe);    
  }
  
  // process smart switch by time and temperature
  uint16_t xmi = (uint16_t)(60*tm->tm_hour) + tm->tm_min; // max 24h = 1440min, current time
  uint16_t bmi = (uint16_t)(60*ee.hstart) + ee.mstart;    // begin in minutes
  uint16_t emi = (uint16_t)(60*ee.hstop) + ee.mstop;      // end in minutes

  if (bmi == emi) heat_enabled = false;
  else { //enable smart if different

    if (((bmi < emi)&&(bmi <= xmi)&&(xmi < emi))||
        ((emi < bmi)&&((bmi <= xmi)||(xmi < emi)))){
         heat_enabled = true;
       } else heat_enabled = false;
  }

  if (heat_enabled_prev){ // smart control (delayed one cycle)
    if (((t + HYST) < ee.tempe)&&(ledState == LED_OFF)){ // OFF->ON once
      ledState = LED_ON;
      ws.textAll("led,ledon");
    }
    if ((((t - HYST) > ee.tempe)&&(ledState == LED_ON))||(!heat_enabled)){ // ->OFF
      ledState = LED_OFF;
      ws.textAll("led,ledoff");
    }
    
    Serial.printf(ledState == LED_ON ? "LED ON" : "LED OFF");
    Serial.print(F(", Smart enabled\n"));
  }
  heat_enabled_prev = heat_enabled; //update 
}

void updateDHT(){
  float h1 = dht.readHumidity();
  float t1 = dht.readTemperature(); //Celsius or dht.readTemperature(true) for Fahrenheit
  if (isnan(h1) || isnan(t1)){
  Serial.println(F("Failed to read from DHT sensor!"));
   } else {
     h = h1 + DHT_H_CORR;
     t = t1 + DHT_T_CORR;
   }
}

void analogSample()
{      
  ws.printfAll("wpMeter,Arduino,%+2.1f,%2.1f,%d", t, h, heat_enabled_prev);
  Serial.printf("T/H.: %+2.1f°C/%2.1f%%,%d\n", t, h, heat_enabled_prev);
}

void checkPhysicalButton()
{
  if (digitalRead(btnPin) == LOW){
    if (btnState != LOW){     // btnState is used to avoid sequential toggles
      ledState = !ledState;
    }
    btnState = LOW;
  } else {
    btnState = HIGH;
  }
}

void mytimer(){
    ++count;          //200ms increments
    checkPhysicalButton();
    if ((count % 25) == 1){ // update temp every 5 seconds
      analogSample();
      udht = true;
    }
    if ((count % 50) == 0){ // update temp every 10 seconds
      ws.cleanupClients();
    }
    if (count >= 150){ // cycle every 30 sec
      showTime();
      count = 0;
    }
}

#ifdef USE_AUTH_COOKIE
 unsigned long key[4] = {0x01F20304,0x05060708,0x090a0b0c,0x0d0e0f00};
 Xtea x(key);

void encip(String &mtk, unsigned long token){
  unsigned long res[2] = {(unsigned long)random(0xFFFFFFFF),token};
  x.encrypt(res);
  char buf1[18];
  sprintf(buf1, "%08X_%08X",res[0],res[1]); //8 bytes for encryping the IP cookie 
  mtk = (String)buf1; 
}

unsigned long decip(const char *pch){
  unsigned long res[2] = {0,0};
  res[0] = strtoul(pch, NULL, 16);
  res[1] = strtoul(&pch[9], NULL, 16);
  x.decrypt(res);
  return res[1];
}

bool myHandshake(AsyncWebServerRequest *request){ // false will 401
   bool rslt = false;
   if (request->hasHeader("Cookie")){
    String cookie = request->header("Cookie");
    Serial.println(cookie);
    
    uint8_t pos = cookie.indexOf(MY_COOKIE_PREF);
    if (pos != -1){
      unsigned long ix = decip(cookie.substring(pos+5, pos+22).c_str());
      Serial.printf("Ask:%08X Got:%08X\n", MY_SECRET_NUMBER, ix);
      if (MY_SECRET_NUMBER == ix)
       rslt=true;
    } else rslt=false;
   } else rslt=false;
   Serial.printf(rslt ? "C-YES\n" : "C-NO\n");
   return rslt;
}
#endif

// server
void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
  if(type == WS_EVT_CONNECT){
    Serial.printf("ws[%s][%u] connect\n", server->url(), client->id());
    //client->printf("Hello Client %u :)", client->id());
    //client->ping();

    IPAddress ip = client->remoteIP();
    Serial.printf("[%u] Connected from %d.%d.%d.%d\n", client->id(), ip[0], ip[1], ip[2], ip[3]);
    showTime();
    analogSample();
    if (ledState == LED_OFF) ws.textAll("led,ledoff");
    else ws.textAll("led,ledon");
    
    ws.printfAll("Now,Setting,%02d:%02d,%02d:%02d,%+2.1f", ee.hstart, ee.mstart, ee.hstop, ee.mstop, ee.tempe);
    ws.printfAll("Now,sched,%d", sched);
    
  } else if(type == WS_EVT_DISCONNECT){
    Serial.printf("ws[%s][%u] disconnect\n", server->url(), client->id());
    ws.textAll("Now,remoff"); 
    
  } else if(type == WS_EVT_ERROR){
    Serial.printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data);
  } else if(type == WS_EVT_PONG){
    Serial.printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:"");
  } else if(type == WS_EVT_DATA){
    AwsFrameInfo * info = (AwsFrameInfo*)arg;
    String msg = "";
    if(info->final && info->index == 0 && info->len == len){
      //the whole message is in a single frame and we got all of it's data
      Serial.printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len);

      if(info->opcode == WS_TEXT){
        for(size_t i=0; i < info->len; i++){ //debug
          msg += (char) data[i];
        }
              if(data[0] == 'L'){ // LED
                if(data[1] == '1'){
                  ledState = LED_ON;
                  ws.textAll("led,ledon"); // for others
                }
                else if(data[1] == '0'){
                  ledState = LED_OFF;
                  ws.textAll("led,ledoff"); 
                }
                                  
              } else if(data[0] == 'T'){ // timeset
                  if (len > 11){
                    data[3] = data[6] = data[9] = data[12] = 0; // cut strings 
                    ee.hstart = (uint8_t) atoi((const char *) &data[1]);
                    ee.mstart = (uint8_t) atoi((const char *) &data[4]);
                    ee.hstop  = (uint8_t) atoi((const char *) &data[7]);
                    ee.mstop  = (uint8_t) atoi((const char *) &data[10]);
                    Serial.printf("[%u] Timer set %02d:%02d - %02d:%02d\n", client->id(), ee.hstart, ee.mstart, ee.hstop, ee.mstop);
                    writeEE();
                    memch = 255; // to force showTime()to send Setting
                    showTime();
                  }
              } else if(data[0] == 'W'){ // temperatureset
                  if (len > 3){
                    if (ee.tempe != (float) atof((const char *) &data[1])){
                     ee.tempe = (float) atof((const char *) &data[1]);
                     Serial.printf("[%u] Temp set %+2.1f\n", client->id(), ee.tempe);
                     writeEE();
                     memch = 255; // to force showTime()to send Setting
                     showTime();
                   }
                  }
              } else if ((data[0] == 'Z')&&(len > 2)){ // sched
                  data[2] = 0;
                  if (sched != (uint8_t) atoi((const char *) &data[1])){
                    sched = (uint8_t) atoi((const char *) &data[1]);
                    EEPROM.put(EESC, sched); //separately
                    EEPROM.commit(); //needed for ESP8266?
                    ws.printfAll("Now,sched,%d", sched);    
                    showTime();
                  }
              }
        
      } else {
        char buff[3];
        for(size_t i=0; i < info->len; i++){
          sprintf(buff, "%02x ", (uint8_t) data[i]);
          msg += buff ;
        }
      }
      Serial.printf("%s\n",msg.c_str());

      if(info->opcode == WS_TEXT)
        client->text("I got your text message");
      else
        client->binary("I got your binary message");
        
    } else {
      //message is comprised of multiple frames or the frame is split into multiple packets
      if(info->index == 0){
        if(info->num == 0)
          Serial.printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
        Serial.printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len);
      }

      Serial.printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT)?"text":"binary", info->index, info->index + len);

      if(info->opcode == WS_TEXT){
        for(size_t i=0; i < len; i++){
          msg += (char) data[i];
        }
      } else {
        char buff[3];
        for(size_t i=0; i < len; i++){
          sprintf(buff, "%02x ", (uint8_t) data[i]);
          msg += buff ;
        }
      }
      Serial.printf("%s\n",msg.c_str());

      if((info->index + len) == info->len){
        Serial.printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len);
        if(info->final){
          Serial.printf("ws[%s][%u] %s-message end\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
          if(info->message_opcode == WS_TEXT)
            client->text("I got your text message");
          else
            client->binary("I got your binary message");
        }
      }
    }
  }
}


// setup -----------------------------------

void setup(){
  Serial.begin(115200);
  //Serial.setDebugOutput(true);

//Wifi 
#ifdef USE_WFM 
  AsyncWiFiManager wifiManager(&server,&dns);
 #ifdef DEL_WFM
  wifiManager.resetSettings();
 #endif
  wifiManager.setTimeout(FBTO); // seconds to config or it creates an own AP, then browse 192.168.4.1
  if (!wifiManager.autoConnect(hostName)){  
    Serial.print(F("*FALLBACK AP*\n"));
    WiFi.mode(WIFI_AP);
    WiFi.softAP(fbssid, fbpassword);
 // MDNS.begin(fbssid);
 // MDNS.addService("http","tcp",80); // Core SVN 5179 use STA as default interface in mDNS (#7042)
  }
  
#else
// Manual simple STA mode to connect to known router
    //WiFi.mode(WIFI_AP_STA); // Core SVN 5179 use STA as default interface in mDNS (#7042)
    //WiFi.softAP(hostName);  // Core SVN 5179 use STA as default interface in mDNS (#7042)
  WiFi.mode(WIFI_STA);      // Core SVN 5179 use STA as default interface in mDNS (#7042)
  WiFi.begin(ssid, password);
  if (WiFi.waitForConnectResult() != WL_CONNECTED){
    Serial.print(F("STA: Failed!\n"));
    WiFi.disconnect(false);
    delay(1000);
    WiFi.begin(ssid, password);
  }
#endif 

  Serial.print(F("*CONNECTED* OWN IP:"));
  Serial.println(WiFi.localIP());
  
//DHT
  dht.begin();
  updateDHT(); //first reading takes time, hold here than the loop;

//Real Time
  time_t rtc = RTC_UTC_TEST;
  timeval tv = { rtc, 0 };     
                               //timezone tz = { 0, 0 };   //(insert) <#5194
  settimeofday(&tv, nullptr);  //settimeofday(&tv, &tz);   // <#5194
  configTzTime(MYTZ, "pool.ntp.org");
  
//MDNS (not needed)
  // MDNS.begin(hostName);
  // MDNS.addService("http","tcp",80); // Core SVN 5179 use STA as default interface in mDNS (#7042)
  
//I/O & DHT
  pinMode(ledPin, OUTPUT);
  pinMode(btnPin, INPUT_PULLUP);
  
//EE
  EEPROM.begin(EEALL);
  //EEPROM.get(EECH, memch); //current channel, no need
  readEE(); // populate structure if healthy 
  Serial.printf("Timer set %02d:%02d - %02d:%02d\n", ee.hstart, ee.mstart, ee.hstop, ee.mstop);
  Serial.printf("Temp set %+2.1f\n", ee.tempe);

//FS
#ifdef USE_FatFS
  if (MYFS.begin(false,"/ffat",3)){ //limit the RAM usage, bottom line 8kb + 4kb takes per each file, default is 10
#else
  if (MYFS.begin()){
#endif
    Serial.print(F("FS mounted\n"));
  } else {
    Serial.print(F("FS mount failed\n"));  
  }

#ifdef USE_AUTH_WS
  ws.setAuthentication(http_username,http_password);
#endif

#ifdef USE_AUTH_COOKIE
  ws.handleHandshake(myHandshake);
#endif

  ws.onEvent(onWsEvent);
  server.addHandler(&ws);

#ifdef ESP32
 #ifdef USE_AUTH_STAT
  server.addHandler(new SPIFFSEditor(MYFS, http_username,http_password));
 #elif defined(USE_AUTH_COOKIE)
  server.addHandler(new SPIFFSEditor(MYFS)).setFilter(myHandshake);
 #else
  server.addHandler(new SPIFFSEditor(MYFS));
 #endif
#elif defined(ESP8266)
 #ifdef USE_AUTH_STAT
  server.addHandler(new SPIFFSEditor(http_username,http_password,MYFS));
 #elif defined(USE_AUTH_COOKIE)
  server.addHandler(new SPIFFSEditor("","",MYFS)).setFilter(myHandshake);
 #else
  server.addHandler(new SPIFFSEditor("","",MYFS)); 
 #endif
#endif

#ifdef USE_AUTH_COOKIE
  server.on("/lg2n", HTTP_POST, [](AsyncWebServerRequest *request){
       
    String ckx;
    encip(ckx, MY_SECRET_NUMBER);  
    
    AsyncWebServerResponse *response;
    
    if(request->hasParam("lg0f",true)){
      response = request->beginResponse(200, "text/html;charset=utf-8", "<h1>Logged Out! <a href='/'>Back</a></h1>");
      response->addHeader("Cache-Control", "no-cache");
      response->addHeader("Set-Cookie", MY_COOKIE_DEL);
      
    } else if(request->hasParam("pa2w",true) && (String(request->getParam("pa2w",true)->value().c_str()) == String(http_password))){
      response = request->beginResponse(301);
      response->addHeader("Location", "/");
      response->addHeader("Cache-Control", "no-cache");
      response->addHeader("Set-Cookie",  MY_COOKIE_PREF + ckx + MY_COOKIE_SUFF);
      
    } else response = request->beginResponse(200, "text/html;charset=utf-8", "<h1>Wrong password! <a href='/'>Back</a></h1>");
    
    request->send(response);
  });
#endif

// below paths need individual auth ////////////////////////////////////////////////

  server.on("/free-ram", HTTP_GET, [](AsyncWebServerRequest *request){  // direct request->answer
#ifdef USE_AUTH_STAT
    if(!request->authenticate(http_username, http_password)) return request->requestAuthentication();
#endif
    
#ifdef ESP32
    request->send(200, "text/plain", String(ESP.getMinFreeHeap()) + ':' + String(ESP.getFreeHeap()) + ':'+ String(ESP.getHeapSize())); 
#else
    request->send(200, "text/plain", String(ESP.getFreeHeap())); 
#endif


#ifdef USE_AUTH_COOKIE
  }).setFilter(myHandshake);
#else
  });
#endif

  server.on("/get-time", HTTP_GET, [](AsyncWebServerRequest *request){
#ifdef USE_AUTH_STAT
      if(!request->authenticate(http_username, http_password)) return request->requestAuthentication();
#endif    
      if(request->hasParam("btime")){
       time_t rtc = (request->getParam("btime")->value()).toInt();
       timeval tv = { rtc, 0 };            
       settimeofday(&tv, nullptr);  
      }
       request->send(200, "text/plain","Got browser time ...");
#ifdef USE_AUTH_COOKIE
  }).setFilter(myHandshake);
#else
  });
#endif

  server.on("/hw-reset", HTTP_GET, [](AsyncWebServerRequest *request){
#ifdef USE_AUTH_STAT
    if(!request->authenticate(http_username, http_password)) return request->requestAuthentication();
#endif
      request->onDisconnect([](){
#ifdef ESP32
        ESP.restart();
#elif defined(ESP8266)
        ESP.reset();
#endif
      });
    request->send(200, "text/plain","Restarting ...");
#ifdef USE_AUTH_COOKIE
  }).setFilter(myHandshake);
#else
  });
#endif

  server.on("/erase-wifi", HTTP_GET, [](AsyncWebServerRequest *request){
#ifdef USE_AUTH_STAT
    if(!request->authenticate(http_username, http_password)) return request->requestAuthentication();
#endif
      request->onDisconnect([](){ 
#ifdef ESP32
/*          
        //https://github.com/espressif/arduino-esp32/issues/400#issuecomment-499631249 
        //WiFi.disconnect(true); // doesn't work on esp32, below needs #include "esp_wifi.h"
        wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); //load the flash-saved configs
        esp_wifi_init(&cfg); //initiate and allocate wifi resources (does not matter if connection fails)
        if(esp_wifi_restore()!=ESP_OK){
            Serial.print(F("WiFi is not initialized by esp_wifi_init "));
        } else {
            Serial.print(F("WiFi Configurations Cleared!"));
        }
*/        
        WiFi.disconnect(true, true); // Works on esp32
        ESP.restart();
#elif defined(ESP8266)
        WiFi.disconnect(true); 
        ESP.reset();
#endif
      });
    request->send(200, "text/plain","Erasing WiFi data ...");
#ifdef USE_AUTH_COOKIE
  }).setFilter(myHandshake);
#else
  });
#endif
  
// above paths need individual auth ////////////////////////////////////////////////

#ifdef USE_AUTH_COOKIE
  server.serveStatic("/", MYFS, "/").setDefaultFile("index.htm").setFilter(myHandshake);
  server.serveStatic("/", MYFS, "/login/").setDefaultFile("index.htm");
#else
  #ifdef USE_AUTH_STAT
    server.serveStatic("/", MYFS, "/").setDefaultFile("index.htm").setAuthentication(http_username,http_password);
  #else
    server.serveStatic("/", MYFS, "/").setDefaultFile("index.htm");
  #endif
#endif

  server.onNotFound([](AsyncWebServerRequest *request){  // nothing known
    Serial.print(F("NOT_FOUND: "));
    if (request->method() == HTTP_OPTIONS) request->send(200); //CORS
    else request->send(404);
  });

  DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");//CORS
  server.begin();
  
  //Timer tick
  tim.attach(0.2, mytimer); //every 0.2s

   //OTA
  ArduinoOTA.setHostname(hostName);
  ArduinoOTA.onStart([](){ 
    Serial.print(F("OTA Started ...\n"));
    MYFS.end(); // Clean FS
    ws.textAll("Now,OTA"); // for all clients
    ws.enable(false);
    ws.closeAll();
  });
  ArduinoOTA.begin();
} // setup end

// loop -----------------------------------
void loop(){
  if (udht){  // 5sec
    updateDHT();
    udht = false;
  }
  doOut();
  ArduinoOTA.handle();
}
