<?php
<?php exit();         
  

/*
krutaya doka:  http://arduino.esp8266.com/versions/1.6.5-1160-gef26c5f/doc/reference.html

curl -F "file=@css/dropdown.css;filename=/css/dropdown.css" 192.168.4.1/update
server upload: https://github.com/jpswensen/ESP8266_WebGen/blob/master/AdvancedWebServerHuzzah_SPIFFS/AdvancedWebServerHuzzah_SPIFFS.ino

 // Функция ESP.deepSleep(microseconds, mode) // Прежим глубокого сна. Для аргумента mode WAKE_RF_DEFAULT, WAKE_RFCAL, WAKE_NO_RFCAL и WAKE_RF_DISABLED.
 // Контакт GPIO16 должен быть привязан к RST – чтобы вывести чип из режима глубокого сна.
 // Функция ESP.restart() Перезапускает процессор.
 // ADC_MODE(ADC_VCC); в самом начале

 * UPGRADE: https://github.com/julienrat/esp8266_update_from_spiffs/blob/master/update_from_spiffs.ino
 * 
 * UPDATE: https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino
 * 
 *  if (!f) {
      Serial.println("file open failed");
  }  Serial.println("====== Reading from SPIFFS file =======");
  // write 10 strings to file
  for (int i=1; i<=10; i++){
    String s=f.readStringUntil('\n');
    Serial.print(i);
    Serial.print(":");
    Serial.println(s);
  }
  stream.findUntil(char target, terminal)




  HTTP.on("/list",HTTP_GET, printDirectory);
  HTTP.on("/edit",HTTP_DELETE, handleDelete);
  HTTP.on("/edit",HTTP_PUT, handleCreate);
  HTTP.on("/edit",HTTP_POST, [](){ returnOK(); }, handleFileUpload);
  HTTP.onNotFound(handleNotFound);

*/


#include "FS.h"
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <ESP8266WebServer.h>
#include <DNSServer.h>
#include <Servo.h>

#include <ESP8266HTTPClient.h>
#include <ESP8266httpUpdate.h>

#include <Ticker.h>  //Ticker Library

const String DefaultConfig="\n"
"soft=default\n"
"autoupdate=yes\n"
"server_update=http://lleo.me/ESP8266/index.php?ip={ip}&chip={chip}&CHIP={CHIP}&soft={soft}\n"
"server_ping=http://home.lleo.me/ESP8266/index.php?ip={ip}&chip={chip}&CHIP={CHIP}&soft={soft}\n"
"AP_name=ESP8266-{chip}\n"
"AP_password=";

const String DefaultINDEX="<html><body><h1>Connect your WiFi for first time SPIFFS install</h1><p><form method='GET' action='/FM'>"
     "<input type='hidden' name='a' value='WIFIconn1'>"
     "<br>Login: <input type='text' name='net'>"
     "<br>Password: <input type='text' name='pass'>"
     "<br><input type='submit' value='Go'></form></body></html>";


#define DEFAULTPORT 80

String WIFICONNECT="";
String LOADED_CONFIG="NO";
String LOOPFILE="";

int8_t servopins[]={-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1};
Servo servo1,servo2,servo3,servo4,servo5,servo6,servo7,servo8,servo9,servo10,servo11;

Ticker tim;

unsigned int tim_count=0;
unsigned int tim_motor_detach=0;
unsigned int tim_LoopDelay=0;
// unsigned int tim_PingCounter=0;

void tim_engine() {
  tim_count++;

  if(tim_LoopDelay) tim_LoopDelay--;
  // if(tim_PingCounter) tim_PingCounter--;
  
  if(tim_motor_detach) {
    if(! --tim_motor_detach) {
      // Serial.println("\n --- detach all servo ---");
      for(uint8_t i=0;i<11;i++) {   if(servopins[i]!=-1) ServoDetach(String(i));    }
    }
  }
  
}

// ADC_MODE(ADC_VCC);

ESP8266WiFiMulti wifiMulti;
DNSServer DNS;
ESP8266WebServer WEB( DEFAULTPORT ); // CF0("WEB_PORT",DEFAULTPORT);

/*
void WiFiEvent(WiFiEvent_t event) {
    Serial.printf("\n\t\t[WiFi-event: %d", event);
    switch(event) {
        case WIFI_EVENT_STAMODE_GOT_IP: Serial.println(" Connected IP: "+ WiFi.localIP().toString()+ " ]"); break;
        case WIFI_EVENT_STAMODE_DISCONNECTED: Serial.println(" Lost connection ]"); break;
        default: Serial.println(" ]"); break;
    }
}
*/

void setup() {
  Serial.begin(115200);
  Serial.println("\n############### Restart #############");
  SPIFFS.begin();

  tim.attach(1,tim_engine); // 1 sec

  Serial.println(FullInfo());

  // WiFi.onEvent(WiFiEvent);
  WIFIinit();

  // init.txt start
  String start=CF("start"); if(start!="") { MOTO(start); } else { start=CF("startfile"); if(start!="") { MOTO(getfile(start)); } }

 // Serial.println("FILE `wifi_last.txt`:"+getfile("wifi_last.txt"));
 // Serial.println("MD5 `wifi_last.txt`:"+MD5file("wifi_last.txt"));

  load_loop("loop.txt");
  
// ================================================== попытка файла ==================================================

if(WIFICONNECT!="") {

  String s=CF("server_ping"); if(s!="") { String otv=file_get_contents(REPER(s)); Serial.println(" ["+otv+"]"); }
  
  if(CF0("autoupdate")) UpgradeALL();

}

// ================================================== попытка файла ==================================================

 WEB.on("/favicon.ico",HTTP_GET,WEBNAH);
 WEB.on("/generate_204",HTTP_GET,WEBNAH);
 WEB.on("/fwlink",HTTP_GET,WEBNAH);
 WEB.on("/v1/public/yql",HTTP_GET,WEBNAH);
 WEB.on("/gslb/",HTTP_GET,WEBNAH);
 WEB.on("/chat",HTTP_POST,WEBNAH);

 WEB.on("/time",HTTP_GET,[](){ idip("Time: "+String(tim_count)); });

 WEB.on("/reset",HTTP_GET,[](){ ESP.reset(); });
 WEB.on("/restart",HTTP_GET,[](){ ESP.restart(); });
 WEB.on("/format",HTTP_GET,[](){ SPIFFS.format(); });

 WEB.on("/PIN",HTTP_GET,[](){ String a=RE("a"); // Serial.print("\nPIN a="+a);

if(a=="servo") {
  String n=RE("n");
  String mode=RE("mode");
  String s=a+n+" "+mode;
  
  if(mode=="attach") {
    String d=RE("d");
    if(ServoAttached(n)) { ServoDetach(n); s+=" [detach] "; }
    s+" #"+d;
    if(ServoAttach(n,d.toInt())) return idip(s+" OK");
    return idip(s+"ERROR");
  }

  if(mode=="detach") {
    if( ! ServoAttached(n) ) return idip(s+=" [not attached]");
    ServoDetach(n);
    if( ServoAttached(n) ) return idip(s+" OK");
    return idip(s+" ERROR");
  }

  if(mode=="go") {
    String x=RE("x");
    ServoWrite(n,x.toInt());
    return idip(s+" TO: "+x);
  }

  return idip("Error mode");
}

if(a=="pinmode") {
  String d=RE("d");
  byte dd=(d=="A0" ? A0 : d.toInt() );
  String mode=RE("mode");
  String s=a+" #"+d+" ["+String(dd)+"] "+mode;
    if(mode=="OUTPUT") pinMode(dd,OUTPUT);
    else if(mode=="INPUT") pinMode(dd,INPUT); 
    else if(mode=="INPUT_PULLUP")  pinMode(dd,INPUT_PULLUP); 
    else return idip(s+" ERROR: unknown mode");
  return idip(s+" OK");
}

if(a=="pin") {
  String d=RE("d");
  String mode=RE("mode");
  String s=a+" #"+d+" "+mode;
  digitalWrite(d.toInt(),mode.toInt());
  return idip(s+" OK");
}

return idie("PIN error");
});




// File Manager System

WEB.on("/FM",HTTP_POST,[]() { String a=RE("a");

if(a=="editsave") {
    String file=RE("file"); file.replace("..","");
    String s=RE("plain");
    if(s=="") return idie("Error length 0");
    savefile(file,s);
    return otprav("clean('editor');salert('saved',300);");
}

/*

Serial.println("\nFM POST a="+a);
    
  HTTPUpload& upload = WEB.upload();

    if(!WEB.args()) Serial.println("NO ARGS");
    else {
        for(uint8_t i=0;i<WEB.args();i++) Serial.println("------> ["+WEB.argName(i)+"]=("+WEB.arg(i)+")");
    }
    return idie("UPLOAD_FILE_END");
    

  
  Serial.println("Upload status: "+String(upload.status));


  
  if (upload.status == UPLOAD_FILE_START) { Serial.println("2");
    
    String filename = upload.filename;

    Serial.printf("UPLOAD_FILE_START: %s\n", filename.c_str());
    // if(!filename.startsWith("/")) { filename = "/" + filename; }
    
    // DBG_OUTPUT_PORT.print("handleFileUpload Name: "); DBG_OUTPUT_PORT.println(filename);
    // fsUploadFile = SPIFFS.open(filename, "w");
    // filename = String();

return idie("UPLOAD_FILE_START");
    
  } else if (upload.status == UPLOAD_FILE_WRITE) {  Serial.println("3");
    //DBG_OUTPUT_PORT.print("handleFileUpload Data: "); DBG_OUTPUT_PORT.println(upload.currentSize);
    // if (fsUploadFile) { fsUploadFile.write(upload.buf, upload.currentSize); }

      Serial.printf("UPLOAD_FILE_WRITE: %u\n", upload.currentSize);

    return idie("UPLOAD_FILE_WRITE");
    
  } else if (upload.status == UPLOAD_FILE_END) {  Serial.println("4");
    // if (fsUploadFile) { fsUploadFile.close(); }
    // DBG_OUTPUT_PORT.print("handleFileUpload Size: "); DBG_OUTPUT_PORT.println(upload.totalSize);
    Serial.printf("UPLOAD_FILE_END: %u\n", upload.totalSize);


    if(!WEB.args()) Serial.println("NO ARGS");
    else {
        for(uint8_t i=0;i<WEB.args();i++) Serial.println("------> ["+WEB.argName(i)+"]=("+WEB.arg(i)+")");
    }
    return idie("UPLOAD_FILE_END");
    
  }

      else {  Serial.println("5");
        Serial.printf("UPLOAD_ZX\n");
        return idie("UPLOAD_XZ");
      }

  */
  
      
});

WEB.on("/FM",HTTP_GET,[](){ String a=RE("a"); Serial.print("\nFM a="+a);

if(a=="WIFIconn1") {
    savefile("wifi_last.txt",RE("net")+"\n"+RE("pass"));
    otprav("Reboot...");
    ESP.restart();
}

if(a=="WIFIconn") {
  String net=RE("net");
  String pass=RE("pass");
  String s="clean('wifipass'); idie('Reconnect to "+net+"');";
  otprav(s);
  savefile("wifi_last.txt",net+"\n"+pass);

  if(WIFItryConnect(net,pass)) {
    Serial.println("========> CONNECT to `"+net+"`");
    Serial.println("========> file `wifi_last.txt` saved: ");  Serial.println( getfile("wifi_last.txt") );
    return;
  }

  Serial.println("========> ERROR connect to `"+net+"`");
  WIFItryMyConnect();
  return idip("Error: Net: ["+net+"] / ["+pass+"]");
}

if(a=="WiFi") {
  String s="";
  int n=WiFi.scanNetworks();
  if(n == 0) return otprav("salert('No Networks found',3000);");
  
  for(int i=0; i<n; ++i) {
      s+="\n"+String(i+1)+": "+( WiFi.encryptionType(i) == ENC_TYPE_NONE ? " &nbsp;&nbsp; " : "<i class=e_zamok></i>" )+"&nbsp;<div class=ll onclick='WIFIconn"+( WiFi.encryptionType(i) == ENC_TYPE_NONE ? "" : "p" )+"(this.innerHTML)'>"+WiFi.SSID(i)+"</div> ("+WiFi.RSSI(i)+")";
  }
  return otprav("idie(toTable('"+String(n)+" networks found',\""+njs(s)+"\"))");
}

if(a=="Files") { // список файлов
    Dir dir = SPIFFS.openDir("");
    String s=""; while(dir.next()) {
      String n=h(String(dir.fileName()));
      File f=dir.openFile("r");
      s+=",'"+n+"':"+String(f.size(),DEC);
    }
    if(s=="") return otprav("salert('no files',2000)");
    return otprav("TabFiles({"+s.substring(1)+"})");
}

if(a=="Info") { return otprav("idie(toTable('System Info',\""+njs(FullInfo())+"\"))"); }
if(a=="Restart") { otprav("salert('Reboot... <img src=ajaxm.gif>',10000); setTimeout('document.location.reload(true)',3000);"); return ESP.restart(); }
if(a=="Upgrade") { return otprav("salert('Upgrade: "+String(UpgradeALL()?"OK":"ERROR")+"',2000)"); }



String file=RE("file"); file.replace("..","");

if(a=="update"||a=="upgrade") {
    file.replace(".",""); if(file=="") file="firmware";
    upgrade_url(CF("server_update")+file+".bin");
    return;
}

if(a=="edit") {
  String F=file; F.toUpperCase(); if(  F.endsWith(".JPG")||F.endsWith(".JPEG")||F.endsWith(".PNG")||F.endsWith(".GIF")||F.endsWith(".ICO")||F.endsWith(".SVG")) return idie("image","<img src='"+file+"'>");
  return otprav("EditFile(\""+njs(file)+"\",\""+njs(getfile(file))+"\")");
}

if(a=="save"){ return idie("Save: "+RE("file")); }

if(a=="del") { 
  delfile(file);
  return otprav("clean(\"file_"+h(file)+"\"); salert(\"Delete: "+h(file)+"\",1000);");
}
  
if(a=="upload") {
  String homeServer=CF("server_update");
  file.replace("/","");
  String o="";
  if(file=="all.list") { Serial.println("========> UPLOAD LIST: ["+homeServer+file+"]");
      String s=file_get_contents(homeServer+file);
      int i; while(i>=0) { i=s.indexOf("\n"); if(i<0) break;
        String fu=s.substring(0,i);
        o+="<br>"+fu+" ";
        if(fu!="" && ! fu.endsWith(".bin") && fu!="all.list") o+=file_upload_binary_o(homeServer+(homeServer.indexOf("?")>=0 ? "&file=" : "")+fu,fu);
        s=s.substring(i+1);
      }
  } else {
      Serial.println("========> UPLOAD BINARY: ["+homeServer+file+"]");
      o+="<br>"+file+" "+file_upload_binary_o(homeServer+(homeServer.indexOf("?")>=0 ? "&file=" : "")+file,file);
      if(file=="loop.txt") load_loop(file);
  }
  return otprav("salert(\"Updated:<br>"+o+"\",2000);");
}


return idie("FM error");

});



WEB.on("/AJAX",HTTP_GET,[](){ String a=RE("a"); Serial.print("\na="+a);

if(a=="run") {
  String s=RE("s");
  Serial.println(" RUN:"+s);
  MOTO(s);
  return otprav("");
}

if(a=="read") {
  uint8_t d=RE0("d");
  uint8_t x=digitalRead(d);
  Serial.println("Read #`"+String(d)+"`: "+x);
  return otprav(String(x));
}

if(a=="aread") {
  uint8_t d=A0; // RE0("d");
  uint16_t x=analogRead(d);
  Serial.println("Analog Read #`A0`: "+x);
  return otprav(String(x));
}

if(a=="servo") {
  String id=RE("id");
  uint8_t x=RE0("x");
  Serial.println("Servo #`"+id+"` to #`"+String(x)+"`");
  ServoWrite(id,x);
  return otprav("");
}

if(a=="MOTO") {
  String file=RE("file");
  Serial.println("MOTO: "+file);
  MOTO(getfile(file));
  return otprav("");
}

    //http://192.168.2.60/AJAX?a=MOTO&file=SVET.txt

/*
if(a=="text") {
  String momma = CF("startfile");
  return idie("ok: "+momma);
}
*/

idip("Error a="+a);

});

WEB.onNotFound([](){ WEBFILE(String(WEB.uri())); });

WEB.begin();
}

int Loopi=0,Loopi2=0;
void loop() {
  
  DNS.processNextRequest();
  WEB.handleClient();

  // if(PINGURL!="" && tim_PingCounter==0) {   tim_PingCounter=10;  }
  
  if(LOOPFILE!="" && tim_LoopDelay==0) {
        int loopstep=1;
        Loopi2=LOOPFILE.indexOf("\n",Loopi);
        if(Loopi2<0) { Loopi2=0; Loopi=0; }
        else {
          String M=LOOPFILE.substring(Loopi,Loopi2);
          if(M!="") {
              Serial.println("LOOP: ["+M+"]");
              if(ARG(M,0)=="sleep") tim_LoopDelay=ARG(M,1).toInt();
              else {
                loopstep=DOMOTO(M); // если wait или что-то еще ожидает
                Serial.println("LOOPSTEP == "+String(loopstep));
              }
          }
          if(loopstep) Loopi=++Loopi2;
        }
  }
}

// ====================================================================
String RE(String n) {
    if(!WEB.args()) return "";
    for(uint8_t i=0;i<WEB.args();i++) if(WEB.argName(i)==n) return WEB.arg(i);
    return "";   
}

int RE0(String n) { return RE(n).toInt(); }

void WEBFILE(String path) {
  String o = (WEB.method() == HTTP_GET )?"GET":"POST";
  o+=": "+path;
  if(WEB.args()) { o += "\nArguments:\n"; for(uint8_t i=0;i<WEB.args();i++) o+=" "+String(WEB.argName(i))+": "+String(WEB.arg(i))+"\n"; }
  Serial.print(o+"\n");
  
  if(handleFileRead(path)) return;

  if(WIFICONNECT!="") return WEBNAH();
          
  String l="http://"+WiFi.softAPIP().toString()+"/";
  Serial.println("Redirect: "+l);
  WEB.sendHeader("Location",l);
  WEB.send(302,"text/plain","Moved Permanently");
}

void WEBNAH() {
  WEB.sendHeader("Cache-Control","no-cache, no-store, must-revalidate");
  WEB.sendHeader("Pragma","no-cache");
  WEB.sendHeader("Expires","-1");
  WEB.send(404,"text/plain","404 Not Found");
}

bool handleFileRead(String path){
        // Serial.println("Load Web: `"+path+"`");
  if(path.endsWith("/")) path += "index.htm";
  if(inStopWEBList(path)) return 0;
  
  String pathWithGz = path + ".gz";
  if(!SPIFFS.exists(pathWithGz) && !SPIFFS.exists(path)) {
     Serial.println("Load Web NOT FOUND: `"+path+"`"); 
     if(path=="/index.htm") { Serial.println("INDEX.HTM: "+DefaultINDEX); WEB.send(200,"text/html",DefaultINDEX); return true; }
     return false;
  }
  if(SPIFFS.exists(pathWithGz)) path = pathWithGz;
  File file = SPIFFS.open(path, "r"); if(!file) return false;
  // ersetze server mit dem Variablennamen eures ESP8266 Webservers
  size_t sent = WEB.streamFile(file,getContentType(path));
  file.close();
  return true;
}


String MD5file(String file) {
    file.replace("/",""); file="/"+file;
    if(!SPIFFS.exists(file)) return "";
    File f = SPIFFS.open(file,"r"); if(!f) return "";
    MD5Builder md5;
    md5.begin();
    md5.addStream(f,f.size()); // md5.add("Test333");
    md5.calculate();
    f.close();
    return md5.toString();
}

String MD5(String s) {
    MD5Builder md5;
    md5.begin();
    md5.add(s.c_str());
    md5.calculate();
    return md5.toString();
}

unsigned long filesize(String file) {
    file.replace("/",""); file="/"+file;
    if(!SPIFFS.exists(file)) return 0;
    File f=SPIFFS.open(file,"r"); if(!f) return 0;
    return (unsigned long)f.size();
}

String getContentType(String filename){
  if(WEB.hasArg("download")) return "application/octet-stream";
  else if(filename.endsWith(".htm") || filename.endsWith(".html") ) return "text/html";
//  else if(filename.endsWith(".svg")) return "image/svg+xml";
  else if(filename.endsWith(".css")) return "text/css";
  else if(filename.endsWith(".js")) return "application/javascript";
  else if(filename.endsWith(".png")) return "image/png";
  else if(filename.endsWith(".gif")) return "image/gif";
  else if(filename.endsWith(".jpg")) return "image/jpeg";
  else if(filename.endsWith(".ico")) return "image/x-icon";
//  else if(filename.endsWith(".xml")) return "text/xml";
//  else if(filename.endsWith(".pdf")) return "application/x-pdf";
  else if(filename.endsWith(".zip")) return "application/x-zip";
  else if(filename.endsWith(".gz")) return "application/x-gzip";
  return "text/plain";
}


// String ipToString(IPAddress ip){ String s=""; for(int i=0; i<4; i++) { s += i ? ".":""; s+= String(ip[i]); } return s; }
void idip(String s) { Serial.println("\nIDIE: "+s); idie(s); }
void idie(String s) { WEB.send(200,"text/plain","helps('idie',\""+njsn(s)+"\")"); }
void idie(String hm,String s) { WEB.send(200,"text/plain","helps('"+hm+"',\""+njsn(s)+"\")"); }
void otprav(String s) { WEB.send(200,"text/plain",s); }

String njsn(String s) { s.replace("\n",""); s.replace("\r",""); s.replace("\"","\\\""); return s; }
String njs(String s) { s.replace("\n","\\n");  s.replace("\r",""); s.replace("\"","\\\""); return s; }
String h(String s) { s.replace("<","&lt;"); s.replace(">","&gt;"); return s; } 

String FullInfo() { return REPER(
      "\nChip: {CHIP}"
      "\nSoft: {soft}"
      "\nServer Update: {server_update}"
      "\nAutoupdate: {autoupdate}"

      "\nChipID/FlashID: {chip}/{FlashChipId} {CpuFreq} MHz"
      "\nPower: {vcc} mV"

      "\nWiFi: "+(WIFICONNECT==""?"not connected, setup mode":WIFICONNECT)+""
      "\n MAC: {macAddress}"
      "\n IP: {ip}:{WEB_PORT}"
      
      "\nServer: "
      "\n AP_name: {AP_name}"
      "\n AP_password: {AP_password}"
      "\n AP_MAC: {softAPmacAddress}"
      "\n soft_AP_IP: {softAPIP}"
      // "\n AP_mask: {AP_mask}"
      // "\n AP_ip: {AP_ip}"

"\nSketchSize: {SketchSize} MD5: {getSketchMD5}"
"\n FreeSketchSpace: {FreeSketchSpace}"
"\n FreeHeap: {FreeHeap}"
      
      "\nFlash Size: {FlashChipSize} / {FlashChipRealSize} / {FlashChipSizeByChipId} bytes"
      "\n Flash mode: {flashmode}"
      "\n FlashChipSpeed: {FlashChipSpeed}"

"\nCoreVersion: {CoreVersion}"
"\nFullVersion: {FullVersion}"
"\nBootVersion: {BootVersion}"
"\nBootMode: {BootMode}"
"\nResetReason: {ResetReason}"
"\nResetInfo: {ResetInfo}"
"\nCycles: {cycles}"

      "\nserver_ping: {server_ping}"
      "\nstartfile: {startfile}"
      "\nloopfile: {loopfile}"

"\nFiles {Files_count}: {Files}"
"\n"
 );
}

String REPER(String s) { String l;
 l="{ip}";  if(s.indexOf(l)>=0) s.replace(l,   WiFi.localIP().toString()  );
 
 l="{macAddress}";  if(s.indexOf(l)>=0) s.replace(l,   String(WiFi.macAddress())  );
 l="{softAPIP}";  if(s.indexOf(l)>=0) s.replace(l,   WiFi.softAPIP().toString()  );
 l="{softAPmacAddress}"; if(s.indexOf(l)>=0) s.replace(l,   String(WiFi.softAPmacAddress())  );
 l="{FreeHeap}"; if(s.indexOf(l)>=0) s.replace(l,   String(ESP.getFreeHeap(),DEC)  );
 l="{FreeSketchSpace}";  if(s.indexOf(l)>=0) s.replace(l,   String(ESP.getFreeSketchSpace(),DEC)  );
 l="{SketchSize}";  if(s.indexOf(l)>=0) s.replace(l,   String(ESP.getSketchSize(),DEC)  );
 
 l="{chip}";  if(s.indexOf(l)>=0) s.replace(l,   get_chip()  );
 l="{CHIP}";  if(s.indexOf(l)>=0) s.replace(l,   get_CHIP()  );
 
 l="{FlashChipId}";  if(s.indexOf(l)>=0) { String c=String(ESP.getFlashChipId(),HEX); c.toUpperCase(); s.replace(l,c);  }
 l="{FlashChipSize}";  if(s.indexOf(l)>=0) s.replace(l,   String(ESP.getFlashChipSize(),DEC)  );
 l="{FlashChipRealSize}";  if(s.indexOf(l)>=0) s.replace(l,   String(ESP.getFlashChipRealSize(),DEC)  );
 l="{FlashChipSpeed}";  if(s.indexOf(l)>=0) s.replace(l,   String(ESP.getFlashChipSpeed(),DEC)  );
 
 l="{cycles}";  if(s.indexOf(l)>=0) s.replace(l,   String(ESP.getCycleCount(),DEC)  );

l="{CoreVersion}";  if(s.indexOf(l)>=0) s.replace(l,   ESP.getCoreVersion()  );
l="{FullVersion}";  if(s.indexOf(l)>=0) s.replace(l,   ESP.getFullVersion()  );
l="{BootVersion}";  if(s.indexOf(l)>=0) s.replace(l,   String(ESP.getBootMode(),DEC)  );
l="{BootMode}";  if(s.indexOf(l)>=0) s.replace(l,   String(ESP.getCycleCount(),DEC)  );
l="{CpuFreq}";  if(s.indexOf(l)>=0) s.replace(l,   String(ESP.getCpuFreqMHz(),DEC)  );
l="{FlashChipSizeByChipId}";  if(s.indexOf(l)>=0) s.replace(l,   String(ESP.getFlashChipSizeByChipId(),DEC)  );
l="{getSketchMD5}";  if(s.indexOf(l)>=0) s.replace(l,   ESP.getSketchMD5()  );

l="{ResetReason}";  if(s.indexOf(l)>=0) s.replace(l,   ESP.getResetReason()  );
l="{ResetInfo}";  if(s.indexOf(l)>=0) s.replace(l,   ESP.getResetInfo()  );
 
 l="{flashmode}";  if(s.indexOf(l)>=0) { FlashMode_t m=ESP.getFlashChipMode(); s.replace(l,   String(m==FM_QIO?"QIO" : m==FM_QOUT?"QOUT" : m==FM_DIO?"DIO" : m==FM_DOUT ? "DOUT" : "UNKNOWN")  ); }
 l="{vcc}";  if(s.indexOf(l)>=0) { s.replace(l, String(ESP.getVcc(),DEC) ); }

 l="{Files}"; if(s.indexOf(l)>=0) { Dir dir=SPIFFS.openDir(""); String o=""; while(dir.next()) o+=" "+String(dir.fileName()); o.trim(); s.replace(l,o); }
 l="{Files_count}"; if(s.indexOf(l)>=0) { int k=0; Dir dir=SPIFFS.openDir(""); while(dir.next()) k++; s.replace(l,String(k)); }

int a=0,b;

while( 1 ) {
  a=s.indexOf("{FILE:"); if(a<0) break;
  b=s.indexOf("}",a+6); if(b<0) break;
  l=s.substring(a+6,b); l.trim();
  s.replace(s.substring(a,b+1),getfile(l));
}

a=0;
while( 1 ) {
  a=s.indexOf("{",a); if(a<0) break;
  b=s.indexOf("}",a+1); if(b<0) break;
  l=s.substring(a+1,b);
  String lz=CF(l);
  s.replace("{"+l+"}",lz);
  a+=lz.length();
 }

 return s;
}


bool delfile(String file) {
  file.replace("/",""); file="/"+file;
  uint8_t i=SPIFFS.remove(file);
  Serial.println("Delete file: "+file+" "+(i?"OK":"ERROR"));
  return i;
}

String getfile(String file) {
  file.replace("/",""); file="/"+file;
  File f=SPIFFS.open(file,"r"); if(!f) return "";
  String s=f.readString();
  f.close();
  return s;
}

void savefile(String file, String s) {
  file.replace("/",""); file="/"+file;
  // https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266HTTPClient/examples/StreamHttpClient/StreamHttpClient.ino

  File f=SPIFFS.open(file,"w"); if(!f) return;

char* data[120];
  
  if(file.endsWith(".jpg") || file.endsWith(".png")) {
      // void serial_send(int serial_fd, char *data, int size)
//    f.write(0,40);  // f,data,
//    f.write(0);  // f,data,
//    f.write(0);  // f,data,
//    f.write(0);  // f,data,
//    f.write(0);  // f,data,
    Serial.println("Can't write BINARY file: "+file); return;
    
  } else f.print(s);
  
  f.close();
}












bool WIFItryConnect(String ssid,String pass) {

//Serial.printf("Default hostname: %s\n", WiFi.hostname().c_str());
//WiFi.hostname("LLEO-TEST_02");
//Serial.printf("New hostname: %s\n", WiFi.hostname().c_str());

//   WiFi.mode(WIFI_STA);
//    Serial.print("\nWiFi.status: "); Serial.println(WiFi.status());

// Serial.printf("\n   Wi-Fi mode set to WIFI_STA %s\n", WiFi.mode(WIFI_STA) ? "" : "Failed!");
// Serial.printf("\n   WiFi.getMode(): %d\n", WiFi.getMode());
//    WiFi.mode(m): set mode to WIFI_AP, WIFI_STA, WIFI_AP_STA or WIFI_OFF
    // WiFi.getMode(): return current Wi-Fi mode (one out of four modes above)
// Serial.printf("\n   Connection status: %d\n", WiFi.status());

Serial.print("Try connecting to: "+ssid+" ");

/*
  if(WiFi.status() == WL_CONNECTED) { 
    Serial.print("Disconnecting WiFi"); WiFi.disconnect(false); WIFICONNECT=""; WiFi.disconnect(true); 
    delay(2000);
  }
  */

  WiFi.begin(ssid.c_str(),pass.c_str());

  for(uint8_t i=0;i<10;i++) {
    Serial.print(".");
    delay(500);
    if(WiFi.status() == WL_CONNECTED) {
        Serial.println("Connected \"" +ssid+ "\" IP: " + WiFi.localIP().toString() );
        WIFICONNECT=ssid;
        return 1;
    }

  if(WiFi.status() == WL_IDLE_STATUS) Serial.println("\n - - - when Wi-Fi is in process of changing between statuses");
  if(WiFi.status() == WL_NO_SSID_AVAIL) Serial.println("\n - - - in case configured SSID cannot be reached");
  if(WiFi.status() == WL_CONNECT_FAILED) Serial.println("\n - - - if password is incorrect");
  // if(WiFi.status() == WL_DISCONNECTED) Serial.println("\n - - - if module is not configured in station mode");
        
  }

  Serial.println(" ERROR connect to \"" +ssid+ "\"" );
  return 0;
}

bool WIFItryMyConnect() {
  String s=getfile("wifi_last.txt");
  if(s!="") {
    int i=s.indexOf("\n");
    String ssid = s.substring(0,i++);
    String pass = s.substring(i);
    Serial.println("Try to connect last WiFi: "+ssid);
    if( WIFItryConnect(ssid,pass) ) return 1;
  }
  return 0;
}

bool WIFIinit() {

  if(WIFItryMyConnect()) return 1;

  if(WIFItryConnect("LLeoNet","trololo123")) return 1; // резервная сетка


  // перепробовать все известные нам сети

/*
  String s=getfile("wifi_list.txt"); Serial.println("\nLoad: "+s);
  if(s!="") {
      int i=0,i0=0;
      String wifi_net="";
      String wifi_pass="";
      while(i>=0) {
        i=s.indexOf("\n\n",i); if(i<0) break;
        i0=s.indexOf("\n",i+=2); if(i0<0) break;
        wifi_net=s.substring(i,i0);
        wifi_pass=s.substring(i0+=1,s.indexOf("\n",i0));
        Serial.println("NETWORK: ["+wifi_net+"] PASSWORD: ["+wifi_pass+"]");  
        wifiMulti.addAP("ssid_from_AP_1", "your_password_for_AP_1");
      }

  Serial.println("Connecting Wifi list...");
  if(wifiMulti.run() == WL_CONNECTED) {
       Serial.println("\nWiFi network \""+WiFi.SSID()+"\" connected IP: "+WiFi.localIP().toString() );
       return 1;
     }
  }
*/

  // а иначе - создать точку доступа свою
  String AP_name=CF("AP_name");
  String AP_password=CF("AP_password");
  // String AP_mask=CF("AP_mask");
  // String AP_ip=CF("AP_ip");
  Serial.println("Create WiFi-network '"+AP_name+"' password: '"+AP_password+"'"); // : IP:"+AP_ip+" MASK:"+AP_mask);
  
  WiFi.mode(WIFI_AP);
  // IPAddress ipsmask( ARG(AP_mask,0,".").toInt() , ARG(AP_mask,1,".").toInt() , ARG(AP_mask,2,".").toInt() , ARG(AP_mask,3,".").toInt() );
  // IPAddress ips( ARG(AP_ip,0,".").toInt() , ARG(AP_ip,1,".").toInt() , ARG(AP_ip,2,".").toInt() , ARG(AP_ip,3,".").toInt() );
  // WiFi.softAPConfig(ips,ips,ipsmask);

  if(AP_password!="") WiFi.softAP(AP_name.c_str(),AP_password.c_str()); else WiFi.softAP(AP_name.c_str());
  delay(500); // Without delay I've seen the IP address blank
  Serial.println("Connected: "+WiFi.softAPIP().toString() );
  DNS.setErrorReplyCode(DNSReplyCode::NoError);
  DNS.start(53,"*",WiFi.softAPIP());

  Serial.println("\n\n"+FullInfo());
    
  return 0;
}


String get_CHIP() { String l=CF("NAME"); return (l!="" ? l : get_chip() ); }
String get_chip() { String l=String(ESP.getChipId(),HEX); l.toUpperCase(); return l; }


String file_upload_binary_o(String url,String file) {
  int i=file_upload_binary(url,file);
  if(i!=0) return "<font color=red>ERROR #"+String(i)+"</font>";
  return "<font color=green>OK</font>";
}

int8_t file_upload_binary(String url,String file) { if(inStopList(file)) return 0;
  int8_t res=0;
  HTTPClient http;
  http.setTimeout(5000);   
  http.begin(url.c_str());
  // Serial.println("File_UPLOAD_BINARY_contents: "+url+" ");
  int c = http.GET();
  if(c == HTTP_CODE_OK) {

        file.replace("/",""); file="/"+file;
        // https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266HTTPClient/examples/StreamHttpClient/StreamHttpClient.ino
        File f=SPIFFS.open(file,"w"); if(!f) res=-3;
        else {

       int len = http.getSize();
        // create buffer for read
       uint8_t buff[128] = { 0 };

        // get tcp stream
        WiFiClient * stream = http.getStreamPtr();

        // read all data from server
        while (http.connected() && (len > 0 || len == -1)) {
          
          // get available data size
          size_t size = stream->available();
          
          if(size) {
            // read up to 128 byte
            int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size));
            // write it to f
            f.write(buff, c);
            if(len > 0) len -= c;
          }
          delay(1);
        }
    } f.close();
  } else { res=-1; Serial.printf("\terror #%d: %s\n", c, http.errorToString(c).c_str()); }
  http.end();
  return res;
}






String file_get_contents(String url) {
  HTTPClient http;
  http.setTimeout(5000);
  http.begin(url.c_str());
  Serial.println("File_get_contents: ["+url+"]");
  int c = http.GET();
  String s="";
  if(c == HTTP_CODE_OK) s=http.getString();
  else Serial.printf("\terror #%d: %s\n", c, http.errorToString(c).c_str());
  http.end();
  return s;
}


bool ServoDetach(String n) {
      if(!ServoAttached(n)) return 0;
      uint8_t ni=servopins[n.toInt()];
      if(n=="1") { servo1.detach();/* pinMode(ni,OUTPUT); digitalWrite(ni,0);*/ }
      else if(n=="2") { if(ni!=-1) servo2.detach(); /*pinMode(ni,OUTPUT); digitalWrite(ni,0);*/ }
      else if(n=="3") { if(ni!=-1) servo3.detach(); /*pinMode(ni,OUTPUT); digitalWrite(ni,0);*/ }
      else if(n=="4") { if(ni!=-1) servo4.detach(); /*pinMode(ni,OUTPUT); digitalWrite(ni,0);*/ }
      else if(n=="5") { if(ni!=-1) servo5.detach(); /*pinMode(ni,OUTPUT); digitalWrite(ni,0);*/ }
      else if(n=="6") { if(ni!=-1) servo6.detach(); /*pinMode(ni,OUTPUT); digitalWrite(ni,0);*/ }
      else if(n=="7") { if(ni!=-1) servo7.detach(); /*pinMode(ni,OUTPUT); digitalWrite(ni,0);*/ }
      else if(n=="8") { if(ni!=-1) servo8.detach(); /*pinMode(ni,OUTPUT); digitalWrite(ni,0);*/ }
      else if(n=="9") { if(ni!=-1) servo9.detach(); /*pinMode(ni,OUTPUT); digitalWrite(ni,0);*/ }
      else if(n=="10") { if(ni!=-1) servo10.detach(); /*pinMode(ni,OUTPUT); digitalWrite(ni,0);*/ }
      else if(n=="11") { if(ni!=-1) servo11.detach(); /*pinMode(ni,OUTPUT); digitalWrite(ni,0);*/ }
      else Serial.print(" error N");
      return ServoAttached(n);
}

bool ServoAttach(String n, uint8_t d) {
      uint8_t i=n.toInt(); if(i<1 || i>6) { Serial.println(" error N"); return -1; }
      if(ServoAttached(n)) return 1;
      if(n=="1") servo1.attach(d);
      else if(n=="2") servo2.attach(d);
      else if(n=="3") servo3.attach(d);
      else if(n=="4") servo4.attach(d);
      else if(n=="5") servo5.attach(d);
      else if(n=="6") servo6.attach(d);
      else if(n=="7") servo7.attach(d);
      else if(n=="8") servo8.attach(d);
      else if(n=="9") servo9.attach(d);
      else if(n=="10") servo10.attach(d);
      else if(n=="11") servo11.attach(d);
      servopins[i]=d;
      tim_motor_detach=3;
      return ServoAttached(n);
}

void ServoWrite(String n, int x) {
      int i=n.toInt(); if(i<1 || i>6) { Serial.println(" error N"); return; }
      tim_motor_detach=2;
      ServoAttach(n,servopins[i]);
        Serial.println("... ServoWrite("+n+":"+String(servopins[i])+") = "+String(x));
      if(n=="1") servo1.write(x);
      else if(n=="2") servo2.write(x);
      else if(n=="3") servo3.write(x);
      else if(n=="4") servo4.write(x);
      else if(n=="5") servo5.write(x);
      else if(n=="6") servo6.write(x);
      else if(n=="7") servo7.write(x);
      else if(n=="8") servo8.write(x);
      else if(n=="9") servo9.write(x);
      else if(n=="10") servo10.write(x);
      else if(n=="11") servo11.write(x);
}

bool ServoAttached(String n) {
    int i=-1;
    if(n=="1") i=servo1.attached();
    else if(n=="2") i=servo2.attached();
    else if(n=="3") i=servo3.attached();
    else if(n=="4") i=servo4.attached();
    else if(n=="5") i=servo5.attached();
    else if(n=="6") i=servo6.attached();
    else if(n=="7") i=servo7.attached();
    else if(n=="8") i=servo7.attached();
    else if(n=="9") i=servo8.attached();
    else if(n=="10") i=servo10.attached();
    else if(n=="11") i=servo11.attached();
    else { Serial.print(" error N"); return 0; }
    // Serial.println("\n ... ServoAttach("+n+") = "+i);
    return i;
}

void MOTO(String s) { if(s=="") return; s.replace(";","\n");
  int i; while(i>=0) {
        i=s.indexOf("\n"); if(i<0) { if(s.length()) DOMOTO(s); break; }
        DOMOTO(s.substring(0,i));
        s=s.substring(i+1);
  }
}

bool DOMOTO(String s) { s=nocomments(s); s.trim(); if(s=="") return 1; String cmd=ARG(s,0);

  if(cmd=="stop") { LOOPFILE=""; Serial.println("STOP LOOP"); return 1; }
  if(cmd=="loop") { String a=ARG(s,1); if(a=="") a="loop.txt"; Serial.println("START LOOP: "+a); load_loop(a); return 1; }
  if(cmd=="ping") { String url=REPER(ARG(s,1)); String otv=file_get_contents(url); Serial.println("PING: " + url + " = ["+otv+"]"); MOTO(otv); return 1; }
  
  if(cmd=="attach") { ServoAttach( ARG(s,1), ARG(s,2).toInt() ); return 1; }
  if(cmd=="detach") { ServoDetach( ARG(s,1) ); return 1; }
  if(cmd=="go") { ServoWrite( ARG(s,1), ARG(s,2).toInt() ); return 1;  }

  if(cmd=="pinmode") {
      uint8_t d=ARG(s,1).toInt();
      String mode=ARG(s,2);
      if(mode=="INPUT_PULLUP") pinMode(d,INPUT_PULLUP);
      else if(mode=="INPUT") pinMode(d,INPUT);
      else pinMode(d,OUTPUT);
      return 1;
  }

  if(cmd=="pin") { digitalWrite(ARG(s,1).toInt(),ARG(s,2).toInt()); return 1; }

  if(cmd=="delay") { delay(ARG(s,1).toInt()); return 1; }

  if(cmd=="run") { MOTO(getfile(ARG(s,1))); return 1; }

  if(cmd=="wait") { // wait 2 < 10
    uint8_t port=ARG(s,1).toInt();
    String z=ARG(s,2);
    uint16_t v=ARG(s,3).toInt();
    uint16_t x;
    bool e;
 
    if(z == "=") { x=digitalRead(port); e=(x == v ? 0 : 1 ); }
    else if(z == "!=") { x=digitalRead(port); e=(x != v ? 0 : 1 ); }
    else {
      x=analogRead(A0);
      if(z == ">") e=(x > v ? 0 : 1 );
      else if(z == ">=") e=(x >= v ? 0 : 1 );
      else if(z == "<") e=(x < v ? 0 : 1 );
      else if(z == "<=") e=(x <= v ? 0 : 1 );
      else { Serial.println("Error WAIT: "+s); return 1; }
    }

        // Serial.println(" . . . . WAIT: port `"+String(1*port)+"` ["+String(x)+"] "+z+" ("+String(v)+") --> "+String(e) );
        return e;
  }

  MOTO(getfile(cmd));
  return 1;

}


String ARG(String s,int n,String sep) { int i=0;for(int j=0;j<n;j++) { i=s.indexOf(sep,i)+1; if(i<=0) return ""; } return s.substring(i,s.indexOf(sep,i+1)); }
String ARG(String s,int n) { return ARG(s,n," "); }

/*
String d=RE("d");
    if(ServoAttached(n)) { ServoDetach(n); s+=" [detach] "; }
    s+" #"+d;
    if(ServoAttach(n,d.toInt())) return idip(s+" OK");
*/


String nocomments(String s) { int j=s.indexOf("#"); if(j>=0) { s=s.substring(0,j); s.trim(); } return s; }

String CF(String v,String D) {
  if(LOADED_CONFIG=="NO") {
     LOADED_CONFIG=getfile("config.txt");
     if(LOADED_CONFIG=="") LOADED_CONFIG=DefaultConfig;
     LOADED_CONFIG.replace("\t"," "); LOADED_CONFIG.replace("\r","");
     LOADED_CONFIG=LOADED_CONFIG+"\n";
  }
        int i=0,i2; while(i>=0) { i2=LOADED_CONFIG.indexOf("\n",i); if(i2<0) break;
            String c=LOADED_CONFIG.substring(i,i2); i=++i2;
            c=nocomments(c); if(c=="") continue;
            int r=c.indexOf("="); if(r<0) continue;
            String n = c.substring(0,r); n.trim();
            if(n!=v) continue;
            c=c.substring(r+1); c.trim();
            if(c.indexOf("{")>=0) c=REPER(c);
            return c;
      }
      return D;
}
int CF0(String v,int D) { String c=CF(v,""); c.toUpperCase(); if(c=="NO"||c=="FALSE") c="0"; else if(c=="YES"||c=="TRUE") c="1"; return (c==""?D:c.toInt()); }

String CF(String v) { return CF(v,""); }
int CF0(String v) { return CF0(v,0); }

void load_loop(String s) { Loopi=0; Loopi2=0; tim_LoopDelay=0; LOOPFILE=CF("loop"); if(LOOPFILE!="") LOOPFILE.replace(";","\n"); else {
  // String s=CF("loopfile"); if(s!="")
  LOOPFILE=getfile(s); } if(LOOPFILE!="") LOOPFILE+="\n"; }

String StopWebList="NO";
String StopList="NO";

bool inStopWEBList(String file) { if(StopWebList=="NO") StopWebList=getfile("stopweblist.txt"); if(StopWebList=="") return 0; return inStop(StopWebList,file); }
bool inStopList(String file) { if(StopList=="NO") StopList=getfile("stoplist.txt"); if(StopList=="") return 0; return inStop(StopList,file); }
bool inStop(String s,String file) { file.replace("/",""); file="/"+file;
  int i=0; while(1) {
     String l=ARG(s,i++,"\n"); if(l=="") return 0;
     if(file.startsWith(l)) { Serial.println("Deprecated file: `"+file+"`"); return 1; }
  }
  return 0;
}


bool upgrade_url(String file) {
    if(inStopList(file)) return 0;
    t_httpUpdate_return ret;
    Serial.println("Update new sketch "+file);

     pinMode(2,OUTPUT);digitalWrite(2,0); // ВКЛЮЧИТЬ ЛАМПУ
    
    ESPhttpUpdate.rebootOnUpdate(true);
    ret = ESPhttpUpdate.update(file);

     digitalWrite(2,1); // ПОГАСИТЬ ЛАМПУ
    
       switch(ret) {
          case HTTP_UPDATE_FAILED: {
              String err=ESPhttpUpdate.getLastErrorString();
              if(err.indexOf("reset")>=0) { idip("Press RESET and repeat!"); ESP.reset(); break; }
              idip("UPDATE ERROR ["+file+"]: "+err);
              break;
          }
          case HTTP_UPDATE_NO_UPDATES: idip("HTTP_UPDATE_NO_UPDATES"); break;
          case HTTP_UPDATE_OK: idip("HTTP_UPDATE_OK"); break;
          default: idip("OTHER"); break;
      }
}

bool UpgradeALL() {
    String s=CF("server_update"); if(s=="") return 0;
    String otv=file_get_contents(REPER(s)); //  Serial.print(" ["+otv+"]");
    if(ARG(otv,0,"\n")!="OK") return 0;
    
    String firmware_md5="";
    int i=1; while(1) {
          String s=ARG(otv,i++,"\n"); if(s=="") break;
          String file=ARG(s,0," ");
          String md5=ARG(s,1," ");
          if(file=="firmware.bin") { firmware_md5=md5; continue; }
          if(MD5file(file)==md5) continue;
          Serial.print("UPDATE "+file+": ");
          int i=file_upload_binary(CF("server_update")+"&file="+file,file); if(i==0 && MD5file(file)==md5) Serial.println("OK"); else Serial.println("ERROR #"+String(i));
        }
     if(firmware_md5 != "" && firmware_md5 != ESP.getSketchMD5() ) { Serial.println("firmware.bin need to UPDATE"); upgrade_url(CF("server_update")+"&file=firmware.bin"); }
     return 1;
}