/* webserver.ino - webserver for Sonoff-Tasmota Copyright (C) 2017 Theo Arends This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef USE_WEBSERVER /*********************************************************************************************\ * Web server and WiFi Manager * * Enables configuration and reconfiguration of WiFi credentials using a Captive Portal * Source by AlexT (https://github.com/tzapu) \*********************************************************************************************/ #define STR_HELPER(x) #x #define STR(x) STR_HELPER(x) const char HTTP_HEAD[] PROGMEM = "" "" "" "" "{v}" "" "" "" "" "
" "

{ha} Module

{h}

"; const char HTTP_SCRIPT_CONSOL[] PROGMEM = "var sn=0;" // Scroll position "var id=99;" // Get most of weblog initially "function l(p){" // Console log and command service "var c,o,t;" "clearTimeout(lt);" "o='';" "t=document.getElementById('t1');" "if(p==1){" "c=document.getElementById('c1');" "o='&c1='+encodeURI(c.value);" "c.value='';" "t.scrollTop=sn;" "}" "if(t.scrollTop>=sn){" // User scrolled back so no updates "if(x!=null){x.abort();}" // Abort if no response within 2 seconds (happens on restart 1) "x=new XMLHttpRequest();" "x.onreadystatechange=function(){" "if(x.readyState==4&&x.status==200){" "var z,d;" "d=x.responseXML;" "id=d.getElementsByTagName('i')[0].childNodes[0].nodeValue;" "if(d.getElementsByTagName('j')[0].childNodes[0].nodeValue==0){t.value='';}" "z=d.getElementsByTagName('l')[0].childNodes;" "if(z.length>0){t.value+=z[0].nodeValue;}" "t.scrollTop=99999;" "sn=t.scrollTop;" "}" "};" "x.open('GET','ax?c2='+id+o,true);" "x.send();" "}" "lt=setTimeout(l,2345);" "return false;" "}" ""; const char HTTP_SCRIPT_MODULE[] PROGMEM = "var os;" "function sk(s,g){" "var o=os.replace(\"value='\"+s+\"'\",\"selected value='\"+s+\"'\");" "document.getElementById('g'+g).innerHTML=o;" "}" "function sl(){" "var o0=\""; const char HTTP_LNK_STYLE[] PROGMEM = ".q{float:right;width:64px;text-align:right;}" ".l{background:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAALVBMVEX///8EBwfBwsLw8PAzNjaCg4NTVVUjJiZDRUUUFxdiZGSho6O" "Sk5Pg4eFydHTCjaf3AAAAZElEQVQ4je2NSw7AIAhEBamKn97/uMXEGBvozkWb9C2Zx4xzWykBhFAeYp9gkLyZE0zIMno9n4g19hmdY39scwqVkOXaxph0ZCXQcqxSpgQpONa59wkRDOL93eA" "XvimwlbPbwwVAegLS1HGfZAAAAABJRU5ErkJggg==') no-repeat left center;background-size:1em;}" ""; const char HTTP_MSG_RSTRT[] PROGMEM = "
Device will restart in a few seconds

"; const char HTTP_BTN_MENU1[] PROGMEM = "
" "
" "
" "
"; const char HTTP_BTN_RSTRT[] PROGMEM = "
"; const char HTTP_BTN_MENU2[] PROGMEM = "
" "
"; const char HTTP_BTN_MENU3[] PROGMEM = "
" #ifdef USE_DOMOTICZ "
" #endif // USE_DOMOTICZ ""; const char HTTP_BTN_MENU4[] PROGMEM = "
" "
" "
" "
" "
"; const char HTTP_BTN_MAIN[] PROGMEM = "

"; const char HTTP_BTN_CONF[] PROGMEM = "

"; const char HTTP_FORM_MODULE[] PROGMEM = "
 Module parameters 
" "" "
Module type ({mt})
" "
AP1 SSId (" STA_SSID1 ")

" "
AP1 Password

" "
AP2 SSId (" STA_SSID2 ")

" "
AP2 Password

" "
Hostname (" WIFI_HOSTNAME ")

"; const char HTTP_FORM_MQTT[] PROGMEM = "
 MQTT parameters " "" "
Host (" MQTT_HOST ")

" "
Port (" STR(MQTT_PORT) ")

" "
Client Id ({m0})

" "
User (" MQTT_USER ")

" "
Password

" "
Topic = %topic% (" MQTT_TOPIC ")

" "
Full Topic (" MQTT_FULLTOPIC ")

"; const char HTTP_FORM_LOG1[] PROGMEM = "
 Logging parameters " ""; const char HTTP_FORM_LOG2[] PROGMEM = "
{b0}log level ({b1})

"; const char HTTP_FORM_LOG3[] PROGMEM = "
Syslog host (" SYS_LOG_HOST ")

" "
Syslog port (" STR(SYS_LOG_PORT) ")

" "
Telemetric period (" STR(TELE_PERIOD) ")

"; const char HTTP_FORM_OTHER[] PROGMEM = "
 Other parameters " "" "
Web Admin Password

" "
MQTT enable
"; const char HTTP_FORM_OTHER2[] PROGMEM = "
Friendly Name {1 ({2)

"; #ifdef USE_EMULATION const char HTTP_FORM_OTHER3[] PROGMEM = "
 Emulation " "
None" "
Belkin WeMo single device" "
Hue Bridge multi devices
"; #endif // USE_EMULATION const char HTTP_FORM_END[] PROGMEM = "
"; const char HTTP_FORM_RST[] PROGMEM = "
" "
 Restore configuration "; const char HTTP_FORM_UPG[] PROGMEM = "
" "
 Upgrade by web server " "
" "
OTA Url

" "
" "


" "
 Upgrade by file upload "; const char HTTP_FORM_RST_UPG[] PROGMEM = "
" "

" "
" "
" "
" ""; const char HTTP_FORM_CMND[] PROGMEM = "


" "
" "
" // "
" "
"; const char HTTP_TABLE100[] PROGMEM = ""; const char HTTP_COUNTER[] PROGMEM = "
"; const char HTTP_SNS_COUNTER[] PROGMEM = ""; const char HTTP_SNS_TEMP[] PROGMEM = ""; const char HTTP_SNS_HUM[] PROGMEM = ""; const char HTTP_SNS_PRESSURE[] PROGMEM = ""; const char HTTP_SNS_LIGHT[] PROGMEM = ""; const char HTTP_SNS_NOISE[] PROGMEM = ""; const char HTTP_SNS_DUST[] PROGMEM = ""; const char HTTP_END[] PROGMEM = "" "" ""; const char HDR_CCNTL[] PROGMEM = "Cache-Control"; const char HDR_REVAL[] PROGMEM = "no-cache, no-store, must-revalidate"; #define DNS_PORT 53 enum http_t {HTTP_OFF, HTTP_USER, HTTP_ADMIN, HTTP_MANAGER}; DNSServer *dnsServer; ESP8266WebServer *webServer; boolean _removeDuplicateAPs = true; int _minimumQuality = -1; uint8_t _httpflag = HTTP_OFF; uint8_t _uploaderror = 0; uint8_t _uploadfiletype; uint8_t _colcount; void startWebserver(int type, IPAddress ipweb) { char log[LOGSZ]; if (!_httpflag) { if (!webServer) { webServer = new ESP8266WebServer((HTTP_MANAGER==type)?80:WEB_PORT); webServer->on("/", handleRoot); webServer->on("/cn", handleConfig); webServer->on("/md", handleModule); webServer->on("/w1", handleWifi1); webServer->on("/w0", handleWifi0); if (sysCfg.flag.mqtt_enabled) { webServer->on("/mq", handleMqtt); #ifdef USE_DOMOTICZ webServer->on("/dm", handleDomoticz); #endif // USE_DOMOTICZ } webServer->on("/lg", handleLog); webServer->on("/co", handleOther); webServer->on("/dl", handleDownload); webServer->on("/sv", handleSave); webServer->on("/rs", handleRestore); webServer->on("/rt", handleReset); webServer->on("/up", handleUpgrade); webServer->on("/u1", handleUpgradeStart); // OTA webServer->on("/u2", HTTP_POST, handleUploadDone, handleUploadLoop); webServer->on("/cm", handleCmnd); webServer->on("/cs", handleConsole); webServer->on("/ax", handleAjax); webServer->on("/ay", handleAjax2); webServer->on("/in", handleInfo); webServer->on("/rb", handleRestart); webServer->on("/fwlink", handleRoot); // Microsoft captive portal. Maybe not needed. Might be handled by notFound handler. #ifdef USE_EMULATION if (EMUL_WEMO == sysCfg.flag.emulation) { webServer->on("/upnp/control/basicevent1", HTTP_POST, handleUPnPevent); webServer->on("/eventservice.xml", handleUPnPservice); webServer->on("/setup.xml", handleUPnPsetupWemo); } if (EMUL_HUE == sysCfg.flag.emulation) { webServer->on("/description.xml", handleUPnPsetupHue); } #endif // USE_EMULATION webServer->onNotFound(handleNotFound); } logajaxflg = 0; webServer->begin(); // Web server start } if (_httpflag != type) { snprintf_P(log, sizeof(log), PSTR("HTTP: Webserver active on %s%s with IP address %s"), Hostname, (mDNSbegun)?".local":"", ipweb.toString().c_str()); addLog(LOG_LEVEL_INFO, log); } if (type) _httpflag = type; } void stopWebserver() { if (_httpflag) { webServer->close(); _httpflag = HTTP_OFF; addLog_P(LOG_LEVEL_INFO, PSTR("HTTP: Webserver stopped")); } } void beginWifiManager() { // setup AP if ((WL_CONNECTED == WiFi.status()) && (static_cast(WiFi.localIP()) != 0)) { WiFi.mode(WIFI_AP_STA); addLog_P(LOG_LEVEL_DEBUG, PSTR("Wifimanager: Set AccessPoint and keep Station")); } else { WiFi.mode(WIFI_AP); addLog_P(LOG_LEVEL_DEBUG, PSTR("Wifimanager: Set AccessPoint")); } stopWebserver(); dnsServer = new DNSServer(); WiFi.softAP(Hostname); delay(500); // Without delay I've seen the IP address blank /* Setup the DNS server redirecting all the domains to the apIP */ dnsServer->setErrorReplyCode(DNSReplyCode::NoError); dnsServer->start(DNS_PORT, "*", WiFi.softAPIP()); startWebserver(HTTP_MANAGER, WiFi.softAPIP()); } void pollDnsWeb() { if (dnsServer) { dnsServer->processNextRequest(); } if (webServer) { webServer->handleClient(); } } void showPage(String &page) { if((HTTP_ADMIN == _httpflag) && (sysCfg.web_password[0] != 0) && !webServer->authenticate(WEB_USERNAME, sysCfg.web_password)) { return webServer->requestAuthentication(); } page.replace(F("{ha}"), my_module.name); page.replace(F("{h}"), sysCfg.friendlyname[0]); if (HTTP_MANAGER == _httpflag) { if (WIFI_configCounter()) { page.replace(F(""), F("")); page += FPSTR(HTTP_COUNTER); } } page += FPSTR(HTTP_END); webServer->sendHeader(FPSTR(HDR_CCNTL), FPSTR(HDR_REVAL)); webServer->sendHeader(F("Pragma"), F("no-cache")); webServer->sendHeader(F("Expires"), F("-1")); webServer->send(200, F("text/html"), page); } void handleRoot() { addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle root")); if (captivePortal()) { // If captive portal redirect instead of displaying the page. return; } if (HTTP_MANAGER == _httpflag) { handleWifi0(); } else { char stemp[10], line[100]; String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), F("Main menu")); page.replace(F(""), F("")); page += F("
"); if (Maxdevice) { if (SONOFF_LED == sysCfg.module) { snprintf_P(line, sizeof(line), PSTR(""), sysCfg.led_dimmer[0]); page += line; } page += FPSTR(HTTP_TABLE100); page += F("
"); for (byte idx = 1; idx <= Maxdevice; idx++) { snprintf_P(stemp, sizeof(stemp), PSTR(" %d"), idx); snprintf_P(line, sizeof(line), PSTR(""), 100 / Maxdevice, idx, (Maxdevice > 1) ? stemp : ""); page += line; } page += F("
Counter%d%d
%s Temperature%s°%c
%s Humidity%s%
%s Pressure%s hPa
%s Light%d%
%s Noise%d%
%s Air quality%d%
"); } if (HTTP_ADMIN == _httpflag) { page += FPSTR(HTTP_BTN_MENU1); page += FPSTR(HTTP_BTN_RSTRT); } showPage(page); } } void handleAjax2() { char svalue[80]; if (strlen(webServer->arg("o").c_str())) { do_cmnd_power(atoi(webServer->arg("o").c_str()), 2); } if (strlen(webServer->arg("d").c_str())) { snprintf_P(svalue, sizeof(svalue), PSTR("dimmer %s"), webServer->arg("d").c_str()); do_cmnd(svalue); } String tpage = ""; for (byte i = 0; i < 4; i++) { if (pin[GPIO_CNTR1 +i] < 99) { snprintf_P(svalue, sizeof(svalue), HTTP_SNS_COUNTER, i+1, rtcMem.pCounter[i]); tpage += svalue; } } if (hlw_flg) { tpage += hlw_webPresent(); } if (SONOFF_SC == sysCfg.module) { tpage += sc_webPresent(); } #ifdef USE_DS18B20 if (pin[GPIO_DSB] < 99) { tpage += dsb_webPresent(); } #endif // USE_DS18B20 #ifdef USE_DS18x20 if (pin[GPIO_DSB] < 99) { tpage += ds18x20_webPresent(); } #endif // USE_DS18x20 #ifdef USE_DHT if (dht_flg) { tpage += dht_webPresent(); } #endif // USE_DHT #ifdef USE_I2C if (i2c_flg) { #ifdef USE_SHT tpage += sht_webPresent(); #endif #ifdef USE_HTU tpage += htu_webPresent(); #endif #ifdef USE_BMP tpage += bmp_webPresent(); #endif #ifdef USE_BH1750 tpage += bh1750_webPresent(); #endif } #endif // USE_I2C String page = ""; if (tpage.length() > 0) { page += FPSTR(HTTP_TABLE100); page += tpage; page += F(""); } char line[120]; if (Maxdevice) { page += FPSTR(HTTP_TABLE100); page += F(""); for (byte idx = 1; idx <= Maxdevice; idx++) { snprintf_P(line, sizeof(line), PSTR("
%s
"), 100 / Maxdevice, 70 - (Maxdevice * 8), getStateText(bitRead(power, idx -1))); page += line; } page += F(""); } /* * Will interrupt user action when selected if (SONOFF_LED == sysCfg.module) { snprintf_P(line, sizeof(line), PSTR(""), sysCfg.led_dimmer[0]); page += line; } */ webServer->send(200, F("text/html"), page); } boolean httpUser() { boolean status = (HTTP_USER == _httpflag); if (status) { handleRoot(); } return status; } void handleConfig() { if (httpUser()) { return; } addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle config")); String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), F("Configuration")); page += FPSTR(HTTP_BTN_MENU2); if (sysCfg.flag.mqtt_enabled) { page += FPSTR(HTTP_BTN_MENU3); } page += FPSTR(HTTP_BTN_MENU4); page += FPSTR(HTTP_BTN_MAIN); showPage(page); } boolean inModule(byte val, uint8_t *arr) { int offset = 0; if (!val) { return false; // None } #ifndef USE_I2C if (GPIO_I2C_SCL == val) { return true; } if (GPIO_I2C_SDA == val) { return true; } #endif #ifndef USE_WS2812 if (GPIO_WS2812 == val) { return true; } #endif #ifndef USE_IR_REMOTE if (GPIO_IRSEND == val) { return true; } #endif if (((val >= GPIO_REL1) && (val <= GPIO_REL4)) || ((val >= GPIO_LED1) && (val <= GPIO_LED4))) { offset = 4; } if (((val >= GPIO_REL1_INV) && (val <= GPIO_REL4_INV)) || ((val >= GPIO_LED1_INV) && (val <= GPIO_LED4_INV))) { offset = -4; } for (byte i = 0; i < MAX_GPIO_PIN; i++) { if (arr[i] == val) { return true; } if (arr[i] == val + offset) { return true; } } return false; } void handleModule() { if (httpUser()) { return; } char stemp[20], line[128]; addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle Module config")); String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), F("Config module")); page += FPSTR(HTTP_FORM_MODULE); snprintf_P(stemp, sizeof(stemp), modules[MODULE].name); page.replace(F("{mt}"), stemp); for (byte i = 0; i < MAXMODULE; i++) { snprintf_P(stemp, sizeof(stemp), modules[i].name); snprintf_P(line, sizeof(line), PSTR("%02d %s"), (i == sysCfg.module) ? " selected" : "", i, i +1, stemp); page += line; } page += F("
"); mytmplt cmodule; memcpy_P(&cmodule, &modules[sysCfg.module], sizeof(cmodule)); String func = FPSTR(HTTP_SCRIPT_MODULE); for (byte j = 0; j < GPIO_SENSOR_END; j++) { if (!inModule(j, cmodule.gp.io)) { snprintf_P(stemp, sizeof(stemp), sensors[j]); snprintf_P(line, sizeof(line), PSTR("-1'%d'>%02d %s-2"), j, j, stemp); func += line; } } func += F("\";os=o0.replace(/-1/g,\"
"); #endif // USE_EMULATION page += FPSTR(HTTP_FORM_END); page += FPSTR(HTTP_BTN_CONF); showPage(page); } void handleDownload() { if (httpUser()) { return; } addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle download config")); uint8_t buffer[sizeof(sysCfg)]; WiFiClient myClient = webServer->client(); webServer->setContentLength(4096); char attachment[100]; snprintf_P(attachment, sizeof(attachment), PSTR("attachment; filename=Config_%s_%s.dmp"), sysCfg.friendlyname[0], Version); webServer->sendHeader(F("Content-Disposition"), attachment); webServer->send(200, F("application/octet-stream"), ""); memcpy(buffer, &sysCfg, sizeof(sysCfg)); buffer[0] = CONFIG_FILE_SIGN; buffer[1] = (!CONFIG_FILE_XOR)?0:1; if (buffer[1]) { for (uint16_t i = 2; i < sizeof(buffer); i++) { buffer[i] ^= (CONFIG_FILE_XOR +i); } } myClient.write((const char*)buffer, sizeof(buffer)); } void handleSave() { if (httpUser()) { return; } char log[LOGSZ +20]; char stemp[TOPSZ]; char stemp2[TOPSZ]; byte what = 0; byte restart; String result = ""; addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Parameter save")); if (strlen(webServer->arg("w").c_str())) { what = atoi(webServer->arg("w").c_str()); } switch (what) { case 1: strlcpy(sysCfg.hostname, (!strlen(webServer->arg("h").c_str())) ? WIFI_HOSTNAME : webServer->arg("h").c_str(), sizeof(sysCfg.hostname)); if (strstr(sysCfg.hostname,"%")) { strlcpy(sysCfg.hostname, WIFI_HOSTNAME, sizeof(sysCfg.hostname)); } strlcpy(sysCfg.sta_ssid[0], (!strlen(webServer->arg("s1").c_str())) ? STA_SSID1 : webServer->arg("s1").c_str(), sizeof(sysCfg.sta_ssid[0])); strlcpy(sysCfg.sta_pwd[0], (!strlen(webServer->arg("p1").c_str())) ? STA_PASS1 : webServer->arg("p1").c_str(), sizeof(sysCfg.sta_pwd[0])); strlcpy(sysCfg.sta_ssid[1], (!strlen(webServer->arg("s2").c_str())) ? STA_SSID2 : webServer->arg("s2").c_str(), sizeof(sysCfg.sta_ssid[1])); strlcpy(sysCfg.sta_pwd[1], (!strlen(webServer->arg("p2").c_str())) ? STA_PASS2 : webServer->arg("p2").c_str(), sizeof(sysCfg.sta_pwd[1])); snprintf_P(log, sizeof(log), PSTR("HTTP: Wifi Hostname %s, SSID1 %s, Password1 %s, SSID2 %s, Password2 %s"), sysCfg.hostname, sysCfg.sta_ssid[0], sysCfg.sta_pwd[0], sysCfg.sta_ssid[1], sysCfg.sta_pwd[1]); addLog(LOG_LEVEL_INFO, log); result += F("
Trying to connect device to network
If it fails reconnect to try again"); break; case 2: strlcpy(stemp, (!strlen(webServer->arg("mt").c_str())) ? MQTT_TOPIC : webServer->arg("mt").c_str(), sizeof(stemp)); strlcpy(stemp2, (!strlen(webServer->arg("mf").c_str())) ? MQTT_FULLTOPIC : webServer->arg("mf").c_str(), sizeof(stemp2)); if ((strcmp(stemp, sysCfg.mqtt_topic)) || (strcmp(stemp2, sysCfg.mqtt_fulltopic))) { mqtt_publish_topic_P(2, PSTR("LWT"), (sysCfg.flag.mqtt_offline) ? "Offline" : "", true); // Offline or remove previous retained topic } strlcpy(sysCfg.mqtt_topic, stemp, sizeof(sysCfg.mqtt_topic)); strlcpy(sysCfg.mqtt_fulltopic, stemp2, sizeof(sysCfg.mqtt_fulltopic)); strlcpy(sysCfg.mqtt_host, (!strlen(webServer->arg("mh").c_str())) ? MQTT_HOST : webServer->arg("mh").c_str(), sizeof(sysCfg.mqtt_host)); sysCfg.mqtt_port = (!strlen(webServer->arg("ml").c_str())) ? MQTT_PORT : atoi(webServer->arg("ml").c_str()); strlcpy(sysCfg.mqtt_client, (!strlen(webServer->arg("mc").c_str())) ? MQTT_CLIENT_ID : webServer->arg("mc").c_str(), sizeof(sysCfg.mqtt_client)); strlcpy(sysCfg.mqtt_user, (!strlen(webServer->arg("mu").c_str())) ? MQTT_USER : (!strcmp(webServer->arg("mu").c_str(),"0")) ? "" : webServer->arg("mu").c_str(), sizeof(sysCfg.mqtt_user)); strlcpy(sysCfg.mqtt_pwd, (!strlen(webServer->arg("mp").c_str())) ? MQTT_PASS : (!strcmp(webServer->arg("mp").c_str(),"0")) ? "" : webServer->arg("mp").c_str(), sizeof(sysCfg.mqtt_pwd)); snprintf_P(log, sizeof(log), PSTR("HTTP: MQTT Host %s, Port %d, Client %s, User %s, Password %s, Topic %s, FullTopic %s"), sysCfg.mqtt_host, sysCfg.mqtt_port, sysCfg.mqtt_client, sysCfg.mqtt_user, sysCfg.mqtt_pwd, sysCfg.mqtt_topic, sysCfg.mqtt_fulltopic); addLog(LOG_LEVEL_INFO, log); break; case 3: sysCfg.seriallog_level = (!strlen(webServer->arg("ls").c_str())) ? SERIAL_LOG_LEVEL : atoi(webServer->arg("ls").c_str()); sysCfg.weblog_level = (!strlen(webServer->arg("lw").c_str())) ? WEB_LOG_LEVEL : atoi(webServer->arg("lw").c_str()); sysCfg.syslog_level = (!strlen(webServer->arg("ll").c_str())) ? SYS_LOG_LEVEL : atoi(webServer->arg("ll").c_str()); syslog_level = sysCfg.syslog_level; syslog_timer = 0; strlcpy(sysCfg.syslog_host, (!strlen(webServer->arg("lh").c_str())) ? SYS_LOG_HOST : webServer->arg("lh").c_str(), sizeof(sysCfg.syslog_host)); sysCfg.syslog_port = (!strlen(webServer->arg("lp").c_str())) ? SYS_LOG_PORT : atoi(webServer->arg("lp").c_str()); sysCfg.tele_period = (!strlen(webServer->arg("lt").c_str())) ? TELE_PERIOD : atoi(webServer->arg("lt").c_str()); snprintf_P(log, sizeof(log), PSTR("HTTP: Logging Seriallog %d, Weblog %d, Syslog %d, Host %s, Port %d, TelePeriod %d"), sysCfg.seriallog_level, sysCfg.weblog_level, sysCfg.syslog_level, sysCfg.syslog_host, sysCfg.syslog_port, sysCfg.tele_period); addLog(LOG_LEVEL_INFO, log); break; #ifdef USE_DOMOTICZ case 4: domoticz_saveSettings(); break; #endif // USE_DOMOTICZ case 5: strlcpy(sysCfg.web_password, (!strlen(webServer->arg("p1").c_str())) ? WEB_PASSWORD : (!strcmp(webServer->arg("p1").c_str(),"0")) ? "" : webServer->arg("p1").c_str(), sizeof(sysCfg.web_password)); sysCfg.flag.mqtt_enabled = webServer->hasArg("b1"); #ifdef USE_EMULATION sysCfg.flag.emulation = (!strlen(webServer->arg("b2").c_str())) ? 0 : atoi(webServer->arg("b2").c_str()); #endif // USE_EMULATION strlcpy(sysCfg.friendlyname[0], (!strlen(webServer->arg("a1").c_str())) ? FRIENDLY_NAME : webServer->arg("a1").c_str(), sizeof(sysCfg.friendlyname[0])); strlcpy(sysCfg.friendlyname[1], (!strlen(webServer->arg("a2").c_str())) ? FRIENDLY_NAME"2" : webServer->arg("a2").c_str(), sizeof(sysCfg.friendlyname[1])); strlcpy(sysCfg.friendlyname[2], (!strlen(webServer->arg("a3").c_str())) ? FRIENDLY_NAME"3" : webServer->arg("a3").c_str(), sizeof(sysCfg.friendlyname[2])); strlcpy(sysCfg.friendlyname[3], (!strlen(webServer->arg("a4").c_str())) ? FRIENDLY_NAME"4" : webServer->arg("a4").c_str(), sizeof(sysCfg.friendlyname[3])); snprintf_P(log, sizeof(log), PSTR("HTTP: Other MQTT Enable %s, Emulation %d, Friendly Names %s, %s, %s and %s"), getStateText(sysCfg.flag.mqtt_enabled), sysCfg.flag.emulation, sysCfg.friendlyname[0], sysCfg.friendlyname[1], sysCfg.friendlyname[2], sysCfg.friendlyname[3]); addLog(LOG_LEVEL_INFO, log); break; case 6: byte new_module = (!strlen(webServer->arg("mt").c_str())) ? MODULE : atoi(webServer->arg("mt").c_str()); byte new_modflg = (sysCfg.module != new_module); sysCfg.module = new_module; mytmplt cmodule; memcpy_P(&cmodule, &modules[sysCfg.module], sizeof(cmodule)); String gpios = ""; for (byte i = 0; i < MAX_GPIO_PIN; i++) { if (new_modflg) { sysCfg.my_module.gp.io[i] = 0; } if (GPIO_USER == cmodule.gp.io[i]) { snprintf_P(stemp, sizeof(stemp), PSTR("g%d"), i); sysCfg.my_module.gp.io[i] = (!strlen(webServer->arg(stemp).c_str())) ? 0 : atoi(webServer->arg(stemp).c_str()); gpios += F(", GPIO"); gpios += String(i); gpios += F(" "); gpios += String(sysCfg.my_module.gp.io[i]); } } setModuleFlashMode(0); snprintf_P(stemp, sizeof(stemp), modules[sysCfg.module].name); snprintf_P(log, sizeof(log), PSTR("HTTP: %s Module%s"), stemp, gpios.c_str()); addLog(LOG_LEVEL_INFO, log); break; } restart = (!strlen(webServer->arg("r").c_str())) ? 1 : atoi(webServer->arg("r").c_str()); if (restart) { String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), F("Save parameters")); page += F("
Parameters saved
"); page += result; page += F("
"); page += FPSTR(HTTP_MSG_RSTRT); if (HTTP_MANAGER == _httpflag) { _httpflag = HTTP_ADMIN; } else { page += FPSTR(HTTP_BTN_MAIN); } showPage(page); restartflag = 2; } else { handleConfig(); } } void handleReset() { if (httpUser()) { return; } char svalue[16]; // was MESSZ addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Reset parameters")); String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), F("Default parameters")); page += F("
Parameters reset to default
"); page += FPSTR(HTTP_MSG_RSTRT); page += FPSTR(HTTP_BTN_MAIN); showPage(page); snprintf_P(svalue, sizeof(svalue), PSTR("reset 1")); do_cmnd(svalue); } void handleRestore() { if (httpUser()) { return; } addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle restore")); String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), F("Restore Configuration")); page += FPSTR(HTTP_FORM_RST); page += FPSTR(HTTP_FORM_RST_UPG); page.replace(F("{r1}"), F("restore")); page += FPSTR(HTTP_BTN_CONF); showPage(page); _uploaderror = 0; _uploadfiletype = 1; } void handleUpgrade() { if (httpUser()) { return; } addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle upgrade")); String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), F("Firmware upgrade")); page += FPSTR(HTTP_FORM_UPG); page.replace(F("{o1}"), sysCfg.otaUrl); page += FPSTR(HTTP_FORM_RST_UPG); page.replace(F("{r1}"), F("upgrade")); page += FPSTR(HTTP_BTN_MAIN); showPage(page); _uploaderror = 0; _uploadfiletype = 0; } void handleUpgradeStart() { if (httpUser()) { return; } char svalue[100]; // was MESSZ addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Firmware upgrade start")); WIFI_configCounter(); if (strlen(webServer->arg("o").c_str())) { snprintf_P(svalue, sizeof(svalue), PSTR("otaurl %s"), webServer->arg("o").c_str()); do_cmnd(svalue); } String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), F("Info")); page += F("
Upgrade started ...
"); page += FPSTR(HTTP_MSG_RSTRT); page += FPSTR(HTTP_BTN_MAIN); showPage(page); snprintf_P(svalue, sizeof(svalue), PSTR("upgrade 1")); do_cmnd(svalue); } void handleUploadDone() { if (httpUser()) { return; } addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: File upload done")); char error[80]; char log[LOGSZ]; WIFI_configCounter(); restartflag = 0; mqttcounter = 0; String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), F("Info")); page += F("
Upload "); if (_uploaderror) { page += F("failed

"); switch (_uploaderror) { case 1: strcpy_P(error, PSTR("No file selected")); break; case 2: strcpy_P(error, PSTR("File size is larger than available free space")); break; case 3: strcpy_P(error, PSTR("File magic header does not start with 0xE9")); break; case 4: strcpy_P(error, PSTR("File flash size is larger than device flash size")); break; case 5: strcpy_P(error, PSTR("File upload buffer miscompare")); break; case 6: strcpy_P(error, PSTR("Upload failed. Enable logging option 3 for more information")); break; case 7: strcpy_P(error, PSTR("Upload aborted")); break; case 8: strcpy_P(error, PSTR("Invalid configuration file")); break; case 9: strcpy_P(error, PSTR("Configuration file too large")); break; default: snprintf_P(error, sizeof(error), PSTR("Upload error code %d"), _uploaderror); } page += error; if (!_uploadfiletype && Update.hasError()) { page += F("

Update error code (see Updater.cpp) "); page += String(Update.getError()); } snprintf_P(log, sizeof(log), PSTR("Upload: Error - %s"), error); addLog(LOG_LEVEL_DEBUG, log); } else { page += F("successful

Device will restart in a few seconds"); restartflag = 2; } page += F("

"); page += FPSTR(HTTP_BTN_MAIN); showPage(page); } void handleUploadLoop() { // Based on ESP8266HTTPUpdateServer.cpp uses ESP8266WebServer Parsing.cpp and Cores Updater.cpp (Update) char log[LOGSZ]; boolean _serialoutput = (LOG_LEVEL_DEBUG <= seriallog_level); if (HTTP_USER == _httpflag) { return; } if (_uploaderror) { if (!_uploadfiletype) { Update.end(); } return; } HTTPUpload& upload = webServer->upload(); if (UPLOAD_FILE_START == upload.status) { restartflag = 60; if (0 == upload.filename.c_str()[0]) { _uploaderror = 1; return; } snprintf_P(log, sizeof(log), PSTR("Upload: File %s ..."), upload.filename.c_str()); addLog(LOG_LEVEL_INFO, log); if (!_uploadfiletype) { mqttcounter = 60; #ifdef USE_EMULATION UDP_Disconnect(); #endif // USE_EMULATION if (sysCfg.flag.mqtt_enabled) { mqttClient.disconnect(); } uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; if (!Update.begin(maxSketchSpace)) { //start with max available size if (_serialoutput) { Update.printError(Serial); } _uploaderror = 2; return; } } _colcount = 0; } else if (!_uploaderror && (UPLOAD_FILE_WRITE == upload.status)) { if (0 == upload.totalSize) { if (_uploadfiletype) { if (upload.buf[0] != CONFIG_FILE_SIGN) { _uploaderror = 8; return; } if (upload.currentSize > sizeof(sysCfg)) { _uploaderror = 9; return; } } else { if (upload.buf[0] != 0xE9) { _uploaderror = 3; return; } uint32_t bin_flash_size = ESP.magicFlashChipSize((upload.buf[3] & 0xf0) >> 4); if(bin_flash_size > ESP.getFlashChipRealSize()) { _uploaderror = 4; return; } if ((SONOFF_TOUCH == sysCfg.module) || (SONOFF_4CH == sysCfg.module)) { upload.buf[2] = 3; // DOUT - ESP8285 addLog_P(LOG_LEVEL_DEBUG, PSTR("FLSH: Set Flash Mode to 3")); } } } if (_uploadfiletype) { // config if (!_uploaderror) { if (upload.buf[1]) { for (uint16_t i = 2; i < upload.currentSize; i++) { upload.buf[i] ^= (CONFIG_FILE_XOR +i); } } CFG_DefaultSet2(); memcpy((char*)&sysCfg +16, upload.buf +16, upload.currentSize -16); } } else { // firmware if (!_uploaderror && (Update.write(upload.buf, upload.currentSize) != upload.currentSize)) { if (_serialoutput) { Update.printError(Serial); } _uploaderror = 5; return; } if (_serialoutput) { Serial.printf("."); _colcount++; if (!(_colcount % 80)) { Serial.println(); } } } } else if(!_uploaderror && (UPLOAD_FILE_END == upload.status)) { if (_serialoutput && (_colcount % 80)) { Serial.println(); } if (!_uploadfiletype) { if (!Update.end(true)) { // true to set the size to the current progress if (_serialoutput) { Update.printError(Serial); } _uploaderror = 6; return; } } if (!_uploaderror) { snprintf_P(log, sizeof(log), PSTR("Upload: Successful %u bytes. Restarting"), upload.totalSize); addLog(LOG_LEVEL_INFO, log); } } else if (UPLOAD_FILE_ABORTED == upload.status) { restartflag = 0; mqttcounter = 0; _uploaderror = 7; if (!_uploadfiletype) { Update.end(); } } delay(0); } void handleCmnd() { if (httpUser()) { return; } char svalue[128]; // was MESSZ addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle cmnd")); uint8_t valid = 1; if (sysCfg.web_password[0] != 0) { if (!(!strcmp(webServer->arg("user").c_str(),WEB_USERNAME) && !strcmp(webServer->arg("password").c_str(),sysCfg.web_password))) { valid = 0; } } String message = ""; if (valid) { byte curridx = logidx; if (strlen(webServer->arg("cmnd").c_str())) { // snprintf_P(svalue, sizeof(svalue), webServer->arg("cmnd").c_str()); snprintf_P(svalue, sizeof(svalue), PSTR("%s"), webServer->arg("cmnd").c_str()); byte syslog_now = syslog_level; syslog_level = 0; // Disable UDP syslog to not trigger hardware WDT do_cmnd(svalue); syslog_level = syslog_now; } if (logidx != curridx) { byte counter = curridx; do { if (Log[counter].length()) { if (message.length()) { message += F("\n"); } if (sysCfg.flag.mqtt_enabled) { // [14:49:36 MQTT: stat/wemos5/RESULT = {"POWER":"OFF"}] > [RESULT = {"POWER":"OFF"}] // message += Log[counter].substring(17 + strlen(PUB_PREFIX) + strlen(sysCfg.mqtt_topic)); message += Log[counter].substring(Log[counter].lastIndexOf("/",Log[counter].indexOf("="))+1); } else { // [14:49:36 RSLT: RESULT = {"POWER":"OFF"}] > [RESULT = {"POWER":"OFF"}] message += Log[counter].substring(Log[counter].indexOf(": ")+2); } } counter++; if (counter > MAX_LOG_LINES -1) { counter = 0; } } while (counter != logidx); } else { message = F("Enable weblog 2 if response expected\n"); } } else { message = F("Need user=&password=\n"); } webServer->send(200, F("text/plain"), message); } void handleConsole() { if (httpUser()) { return; } addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle console")); String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), F("Console")); page.replace(F(""), FPSTR(HTTP_SCRIPT_CONSOL)); page.replace(F(""), F("")); page += FPSTR(HTTP_FORM_CMND); page += FPSTR(HTTP_BTN_MAIN); showPage(page); } void handleAjax() { if (httpUser()) { return; } char log[LOGSZ]; char svalue[128]; // was MESSZ byte cflg = 1; byte counter = 99; if (strlen(webServer->arg("c1").c_str())) { snprintf_P(svalue, sizeof(svalue), PSTR("%s"), webServer->arg("c1").c_str()); snprintf_P(log, sizeof(log), PSTR("CMND: %s"), svalue); addLog(LOG_LEVEL_INFO, log); byte syslog_now = syslog_level; syslog_level = 0; // Disable UDP syslog to not trigger hardware WDT do_cmnd(svalue); syslog_level = syslog_now; } if (strlen(webServer->arg("c2").c_str())) { counter = atoi(webServer->arg("c2").c_str()); } String message = F(""); message += String(logidx); message += F(""); message += String(logajaxflg); if (!logajaxflg) { counter = 99; logajaxflg = 1; } message += F(""); if (counter != logidx) { if (99 == counter) { counter = logidx; cflg = 0; } do { if (Log[counter].length()) { if (cflg) { message += F("\n"); } else { cflg = 1; } message += Log[counter]; } counter++; if (counter > MAX_LOG_LINES -1) { counter = 0; } } while (counter != logidx); } message += F(""); webServer->send(200, F("text/xml"), message); } void handleInfo() { if (httpUser()) { return; } addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle info")); char stopic[TOPSZ]; int freeMem = ESP.getFreeHeap(); String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), F("Information")); // page += F("
 Information "); page += F(""); page += F(""); page += F(""); page += F(""); page += F(""); // page += F(""); page += F(""); page += F(""); page += F(""); page += F(""); for (byte i = 0; i < Maxdevice; i++) { page += F(""); } page += F(""); // page += F(""); page += F(""); page += F(""); if (static_cast(WiFi.localIP()) != 0) { page += F(""); page += F(""); page += F(""); page += F(""); page += F(""); } if (static_cast(WiFi.softAPIP()) != 0) { page += F(""); page += F(""); page += F(""); } page += F(""); if (sysCfg.flag.mqtt_enabled) { page += F(""); page += F(""); page += F(""); page += F(""); // page += F(""); page += F(""); page += F(""); getTopic_P(stopic, 0, sysCfg.mqtt_topic, ""); page += F(""); } else { page += F(""); } page += F(""); page += F(""); page += F(""); page += F(""); page += F(""); page += F(""); page += F(""); page += F(""); page += F(""); page += F(""); page += F(""); page += F(""); page += F("
Program version"); page += Version; page += F("
Build Date & Time"); page += getBuildDateTime(); page += F("
Core/SDK version"); page += ESP.getCoreVersion(); page += F("/"); page += String(ESP.getSdkVersion()); page += F("
Boot version"); page += String(ESP.getBootVersion()); page += F("
Uptime"); page += String(uptime); page += F(" Hours
Flash write count"); page += String(sysCfg.saveFlag); page += F("
Boot count"); page += String(sysCfg.bootcount); page += F("
Reset reason"); page += getResetReason(); page += F("
Friendly name "); page += i +1; page += F(""); page += sysCfg.friendlyname[i]; page += F("
 
SSId (RSSI)"); page += (sysCfg.sta_active)? sysCfg.sta_ssid2 : sysCfg.sta_ssid1; page += F(" ("); page += WIFI_getRSSIasQuality(WiFi.RSSI()); page += F("%)
AP"); page += String(sysCfg.sta_active +1); page += F(" SSId (RSSI)"); page += sysCfg.sta_ssid[sysCfg.sta_active]; page += F(" ("); page += WIFI_getRSSIasQuality(WiFi.RSSI()); page += F("%)
Hostname"); page += Hostname; page += F("
IP address"); page += WiFi.localIP().toString(); page += F("
Gateway"); page += IPAddress(sysCfg.ip_address[1]).toString(); page += F("
Subnet mask"); page += IPAddress(sysCfg.ip_address[2]).toString(); page += F("
DNS server"); page += IPAddress(sysCfg.ip_address[3]).toString(); page += F("
MAC address"); page += WiFi.macAddress(); page += F("
AP IP address"); page += WiFi.softAPIP().toString(); page += F("
AP Gateway"); page += WiFi.softAPIP().toString(); page += F("
AP MAC address"); page += WiFi.softAPmacAddress(); page += F("
 
MQTT Host"); page += sysCfg.mqtt_host; page += F("
MQTT Port"); page += String(sysCfg.mqtt_port); page += F("
MQTT Client &
 Fallback Topic
"); page += MQTTClient; page += F("
MQTT User"); page += sysCfg.mqtt_user; page += F("
MQTT Password"); page += sysCfg.mqtt_pwd; page += F("
MQTT Topic"); page += sysCfg.mqtt_topic; page += F("
MQTT Group Topic"); page += sysCfg.mqtt_grptopic; page += F("
MQTT Full Topic"); page += stopic; page += F("
MQTTDisabled
 
Emulation"); #ifdef USE_EMULATION if (EMUL_WEMO == sysCfg.flag.emulation) { page += F("Belkin WeMo"); } else if (EMUL_HUE == sysCfg.flag.emulation) { page += F("Hue Bridge"); } else { page += F("None"); } #else page += F("Disabled"); #endif // USE_EMULATION page += F("
mDNS Discovery"); #ifdef USE_DISCOVERY page += F("Enabled"); page += F("
mDNS Advertise"); #ifdef WEBSERVER_ADVERTISE page += F("Webserver"); #else page += F("Disabled"); #endif // WEBSERVER_ADVERTISE #else page += F("Disabled"); #endif // USE_DISCOVERY page += F("
 
ESP Chip id"); page += String(ESP.getChipId()); page += F("
Flash Chip id"); page += String(ESP.getFlashChipId()); page += F("
Flash size"); page += String(ESP.getFlashChipRealSize() / 1024); page += F("kB
Program flash size"); page += String(ESP.getFlashChipSize() / 1024); page += F("kB
Program size"); page += String(ESP.getSketchSize() / 1024); page += F("kB
Free program space"); page += String(ESP.getFreeSketchSpace() / 1024); page += F("kB
Free memory"); page += String(freeMem / 1024); page += F("kB
"); // page += F("
"); page += FPSTR(HTTP_BTN_MAIN); showPage(page); } void handleRestart() { if (httpUser()) { return; } addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Restarting")); String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), F("Info")); page += FPSTR(HTTP_MSG_RSTRT); if (HTTP_MANAGER == _httpflag) { _httpflag = HTTP_ADMIN; } else { page += FPSTR(HTTP_BTN_MAIN); } showPage(page); restartflag = 2; } /********************************************************************************************/ void handleNotFound() { if (captivePortal()) { // If captive portal redirect instead of displaying the error page. return; } #ifdef USE_EMULATION String path = webServer->uri(); if ((EMUL_HUE == sysCfg.flag.emulation) && (path.startsWith("/api"))) { handle_hue_api(&path); } else #endif // USE_EMULATION { String message = F("File Not Found\n\nURI: "); message += webServer->uri(); message += F("\nMethod: "); message += ( webServer->method() == HTTP_GET ) ? F("GET") : F("POST"); message += F("\nArguments: "); message += webServer->args(); message += F("\n"); for ( uint8_t i = 0; i < webServer->args(); i++ ) { message += " " + webServer->argName ( i ) + ": " + webServer->arg ( i ) + "\n"; } webServer->sendHeader(FPSTR(HDR_CCNTL), FPSTR(HDR_REVAL)); webServer->sendHeader(F("Pragma"), F("no-cache")); webServer->sendHeader(F("Expires"), F("-1")); webServer->send(404, F("text/plain"), message); } } /* Redirect to captive portal if we got a request for another domain. Return true in that case so the page handler do not try to handle the request again. */ boolean captivePortal() { if ((HTTP_MANAGER == _httpflag) && !isIp(webServer->hostHeader())) { addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Request redirected to captive portal")); webServer->sendHeader(F("Location"), String("http://") + webServer->client().localIP().toString(), true); webServer->send(302, F("text/plain"), ""); // Empty content inhibits Content-length header so we have to close the socket ourselves. webServer->client().stop(); // Stop is needed because we sent no content length return true; } return false; } /** Is this an IP? */ boolean isIp(String str) { for (uint16_t i = 0; i < str.length(); i++) { int c = str.charAt(i); if (c != '.' && (c < '0' || c > '9')) { return false; } } return true; } #endif // USE_WEBSERVER