/* Copyright (c) 2017 Theo Arends. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #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 (" MQTT_TOPIC ")

"; 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_CMND[] PROGMEM = "


" "
" "
" // "
" "
"; const char HTTP_TABLE100[] PROGMEM = ""; const char HTTP_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 = "" "" ""; #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, _uploaderror = 0, _uploadfiletype, _colcount; void startWebserver(int type, IPAddress ipweb) { char log[LOGSZ]; if (!_httpflag) { if (!webServer) { webServer = new ESP8266WebServer((type==HTTP_MANAGER)?80:WEB_PORT); webServer->on("/", handleRoot); webServer->on("/cn", handleConfig); webServer->on("/md", handleModule); webServer->on("/w1", handleWifi1); webServer->on("/w0", handleWifi0); if (sysCfg.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 (sysCfg.emulation == EMUL_WEMO) { webServer->on("/upnp/control/basicevent1", HTTP_POST, handleUPnPevent); webServer->on("/eventservice.xml", handleUPnPservice); webServer->on("/setup.xml", handleUPnPsetupWemo); } if (sysCfg.emulation == EMUL_HUE) { 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 ((WiFi.status() == WL_CONNECTED) && (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((_httpflag == HTTP_ADMIN) && (sysCfg.web_password[0] != 0) && !webServer->authenticate(WEB_USERNAME, sysCfg.web_password)) { return webServer->requestAuthentication(); } page.replace("{ha}", my_module.name); page.replace("{h}", sysCfg.friendlyname[0]); if (_httpflag == HTTP_MANAGER) { if (WIFI_configCounter()) { page.replace("", ""); page += FPSTR(HTTP_COUNTER); } } page += FPSTR(HTTP_END); webServer->sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); webServer->sendHeader("Pragma", "no-cache"); webServer->sendHeader("Expires", "-1"); webServer->send(200, "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 (_httpflag == HTTP_MANAGER) { handleWifi0(); } else { char stemp[10], line[100]; String page = FPSTR(HTTP_HEAD); page.replace("{v}", "Main menu"); page.replace("", ""); page += F("
"); if (Maxdevice) { if (sysCfg.module == SONOFF_LED) { 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("
%s Temperature%s°%c
%s Humidity%s%
%s Pressure%s hPa
%s Light%d%
%s Noise%d%
%s Air quality%d%
"); } if (_httpflag == HTTP_ADMIN) { page += FPSTR(HTTP_BTN_MENU1); page += FPSTR(HTTP_BTN_RSTRT); } showPage(page); } } void handleAjax2() { char svalue[16]; 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 = ""; if (hlw_flg) tpage += hlw_webPresent(); if (sysCfg.module == SONOFF_SC) 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_type) 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), (power & (0x01 << (idx -1))) ? "ON" : "OFF"); 100 / Maxdevice, 70 - (Maxdevice * 8), getStateText(bitRead(power, idx -1))); page += line; } page += F(""); } /* * Will interrupt user action when selected if (sysCfg.module == SONOFF_LED) { snprintf_P(line, sizeof(line), PSTR(""), sysCfg.led_dimmer[0]); page += line; } */ webServer->sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); webServer->sendHeader("Pragma", "no-cache"); webServer->sendHeader("Expires", "-1"); webServer->send(200, "text/plain", page); } boolean httpUser() { boolean status = (_httpflag == HTTP_USER); 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("{v}", "Configuration"); page += FPSTR(HTTP_BTN_MENU2); if (sysCfg.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 (val == GPIO_I2C_SCL) return true; if (val == GPIO_I2C_SDA) return true; #endif #ifndef USE_WS2812 if (val == GPIO_WS2812) return true; #endif #ifndef USE_IR_REMOTE if (val == GPIO_IRSEND) 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("{v}", "Config module"); page += FPSTR(HTTP_FORM_MODULE); snprintf_P(stemp, sizeof(stemp), modules[MODULE].name); page.replace("{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,\"