mirror of https://github.com/arendst/Tasmota.git
1703 lines
63 KiB
C++
1703 lines
63 KiB
C++
/*
|
|
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 =
|
|
"<!DOCTYPE html><html lang=\"en\" class=\"\">"
|
|
"<head>"
|
|
"<meta charset='utf-8'>"
|
|
"<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,user-scalable=no\"/>"
|
|
"<title>{v}</title>"
|
|
"<script>"
|
|
"var cn=120;"
|
|
"function u(){"
|
|
"if(cn>=0){"
|
|
"document.getElementById('t').innerHTML='Restart in '+cn+' seconds';"
|
|
"cn--;"
|
|
"setTimeout(u,1000);"
|
|
"}"
|
|
"}"
|
|
"function c(l){"
|
|
"document.getElementById('s1').value=l.innerText||l.textContent;"
|
|
"document.getElementById('p1').focus();"
|
|
"}"
|
|
"var sn=0;"
|
|
"function l(){"
|
|
"var e=document.getElementById('t1');"
|
|
"if(e.scrollTop>=sn){"
|
|
"var x=new XMLHttpRequest();"
|
|
"x.onreadystatechange=function(){"
|
|
"if(x.readyState==4&&x.status==200){"
|
|
"e.value=x.responseText;"
|
|
"e.scrollTop=100000;"
|
|
"sn=e.scrollTop;"
|
|
"}"
|
|
"};"
|
|
"x.open('GET','ax',true);"
|
|
"x.send();"
|
|
"}"
|
|
"setTimeout(l,2000);"
|
|
"}"
|
|
"</script>"
|
|
"<style>"
|
|
"div,fieldset,input,select{padding:5px;font-size:1em;}"
|
|
"input{width:95%;}"
|
|
"select{width:100%;}"
|
|
"textarea{resize:none;width:98%;height:312px;padding:5px;overflow:auto;}"
|
|
"body{text-align:center;font-family:verdana;}"
|
|
"td{padding:0px;}"
|
|
"button{border:0;border-radius:0.3rem;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;-webkit-transition-duration:0.4s;transition-duration:0.4s;}"
|
|
"button:hover{background-color:#006cba;}"
|
|
".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;}"
|
|
"</style>"
|
|
"</head>"
|
|
"<body>"
|
|
"<div style='text-align:left;display:inline-block;min-width:260px;'>"
|
|
"<div style='text-align:center;'><h3>{ha} Module</h3><h2>{h}</h2></div>";
|
|
const char HTTP_MSG_RSTRT[] PROGMEM =
|
|
"<br/><div style='text-align:center;'>Device will restart in a few seconds</div><br/>";
|
|
const char HTTP_BTN_MENU1[] PROGMEM =
|
|
"<br/><form action='/cn' method='post'><button>Configuration</button></form>"
|
|
"<br/><form action='/in' method='post'><button>Information</button></form>"
|
|
"<br/><form action='/up' method='post'><button>Firmware upgrade</button></form>"
|
|
"<br/><form action='/cs' method='post'><button>Console</button></form>";
|
|
const char HTTP_BTN_RSTRT[] PROGMEM =
|
|
"<br/><form action='/rb' method='post' onsubmit='return confirm(\"Confirm Restart\");'><button>Restart</button></form>";
|
|
const char HTTP_BTN_MENU2[] PROGMEM =
|
|
"<br/><form action='/md' method='post'><button>Configure Module</button></form>"
|
|
"<br/><form action='/w0' method='post'><button>Configure WiFi</button></form>";
|
|
const char HTTP_BTN_MENU3[] PROGMEM =
|
|
"<br/><form action='/mq' method='post'><button>Configure MQTT</button></form>"
|
|
#ifdef USE_DOMOTICZ
|
|
"<br/><form action='/dm' method='post'><button>Configure Domoticz</button></form>"
|
|
#endif // USE_DOMOTICZ
|
|
"";
|
|
const char HTTP_BTN_MENU4[] PROGMEM =
|
|
"<br/><form action='/lg' method='post'><button>Configure Logging</button></form>"
|
|
"<br/><form action='/co' method='post'><button>Configure Other</button></form>"
|
|
"<br/><form action='/rt' method='post' onsubmit='return confirm(\"Confirm Reset Configuration\");'><button>Reset Configuration</button></form>"
|
|
"<br/><form action='/dl' method='post'><button>Backup Configuration</button></form>"
|
|
"<br/><form action='/rs' method='post'><button>Restore Configuration</button></form>";
|
|
const char HTTP_BTN_MAIN[] PROGMEM =
|
|
"<br/><br/><form action='/' method='post'><button>Main menu</button></form>";
|
|
const char HTTP_BTN_CONF[] PROGMEM =
|
|
"<br/><br/><form action='/cn' method='post'><button>Configuration menu</button></form>";
|
|
const char HTTP_FORM_MODULE[] PROGMEM =
|
|
"<fieldset><legend><b> Module parameters </b></legend><form method='post' action='sv'>"
|
|
"<input id='w' name='w' value='6' hidden><input id='r' name='r' value='1' hidden>"
|
|
"<br/><b>Module type</b> ({mt})<br/><select id='mt' name='mt'>";
|
|
const char HTTP_LNK_ITEM[] PROGMEM =
|
|
"<div><a href='#p' onclick='c(this)'>{v}</a> <span class='q {i}'>{r}%</span></div>";
|
|
const char HTTP_LNK_SCAN[] PROGMEM =
|
|
"<div><a href='/w1'>Scan for wifi networks</a></div><br/>";
|
|
const char HTTP_FORM_WIFI[] PROGMEM =
|
|
"<fieldset><legend><b> Wifi parameters </b></legend><form method='post' action='sv'>"
|
|
"<input id='w' name='w' value='1' hidden><input id='r' name='r' value='1' hidden>"
|
|
"<br/><b>AP1 SSId</b> (" STA_SSID1 ")<br/><input id='s1' name='s1' length=32 placeholder='" STA_SSID1 "' value='{s1}'><br/>"
|
|
"<br/><b>AP1 Password</b></br><input id='p1' name='p1' length=64 type='password' placeholder='" STA_PASS1 "' value='{p1}'><br/>"
|
|
"<br/><b>AP2 SSId</b> (" STA_SSID2 ")<br/><input id='s2' name='s2' length=32 placeholder='" STA_SSID2 "' value='{s2}'><br/>"
|
|
"<br/><b>AP2 Password</b></br><input id='p2' name='p2' length=64 type='password' placeholder='" STA_PASS2 "' value='{p2}'><br/>"
|
|
"<br/><b>Hostname</b> (" WIFI_HOSTNAME ")<br/><input id='h' name='h' length=32 placeholder='" WIFI_HOSTNAME" ' value='{h1}'><br/>";
|
|
const char HTTP_FORM_MQTT[] PROGMEM =
|
|
"<fieldset><legend><b> MQTT parameters </b></legend><form method='post' action='sv'>"
|
|
"<input id='w' name='w' value='2' hidden><input id='r' name='r' value='1' hidden>"
|
|
"<br/><b>Host</b> (" MQTT_HOST ")<br/><input id='mh' name='mh' length=32 placeholder='" MQTT_HOST" ' value='{m1}'><br/>"
|
|
"<br/><b>Port</b> (" STR(MQTT_PORT) ")<br/><input id='ml' name='ml' length=5 placeholder='" STR(MQTT_PORT) "' value='{m2}'><br/>"
|
|
"<br/><b>Client Id</b> ({m0})<br/><input id='mc' name='mc' length=32 placeholder='" MQTT_CLIENT_ID "' value='{m3}'><br/>"
|
|
"<br/><b>User</b> (" MQTT_USER ")<br/><input id='mu' name='mu' length=32 placeholder='" MQTT_USER "' value='{m4}'><br/>"
|
|
"<br/><b>Password</b><br/><input id='mp' name='mp' length=32 type='password' placeholder='" MQTT_PASS "' value='{m5}'><br/>"
|
|
"<br/><b>Topic</b> (" MQTT_TOPIC ")<br/><input id='mt' name='mt' length=32 placeholder='" MQTT_TOPIC" ' value='{m6}'><br/>";
|
|
const char HTTP_FORM_LOG1[] PROGMEM =
|
|
"<fieldset><legend><b> Logging parameters </b></legend><form method='post' action='sv'>"
|
|
"<input id='w' name='w' value='3' hidden><input id='r' name='r' value='0' hidden>";
|
|
const char HTTP_FORM_LOG2[] PROGMEM =
|
|
"<br/><b>{b0} level</b> ({b1})<br/><select id='{b2}' name='{b2}'>"
|
|
"<option{a0value='0'>0 None</option>"
|
|
"<option{a1value='1'>1 Error</option>"
|
|
"<option{a2value='2'>2 Info</option>"
|
|
"<option{a3value='3'>3 Debug</option>"
|
|
"<option{a4value='4'>4 More debug</option>"
|
|
"</select></br>";
|
|
const char HTTP_FORM_LOG3[] PROGMEM =
|
|
"<br/><b>Syslog host</b> (" SYS_LOG_HOST ")<br/><input id='lh' name='lh' length=32 placeholder='" SYS_LOG_HOST "' value='{l2}'><br/>"
|
|
"<br/><b>Syslog port</b> (" STR(SYS_LOG_PORT) ")<br/><input id='lp' name='lp' length=5 placeholder='" STR(SYS_LOG_PORT) "' value='{l3}'><br/>"
|
|
"<br/><b>Telemetric period</b> (" STR(TELE_PERIOD) ")<br/><input id='lt' name='lt' length=4 placeholder='" STR(TELE_PERIOD) "' value='{l4}'><br/>";
|
|
const char HTTP_FORM_OTHER[] PROGMEM =
|
|
"<fieldset><legend><b> Other parameters </b></legend><form method='post' action='sv'>"
|
|
"<input id='w' name='w' value='5' hidden><input id='r' name='r' value='1' hidden>"
|
|
"<br/><input style='width:10%;float:left' id='b1' name='b1' type='checkbox'{r1}><b>MQTT enable</b><br/>";
|
|
const char HTTP_FORM_OTHER2[] PROGMEM =
|
|
"<br/><b>Friendly Name {1</b> ({2)<br/><input id='a{1' name='a{1' length=32 placeholder='{2' value='{3'><br/>";
|
|
#ifdef USE_EMULATION
|
|
const char HTTP_FORM_OTHER3[] PROGMEM =
|
|
"<br/><fieldset><legend><b> Emulation </b></legend>"
|
|
"<br/><input style='width:10%;float:left' id='b2' name='b2' type='radio' value='0'{r2}><b>None</b>"
|
|
"<br/><input style='width:10%;float:left' id='b2' name='b2' type='radio' value='1'{r3}><b>Belkin WeMo</b>"
|
|
"<br/><input style='width:10%;float:left' id='b2' name='b2' type='radio' value='2'{r4}><b>Hue Bridge</b><br/>";
|
|
#endif // USE_EMULATION
|
|
const char HTTP_FORM_END[] PROGMEM =
|
|
"<br/><button type='submit'>Save</button></form></fieldset>";
|
|
const char HTTP_FORM_RST[] PROGMEM =
|
|
"<div id='f1' name='f1' style='display:block;'>"
|
|
"<fieldset><legend><b> Restore configuration </b></legend>"
|
|
"<form method='post' action='u2' enctype='multipart/form-data'>"
|
|
"<br/><input type='file' name='u2'><br/>"
|
|
"<br/><button type='submit' onclick='document.getElementById(\"f1\").style.display=\"none\";document.getElementById(\"f2\").style.display=\"block\";this.form.submit();'>Start restore</button></form>"
|
|
"</fieldset>"
|
|
"</div>"
|
|
"<div id='f2' name='f2' style='display:none;text-align:center;'><b>Restore started ...</b></div>";
|
|
const char HTTP_FORM_UPG[] PROGMEM =
|
|
"<div id='f1' name='f1' style='display:block;'>"
|
|
"<fieldset><legend><b> Upgrade by web server </b></legend>"
|
|
"<form method='post' action='u1'>"
|
|
"<br/>OTA Url<br/><input id='o' name='o' length=80 placeholder='OTA_URL' value='{o1}'><br/>"
|
|
"<br/><button type='submit'>Start upgrade</button></form>"
|
|
"</fieldset><br/><br/>"
|
|
"<fieldset><legend><b> Upgrade by file upload </b></legend>"
|
|
"<form method='post' action='u2' enctype='multipart/form-data'>"
|
|
"<br/><input type='file' name='u2'><br/>"
|
|
// "<br/><button type='submit' onclick='this.disabled=true;this.form.submit();'>Start upgrade</button></form></fieldset>"
|
|
"<br/><button type='submit' onclick='document.getElementById(\"f1\").style.display=\"none\";document.getElementById(\"f2\").style.display=\"block\";this.form.submit();'>Start upgrade</button></form>"
|
|
"</fieldset>"
|
|
"</div>"
|
|
"<div id='f2' name='f2' style='display:none;text-align:center;'><b>Upload started ...</b></div>";
|
|
const char HTTP_FORM_CMND[] PROGMEM =
|
|
"<br/><textarea readonly id='t1' name='t1' cols='80' wrap='off'></textarea><br/><br/>"
|
|
"<form method='post' action='cs'>"
|
|
"<input style='width:98%' id='c1' name='c1' length=80 placeholder='Enter command' autofocus><br/>"
|
|
// "<br/><button type='submit'>Send command</button>"
|
|
"</form>";
|
|
const char HTTP_COUNTER[] PROGMEM =
|
|
"<br/><div id='t' name='t' style='text-align:center;'></div>";
|
|
const char HTTP_END[] PROGMEM =
|
|
"</div>"
|
|
"</body>"
|
|
"</html>";
|
|
#ifdef USE_EMULATION
|
|
const char WEMO_EVENTSERVICE_XML[] PROGMEM =
|
|
"<?scpd xmlns=\"urn:Belkin:service-1-0\"?>"
|
|
"<actionList>"
|
|
"<action>"
|
|
"<name>SetBinaryState</name>"
|
|
"<argumentList>"
|
|
"<argument>"
|
|
"<retval/>"
|
|
"<name>BinaryState</name>"
|
|
"<relatedStateVariable>BinaryState</relatedStateVariable>"
|
|
"<direction>in</direction>"
|
|
"</argument>"
|
|
"</argumentList>"
|
|
"<serviceStateTable>"
|
|
"<stateVariable sendEvents=\"yes\">"
|
|
"<name>BinaryState</name>"
|
|
"<dataType>Boolean</dataType>"
|
|
"<defaultValue>0</defaultValue>"
|
|
"</stateVariable>"
|
|
"<stateVariable sendEvents=\"yes\">"
|
|
"<name>level</name>"
|
|
"<dataType>string</dataType>"
|
|
"<defaultValue>0</defaultValue>"
|
|
"</stateVariable>"
|
|
"</serviceStateTable>"
|
|
"</action>"
|
|
"</scpd>\r\n"
|
|
"\r\n";
|
|
const char WEMO_SETUP_XML[] PROGMEM =
|
|
"<?xml version=\"1.0\"?>"
|
|
"<root>"
|
|
"<device>"
|
|
"<deviceType>urn:Belkin:device:controllee:1</deviceType>"
|
|
"<friendlyName>{x1}</friendlyName>"
|
|
"<manufacturer>Belkin International Inc.</manufacturer>"
|
|
"<modelName>Sonoff Socket</modelName>"
|
|
"<modelNumber>3.1415</modelNumber>"
|
|
"<UDN>uuid:{x2}</UDN>"
|
|
"<serialNumber>{x3}</serialNumber>"
|
|
"<binaryState>0</binaryState>"
|
|
"<serviceList>"
|
|
"<service>"
|
|
"<serviceType>urn:Belkin:service:basicevent:1</serviceType>"
|
|
"<serviceId>urn:Belkin:serviceId:basicevent1</serviceId>"
|
|
"<controlURL>/upnp/control/basicevent1</controlURL>"
|
|
"<eventSubURL>/upnp/event/basicevent1</eventSubURL>"
|
|
"<SCPDURL>/eventservice.xml</SCPDURL>"
|
|
"</service>"
|
|
"</serviceList>"
|
|
"</device>"
|
|
"</root>\r\n"
|
|
"\r\n";
|
|
const char HUE_DESCRIPTION_XML[] PROGMEM =
|
|
"<?xml version=\"1.0\"?>"
|
|
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\">"
|
|
"<specVersion>"
|
|
"<major>1</major>"
|
|
"<minor>0</minor>"
|
|
"</specVersion>"
|
|
"<URLBase>http://{x1}/</URLBase>"
|
|
"<device>"
|
|
"<deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>"
|
|
"<friendlyName>Amazon-Echo-HA-Bridge ({x1})</friendlyName>"
|
|
"<manufacturer>Royal Philips Electronics</manufacturer>"
|
|
"<modelName>Philips hue bridge 2012</modelName>"
|
|
"<modelNumber>929000226503</modelNumber>"
|
|
"<UDN>uuid:{x2}</UDN>"
|
|
"</device>"
|
|
"</root>\r\n"
|
|
"\r\n";
|
|
const char HUE_LIGHT_STATUS_JSON[] PROGMEM =
|
|
"{\"state\":"
|
|
"{\"on\":{state},"
|
|
"\"bri\":{b},"
|
|
"\"hue\":{h},"
|
|
"\"sat\":{s},"
|
|
"\"effect\":\"none\","
|
|
"\"ct\":0,"
|
|
"\"alert\":\"none\","
|
|
"\"reachable\":true"
|
|
"},"
|
|
"\"type\":\"Dimmable light\","
|
|
"\"name\":\"{j1}\","
|
|
"\"modelid\":\"LWB004\","
|
|
"\"manufacturername\":\"Philips\","
|
|
"\"uniqueid\":\"{j2}\","
|
|
"\"swversion\":\"66012040\""
|
|
"}";
|
|
const char HUE_LIGHT_RESPONSE_JSON[] PROGMEM =
|
|
"{\"success\":{\"{api}/{id}/{cmd}\":{res}}}";
|
|
const char HUE_CONFIG_RESPONSE_JSON[] PROGMEM =
|
|
"{\"name\":\"Philips hue\","
|
|
"\"mac\":\"{mac}\","
|
|
"\"dhcp\":true,"
|
|
"\"ipaddress\":\"{ip}\","
|
|
"\"netmask\":\"{mask}\","
|
|
"\"gateway\":\"{gw}\","
|
|
"\"proxyaddress\":\"\","
|
|
"\"proxyport\":0,"
|
|
"\"UTC\":\"{dt}\","
|
|
"\"whitelist\":{\"{id}\":{"
|
|
"\"last use date\":\"{dt}\","
|
|
"\"create date\":\"{dt}\","
|
|
"\"name\":\"Remote\"}},"
|
|
"\"swversion\":\"01036659\","
|
|
"\"apiversion\":\"1.16.0\","
|
|
"\"swupdate\":{\"updatestate\":0,\"url\":\"\",\"text\":\"\",\"notify\": false},"
|
|
"\"linkbutton\":false,"
|
|
"\"portalservices\":false"
|
|
"}";
|
|
const char HUE_NO_AUTH_JSON[] PROGMEM =
|
|
"[{\"error\":{\"type\":101,\"address\":\"/\",\"description\":\"link button not pressed\"}}]";
|
|
#endif // USE_EMULATION
|
|
|
|
#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(80);
|
|
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("/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);
|
|
}
|
|
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<uint32_t>(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)
|
|
{
|
|
page.replace("{ha}", my_module.name);
|
|
page.replace("{h}", String(sysCfg.friendlyname[0]));
|
|
if (_httpflag == HTTP_MANAGER) {
|
|
if (WIFI_configCounter()) {
|
|
page.replace("<body>", "<body onload='u()'>");
|
|
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 {
|
|
|
|
String page = FPSTR(HTTP_HEAD);
|
|
// page.replace("<meta", "<meta http-equiv=\"refresh\" content=\"4; URL=/\"><meta"); // Fails Edge (asks for reload)
|
|
// page.replace("</script>", "setTimeout(function(){window.location.reload(1);},4000);</script>"); // Repeats POST on All
|
|
page.replace("</script>", "setTimeout(function(){window.location.replace(\"/\");},4000);</script>"); // OK on All
|
|
page.replace("{v}", "Main menu");
|
|
|
|
if (Maxdevice) {
|
|
if (strlen(webServer->arg("o").c_str())) {
|
|
do_cmnd_power(atoi(webServer->arg("o").c_str()), 2);
|
|
}
|
|
|
|
page += F("<table style='width:100%'><tr>");
|
|
for (byte idx = 1; idx <= Maxdevice; idx++) {
|
|
page += F("<td style='width:");
|
|
page += String(100 / Maxdevice);
|
|
page += F("%'><form action='/?o=");
|
|
page += String(idx);
|
|
page += F("' method='post'><div style='text-align:center;font-weight:bold;font-size:");
|
|
page += String(70 - (Maxdevice * 8));
|
|
page += F("px'>");
|
|
page += (power & (0x01 << (idx -1))) ? "ON" : "OFF";
|
|
page += F("</div><br/><button>Toggle");
|
|
if (Maxdevice > 1) {
|
|
page += F(" ");
|
|
page += String(idx);
|
|
}
|
|
page += F("</button></form></td>");
|
|
}
|
|
page += F("</tr></table><br/>");
|
|
}
|
|
|
|
String tpage = "";
|
|
if (hlw_flg) tpage += hlw_webPresent();
|
|
#ifdef USE_DS18B20
|
|
if (pin[GPIO_DSB] < 99) tpage += dsb_webPresent();
|
|
#endif // USE_DS18B20
|
|
#ifdef USE_DS18x20
|
|
if (pin[GPIO_DSB] < 99) page += ds18x20_webPresent();
|
|
#endif // USE_DS18x20
|
|
#if defined(USE_DHT) || defined(USE_DHT2)
|
|
if (dht_type) tpage += dht_webPresent();
|
|
#endif // USE_DHT/2
|
|
#ifdef USE_I2C
|
|
if (i2c_flg) {
|
|
tpage += htu_webPresent();
|
|
tpage += bmp_webPresent();
|
|
tpage += bh1750_webPresent();
|
|
}
|
|
#endif // USE_I2C
|
|
if (tpage.length() > 0) {
|
|
page += F("<table style='width:100%'>");
|
|
page += tpage;
|
|
page += F("</table><br/>");
|
|
}
|
|
|
|
if (_httpflag == HTTP_ADMIN) {
|
|
page += FPSTR(HTTP_BTN_MENU1);
|
|
page += FPSTR(HTTP_BTN_RSTRT);
|
|
}
|
|
showPage(page);
|
|
|
|
#ifdef USE_DS18x20
|
|
ds18x20_search(); // Check for changes in sensors number
|
|
ds18x20_convert(); // Start Conversion, takes up to one second
|
|
#endif // USE_DS18x20
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void handleModule()
|
|
{
|
|
if (httpUser()) return;
|
|
char stemp[20];
|
|
|
|
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++) {
|
|
page += F("<option ");
|
|
if (i == sysCfg.module) page += F("selected ");
|
|
snprintf_P(stemp, sizeof(stemp), modules[i].name);
|
|
page += F("value='"); page += String(i); page += F("'>"); page += String(i +1); page += F(" "); page += stemp; page += F("</option>");
|
|
}
|
|
page += F("</select></br>");
|
|
|
|
mytmplt cmodule;
|
|
memcpy_P(&cmodule, &modules[sysCfg.module], sizeof(cmodule));
|
|
for (byte i = 0; i < MAX_GPIO_PIN; i++) {
|
|
if (cmodule.gp.io[i] == GPIO_USER) {
|
|
page += F("<br/><b>GPIO"); page += String(i); page += F("</b> <select id='g"); page += String(i); page += F("' name='g"); page += String(i); page += F("'>");
|
|
for (byte j = GPIO_SENSOR_START; j < GPIO_SENSOR_END; j++) {
|
|
page += F("<option ");
|
|
if (j == my_module.gp.io[i]) page += F("selected ");
|
|
page += F("value='"); page += String(j); page += F("'>");
|
|
page += String(j); page += F(" ");
|
|
snprintf_P(stemp, sizeof(stemp), sensors[j]);
|
|
page += stemp; page += F("</option>");
|
|
}
|
|
page += F("</select></br>");
|
|
}
|
|
}
|
|
|
|
page += FPSTR(HTTP_FORM_END);
|
|
page += FPSTR(HTTP_BTN_CONF);
|
|
showPage(page);
|
|
}
|
|
|
|
void handleWifi1()
|
|
{
|
|
handleWifi(true);
|
|
}
|
|
|
|
void handleWifi0()
|
|
{
|
|
handleWifi(false);
|
|
}
|
|
|
|
void handleWifi(boolean scan)
|
|
{
|
|
if (httpUser()) return;
|
|
char log[LOGSZ];
|
|
|
|
addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle Wifi config"));
|
|
|
|
String page = FPSTR(HTTP_HEAD);
|
|
page.replace("{v}", "Configure Wifi");
|
|
|
|
if (scan) {
|
|
#ifdef USE_EMULATION
|
|
UDP_Disconnect();
|
|
#endif // USE_EMULATION
|
|
int n = WiFi.scanNetworks();
|
|
addLog_P(LOG_LEVEL_DEBUG, PSTR("Wifi: Scan done"));
|
|
|
|
if (n == 0) {
|
|
addLog_P(LOG_LEVEL_DEBUG, PSTR("Wifi: No networks found"));
|
|
page += F("No networks found. 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 (_removeDuplicateAPs) {
|
|
String cssid;
|
|
for (int i = 0; i < n; i++) {
|
|
if (indices[i] == -1) continue;
|
|
cssid = WiFi.SSID(indices[i]);
|
|
for (int j = i + 1; j < n; j++) {
|
|
if (cssid == WiFi.SSID(indices[j])) {
|
|
snprintf_P(log, sizeof(log), PSTR("Wifi: Duplicate AccessPoint %s"), WiFi.SSID(indices[j]).c_str());
|
|
addLog(LOG_LEVEL_DEBUG, log);
|
|
indices[j] = -1; // set dup aps to index -1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//display networks in page
|
|
for (int i = 0; i < n; i++) {
|
|
if (indices[i] == -1) continue; // skip dups
|
|
snprintf_P(log, sizeof(log), PSTR("Wifi: SSID %s, RSSI %d"), WiFi.SSID(indices[i]).c_str(), WiFi.RSSI(indices[i]));
|
|
addLog(LOG_LEVEL_DEBUG, log);
|
|
int quality = WIFI_getRSSIasQuality(WiFi.RSSI(indices[i]));
|
|
|
|
if (_minimumQuality == -1 || _minimumQuality < quality) {
|
|
String item = FPSTR(HTTP_LNK_ITEM);
|
|
String rssiQ;
|
|
rssiQ += quality;
|
|
item.replace("{v}", WiFi.SSID(indices[i]));
|
|
item.replace("{r}", rssiQ);
|
|
if (WiFi.encryptionType(indices[i]) != ENC_TYPE_NONE) {
|
|
item.replace("{i}", "l");
|
|
} else {
|
|
item.replace("{i}", "");
|
|
}
|
|
page += item;
|
|
delay(0);
|
|
} else {
|
|
addLog_P(LOG_LEVEL_DEBUG, PSTR("Wifi: Skipping due to low quality"));
|
|
}
|
|
|
|
}
|
|
page += "<br/>";
|
|
}
|
|
} else {
|
|
page += FPSTR(HTTP_LNK_SCAN);
|
|
}
|
|
|
|
page += FPSTR(HTTP_FORM_WIFI);
|
|
page.replace("{h1}", String(sysCfg.hostname));
|
|
page.replace("{s1}", String(sysCfg.sta_ssid[0]));
|
|
page.replace("{p1}", String(sysCfg.sta_pwd[0]));
|
|
page.replace("{s2}", String(sysCfg.sta_ssid[1]));
|
|
page.replace("{p2}", String(sysCfg.sta_pwd[1]));
|
|
page += FPSTR(HTTP_FORM_END);
|
|
if (_httpflag == HTTP_MANAGER) {
|
|
page += FPSTR(HTTP_BTN_RSTRT);
|
|
} else {
|
|
page += FPSTR(HTTP_BTN_CONF);
|
|
}
|
|
showPage(page);
|
|
}
|
|
|
|
void handleMqtt()
|
|
{
|
|
if (httpUser()) return;
|
|
addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle MQTT config"));
|
|
|
|
String page = FPSTR(HTTP_HEAD);
|
|
page.replace("{v}", "Configure MQTT");
|
|
page += FPSTR(HTTP_FORM_MQTT);
|
|
char str[sizeof(sysCfg.mqtt_client)];
|
|
getClient(str, MQTT_CLIENT_ID, sizeof(sysCfg.mqtt_client));
|
|
page.replace("{m0}", str);
|
|
page.replace("{m1}", String(sysCfg.mqtt_host));
|
|
page.replace("{m2}", String(sysCfg.mqtt_port));
|
|
page.replace("{m3}", String(sysCfg.mqtt_client));
|
|
page.replace("{m4}", String(sysCfg.mqtt_user));
|
|
page.replace("{m5}", String(sysCfg.mqtt_pwd));
|
|
page.replace("{m6}", String(sysCfg.mqtt_topic));
|
|
page += FPSTR(HTTP_FORM_END);
|
|
page += FPSTR(HTTP_BTN_CONF);
|
|
showPage(page);
|
|
}
|
|
|
|
void handleLog()
|
|
{
|
|
if (httpUser()) return;
|
|
addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle Log config"));
|
|
|
|
String page = FPSTR(HTTP_HEAD);
|
|
page.replace("{v}", "Config logging");
|
|
page += FPSTR(HTTP_FORM_LOG1);
|
|
for (byte idx = 0; idx < 3; idx++) {
|
|
page += FPSTR(HTTP_FORM_LOG2);
|
|
switch (idx) {
|
|
case 0:
|
|
page.replace("{b0}", "Serial log");
|
|
page.replace("{b1}", STR(SERIAL_LOG_LEVEL));
|
|
page.replace("{b2}", "ls");
|
|
for (byte i = LOG_LEVEL_NONE; i < LOG_LEVEL_ALL; i++) {
|
|
page.replace("{a" + String(i), (i == sysCfg.seriallog_level) ? " selected " : " ");
|
|
}
|
|
break;
|
|
case 1:
|
|
page.replace("{b0}", "Web log");
|
|
page.replace("{b1}", STR(WEB_LOG_LEVEL));
|
|
page.replace("{b2}", "lw");
|
|
for (byte i = LOG_LEVEL_NONE; i < LOG_LEVEL_ALL; i++) {
|
|
page.replace("{a" + String(i), (i == sysCfg.weblog_level) ? " selected " : " ");
|
|
}
|
|
break;
|
|
case 2:
|
|
page.replace("{b0}", "Syslog");
|
|
page.replace("{b1}", STR(SYS_LOG_LEVEL));
|
|
page.replace("{b2}", "ll");
|
|
for (byte i = LOG_LEVEL_NONE; i < LOG_LEVEL_ALL; i++) {
|
|
page.replace("{a" + String(i), (i == sysCfg.syslog_level) ? " selected " : " ");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
page += FPSTR(HTTP_FORM_LOG3);
|
|
page.replace("{l2}", String(sysCfg.syslog_host));
|
|
page.replace("{l3}", String(sysCfg.syslog_port));
|
|
page.replace("{l4}", String(sysCfg.tele_period));
|
|
page += FPSTR(HTTP_FORM_END);
|
|
page += FPSTR(HTTP_BTN_CONF);
|
|
showPage(page);
|
|
}
|
|
|
|
void handleOther()
|
|
{
|
|
if (httpUser()) return;
|
|
addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle other config"));
|
|
char stemp[40];
|
|
|
|
String page = FPSTR(HTTP_HEAD);
|
|
page.replace("{v}", "Configure Other");
|
|
page += FPSTR(HTTP_FORM_OTHER);
|
|
page.replace("{r1}", (sysCfg.mqtt_enabled) ? " checked" : "");
|
|
page += FPSTR(HTTP_FORM_OTHER2);
|
|
page.replace("{1", "1");
|
|
page.replace("{2", FRIENDLY_NAME);
|
|
page.replace("{3", String(sysCfg.friendlyname[0]));
|
|
#ifdef USE_EMULATION
|
|
page += FPSTR(HTTP_FORM_OTHER3);
|
|
page.replace("{r2}", (sysCfg.emulation == EMUL_NONE) ? " checked" : "");
|
|
page.replace("{r3}", (sysCfg.emulation == EMUL_WEMO) ? " checked" : "");
|
|
page.replace("{r4}", (sysCfg.emulation == EMUL_HUE) ? " checked" : "");
|
|
for (int i = 1; i < Maxdevice; i++) {
|
|
page += FPSTR(HTTP_FORM_OTHER2);
|
|
page.replace("{1", String(i +1));
|
|
snprintf_P(stemp, sizeof(stemp), PSTR(FRIENDLY_NAME"%d"), i +1);
|
|
page.replace("{2", stemp);
|
|
page.replace("{3", String(sysCfg.friendlyname[i]));
|
|
}
|
|
page += F("<br/></fieldset>");
|
|
#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);
|
|
String attachment = F("attachment; filename=Config_");
|
|
attachment += sysCfg.friendlyname[0];
|
|
attachment += F("_");
|
|
attachment += Version;
|
|
attachment += F(".dmp");
|
|
webServer->sendHeader("Content-Disposition", attachment);
|
|
webServer->send(200, "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], stemp[20];
|
|
byte what = 0, 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("<br/>Trying to connect device to network<br/>If it fails reconnect to try again");
|
|
break;
|
|
case 2:
|
|
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 : webServer->arg("mu").c_str(), sizeof(sysCfg.mqtt_user));
|
|
strlcpy(sysCfg.mqtt_pwd, (!strlen(webServer->arg("mp").c_str())) ? MQTT_PASS : webServer->arg("mp").c_str(), sizeof(sysCfg.mqtt_pwd));
|
|
strlcpy(sysCfg.mqtt_topic, (!strlen(webServer->arg("mt").c_str())) ? MQTT_TOPIC : webServer->arg("mt").c_str(), sizeof(sysCfg.mqtt_topic));
|
|
snprintf_P(log, sizeof(log), PSTR("HTTP: MQTT Host %s, Port %d, Client %s, User %s, Password %s, Topic %s"),
|
|
sysCfg.mqtt_host, sysCfg.mqtt_port, sysCfg.mqtt_client, sysCfg.mqtt_user, sysCfg.mqtt_pwd, sysCfg.mqtt_topic);
|
|
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:
|
|
sysCfg.mqtt_enabled = webServer->hasArg("b1");
|
|
#ifdef USE_EMULATION
|
|
sysCfg.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"),
|
|
(sysCfg.mqtt_enabled) ? MQTT_STATUS_ON : MQTT_STATUS_OFF, sysCfg.emulation, sysCfg.friendlyname[0], sysCfg.friendlyname[1], sysCfg.friendlyname[2], sysCfg.friendlyname[3]);
|
|
addLog(LOG_LEVEL_INFO, log);
|
|
break;
|
|
case 6:
|
|
sysCfg.module = (!strlen(webServer->arg("mt").c_str())) ? MODULE : atoi(webServer->arg("mt").c_str());
|
|
mytmplt cmodule;
|
|
memcpy_P(&cmodule, &modules[sysCfg.module], sizeof(cmodule));
|
|
String gpios = "";
|
|
for (byte i = 0; i < MAX_GPIO_PIN; i++) {
|
|
if (cmodule.gp.io[i] == GPIO_USER) {
|
|
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]);
|
|
}
|
|
}
|
|
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("{v}", "Save parameters");
|
|
page += F("<div style='text-align:center;'><b>Parameters saved</b><br/>");
|
|
page += result;
|
|
page += F("</div>");
|
|
page += FPSTR(HTTP_MSG_RSTRT);
|
|
if (_httpflag == HTTP_MANAGER) {
|
|
_httpflag = HTTP_ADMIN;
|
|
} else {
|
|
page += FPSTR(HTTP_BTN_MAIN);
|
|
}
|
|
showPage(page);
|
|
|
|
restartflag = 2;
|
|
} else {
|
|
handleConfig();
|
|
}
|
|
}
|
|
|
|
void handleReset()
|
|
{
|
|
if (httpUser()) return;
|
|
|
|
char svalue[MESSZ];
|
|
|
|
addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Reset parameters"));
|
|
|
|
String page = FPSTR(HTTP_HEAD);
|
|
page.replace("{v}", "Default parameters");
|
|
page += F("<div style='text-align:center;'>Parameters reset to default</div>");
|
|
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("{v}", "Restore Configuration");
|
|
page += FPSTR(HTTP_FORM_RST);
|
|
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("{v}", "Firmware upgrade");
|
|
page += FPSTR(HTTP_FORM_UPG);
|
|
page.replace("{o1}", String(sysCfg.otaUrl));
|
|
page += FPSTR(HTTP_BTN_MAIN);
|
|
showPage(page);
|
|
|
|
_uploaderror = 0;
|
|
_uploadfiletype = 0;
|
|
}
|
|
|
|
void handleUpgradeStart()
|
|
{
|
|
if (httpUser()) return;
|
|
char svalue[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("{v}", "Info");
|
|
page += F("<div style='text-align:center;'><b>Upgrade started ...</b></div>");
|
|
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 log[LOGSZ];
|
|
WIFI_configCounter();
|
|
restartflag = 0;
|
|
mqttcounter = 0;
|
|
|
|
String page = FPSTR(HTTP_HEAD);
|
|
page.replace("{v}", "Info");
|
|
page += F("<div style='text-align:center;'><b>Upload ");
|
|
if (_uploaderror) {
|
|
page += F("<font color='red'>failed</font></b><br/><br/>");
|
|
String error = "";
|
|
if (_uploaderror == 1) {
|
|
error = F("No file selected");
|
|
} else if (_uploaderror == 2) {
|
|
error = F("File size is larger than available free space");
|
|
} else if (_uploaderror == 3) {
|
|
error = F("File magic header does not start with 0xE9");
|
|
} else if (_uploaderror == 4) {
|
|
error = F("File flash size is larger than device flash size");
|
|
} else if (_uploaderror == 5) {
|
|
error = F("File upload buffer miscompare");
|
|
} else if (_uploaderror == 6) {
|
|
error = F("Upload failed. Enable logging option 3 for more information");
|
|
} else if (_uploaderror == 7) {
|
|
error = F("Upload aborted");
|
|
} else if (_uploaderror == 8) {
|
|
error = F("Invalid configuration file");
|
|
} else if (_uploaderror == 9) {
|
|
error = F("Configuration file too large");
|
|
} else {
|
|
error = F("Upload error code ");
|
|
error += String(_uploaderror);
|
|
}
|
|
page += error;
|
|
if (!_uploadfiletype && Update.hasError()) {
|
|
page += F("<br/><br/>Update error code (see Updater.cpp) ");
|
|
page += String(Update.getError());
|
|
}
|
|
snprintf_P(log, sizeof(log), PSTR("Upload: Error - %s"), error.c_str());
|
|
addLog(LOG_LEVEL_DEBUG, log);
|
|
} else {
|
|
page += F("<font color='green'>successful</font></b><br/><br/>Device will restart in a few seconds");
|
|
restartflag = 2;
|
|
}
|
|
page += F("</div><br/>");
|
|
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 (_httpflag == HTTP_USER) return;
|
|
if (_uploaderror) {
|
|
if (!_uploadfiletype) Update.end();
|
|
return;
|
|
}
|
|
|
|
HTTPUpload& upload = webServer->upload();
|
|
|
|
if (upload.status == UPLOAD_FILE_START) {
|
|
restartflag = 60;
|
|
if (upload.filename.c_str()[0] == 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.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.status == UPLOAD_FILE_WRITE)) {
|
|
if (upload.totalSize == 0)
|
|
{
|
|
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 ((sysCfg.module == SONOFF_TOUCH) || (sysCfg.module == SONOFF_4CH)) {
|
|
upload.buf[2] = 3; // DOUT - ESP8285
|
|
addLog_P(LOG_LEVEL_DEBUG, PSTR("FLSH: Updated Flash Chip 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.status == UPLOAD_FILE_END)) {
|
|
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.status == UPLOAD_FILE_ABORTED) {
|
|
restartflag = 0;
|
|
mqttcounter = 0;
|
|
_uploaderror = 7;
|
|
if (!_uploadfiletype) Update.end();
|
|
}
|
|
delay(0);
|
|
}
|
|
|
|
void handleCmnd()
|
|
{
|
|
if (httpUser()) return;
|
|
char svalue[MESSZ];
|
|
|
|
addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle cmnd"));
|
|
|
|
byte curridx = logidx;
|
|
if (strlen(webServer->arg(0).c_str())) {
|
|
snprintf_P(svalue, sizeof(svalue), webServer->arg(0).c_str());
|
|
do_cmnd(svalue);
|
|
}
|
|
|
|
String message = "";
|
|
if (logidx != curridx) {
|
|
byte counter = curridx;
|
|
do {
|
|
if (Log[counter].length()) {
|
|
if (message.length()) message += F("\n");
|
|
if (sysCfg.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");
|
|
}
|
|
|
|
webServer->sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
webServer->sendHeader("Pragma", "no-cache");
|
|
webServer->sendHeader("Expires", "-1");
|
|
webServer->send(200, "text/plain", message);
|
|
}
|
|
|
|
void handleConsole()
|
|
{
|
|
if (httpUser()) return;
|
|
char svalue[MESSZ];
|
|
|
|
addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle console"));
|
|
|
|
if (strlen(webServer->arg("c1").c_str())) {
|
|
snprintf_P(svalue, sizeof(svalue), webServer->arg("c1").c_str());
|
|
do_cmnd(svalue);
|
|
}
|
|
|
|
String page = FPSTR(HTTP_HEAD);
|
|
page.replace("{v}", "Console");
|
|
page.replace("<body>", "<body onload='l()'>");
|
|
page += FPSTR(HTTP_FORM_CMND);
|
|
page += FPSTR(HTTP_BTN_MAIN);
|
|
showPage(page);
|
|
}
|
|
|
|
void handleAjax()
|
|
{
|
|
if (httpUser()) return;
|
|
String message = "";
|
|
uint16_t size = 0;
|
|
|
|
int maxSize = ESP.getFreeHeap() - 6000;
|
|
|
|
byte counter = logidx;
|
|
do {
|
|
counter--;
|
|
if (counter == 255) counter = MAX_LOG_LINES -1;
|
|
size += Log[counter].length();
|
|
} while ((counter != logidx) && (size < maxSize));
|
|
do {
|
|
if (Log[counter].length()) {
|
|
if (message.length()) message += F("\n");
|
|
message += Log[counter];
|
|
}
|
|
counter++;
|
|
if (counter > MAX_LOG_LINES -1) counter = 0;
|
|
} while (counter != logidx);
|
|
webServer->sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
webServer->sendHeader("Pragma", "no-cache");
|
|
webServer->sendHeader("Expires", "-1");
|
|
webServer->send(200, "text/plain", message);
|
|
}
|
|
|
|
void handleInfo()
|
|
{
|
|
if (httpUser()) return;
|
|
addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle info"));
|
|
|
|
int freeMem = ESP.getFreeHeap();
|
|
|
|
String page = FPSTR(HTTP_HEAD);
|
|
page.replace("{v}", "Information");
|
|
// page += F("<fieldset><legend><b> Information </b></legend>");
|
|
page += F("<style>td{padding:0px 5px;}</style>");
|
|
page += F("<table style'width:100%;'>");
|
|
page += F("<tr><th>Program version</th><td>"); page += Version; page += F("</td></tr>");
|
|
page += F("<tr><th>Build Date/Time</th><td>"); page += __DATE__;
|
|
page += F("/"); page += __TIME__ ; page += F("</td></tr>");
|
|
page += F("<tr><th>Core/SDK version</th><td>"); page += ESP.getCoreVersion(); page += F("/"); page += String(ESP.getSdkVersion()); page += F("</td></tr>");
|
|
// page += F("<tr><th>Boot version</th><td>"); page += String(ESP.getBootVersion()); page += F("</td></tr>");
|
|
page += F("<tr><th>Uptime</th><td>"); page += String(uptime); page += F(" Hours</td></tr>");
|
|
page += F("<tr><th>Flash write count</th><td>"); page += String(sysCfg.saveFlag); page += F("</td></tr>");
|
|
page += F("<tr><th>Boot count</th><td>"); page += String(sysCfg.bootcount); page += F("</td></tr>");
|
|
page += F("<tr><th>Reset reason</th><td>"); page += ESP.getResetReason(); page += F("</td></tr>");
|
|
for (byte i = 0; i < Maxdevice; i++) {
|
|
page += F("<tr><th>Friendly name ");
|
|
page += i +1;
|
|
page += F("</th><td>"); page += String(sysCfg.friendlyname[i]); page += F("</td></tr>");
|
|
}
|
|
page += F("<tr><td> </td></tr>");
|
|
// page += F("<tr><th>SSId (RSSI)</th><td>"); page += (sysCfg.sta_active)? sysCfg.sta_ssid2 : sysCfg.sta_ssid1; page += F(" ("); page += WIFI_getRSSIasQuality(WiFi.RSSI()); page += F("%)</td></tr>");
|
|
page += F("<tr><th>AP"); page += String(sysCfg.sta_active +1); page += F(" SSId (RSSI)</th><td>"); page += sysCfg.sta_ssid[sysCfg.sta_active]; page += F(" ("); page += WIFI_getRSSIasQuality(WiFi.RSSI()); page += F("%)</td></tr>");
|
|
page += F("<tr><th>Hostname</th><td>"); page += Hostname; page += F("</td></tr>");
|
|
if (static_cast<uint32_t>(WiFi.localIP()) != 0) {
|
|
page += F("<tr><th>IP address</th><td>"); page += WiFi.localIP().toString(); page += F("</td></tr>");
|
|
page += F("<tr><th>Gateway</th><td>"); page += WiFi.gatewayIP().toString(); page += F("</td></tr>");
|
|
page += F("<tr><th>MAC address</th><td>"); page += WiFi.macAddress(); page += F("</td></tr>");
|
|
}
|
|
if (static_cast<uint32_t>(WiFi.softAPIP()) != 0) {
|
|
page += F("<tr><th>AP IP address</th><td>"); page += WiFi.softAPIP().toString(); page += F("</td></tr>");
|
|
page += F("<tr><th>AP Gateway</th><td>"); page += WiFi.softAPIP().toString(); page += F("</td></tr>");
|
|
page += F("<tr><th>AP MAC address</th><td>"); page += WiFi.softAPmacAddress(); page += F("</td></tr>");
|
|
}
|
|
page += F("<tr><td> </td></tr>");
|
|
if (sysCfg.mqtt_enabled) {
|
|
page += F("<tr><th>MQTT Host</th><td>"); page += sysCfg.mqtt_host; page += F("</td></tr>");
|
|
page += F("<tr><th>MQTT Port</th><td>"); page += String(sysCfg.mqtt_port); page += F("</td></tr>");
|
|
page += F("<tr><th>MQTT Client and<br/> Fallback Topic</th><td>"); page += MQTTClient; page += F("</td></tr>");
|
|
page += F("<tr><th>MQTT User</th><td>"); page += sysCfg.mqtt_user; page += F("</td></tr>");
|
|
// page += F("<tr><th>MQTT Password</th><td>"); page += sysCfg.mqtt_pwd; page += F("</td></tr>");
|
|
page += F("<tr><th>MQTT Topic</th><td>"); page += sysCfg.mqtt_topic; page += F("</td></tr>");
|
|
page += F("<tr><th>MQTT Group Topic</th><td>"); page += sysCfg.mqtt_grptopic; page += F("</td></tr>");
|
|
} else {
|
|
page += F("<tr><th>MQTT</th><td>Disabled</td></tr>");
|
|
}
|
|
|
|
page += F("<tr><th>Emulation</th><td>");
|
|
#ifdef USE_EMULATION
|
|
if (sysCfg.emulation == EMUL_WEMO) {
|
|
page += F("Belkin WeMo");
|
|
}
|
|
else if (sysCfg.emulation == EMUL_HUE) {
|
|
page += F("Hue Bridge");
|
|
}
|
|
else {
|
|
page += F("None");
|
|
}
|
|
#else
|
|
page += F("Disabled");
|
|
#endif // USE_EMULATION
|
|
page += F("</td></tr>");
|
|
|
|
page += F("<tr><th>mDNS Discovery</th><td>");
|
|
#ifdef USE_DISCOVERY
|
|
page += F("Enabled");
|
|
page += F("</td></tr>");
|
|
page += F("<tr><th>mDNS Webserver Advertise</th><td>");
|
|
#ifdef WEBSERVER_ADVERTISE
|
|
page += F("Enabled");
|
|
#else
|
|
page += F("Disabled");
|
|
#endif // WEBSERVER_ADVERTISE
|
|
#else
|
|
page += F("Disabled");
|
|
#endif // USE_DISCOVERY
|
|
page += F("</td></tr>");
|
|
|
|
page += F("<tr><td> </td></tr>");
|
|
page += F("<tr><th>ESP Chip id</th><td>"); page += String(ESP.getChipId()); page += F("</td></tr>");
|
|
page += F("<tr><th>Flash Chip id</th><td>"); page += String(ESP.getFlashChipId()); page += F("</td></tr>");
|
|
page += F("<tr><th>Flash size</th><td>"); page += String(ESP.getFlashChipRealSize() / 1024); page += F("kB</td></tr>");
|
|
page += F("<tr><th>Program flash size</th><td>"); page += String(ESP.getFlashChipSize() / 1024); page += F("kB</td></tr>");
|
|
page += F("<tr><th>Program size</th><td>"); page += String(ESP.getSketchSize() / 1024); page += F("kB</td></tr>");
|
|
page += F("<tr><th>Free program space</th><td>"); page += String(ESP.getFreeSketchSpace() / 1024); page += F("kB</td></tr>");
|
|
page += F("<tr><th>Free memory</th><td>"); page += String(freeMem / 1024); page += F("kB</td></tr>");
|
|
page += F("</table>");
|
|
// page += F("</fieldset>");
|
|
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("{v}", "Info");
|
|
page += FPSTR(HTTP_MSG_RSTRT);
|
|
if (_httpflag == HTTP_MANAGER) {
|
|
_httpflag = HTTP_ADMIN;
|
|
} else {
|
|
page += FPSTR(HTTP_BTN_MAIN);
|
|
}
|
|
showPage(page);
|
|
|
|
restartflag = 2;
|
|
}
|
|
|
|
/********************************************************************************************/
|
|
|
|
#ifdef USE_EMULATION
|
|
void handleUPnPevent()
|
|
{
|
|
addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle WeMo basic event"));
|
|
|
|
String request = webServer->arg(0);
|
|
if (request.indexOf("State>1</Binary") > 0) do_cmnd_power(1, 1);
|
|
if (request.indexOf("State>0</Binary") > 0) do_cmnd_power(1, 0);
|
|
webServer->send(200, "text/plain", "");
|
|
}
|
|
|
|
void handleUPnPservice()
|
|
{
|
|
addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle WeMo event service"));
|
|
|
|
String eventservice_xml = FPSTR(WEMO_EVENTSERVICE_XML);
|
|
webServer->send(200, "text/plain", eventservice_xml);
|
|
}
|
|
|
|
void handleUPnPsetupWemo()
|
|
{
|
|
addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle WeMo setup"));
|
|
|
|
String setup_xml = FPSTR(WEMO_SETUP_XML);
|
|
setup_xml.replace("{x1}", String(sysCfg.friendlyname[0]));
|
|
setup_xml.replace("{x2}", wemo_UUID());
|
|
setup_xml.replace("{x3}", wemo_serial());
|
|
webServer->send(200, "text/xml", setup_xml);
|
|
}
|
|
|
|
/********************************************************************************************/
|
|
|
|
String hue_deviceId(uint8_t id)
|
|
{
|
|
char deviceid[16];
|
|
|
|
snprintf_P(deviceid, sizeof(deviceid), PSTR("5CCF7F%03X-%0d"), ESP.getChipId(), id);
|
|
return String(deviceid);
|
|
}
|
|
|
|
void handleUPnPsetupHue()
|
|
{
|
|
addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle Hue Bridge setup"));
|
|
|
|
String description_xml = FPSTR(HUE_DESCRIPTION_XML);
|
|
description_xml.replace("{x1}", WiFi.localIP().toString());
|
|
description_xml.replace("{x2}", hue_UUID());
|
|
webServer->send(200, "text/xml", description_xml);
|
|
}
|
|
|
|
void hue_todo(String *path)
|
|
{
|
|
char log[LOGSZ];
|
|
|
|
snprintf_P(log, sizeof(log), PSTR("HTTP: HUE API not implemented (%s)"),path->c_str());
|
|
addLog(LOG_LEVEL_DEBUG_MORE, log);
|
|
}
|
|
|
|
void hue_config_response(String *response)
|
|
{
|
|
char buffer[21];
|
|
|
|
*response += FPSTR(HUE_CONFIG_RESPONSE_JSON);
|
|
response->replace("{mac}", WiFi.macAddress());
|
|
response->replace("{ip}", WiFi.localIP().toString());
|
|
response->replace("{mask}", WiFi.subnetMask().toString());
|
|
response->replace("{gw}", WiFi.gatewayIP().toString());
|
|
snprintf_P(buffer, sizeof(buffer), PSTR("%04d-%02d-%02dT%02d:%02d:%02d"),
|
|
rtcTime.Year, rtcTime.Month, rtcTime.Day, rtcTime.Hour, rtcTime.Minute, rtcTime.Second);
|
|
response->replace("{dt}", String(buffer));
|
|
}
|
|
|
|
void hue_global_cfg(String *path)
|
|
{
|
|
String response;
|
|
|
|
path->remove(0,1); // cut leading / to get <id>
|
|
response = "{\"lights\":{\"";
|
|
for (uint8_t i = 1; i <= Maxdevice; i++)
|
|
{
|
|
response += i;
|
|
response += "\":";
|
|
response += FPSTR(HUE_LIGHT_STATUS_JSON);
|
|
if (i < Maxdevice) response += ",\"";
|
|
response.replace("{state}", (power & (0x01 << (i-1))) ? "true" : "false");
|
|
response.replace("{j1}", sysCfg.friendlyname[i-1]);
|
|
response.replace("{j2}", hue_deviceId(i));
|
|
if (pin[GPIO_WS2812] < 99) {
|
|
#ifdef USE_WS2812
|
|
ws2812_replaceHSB(&response);
|
|
#endif // USE_WS2812
|
|
} else
|
|
{
|
|
response.replace("{h}", "0");
|
|
response.replace("{s}", "0");
|
|
response.replace("{b}", "0");
|
|
}
|
|
}
|
|
response += F("},\"groups\":{},\"schedules\":{},\"config\":");
|
|
|
|
hue_config_response(&response);
|
|
response.replace("{id}", *path);
|
|
response += "}";
|
|
webServer->send(200, "application/json", response);
|
|
}
|
|
|
|
void hue_auth(String *path)
|
|
{
|
|
String response;
|
|
char uid[7];
|
|
|
|
snprintf_P(uid, sizeof(uid), PSTR("%03x"), ESP.getChipId());
|
|
response="[{\"success\":{\"username\":\"";
|
|
response+=String(uid);
|
|
response+="\"}}]";
|
|
webServer->send(200, "application/json", response);
|
|
}
|
|
|
|
void hue_config(String *path)
|
|
{
|
|
String response = "";
|
|
|
|
path->remove(0,1); // cut leading / to get <id>
|
|
hue_config_response(&response);
|
|
response.replace("{id}", *path);
|
|
webServer->send(200, "application/json", response);
|
|
}
|
|
|
|
void hue_lights(String *path)
|
|
{
|
|
String response;
|
|
uint8_t device = 1;
|
|
int16_t pos = 0;
|
|
uint8_t bri = 0;
|
|
char id[4];
|
|
|
|
path->remove(0,path->indexOf("/lights")); // Remove until /lights
|
|
if (path->endsWith("/lights")) // Got /lights
|
|
{
|
|
response = "{\"";
|
|
for (uint8_t i = 1; i <= Maxdevice; i++)
|
|
{
|
|
response += i;
|
|
response += "\":";
|
|
response += FPSTR(HUE_LIGHT_STATUS_JSON);
|
|
if (i < Maxdevice) response += ",\"";
|
|
response.replace("{state}", (power & (0x01 << (i-1))) ? "true" : "false");
|
|
response.replace("{j1}", sysCfg.friendlyname[i-1]);
|
|
response.replace("{j2}", hue_deviceId(i));
|
|
if (pin[GPIO_WS2812] < 99) {
|
|
#ifdef USE_WS2812
|
|
ws2812_replaceHSB(&response);
|
|
#endif // USE_WS2812
|
|
} else
|
|
{
|
|
response.replace("{h}", "0");
|
|
response.replace("{s}", "0");
|
|
response.replace("{b}", "0");
|
|
}
|
|
}
|
|
response += "}";
|
|
webServer->send(200, "application/json", response);
|
|
}
|
|
else if (path->endsWith("/state")) // Got ID/state
|
|
{
|
|
path->remove(0,8); // Remove /lights/
|
|
path->remove(path->indexOf("/state")); // Remove /state
|
|
device = atoi(path->c_str());
|
|
if ((device < 1) || (device > Maxdevice)) device = 1;
|
|
response = "[";
|
|
response += FPSTR(HUE_LIGHT_RESPONSE_JSON);
|
|
response.replace("{api}", "/lights");
|
|
response.replace("{id}", String(device));
|
|
response.replace("{cmd}", "state/on");
|
|
if (webServer->args() == 1)
|
|
{
|
|
String json = webServer->arg(0);
|
|
json.replace(" ",""); // remove blanks
|
|
|
|
if (json.indexOf("\"on\":") >= 0) // Got "on" command
|
|
{
|
|
if (json.indexOf("false") >= 0) // false -> turn device off
|
|
{
|
|
do_cmnd_power(device, 0);
|
|
response.replace("{res}", "false");
|
|
}
|
|
else if(json.indexOf("true") >= 0) // true -> Turn device on
|
|
{
|
|
do_cmnd_power(device, 1);
|
|
response.replace("{res}", "true");
|
|
}
|
|
else
|
|
{
|
|
response.replace("{res}", (power & (0x01 << (device-1))) ? "true" : "false");
|
|
}
|
|
}
|
|
#ifdef USE_WS2812
|
|
if ((pin[GPIO_WS2812] < 99) && ((pos=json.indexOf("\"bri\":")) >= 0)) {
|
|
bri = atoi(json.substring(pos+6).c_str());
|
|
ws2812_changeBrightness(bri);
|
|
response += ",";
|
|
response += FPSTR(HUE_LIGHT_RESPONSE_JSON);
|
|
response.replace("{api}", "/lights");
|
|
response.replace("{id}", String(device));
|
|
response.replace("{cmd}", "state/bri");
|
|
response.replace("{res}", String(bri));
|
|
}
|
|
#endif // USE_WS2812
|
|
response += "]";
|
|
webServer->send(200, "application/json", response);
|
|
}
|
|
else webServer->send(406, "application/json", "{}");
|
|
}
|
|
else if(path->indexOf("/lights/") >= 0) { // Got /lights/ID
|
|
path->remove(0,8); // Remove /lights/
|
|
device = atoi(path->c_str());
|
|
if ((device < 1) || (device > Maxdevice)) device = 1;
|
|
response = FPSTR(HUE_LIGHT_STATUS_JSON);
|
|
response.replace("{state}", (power & (0x01 << (device -1))) ? "true" : "false");
|
|
response.replace("{j1}", sysCfg.friendlyname[device -1]);
|
|
response.replace("{j2}", hue_deviceId(device));
|
|
if (pin[GPIO_WS2812] < 99) {
|
|
#ifdef USE_WS2812
|
|
ws2812_replaceHSB(&response);
|
|
#endif // USE_WS2812
|
|
} else
|
|
{
|
|
response.replace("{h}", "0");
|
|
response.replace("{s}", "0");
|
|
response.replace("{b}", "0");
|
|
}
|
|
webServer->send(200, "application/json", response);
|
|
}
|
|
else webServer->send(406, "application/json", "{}");
|
|
}
|
|
|
|
void handle_hue_api(String *path)
|
|
{
|
|
/* HUE API uses /api/<userid>/<command> syntax. The userid is created by the echo device and
|
|
* on original HUE the pressed button allows for creation of this user. We simply ignore the
|
|
* user part and allow every caller as with Web or WeMo.
|
|
*
|
|
* (c) Heiko Krupp, 2017
|
|
*/
|
|
|
|
char log[LOGSZ];
|
|
uint8_t args = 0;
|
|
|
|
path->remove(0, 4); // remove /api
|
|
snprintf_P(log, sizeof(log), PSTR("HTTP: Handle Hue API (%s)"), path->c_str());
|
|
addLog(LOG_LEVEL_DEBUG_MORE, log);
|
|
for (args = 0; args < webServer->args(); args++) {
|
|
String json = webServer->arg(args);
|
|
snprintf_P(log, sizeof(log), PSTR("HTTP: Hue POST args (%s)"), json.c_str());
|
|
addLog(LOG_LEVEL_DEBUG_MORE, log);
|
|
}
|
|
|
|
if (path->endsWith("/invalid/")) {} // Just ignore
|
|
else if (path->endsWith("/")) hue_auth(path); // New HUE App setup
|
|
else if (path->endsWith("/config")) hue_config(path);
|
|
else if (path->indexOf("/lights") >= 0) hue_lights(path);
|
|
else if (path->endsWith("/groups")) hue_todo(path);
|
|
else if (path->endsWith("/schedules")) hue_todo(path);
|
|
else if (path->endsWith("/sensors")) hue_todo(path);
|
|
else if (path->endsWith("/scenes")) hue_todo(path);
|
|
else if (path->endsWith("/rules")) hue_todo(path);
|
|
else hue_global_cfg(path);
|
|
/*
|
|
{
|
|
snprintf_P(log, sizeof(log), PSTR("HTTP: Handle Hue API (%s)"),path->c_str());
|
|
addLog(LOG_LEVEL_DEBUG_MORE, log);
|
|
webServer->send(406, "application/json", "{}");
|
|
}
|
|
*/
|
|
}
|
|
#endif // USE_EMULATION
|
|
|
|
/********************************************************************************************/
|
|
|
|
void handleNotFound()
|
|
{
|
|
if (captivePortal()) { // If captive portal redirect instead of displaying the error page.
|
|
return;
|
|
}
|
|
|
|
#ifdef USE_EMULATION
|
|
String path = webServer->uri();
|
|
if ((sysCfg.emulation == EMUL_HUE) && (path.startsWith("/api"))) {
|
|
handle_hue_api(&path);
|
|
} else
|
|
#endif // USE_EMULATION
|
|
{
|
|
String message = "File Not Found\n\n";
|
|
message += "URI: ";
|
|
message += webServer->uri();
|
|
message += "\nMethod: ";
|
|
message += ( webServer->method() == HTTP_GET ) ? "GET" : "POST";
|
|
message += "\nArguments: ";
|
|
message += webServer->args();
|
|
message += "\n";
|
|
for ( uint8_t i = 0; i < webServer->args(); i++ ) {
|
|
message += " " + webServer->argName ( i ) + ": " + webServer->arg ( i ) + "\n";
|
|
}
|
|
|
|
webServer->sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
webServer->sendHeader("Pragma", "no-cache");
|
|
webServer->sendHeader("Expires", "-1");
|
|
webServer->send(404, "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 (!isIp(webServer->hostHeader())) {
|
|
addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Request redirected to captive portal"));
|
|
|
|
webServer->sendHeader("Location", String("http://") + webServer->client().localIP().toString(), true);
|
|
webServer->send(302, "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
|