xdrv_01_webserver.ino 78 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086
  1. /*
  2. xdrv_01_webserver.ino - webserver for Sonoff-Tasmota
  3. Copyright (C) 2018 Theo Arends
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. #ifdef USE_WEBSERVER
  16. /*********************************************************************************************\
  17. * Web server and WiFi Manager
  18. *
  19. * Enables configuration and reconfiguration of WiFi credentials using a Captive Portal
  20. * Based on source by AlexT (https://github.com/tzapu)
  21. \*********************************************************************************************/
  22. #define XDRV_01 1
  23. #ifndef WIFI_SOFT_AP_CHANNEL
  24. #define WIFI_SOFT_AP_CHANNEL 1 // Soft Access Point Channel number between 1 and 11 as used by SmartConfig web GUI
  25. #endif
  26. #define HTTP_REFRESH_TIME 2345 // milliseconds
  27. #define HTTP_RESTART_RECONNECT_TIME 9000 // milliseconds
  28. #define HTTP_OTA_RESTART_RECONNECT_TIME 20000 // milliseconds
  29. #include <ESP8266WebServer.h>
  30. #include <DNSServer.h>
  31. #ifdef USE_RF_FLASH
  32. uint8_t *efm8bb1_update = NULL;
  33. #endif // USE_RF_FLASH
  34. enum UploadTypes { UPL_TASMOTA, UPL_SETTINGS, UPL_EFM8BB1 };
  35. const char HTTP_HEAD[] PROGMEM =
  36. "<!DOCTYPE html><html lang=\"" D_HTML_LANGUAGE "\" class=\"\">"
  37. "<head>"
  38. "<meta charset='utf-8'>"
  39. "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,user-scalable=no\"/>"
  40. "<title>{h} - {v}</title>"
  41. "<script>"
  42. "var x=null,lt,to,tp,pc='';" // x=null allow for abortion
  43. "function eb(s){"
  44. "return document.getElementById(s);" // Save code space
  45. "}";
  46. const char HTTP_SCRIPT_COUNTER[] PROGMEM =
  47. "var cn=180;" // seconds
  48. "function u(){"
  49. "if(cn>=0){"
  50. "eb('t').innerHTML='" D_RESTART_IN " '+cn+' " D_SECONDS "';"
  51. "cn--;"
  52. "setTimeout(u,1000);"
  53. "}"
  54. "}"
  55. "</script>";
  56. const char HTTP_SCRIPT_ROOT[] PROGMEM =
  57. "function la(p){"
  58. "var a='';"
  59. "if(la.arguments.length==1){"
  60. "a=p;"
  61. "clearTimeout(lt);"
  62. "}"
  63. "if(x!=null){x.abort();}" // Abort if no response within 2 seconds (happens on restart 1)
  64. "x=new XMLHttpRequest();"
  65. "x.onreadystatechange=function(){"
  66. "if(x.readyState==4&&x.status==200){"
  67. "var s=x.responseText.replace(/{t}/g,\"<table style='width:100%'>\").replace(/{s}/g,\"<tr><th>\").replace(/{m}/g,\"</th><td>\").replace(/{e}/g,\"</td></tr>\").replace(/{c}/g,\"%'><div style='text-align:center;font-weight:\");"
  68. "eb('l1').innerHTML=s;"
  69. "}"
  70. "};"
  71. "x.open('GET','ay'+a,true);"
  72. "x.send();"
  73. "lt=setTimeout(la,{a});" // Settings.web_refresh
  74. "}"
  75. "function lb(p){"
  76. "la('?d='+p);" // ?d related to WebGetArg("d", tmp, sizeof(tmp));
  77. "}"
  78. "function lc(p){"
  79. "la('?t='+p);" // ?t related to WebGetArg("t", tmp, sizeof(tmp));
  80. "}";
  81. const char HTTP_SCRIPT_WIFI[] PROGMEM =
  82. "function c(l){"
  83. "eb('s1').value=l.innerText||l.textContent;"
  84. "eb('p1').focus();"
  85. "}";
  86. const char HTTP_SCRIPT_RELOAD[] PROGMEM =
  87. "setTimeout(function(){location.href='.';}," STR(HTTP_RESTART_RECONNECT_TIME) ");"
  88. "</script>";
  89. // Local OTA upgrade requires more time to complete cp: before web ui should be reloaded
  90. const char HTTP_SCRIPT_RELOAD_OTA[] PROGMEM =
  91. "setTimeout(function(){location.href='.';}," STR(HTTP_OTA_RESTART_RECONNECT_TIME) ");"
  92. "</script>";
  93. const char HTTP_SCRIPT_CONSOL[] PROGMEM =
  94. "var sn=0;" // Scroll position
  95. "var id=0;" // Get most of weblog initially
  96. "function l(p){" // Console log and command service
  97. "var c,o,t;"
  98. "clearTimeout(lt);"
  99. "o='';"
  100. "t=eb('t1');"
  101. "if(p==1){"
  102. "c=eb('c1');"
  103. "o='&c1='+encodeURIComponent(c.value);"
  104. "c.value='';"
  105. "t.scrollTop=sn;"
  106. "}"
  107. "if(t.scrollTop>=sn){" // User scrolled back so no updates
  108. "if(x!=null){x.abort();}" // Abort if no response within 2 seconds (happens on restart 1)
  109. "x=new XMLHttpRequest();"
  110. "x.onreadystatechange=function(){"
  111. "if(x.readyState==4&&x.status==200){"
  112. "var z,d;"
  113. "d=x.responseXML;"
  114. "id=d.getElementsByTagName('i')[0].childNodes[0].nodeValue;"
  115. "if(d.getElementsByTagName('j')[0].childNodes[0].nodeValue==0){t.value='';}"
  116. "z=d.getElementsByTagName('l')[0].childNodes;"
  117. "if(z.length>0){t.value+=decodeURIComponent(z[0].nodeValue);}"
  118. "t.scrollTop=99999;"
  119. "sn=t.scrollTop;"
  120. "}"
  121. "};"
  122. "x.open('GET','ax?c2='+id+o,true);"
  123. "x.send();"
  124. "}"
  125. "lt=setTimeout(l,{a});"
  126. "return false;"
  127. "}"
  128. "</script>";
  129. const char HTTP_SCRIPT_MODULE1[] PROGMEM =
  130. "var os;"
  131. "function sk(s,g){" // s = value, g = id and name
  132. "var o=os.replace(\"value='\"+s+\"'\",\"selected value='\"+s+\"'\");"
  133. "eb('g'+g).innerHTML=o;"
  134. "}"
  135. "function sl(){"
  136. "if(x!=null){x.abort();}" // Abort any request pending
  137. "x=new XMLHttpRequest();"
  138. "x.onreadystatechange=function(){"
  139. "if(x.readyState==4&&x.status==200){"
  140. "var i,o=x.responseText.replace(/}1/g,\"<option value=\").replace(/}2/g,\"</option>\");"
  141. "i=o.indexOf(\"}3\");" // String separator means do not use "}3" in Module name and Sensor name
  142. "os=o.substring(0,i);"
  143. "sk(}4,99);"
  144. "os=o.substring(i+2);"; // +2 is length "}3"
  145. const char HTTP_SCRIPT_MODULE2[] PROGMEM =
  146. "}"
  147. "};"
  148. "x.open('GET','md?m=1',true);" // ?m related to WebServer->hasArg("m")
  149. "x.send();"
  150. "}";
  151. const char HTTP_SCRIPT_MODULE3[] PROGMEM =
  152. "}1'%d'>%s (%02d)}2"; // "}1" and "}2" means do not use "}x" in Module name and Sensor name
  153. const char HTTP_SCRIPT_INFO_BEGIN[] PROGMEM =
  154. "function i(){"
  155. "var s,o=\"";
  156. const char HTTP_SCRIPT_INFO_END[] PROGMEM =
  157. "\";" // "}1" and "}2" means do not use "}x" in Information text
  158. "s=o.replace(/}1/g,\"</td></tr><tr><th>\").replace(/}2/g,\"</th><td>\");"
  159. "eb('i').innerHTML=s;"
  160. "}"
  161. "</script>";
  162. const char HTTP_HEAD_STYLE[] PROGMEM =
  163. "</script>"
  164. "<style>"
  165. "div,fieldset,input,select{padding:5px;font-size:1em;}"
  166. "input{width:100%;box-sizing:border-box;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;}"
  167. "select{width:100%;}"
  168. "textarea{resize:none;width:98%;height:318px;padding:5px;overflow:auto;}"
  169. "body{text-align:center;font-family:verdana;}"
  170. "td{padding:0px;}"
  171. "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;cursor:pointer;}"
  172. "button:hover{background-color:#0e70a4;}"
  173. ".bred{background-color:#d43535;}"
  174. ".bred:hover{background-color:#931f1f;}"
  175. ".bgrn{background-color:#47c266;}"
  176. ".bgrn:hover{background-color:#5aaf6f;}"
  177. "a{text-decoration:none;}"
  178. ".p{float:left;text-align:left;}"
  179. ".q{float:right;text-align:right;}"
  180. "</style>"
  181. "</head>"
  182. "<body>"
  183. "<div style='text-align:left;display:inline-block;min-width:340px;'>"
  184. #ifdef BE_MINIMAL
  185. "<div style='text-align:center;color:red;'><h3>" D_MINIMAL_FIRMWARE_PLEASE_UPGRADE "</h3></div>"
  186. #endif
  187. "<div style='text-align:center;'><noscript>" D_NOSCRIPT "<br/></noscript>"
  188. #ifdef LANGUAGE_MODULE_NAME
  189. "<h3>" D_MODULE " {ha</h3>"
  190. #else
  191. "<h3>{ha " D_MODULE "</h3>"
  192. #endif
  193. "<h2>{h}</h2>{j}</div>";
  194. const char HTTP_MSG_SLIDER1[] PROGMEM =
  195. "<div><span class='p'>" D_COLDLIGHT "</span><span class='q'>" D_WARMLIGHT "</span></div>"
  196. "<div><input type='range' min='153' max='500' value='%d' onchange='lc(value)'></div>";
  197. const char HTTP_MSG_SLIDER2[] PROGMEM =
  198. "<div><span class='p'>" D_DARKLIGHT "</span><span class='q'>" D_BRIGHTLIGHT "</span></div>"
  199. "<div><input type='range' min='1' max='100' value='%d' onchange='lb(value)'></div>";
  200. const char HTTP_MSG_RSTRT[] PROGMEM =
  201. "<br/><div style='text-align:center;'>" D_DEVICE_WILL_RESTART "</div><br/>";
  202. const char HTTP_BTN_MENU1[] PROGMEM =
  203. #ifndef BE_MINIMAL
  204. "<br/><form action='cn' method='get'><button>" D_CONFIGURATION "</button></form>"
  205. "<br/><form action='in' method='get'><button>" D_INFORMATION "</button></form>"
  206. #endif
  207. "<br/><form action='up' method='get'><button>" D_FIRMWARE_UPGRADE "</button></form>"
  208. "<br/><form action='cs' method='get'><button>" D_CONSOLE "</button></form>";
  209. const char HTTP_BTN_RSTRT[] PROGMEM =
  210. "<br/><form action='.' method='get' onsubmit='return confirm(\"" D_CONFIRM_RESTART "\");'><button name='rstrt' class='button bred'>" D_RESTART "</button></form>";
  211. const char HTTP_BTN_MENU_MODULE[] PROGMEM =
  212. "<br/><form action='md' method='get'><button>" D_CONFIGURE_MODULE "</button></form>"
  213. "<br/><form action='wi' method='get'><button>" D_CONFIGURE_WIFI "</button></form>";
  214. const char HTTP_BTN_MENU4[] PROGMEM =
  215. "<br/><form action='lg' method='get'><button>" D_CONFIGURE_LOGGING "</button></form>"
  216. "<br/><form action='co' method='get'><button>" D_CONFIGURE_OTHER "</button></form>"
  217. "<br/>"
  218. "<br/><form action='rt' method='get' onsubmit='return confirm(\"" D_CONFIRM_RESET_CONFIGURATION "\");'><button class='button bred'>" D_RESET_CONFIGURATION "</button></form>"
  219. "<br/><form action='dl' method='get'><button>" D_BACKUP_CONFIGURATION "</button></form>"
  220. "<br/><form action='rs' method='get'><button>" D_RESTORE_CONFIGURATION "</button></form>";
  221. const char HTTP_BTN_MAIN[] PROGMEM =
  222. "<br/><br/><form action='.' method='get'><button>" D_MAIN_MENU "</button></form>";
  223. const char HTTP_FORM_LOGIN[] PROGMEM =
  224. "<form method='post' action='/'>"
  225. "<br/><b>" D_USER "</b><br/><input name='USER1' placeholder='" D_USER "'><br/>"
  226. "<br/><b>" D_PASSWORD "</b><br/><input name='PASS1' type='password' placeholder='" D_PASSWORD "'><br/>"
  227. "<br/>"
  228. "<br/><button>" D_OK "</button></form>";
  229. const char HTTP_BTN_CONF[] PROGMEM =
  230. "<br/><br/><form action='cn' method='get'><button>" D_CONFIGURATION "</button></form>";
  231. const char HTTP_FORM_MODULE[] PROGMEM =
  232. "<fieldset><legend><b>&nbsp;" D_MODULE_PARAMETERS "&nbsp;</b></legend><form method='get' action='md'>"
  233. "<br/><b>" D_MODULE_TYPE "</b> ({mt)<br/><select id='g99' name='g99'></select><br/>";
  234. const char HTTP_LNK_ITEM[] PROGMEM =
  235. "<div><a href='#p' onclick='c(this)'>{v}</a>&nbsp;({w})&nbsp<span class='q'>{i} {r}%</span></div>";
  236. const char HTTP_LNK_SCAN[] PROGMEM =
  237. "<div><a href='/wi?scan='>" D_SCAN_FOR_WIFI_NETWORKS "</a></div><br/>";
  238. const char HTTP_FORM_WIFI[] PROGMEM =
  239. "<fieldset><legend><b>&nbsp;" D_WIFI_PARAMETERS "&nbsp;</b></legend><form method='get' action='wi'>"
  240. "<br/><b>" D_AP1_SSID "</b> (" STA_SSID1 ")<br/><input id='s1' name='s1' placeholder='" STA_SSID1 "' value='{s1'><br/>"
  241. "<br/><b>" D_AP1_PASSWORD "</b><br/><input id='p1' name='p1' type='password' placeholder='" D_AP1_PASSWORD "' value='" D_ASTERIX "'><br/>"
  242. "<br/><b>" D_AP2_SSID "</b> (" STA_SSID2 ")<br/><input id='s2' name='s2' placeholder='" STA_SSID2 "' value='{s2'><br/>"
  243. "<br/><b>" D_AP2_PASSWORD "</b><br/><input id='p2' name='p2' type='password' placeholder='" D_AP2_PASSWORD "' value='" D_ASTERIX "'><br/>"
  244. "<br/><b>" D_HOSTNAME "</b> (" WIFI_HOSTNAME ")<br/><input id='h' name='h' placeholder='" WIFI_HOSTNAME" ' value='{h1'><br/>";
  245. const char HTTP_FORM_LOG1[] PROGMEM =
  246. "<fieldset><legend><b>&nbsp;" D_LOGGING_PARAMETERS "&nbsp;</b></legend><form method='get' action='lg'>";
  247. const char HTTP_FORM_LOG2[] PROGMEM =
  248. "<br/><b>{b0</b> ({b1)<br/><select id='{b2' name='{b2'>"
  249. "<option{a0value='0'>0 " D_NONE "</option>"
  250. "<option{a1value='1'>1 " D_ERROR "</option>"
  251. "<option{a2value='2'>2 " D_INFO "</option>"
  252. "<option{a3value='3'>3 " D_DEBUG "</option>"
  253. "<option{a4value='4'>4 " D_MORE_DEBUG "</option>"
  254. "</select><br/>";
  255. const char HTTP_FORM_LOG3[] PROGMEM =
  256. "<br/><b>" D_SYSLOG_HOST "</b> (" SYS_LOG_HOST ")<br/><input id='lh' name='lh' placeholder='" SYS_LOG_HOST "' value='{l2'><br/>"
  257. "<br/><b>" D_SYSLOG_PORT "</b> (" STR(SYS_LOG_PORT) ")<br/><input id='lp' name='lp' placeholder='" STR(SYS_LOG_PORT) "' value='{l3'><br/>"
  258. "<br/><b>" D_TELEMETRY_PERIOD "</b> (" STR(TELE_PERIOD) ")<br/><input id='lt' name='lt' placeholder='" STR(TELE_PERIOD) "' value='{l4'><br/>";
  259. const char HTTP_FORM_OTHER[] PROGMEM =
  260. "<fieldset><legend><b>&nbsp;" D_OTHER_PARAMETERS "&nbsp;</b></legend><form method='get' action='co'>"
  261. // "<input id='w' name='w' value='5,1' hidden>"
  262. "<br/><b>" D_WEB_ADMIN_PASSWORD "</b><br/><input id='p1' name='p1' type='password' placeholder='" D_WEB_ADMIN_PASSWORD "' value='" D_ASTERIX "'><br/>"
  263. "<br/><input style='width:10%;' id='b1' name='b1' type='checkbox'{r1><b>" D_MQTT_ENABLE "</b><br/>";
  264. const char HTTP_FORM_OTHER2[] PROGMEM =
  265. "<br/><b>" D_FRIENDLY_NAME " {1</b> ({2)<br/><input id='a{1' name='a{1' placeholder='{2' value='{3'><br/>";
  266. #ifdef USE_EMULATION
  267. const char HTTP_FORM_OTHER3a[] PROGMEM =
  268. "<br/><fieldset><legend><b>&nbsp;" D_EMULATION "&nbsp;</b></legend>";
  269. const char HTTP_FORM_OTHER3b[] PROGMEM =
  270. "<br/><input style='width:10%;' id='r{1' name='b2' type='radio' value='{1'{2><b>{3</b>{4"; // Different id only used for labels
  271. #endif // USE_EMULATION
  272. const char HTTP_FORM_END[] PROGMEM =
  273. "<br/><button name='save' type='submit' class='button bgrn'>" D_SAVE "</button></form></fieldset>";
  274. const char HTTP_FORM_RST[] PROGMEM =
  275. "<div id='f1' name='f1' style='display:block;'>"
  276. "<fieldset><legend><b>&nbsp;" D_RESTORE_CONFIGURATION "&nbsp;</b></legend>";
  277. const char HTTP_FORM_UPG[] PROGMEM =
  278. "<div id='f1' name='f1' style='display:block;'>"
  279. "<fieldset><legend><b>&nbsp;" D_UPGRADE_BY_WEBSERVER "&nbsp;</b></legend>"
  280. "<form method='get' action='u1'>"
  281. "<br/>" D_OTA_URL "<br/><input id='o' name='o' placeholder='OTA_URL' value='{o1'><br/>"
  282. "<br/><button type='submit'>" D_START_UPGRADE "</button></form>"
  283. "</fieldset><br/><br/>"
  284. "<fieldset><legend><b>&nbsp;" D_UPGRADE_BY_FILE_UPLOAD "&nbsp;</b></legend>";
  285. const char HTTP_FORM_RST_UPG[] PROGMEM =
  286. "<form method='post' action='u2' enctype='multipart/form-data'>"
  287. "<br/><input type='file' name='u2'><br/>"
  288. "<br/><button type='submit' onclick='eb(\"f1\").style.display=\"none\";eb(\"f2\").style.display=\"block\";this.form.submit();'>" D_START " {r1</button></form>"
  289. "</fieldset>"
  290. "</div>"
  291. "<div id='f2' name='f2' style='display:none;text-align:center;'><b>" D_UPLOAD_STARTED " ...</b></div>";
  292. const char HTTP_FORM_CMND[] PROGMEM =
  293. "<br/><textarea readonly id='t1' name='t1' cols='340' wrap='off'></textarea><br/><br/>"
  294. "<form method='get' onsubmit='return l(1);'>"
  295. "<input id='c1' name='c1' placeholder='" D_ENTER_COMMAND "' autofocus><br/>"
  296. // "<br/><button type='submit'>Send command</button>"
  297. "</form>";
  298. const char HTTP_TABLE100[] PROGMEM =
  299. "<table style='width:100%'>";
  300. const char HTTP_COUNTER[] PROGMEM =
  301. "<br/><div id='t' name='t' style='text-align:center;'></div>";
  302. const char HTTP_END[] PROGMEM =
  303. "<br/>"
  304. "<div style='text-align:right;font-size:11px;'><hr/><a href='" D_WEBLINK "' target='_blank' style='color:#aaa;'>" D_PROGRAMNAME " {mv " D_BY " " D_AUTHOR "</a></div>"
  305. "</div>"
  306. "</body>"
  307. "</html>";
  308. const char HTTP_DEVICE_CONTROL[] PROGMEM = "<td style='width:%d%%'><button onclick='la(\"?o=%d\");'>%s%s</button></td>"; // ?o is related to WebGetArg("o", tmp, sizeof(tmp));
  309. const char HTTP_DEVICE_STATE[] PROGMEM = "%s<td style='width:%d{c}%s;font-size:%dpx'>%s</div></td>"; // {c} = %'><div style='text-align:center;font-weight:
  310. const char HDR_CTYPE_PLAIN[] PROGMEM = "text/plain";
  311. const char HDR_CTYPE_HTML[] PROGMEM = "text/html";
  312. const char HDR_CTYPE_XML[] PROGMEM = "text/xml";
  313. const char HDR_CTYPE_JSON[] PROGMEM = "application/json";
  314. const char HDR_CTYPE_STREAM[] PROGMEM = "application/octet-stream";
  315. #define DNS_PORT 53
  316. enum HttpOptions {HTTP_OFF, HTTP_USER, HTTP_ADMIN, HTTP_MANAGER};
  317. DNSServer *DnsServer;
  318. ESP8266WebServer *WebServer;
  319. boolean remove_duplicate_access_points = true;
  320. int minimum_signal_quality = -1;
  321. uint8_t webserver_state = HTTP_OFF;
  322. uint8_t upload_error = 0;
  323. uint8_t upload_file_type;
  324. uint8_t upload_progress_dot_count;
  325. uint8_t config_block_count = 0;
  326. uint8_t config_xor_on = 0;
  327. uint8_t config_xor_on_set = CONFIG_FILE_XOR;
  328. // Helper function to avoid code duplication (saves 4k Flash)
  329. static void WebGetArg(const char* arg, char* out, size_t max)
  330. {
  331. String s = WebServer->arg(arg);
  332. strlcpy(out, s.c_str(), max);
  333. // out[max-1] = '\0'; // Ensure terminating NUL
  334. }
  335. void ShowWebSource(int source)
  336. {
  337. if ((source > 0) && (source < SRC_MAX)) {
  338. char stemp1[20];
  339. snprintf_P(log_data, sizeof(log_data), PSTR("SRC: %s from %s"), GetTextIndexed(stemp1, sizeof(stemp1), source, kCommandSource), WebServer->client().remoteIP().toString().c_str());
  340. AddLog(LOG_LEVEL_DEBUG);
  341. }
  342. }
  343. void ExecuteWebCommand(char* svalue, int source)
  344. {
  345. ShowWebSource(source);
  346. ExecuteCommand(svalue, SRC_IGNORE);
  347. }
  348. void StartWebserver(int type, IPAddress ipweb)
  349. {
  350. if (!Settings.web_refresh) { Settings.web_refresh = HTTP_REFRESH_TIME; }
  351. if (!webserver_state) {
  352. if (!WebServer) {
  353. WebServer = new ESP8266WebServer((HTTP_MANAGER==type) ? 80 : WEB_PORT);
  354. WebServer->on("/", HandleRoot);
  355. WebServer->on("/up", HandleUpgradeFirmware);
  356. WebServer->on("/u1", HandleUpgradeFirmwareStart); // OTA
  357. WebServer->on("/u2", HTTP_POST, HandleUploadDone, HandleUploadLoop);
  358. WebServer->on("/u2", HTTP_OPTIONS, HandlePreflightRequest);
  359. WebServer->on("/cs", HandleConsole);
  360. WebServer->on("/ax", HandleAjaxConsoleRefresh);
  361. WebServer->on("/ay", HandleAjaxStatusRefresh);
  362. WebServer->on("/cm", HandleHttpCommand);
  363. WebServer->onNotFound(HandleNotFound);
  364. #ifndef BE_MINIMAL
  365. WebServer->on("/cn", HandleConfiguration);
  366. WebServer->on("/md", HandleModuleConfiguration);
  367. WebServer->on("/wi", HandleWifiConfiguration);
  368. WebServer->on("/lg", HandleLoggingConfiguration);
  369. WebServer->on("/co", HandleOtherConfiguration);
  370. WebServer->on("/dl", HandleBackupConfiguration);
  371. WebServer->on("/rs", HandleRestoreConfiguration);
  372. WebServer->on("/rt", HandleResetConfiguration);
  373. WebServer->on("/in", HandleInformation);
  374. #ifdef USE_EMULATION
  375. HueWemoAddHandlers();
  376. #endif // USE_EMULATION
  377. XdrvCall(FUNC_WEB_ADD_HANDLER);
  378. XsnsCall(FUNC_WEB_ADD_HANDLER);
  379. #endif // Not BE_MINIMAL
  380. }
  381. reset_web_log_flag = 0;
  382. WebServer->begin(); // Web server start
  383. }
  384. if (webserver_state != type) {
  385. snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_HTTP D_WEBSERVER_ACTIVE_ON " %s%s " D_WITH_IP_ADDRESS " %s"),
  386. my_hostname, (mdns_begun) ? ".local" : "", ipweb.toString().c_str());
  387. AddLog(LOG_LEVEL_INFO);
  388. }
  389. if (type) { webserver_state = type; }
  390. }
  391. void StopWebserver(void)
  392. {
  393. if (webserver_state) {
  394. WebServer->close();
  395. webserver_state = HTTP_OFF;
  396. AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_STOPPED));
  397. }
  398. }
  399. void WifiManagerBegin(void)
  400. {
  401. // setup AP
  402. if (!global_state.wifi_down) {
  403. WiFi.mode(WIFI_AP_STA);
  404. AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI D_WIFIMANAGER_SET_ACCESSPOINT_AND_STATION));
  405. } else {
  406. WiFi.mode(WIFI_AP);
  407. AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI D_WIFIMANAGER_SET_ACCESSPOINT));
  408. }
  409. StopWebserver();
  410. DnsServer = new DNSServer();
  411. int channel = WIFI_SOFT_AP_CHANNEL;
  412. if ((channel < 1) || (channel > 13)) { channel = 1; }
  413. WiFi.softAP(my_hostname, NULL, channel);
  414. delay(500); // Without delay I've seen the IP address blank
  415. /* Setup the DNS server redirecting all the domains to the apIP */
  416. DnsServer->setErrorReplyCode(DNSReplyCode::NoError);
  417. DnsServer->start(DNS_PORT, "*", WiFi.softAPIP());
  418. StartWebserver(HTTP_MANAGER, WiFi.softAPIP());
  419. }
  420. void PollDnsWebserver(void)
  421. {
  422. if (DnsServer) { DnsServer->processNextRequest(); }
  423. if (WebServer) { WebServer->handleClient(); }
  424. }
  425. /*********************************************************************************************/
  426. void SetHeader(void)
  427. {
  428. WebServer->sendHeader(F("Cache-Control"), F("no-cache, no-store, must-revalidate"));
  429. WebServer->sendHeader(F("Pragma"), F("no-cache"));
  430. WebServer->sendHeader(F("Expires"), F("-1"));
  431. #ifndef ARDUINO_ESP8266_RELEASE_2_3_0
  432. WebServer->sendHeader(F("Access-Control-Allow-Origin"), F("*"));
  433. #endif
  434. }
  435. bool WebAuthenticate(void)
  436. {
  437. if (Settings.web_password[0] != 0) {
  438. return WebServer->authenticate(WEB_USERNAME, Settings.web_password);
  439. } else {
  440. return true;
  441. }
  442. }
  443. void ShowPage(String &page, bool auth)
  444. {
  445. if (auth && (Settings.web_password[0] != 0) && !WebServer->authenticate(WEB_USERNAME, Settings.web_password)) {
  446. return WebServer->requestAuthentication();
  447. }
  448. page.replace(F("{a}"), String(Settings.web_refresh));
  449. page.replace(F("{ha"), my_module.name);
  450. page.replace(F("{h}"), Settings.friendlyname[0]);
  451. String info = "";
  452. if (Settings.flag3.gui_hostname_ip) {
  453. uint8_t more_ips = 0;
  454. info += F("<h3>"); info += my_hostname;
  455. if (mdns_begun) { info += F(".local"); }
  456. info += F(" (");
  457. if (static_cast<uint32_t>(WiFi.localIP()) != 0) {
  458. info += WiFi.localIP().toString();
  459. more_ips++;
  460. }
  461. if (static_cast<uint32_t>(WiFi.softAPIP()) != 0) {
  462. if (more_ips) { info += F(", "); }
  463. info += WiFi.softAPIP().toString();
  464. }
  465. info += F(")</h3>");
  466. }
  467. page.replace(F("{j}"), info);
  468. if (HTTP_MANAGER == webserver_state) {
  469. if (WifiConfigCounter()) {
  470. page.replace(F("</script>"), FPSTR(HTTP_SCRIPT_COUNTER));
  471. page.replace(F("<body>"), F("<body onload='u()'>"));
  472. page += FPSTR(HTTP_COUNTER);
  473. }
  474. }
  475. page += FPSTR(HTTP_END);
  476. page.replace(F("{mv"), my_version);
  477. SetHeader();
  478. ShowFreeMem(PSTR("ShowPage"));
  479. WebServer->send(200, FPSTR(HDR_CTYPE_HTML), page);
  480. }
  481. void ShowPage(String &page)
  482. {
  483. ShowPage(page, true);
  484. }
  485. /*-------------------------------------------------------------------------------------------*/
  486. void WebRestart(uint8_t type)
  487. {
  488. // type 0 = restart
  489. // type 1 = restart after config change
  490. // type 2 = restart after config change with possible ip address change too
  491. AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_RESTART);
  492. String page = FPSTR(HTTP_HEAD);
  493. page += FPSTR(HTTP_HEAD_STYLE);
  494. if (type) {
  495. page.replace(F("{v}"), FPSTR(S_SAVE_CONFIGURATION));
  496. page += F("<div style='text-align:center;'><b>" D_CONFIGURATION_SAVED "</b><br/>");
  497. if (2 == type) {
  498. page += F("<br/>" D_TRYING_TO_CONNECT "<br/>");
  499. }
  500. page += F("</div>");
  501. }
  502. else {
  503. page.replace(F("{v}"), FPSTR(S_RESTART));
  504. }
  505. page += FPSTR(HTTP_MSG_RSTRT);
  506. if (HTTP_MANAGER == webserver_state) {
  507. webserver_state = HTTP_ADMIN;
  508. } else {
  509. page += FPSTR(HTTP_BTN_MAIN);
  510. }
  511. page.replace(F("</script>"), FPSTR(HTTP_SCRIPT_RELOAD));
  512. ShowPage(page);
  513. ShowWebSource(SRC_WEBGUI);
  514. restart_flag = 2;
  515. }
  516. /*********************************************************************************************/
  517. void HandleWifiLogin(void)
  518. {
  519. String page = FPSTR(HTTP_HEAD);
  520. page.replace(F("{v}"), FPSTR( D_CONFIGURE_WIFI ));
  521. page += FPSTR(HTTP_HEAD_STYLE);
  522. page += FPSTR(HTTP_FORM_LOGIN);
  523. ShowPage(page, false); // false means show page no matter if the client has or has not credentials
  524. }
  525. void HandleRoot(void)
  526. {
  527. AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_MAIN_MENU);
  528. if (CaptivePortal()) { return; } // If captive portal redirect instead of displaying the page.
  529. if ( WebServer->hasArg("rstrt") ) {
  530. WebRestart(0);
  531. return;
  532. }
  533. if (HTTP_MANAGER == webserver_state) {
  534. #ifndef BE_MINIMAL
  535. if ((Settings.web_password[0] != 0) && !(WebServer->hasArg("USER1")) && !(WebServer->hasArg("PASS1"))) {
  536. HandleWifiLogin();
  537. } else {
  538. /*
  539. char tmp1[100];
  540. WebGetArg("USER1", tmp1, sizeof(tmp1));
  541. char tmp2[100];
  542. WebGetArg("PASS1", tmp2, sizeof(tmp2));
  543. if (!(Settings.web_password[0] != 0) || (!(!strcmp(tmp1, WEB_USERNAME) && !strcmp(tmp2, Settings.web_password)))) {
  544. */
  545. if (!(Settings.web_password[0] != 0) || ((WebServer->arg("USER1") == WEB_USERNAME ) && (WebServer->arg("PASS1") == Settings.web_password ))) {
  546. HandleWifiConfiguration();
  547. } else {
  548. // wrong user and pass
  549. HandleWifiLogin();
  550. }
  551. }
  552. #endif // Not BE_MINIMAL
  553. } else {
  554. char stemp[10];
  555. String page = FPSTR(HTTP_HEAD);
  556. page.replace(F("{v}"), FPSTR(S_MAIN_MENU));
  557. page += FPSTR(HTTP_SCRIPT_ROOT);
  558. page += FPSTR(HTTP_HEAD_STYLE);
  559. page.replace(F("<body>"), F("<body onload='la()'>"));
  560. page += F("<div id='l1' name='l1'></div>");
  561. if (devices_present) {
  562. if (light_type) {
  563. if ((LST_COLDWARM == (light_type &7)) || (LST_RGBWC == (light_type &7))) {
  564. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_MSG_SLIDER1, LightGetColorTemp());
  565. page += mqtt_data;
  566. }
  567. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_MSG_SLIDER2, Settings.light_dimmer);
  568. page += mqtt_data;
  569. }
  570. page += FPSTR(HTTP_TABLE100);
  571. page += F("<tr>");
  572. if (SONOFF_IFAN02 == Settings.module) {
  573. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_DEVICE_CONTROL, 36, 1, D_BUTTON_TOGGLE, "");
  574. page += mqtt_data;
  575. for (byte i = 0; i < MAX_FAN_SPEED; i++) {
  576. snprintf_P(stemp, sizeof(stemp), PSTR("%d"), i);
  577. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_DEVICE_CONTROL, 16, i +2, stemp, "");
  578. page += mqtt_data;
  579. }
  580. } else {
  581. for (byte idx = 1; idx <= devices_present; idx++) {
  582. snprintf_P(stemp, sizeof(stemp), PSTR(" %d"), idx);
  583. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_DEVICE_CONTROL,
  584. 100 / devices_present, idx, (devices_present < 5) ? D_BUTTON_TOGGLE : "", (devices_present > 1) ? stemp : "");
  585. page += mqtt_data;
  586. }
  587. }
  588. page += F("</tr></table>");
  589. }
  590. if (SONOFF_BRIDGE == Settings.module) {
  591. page += FPSTR(HTTP_TABLE100);
  592. page += F("<tr>");
  593. byte idx = 0;
  594. for (byte i = 0; i < 4; i++) {
  595. if (idx > 0) { page += F("</tr><tr>"); }
  596. for (byte j = 0; j < 4; j++) {
  597. idx++;
  598. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("<td style='width:25%'><button onclick='la(\"?k=%d\");'>%d</button></td>"), idx, idx); // ?k is related to WebGetArg("k", tmp, sizeof(tmp));
  599. page += mqtt_data;
  600. }
  601. }
  602. page += F("</tr></table>");
  603. }
  604. #ifndef BE_MINIMAL
  605. mqtt_data[0] = '\0';
  606. XdrvCall(FUNC_WEB_ADD_MAIN_BUTTON);
  607. XsnsCall(FUNC_WEB_ADD_MAIN_BUTTON);
  608. page += String(mqtt_data);
  609. #endif // Not BE_MINIMAL
  610. if (HTTP_ADMIN == webserver_state) {
  611. page += FPSTR(HTTP_BTN_MENU1);
  612. page += FPSTR(HTTP_BTN_RSTRT);
  613. }
  614. ShowPage(page);
  615. }
  616. }
  617. void HandleAjaxStatusRefresh(void)
  618. {
  619. if (!WebAuthenticate()) { return WebServer->requestAuthentication(); }
  620. char svalue[80];
  621. char tmp[100];
  622. WebGetArg("o", tmp, sizeof(tmp));
  623. if (strlen(tmp)) {
  624. ShowWebSource(SRC_WEBGUI);
  625. uint8_t device = atoi(tmp);
  626. if (SONOFF_IFAN02 == Settings.module) {
  627. if (device < 2) {
  628. ExecuteCommandPower(1, POWER_TOGGLE, SRC_IGNORE);
  629. } else {
  630. snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_FANSPEED " %d"), device -2);
  631. ExecuteCommand(svalue, SRC_WEBGUI);
  632. }
  633. } else {
  634. ExecuteCommandPower(device, POWER_TOGGLE, SRC_IGNORE);
  635. }
  636. }
  637. WebGetArg("d", tmp, sizeof(tmp));
  638. if (strlen(tmp)) {
  639. snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_DIMMER " %s"), tmp);
  640. ExecuteWebCommand(svalue, SRC_WEBGUI);
  641. }
  642. WebGetArg("t", tmp, sizeof(tmp));
  643. if (strlen(tmp)) {
  644. snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_COLORTEMPERATURE " %s"), tmp);
  645. ExecuteWebCommand(svalue, SRC_WEBGUI);
  646. }
  647. WebGetArg("k", tmp, sizeof(tmp));
  648. if (strlen(tmp)) {
  649. snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_RFKEY "%s"), tmp);
  650. ExecuteWebCommand(svalue, SRC_WEBGUI);
  651. }
  652. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{t}"));
  653. XsnsCall(FUNC_WEB_APPEND);
  654. if (D_DECIMAL_SEPARATOR[0] != '.') {
  655. for (uint16_t i = 0; i < strlen(mqtt_data); i++) {
  656. if ('.' == mqtt_data[i]) {
  657. mqtt_data[i] = D_DECIMAL_SEPARATOR[0];
  658. }
  659. }
  660. }
  661. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s</table>"), mqtt_data);
  662. if (devices_present) {
  663. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s{t}<tr>"), mqtt_data);
  664. uint8_t fsize = (devices_present < 5) ? 70 - (devices_present * 8) : 32;
  665. if (SONOFF_IFAN02 == Settings.module) {
  666. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_DEVICE_STATE,
  667. mqtt_data, 36, (bitRead(power, 0)) ? "bold" : "normal", 54, GetStateText(bitRead(power, 0)));
  668. uint8_t fanspeed = GetFanspeed();
  669. snprintf_P(svalue, sizeof(svalue), PSTR("%d"), fanspeed);
  670. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_DEVICE_STATE,
  671. mqtt_data, 64, (fanspeed) ? "bold" : "normal", 54, (fanspeed) ? svalue : GetStateText(0));
  672. } else {
  673. for (byte idx = 1; idx <= devices_present; idx++) {
  674. snprintf_P(svalue, sizeof(svalue), PSTR("%d"), bitRead(power, idx -1));
  675. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_DEVICE_STATE,
  676. mqtt_data, 100 / devices_present, (bitRead(power, idx -1)) ? "bold" : "normal", fsize, (devices_present < 5) ? GetStateText(bitRead(power, idx -1)) : svalue);
  677. }
  678. }
  679. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s</tr></table>"), mqtt_data);
  680. }
  681. WebServer->send(200, FPSTR(HDR_CTYPE_HTML), mqtt_data);
  682. }
  683. boolean HttpUser(void)
  684. {
  685. boolean status = (HTTP_USER == webserver_state);
  686. if (status) { HandleRoot(); }
  687. return status;
  688. }
  689. /*-------------------------------------------------------------------------------------------*/
  690. #ifndef BE_MINIMAL
  691. void HandleConfiguration(void)
  692. {
  693. if (HttpUser()) { return; }
  694. if (!WebAuthenticate()) { return WebServer->requestAuthentication(); }
  695. AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURATION);
  696. String page = FPSTR(HTTP_HEAD);
  697. page.replace(F("{v}"), FPSTR(S_CONFIGURATION));
  698. page += FPSTR(HTTP_HEAD_STYLE);
  699. page += FPSTR(HTTP_BTN_MENU_MODULE);
  700. mqtt_data[0] = '\0';
  701. XdrvCall(FUNC_WEB_ADD_BUTTON);
  702. XsnsCall(FUNC_WEB_ADD_BUTTON);
  703. page += String(mqtt_data);
  704. page += FPSTR(HTTP_BTN_MENU4);
  705. page += FPSTR(HTTP_BTN_MAIN);
  706. ShowPage(page);
  707. }
  708. /*-------------------------------------------------------------------------------------------*/
  709. void HandleModuleConfiguration(void)
  710. {
  711. if (HttpUser()) { return; }
  712. if (!WebAuthenticate()) { return WebServer->requestAuthentication(); }
  713. if (WebServer->hasArg("save")) {
  714. ModuleSaveSettings();
  715. WebRestart(1);
  716. return;
  717. }
  718. char stemp[20];
  719. uint8_t midx;
  720. mytmplt cmodule;
  721. memcpy_P(&cmodule, &kModules[Settings.module], sizeof(cmodule));
  722. if (WebServer->hasArg("m")) {
  723. String page = "";
  724. for (byte i = 0; i < MAXMODULE; i++) {
  725. midx = pgm_read_byte(kModuleNiceList + i);
  726. snprintf_P(stemp, sizeof(stemp), kModules[midx].name);
  727. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SCRIPT_MODULE3, midx, stemp, midx +1);
  728. page += mqtt_data;
  729. }
  730. page += "}3"; // String separator means do not use "}3" in Module name and Sensor name
  731. for (byte j = 0; j < sizeof(kGpioNiceList); j++) {
  732. midx = pgm_read_byte(kGpioNiceList + j);
  733. if (!GetUsedInModule(midx, cmodule.gp.io)) {
  734. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SCRIPT_MODULE3, midx, GetTextIndexed(stemp, sizeof(stemp), midx, kSensorNames), midx);
  735. page += mqtt_data;
  736. }
  737. }
  738. WebServer->send(200, FPSTR(HDR_CTYPE_PLAIN), page);
  739. return;
  740. }
  741. AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_MODULE);
  742. String page = FPSTR(HTTP_HEAD);
  743. page.replace(F("{v}"), FPSTR(S_CONFIGURE_MODULE));
  744. page += FPSTR(HTTP_SCRIPT_MODULE1);
  745. page.replace(F("}4"), String(Settings.module));
  746. for (byte i = 0; i < MAX_GPIO_PIN; i++) {
  747. if (GPIO_USER == ValidGPIO(i, cmodule.gp.io[i])) {
  748. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("sk(%d,%d);"), my_module.gp.io[i], i); // g0 - g16
  749. page += mqtt_data;
  750. }
  751. }
  752. page += FPSTR(HTTP_SCRIPT_MODULE2);
  753. page += FPSTR(HTTP_HEAD_STYLE);
  754. page.replace(F("<body>"), F("<body onload='sl()'>"));
  755. page += FPSTR(HTTP_FORM_MODULE);
  756. snprintf_P(stemp, sizeof(stemp), kModules[MODULE].name);
  757. page.replace(F("{mt"), stemp);
  758. page += F("<br/><table>");
  759. for (byte i = 0; i < MAX_GPIO_PIN; i++) {
  760. if (GPIO_USER == ValidGPIO(i, cmodule.gp.io[i])) {
  761. snprintf_P(stemp, 3, PINS_WEMOS +i*2);
  762. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("<tr><td style='width:190px'>%s <b>" D_GPIO "%d</b> %s</td><td style='width:160px'><select id='g%d' name='g%d'></select></td></tr>"),
  763. (WEMOS==Settings.module)?stemp:"", i, (0==i)? D_SENSOR_BUTTON "1":(1==i)? D_SERIAL_OUT :(3==i)? D_SERIAL_IN :(9==i)? "<font color='red'>ESP8285</font>" :(10==i)? "<font color='red'>ESP8285</font>" :(12==i)? D_SENSOR_RELAY "1":(13==i)? D_SENSOR_LED "1i":(14==i)? D_SENSOR :"", i, i);
  764. page += mqtt_data;
  765. }
  766. }
  767. page += F("</table>");
  768. page += FPSTR(HTTP_FORM_END);
  769. page += FPSTR(HTTP_BTN_CONF);
  770. ShowPage(page);
  771. }
  772. void ModuleSaveSettings(void)
  773. {
  774. char tmp[100];
  775. char stemp[TOPSZ];
  776. WebGetArg("g99", tmp, sizeof(tmp));
  777. byte new_module = (!strlen(tmp)) ? MODULE : atoi(tmp);
  778. Settings.last_module = Settings.module;
  779. Settings.module = new_module;
  780. mytmplt cmodule;
  781. memcpy_P(&cmodule, &kModules[Settings.module], sizeof(cmodule));
  782. String gpios = "";
  783. for (byte i = 0; i < MAX_GPIO_PIN; i++) {
  784. if (Settings.last_module != new_module) {
  785. Settings.my_gp.io[i] = 0;
  786. } else {
  787. if (GPIO_USER == ValidGPIO(i, cmodule.gp.io[i])) {
  788. snprintf_P(stemp, sizeof(stemp), PSTR("g%d"), i);
  789. WebGetArg(stemp, tmp, sizeof(tmp));
  790. Settings.my_gp.io[i] = (!strlen(tmp)) ? 0 : atoi(tmp);
  791. gpios += F(", " D_GPIO ); gpios += String(i); gpios += F(" "); gpios += String(Settings.my_gp.io[i]);
  792. }
  793. }
  794. }
  795. snprintf_P(stemp, sizeof(stemp), kModules[Settings.module].name);
  796. snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_MODULE "%s " D_CMND_MODULE "%s"), stemp, gpios.c_str());
  797. AddLog(LOG_LEVEL_INFO);
  798. }
  799. /*-------------------------------------------------------------------------------------------*/
  800. String htmlEscape(String s)
  801. {
  802. s.replace("&", "&amp;");
  803. s.replace("<", "&lt;");
  804. s.replace(">", "&gt;");
  805. s.replace("\"", "&quot;");
  806. s.replace("'", "&#x27;");
  807. s.replace("/", "&#x2F;");
  808. return s;
  809. }
  810. void HandleWifiConfiguration(void)
  811. {
  812. if (HttpUser()) { return; }
  813. if (!WebAuthenticate()) { return WebServer->requestAuthentication(); }
  814. AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_WIFI);
  815. if (WebServer->hasArg("save")) {
  816. WifiSaveSettings();
  817. WebRestart(2);
  818. return;
  819. }
  820. String page = FPSTR(HTTP_HEAD);
  821. page.replace(F("{v}"), FPSTR(S_CONFIGURE_WIFI));
  822. page += FPSTR(HTTP_SCRIPT_WIFI);
  823. page += FPSTR(HTTP_HEAD_STYLE);
  824. if (WebServer->hasArg("scan")) {
  825. #ifdef USE_EMULATION
  826. UdpDisconnect();
  827. #endif // USE_EMULATION
  828. int n = WiFi.scanNetworks();
  829. AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI D_SCAN_DONE));
  830. if (0 == n) {
  831. AddLog_P(LOG_LEVEL_DEBUG, S_LOG_WIFI, S_NO_NETWORKS_FOUND);
  832. page += FPSTR(S_NO_NETWORKS_FOUND);
  833. page += F(". " D_REFRESH_TO_SCAN_AGAIN ".");
  834. } else {
  835. //sort networks
  836. int indices[n];
  837. for (int i = 0; i < n; i++) {
  838. indices[i] = i;
  839. }
  840. // RSSI SORT
  841. for (int i = 0; i < n; i++) {
  842. for (int j = i + 1; j < n; j++) {
  843. if (WiFi.RSSI(indices[j]) > WiFi.RSSI(indices[i])) {
  844. std::swap(indices[i], indices[j]);
  845. }
  846. }
  847. }
  848. // remove duplicates ( must be RSSI sorted )
  849. if (remove_duplicate_access_points) {
  850. String cssid;
  851. for (int i = 0; i < n; i++) {
  852. if (-1 == indices[i]) { continue; }
  853. cssid = WiFi.SSID(indices[i]);
  854. for (int j = i + 1; j < n; j++) {
  855. if (cssid == WiFi.SSID(indices[j])) {
  856. snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_WIFI D_DUPLICATE_ACCESSPOINT " %s"), WiFi.SSID(indices[j]).c_str());
  857. AddLog(LOG_LEVEL_DEBUG);
  858. indices[j] = -1; // set dup aps to index -1
  859. }
  860. }
  861. }
  862. }
  863. //display networks in page
  864. for (int i = 0; i < n; i++) {
  865. if (-1 == indices[i]) { continue; } // skip dups
  866. 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]));
  867. AddLog(LOG_LEVEL_DEBUG);
  868. int quality = WifiGetRssiAsQuality(WiFi.RSSI(indices[i]));
  869. if (minimum_signal_quality == -1 || minimum_signal_quality < quality) {
  870. String item = FPSTR(HTTP_LNK_ITEM);
  871. String rssiQ;
  872. rssiQ += quality;
  873. item.replace(F("{v}"), htmlEscape(WiFi.SSID(indices[i])));
  874. item.replace(F("{w}"), String(WiFi.channel(indices[i])));
  875. item.replace(F("{r}"), rssiQ);
  876. uint8_t auth = WiFi.encryptionType(indices[i]);
  877. 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(""));
  878. page += item;
  879. delay(0);
  880. } else {
  881. AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI D_SKIPPING_LOW_QUALITY));
  882. }
  883. }
  884. page += "<br/>";
  885. }
  886. } else {
  887. page += FPSTR(HTTP_LNK_SCAN);
  888. }
  889. page += FPSTR(HTTP_FORM_WIFI);
  890. page.replace(F("{h1"), Settings.hostname);
  891. page.replace(F("{s1"), Settings.sta_ssid[0]);
  892. page.replace(F("{s2"), Settings.sta_ssid[1]);
  893. page += FPSTR(HTTP_FORM_END);
  894. if (HTTP_MANAGER == webserver_state) {
  895. page += FPSTR(HTTP_BTN_RSTRT);
  896. } else {
  897. page += FPSTR(HTTP_BTN_CONF);
  898. }
  899. // ShowPage(page);
  900. ShowPage(page, !(HTTP_MANAGER == webserver_state));
  901. }
  902. void WifiSaveSettings(void)
  903. {
  904. char tmp[100];
  905. WebGetArg("h", tmp, sizeof(tmp));
  906. strlcpy(Settings.hostname, (!strlen(tmp)) ? WIFI_HOSTNAME : tmp, sizeof(Settings.hostname));
  907. if (strstr(Settings.hostname,"%")) {
  908. strlcpy(Settings.hostname, WIFI_HOSTNAME, sizeof(Settings.hostname));
  909. }
  910. WebGetArg("s1", tmp, sizeof(tmp));
  911. strlcpy(Settings.sta_ssid[0], (!strlen(tmp)) ? STA_SSID1 : tmp, sizeof(Settings.sta_ssid[0]));
  912. WebGetArg("s2", tmp, sizeof(tmp));
  913. strlcpy(Settings.sta_ssid[1], (!strlen(tmp)) ? STA_SSID2 : tmp, sizeof(Settings.sta_ssid[1]));
  914. WebGetArg("p1", tmp, sizeof(tmp));
  915. strlcpy(Settings.sta_pwd[0], (!strlen(tmp)) ? "" : (strchr(tmp,'*')) ? Settings.sta_pwd[0] : tmp, sizeof(Settings.sta_pwd[0]));
  916. WebGetArg("p2", tmp, sizeof(tmp));
  917. strlcpy(Settings.sta_pwd[1], (!strlen(tmp)) ? "" : (strchr(tmp,'*')) ? Settings.sta_pwd[1] : tmp, sizeof(Settings.sta_pwd[1]));
  918. 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"),
  919. Settings.hostname, Settings.sta_ssid[0], Settings.sta_ssid[1]);
  920. AddLog(LOG_LEVEL_INFO);
  921. }
  922. /*-------------------------------------------------------------------------------------------*/
  923. void HandleLoggingConfiguration(void)
  924. {
  925. if (HttpUser()) { return; }
  926. if (!WebAuthenticate()) { return WebServer->requestAuthentication(); }
  927. AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_LOGGING);
  928. if (WebServer->hasArg("save")) {
  929. LoggingSaveSettings();
  930. HandleConfiguration();
  931. return;
  932. }
  933. String page = FPSTR(HTTP_HEAD);
  934. page.replace(F("{v}"), FPSTR(S_CONFIGURE_LOGGING));
  935. page += FPSTR(HTTP_HEAD_STYLE);
  936. page += FPSTR(HTTP_FORM_LOG1);
  937. for (byte idx = 0; idx < 3; idx++) {
  938. page += FPSTR(HTTP_FORM_LOG2);
  939. switch (idx) {
  940. case 0:
  941. page.replace(F("{b0"), F(D_SERIAL_LOG_LEVEL));
  942. page.replace(F("{b1"), STR(SERIAL_LOG_LEVEL));
  943. page.replace(F("{b2"), F("ls"));
  944. for (byte i = LOG_LEVEL_NONE; i < LOG_LEVEL_ALL; i++) {
  945. page.replace("{a" + String(i), (i == Settings.seriallog_level) ? F(" selected ") : F(" "));
  946. }
  947. break;
  948. case 1:
  949. page.replace(F("{b0"), F(D_WEB_LOG_LEVEL));
  950. page.replace(F("{b1"), STR(WEB_LOG_LEVEL));
  951. page.replace(F("{b2"), F("lw"));
  952. for (byte i = LOG_LEVEL_NONE; i < LOG_LEVEL_ALL; i++) {
  953. page.replace("{a" + String(i), (i == Settings.weblog_level) ? F(" selected ") : F(" "));
  954. }
  955. break;
  956. case 2:
  957. page.replace(F("{b0"), F(D_SYS_LOG_LEVEL));
  958. page.replace(F("{b1"), STR(SYS_LOG_LEVEL));
  959. page.replace(F("{b2"), F("ll"));
  960. for (byte i = LOG_LEVEL_NONE; i < LOG_LEVEL_ALL; i++) {
  961. page.replace("{a" + String(i), (i == Settings.syslog_level) ? F(" selected ") : F(" "));
  962. }
  963. break;
  964. }
  965. }
  966. page += FPSTR(HTTP_FORM_LOG3);
  967. page.replace(F("{l2"), Settings.syslog_host);
  968. page.replace(F("{l3"), String(Settings.syslog_port));
  969. page.replace(F("{l4"), String(Settings.tele_period));
  970. page += FPSTR(HTTP_FORM_END);
  971. page += FPSTR(HTTP_BTN_CONF);
  972. ShowPage(page);
  973. }
  974. void LoggingSaveSettings(void)
  975. {
  976. char tmp[100];
  977. WebGetArg("ls", tmp, sizeof(tmp));
  978. Settings.seriallog_level = (!strlen(tmp)) ? SERIAL_LOG_LEVEL : atoi(tmp);
  979. WebGetArg("lw", tmp, sizeof(tmp));
  980. Settings.weblog_level = (!strlen(tmp)) ? WEB_LOG_LEVEL : atoi(tmp);
  981. WebGetArg("ll", tmp, sizeof(tmp));
  982. Settings.syslog_level = (!strlen(tmp)) ? SYS_LOG_LEVEL : atoi(tmp);
  983. syslog_level = Settings.syslog_level;
  984. syslog_timer = 0;
  985. WebGetArg("lh", tmp, sizeof(tmp));
  986. strlcpy(Settings.syslog_host, (!strlen(tmp)) ? SYS_LOG_HOST : tmp, sizeof(Settings.syslog_host));
  987. WebGetArg("lp", tmp, sizeof(tmp));
  988. Settings.syslog_port = (!strlen(tmp)) ? SYS_LOG_PORT : atoi(tmp);
  989. WebGetArg("lt", tmp, sizeof(tmp));
  990. Settings.tele_period = (!strlen(tmp)) ? TELE_PERIOD : atoi(tmp);
  991. if ((Settings.tele_period > 0) && (Settings.tele_period < 10)) {
  992. Settings.tele_period = 10; // Do not allow periods < 10 seconds
  993. }
  994. 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"),
  995. Settings.seriallog_level, Settings.weblog_level, Settings.syslog_level, Settings.syslog_host, Settings.syslog_port, Settings.tele_period);
  996. AddLog(LOG_LEVEL_INFO);
  997. }
  998. /*-------------------------------------------------------------------------------------------*/
  999. void HandleOtherConfiguration(void)
  1000. {
  1001. if (HttpUser()) { return; }
  1002. if (!WebAuthenticate()) { return WebServer->requestAuthentication(); }
  1003. AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_OTHER);
  1004. if (WebServer->hasArg("save")) {
  1005. OtherSaveSettings();
  1006. WebRestart(1);
  1007. return;
  1008. }
  1009. char stemp[40];
  1010. String page = FPSTR(HTTP_HEAD);
  1011. page.replace(F("{v}"), FPSTR(S_CONFIGURE_OTHER));
  1012. page += FPSTR(HTTP_HEAD_STYLE);
  1013. page += FPSTR(HTTP_FORM_OTHER);
  1014. page.replace(F("{r1"), (Settings.flag.mqtt_enabled) ? F(" checked") : F(""));
  1015. uint8_t maxfn = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : (!devices_present) ? 1 : devices_present;
  1016. if (SONOFF_IFAN02 == Settings.module) { maxfn = 1; }
  1017. for (byte i = 0; i < maxfn; i++) {
  1018. page += FPSTR(HTTP_FORM_OTHER2);
  1019. page.replace(F("{1"), String(i +1));
  1020. snprintf_P(stemp, sizeof(stemp), PSTR(FRIENDLY_NAME"%d"), i +1);
  1021. page.replace(F("{2"), (i) ? stemp : FRIENDLY_NAME);
  1022. page.replace(F("{3"), Settings.friendlyname[i]);
  1023. }
  1024. #ifdef USE_EMULATION
  1025. page += FPSTR(HTTP_FORM_OTHER3a);
  1026. for (byte i = 0; i < EMUL_MAX; i++) {
  1027. page += FPSTR(HTTP_FORM_OTHER3b);
  1028. page.replace(F("{1"), String(i));
  1029. page.replace(F("{2"), (i == Settings.flag2.emulation) ? F(" checked") : F(""));
  1030. page.replace(F("{3"), (i == EMUL_NONE) ? F(D_NONE) : (i == EMUL_WEMO) ? F(D_BELKIN_WEMO) : F(D_HUE_BRIDGE));
  1031. page.replace(F("{4"), (i == EMUL_NONE) ? F("") : (i == EMUL_WEMO) ? F(" " D_SINGLE_DEVICE) : F(" " D_MULTI_DEVICE));
  1032. }
  1033. page += F("<br/>");
  1034. page += F("<br/></fieldset>");
  1035. #endif // USE_EMULATION
  1036. page += FPSTR(HTTP_FORM_END);
  1037. page += FPSTR(HTTP_BTN_CONF);
  1038. ShowPage(page);
  1039. }
  1040. void OtherSaveSettings(void)
  1041. {
  1042. char tmp[100];
  1043. char stemp[TOPSZ];
  1044. char stemp2[TOPSZ];
  1045. WebGetArg("p1", tmp, sizeof(tmp));
  1046. strlcpy(Settings.web_password, (!strlen(tmp)) ? "" : (strchr(tmp,'*')) ? Settings.web_password : tmp, sizeof(Settings.web_password));
  1047. Settings.flag.mqtt_enabled = WebServer->hasArg("b1");
  1048. #ifdef USE_EMULATION
  1049. WebGetArg("b2", tmp, sizeof(tmp));
  1050. Settings.flag2.emulation = (!strlen(tmp)) ? 0 : atoi(tmp);
  1051. #endif // USE_EMULATION
  1052. 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);
  1053. for (byte i = 0; i < MAX_FRIENDLYNAMES; i++) {
  1054. snprintf_P(stemp, sizeof(stemp), PSTR("a%d"), i +1);
  1055. WebGetArg(stemp, tmp, sizeof(tmp));
  1056. snprintf_P(stemp2, sizeof(stemp2), PSTR(FRIENDLY_NAME"%d"), i +1);
  1057. strlcpy(Settings.friendlyname[i], (!strlen(tmp)) ? (i) ? stemp2 : FRIENDLY_NAME : tmp, sizeof(Settings.friendlyname[i]));
  1058. snprintf_P(log_data, sizeof(log_data), PSTR("%s%s %s"), log_data, (i) ? "," : "", Settings.friendlyname[i]);
  1059. }
  1060. AddLog(LOG_LEVEL_INFO);
  1061. }
  1062. /*-------------------------------------------------------------------------------------------*/
  1063. void HandleBackupConfiguration(void)
  1064. {
  1065. if (HttpUser()) { return; }
  1066. if (!WebAuthenticate()) { return WebServer->requestAuthentication(); }
  1067. AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_BACKUP_CONFIGURATION));
  1068. if (!SettingsBufferAlloc()) { return; }
  1069. WiFiClient myClient = WebServer->client();
  1070. WebServer->setContentLength(sizeof(Settings));
  1071. char attachment[100];
  1072. char friendlyname[sizeof(Settings.friendlyname[0])];
  1073. snprintf_P(attachment, sizeof(attachment), PSTR("attachment; filename=Config_%s_%s.dmp"), NoAlNumToUnderscore(friendlyname, Settings.friendlyname[0]), my_version);
  1074. WebServer->sendHeader(F("Content-Disposition"), attachment);
  1075. WebServer->send(200, FPSTR(HDR_CTYPE_STREAM), "");
  1076. uint16_t cfg_crc = Settings.cfg_crc;
  1077. Settings.cfg_crc = GetSettingsCrc(); // Calculate crc (again) as it might be wrong when savedata = 0 (#3918)
  1078. memcpy(settings_buffer, &Settings, sizeof(Settings));
  1079. if (config_xor_on_set) {
  1080. for (uint16_t i = 2; i < sizeof(Settings); i++) {
  1081. settings_buffer[i] ^= (config_xor_on_set +i);
  1082. }
  1083. }
  1084. #ifdef ARDUINO_ESP8266_RELEASE_2_3_0
  1085. size_t written = myClient.write((const char*)settings_buffer, sizeof(Settings));
  1086. if (written < sizeof(Settings)) { // https://github.com/esp8266/Arduino/issues/3218
  1087. myClient.write((const char*)settings_buffer +written, sizeof(Settings) -written);
  1088. }
  1089. #else
  1090. myClient.write((const char*)settings_buffer, sizeof(Settings));
  1091. #endif
  1092. SettingsBufferFree();
  1093. Settings.cfg_crc = cfg_crc; // Restore crc in case savedata = 0 to make sure settings will be noted as changed
  1094. }
  1095. /*-------------------------------------------------------------------------------------------*/
  1096. void HandleResetConfiguration(void)
  1097. {
  1098. if (HttpUser()) { return; }
  1099. if (!WebAuthenticate()) { return WebServer->requestAuthentication(); }
  1100. char svalue[33];
  1101. AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_RESET_CONFIGURATION);
  1102. String page = FPSTR(HTTP_HEAD);
  1103. page.replace(F("{v}"), FPSTR(S_RESET_CONFIGURATION));
  1104. page += FPSTR(HTTP_HEAD_STYLE);
  1105. page += F("<div style='text-align:center;'>" D_CONFIGURATION_RESET "</div>");
  1106. page += FPSTR(HTTP_MSG_RSTRT);
  1107. page += FPSTR(HTTP_BTN_MAIN);
  1108. ShowPage(page);
  1109. snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_RESET " 1"));
  1110. ExecuteWebCommand(svalue, SRC_WEBGUI);
  1111. }
  1112. void HandleRestoreConfiguration(void)
  1113. {
  1114. if (HttpUser()) { return; }
  1115. if (!WebAuthenticate()) { return WebServer->requestAuthentication(); }
  1116. AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_RESTORE_CONFIGURATION);
  1117. String page = FPSTR(HTTP_HEAD);
  1118. page.replace(F("{v}"), FPSTR(S_RESTORE_CONFIGURATION));
  1119. page += FPSTR(HTTP_HEAD_STYLE);
  1120. page += FPSTR(HTTP_FORM_RST);
  1121. page += FPSTR(HTTP_FORM_RST_UPG);
  1122. page.replace(F("{r1"), F(D_RESTORE));
  1123. page += FPSTR(HTTP_BTN_CONF);
  1124. ShowPage(page);
  1125. upload_error = 0;
  1126. upload_file_type = UPL_SETTINGS;
  1127. }
  1128. /*-------------------------------------------------------------------------------------------*/
  1129. void HandleInformation(void)
  1130. {
  1131. if (HttpUser()) { return; }
  1132. if (!WebAuthenticate()) { return WebServer->requestAuthentication(); }
  1133. AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_INFORMATION);
  1134. char stopic[TOPSZ];
  1135. int freeMem = ESP.getFreeHeap();
  1136. String page = FPSTR(HTTP_HEAD);
  1137. page.replace(F("{v}"), FPSTR(S_INFORMATION));
  1138. page += FPSTR(HTTP_HEAD_STYLE);
  1139. // page += F("<fieldset><legend><b>&nbsp;Information&nbsp;</b></legend>");
  1140. page += F("<style>td{padding:0px 5px;}</style>");
  1141. page += F("<div id='i' name='i'></div>");
  1142. // Save 1k of code space replacing table html with javascript replace codes
  1143. // }1 = </td></tr><tr><th>
  1144. // }2 = </th><td>
  1145. String func = FPSTR(HTTP_SCRIPT_INFO_BEGIN);
  1146. func += F("<table style='width:100%'><tr><th>");
  1147. func += F(D_PROGRAM_VERSION "}2"); func += my_version; func += my_image;
  1148. func += F("}1" D_BUILD_DATE_AND_TIME "}2"); func += GetBuildDateAndTime();
  1149. func += F("}1" D_CORE_AND_SDK_VERSION "}2" ARDUINO_ESP8266_RELEASE "/"); func += String(ESP.getSdkVersion());
  1150. func += F("}1" D_UPTIME "}2"); func += GetUptime();
  1151. snprintf_P(stopic, sizeof(stopic), PSTR(" at 0x%X"), GetSettingsAddress());
  1152. func += F("}1" D_FLASH_WRITE_COUNT "}2"); func += String(Settings.save_flag); func += stopic;
  1153. func += F("}1" D_BOOT_COUNT "}2"); func += String(Settings.bootcount);
  1154. func += F("}1" D_RESTART_REASON "}2"); func += GetResetReason();
  1155. uint8_t maxfn = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : devices_present;
  1156. if (SONOFF_IFAN02 == Settings.module) { maxfn = 1; }
  1157. for (byte i = 0; i < maxfn; i++) {
  1158. func += F("}1" D_FRIENDLY_NAME " "); func += i +1; func += F("}2"); func += Settings.friendlyname[i];
  1159. }
  1160. func += F("}1}2&nbsp;"); // Empty line
  1161. func += F("}1" D_AP); func += String(Settings.sta_active +1);
  1162. func += F(" " D_SSID " (" D_RSSI ")}2"); func += Settings.sta_ssid[Settings.sta_active]; func += F(" ("); func += WifiGetRssiAsQuality(WiFi.RSSI()); func += F("%)");
  1163. func += F("}1" D_HOSTNAME "}2"); func += my_hostname;
  1164. if (mdns_begun) { func += F(".local"); }
  1165. if (static_cast<uint32_t>(WiFi.localIP()) != 0) {
  1166. func += F("}1" D_IP_ADDRESS "}2"); func += WiFi.localIP().toString();
  1167. func += F("}1" D_GATEWAY "}2"); func += IPAddress(Settings.ip_address[1]).toString();
  1168. func += F("}1" D_SUBNET_MASK "}2"); func += IPAddress(Settings.ip_address[2]).toString();
  1169. func += F("}1" D_DNS_SERVER "}2"); func += IPAddress(Settings.ip_address[3]).toString();
  1170. func += F("}1" D_MAC_ADDRESS "}2"); func += WiFi.macAddress();
  1171. }
  1172. if (static_cast<uint32_t>(WiFi.softAPIP()) != 0) {
  1173. func += F("}1" D_AP " " D_IP_ADDRESS "}2"); func += WiFi.softAPIP().toString();
  1174. func += F("}1" D_AP " " D_GATEWAY "}2"); func += WiFi.softAPIP().toString();
  1175. func += F("}1" D_AP " " D_MAC_ADDRESS "}2"); func += WiFi.softAPmacAddress();
  1176. }
  1177. func += F("}1}2&nbsp;"); // Empty line
  1178. if (Settings.flag.mqtt_enabled) {
  1179. func += F("}1" D_MQTT_HOST "}2"); func += Settings.mqtt_host;
  1180. func += F("}1" D_MQTT_PORT "}2"); func += String(Settings.mqtt_port);
  1181. func += F("}1" D_MQTT_USER "}2"); func += Settings.mqtt_user;
  1182. func += F("}1" D_MQTT_CLIENT "}2"); func += mqtt_client;
  1183. func += F("}1" D_MQTT_TOPIC "}2"); func += Settings.mqtt_topic;
  1184. func += F("}1" D_MQTT_GROUP_TOPIC "}2"); func += Settings.mqtt_grptopic;
  1185. func += F("}1" D_MQTT_FULL_TOPIC "}2"); func += GetTopic_P(stopic, CMND, mqtt_topic, "");
  1186. func += F("}1" D_MQTT " " D_FALLBACK_TOPIC "}2"); func += GetFallbackTopic_P(stopic, CMND, "");
  1187. } else {
  1188. func += F("}1" D_MQTT "}2" D_DISABLED);
  1189. }
  1190. func += F("}1}2&nbsp;"); // Empty line
  1191. func += F("}1" D_EMULATION "}2");
  1192. #ifdef USE_EMULATION
  1193. if (EMUL_WEMO == Settings.flag2.emulation) {
  1194. func += F(D_BELKIN_WEMO);
  1195. }
  1196. else if (EMUL_HUE == Settings.flag2.emulation) {
  1197. func += F(D_HUE_BRIDGE);
  1198. }
  1199. else {
  1200. func += F(D_NONE);
  1201. }
  1202. #else
  1203. func += F(D_DISABLED);
  1204. #endif // USE_EMULATION
  1205. func += F("}1" D_MDNS_DISCOVERY "}2");
  1206. #ifdef USE_DISCOVERY
  1207. func += F(D_ENABLED);
  1208. func += F("}1" D_MDNS_ADVERTISE "}2");
  1209. #ifdef WEBSERVER_ADVERTISE
  1210. func += F(D_WEB_SERVER);
  1211. #else
  1212. func += F(D_DISABLED);
  1213. #endif // WEBSERVER_ADVERTISE
  1214. #else
  1215. func += F(D_DISABLED);
  1216. #endif // USE_DISCOVERY
  1217. func += F("}1}2&nbsp;"); // Empty line
  1218. func += F("}1" D_ESP_CHIP_ID "}2"); func += String(ESP.getChipId());
  1219. snprintf_P(stopic, sizeof(stopic), PSTR("0x%06X"), ESP.getFlashChipId());
  1220. func += F("}1" D_FLASH_CHIP_ID "}2"); func += stopic;
  1221. func += F("}1" D_FLASH_CHIP_SIZE "}2"); func += String(ESP.getFlashChipRealSize() / 1024); func += F("kB");
  1222. func += F("}1" D_PROGRAM_FLASH_SIZE "}2"); func += String(ESP.getFlashChipSize() / 1024); func += F("kB");
  1223. func += F("}1" D_PROGRAM_SIZE "}2"); func += String(ESP.getSketchSize() / 1024); func += F("kB");
  1224. func += F("}1" D_FREE_PROGRAM_SPACE "}2"); func += String(ESP.getFreeSketchSpace() / 1024); func += F("kB");
  1225. func += F("}1" D_FREE_MEMORY "}2"); func += String(freeMem / 1024); func += F("kB");
  1226. func += F("</td></tr></table>");
  1227. func += FPSTR(HTTP_SCRIPT_INFO_END);
  1228. page.replace(F("</script>"), func);
  1229. page.replace(F("<body>"), F("<body onload='i()'>"));
  1230. // page += F("</fieldset>");
  1231. page += FPSTR(HTTP_BTN_MAIN);
  1232. ShowPage(page);
  1233. }
  1234. #endif // Not BE_MINIMAL
  1235. /*-------------------------------------------------------------------------------------------*/
  1236. void HandleUpgradeFirmware(void)
  1237. {
  1238. if (HttpUser()) { return; }
  1239. if (!WebAuthenticate()) { return WebServer->requestAuthentication(); }
  1240. AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_FIRMWARE_UPGRADE);
  1241. String page = FPSTR(HTTP_HEAD);
  1242. page.replace(F("{v}"), FPSTR(S_FIRMWARE_UPGRADE));
  1243. page += FPSTR(HTTP_HEAD_STYLE);
  1244. page += FPSTR(HTTP_FORM_UPG);
  1245. page.replace(F("{o1"), Settings.ota_url);
  1246. page += FPSTR(HTTP_FORM_RST_UPG);
  1247. page.replace(F("{r1"), F(D_UPGRADE));
  1248. page += FPSTR(HTTP_BTN_MAIN);
  1249. ShowPage(page);
  1250. upload_error = 0;
  1251. upload_file_type = UPL_TASMOTA;
  1252. }
  1253. void HandleUpgradeFirmwareStart(void)
  1254. {
  1255. if (HttpUser()) { return; }
  1256. if (!WebAuthenticate()) { return WebServer->requestAuthentication(); }
  1257. char svalue[100];
  1258. AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_UPGRADE_STARTED));
  1259. WifiConfigCounter();
  1260. char tmp[100];
  1261. WebGetArg("o", tmp, sizeof(tmp));
  1262. if (strlen(tmp)) {
  1263. snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_OTAURL " %s"), tmp);
  1264. ExecuteWebCommand(svalue, SRC_WEBGUI);
  1265. }
  1266. String page = FPSTR(HTTP_HEAD);
  1267. page.replace(F("{v}"), FPSTR(S_INFORMATION));
  1268. page += FPSTR(HTTP_HEAD_STYLE);
  1269. page += F("<div style='text-align:center;'><b>" D_UPGRADE_STARTED " ...</b></div>");
  1270. page += FPSTR(HTTP_MSG_RSTRT);
  1271. page += FPSTR(HTTP_BTN_MAIN);
  1272. page.replace(F("</script>"), FPSTR(HTTP_SCRIPT_RELOAD_OTA));
  1273. ShowPage(page);
  1274. snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_UPGRADE " 1"));
  1275. ExecuteWebCommand(svalue, SRC_WEBGUI);
  1276. }
  1277. void HandleUploadDone(void)
  1278. {
  1279. if (HttpUser()) { return; }
  1280. if (!WebAuthenticate()) { return WebServer->requestAuthentication(); }
  1281. AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_UPLOAD_DONE));
  1282. char error[100];
  1283. WifiConfigCounter();
  1284. restart_flag = 0;
  1285. MqttRetryCounter(0);
  1286. String page = FPSTR(HTTP_HEAD);
  1287. page.replace(F("{v}"), FPSTR(S_INFORMATION));
  1288. page += FPSTR(HTTP_HEAD_STYLE);
  1289. page += F("<div style='text-align:center;'><b>" D_UPLOAD " <font color='");
  1290. if (upload_error) {
  1291. page += F("red'>" D_FAILED "</font></b><br/><br/>");
  1292. switch (upload_error) {
  1293. case 1: strncpy_P(error, PSTR(D_UPLOAD_ERR_1), sizeof(error)); break;
  1294. case 2: strncpy_P(error, PSTR(D_UPLOAD_ERR_2), sizeof(error)); break;
  1295. case 3: strncpy_P(error, PSTR(D_UPLOAD_ERR_3), sizeof(error)); break;
  1296. case 4: strncpy_P(error, PSTR(D_UPLOAD_ERR_4), sizeof(error)); break;
  1297. case 5: strncpy_P(error, PSTR(D_UPLOAD_ERR_5), sizeof(error)); break;
  1298. case 6: strncpy_P(error, PSTR(D_UPLOAD_ERR_6), sizeof(error)); break;
  1299. case 7: strncpy_P(error, PSTR(D_UPLOAD_ERR_7), sizeof(error)); break;
  1300. case 8: strncpy_P(error, PSTR(D_UPLOAD_ERR_8), sizeof(error)); break;
  1301. case 9: strncpy_P(error, PSTR(D_UPLOAD_ERR_9), sizeof(error)); break;
  1302. #ifdef USE_RF_FLASH
  1303. case 10: strncpy_P(error, PSTR(D_UPLOAD_ERR_10), sizeof(error)); break;
  1304. case 11: strncpy_P(error, PSTR(D_UPLOAD_ERR_11), sizeof(error)); break;
  1305. case 12: strncpy_P(error, PSTR(D_UPLOAD_ERR_12), sizeof(error)); break;
  1306. case 13: strncpy_P(error, PSTR(D_UPLOAD_ERR_13), sizeof(error)); break;
  1307. #endif
  1308. default:
  1309. snprintf_P(error, sizeof(error), PSTR(D_UPLOAD_ERROR_CODE " %d"), upload_error);
  1310. }
  1311. page += error;
  1312. snprintf_P(log_data, sizeof(log_data), PSTR(D_UPLOAD ": %s"), error);
  1313. AddLog(LOG_LEVEL_DEBUG);
  1314. stop_flash_rotate = Settings.flag.stop_flash_rotate;
  1315. } else {
  1316. page += F("green'>" D_SUCCESSFUL "</font></b><br/>");
  1317. page += FPSTR(HTTP_MSG_RSTRT);
  1318. page.replace(F("</script>"), FPSTR(HTTP_SCRIPT_RELOAD_OTA)); // Refesh main web ui after OTA upgrade
  1319. ShowWebSource(SRC_WEBGUI);
  1320. restart_flag = 2; // Always restart to re-enable disabled features during update
  1321. }
  1322. SettingsBufferFree();
  1323. page += F("</div><br/>");
  1324. page += FPSTR(HTTP_BTN_MAIN);
  1325. ShowPage(page);
  1326. }
  1327. void HandleUploadLoop(void)
  1328. {
  1329. // Based on ESP8266HTTPUpdateServer.cpp uses ESP8266WebServer Parsing.cpp and Cores Updater.cpp (Update)
  1330. boolean _serialoutput = (LOG_LEVEL_DEBUG <= seriallog_level);
  1331. if (HTTP_USER == webserver_state) { return; }
  1332. if (upload_error) {
  1333. if (UPL_TASMOTA == upload_file_type) { Update.end(); }
  1334. return;
  1335. }
  1336. HTTPUpload& upload = WebServer->upload();
  1337. if (UPLOAD_FILE_START == upload.status) {
  1338. restart_flag = 60;
  1339. if (0 == upload.filename.c_str()[0]) {
  1340. upload_error = 1; // No file selected
  1341. return;
  1342. }
  1343. SettingsSave(1); // Free flash for upload
  1344. snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_UPLOAD D_FILE " %s ..."), upload.filename.c_str());
  1345. AddLog(LOG_LEVEL_INFO);
  1346. if (UPL_SETTINGS == upload_file_type) {
  1347. if (!SettingsBufferAlloc()) {
  1348. upload_error = 2; // Not enough space
  1349. return;
  1350. }
  1351. } else {
  1352. MqttRetryCounter(60);
  1353. #ifdef USE_EMULATION
  1354. UdpDisconnect();
  1355. #endif // USE_EMULATION
  1356. #ifdef USE_ARILUX_RF
  1357. AriluxRfDisable(); // Prevent restart exception on Arilux Interrupt routine
  1358. #endif // USE_ARILUX_RF
  1359. if (Settings.flag.mqtt_enabled) MqttDisconnect();
  1360. uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
  1361. if (!Update.begin(maxSketchSpace)) { //start with max available size
  1362. // if (_serialoutput) Update.printError(Serial);
  1363. // if (Update.getError() == UPDATE_ERROR_BOOTSTRAP) {
  1364. // if (_serialoutput) Serial.println("Device still in UART update mode, perform powercycle");
  1365. // }
  1366. upload_error = 2; // Not enough space
  1367. return;
  1368. }
  1369. }
  1370. upload_progress_dot_count = 0;
  1371. } else if (!upload_error && (UPLOAD_FILE_WRITE == upload.status)) {
  1372. if (0 == upload.totalSize) {
  1373. if (UPL_SETTINGS == upload_file_type) {
  1374. config_block_count = 0;
  1375. }
  1376. else {
  1377. #ifdef USE_RF_FLASH
  1378. if ((SONOFF_BRIDGE == Settings.module) && (upload.buf[0] == ':')) { // Check if this is a RF bridge FW file
  1379. Update.end(); // End esp8266 update session
  1380. upload_file_type = UPL_EFM8BB1;
  1381. upload_error = SnfBrUpdateInit();
  1382. if (upload_error != 0) { return; }
  1383. } else
  1384. #endif // USE_RF_FLASH
  1385. {
  1386. if (upload.buf[0] != 0xE9) {
  1387. upload_error = 3; // Magic byte is not 0xE9
  1388. return;
  1389. }
  1390. uint32_t bin_flash_size = ESP.magicFlashChipSize((upload.buf[3] & 0xf0) >> 4);
  1391. if(bin_flash_size > ESP.getFlashChipRealSize()) {
  1392. upload_error = 4; // Program flash size is larger than real flash size
  1393. return;
  1394. }
  1395. // upload.buf[2] = 3; // Force DOUT - ESP8285
  1396. }
  1397. }
  1398. }
  1399. if (UPL_SETTINGS == upload_file_type) {
  1400. if (!upload_error) {
  1401. if (upload.currentSize > (sizeof(Settings) - (config_block_count * HTTP_UPLOAD_BUFLEN))) {
  1402. upload_error = 9; // File too large
  1403. return;
  1404. }
  1405. memcpy(settings_buffer + (config_block_count * HTTP_UPLOAD_BUFLEN), upload.buf, upload.currentSize);
  1406. config_block_count++;
  1407. }
  1408. }
  1409. #ifdef USE_RF_FLASH
  1410. else if (UPL_EFM8BB1 == upload_file_type) {
  1411. if (efm8bb1_update != NULL) { // We have carry over data since last write, i. e. a start but not an end
  1412. ssize_t result = rf_glue_remnant_with_new_data_and_write(efm8bb1_update, upload.buf, upload.currentSize);
  1413. free(efm8bb1_update);
  1414. efm8bb1_update = NULL;
  1415. if (result != 0) {
  1416. upload_error = abs(result); // 2 = Not enough space, 8 = File invalid
  1417. return;
  1418. }
  1419. }
  1420. ssize_t result = rf_search_and_write(upload.buf, upload.currentSize);
  1421. if (result < 0) {
  1422. upload_error = abs(result);
  1423. return;
  1424. } else if (result > 0) {
  1425. if (result > upload.currentSize) {
  1426. // Offset is larger than the buffer supplied, this should not happen
  1427. upload_error = 9; // File too large - Failed to decode RF firmware
  1428. return;
  1429. }
  1430. // A remnant has been detected, allocate data for it plus a null termination byte
  1431. size_t remnant_sz = upload.currentSize - result;
  1432. efm8bb1_update = (uint8_t *) malloc(remnant_sz + 1);
  1433. if (efm8bb1_update == NULL) {
  1434. upload_error = 2; // Not enough space - Unable to allocate memory to store new RF firmware
  1435. return;
  1436. }
  1437. memcpy(efm8bb1_update, upload.buf + result, remnant_sz);
  1438. // Add null termination at the end of of remnant buffer
  1439. efm8bb1_update[remnant_sz] = '\0';
  1440. }
  1441. }
  1442. #endif // USE_RF_FLASH
  1443. else { // firmware
  1444. if (!upload_error && (Update.write(upload.buf, upload.currentSize) != upload.currentSize)) {
  1445. upload_error = 5; // Upload buffer miscompare
  1446. return;
  1447. }
  1448. if (_serialoutput) {
  1449. Serial.printf(".");
  1450. upload_progress_dot_count++;
  1451. if (!(upload_progress_dot_count % 80)) { Serial.println(); }
  1452. }
  1453. }
  1454. } else if(!upload_error && (UPLOAD_FILE_END == upload.status)) {
  1455. if (_serialoutput && (upload_progress_dot_count % 80)) {
  1456. Serial.println();
  1457. }
  1458. if (UPL_SETTINGS == upload_file_type) {
  1459. if (config_xor_on_set) {
  1460. for (uint16_t i = 2; i < sizeof(Settings); i++) {
  1461. settings_buffer[i] ^= (config_xor_on_set +i);
  1462. }
  1463. }
  1464. bool valid_settings = false;
  1465. unsigned long buffer_version = settings_buffer[11] << 24 | settings_buffer[10] << 16 | settings_buffer[9] << 8 | settings_buffer[8];
  1466. if (buffer_version > 0x06000000) {
  1467. uint16_t buffer_size = settings_buffer[3] << 8 | settings_buffer[2];
  1468. uint16_t buffer_crc = settings_buffer[15] << 8 | settings_buffer[14];
  1469. uint16_t crc = 0;
  1470. for (uint16_t i = 0; i < buffer_size; i++) {
  1471. if ((i < 14) || (i > 15)) { crc += settings_buffer[i]*(i+1); } // Skip crc
  1472. }
  1473. valid_settings = (buffer_crc == crc);
  1474. } else {
  1475. valid_settings = (settings_buffer[0] == CONFIG_FILE_SIGN);
  1476. }
  1477. if (valid_settings) {
  1478. SettingsDefaultSet2();
  1479. memcpy((char*)&Settings +16, settings_buffer +16, sizeof(Settings) -16);
  1480. Settings.version = buffer_version; // Restore version and auto upgrade after restart
  1481. SettingsBufferFree();
  1482. } else {
  1483. upload_error = 8; // File invalid
  1484. return;
  1485. }
  1486. }
  1487. #ifdef USE_RF_FLASH
  1488. else if (UPL_EFM8BB1 == upload_file_type) {
  1489. // RF FW flash done
  1490. upload_file_type = UPL_TASMOTA;
  1491. }
  1492. #endif // USE_RF_FLASH
  1493. else {
  1494. if (!Update.end(true)) { // true to set the size to the current progress
  1495. if (_serialoutput) { Update.printError(Serial); }
  1496. upload_error = 6; // Upload failed. Enable logging 3
  1497. return;
  1498. }
  1499. }
  1500. if (!upload_error) {
  1501. snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_UPLOAD D_SUCCESSFUL " %u bytes. " D_RESTARTING), upload.totalSize);
  1502. AddLog(LOG_LEVEL_INFO);
  1503. }
  1504. } else if (UPLOAD_FILE_ABORTED == upload.status) {
  1505. restart_flag = 0;
  1506. MqttRetryCounter(0);
  1507. upload_error = 7; // Upload aborted
  1508. if (UPL_TASMOTA == upload_file_type) { Update.end(); }
  1509. }
  1510. delay(0);
  1511. }
  1512. /*-------------------------------------------------------------------------------------------*/
  1513. void HandlePreflightRequest(void)
  1514. {
  1515. WebServer->sendHeader(F("Access-Control-Allow-Origin"), F("*"));
  1516. WebServer->sendHeader(F("Access-Control-Allow-Methods"), F("GET, POST"));
  1517. WebServer->sendHeader(F("Access-Control-Allow-Headers"), F("authorization"));
  1518. WebServer->send(200, FPSTR(HDR_CTYPE_HTML), "");
  1519. }
  1520. /*-------------------------------------------------------------------------------------------*/
  1521. void HandleHttpCommand(void)
  1522. {
  1523. if (HttpUser()) { return; }
  1524. // if (!WebAuthenticate()) { return WebServer->requestAuthentication(); }
  1525. char svalue[INPUT_BUFFER_SIZE]; // Large to serve Backlog
  1526. AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_COMMAND));
  1527. uint8_t valid = 1;
  1528. if (Settings.web_password[0] != 0) {
  1529. char tmp1[100];
  1530. WebGetArg("user", tmp1, sizeof(tmp1));
  1531. char tmp2[100];
  1532. WebGetArg("password", tmp2, sizeof(tmp2));
  1533. if (!(!strcmp(tmp1, WEB_USERNAME) && !strcmp(tmp2, Settings.web_password))) { valid = 0; }
  1534. }
  1535. String message = F("{\"" D_RSLT_WARNING "\":\"");
  1536. if (valid) {
  1537. byte curridx = web_log_index;
  1538. WebGetArg("cmnd", svalue, sizeof(svalue));
  1539. if (strlen(svalue)) {
  1540. ExecuteWebCommand(svalue, SRC_WEBCOMMAND);
  1541. if (web_log_index != curridx) {
  1542. byte counter = curridx;
  1543. message = F("{");
  1544. do {
  1545. char* tmp;
  1546. size_t len;
  1547. GetLog(counter, &tmp, &len);
  1548. if (len) {
  1549. // [14:49:36 MQTT: stat/wemos5/RESULT = {"POWER":"OFF"}] > [{"POWER":"OFF"}]
  1550. char* JSON = (char*)memchr(tmp, '{', len);
  1551. if (JSON) { // Is it a JSON message (and not only [15:26:08 MQT: stat/wemos5/POWER = O])
  1552. if (message.length() > 1) { message += F(","); }
  1553. size_t JSONlen = len - (JSON - tmp);
  1554. strlcpy(mqtt_data, JSON +1, JSONlen -2);
  1555. message += mqtt_data;
  1556. }
  1557. }
  1558. counter++;
  1559. if (!counter) counter++; // Skip 0 as it is not allowed
  1560. } while (counter != web_log_index);
  1561. message += F("}");
  1562. } else {
  1563. message += F(D_ENABLE_WEBLOG_FOR_RESPONSE "\"}");
  1564. }
  1565. } else {
  1566. message += F(D_ENTER_COMMAND " cmnd=\"}");
  1567. }
  1568. } else {
  1569. message += F(D_NEED_USER_AND_PASSWORD "\"}");
  1570. }
  1571. SetHeader();
  1572. WebServer->send(200, FPSTR(HDR_CTYPE_JSON), message);
  1573. }
  1574. /*-------------------------------------------------------------------------------------------*/
  1575. void HandleConsole(void)
  1576. {
  1577. if (HttpUser()) { return; }
  1578. if (!WebAuthenticate()) { return WebServer->requestAuthentication(); }
  1579. AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONSOLE);
  1580. String page = FPSTR(HTTP_HEAD);
  1581. page.replace(F("{v}"), FPSTR(S_CONSOLE));
  1582. page += FPSTR(HTTP_HEAD_STYLE);
  1583. page.replace(F("</script>"), FPSTR(HTTP_SCRIPT_CONSOL));
  1584. page.replace(F("<body>"), F("<body onload='l()'>"));
  1585. page += FPSTR(HTTP_FORM_CMND);
  1586. page += FPSTR(HTTP_BTN_MAIN);
  1587. ShowPage(page);
  1588. }
  1589. void HandleAjaxConsoleRefresh(void)
  1590. {
  1591. if (HttpUser()) { return; }
  1592. if (!WebAuthenticate()) { return WebServer->requestAuthentication(); }
  1593. char svalue[INPUT_BUFFER_SIZE]; // Large to serve Backlog
  1594. byte cflg = 1;
  1595. byte counter = 0; // Initial start, should never be 0 again
  1596. WebGetArg("c1", svalue, sizeof(svalue));
  1597. if (strlen(svalue)) {
  1598. snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_COMMAND "%s"), svalue);
  1599. AddLog(LOG_LEVEL_INFO);
  1600. ExecuteWebCommand(svalue, SRC_WEBCONSOLE);
  1601. }
  1602. WebGetArg("c2", svalue, sizeof(svalue));
  1603. if (strlen(svalue)) { counter = atoi(svalue); }
  1604. byte last_reset_web_log_flag = reset_web_log_flag;
  1605. String message = F("}9"); // Cannot load mqtt_data here as <> will be encoded by replacements below
  1606. if (!reset_web_log_flag) {
  1607. counter = 0;
  1608. reset_web_log_flag = 1;
  1609. }
  1610. if (counter != web_log_index) {
  1611. if (!counter) {
  1612. counter = web_log_index;
  1613. cflg = 0;
  1614. }
  1615. do {
  1616. char* tmp;
  1617. size_t len;
  1618. GetLog(counter, &tmp, &len);
  1619. if (len) {
  1620. if (cflg) {
  1621. message += F("\n");
  1622. } else {
  1623. cflg = 1;
  1624. }
  1625. strlcpy(mqtt_data, tmp, len);
  1626. message += mqtt_data;
  1627. }
  1628. counter++;
  1629. if (!counter) { counter++; } // Skip 0 as it is not allowed
  1630. } while (counter != web_log_index);
  1631. // XML encoding to fix blank console log in concert with javascript decodeURIComponent
  1632. message.replace(F("%"), F("%25")); // Needs to be done first as otherwise the % in %26 will also be converted
  1633. message.replace(F("&"), F("%26"));
  1634. message.replace(F("<"), F("%3C"));
  1635. message.replace(F(">"), F("%3E"));
  1636. }
  1637. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("<r><i>%d</i><j>%d</j><l>"), web_log_index, last_reset_web_log_flag);
  1638. message.replace(F("}9"), mqtt_data); // Save to load here
  1639. message += F("</l></r>");
  1640. WebServer->send(200, FPSTR(HDR_CTYPE_XML), message);
  1641. }
  1642. /********************************************************************************************/
  1643. void HandleNotFound(void)
  1644. {
  1645. // snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_HTTP "Not fount (%s)"), WebServer->uri().c_str());
  1646. // AddLog(LOG_LEVEL_DEBUG);
  1647. if (CaptivePortal()) { return; } // If captive portal redirect instead of displaying the error page.
  1648. #ifdef USE_EMULATION
  1649. String path = WebServer->uri();
  1650. if ((EMUL_HUE == Settings.flag2.emulation) && (path.startsWith("/api"))) {
  1651. HandleHueApi(&path);
  1652. } else
  1653. #endif // USE_EMULATION
  1654. {
  1655. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR(D_FILE_NOT_FOUND "\n\nURI: %s\nMethod: %s\nArguments: %d\n"),
  1656. WebServer->uri().c_str(), (WebServer->method() == HTTP_GET) ? "GET" : "POST", WebServer->args());
  1657. for (uint8_t i = 0; i < WebServer->args(); i++) {
  1658. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s %s: %s\n"), mqtt_data, WebServer->argName(i).c_str(), WebServer->arg(i).c_str());
  1659. }
  1660. SetHeader();
  1661. WebServer->send(404, FPSTR(HDR_CTYPE_PLAIN), mqtt_data);
  1662. }
  1663. }
  1664. /* 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. */
  1665. boolean CaptivePortal(void)
  1666. {
  1667. if ((HTTP_MANAGER == webserver_state) && !ValidIpAddress(WebServer->hostHeader())) {
  1668. AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_REDIRECTED));
  1669. WebServer->sendHeader(F("Location"), String("http://") + WebServer->client().localIP().toString(), true);
  1670. WebServer->send(302, FPSTR(HDR_CTYPE_PLAIN), ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
  1671. WebServer->client().stop(); // Stop is needed because we sent no content length
  1672. return true;
  1673. }
  1674. return false;
  1675. }
  1676. /** Is this an IP? */
  1677. boolean ValidIpAddress(String str)
  1678. {
  1679. for (uint16_t i = 0; i < str.length(); i++) {
  1680. int c = str.charAt(i);
  1681. if (c != '.' && (c < '0' || c > '9')) { return false; }
  1682. }
  1683. return true;
  1684. }
  1685. /*********************************************************************************************/
  1686. String UrlEncode(const String& text)
  1687. {
  1688. const char hex[] = "0123456789ABCDEF";
  1689. String encoded = "";
  1690. int len = text.length();
  1691. int i = 0;
  1692. while (i < len) {
  1693. char decodedChar = text.charAt(i++);
  1694. /*
  1695. if (('a' <= decodedChar && decodedChar <= 'z') ||
  1696. ('A' <= decodedChar && decodedChar <= 'Z') ||
  1697. ('0' <= decodedChar && decodedChar <= '9') ||
  1698. ('=' == decodedChar)) {
  1699. encoded += decodedChar;
  1700. } else {
  1701. encoded += '%';
  1702. encoded += hex[decodedChar >> 4];
  1703. encoded += hex[decodedChar & 0xF];
  1704. }
  1705. */
  1706. if (' ' == decodedChar) {
  1707. encoded += '%';
  1708. encoded += hex[decodedChar >> 4];
  1709. encoded += hex[decodedChar & 0xF];
  1710. } else {
  1711. encoded += decodedChar;
  1712. }
  1713. }
  1714. return encoded;
  1715. }
  1716. int WebSend(char *buffer)
  1717. {
  1718. /* [sonoff] POWER1 ON --> Sends http://sonoff/cm?cmnd=POWER1 ON
  1719. * [192.168.178.86:80,admin:joker] POWER1 ON --> Sends http://hostname:80/cm?user=admin&password=joker&cmnd=POWER1 ON
  1720. * [sonoff] /any/link/starting/with/a/slash.php?log=123 --> Sends http://sonoff/any/link/starting/with/a/slash.php?log=123
  1721. * [sonoff,admin:joker] /any/link/starting/with/a/slash.php?log=123 --> Sends http://sonoff/any/link/starting/with/a/slash.php?log=123
  1722. */
  1723. char *host;
  1724. char *port;
  1725. char *user;
  1726. char *password;
  1727. char *command;
  1728. uint16_t nport = 80;
  1729. int status = 1; // Wrong parameters
  1730. // buffer = | [ 192.168.178.86 : 80 , admin : joker ] POWER1 ON |
  1731. host = strtok_r(buffer, "]", &command); // host = | [ 192.168.178.86 : 80 , admin : joker |, command = | POWER1 ON |
  1732. if (host && command) {
  1733. host = Trim(host); // host = |[ 192.168.178.86 : 80 , admin : joker|
  1734. host++; // host = | 192.168.178.86 : 80 , admin : joker| - Skip [
  1735. host = strtok_r(host, ",", &user); // host = | 192.168.178.86 : 80 |, user = | admin : joker|
  1736. host = strtok_r(host, ":", &port); // host = | 192.168.178.86 |, port = | 80 |
  1737. host = Trim(host); // host = |192.168.178.86|
  1738. if (port) {
  1739. port = Trim(port); // port = |80|
  1740. nport = atoi(port);
  1741. }
  1742. if (user) {
  1743. user = strtok_r(user, ":", &password); // user = | admin |, password = | joker|
  1744. user = Trim(user); // user = |admin|
  1745. if (password) { password = Trim(password); } // password = |joker|
  1746. }
  1747. command = Trim(command); // command = |POWER1 ON| or |/any/link/starting/with/a/slash.php?log=123|
  1748. String nuri = "";
  1749. if (command[0] != '/') {
  1750. nuri = "/cm?";
  1751. if (user && password) {
  1752. nuri += F("user=");
  1753. nuri += user;
  1754. nuri += F("&password=");
  1755. nuri += password;
  1756. nuri += F("&");
  1757. }
  1758. nuri += F("cmnd=");
  1759. }
  1760. nuri += command;
  1761. String uri = UrlEncode(nuri);
  1762. IPAddress host_ip;
  1763. if (WiFi.hostByName(host, host_ip)) {
  1764. WiFiClient client;
  1765. bool connected = false;
  1766. byte retry = 2;
  1767. while ((retry > 0) && !connected) {
  1768. --retry;
  1769. connected = client.connect(host_ip, nport);
  1770. if (connected) break;
  1771. }
  1772. if (connected) {
  1773. String url = F("GET ");
  1774. url += uri;
  1775. url += F(" HTTP/1.1\r\nHost: ");
  1776. // url += IPAddress(host_ip).toString();
  1777. url += host; // https://tools.ietf.org/html/rfc7230#section-5.4 (#4331)
  1778. if (port) {
  1779. url += F(":");
  1780. url += port;
  1781. }
  1782. url += F("\r\nConnection: close\r\n\r\n");
  1783. //snprintf_P(log_data, sizeof(log_data), PSTR("DBG: Url |%s|"), url.c_str());
  1784. //AddLog(LOG_LEVEL_DEBUG);
  1785. client.print(url.c_str());
  1786. client.flush();
  1787. client.stop();
  1788. status = 0; // No error - Done
  1789. } else {
  1790. status = 2; // Connection failed
  1791. }
  1792. } else {
  1793. status = 3; // Host not found
  1794. }
  1795. }
  1796. return status;
  1797. }
  1798. /*********************************************************************************************/
  1799. enum WebCommands { CMND_WEBSERVER, CMND_WEBPASSWORD, CMND_WEBLOG, CMND_WEBREFRESH, CMND_WEBSEND, CMND_EMULATION };
  1800. const char kWebCommands[] PROGMEM = D_CMND_WEBSERVER "|" D_CMND_WEBPASSWORD "|" D_CMND_WEBLOG "|" D_CMND_WEBREFRESH "|" D_CMND_WEBSEND "|" D_CMND_EMULATION ;
  1801. const char kWebSendStatus[] PROGMEM = D_JSON_DONE "|" D_JSON_WRONG_PARAMETERS "|" D_JSON_CONNECT_FAILED "|" D_JSON_HOST_NOT_FOUND ;
  1802. bool WebCommand(void)
  1803. {
  1804. char command[CMDSZ];
  1805. bool serviced = true;
  1806. int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic, kWebCommands);
  1807. if (-1 == command_code) {
  1808. serviced = false; // Unknown command
  1809. }
  1810. if (CMND_WEBSERVER == command_code) {
  1811. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) { Settings.webserver = XdrvMailbox.payload; }
  1812. if (Settings.webserver) {
  1813. 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\"}"),
  1814. (2 == Settings.webserver) ? D_ADMIN : D_USER, my_hostname, WiFi.localIP().toString().c_str());
  1815. } else {
  1816. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, GetStateText(0));
  1817. }
  1818. }
  1819. else if (CMND_WEBPASSWORD == command_code) {
  1820. if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.web_password))) {
  1821. strlcpy(Settings.web_password, (SC_CLEAR == Shortcut(XdrvMailbox.data)) ? "" : (SC_DEFAULT == Shortcut(XdrvMailbox.data)) ? WEB_PASSWORD : XdrvMailbox.data, sizeof(Settings.web_password));
  1822. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.web_password);
  1823. } else {
  1824. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_ASTERIX, command);
  1825. }
  1826. }
  1827. else if (CMND_WEBLOG == command_code) {
  1828. if ((XdrvMailbox.payload >= LOG_LEVEL_NONE) && (XdrvMailbox.payload <= LOG_LEVEL_ALL)) { Settings.weblog_level = XdrvMailbox.payload; }
  1829. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.weblog_level);
  1830. }
  1831. else if (CMND_WEBREFRESH == command_code) {
  1832. if ((XdrvMailbox.payload > 999) && (XdrvMailbox.payload <= 10000)) { Settings.web_refresh = XdrvMailbox.payload; }
  1833. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.web_refresh);
  1834. }
  1835. else if (CMND_WEBSEND == command_code) {
  1836. if (XdrvMailbox.data_len > 0) {
  1837. uint8_t result = WebSend(XdrvMailbox.data);
  1838. char stemp1[20];
  1839. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, GetTextIndexed(stemp1, sizeof(stemp1), result, kWebSendStatus));
  1840. }
  1841. }
  1842. #ifdef USE_EMULATION
  1843. else if (CMND_EMULATION == command_code) {
  1844. if ((XdrvMailbox.payload >= EMUL_NONE) && (XdrvMailbox.payload < EMUL_MAX)) {
  1845. Settings.flag2.emulation = XdrvMailbox.payload;
  1846. restart_flag = 2;
  1847. }
  1848. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.flag2.emulation);
  1849. }
  1850. #endif // USE_EMULATION
  1851. else serviced = false; // Unknown command
  1852. return serviced;
  1853. }
  1854. /*********************************************************************************************\
  1855. * Interface
  1856. \*********************************************************************************************/
  1857. boolean Xdrv01(byte function)
  1858. {
  1859. boolean result = false;
  1860. switch (function) {
  1861. case FUNC_LOOP:
  1862. PollDnsWebserver();
  1863. #ifdef USE_EMULATION
  1864. if (Settings.flag2.emulation) PollUdp();
  1865. #endif // USE_EMULATION
  1866. break;
  1867. case FUNC_COMMAND:
  1868. result = WebCommand();
  1869. break;
  1870. }
  1871. return result;
  1872. }
  1873. #endif // USE_WEBSERVER