/* xdrv_01_webserver.ino - webserver for Sonoff-Tasmota Copyright (C) 2019 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 * Based on source by AlexT (https://github.com/tzapu) \*********************************************************************************************/ #define XDRV_01 1 #ifndef WIFI_SOFT_AP_CHANNEL #define WIFI_SOFT_AP_CHANNEL 1 // Soft Access Point Channel number between 1 and 11 as used by SmartConfig web GUI #endif #define HTTP_REFRESH_TIME 2345 // milliseconds #define HTTP_RESTART_RECONNECT_TIME 9000 // milliseconds #define HTTP_OTA_RESTART_RECONNECT_TIME 20000 // milliseconds #include #include #ifdef USE_RF_FLASH uint8_t *efm8bb1_update = NULL; #endif // USE_RF_FLASH enum UploadTypes { UPL_TASMOTA, UPL_SETTINGS, UPL_EFM8BB1 }; const char HTTP_HEAD[] PROGMEM = "" "" "" "" "{h} - {v}" ""; const char HTTP_SCRIPT_ROOT[] PROGMEM = "function la(p){" "var a='';" "if(la.arguments.length==1){" "a=p;" "clearTimeout(lt);" "}" "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 s=x.responseText.replace(/{t}/g,\"\").replace(/{s}/g,\"\").replace(/{c}/g,\"%'>
"; // Local OTA upgrade requires more time to complete cp: before web ui should be reloaded const char HTTP_SCRIPT_RELOAD_OTA[] PROGMEM = "setTimeout(function(){location.href='.';}," STR(HTTP_OTA_RESTART_RECONNECT_TIME) ");" ""; const char HTTP_SCRIPT_CONSOL[] PROGMEM = "var sn=0;" // Scroll position "var id=0;" // Get most of weblog initially "function l(p){" // Console log and command service "var c,o,t;" "clearTimeout(lt);" "o='';" "t=eb('t1');" "if(p==1){" "c=eb('c1');" "o='&c1='+encodeURIComponent(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.responseText.split(/\1/);" "id=d.shift();" "if(d.shift()==0){t.value='';}" "z=d.shift();" "if(z.length>0){t.value+=z;}" "t.scrollTop=99999;" "sn=t.scrollTop;" "}" "};" "x.open('GET','ax?c2='+id+o,true);" "x.send();" "}" "lt=setTimeout(l,{a});" "return false;" "}" "window.onload=l;" ""; const char HTTP_SCRIPT_MODULE_TEMPLATE[] PROGMEM = "var os;" "function sk(s,g){" // s = value, g = id and name "var o=os.replace(/}1/g,\"
"); uint8_t idx = 0; for (uint8_t i = 0; i < 4; i++) { if (idx > 0) { page += F(""); } for (uint8_t j = 0; j < 4; j++) { idx++; snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR(""), idx, idx); // ?k is related to WebGetArg("k", tmp, sizeof(tmp)); page += mqtt_data; } } page += F("
\").replace(/{m}/g,\"\").replace(/{e}/g,\"
\").replace(/}2/g,\"\");" "eb('i').innerHTML=s;" "}" "window.onload=i;" ""; const char HTTP_HEAD_STYLE[] PROGMEM = "" "" "" "" "
" #ifdef FIRMWARE_MINIMAL "

" D_MINIMAL_FIRMWARE_PLEASE_UPGRADE "

" #endif "
" #ifdef LANGUAGE_MODULE_NAME "

" D_MODULE " {ha

" #else "

{ha " D_MODULE "

" #endif "

{h}

{j}
"; const char HTTP_MSG_SLIDER1[] PROGMEM = "
" D_COLDLIGHT "" D_WARMLIGHT "
" "
"; const char HTTP_MSG_SLIDER2[] PROGMEM = "
" D_DARKLIGHT "" D_BRIGHTLIGHT "
" "
"; const char HTTP_MSG_RSTRT[] PROGMEM = "
" D_DEVICE_WILL_RESTART "

"; const char HTTP_BTN_MENU1[] PROGMEM = "
" #ifndef FIRMWARE_MINIMAL "
" "

" #endif "
" "

"; const char HTTP_BTN_RSTRT[] PROGMEM = "
"; const char HTTP_BTN_MENU_MODULE[] PROGMEM = "

" "

"; const char HTTP_BTN_RESET[] PROGMEM = "
" "
"; const char HTTP_BTN_MENU4[] PROGMEM = "

" "

" "

"; const char HTTP_BTN_MENU5[] PROGMEM = "

" "

"; const char HTTP_BTN_MAIN[] PROGMEM = "
" "
"; const char HTTP_FORM_LOGIN[] PROGMEM = "
" "
" D_USER "

" "
" D_PASSWORD "

" "
" "
"; const char HTTP_BTN_CONF[] PROGMEM = "
" "
"; const char HTTP_FORM_TEMPLATE[] PROGMEM = "
 " D_TEMPLATE_PARAMETERS " " "
" "

" D_TEMPLATE_NAME "

" "

" D_BASE_TYPE "

"; const char HTTP_FORM_TEMPLATE_FLAG[] PROGMEM = "

" // Keep close so do not use
"
 " D_TEMPLATE_FLAGS " 

" "" D_ALLOW_ADC0 "
" "

"; const char HTTP_FORM_MODULE[] PROGMEM = "
 " D_MODULE_PARAMETERS " " "" "

" D_MODULE_TYPE " ({mt)

"; const char HTTP_LNK_ITEM[] PROGMEM = "
{v} ({w}) {i} {r}%
"; const char HTTP_LNK_SCAN[] PROGMEM = "
"; const char HTTP_FORM_WIFI[] PROGMEM = "
 " D_WIFI_PARAMETERS " " "" "

" D_AP1_SSID " (" STA_SSID1 ")

" "

" D_AP1_PASSWORD "

" "

" D_AP2_SSID " (" STA_SSID2 ")

" "

" D_AP2_PASSWORD "

" "

" D_HOSTNAME " (" WIFI_HOSTNAME ")

"; const char HTTP_FORM_LOG1[] PROGMEM = "
 " D_LOGGING_PARAMETERS " " ""; const char HTTP_FORM_LOG2[] PROGMEM = "

{b0 ({b1)

"; const char HTTP_FORM_LOG3[] PROGMEM = "

" D_SYSLOG_HOST " (" SYS_LOG_HOST ")

" "

" D_SYSLOG_PORT " (" STR(SYS_LOG_PORT) ")

" "

" D_TELEMETRY_PERIOD " (" STR(TELE_PERIOD) ")

"; const char HTTP_FORM_OTHER[] PROGMEM = "
 " D_OTHER_PARAMETERS " " "" "

" "
 " D_TEMPLATE " " "

" "

" D_ACTIVATE "

" "
" "
" "" D_WEB_ADMIN_PASSWORD "

" "
" "" D_MQTT_ENABLE "
" "
"; const char HTTP_FORM_OTHER2[] PROGMEM = "" D_FRIENDLY_NAME " {1 ({2)

"; #ifdef USE_EMULATION const char HTTP_FORM_OTHER3a[] PROGMEM = "

" // Keep close to Friendlynames so do not use
"
 " D_EMULATION " 

"; const char HTTP_FORM_OTHER3b[] PROGMEM = "{3{4
"; // Different id only used for labels const char HTTP_FORM_OTHER3c[] PROGMEM = "

"; #endif // USE_EMULATION const char HTTP_FORM_END[] PROGMEM = "
" "" "
"; const char HTTP_FORM_RST[] PROGMEM = "
" "
 " D_RESTORE_CONFIGURATION " "; const char HTTP_FORM_UPG[] PROGMEM = "
" "
 " D_UPGRADE_BY_WEBSERVER " " "
" "
" D_OTA_URL "

" "
" "


" "
 " D_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_END[] PROGMEM = "
" "" "" "" ""; const char HTTP_DEVICE_CONTROL[] PROGMEM = ""; // ?o is related to WebGetArg("o", tmp, sizeof(tmp)); const char HTTP_DEVICE_STATE[] PROGMEM = "%s"; // {c} = %'>
0) && (source < SRC_MAX)) { char stemp1[20]; snprintf_P(log_data, sizeof(log_data), PSTR("SRC: %s from %s"), GetTextIndexed(stemp1, sizeof(stemp1), source, kCommandSource), WebServer->client().remoteIP().toString().c_str()); AddLog(LOG_LEVEL_DEBUG); } } void ExecuteWebCommand(char* svalue, int source) { ShowWebSource(source); ExecuteCommand(svalue, SRC_IGNORE); } void StartWebserver(int type, IPAddress ipweb) { if (!Settings.web_refresh) { Settings.web_refresh = HTTP_REFRESH_TIME; } if (!webserver_state) { if (!WebServer) { WebServer = new ESP8266WebServer((HTTP_MANAGER==type || HTTP_MANAGER_RESET_ONLY == type) ? 80 : WEB_PORT); WebServer->on("/", HandleRoot); WebServer->onNotFound(HandleNotFound); #ifndef FIRMWARE_MINIMAL WebServer->on("/rt", HandleResetConfiguration); #endif // FIRMWARE_MINIMAL if(HTTP_MANAGER_RESET_ONLY != type){ WebServer->on("/up", HandleUpgradeFirmware); WebServer->on("/u1", HandleUpgradeFirmwareStart); // OTA WebServer->on("/u2", HTTP_POST, HandleUploadDone, HandleUploadLoop); WebServer->on("/u2", HTTP_OPTIONS, HandlePreflightRequest); WebServer->on("/cs", HandleConsole); WebServer->on("/ax", HandleAjaxConsoleRefresh); WebServer->on("/ay", HandleAjaxStatusRefresh); WebServer->on("/cm", HandleHttpCommand); #ifndef FIRMWARE_MINIMAL WebServer->on("/cn", HandleConfiguration); WebServer->on("/md", HandleModuleConfiguration); WebServer->on("/wi", HandleWifiConfiguration); WebServer->on("/lg", HandleLoggingConfiguration); WebServer->on("/tp", HandleTemplateConfiguration); WebServer->on("/co", HandleOtherConfiguration); WebServer->on("/dl", HandleBackupConfiguration); WebServer->on("/rs", HandleRestoreConfiguration); WebServer->on("/rt", HandleResetConfiguration); WebServer->on("/in", HandleInformation); #ifdef USE_EMULATION HueWemoAddHandlers(); #endif // USE_EMULATION XdrvCall(FUNC_WEB_ADD_HANDLER); XsnsCall(FUNC_WEB_ADD_HANDLER); #endif // Not FIRMWARE_MINIMAL } } reset_web_log_flag = false; WebServer->begin(); // Web server start } if (webserver_state != type) { snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_HTTP D_WEBSERVER_ACTIVE_ON " %s%s " D_WITH_IP_ADDRESS " %s"), my_hostname, (mdns_begun) ? ".local" : "", ipweb.toString().c_str()); AddLog(LOG_LEVEL_INFO); } if (type) { webserver_state = type; } } void StopWebserver(void) { if (webserver_state) { WebServer->close(); webserver_state = HTTP_OFF; AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_STOPPED)); } } void WifiManagerBegin(bool reset_only) { // setup AP if (!global_state.wifi_down) { WiFi.mode(WIFI_AP_STA); AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI D_WIFIMANAGER_SET_ACCESSPOINT_AND_STATION)); } else { WiFi.mode(WIFI_AP); AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI D_WIFIMANAGER_SET_ACCESSPOINT)); } StopWebserver(); DnsServer = new DNSServer(); int channel = WIFI_SOFT_AP_CHANNEL; if ((channel < 1) || (channel > 13)) { channel = 1; } WiFi.softAP(my_hostname, NULL, channel); 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((reset_only ? HTTP_MANAGER_RESET_ONLY : HTTP_MANAGER), WiFi.softAPIP()); } void PollDnsWebserver(void) { if (DnsServer) { DnsServer->processNextRequest(); } if (WebServer) { WebServer->handleClient(); } } /*********************************************************************************************/ void SetHeader(void) { WebServer->sendHeader(F("Cache-Control"), F("no-cache, no-store, must-revalidate")); WebServer->sendHeader(F("Pragma"), F("no-cache")); WebServer->sendHeader(F("Expires"), F("-1")); #ifndef ARDUINO_ESP8266_RELEASE_2_3_0 WebServer->sendHeader(F("Access-Control-Allow-Origin"), F("*")); #endif } bool WebAuthenticate(void) { if (Settings.web_password[0] != 0 && HTTP_MANAGER_RESET_ONLY != webserver_state) { return WebServer->authenticate(WEB_USERNAME, Settings.web_password); } else { return true; } } void ShowPage(String &page, bool auth) { if (auth && (Settings.web_password[0] != 0) && !WebServer->authenticate(WEB_USERNAME, Settings.web_password)) { return WebServer->requestAuthentication(); } page.replace(F("{a}"), String(Settings.web_refresh)); page.replace(F("{ha"), ModuleName()); page.replace(F("{h}"), Settings.friendlyname[0]); String info = ""; if (Settings.flag3.gui_hostname_ip) { uint8_t more_ips = 0; info += F("

"); info += my_hostname; if (mdns_begun) { info += F(".local"); } info += F(" ("); if (static_cast(WiFi.localIP()) != 0) { info += WiFi.localIP().toString(); more_ips++; } if (static_cast(WiFi.softAPIP()) != 0) { if (more_ips) { info += F(", "); } info += WiFi.softAPIP().toString(); } info += F(")

"); } page.replace(F("{j}"), info); if (WifiIsInManagerMode()) { if (WifiConfigCounter()) { page.replace(F(""), FPSTR(HTTP_SCRIPT_COUNTER)); page += FPSTR(HTTP_COUNTER); } } page += FPSTR(HTTP_END); page.replace(F("{mv"), my_version); SetHeader(); ShowFreeMem(PSTR("ShowPage")); WebServer->send(200, FPSTR(HDR_CTYPE_HTML), page); } void ShowPage(String &page) { ShowPage(page, true); } /*-------------------------------------------------------------------------------------------*/ void WebRestart(uint8_t type) { // type 0 = restart // type 1 = restart after config change // type 2 = restart after config change with possible ip address change too AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_RESTART); String page = FPSTR(HTTP_HEAD); page += FPSTR(HTTP_HEAD_STYLE); if (type) { page.replace(F("{v}"), FPSTR(S_SAVE_CONFIGURATION)); page += F("
" D_CONFIGURATION_SAVED "
"); if (2 == type) { page += F("
" D_TRYING_TO_CONNECT "
"); } page += F("
"); } else { page.replace(F("{v}"), FPSTR(S_RESTART)); } bool reset_only = (HTTP_MANAGER_RESET_ONLY == webserver_state); page += FPSTR(HTTP_MSG_RSTRT); if (HTTP_MANAGER == webserver_state || reset_only) { webserver_state = HTTP_ADMIN; } else { page += FPSTR(HTTP_BTN_MAIN); } page.replace(F(""), FPSTR(HTTP_SCRIPT_RELOAD)); ShowPage(page, !reset_only); ShowWebSource(SRC_WEBGUI); restart_flag = 2; } /*********************************************************************************************/ void HandleWifiLogin(void) { String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), FPSTR( D_CONFIGURE_WIFI )); page += FPSTR(HTTP_HEAD_STYLE); page += FPSTR(HTTP_FORM_LOGIN); ShowPage(page, false); // false means show page no matter if the client has or has not credentials } void HandleRoot(void) { AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_MAIN_MENU); if (CaptivePortal()) { return; } // If captive portal redirect instead of displaying the page. if ( WebServer->hasArg("rstrt") ) { WebRestart(0); return; } if (WifiIsInManagerMode()) { #ifndef FIRMWARE_MINIMAL if ((Settings.web_password[0] != 0) && !(WebServer->hasArg("USER1")) && !(WebServer->hasArg("PASS1")) && HTTP_MANAGER_RESET_ONLY != webserver_state) { HandleWifiLogin(); } else { if (!(Settings.web_password[0] != 0) || ((WebServer->arg("USER1") == WEB_USERNAME ) && (WebServer->arg("PASS1") == Settings.web_password ) || HTTP_MANAGER_RESET_ONLY == webserver_state)) { HandleWifiConfiguration(); } else { // wrong user and pass HandleWifiLogin(); } } #endif // Not FIRMWARE_MINIMAL } else { char stemp[10]; String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), FPSTR(S_MAIN_MENU)); page += FPSTR(HTTP_SCRIPT_ROOT); page += FPSTR(HTTP_HEAD_STYLE); page += F("
"); if (devices_present) { if (light_type) { if ((LST_COLDWARM == (light_type &7)) || (LST_RGBWC == (light_type &7))) { snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_MSG_SLIDER1, LightGetColorTemp()); page += mqtt_data; } snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_MSG_SLIDER2, Settings.light_dimmer); page += mqtt_data; } page += FPSTR(HTTP_TABLE100); page += F("
"); if (SONOFF_IFAN02 == my_module_type) { snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_DEVICE_CONTROL, 36, 1, D_BUTTON_TOGGLE, ""); page += mqtt_data; for (uint8_t i = 0; i < MAX_FAN_SPEED; i++) { snprintf_P(stemp, sizeof(stemp), PSTR("%d"), i); snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_DEVICE_CONTROL, 16, i +2, stemp, ""); page += mqtt_data; } } else { for (uint8_t idx = 1; idx <= devices_present; idx++) { snprintf_P(stemp, sizeof(stemp), PSTR(" %d"), idx); snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_DEVICE_CONTROL, 100 / devices_present, idx, (devices_present < 5) ? D_BUTTON_TOGGLE : "", (devices_present > 1) ? stemp : ""); page += mqtt_data; } } page += F("
%s
"); } if (SONOFF_BRIDGE == my_module_type) { page += FPSTR(HTTP_TABLE100); page += F("
"); } #ifndef FIRMWARE_MINIMAL mqtt_data[0] = '\0'; XdrvCall(FUNC_WEB_ADD_MAIN_BUTTON); XsnsCall(FUNC_WEB_ADD_MAIN_BUTTON); page += String(mqtt_data); #endif // Not FIRMWARE_MINIMAL if (HTTP_ADMIN == webserver_state) { page += FPSTR(HTTP_BTN_MENU1); page += FPSTR(HTTP_BTN_RSTRT); } ShowPage(page); } } void HandleAjaxStatusRefresh(void) { if (!WebAuthenticate()) { return WebServer->requestAuthentication(); } char svalue[80]; char tmp[100]; WebGetArg("o", tmp, sizeof(tmp)); if (strlen(tmp)) { ShowWebSource(SRC_WEBGUI); uint8_t device = atoi(tmp); if (SONOFF_IFAN02 == my_module_type) { if (device < 2) { ExecuteCommandPower(1, POWER_TOGGLE, SRC_IGNORE); } else { snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_FANSPEED " %d"), device -2); ExecuteCommand(svalue, SRC_WEBGUI); } } else { ExecuteCommandPower(device, POWER_TOGGLE, SRC_IGNORE); } } WebGetArg("d", tmp, sizeof(tmp)); if (strlen(tmp)) { snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_DIMMER " %s"), tmp); ExecuteWebCommand(svalue, SRC_WEBGUI); } WebGetArg("t", tmp, sizeof(tmp)); if (strlen(tmp)) { snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_COLORTEMPERATURE " %s"), tmp); ExecuteWebCommand(svalue, SRC_WEBGUI); } WebGetArg("k", tmp, sizeof(tmp)); if (strlen(tmp)) { snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_RFKEY "%s"), tmp); ExecuteWebCommand(svalue, SRC_WEBGUI); } snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{t}")); XsnsCall(FUNC_WEB_APPEND); if (D_DECIMAL_SEPARATOR[0] != '.') { for (uint16_t i = 0; i < strlen(mqtt_data); i++) { if ('.' == mqtt_data[i]) { mqtt_data[i] = D_DECIMAL_SEPARATOR[0]; } } } snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s"), mqtt_data); if (devices_present) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s{t}"), mqtt_data); uint8_t fsize = (devices_present < 5) ? 70 - (devices_present * 8) : 32; if (SONOFF_IFAN02 == my_module_type) { snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_DEVICE_STATE, mqtt_data, 36, (bitRead(power, 0)) ? "bold" : "normal", 54, GetStateText(bitRead(power, 0))); uint8_t fanspeed = GetFanspeed(); snprintf_P(svalue, sizeof(svalue), PSTR("%d"), fanspeed); snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_DEVICE_STATE, mqtt_data, 64, (fanspeed) ? "bold" : "normal", 54, (fanspeed) ? svalue : GetStateText(0)); } else { for (uint8_t idx = 1; idx <= devices_present; idx++) { snprintf_P(svalue, sizeof(svalue), PSTR("%d"), bitRead(power, idx -1)); snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_DEVICE_STATE, mqtt_data, 100 / devices_present, (bitRead(power, idx -1)) ? "bold" : "normal", fsize, (devices_present < 5) ? GetStateText(bitRead(power, idx -1)) : svalue); } } snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s"), mqtt_data); } WebServer->send(200, FPSTR(HDR_CTYPE_HTML), mqtt_data); } bool HttpCheckPriviledgedAccess(bool autorequestauth = true) { if (HTTP_USER == webserver_state) { HandleRoot(); return false; } if (autorequestauth && !WebAuthenticate()) { WebServer->requestAuthentication(); return false; } return true; } /*-------------------------------------------------------------------------------------------*/ #ifndef FIRMWARE_MINIMAL void HandleConfiguration(void) { if (!HttpCheckPriviledgedAccess()) { return; } AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURATION); String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), FPSTR(S_CONFIGURATION)); page += FPSTR(HTTP_HEAD_STYLE); page += FPSTR(HTTP_BTN_MENU_MODULE); mqtt_data[0] = '\0'; XdrvCall(FUNC_WEB_ADD_BUTTON); XsnsCall(FUNC_WEB_ADD_BUTTON); page += String(mqtt_data); page += FPSTR(HTTP_BTN_MENU4); page += FPSTR(HTTP_BTN_RESET); page += FPSTR(HTTP_BTN_MENU5); page += FPSTR(HTTP_BTN_MAIN); ShowPage(page); } /*-------------------------------------------------------------------------------------------*/ void HandleTemplateConfiguration(void) { if (!HttpCheckPriviledgedAccess()) { return; } if (WebServer->hasArg("save")) { TemplateSaveSettings(); WebRestart(1); return; } char stemp[20]; if (WebServer->hasArg("m")) { String page = ""; for (uint8_t i = 0; i < MAXMODULE; i++) { // "}1'%d'>%s (%d)}2" - "}1'0'>Sonoff Basic (1)}2" uint8_t midx = pgm_read_byte(kModuleNiceList + i); snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SCRIPT_MODULE3, midx, AnyModuleName(midx).c_str(), midx +1); page += mqtt_data; } WebServer->send(200, FPSTR(HDR_CTYPE_PLAIN), page); return; } WebGetArg("t", stemp, sizeof(stemp)); if (strlen(stemp)) { uint8_t module = atoi(stemp); uint8_t module_save = Settings.module; Settings.module = module; myio cmodule; ModuleGpios(&cmodule); gpio_flag flag = ModuleFlag(); Settings.module = module_save; String page = AnyModuleName(module); // NAME: Generic for (uint8_t i = 0; i < sizeof(kGpioNiceList); i++) { // GPIO: }1'0'>None (0)}2}1'17'>Button1 (17)}2... if (1 == i) { page += F("}1'255'>" D_SENSOR_USER " (255)}2"); // }1'255'>User (255)}2 } uint8_t midx = pgm_read_byte(kGpioNiceList + i); snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SCRIPT_MODULE3, midx, GetTextIndexed(stemp, sizeof(stemp), midx, kSensorNames), midx); page += mqtt_data; } page += F("}3"); // }3 mqtt_data[0] = '\0'; for (uint8_t i = 0; i < sizeof(cmodule); i++) { // 17,148,29,149,7,255,255,255,138,255,139,255,255 if ((i < 6) || ((i > 8) && (i != 11))) { // Ignore flash pins GPIO06, 7, 8 and 11 snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%s%d"), mqtt_data, (i>0)?",":"", cmodule.io[i]); } } snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,%d,%d"), mqtt_data, flag, Settings.user_template_base); // FLAG: ,1 BASE: ,17 page += mqtt_data; WebServer->send(200, FPSTR(HDR_CTYPE_PLAIN), page); return; } AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_TEMPLATE); String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), FPSTR(S_CONFIGURE_TEMPLATE)); page += FPSTR(HTTP_SCRIPT_MODULE_TEMPLATE); page += FPSTR(HTTP_SCRIPT_TEMPLATE); page += FPSTR(HTTP_HEAD_STYLE); page += FPSTR(HTTP_FORM_TEMPLATE); page += F("
"); for (uint8_t i = 0; i < 17; i++) { if ((i < 6) || ((i > 8) && (i != 11))) { // Ignore flash pins GPIO06, 7, 8 and 11 snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("" D_GPIO "%d"), (0==i)?" style='width:74px'":"", i, ((9==i)||(10==i))? "ESP8285" :"", (0==i)?" style='width:176px'":"", i, i); page += mqtt_data; } } page += F("
%s
"); page += FPSTR(HTTP_FORM_TEMPLATE_FLAG); page += FPSTR(HTTP_FORM_END); page += FPSTR(HTTP_BTN_CONF); ShowPage(page); } void TemplateSaveSettings(void) { char svalue[128]; char tmp[100]; char stemp[20]; WebGetArg("s1", tmp, sizeof(tmp)); // NAME snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_TEMPLATE " {\"" D_JSON_NAME "\":\"%s\",\"" D_JSON_GPIO "\":["), tmp); uint8_t j = 0; for (uint8_t i = 0; i < sizeof(Settings.user_template.gp); i++) { if (6 == i) { j = 9; } if (8 == i) { j = 12; } snprintf_P(stemp, sizeof(stemp), PSTR("g%d"), j); WebGetArg(stemp, tmp, sizeof(tmp)); // GPIO uint8_t gpio = atoi(tmp); snprintf_P(svalue, sizeof(svalue), PSTR("%s%s%d"), svalue, (i>0)?",":"", gpio); j++; } uint8_t flag = 0; for (uint8_t i = 0; i < GPIO_FLAG_USED; i++) { snprintf_P(stemp, sizeof(stemp), PSTR("c%d"), i); uint8_t state = WebServer->hasArg(stemp) << i; // FLAG flag += state; } WebGetArg("g99", tmp, sizeof(tmp)); // BASE uint8_t base = atoi(tmp) +1; snprintf_P(svalue, sizeof(svalue), PSTR("%s],\"" D_JSON_FLAG "\":%d,\"" D_JSON_BASE "\":%d}"), svalue, flag, base); ExecuteWebCommand(svalue, SRC_WEBGUI); } /*-------------------------------------------------------------------------------------------*/ void HandleModuleConfiguration(void) { if (!HttpCheckPriviledgedAccess()) { return; } if (WebServer->hasArg("save")) { ModuleSaveSettings(); WebRestart(1); return; } char stemp[20]; uint8_t midx; myio cmodule; ModuleGpios(&cmodule); if (WebServer->hasArg("m")) { String page = ""; uint8_t vidx = 0; for (uint8_t i = 0; i <= MAXMODULE; i++) { // "}1'%d'>%s (%d)}2" - "}1'255'>UserTemplate (0)}2" - "}1'0'>Sonoff Basic (1)}2" if (0 == i) { midx = USER_MODULE; vidx = 0; } else { midx = pgm_read_byte(kModuleNiceList + i -1); vidx = midx +1; } snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SCRIPT_MODULE3, midx, AnyModuleName(midx).c_str(), vidx); page += mqtt_data; } WebServer->send(200, FPSTR(HDR_CTYPE_PLAIN), page); return; } if (WebServer->hasArg("g")) { String page = ""; for (uint8_t j = 0; j < sizeof(kGpioNiceList); j++) { midx = pgm_read_byte(kGpioNiceList + j); if (!GetUsedInModule(midx, cmodule.io)) { snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SCRIPT_MODULE3, midx, GetTextIndexed(stemp, sizeof(stemp), midx, kSensorNames), midx); page += mqtt_data; } } WebServer->send(200, FPSTR(HDR_CTYPE_PLAIN), page); return; } AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_MODULE); String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), FPSTR(S_CONFIGURE_MODULE)); page += FPSTR(HTTP_SCRIPT_MODULE_TEMPLATE); page += FPSTR(HTTP_SCRIPT_MODULE1); page.replace(F("}4"), String(Settings.module)); for (uint8_t i = 0; i < sizeof(cmodule); i++) { if (ValidGPIO(i, cmodule.io[i])) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("sk(%d,%d);"), my_module.io[i], i); // g0 - g16 page += mqtt_data; } } page += FPSTR(HTTP_SCRIPT_MODULE2); page += FPSTR(HTTP_HEAD_STYLE); page += FPSTR(HTTP_FORM_MODULE); page.replace(F("{mt"), AnyModuleName(MODULE)); page += F("
"); for (uint8_t i = 0; i < sizeof(cmodule); i++) { if (ValidGPIO(i, cmodule.io[i])) { snprintf_P(stemp, 3, PINS_WEMOS +i*2); snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR(""), (WEMOS==my_module_type)?stemp:"", i, (0==i)? D_SENSOR_BUTTON "1":(1==i)? D_SERIAL_OUT :(3==i)? D_SERIAL_IN :((9==i)||(10==i))? "ESP8285" :(12==i)? D_SENSOR_RELAY "1":(13==i)? D_SENSOR_LED "1i":(14==i)? D_SENSOR :"", i, i); page += mqtt_data; } } page += F("
%s " D_GPIO "%d %s
"); page += FPSTR(HTTP_FORM_END); page += FPSTR(HTTP_BTN_CONF); ShowPage(page); } void ModuleSaveSettings(void) { char tmp[100]; char stemp[TOPSZ]; WebGetArg("g99", tmp, sizeof(tmp)); uint8_t new_module = (!strlen(tmp)) ? MODULE : atoi(tmp); Settings.last_module = Settings.module; Settings.module = new_module; SetModuleType(); myio cmodule; ModuleGpios(&cmodule); String gpios = ""; for (uint8_t i = 0; i < sizeof(cmodule); i++) { if (Settings.last_module != new_module) { Settings.my_gp.io[i] = GPIO_NONE; } else { if (ValidGPIO(i, cmodule.io[i])) { snprintf_P(stemp, sizeof(stemp), PSTR("g%d"), i); WebGetArg(stemp, tmp, sizeof(tmp)); Settings.my_gp.io[i] = (!strlen(tmp)) ? 0 : atoi(tmp); gpios += F(", " D_GPIO ); gpios += String(i); gpios += F(" "); gpios += String(Settings.my_gp.io[i]); } } } snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_MODULE "%s " D_CMND_MODULE "%s"), ModuleName().c_str(), gpios.c_str()); AddLog(LOG_LEVEL_INFO); } /*-------------------------------------------------------------------------------------------*/ String htmlEscape(String s) { s.replace("&", "&"); s.replace("<", "<"); s.replace(">", ">"); s.replace("\"", """); s.replace("'", "'"); s.replace("/", "/"); return s; } void HandleWifiConfiguration(void) { if (!HttpCheckPriviledgedAccess()) { return; } AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_WIFI); if (WebServer->hasArg("save") && HTTP_MANAGER_RESET_ONLY != webserver_state) { WifiSaveSettings(); WebRestart(2); return; } String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), FPSTR(S_CONFIGURE_WIFI)); page += FPSTR(HTTP_SCRIPT_WIFI); page += FPSTR(HTTP_HEAD_STYLE); if(HTTP_MANAGER_RESET_ONLY != webserver_state){ if (WebServer->hasArg("scan")) { #ifdef USE_EMULATION UdpDisconnect(); #endif // USE_EMULATION int n = WiFi.scanNetworks(); AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI D_SCAN_DONE)); if (0 == n) { AddLog_P(LOG_LEVEL_DEBUG, S_LOG_WIFI, S_NO_NETWORKS_FOUND); page += FPSTR(S_NO_NETWORKS_FOUND); page += F(". " D_REFRESH_TO_SCAN_AGAIN "."); } else { //sort networks int indices[n]; for (int i = 0; i < n; i++) { indices[i] = i; } // RSSI SORT for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (WiFi.RSSI(indices[j]) > WiFi.RSSI(indices[i])) { std::swap(indices[i], indices[j]); } } } // remove duplicates ( must be RSSI sorted ) if (remove_duplicate_access_points) { String cssid; for (int i = 0; i < n; i++) { if (-1 == indices[i]) { continue; } cssid = WiFi.SSID(indices[i]); for (int j = i + 1; j < n; j++) { if (cssid == WiFi.SSID(indices[j])) { snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_WIFI D_DUPLICATE_ACCESSPOINT " %s"), WiFi.SSID(indices[j]).c_str()); AddLog(LOG_LEVEL_DEBUG); indices[j] = -1; // set dup aps to index -1 } } } } //display networks in page for (int i = 0; i < n; i++) { if (-1 == indices[i]) { continue; } // skip dups snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_WIFI D_SSID " %s, " D_BSSID " %s, " D_CHANNEL " %d, " D_RSSI " %d"), WiFi.SSID(indices[i]).c_str(), WiFi.BSSIDstr(indices[i]).c_str(), WiFi.channel(indices[i]), WiFi.RSSI(indices[i])); AddLog(LOG_LEVEL_DEBUG); int quality = WifiGetRssiAsQuality(WiFi.RSSI(indices[i])); if (minimum_signal_quality == -1 || minimum_signal_quality < quality) { String item = FPSTR(HTTP_LNK_ITEM); String rssiQ; rssiQ += quality; item.replace(F("{v}"), htmlEscape(WiFi.SSID(indices[i]))); item.replace(F("{w}"), String(WiFi.channel(indices[i]))); item.replace(F("{r}"), rssiQ); uint8_t auth = WiFi.encryptionType(indices[i]); item.replace(F("{i}"), (ENC_TYPE_WEP == auth) ? F(D_WEP) : (ENC_TYPE_TKIP == auth) ? F(D_WPA_PSK) : (ENC_TYPE_CCMP == auth) ? F(D_WPA2_PSK) : (ENC_TYPE_AUTO == auth) ? F(D_AUTO) : F("")); page += item; delay(0); } else { AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI D_SKIPPING_LOW_QUALITY)); } } page += "
"; } } else { page += FPSTR(HTTP_LNK_SCAN); } page += FPSTR(HTTP_FORM_WIFI); page.replace(F("{h1"), Settings.hostname); page.replace(F("{s1"), Settings.sta_ssid[0]); page.replace(F("{s2"), Settings.sta_ssid[1]); page += FPSTR(HTTP_FORM_END); } if (WifiIsInManagerMode()) { page += FPSTR(HTTP_BTN_RSTRT); #ifndef FIRMWARE_MINIMAL page += FPSTR(HTTP_BTN_RESET); #endif // FIRMWARE_MINIMAL } else { page += FPSTR(HTTP_BTN_CONF); } // ShowPage(page); ShowPage(page, !(WifiIsInManagerMode())); } void WifiSaveSettings(void) { char tmp[100]; WebGetArg("h", tmp, sizeof(tmp)); strlcpy(Settings.hostname, (!strlen(tmp)) ? WIFI_HOSTNAME : tmp, sizeof(Settings.hostname)); if (strstr(Settings.hostname,"%")) { strlcpy(Settings.hostname, WIFI_HOSTNAME, sizeof(Settings.hostname)); } WebGetArg("s1", tmp, sizeof(tmp)); strlcpy(Settings.sta_ssid[0], (!strlen(tmp)) ? STA_SSID1 : tmp, sizeof(Settings.sta_ssid[0])); WebGetArg("s2", tmp, sizeof(tmp)); strlcpy(Settings.sta_ssid[1], (!strlen(tmp)) ? STA_SSID2 : tmp, sizeof(Settings.sta_ssid[1])); WebGetArg("p1", tmp, sizeof(tmp)); strlcpy(Settings.sta_pwd[0], (!strlen(tmp)) ? "" : (strlen(tmp) < 5) ? Settings.sta_pwd[0] : tmp, sizeof(Settings.sta_pwd[0])); WebGetArg("p2", tmp, sizeof(tmp)); strlcpy(Settings.sta_pwd[1], (!strlen(tmp)) ? "" : (strlen(tmp) < 5) ? Settings.sta_pwd[1] : tmp, sizeof(Settings.sta_pwd[1])); snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_WIFI D_CMND_HOSTNAME " %s, " D_CMND_SSID "1 %s, " D_CMND_SSID "2 %s"), Settings.hostname, Settings.sta_ssid[0], Settings.sta_ssid[1]); AddLog(LOG_LEVEL_INFO); } /*-------------------------------------------------------------------------------------------*/ void HandleLoggingConfiguration(void) { if (!HttpCheckPriviledgedAccess()) { return; } AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_LOGGING); if (WebServer->hasArg("save")) { LoggingSaveSettings(); HandleConfiguration(); return; } String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), FPSTR(S_CONFIGURE_LOGGING)); page += FPSTR(HTTP_HEAD_STYLE); page += FPSTR(HTTP_FORM_LOG1); for (uint8_t idx = 0; idx < 3; idx++) { page += FPSTR(HTTP_FORM_LOG2); switch (idx) { case 0: page.replace(F("{b0"), F(D_SERIAL_LOG_LEVEL)); page.replace(F("{b1"), STR(SERIAL_LOG_LEVEL)); page.replace(F("{b2"), F("ls")); for (uint8_t i = LOG_LEVEL_NONE; i < LOG_LEVEL_ALL; i++) { page.replace("{a" + String(i), (i == Settings.seriallog_level) ? F(" selected ") : F(" ")); } break; case 1: page.replace(F("{b0"), F(D_WEB_LOG_LEVEL)); page.replace(F("{b1"), STR(WEB_LOG_LEVEL)); page.replace(F("{b2"), F("lw")); for (uint8_t i = LOG_LEVEL_NONE; i < LOG_LEVEL_ALL; i++) { page.replace("{a" + String(i), (i == Settings.weblog_level) ? F(" selected ") : F(" ")); } break; case 2: page.replace(F("{b0"), F(D_SYS_LOG_LEVEL)); page.replace(F("{b1"), STR(SYS_LOG_LEVEL)); page.replace(F("{b2"), F("ll")); for (uint8_t i = LOG_LEVEL_NONE; i < LOG_LEVEL_ALL; i++) { page.replace("{a" + String(i), (i == Settings.syslog_level) ? F(" selected ") : F(" ")); } break; } } page += FPSTR(HTTP_FORM_LOG3); page.replace(F("{l2"), Settings.syslog_host); page.replace(F("{l3"), String(Settings.syslog_port)); page.replace(F("{l4"), String(Settings.tele_period)); page += FPSTR(HTTP_FORM_END); page += FPSTR(HTTP_BTN_CONF); ShowPage(page); } void LoggingSaveSettings(void) { char tmp[100]; WebGetArg("ls", tmp, sizeof(tmp)); Settings.seriallog_level = (!strlen(tmp)) ? SERIAL_LOG_LEVEL : atoi(tmp); WebGetArg("lw", tmp, sizeof(tmp)); Settings.weblog_level = (!strlen(tmp)) ? WEB_LOG_LEVEL : atoi(tmp); WebGetArg("ll", tmp, sizeof(tmp)); Settings.syslog_level = (!strlen(tmp)) ? SYS_LOG_LEVEL : atoi(tmp); syslog_level = Settings.syslog_level; syslog_timer = 0; WebGetArg("lh", tmp, sizeof(tmp)); strlcpy(Settings.syslog_host, (!strlen(tmp)) ? SYS_LOG_HOST : tmp, sizeof(Settings.syslog_host)); WebGetArg("lp", tmp, sizeof(tmp)); Settings.syslog_port = (!strlen(tmp)) ? SYS_LOG_PORT : atoi(tmp); WebGetArg("lt", tmp, sizeof(tmp)); Settings.tele_period = (!strlen(tmp)) ? TELE_PERIOD : atoi(tmp); if ((Settings.tele_period > 0) && (Settings.tele_period < 10)) { Settings.tele_period = 10; // Do not allow periods < 10 seconds } snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_LOG D_CMND_SERIALLOG " %d, " D_CMND_WEBLOG " %d, " D_CMND_SYSLOG " %d, " D_CMND_LOGHOST " %s, " D_CMND_LOGPORT " %d, " D_CMND_TELEPERIOD " %d"), Settings.seriallog_level, Settings.weblog_level, Settings.syslog_level, Settings.syslog_host, Settings.syslog_port, Settings.tele_period); AddLog(LOG_LEVEL_INFO); } /*-------------------------------------------------------------------------------------------*/ void HandleOtherConfiguration(void) { if (!HttpCheckPriviledgedAccess()) { return; } AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_OTHER); if (WebServer->hasArg("save")) { OtherSaveSettings(); WebRestart(1); return; } char stemp[40]; String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), FPSTR(S_CONFIGURE_OTHER)); page += FPSTR(HTTP_HEAD_STYLE); page += FPSTR(HTTP_FORM_OTHER); TemplateJson(); page.replace(F("{t1"), mqtt_data); page.replace(F("{t2"), (USER_MODULE == Settings.module) ? F(" checked disabled") : F("")); page.replace(F("{r1"), (Settings.flag.mqtt_enabled) ? F(" checked") : F("")); uint8_t maxfn = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : (!devices_present) ? 1 : devices_present; if (SONOFF_IFAN02 == my_module_type) { maxfn = 1; } for (uint8_t i = 0; i < maxfn; i++) { page += FPSTR(HTTP_FORM_OTHER2); page.replace(F("{1"), String(i +1)); snprintf_P(stemp, sizeof(stemp), PSTR(FRIENDLY_NAME"%d"), i +1); page.replace(F("{2"), (i) ? stemp : FRIENDLY_NAME); page.replace(F("{3"), Settings.friendlyname[i]); } #ifdef USE_EMULATION page += FPSTR(HTTP_FORM_OTHER3a); for (uint8_t i = 0; i < EMUL_MAX; i++) { page += FPSTR(HTTP_FORM_OTHER3b); page.replace(F("{1"), String(i)); page.replace(F("{2"), (i == Settings.flag2.emulation) ? F(" checked") : F("")); page.replace(F("{3"), (i == EMUL_NONE) ? F(D_NONE) : (i == EMUL_WEMO) ? F(D_BELKIN_WEMO) : F(D_HUE_BRIDGE)); page.replace(F("{4"), (i == EMUL_NONE) ? F("") : (i == EMUL_WEMO) ? F(" " D_SINGLE_DEVICE) : F(" " D_MULTI_DEVICE)); } page += FPSTR(HTTP_FORM_OTHER3c); #endif // USE_EMULATION page += FPSTR(HTTP_FORM_END); page += FPSTR(HTTP_BTN_CONF); ShowPage(page); } void OtherSaveSettings(void) { char tmp[128]; char stemp[TOPSZ]; char stemp2[TOPSZ]; WebGetArg("p1", tmp, sizeof(tmp)); strlcpy(Settings.web_password, (!strlen(tmp)) ? "" : (strchr(tmp,'*')) ? Settings.web_password : tmp, sizeof(Settings.web_password)); Settings.flag.mqtt_enabled = WebServer->hasArg("b1"); #ifdef USE_EMULATION WebGetArg("b2", tmp, sizeof(tmp)); Settings.flag2.emulation = (!strlen(tmp)) ? 0 : atoi(tmp); #endif // USE_EMULATION snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_OTHER D_MQTT_ENABLE " %s, " D_CMND_EMULATION " %d, " D_CMND_FRIENDLYNAME), GetStateText(Settings.flag.mqtt_enabled), Settings.flag2.emulation); for (uint8_t i = 0; i < MAX_FRIENDLYNAMES; i++) { snprintf_P(stemp, sizeof(stemp), PSTR("a%d"), i +1); WebGetArg(stemp, tmp, sizeof(tmp)); snprintf_P(stemp2, sizeof(stemp2), PSTR(FRIENDLY_NAME"%d"), i +1); strlcpy(Settings.friendlyname[i], (!strlen(tmp)) ? (i) ? stemp2 : FRIENDLY_NAME : tmp, sizeof(Settings.friendlyname[i])); snprintf_P(log_data, sizeof(log_data), PSTR("%s%s %s"), log_data, (i) ? "," : "", Settings.friendlyname[i]); } AddLog(LOG_LEVEL_INFO); WebGetArg("t1", tmp, sizeof(tmp)); if (strlen(tmp)) { // {"NAME":"12345678901234","GPIO":[255,255,255,255,255,255,255,255,255,255,255,255,255],"FLAG":255,"BASE":255} char svalue[128]; snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_TEMPLATE " %s"), tmp); ExecuteWebCommand(svalue, SRC_WEBGUI); if (WebServer->hasArg("t2")) { snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_MODULE " 0")); ExecuteWebCommand(svalue, SRC_WEBGUI); } } } /*-------------------------------------------------------------------------------------------*/ void HandleBackupConfiguration(void) { if (!HttpCheckPriviledgedAccess()) { return; } AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_BACKUP_CONFIGURATION)); if (!SettingsBufferAlloc()) { return; } WiFiClient myClient = WebServer->client(); WebServer->setContentLength(sizeof(Settings)); char attachment[100]; char friendlyname[sizeof(Settings.friendlyname[0])]; snprintf_P(attachment, sizeof(attachment), PSTR("attachment; filename=Config_%s_%s.dmp"), NoAlNumToUnderscore(friendlyname, Settings.friendlyname[0]), my_version); WebServer->sendHeader(F("Content-Disposition"), attachment); WebServer->send(200, FPSTR(HDR_CTYPE_STREAM), ""); uint16_t cfg_crc = Settings.cfg_crc; Settings.cfg_crc = GetSettingsCrc(); // Calculate crc (again) as it might be wrong when savedata = 0 (#3918) memcpy(settings_buffer, &Settings, sizeof(Settings)); if (config_xor_on_set) { for (uint16_t i = 2; i < sizeof(Settings); i++) { settings_buffer[i] ^= (config_xor_on_set +i); } } #ifdef ARDUINO_ESP8266_RELEASE_2_3_0 size_t written = myClient.write((const char*)settings_buffer, sizeof(Settings)); if (written < sizeof(Settings)) { // https://github.com/esp8266/Arduino/issues/3218 myClient.write((const char*)settings_buffer +written, sizeof(Settings) -written); } #else myClient.write((const char*)settings_buffer, sizeof(Settings)); #endif SettingsBufferFree(); Settings.cfg_crc = cfg_crc; // Restore crc in case savedata = 0 to make sure settings will be noted as changed } /*-------------------------------------------------------------------------------------------*/ void HandleResetConfiguration(void) { if (!HttpCheckPriviledgedAccess()) { return; } char svalue[33]; AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_RESET_CONFIGURATION); String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), FPSTR(S_RESET_CONFIGURATION)); page += FPSTR(HTTP_HEAD_STYLE); page += F("
" D_CONFIGURATION_RESET "
"); page += FPSTR(HTTP_MSG_RSTRT); page += FPSTR(HTTP_BTN_MAIN); ShowPage(page, HTTP_MANAGER_RESET_ONLY != webserver_state); snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_RESET " 1")); ExecuteWebCommand(svalue, SRC_WEBGUI); } void HandleRestoreConfiguration(void) { if (!HttpCheckPriviledgedAccess()) { return; } AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_RESTORE_CONFIGURATION); String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), FPSTR(S_RESTORE_CONFIGURATION)); page += FPSTR(HTTP_HEAD_STYLE); page += FPSTR(HTTP_FORM_RST); page += FPSTR(HTTP_FORM_RST_UPG); page.replace(F("{r1"), F(D_RESTORE)); page += FPSTR(HTTP_BTN_CONF); ShowPage(page); upload_error = 0; upload_file_type = UPL_SETTINGS; } /*-------------------------------------------------------------------------------------------*/ void HandleInformation(void) { if (!HttpCheckPriviledgedAccess()) { return; } AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_INFORMATION); char stopic[TOPSZ]; int freeMem = ESP.getFreeHeap(); String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), FPSTR(S_INFORMATION)); page += FPSTR(HTTP_HEAD_STYLE); // page += F("
 Information "); page += F(""); page += F("
"); // Save 1k of code space replacing table html with javascript replace codes // }1 = // }2 = String func = FPSTR(HTTP_SCRIPT_INFO_BEGIN); func += F("
"); func += F(D_PROGRAM_VERSION "}2"); func += my_version; func += my_image; func += F("}1" D_BUILD_DATE_AND_TIME "}2"); func += GetBuildDateAndTime(); func += F("}1" D_CORE_AND_SDK_VERSION "}2" ARDUINO_ESP8266_RELEASE "/"); func += String(ESP.getSdkVersion()); func += F("}1" D_UPTIME "}2"); func += GetUptime(); snprintf_P(stopic, sizeof(stopic), PSTR(" at 0x%X"), GetSettingsAddress()); func += F("}1" D_FLASH_WRITE_COUNT "}2"); func += String(Settings.save_flag); func += stopic; func += F("}1" D_BOOT_COUNT "}2"); func += String(Settings.bootcount); func += F("}1" D_RESTART_REASON "}2"); func += GetResetReason(); uint8_t maxfn = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : devices_present; if (SONOFF_IFAN02 == my_module_type) { maxfn = 1; } for (uint8_t i = 0; i < maxfn; i++) { func += F("}1" D_FRIENDLY_NAME " "); func += i +1; func += F("}2"); func += Settings.friendlyname[i]; } func += F("}1}2 "); // Empty line func += F("}1" D_AP); func += String(Settings.sta_active +1); func += F(" " D_SSID " (" D_RSSI ")}2"); func += Settings.sta_ssid[Settings.sta_active]; func += F(" ("); func += WifiGetRssiAsQuality(WiFi.RSSI()); func += F("%)"); func += F("}1" D_HOSTNAME "}2"); func += my_hostname; if (mdns_begun) { func += F(".local"); } if (static_cast(WiFi.localIP()) != 0) { func += F("}1" D_IP_ADDRESS "}2"); func += WiFi.localIP().toString(); func += F("}1" D_GATEWAY "}2"); func += IPAddress(Settings.ip_address[1]).toString(); func += F("}1" D_SUBNET_MASK "}2"); func += IPAddress(Settings.ip_address[2]).toString(); func += F("}1" D_DNS_SERVER "}2"); func += IPAddress(Settings.ip_address[3]).toString(); func += F("}1" D_MAC_ADDRESS "}2"); func += WiFi.macAddress(); } if (static_cast(WiFi.softAPIP()) != 0) { func += F("}1" D_AP " " D_IP_ADDRESS "}2"); func += WiFi.softAPIP().toString(); func += F("}1" D_AP " " D_GATEWAY "}2"); func += WiFi.softAPIP().toString(); func += F("}1" D_AP " " D_MAC_ADDRESS "}2"); func += WiFi.softAPmacAddress(); } func += F("}1}2 "); // Empty line if (Settings.flag.mqtt_enabled) { func += F("}1" D_MQTT_HOST "}2"); func += Settings.mqtt_host; func += F("}1" D_MQTT_PORT "}2"); func += String(Settings.mqtt_port); func += F("}1" D_MQTT_USER "}2"); func += Settings.mqtt_user; func += F("}1" D_MQTT_CLIENT "}2"); func += mqtt_client; func += F("}1" D_MQTT_TOPIC "}2"); func += Settings.mqtt_topic; func += F("}1" D_MQTT_GROUP_TOPIC "}2"); func += Settings.mqtt_grptopic; func += F("}1" D_MQTT_FULL_TOPIC "}2"); func += GetTopic_P(stopic, CMND, mqtt_topic, ""); func += F("}1" D_MQTT " " D_FALLBACK_TOPIC "}2"); func += GetFallbackTopic_P(stopic, CMND, ""); } else { func += F("}1" D_MQTT "}2" D_DISABLED); } func += F("}1}2 "); // Empty line func += F("}1" D_EMULATION "}2"); #ifdef USE_EMULATION if (EMUL_WEMO == Settings.flag2.emulation) { func += F(D_BELKIN_WEMO); } else if (EMUL_HUE == Settings.flag2.emulation) { func += F(D_HUE_BRIDGE); } else { func += F(D_NONE); } #else func += F(D_DISABLED); #endif // USE_EMULATION func += F("}1" D_MDNS_DISCOVERY "}2"); #ifdef USE_DISCOVERY if (Settings.flag3.mdns_enabled) { func += F(D_ENABLED); func += F("}1" D_MDNS_ADVERTISE "}2"); #ifdef WEBSERVER_ADVERTISE func += F(D_WEB_SERVER); #else func += F(D_DISABLED); #endif // WEBSERVER_ADVERTISE } else { func += F(D_DISABLED); } #else func += F(D_DISABLED); #endif // USE_DISCOVERY func += F("}1}2 "); // Empty line func += F("}1" D_ESP_CHIP_ID "}2"); func += String(ESP.getChipId()); snprintf_P(stopic, sizeof(stopic), PSTR("0x%06X"), ESP.getFlashChipId()); func += F("}1" D_FLASH_CHIP_ID "}2"); func += stopic; func += F("}1" D_FLASH_CHIP_SIZE "}2"); func += String(ESP.getFlashChipRealSize() / 1024); func += F("kB"); func += F("}1" D_PROGRAM_FLASH_SIZE "}2"); func += String(ESP.getFlashChipSize() / 1024); func += F("kB"); func += F("}1" D_PROGRAM_SIZE "}2"); func += String(ESP.getSketchSize() / 1024); func += F("kB"); func += F("}1" D_FREE_PROGRAM_SPACE "}2"); func += String(ESP.getFreeSketchSpace() / 1024); func += F("kB"); func += F("}1" D_FREE_MEMORY "}2"); func += String(freeMem / 1024); func += F("kB"); func += F("
"); func += FPSTR(HTTP_SCRIPT_INFO_END); page.replace(F(""), func); // page += F("
"); page += FPSTR(HTTP_BTN_MAIN); ShowPage(page); } #endif // Not FIRMWARE_MINIMAL /*-------------------------------------------------------------------------------------------*/ void HandleUpgradeFirmware(void) { if (!HttpCheckPriviledgedAccess()) { return; } AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_FIRMWARE_UPGRADE); String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), FPSTR(S_FIRMWARE_UPGRADE)); page += FPSTR(HTTP_HEAD_STYLE); page += FPSTR(HTTP_FORM_UPG); page.replace(F("{o1"), Settings.ota_url); page += FPSTR(HTTP_FORM_RST_UPG); page.replace(F("{r1"), F(D_UPGRADE)); page += FPSTR(HTTP_BTN_MAIN); ShowPage(page); upload_error = 0; upload_file_type = UPL_TASMOTA; } void HandleUpgradeFirmwareStart(void) { if (!HttpCheckPriviledgedAccess()) { return; } char svalue[100]; AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_UPGRADE_STARTED)); WifiConfigCounter(); char tmp[100]; WebGetArg("o", tmp, sizeof(tmp)); if (strlen(tmp)) { snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_OTAURL " %s"), tmp); ExecuteWebCommand(svalue, SRC_WEBGUI); } String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), FPSTR(S_INFORMATION)); page += FPSTR(HTTP_HEAD_STYLE); page += F("
" D_UPGRADE_STARTED " ...
"); page += FPSTR(HTTP_MSG_RSTRT); page += FPSTR(HTTP_BTN_MAIN); page.replace(F(""), FPSTR(HTTP_SCRIPT_RELOAD_OTA)); ShowPage(page); snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_UPGRADE " 1")); ExecuteWebCommand(svalue, SRC_WEBGUI); } void HandleUploadDone(void) { if (!HttpCheckPriviledgedAccess()) { return; } AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_UPLOAD_DONE)); char error[100]; WifiConfigCounter(); restart_flag = 0; MqttRetryCounter(0); String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), FPSTR(S_INFORMATION)); page += FPSTR(HTTP_HEAD_STYLE); page += F("
" D_UPLOAD " " D_FAILED "

"); #ifdef USE_RF_FLASH if (upload_error < 14) { #else if (upload_error < 10) { #endif GetTextIndexed(error, sizeof(error), upload_error -1, kUploadErrors); } else { snprintf_P(error, sizeof(error), PSTR(D_UPLOAD_ERROR_CODE " %d"), upload_error); } page += error; snprintf_P(log_data, sizeof(log_data), PSTR(D_UPLOAD ": %s"), error); AddLog(LOG_LEVEL_DEBUG); stop_flash_rotate = Settings.flag.stop_flash_rotate; } else { page += F("green'>" D_SUCCESSFUL "
"); page += FPSTR(HTTP_MSG_RSTRT); page.replace(F(""), FPSTR(HTTP_SCRIPT_RELOAD_OTA)); // Refesh main web ui after OTA upgrade ShowWebSource(SRC_WEBGUI); restart_flag = 2; // Always restart to re-enable disabled features during update } SettingsBufferFree(); page += F("

"); page += FPSTR(HTTP_BTN_MAIN); ShowPage(page); } void HandleUploadLoop(void) { // Based on ESP8266HTTPUpdateServer.cpp uses ESP8266WebServer Parsing.cpp and Cores Updater.cpp (Update) bool _serialoutput = (LOG_LEVEL_DEBUG <= seriallog_level); if (HTTP_USER == webserver_state) { return; } if (upload_error) { if (UPL_TASMOTA == upload_file_type) { Update.end(); } return; } HTTPUpload& upload = WebServer->upload(); if (UPLOAD_FILE_START == upload.status) { restart_flag = 60; if (0 == upload.filename.c_str()[0]) { upload_error = 1; // No file selected return; } SettingsSave(1); // Free flash for upload snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_UPLOAD D_FILE " %s ..."), upload.filename.c_str()); AddLog(LOG_LEVEL_INFO); if (UPL_SETTINGS == upload_file_type) { if (!SettingsBufferAlloc()) { upload_error = 2; // Not enough space return; } } else { MqttRetryCounter(60); #ifdef USE_EMULATION UdpDisconnect(); #endif // USE_EMULATION #ifdef USE_ARILUX_RF AriluxRfDisable(); // Prevent restart exception on Arilux Interrupt routine #endif // USE_ARILUX_RF if (Settings.flag.mqtt_enabled) MqttDisconnect(); uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; if (!Update.begin(maxSketchSpace)) { //start with max available size // if (_serialoutput) Update.printError(Serial); // if (Update.getError() == UPDATE_ERROR_BOOTSTRAP) { // if (_serialoutput) Serial.println("Device still in UART update mode, perform powercycle"); // } upload_error = 2; // Not enough space return; } } upload_progress_dot_count = 0; } else if (!upload_error && (UPLOAD_FILE_WRITE == upload.status)) { if (0 == upload.totalSize) { if (UPL_SETTINGS == upload_file_type) { config_block_count = 0; } else { #ifdef USE_RF_FLASH if ((SONOFF_BRIDGE == my_module_type) && (upload.buf[0] == ':')) { // Check if this is a RF bridge FW file Update.end(); // End esp8266 update session upload_file_type = UPL_EFM8BB1; upload_error = SnfBrUpdateInit(); if (upload_error != 0) { return; } } else #endif // USE_RF_FLASH { if (upload.buf[0] != 0xE9) { upload_error = 3; // Magic byte is not 0xE9 return; } uint32_t bin_flash_size = ESP.magicFlashChipSize((upload.buf[3] & 0xf0) >> 4); if(bin_flash_size > ESP.getFlashChipRealSize()) { upload_error = 4; // Program flash size is larger than real flash size return; } // upload.buf[2] = 3; // Force DOUT - ESP8285 } } } if (UPL_SETTINGS == upload_file_type) { if (!upload_error) { if (upload.currentSize > (sizeof(Settings) - (config_block_count * HTTP_UPLOAD_BUFLEN))) { upload_error = 9; // File too large return; } memcpy(settings_buffer + (config_block_count * HTTP_UPLOAD_BUFLEN), upload.buf, upload.currentSize); config_block_count++; } } #ifdef USE_RF_FLASH else if (UPL_EFM8BB1 == upload_file_type) { if (efm8bb1_update != NULL) { // We have carry over data since last write, i. e. a start but not an end ssize_t result = rf_glue_remnant_with_new_data_and_write(efm8bb1_update, upload.buf, upload.currentSize); free(efm8bb1_update); efm8bb1_update = NULL; if (result != 0) { upload_error = abs(result); // 2 = Not enough space, 8 = File invalid return; } } ssize_t result = rf_search_and_write(upload.buf, upload.currentSize); if (result < 0) { upload_error = abs(result); return; } else if (result > 0) { if ((size_t)result > upload.currentSize) { // Offset is larger than the buffer supplied, this should not happen upload_error = 9; // File too large - Failed to decode RF firmware return; } // A remnant has been detected, allocate data for it plus a null termination byte size_t remnant_sz = upload.currentSize - result; efm8bb1_update = (uint8_t *) malloc(remnant_sz + 1); if (efm8bb1_update == NULL) { upload_error = 2; // Not enough space - Unable to allocate memory to store new RF firmware return; } memcpy(efm8bb1_update, upload.buf + result, remnant_sz); // Add null termination at the end of of remnant buffer efm8bb1_update[remnant_sz] = '\0'; } } #endif // USE_RF_FLASH else { // firmware if (!upload_error && (Update.write(upload.buf, upload.currentSize) != upload.currentSize)) { upload_error = 5; // Upload buffer miscompare return; } if (_serialoutput) { Serial.printf("."); upload_progress_dot_count++; if (!(upload_progress_dot_count % 80)) { Serial.println(); } } } } else if(!upload_error && (UPLOAD_FILE_END == upload.status)) { if (_serialoutput && (upload_progress_dot_count % 80)) { Serial.println(); } if (UPL_SETTINGS == upload_file_type) { if (config_xor_on_set) { for (uint16_t i = 2; i < sizeof(Settings); i++) { settings_buffer[i] ^= (config_xor_on_set +i); } } bool valid_settings = false; unsigned long buffer_version = settings_buffer[11] << 24 | settings_buffer[10] << 16 | settings_buffer[9] << 8 | settings_buffer[8]; if (buffer_version > 0x06000000) { uint16_t buffer_size = settings_buffer[3] << 8 | settings_buffer[2]; uint16_t buffer_crc = settings_buffer[15] << 8 | settings_buffer[14]; uint16_t crc = 0; for (uint16_t i = 0; i < buffer_size; i++) { if ((i < 14) || (i > 15)) { crc += settings_buffer[i]*(i+1); } // Skip crc } valid_settings = (buffer_crc == crc); } else { valid_settings = (settings_buffer[0] == CONFIG_FILE_SIGN); } if (valid_settings) { SettingsDefaultSet2(); memcpy((char*)&Settings +16, settings_buffer +16, sizeof(Settings) -16); Settings.version = buffer_version; // Restore version and auto upgrade after restart SettingsBufferFree(); } else { upload_error = 8; // File invalid return; } } #ifdef USE_RF_FLASH else if (UPL_EFM8BB1 == upload_file_type) { // RF FW flash done upload_file_type = UPL_TASMOTA; } #endif // USE_RF_FLASH else { if (!Update.end(true)) { // true to set the size to the current progress if (_serialoutput) { Update.printError(Serial); } upload_error = 6; // Upload failed. Enable logging 3 return; } } if (!upload_error) { snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_UPLOAD D_SUCCESSFUL " %u bytes. " D_RESTARTING), upload.totalSize); AddLog(LOG_LEVEL_INFO); } } else if (UPLOAD_FILE_ABORTED == upload.status) { restart_flag = 0; MqttRetryCounter(0); upload_error = 7; // Upload aborted if (UPL_TASMOTA == upload_file_type) { Update.end(); } } delay(0); } /*-------------------------------------------------------------------------------------------*/ void HandlePreflightRequest(void) { WebServer->sendHeader(F("Access-Control-Allow-Origin"), F("*")); WebServer->sendHeader(F("Access-Control-Allow-Methods"), F("GET, POST")); WebServer->sendHeader(F("Access-Control-Allow-Headers"), F("authorization")); WebServer->send(200, FPSTR(HDR_CTYPE_HTML), ""); } /*-------------------------------------------------------------------------------------------*/ void HandleHttpCommand(void) { if (!HttpCheckPriviledgedAccess(false)) { return; } AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_COMMAND)); uint8_t valid = 1; if (Settings.web_password[0] != 0) { char tmp1[100]; WebGetArg("user", tmp1, sizeof(tmp1)); char tmp2[100]; WebGetArg("password", tmp2, sizeof(tmp2)); if (!(!strcmp(tmp1, WEB_USERNAME) && !strcmp(tmp2, Settings.web_password))) { valid = 0; } } String message = F("{\"" D_RSLT_WARNING "\":\""); if (valid) { uint8_t curridx = web_log_index; String svalue = WebServer->arg("cmnd"); if (svalue.length() && (svalue.length() < INPUT_BUFFER_SIZE)) { ExecuteWebCommand((char*)svalue.c_str(), SRC_WEBCOMMAND); if (web_log_index != curridx) { uint8_t counter = curridx; message = F("{"); do { char* tmp; size_t len; GetLog(counter, &tmp, &len); if (len) { // [14:49:36 MQTT: stat/wemos5/RESULT = {"POWER":"OFF"}] > [{"POWER":"OFF"}] char* JSON = (char*)memchr(tmp, '{', len); if (JSON) { // Is it a JSON message (and not only [15:26:08 MQT: stat/wemos5/POWER = O]) if (message.length() > 1) { message += F(","); } size_t JSONlen = len - (JSON - tmp); if (JSONlen > sizeof(mqtt_data)) { JSONlen = sizeof(mqtt_data); } strlcpy(mqtt_data, JSON +1, JSONlen -2); message += mqtt_data; } } counter++; if (!counter) counter++; // Skip 0 as it is not allowed } while (counter != web_log_index); message += F("}"); } else { message += F(D_ENABLE_WEBLOG_FOR_RESPONSE "\"}"); } } else { message += F(D_ENTER_COMMAND " cmnd=\"}"); } } else { message += F(D_NEED_USER_AND_PASSWORD "\"}"); } SetHeader(); WebServer->send(200, FPSTR(HDR_CTYPE_JSON), message); } /*-------------------------------------------------------------------------------------------*/ void HandleConsole(void) { if (!HttpCheckPriviledgedAccess()) { return; } AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONSOLE); String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), FPSTR(S_CONSOLE)); page += FPSTR(HTTP_HEAD_STYLE); page.replace(F(""), FPSTR(HTTP_SCRIPT_CONSOL)); page += FPSTR(HTTP_FORM_CMND); page += FPSTR(HTTP_BTN_MAIN); ShowPage(page); } void HandleAjaxConsoleRefresh(void) { if (!HttpCheckPriviledgedAccess()) { return; } bool cflg = true; uint8_t counter = 0; // Initial start, should never be 0 again String svalue = WebServer->arg("c1"); if (svalue.length() && (svalue.length() < INPUT_BUFFER_SIZE)) { snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_COMMAND "%s"), svalue.c_str()); AddLog(LOG_LEVEL_INFO); ExecuteWebCommand((char*)svalue.c_str(), SRC_WEBCONSOLE); } char stmp[10]; WebGetArg("c2", stmp, sizeof(stmp)); if (strlen(stmp)) { counter = atoi(stmp); } bool last_reset_web_log_flag = reset_web_log_flag; // mqtt_data used as scratch space snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%d\1%d\1"), web_log_index, last_reset_web_log_flag); String message = mqtt_data; if (!reset_web_log_flag) { counter = 0; reset_web_log_flag = true; } if (counter != web_log_index) { if (!counter) { counter = web_log_index; cflg = false; } do { char* tmp; size_t len; GetLog(counter, &tmp, &len); if (len) { if (cflg) { message += F("\n"); } else { cflg = true; } if (len > sizeof(mqtt_data) -2) { len = sizeof(mqtt_data); } strlcpy(mqtt_data, tmp, len); message += mqtt_data; // mqtt_data used as scratch space } counter++; if (!counter) { counter++; } // Skip log index 0 as it is not allowed } while (counter != web_log_index); } message += F("\1"); WebServer->send(200, FPSTR(HDR_CTYPE_XML), message); } /********************************************************************************************/ void HandleNotFound(void) { // snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_HTTP "Not fount (%s)"), WebServer->uri().c_str()); // AddLog(LOG_LEVEL_DEBUG); if (CaptivePortal()) { return; } // If captive portal redirect instead of displaying the error page. #ifdef USE_EMULATION String path = WebServer->uri(); if ((EMUL_HUE == Settings.flag2.emulation) && (path.startsWith("/api"))) { HandleHueApi(&path); } else #endif // USE_EMULATION { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR(D_FILE_NOT_FOUND "\n\nURI: %s\nMethod: %s\nArguments: %d\n"), WebServer->uri().c_str(), (WebServer->method() == HTTP_GET) ? "GET" : "POST", WebServer->args()); for (uint8_t i = 0; i < WebServer->args(); i++) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s %s: %s\n"), mqtt_data, WebServer->argName(i).c_str(), WebServer->arg(i).c_str()); } SetHeader(); WebServer->send(404, FPSTR(HDR_CTYPE_PLAIN), mqtt_data); } } /* 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. */ bool CaptivePortal(void) { if ((WifiIsInManagerMode()) && !ValidIpAddress(WebServer->hostHeader())) { AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_REDIRECTED)); WebServer->sendHeader(F("Location"), String("http://") + WebServer->client().localIP().toString(), true); WebServer->send(302, FPSTR(HDR_CTYPE_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? */ bool ValidIpAddress(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; } /*********************************************************************************************/ String UrlEncode(const String& text) { const char hex[] = "0123456789ABCDEF"; String encoded = ""; int len = text.length(); int i = 0; while (i < len) { char decodedChar = text.charAt(i++); /* if (('a' <= decodedChar && decodedChar <= 'z') || ('A' <= decodedChar && decodedChar <= 'Z') || ('0' <= decodedChar && decodedChar <= '9') || ('=' == decodedChar)) { encoded += decodedChar; } else { encoded += '%'; encoded += hex[decodedChar >> 4]; encoded += hex[decodedChar & 0xF]; } */ if (' ' == decodedChar) { encoded += '%'; encoded += hex[decodedChar >> 4]; encoded += hex[decodedChar & 0xF]; } else { encoded += decodedChar; } } return encoded; } int WebSend(char *buffer) { // [sonoff] POWER1 ON --> Sends http://sonoff/cm?cmnd=POWER1 ON // [192.168.178.86:80,admin:joker] POWER1 ON --> Sends http://hostname:80/cm?user=admin&password=joker&cmnd=POWER1 ON // [sonoff] /any/link/starting/with/a/slash.php?log=123 --> Sends http://sonoff/any/link/starting/with/a/slash.php?log=123 // [sonoff,admin:joker] /any/link/starting/with/a/slash.php?log=123 --> Sends http://sonoff/any/link/starting/with/a/slash.php?log=123 char *host; char *user; char *password; char *command; int status = 1; // Wrong parameters // buffer = | [ 192.168.178.86 : 80 , admin : joker ] POWER1 ON | host = strtok_r(buffer, "]", &command); // host = | [ 192.168.178.86 : 80 , admin : joker |, command = | POWER1 ON | if (host && command) { RemoveSpace(host); // host = |[192.168.178.86:80,admin:joker| host++; // host = |192.168.178.86:80,admin:joker| - Skip [ host = strtok_r(host, ",", &user); // host = |192.168.178.86:80|, user = |admin:joker| String url = F("http://"); // url = |http://| url += host; // url = |http://192.168.178.86:80| command = Trim(command); // command = |POWER1 ON| or |/any/link/starting/with/a/slash.php?log=123| if (command[0] != '/') { url += F("/cm?"); // url = |http://192.168.178.86/cm?| if (user) { user = strtok_r(user, ":", &password); // user = |admin|, password = |joker| if (user && password) { char userpass[128]; snprintf_P(userpass, sizeof(userpass), PSTR("user=%s&password=%s&"), user, password); url += userpass; // url = |http://192.168.178.86/cm?user=admin&password=joker&| } } url += F("cmnd="); // url = |http://192.168.178.86/cm?cmnd=| or |http://192.168.178.86/cm?user=admin&password=joker&cmnd=| } url += command; // url = |http://192.168.178.86/cm?cmnd=POWER1 ON| //snprintf_P(log_data, sizeof(log_data), PSTR("DBG: Uri |%s|"), url.c_str()); //AddLog(LOG_LEVEL_DEBUG); #if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1) || defined(ARDUINO_ESP8266_RELEASE_2_4_2) HTTPClient http; if (http.begin(UrlEncode(url))) { // UrlEncode(url) = |http://192.168.178.86/cm?cmnd=POWER1%20ON| #else WiFiClient http_client; HTTPClient http; if (http.begin(http_client, UrlEncode(url))) { // UrlEncode(url) = |http://192.168.178.86/cm?cmnd=POWER1%20ON| #endif int http_code = http.GET(); // Start connection and send HTTP header if (http_code > 0) { // http_code will be negative on error if (http_code == HTTP_CODE_OK || http_code == HTTP_CODE_MOVED_PERMANENTLY) { /* // Return received data to the user - Adds 900+ bytes to the code String result = http.getString(); // File found at server - may need lot of ram or trigger out of memory! uint16_t j = 0; for (uint16_t i = 0; i < result.length(); i++) { char text = result.charAt(i); if (text > 31) { // Remove control characters like linefeed mqtt_data[j++] = text; if (j == sizeof(mqtt_data) -2) { break; } } } mqtt_data[j] = '\0'; MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_WEBSEND)); */ } status = 0; // No error - Done } else { status = 2; // Connection failed } http.end(); // Clean up connection data } else { status = 3; // Host not found or connection error } } return status; } /*********************************************************************************************/ enum WebCommands { CMND_WEBSERVER, CMND_WEBPASSWORD, CMND_WEBLOG, CMND_WEBREFRESH, CMND_WEBSEND, CMND_EMULATION }; const char kWebCommands[] PROGMEM = D_CMND_WEBSERVER "|" D_CMND_WEBPASSWORD "|" D_CMND_WEBLOG "|" D_CMND_WEBREFRESH "|" D_CMND_WEBSEND "|" D_CMND_EMULATION ; const char kWebSendStatus[] PROGMEM = D_JSON_DONE "|" D_JSON_WRONG_PARAMETERS "|" D_JSON_CONNECT_FAILED "|" D_JSON_HOST_NOT_FOUND ; bool WebCommand(void) { char command[CMDSZ]; bool serviced = true; int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic, kWebCommands); if (-1 == command_code) { serviced = false; // Unknown command } if (CMND_WEBSERVER == command_code) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) { Settings.webserver = XdrvMailbox.payload; } if (Settings.webserver) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_WEBSERVER "\":\"" D_JSON_ACTIVE_FOR " %s " D_JSON_ON_DEVICE " %s " D_JSON_WITH_IP_ADDRESS " %s\"}"), (2 == Settings.webserver) ? D_ADMIN : D_USER, my_hostname, WiFi.localIP().toString().c_str()); } else { snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, GetStateText(0)); } } else if (CMND_WEBPASSWORD == command_code) { if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.web_password))) { strlcpy(Settings.web_password, (SC_CLEAR == Shortcut(XdrvMailbox.data)) ? "" : (SC_DEFAULT == Shortcut(XdrvMailbox.data)) ? WEB_PASSWORD : XdrvMailbox.data, sizeof(Settings.web_password)); snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.web_password); } else { snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_ASTERIX, command); } } else if (CMND_WEBLOG == command_code) { if ((XdrvMailbox.payload >= LOG_LEVEL_NONE) && (XdrvMailbox.payload <= LOG_LEVEL_ALL)) { Settings.weblog_level = XdrvMailbox.payload; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.weblog_level); } else if (CMND_WEBREFRESH == command_code) { if ((XdrvMailbox.payload > 999) && (XdrvMailbox.payload <= 10000)) { Settings.web_refresh = XdrvMailbox.payload; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.web_refresh); } else if (CMND_WEBSEND == command_code) { if (XdrvMailbox.data_len > 0) { uint8_t result = WebSend(XdrvMailbox.data); char stemp1[20]; snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, GetTextIndexed(stemp1, sizeof(stemp1), result, kWebSendStatus)); } } #ifdef USE_EMULATION else if (CMND_EMULATION == command_code) { if ((XdrvMailbox.payload >= EMUL_NONE) && (XdrvMailbox.payload < EMUL_MAX)) { Settings.flag2.emulation = XdrvMailbox.payload; restart_flag = 2; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.flag2.emulation); } #endif // USE_EMULATION else serviced = false; // Unknown command return serviced; } /*********************************************************************************************\ * Interface \*********************************************************************************************/ bool Xdrv01(uint8_t function) { bool result = false; switch (function) { case FUNC_LOOP: PollDnsWebserver(); #ifdef USE_EMULATION if (Settings.flag2.emulation) PollUdp(); #endif // USE_EMULATION break; case FUNC_COMMAND: result = WebCommand(); break; } return result; } #endif // USE_WEBSERVER